fixes
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 370 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 373 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 312 KiB After Width: | Height: | Size: 601 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 519 KiB |
@@ -4,7 +4,7 @@ export default function ResultsLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<main className="flex-1 md:max-lg:pt-[120px] pt-[100px] md:px-4 lg:px-[1.389vw] px-[10px] overflow-clip relative">
|
||||
<main className="flex-1 md:max-lg:pt-[120px] pt-[100px] md:px-4 lg:px-[1.389vw] px-[10px] relative">
|
||||
{children}
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from "react";
|
||||
const ReadIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="m1.177 10.862 3.283 3.064 8.618-8.044m5.746.192-8.619 8.044-1.026-.958"
|
||||
stroke="#fff"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default ReadIcon;
|
||||
@@ -40,14 +40,14 @@ export function Map() {
|
||||
<Slider mapPoint={mapPoint} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:hidden absolute left-[50vw] bottom-[4.444vw] translate-y-1/2 -translate-x-1/2 md:w-[42.161vw] w-[68.611vw] aspect-[247/56] rounded-[4.444vw] bg-[#37393B99] px-[4.167vw] py-[4.444vw] flex gap-[2.222vw] justify-between items-center">
|
||||
{/* <div className="lg:hidden absolute left-[50vw] bottom-[4.444vw] translate-y-1/2 -translate-x-1/2 md:w-[42.161vw] w-[68.611vw] aspect-[247/56] rounded-[4.444vw] bg-[#37393B99] px-[4.167vw] py-[4.444vw] flex gap-[2.222vw] justify-between items-center">
|
||||
<div className="text-white lg:size-[6.667vw] size-6 flex-shrink-0">
|
||||
<FingerPrintIcon />
|
||||
</div>
|
||||
<p className="caption leading-[120%] font-medium select-none">
|
||||
Нажмите и удерживайте, чтобы посмотреть всех партнеров в регионе
|
||||
</p>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ export default function ResultsAchievements() {
|
||||
}}
|
||||
className="flex justify-between h-[100vh] relative items-end -mx-[1.389vw]"
|
||||
>
|
||||
<h2 className="line2 !font-medium absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center z-[2] bg-[radial-gradient(rgba(0,0,0,0.2)_20%,rgba(0,0,0,0)_70%)]">
|
||||
<h2 className="line2 !font-medium absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center z-[2] bg-[radial-gradient(rgba(0,0,0,0.65)_0%,rgba(0,0,0,0)_70%)]">
|
||||
Кто хочет стать <br /> следующим?
|
||||
</h2>
|
||||
<div className="h-full flex gap-[1.111vw] min-h-0">
|
||||
@@ -219,8 +219,8 @@ export default function ResultsAchievements() {
|
||||
</h2>
|
||||
<div className="p-[2.222vw] bg-[#FFFFFF1A] backdrop-blur-[24px] border-[2px] border-[#FFFFFF1A] rounded-[1.667vw] w-[42.431vw]">
|
||||
<p className="headline2 mb-[1.667vw] !font-medium">
|
||||
Научили AI-модель выполнять некоторые <br /> функции мнеджера даже
|
||||
лучше человека
|
||||
Научили AI-модель выполнять полезные
|
||||
<br /> функции в помощь человеку
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-x-[0.556vw] gap-y-[0.556vw] w-[100%] !font-medium">
|
||||
<span className="btnm bg-[#FFFFFF29] py-[0.694vw] px-[1.389vw] rounded-full">
|
||||
|
||||
@@ -5,7 +5,7 @@ import ResultsAchievements from "./ResultsAchievements";
|
||||
import QuestionFormModal from "@/components/modals/QuestionFormModal";
|
||||
import ResultsTeamWheel from "../ResultsTeamWheel";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import ResutsCongratulations from "./ResutsCongratulations";
|
||||
import ResultsCongratulations from "./ResutsCongratulations";
|
||||
|
||||
export default function ResultsEventsReelsWithAchievements() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -32,11 +32,17 @@ export default function ResultsEventsReelsWithAchievements() {
|
||||
</div>
|
||||
|
||||
{/* Team */}
|
||||
<div className="flex flex-col justify-between h-[100vh]">
|
||||
<div className="flex flex-col justify-between h-[100vh] relative">
|
||||
<h2 className="line2 !font-medium mb-[1.944vw] mt-[10vw] z-[10] text-center">
|
||||
Наш штат расширился на 30% <br /> Теперь в команде более 70 человек
|
||||
</h2>
|
||||
<div className="w-[66.806vw] h-[33.3vw] overflow-hidden mx-auto relative">
|
||||
<div
|
||||
style={{
|
||||
maskImage:
|
||||
"linear-gradient(to bottom, transparent 0%, black 10%, black 90%, transparent 100%)",
|
||||
}}
|
||||
className="w-[66.806vw] h-[33.3vw] overflow-hidden mx-auto relative"
|
||||
>
|
||||
<ResultsTeamWheel />
|
||||
<div className="absolute left-[2.5vw] right-[2.5vw] top-[2.5vw] bottom-[-2.5vw] w-[calc(100%-5vw)] h-full rounded-t-full backdrop-blur-[24px]"></div>
|
||||
</div>
|
||||
@@ -51,6 +57,8 @@ export default function ResultsEventsReelsWithAchievements() {
|
||||
Оставить заявку
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ResultsCongratulations />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -25,15 +25,15 @@ export default function ResultsGeneral() {
|
||||
transform: useTransform(
|
||||
discreteScroll,
|
||||
[0.6, 0.7],
|
||||
["translate(0%, 0%)", "translate(0%, -6.944vw)"]
|
||||
["translate(0%, 0%)", "translate(0%, -8.944vw)"]
|
||||
),
|
||||
}}
|
||||
className="text-center space-y-[1.528vw] absolute top-[16.056vw] z-10"
|
||||
className="text-center space-y-[1.528vw] absolute top-[18.056vw] z-10"
|
||||
>
|
||||
<span className="accent">В 2025</span>
|
||||
|
||||
<ShiftText
|
||||
className="absolute left-1/2 font-medium line2 text-center text-[4.444vw] h-[8.403vw] z-10"
|
||||
className="absolute left-1/2 font-medium line2 text-center text-[4.444vw] h-[8.403vw] !z-[10]"
|
||||
style={{ x: "-50%" }}
|
||||
paragraphs={[
|
||||
<>
|
||||
@@ -270,7 +270,7 @@ function Appartaments({
|
||||
<div className="size-[0.903vw] rotate-180">
|
||||
<PlayIcon />
|
||||
</div>
|
||||
<div className="w-[9.722vw] h-[3.611vw] rounded-[1.111vw] bg-white flex items-center gap-[0.556vw] p-[0.139vw] pr-[1.111vw]">
|
||||
<div className="w-[9.722vw] h-[3.611vw] rounded-[1.111vw] bg-white flex items-center gap-[0.556vw] p-[0.139vw] pl-[0.2vw] pr-[1.111vw]">
|
||||
<div className="size-[3.264vw] rounded-[0.903vw] bg-[#7A55FF] flex items-center justify-center btnm !text-[1.181vw]">
|
||||
{floor}
|
||||
</div>
|
||||
@@ -302,9 +302,9 @@ function Appartaments({
|
||||
]
|
||||
),
|
||||
}}
|
||||
src="/img/pages/results/general/building.png"
|
||||
src="/img/pages/results/general/building_lg.png"
|
||||
alt=""
|
||||
className="absolute top-[6.167vw] left-1/2 w-[34.306vw] h-full object-cover"
|
||||
className="absolute top-[6.167vw] left-1/2 h-full object-cover"
|
||||
/>
|
||||
<motion.div
|
||||
style={{
|
||||
@@ -324,12 +324,12 @@ function Appartaments({
|
||||
]
|
||||
),
|
||||
}}
|
||||
className="absolute bottom-[70px] left-[67.917vw] flex flex-col gap-[0.556vw]"
|
||||
className="absolute bottom-[20px] left-[68.917vw] flex flex-col gap-[0.556vw]"
|
||||
>
|
||||
<FloorIndicator
|
||||
floor={32}
|
||||
appartamentsCount={8}
|
||||
className="mb-[17.083vw]"
|
||||
className="mb-[16.583vw]"
|
||||
/>
|
||||
<FloorIndicator floor={25} appartamentsCount={12} />
|
||||
<FloorIndicator floor={24} appartamentsCount={11} />
|
||||
@@ -437,7 +437,7 @@ function Reservations({
|
||||
}}
|
||||
src="/img/pages/results/general/booking.png"
|
||||
alt=""
|
||||
className="absolute bottom-0 left-1/2 w-[27.778vw] h-[23.1vw] object-cover z-[10]"
|
||||
className="absolute bottom-0 left-1/2 w-[27.778vw] h-[23.1vw] object-cover z-[3]"
|
||||
/>
|
||||
<motion.img
|
||||
style={{
|
||||
@@ -459,7 +459,7 @@ function Reservations({
|
||||
}}
|
||||
src="/img/pages/results/general/booking.png"
|
||||
alt=""
|
||||
className="absolute bottom-0 left-1/2 w-[27.778vw] h-[23.1vw] object-cover z-[9]"
|
||||
className="absolute bottom-0 left-1/2 w-[27.778vw] h-[23.1vw] object-cover z-[2]"
|
||||
/>
|
||||
<motion.img
|
||||
style={{
|
||||
@@ -481,7 +481,7 @@ function Reservations({
|
||||
}}
|
||||
src="/img/pages/results/general/booking.png"
|
||||
alt=""
|
||||
className="absolute bottom-0 left-1/2 w-[27.778vw] h-[23.1vw] object-cover z-[8]"
|
||||
className="absolute bottom-0 left-1/2 w-[27.778vw] h-[23.1vw] object-cover z-[1]"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -529,9 +529,9 @@ function ReservationsTotal({
|
||||
className="bottom-[4.861vw] left-[20.833vw] "
|
||||
/>
|
||||
<BookingCard
|
||||
src="/img/pages/results/general/booking_3.png"
|
||||
className="bottom-[4.861vw] right-[20.833vw] "
|
||||
imageClassName="translate-x-[2vw] h-[18.222vw] translate-y-[1vw]"
|
||||
src="/img/pages/results/general/booking_3_alt.png"
|
||||
className="bottom-[4.861vw] right-[20.833vw] overflow-hidden"
|
||||
imageClassName="translate-x-[2vw] h-[24.444vw] translate-y-[1vw] ml-[5vw]"
|
||||
/>
|
||||
<BookingCard
|
||||
src="/img/pages/results/general/booking_2.png"
|
||||
|
||||
@@ -1,78 +1,149 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import ReadIcon from "@/components/icons/ReadIcon";
|
||||
import React from "react";
|
||||
import congratulationsData from "@/consts/congratulations.json";
|
||||
import { VideoPlayer } from "@/ui/VideoPlayer";
|
||||
import { motion, useInView } from "framer-motion";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ReviewTab } from "../../MainPage/Reviews/ReviewTab";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
|
||||
export default function ResutsCongratulations() {
|
||||
const [tab, setTab] = useState(0);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (videoRef.current) videoRef.current!.currentTime = tab === 0 ? 6 : 5;
|
||||
}, [tab]);
|
||||
|
||||
const inView = useInView(ref, { margin: "100% 0px -75% 0px" });
|
||||
|
||||
return (
|
||||
<div className="max-lg:space-y-2 h-[100vh] w-full">
|
||||
<div
|
||||
ref={ref}
|
||||
className={`relative mx-auto lg:mt-[9.722vw] md:max-lg:mt-[13.021vw] mt-[100px] transition-all duration-500 md:max-lg:w-[95.833vw] md:max-lg:h-[62.5vw] w-[94.444vw] h-[154.722vw] ${
|
||||
inView
|
||||
? "lg:w-[97.222vw] lg:h-[42.778vw]"
|
||||
: "lg:w-[72.222vw] lg:h-[37.5vw]"
|
||||
}`}
|
||||
>
|
||||
<VideoPlayer
|
||||
src={congratulationsData[tab].src}
|
||||
showMutingBtn
|
||||
ref={videoRef}
|
||||
>
|
||||
<div className="lg:space-y-6 space-y-4 absolute lg:left-6 lg:top-6 md:max-lg:top-4 left-4 bottom-4 transition-opacity z-[5] lg:max-w-[40vw]">
|
||||
<p className="text2 opacity-80">
|
||||
{congratulationsData[tab].author}
|
||||
</p>
|
||||
<p className="accent font-medium">
|
||||
{congratulationsData[tab].text}
|
||||
</p>
|
||||
</div>
|
||||
{inView && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="absolute flex flex-nowrap bottom-6 left-6 gap-2 z-[6] max-lg:hidden"
|
||||
>
|
||||
{congratulationsData.map(
|
||||
({ author, title, miniImage }, index) => (
|
||||
<ReviewTab
|
||||
key={author}
|
||||
image={miniImage}
|
||||
onClick={() => setTab(index)}
|
||||
currentPlaying={tab === index}
|
||||
title={title}
|
||||
<div className="w-full lg:pt-[15.556vw] lg:pb-[7.5vw] md:pb-[9.505vw] pb-[20.278vw]">
|
||||
<div className="flex flex-col lg:gap-[5.556vw] md:gap-[5.556vw] gap-[0px] relative z-[10] items-center ">
|
||||
<div className="flex lg:flex-row flex-col lg:gap-[2.778vw] md:gap-[6.25vw] gap-[6.111vw] items-end max-lg:w-full max-lg:items-center max-lg:justify-center max-lg:md:px-[13.151vw] max-md:px-[6.667vw] max-lg:min-h-screen">
|
||||
<MessageSender
|
||||
name="Георгий Уморин"
|
||||
avatar="/img/pages/results/components/wheel-avatars/5.png"
|
||||
subtitle="генеральный директор и основатель"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</VideoPlayer>
|
||||
<MessageBubble time="13:07">
|
||||
<p>
|
||||
Для нас каждый проект — это не просто ещё одна реализация, а
|
||||
совместная работа с девелопером над конкретной бизнес-задачей. Мы
|
||||
всегда смотрим на решения в долгую: как они будут работать через
|
||||
год, два, пять, и как впишутся в реальные процессы отдела продаж.
|
||||
</p>
|
||||
<p>
|
||||
GRAFF.estate строится как компания-партнёр, а не как подрядчик «на
|
||||
один проект». Именно поэтому мы внимательно относимся к деталям,
|
||||
срокам и обязательствам и мы рады работать с проектами, где можем
|
||||
принести реальную пользу.
|
||||
</p>
|
||||
</MessageBubble>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 z-[3] lg:hidden mt-3 max-lg:-mx-5 max-lg:px-5 overflow-auto [scrollbar-width:none] [-webkit-scrollbar-width:none] snap-x snap-mandatory scroll-pl-6">
|
||||
{congratulationsData.map(({ author, title, miniImage }, index) => (
|
||||
<ReviewTab
|
||||
key={author}
|
||||
currentPlaying={tab === index}
|
||||
image={miniImage}
|
||||
onClick={() => setTab(index)}
|
||||
title={title}
|
||||
<div className="flex lg:flex-row-reverse flex-col lg:gap-[2.778vw] md:gap-[6.25vw] gap-[6.111vw] items-end max-lg:w-full max-lg:items-center max-lg:justify-center max-lg:md:px-[13.151vw] max-md:px-[6.667vw]">
|
||||
<MessageSender
|
||||
name="Вячеслав Зернов"
|
||||
avatar="/img/pages/results/components/wheel-avatars/4.png"
|
||||
subtitle="директор по продукту"
|
||||
/>
|
||||
))}
|
||||
<MessageBubble position="right" time="13:08">
|
||||
<p>
|
||||
За этот год над проектами вместе с нами работала команда из более
|
||||
чем 70 сильных специалистов. Мы относимся к продуктам наших
|
||||
клиентов бережно и внимательно, потому что понимаем, какую роль
|
||||
они играют в бизнесе и продажах.
|
||||
</p>
|
||||
<p>
|
||||
Опираясь на этот опыт, мы развиваем и собственный продукт —
|
||||
цифровой инструмент продаж GRAFF.estate. Он растёт вместе с
|
||||
проектами, которые мы делаем, и с задачами, которые вы перед нами
|
||||
ставите.
|
||||
</p>
|
||||
<p>
|
||||
Спасибо тем, кто уже доверил нам свои проекты. Мы ценим это
|
||||
доверие и ответственность, которую оно за собой несёт. И, конечно,
|
||||
будем рады новым задачам и новым партнёрствам в следующем году.
|
||||
</p>
|
||||
<strong>До встречи в новом году.</strong>
|
||||
</MessageBubble>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MessageBubble({
|
||||
children,
|
||||
position = "left",
|
||||
time = "12:00",
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
position?: "left" | "right";
|
||||
time?: string;
|
||||
}) {
|
||||
const { isMd, isLg } = useMediaQueries();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bg-[#FFFFFF1A] ${
|
||||
position === "left" && isLg
|
||||
? "lg:rounded-bl-none md:rounded-bl-none rounded-bl-none"
|
||||
: "lg:rounded-br-none md:rounded-br-none rounded-br-none"
|
||||
} backdrop-blur-[24px] text-white lg:rounded-[1.111vw] md:rounded-[2.083vw] rounded-[4.444vw] lg:w-[50.556vw] w-full lg:p-[1.944vw] md:p-[3.646vw] p-[5.556vw] lg:pb-[3.056vw] md:pb-[5.729vw] pb-[10.556vw] flex flex-col lg:gap-[1.111vw] md:gap-[2.083vw] gap-[3.333vw] lg:text-[1.389vw] text-[3.333vw] leading-[135%] tracking-[0] relative`}
|
||||
>
|
||||
{children}
|
||||
|
||||
{/* treugolnik */}
|
||||
<div
|
||||
className={`absolute bottom-0 ${
|
||||
position === "left" && isLg
|
||||
? "lg:left-[-1.111vw] md:left-[-2.083vw] left-[-1.563vw]"
|
||||
: "lg:right-[-1.111vw] md:right-[-2.083vw] right-[-1.563vw]"
|
||||
}
|
||||
lg:size-[1.111vw] md:size-[2.083vw] size-[1.563vw] bg-[#FFFFFF1A] backdrop-blur-[24px]`}
|
||||
style={{
|
||||
maskImage: `radial-gradient(circle at ${
|
||||
position === "left" && isLg ? "top left" : "top right"
|
||||
}, transparent ${
|
||||
isLg ? "1.111vw" : isMd ? "2.083vw" : "1.111vw"
|
||||
}, black ${isLg ? "1.111vw" : isMd ? "2.083vw" : "1.111vw"} )`,
|
||||
WebkitMaskImage: `radial-gradient(circle at ${
|
||||
position === "left" && isLg ? "top left" : "top right"
|
||||
}, transparent ${
|
||||
isLg ? "1.111vw" : isMd ? "2.083vw" : "1.111vw"
|
||||
}, black ${isLg ? "1.111vw" : isMd ? "2.083vw" : "1.111vw"} )`,
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`flex items-center lg:gap-[0.556vw] md:gap-[1.042vw] gap-[1.563vw] absolute lg:bottom-[0.556vw] md:bottom-[1.042vw] bottom-[2.222vw] ${
|
||||
position === "left" && isLg
|
||||
? "lg:left-[1.944vw] md:left-[3.646vw] left-[5.556vw]"
|
||||
: "lg:right-[1.944vw] md:right-[3.646vw] right-[5.556vw]"
|
||||
}`}
|
||||
>
|
||||
<p className="lg:text-[0.972vw] md:text-[1.823vw] text-[3.333vw] leading-[135%] tracking-[0]">
|
||||
{time}
|
||||
</p>
|
||||
<div className="text-white lg:size-[1.389vw] md:size-[2.604vw] size-[4.444vw]">
|
||||
<ReadIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MessageSender({
|
||||
name,
|
||||
avatar,
|
||||
subtitle,
|
||||
}: {
|
||||
name: string;
|
||||
avatar: string;
|
||||
subtitle: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col items-center lg:gap-[0.833vw] md:gap-[1.563vw] gap-[3.333vw]">
|
||||
<div className="text-white lg:size-[16.667vw] md:size-[31.25vw] size-[35.556vw]">
|
||||
<img className="size-full" src={avatar} alt={name} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center">
|
||||
<p className="!font-medium lg:text-[1.667vw] md:text-[3.125vw] text-[4.444vw] leading-[135%] tracking-[0] headline-1">
|
||||
{name}
|
||||
</p>
|
||||
<p className="lg:text-[0.972vw] md:text-[1.823vw] text-[2.778vw] leading-[135%] tracking-[0] text-1 opacity-60">
|
||||
{subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
+2
-2
@@ -42,7 +42,7 @@ export default function CardsSwiper({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`relative md:aspect-[736/650] aspect-[320/360] [transform-style:preserve-3d] md:px-[20.24vw] px-[15vw] my-auto ${className}`}
|
||||
className={`relative md:aspect-[736/650] aspect-[320/320] [transform-style:preserve-3d] md:px-[20.24vw] px-[15vw] my-auto ${className}`}
|
||||
{...handlers}
|
||||
>
|
||||
{cards.map((card, index) => (
|
||||
@@ -57,7 +57,7 @@ export default function CardsSwiper({
|
||||
))}
|
||||
</div>
|
||||
{controls && (
|
||||
<div className="flex items-center justify-center gap-[1.111vw] w-[94.444vw] mx-auto md:mb-[2vw] mb-[4.444vw] z-[3]">
|
||||
<div className="flex items-center justify-center gap-[1.111vw] w-[94.444vw] mx-auto md:mb-[2vw] mb-[10.444vw] z-[3]">
|
||||
<button
|
||||
onClick={handleSwipeLeft}
|
||||
className="size-[15.556vw] flex items-center justify-center"
|
||||
@@ -14,7 +14,8 @@ export default function SlideWrapper({
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
`snap-center md:rounded-[5.208vw] rounded-[11.111vw] overflow-hidden relative ${className}`
|
||||
`snap-center md:rounded-[5.208vw] rounded-[11.111vw] overflow-hidden relative [-webkit-mask-image:-webkit-radial-gradient(white,black)] will-change-transform ${className}`
|
||||
// [-webkit-mask-image:-webkit-radial-gradient(white,black)] will-change-transform - overflow fix for safari
|
||||
)}
|
||||
style={{
|
||||
minHeight: "calc(var(--vh, 1vh) * 100)",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef } from "react";
|
||||
import React, { useRef, useEffect, useCallback } from "react";
|
||||
import { useViewportHeight } from "@/hooks/useViewportHeight";
|
||||
|
||||
interface SnapWrapperProps {
|
||||
@@ -10,6 +10,74 @@ export default function SnapWrapper({ children }: SnapWrapperProps) {
|
||||
const childRefs = useRef<HTMLDivElement[]>([]);
|
||||
const flatChildren = React.Children.toArray(children);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const lastScrollTop = useRef(0);
|
||||
const rafId = useRef<number | null>(null);
|
||||
|
||||
// Handle scroll to hide/show iOS Safari address bar
|
||||
const handleScroll = useCallback(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const currentScrollTop = container.scrollTop;
|
||||
|
||||
// Cancel previous animation frame if exists
|
||||
if (rafId.current !== null) {
|
||||
cancelAnimationFrame(rafId.current);
|
||||
}
|
||||
|
||||
// If scroll is at the top ( < 40), show the address bar
|
||||
if (currentScrollTop < 40) {
|
||||
rafId.current = requestAnimationFrame(() => {
|
||||
if (window.scrollY > 0) {
|
||||
try {
|
||||
window.scrollTo({ top: 0, behavior: "instant" as ScrollBehavior });
|
||||
} catch {
|
||||
// Fallback for older browsers
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
rafId.current = null;
|
||||
});
|
||||
}
|
||||
// Check if user is scrolling down
|
||||
else if (currentScrollTop > lastScrollTop.current && currentScrollTop > 0) {
|
||||
// Trigger a minimal window scroll to hide the address bar on iOS
|
||||
rafId.current = requestAnimationFrame(() => {
|
||||
if (window.scrollY === 0) {
|
||||
try {
|
||||
window.scrollTo({ top: 1, behavior: "instant" as ScrollBehavior });
|
||||
} catch {
|
||||
// Fallback for older browsers
|
||||
window.scrollTo(0, 1);
|
||||
}
|
||||
}
|
||||
rafId.current = null;
|
||||
});
|
||||
}
|
||||
|
||||
lastScrollTop.current = currentScrollTop;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
// Ensure body has enough height for minimal scroll on iOS
|
||||
const originalBodyMinHeight = document.body.style.minHeight;
|
||||
document.body.style.minHeight = "101vh";
|
||||
|
||||
container.addEventListener("scroll", handleScroll, { passive: true });
|
||||
|
||||
return () => {
|
||||
container.removeEventListener("scroll", handleScroll);
|
||||
document.body.style.minHeight = originalBodyMinHeight;
|
||||
|
||||
// Cancel any pending animation frame
|
||||
if (rafId.current !== null) {
|
||||
cancelAnimationFrame(rafId.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -17,6 +85,8 @@ export default function SnapWrapper({ children }: SnapWrapperProps) {
|
||||
className="overflow-y-scroll overflow-x-hidden hide-scrollbars snap-y snap-mandatory"
|
||||
style={{
|
||||
height: "calc(var(--vh, 1vh) * 100)",
|
||||
WebkitOverflowScrolling: "touch",
|
||||
scrollBehavior: "smooth",
|
||||
}}
|
||||
>
|
||||
{flatChildren.map((child, index) => (
|
||||
|
||||
@@ -42,8 +42,8 @@ export default function AI() {
|
||||
|
||||
<div className="md:mb-[6.25vw] mb-[11.111vw] z-[3]">
|
||||
<p className="text-white text-center w-[91.111vw] mx-auto headline2 !font-medium ">
|
||||
Научили AI-модель выполнять некоторые функции{" "}
|
||||
<br className="md:block hidden " /> мнеджера даже лучше человека{" "}
|
||||
Научили AI-модель выполнять полезные
|
||||
<br className="md:block hidden " /> функции в помощь человеку
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-[0.521vw] md:w-[60.469vw] w-[96.889vw] mx-auto justify-center mt-[3.333vw] ">
|
||||
<GraffLabel title="Саммаризация встречи" />
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function Details() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2 md:overflow-hidden flex flex-col md:justify-start justify-center gap-[0.556vw] backdrop-blur-[24px] [-webkit-backdrop-filter:blur(24px)] bg-[#FFFFFF1A] md:py-[4.167vw] md:pl-[4.167vw] flex-1 pl-[5.556vw] border-[2px] border-[#FFFFFF1A] md:rounded-[3.125vw] rounded-[8.889vw]">
|
||||
<div className="md:col-span-2 md:overflow-hidden flex flex-col md:justify-start relative justify-center gap-[0.556vw] backdrop-blur-[24px] [-webkit-backdrop-filter:blur(24px)] bg-[#FFFFFF1A] md:py-[4.167vw] md:pl-[4.167vw] flex-1 pl-[5.556vw] border-[2px] border-[#FFFFFF1A] md:rounded-[3.125vw] rounded-[8.889vw]">
|
||||
<p className="md:headline1 headline2 !font-medium mb-[2.222vw]">
|
||||
Новая модель <br /> интерактивного стола
|
||||
</p>
|
||||
@@ -63,7 +63,7 @@ export default function Details() {
|
||||
<img
|
||||
src="/img/pages/results/reels/details/Mobile/table.png"
|
||||
alt=""
|
||||
className="absolute md:bottom-[-21.229vw] bottom-[5.556vw] md:right-[2.5vw] right-[5.556vw] md:h-[64.844vw] h-[29.444vw] object-cover"
|
||||
className="absolute md:bottom-[-20.229vw] max-md:top-[50%] max-md:-translate-y-1/2 md:right-[2.5vw] right-[5.556vw] md:h-[64.844vw] h-[29.444vw] object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import CardsSwiper from "../../../Tablet&Mobile/CardsSwiper";
|
||||
import CardsSwiper from "../../components/CardsSwiper";
|
||||
import SlideWrapper from "../../components/SlideWrapper";
|
||||
|
||||
export default function Modules() {
|
||||
|
||||
@@ -145,7 +145,7 @@ export default function Next() {
|
||||
<img
|
||||
src={src}
|
||||
alt=""
|
||||
className="h-full w-[50vw] flex-shrink-0 object-cover rounded-[1.111vw]"
|
||||
className="h-full w-[50vw] flex-shrink-0 object-cover rounded-[2.222vw]"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function Team() {
|
||||
return (
|
||||
<SlideWrapper
|
||||
id="team-area"
|
||||
className="snap-end md:mt-[1.042vw] mt-[2.222vw] flex flex-col bg-[url('/img/pages/results/components/gradients/mobile-team.png')] bg-center bg-cover bg-no-repeat bg-[#37393B99]"
|
||||
className="md:mt-[1.042vw] mt-[2.222vw] flex flex-col bg-[url('/img/pages/results/components/gradients/mobile-team.png')] bg-center bg-cover bg-no-repeat bg-[#37393B99]"
|
||||
>
|
||||
<p className="text-white text-center z-[3] w-[91.111vw] mx-auto headline2 md:text-[5.208vw] !font-medium md:mt-[5.208vw] mt-[11.111vw]">
|
||||
Наш штат расширился на 30%
|
||||
@@ -18,9 +18,9 @@ export default function Team() {
|
||||
<h2 className="text-white text-center text-[5.208vw] font-medium leading-[95%] tracking-[-0.02em] z-[10] md:mt-[1vw] mb-[12.222vw] line2 mt-[3.333vw]">
|
||||
Теперь в команде <br className="md:hidden block" /> более 70 человек
|
||||
</h2>
|
||||
<div className="w-[267.222vw] ml-[-82.805vw] mx-auto overflow-hidden">
|
||||
<ResultsTeamWheel />
|
||||
|
||||
<div className="w-[267.222vw] ml-[-82.805vw] mx-auto">
|
||||
<ResultsTeamWheel />
|
||||
<button
|
||||
onClick={() =>
|
||||
setModal(
|
||||
|
||||
+62
-89
@@ -3,105 +3,78 @@ import MuteIcon from "@/components/icons/MuteIcon";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { CircularProgressbar } from "react-circular-progressbar";
|
||||
import SlideWrapper from "../../components/SlideWrapper";
|
||||
import ResutsCongratulations from "../../../Desktop/ResutsCongratulations";
|
||||
|
||||
export default function MobileCongratulations() {
|
||||
return (
|
||||
<SlideWrapper className='md:mt-[1.042vw] mt-[2.222vw] flex flex-col bg-[url("/img/pages/results/components/gradients/mobile-congratulations.png")] bg-center bg-cover bg-no-repeat !h-max'>
|
||||
<img
|
||||
src="/img/pages/results/components/garland/garland_mobile.svg"
|
||||
alt=""
|
||||
className="absolute w-full object-cover"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<p className="text-white text-center z-[3] w-[91.111vw] mx-auto md:mt-[49.479vw] md:text-[2.604vw] mt-[47.778vw] headline2 !font-medium">
|
||||
Пару слов об итогах года <br /> от генерального директора и основателя
|
||||
<br />
|
||||
GRAFF.estate Георгия Уморина
|
||||
</p>
|
||||
|
||||
<div className="md:w-[59.896vw] md:h-[59.896vw] w-[83.333vw] h-[83.333vw] mx-auto mt-[6.667vw] mb-[11.111vw]">
|
||||
<RoundVideo src="/videos/pages/home/story.mp4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-white text-center z-[3] w-[91.111vw] mx-auto md:text-[2.604vw] headline2 !font-medium">
|
||||
Поздравление от директора по продукту
|
||||
<br />
|
||||
Вячеслава Зернова
|
||||
</p>
|
||||
|
||||
<div className="md:w-[59.896vw] md:h-[59.896vw] w-[83.333vw] h-[83.333vw] mx-auto mt-[6.667vw] mb-[16.667vw]">
|
||||
<RoundVideo src="/videos/pages/home/story.mp4" />
|
||||
</div>
|
||||
</div>
|
||||
<SlideWrapper className='md:mt-[1.042vw] mt-[2.222vw] flex flex-col bg-[url("/img/pages/results/components/gradients/mobile-purple.png")] bg-center bg-cover bg-no-repeat !h-max !snap-start'>
|
||||
<ResutsCongratulations />
|
||||
</SlideWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
function RoundVideo({ src }: { src: string }) {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const [progress, setProgress] = useState<number>(0);
|
||||
const [isMuted, setIsMuted] = useState<boolean>(true);
|
||||
// function RoundVideo({ src }: { src: string }) {
|
||||
// const videoRef = useRef<HTMLVideoElement>(null);
|
||||
// const [progress, setProgress] = useState<number>(0);
|
||||
// const [isMuted, setIsMuted] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
const timeUpdateHandler = () =>
|
||||
setProgress((video.currentTime / video.duration) * 100);
|
||||
// useEffect(() => {
|
||||
// const video = videoRef.current;
|
||||
// if (!video) return;
|
||||
// const timeUpdateHandler = () =>
|
||||
// setProgress((video.currentTime / video.duration) * 100);
|
||||
|
||||
videoRef.current.addEventListener("timeupdate", timeUpdateHandler);
|
||||
return () => video.removeEventListener("timeupdate", timeUpdateHandler);
|
||||
}, []);
|
||||
// videoRef.current.addEventListener("timeupdate", timeUpdateHandler);
|
||||
// return () => video.removeEventListener("timeupdate", timeUpdateHandler);
|
||||
// }, []);
|
||||
|
||||
function onVideoClick() {
|
||||
setIsMuted(!isMuted);
|
||||
}
|
||||
// function onVideoClick() {
|
||||
// setIsMuted(!isMuted);
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className="aspect-square relative">
|
||||
<button
|
||||
onClick={onVideoClick}
|
||||
className="bg-transaparent w-full h-full absolute top-0 left-0 rounded-full z-[10]"
|
||||
></button>
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={src}
|
||||
poster="/img/pages/home/motivation/stories_poster.jpg"
|
||||
className="aspect-square object-cover w-full h-full rounded-full cursor-pointer z-[9]"
|
||||
loop
|
||||
playsInline
|
||||
autoPlay
|
||||
muted={isMuted}
|
||||
/>
|
||||
// return (
|
||||
// <div className="aspect-square relative">
|
||||
// <button
|
||||
// onClick={onVideoClick}
|
||||
// className="bg-transaparent w-full h-full absolute top-0 left-0 rounded-full z-[10]"
|
||||
// ></button>
|
||||
// <video
|
||||
// ref={videoRef}
|
||||
// src={src}
|
||||
// poster="/img/pages/home/motivation/stories_poster.jpg"
|
||||
// className="aspect-square object-cover w-full h-full rounded-full cursor-pointer z-[9]"
|
||||
// loop
|
||||
// playsInline
|
||||
// autoPlay
|
||||
// muted={isMuted}
|
||||
// />
|
||||
|
||||
{/* Индикатор заглушенного звука */}
|
||||
{isMuted && (
|
||||
<div className="md:size-[4.167vw] size-[8.889vw] absolute bottom-[3.333vw] left-[50%] translate-x-[-50%] z-[12] rounded-full bg-[#FFFFFF33] backdrop-blur-[24px] flex items-center justify-center text-white pointer-events-none">
|
||||
<div className="md:size-[2.604vw] size-[4.444vw]">
|
||||
<MuteIcon />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
// {/* Индикатор заглушенного звука */}
|
||||
// {isMuted && (
|
||||
// <div className="md:size-[4.167vw] size-[8.889vw] absolute bottom-[3.333vw] left-[50%] translate-x-[-50%] z-[12] rounded-full bg-[#FFFFFF33] backdrop-blur-[24px] flex items-center justify-center text-white pointer-events-none">
|
||||
// <div className="md:size-[2.604vw] size-[4.444vw]">
|
||||
// <MuteIcon />
|
||||
// </div>
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
<CircularProgressbar
|
||||
value={progress}
|
||||
strokeWidth={2}
|
||||
styles={
|
||||
Math.trunc(progress) === 0
|
||||
? {}
|
||||
: {
|
||||
path: {
|
||||
stroke: "white",
|
||||
strokeLinecap: "round",
|
||||
transition: "linear",
|
||||
transitionDuration: "0.3s",
|
||||
},
|
||||
}
|
||||
}
|
||||
className="absolute top-0 text-white rounded-full"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// <CircularProgressbar
|
||||
// value={progress}
|
||||
// strokeWidth={2}
|
||||
// styles={
|
||||
// Math.trunc(progress) === 0
|
||||
// ? {}
|
||||
// : {
|
||||
// path: {
|
||||
// stroke: "white",
|
||||
// strokeLinecap: "round",
|
||||
// transition: "linear",
|
||||
// transitionDuration: "0.3s",
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
// className="absolute top-0 text-white rounded-full"
|
||||
// />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -48,28 +48,12 @@ export default function Appartaments() {
|
||||
<br className="md:hidden block" /> нового дома
|
||||
</p>
|
||||
|
||||
<div className="absolute md:h-[62.76vw] h-[78.278vw] md:w-[49.349vw] w-[63.056vw] md:top-[-62.76vw] top-[-78.8vw] left-1/2 -translate-x-1/2">
|
||||
<div className="absolute md:h-[62.76vw] h-[78.278vw] md:w-[55.349vw] w-[63.056vw] md:top-[-62.86vw] top-[-78.8vw] left-1/2 -translate-x-1/2 ">
|
||||
<img
|
||||
src="/img/pages/results/general/building.png"
|
||||
alt=""
|
||||
className="w-full h-full "
|
||||
/>
|
||||
|
||||
<FloorIndicator
|
||||
floor={32}
|
||||
appartamentsCount={8}
|
||||
className="absolute md:top-[20.063vw] md:right-[-7.161vw] top-[21.389vw] right-[-3.333vw]"
|
||||
/>
|
||||
<FloorIndicator
|
||||
floor={25}
|
||||
appartamentsCount={8}
|
||||
className="absolute md:top-[45.063vw] md:right-[-7.161vw] top-[33.333vw] right-[-3.333vw]"
|
||||
/>
|
||||
<FloorIndicator
|
||||
floor={24}
|
||||
appartamentsCount={6}
|
||||
className="absolute md:top-[55.063vw] md:right-[-7.161vw] top-[65.556vw] right-[-3.333vw]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SlideWrapper>
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useRef, useState, useMemo } from "react";
|
||||
import ShiftText from "../../../ShiftText";
|
||||
import { motion, useMotionValue } from "framer-motion";
|
||||
import SlideWrapper from "../../components/SlideWrapper";
|
||||
import { useViewportHeight } from "@/hooks/useViewportHeight";
|
||||
import { useInView } from "framer-motion";
|
||||
|
||||
const calculateFlexBasis = (isMd: boolean) => {
|
||||
const viewportHeight = window.innerHeight;
|
||||
const vhMultiplier = isMd ? 85 : 77;
|
||||
const calculatedValue = (viewportHeight * vhMultiplier) / 100 / 2.8;
|
||||
return `${calculatedValue}px`;
|
||||
};
|
||||
|
||||
export default function Projects({ isMd }: { isMd: boolean }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -13,15 +21,17 @@ export default function Projects({ isMd }: { isMd: boolean }) {
|
||||
ease: "easeInOut",
|
||||
};
|
||||
const progress = useMotionValue(0);
|
||||
|
||||
const isInView = useInView(containerRef);
|
||||
useViewportHeight();
|
||||
|
||||
const initialFlexBasis = useMemo(() => calculateFlexBasis(isMd), [isMd]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInView) {
|
||||
const timeout = setTimeout(() => {
|
||||
setExpanded(true);
|
||||
progress.set(1);
|
||||
}, 2000);
|
||||
}, 500);
|
||||
return () => clearTimeout(timeout);
|
||||
}
|
||||
}, [isInView]);
|
||||
@@ -40,18 +50,14 @@ export default function Projects({ isMd }: { isMd: boolean }) {
|
||||
gap: "1.111vw",
|
||||
flexGrow: 0,
|
||||
flexShrink: 0,
|
||||
flexBasis: isMd ? "calc(85vh/2.8)" : "calc(77vh/2.8)",
|
||||
flexBasis: initialFlexBasis,
|
||||
}}
|
||||
animate={{
|
||||
translateX: expanded ? 100 * offsetDirection : 0,
|
||||
gap: expanded ? "0.556vw" : "1.111vw",
|
||||
flexGrow: expanded ? 1 : 0,
|
||||
flexShrink: expanded ? 1 : 0,
|
||||
flexBasis: expanded
|
||||
? "0%"
|
||||
: isMd
|
||||
? "calc(85vh/2.8)"
|
||||
: "calc(77vh/2.8)",
|
||||
flexBasis: expanded ? "0%" : initialFlexBasis,
|
||||
}}
|
||||
transition={{
|
||||
...transition,
|
||||
@@ -76,7 +82,7 @@ export default function Projects({ isMd }: { isMd: boolean }) {
|
||||
src={src}
|
||||
alt=""
|
||||
className={
|
||||
"md:w-[70.703vw] md:aspect-[544/307] aspect-[294/166] object-cover flex-shrink h-full"
|
||||
"md:w-[70.703vw] md:aspect-[544/307] aspect-[294/166] object-cover flex-shrink max-h-full"
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { motion, MotionValue, useScroll, useTransform } from "framer-motion";
|
||||
import ResultsGarland from "./Desktop/ResultsGarland";
|
||||
import { useRef } from "react";
|
||||
import { useRef, useEffect } from "react";
|
||||
import ResultsProjects from "./Desktop/ResultsProjects";
|
||||
import ResultsGeneral from "./Desktop/ResultsGeneral";
|
||||
import ResultsEventsReelsWithAchievements from "./Desktop/ResultsEventsReelsWithAchievements";
|
||||
@@ -40,6 +40,21 @@ export function Results2025() {
|
||||
|
||||
const { isLg, isMd } = useMediaQueries();
|
||||
|
||||
useEffect(() => {
|
||||
// Сохраняем текущее значение overflow-y
|
||||
const originalOverflowY = document.body.style.overflowY;
|
||||
|
||||
// Отключаем overflow-y на всех разрешениях кроме Lg
|
||||
if (!isLg) {
|
||||
document.body.style.overflowY = "hidden";
|
||||
}
|
||||
|
||||
// Восстанавливаем при размонтировании
|
||||
return () => {
|
||||
document.body.style.overflowY = originalOverflowY;
|
||||
};
|
||||
}, [isLg]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
@@ -53,7 +68,6 @@ export function Results2025() {
|
||||
<ResultsMap />
|
||||
<ResultsEventsReelsWithAchievements />
|
||||
|
||||
<ResutsCongratulations />
|
||||
<Button scrollYProgress={scrollYProgress} />
|
||||
</>
|
||||
)}
|
||||
@@ -79,6 +93,7 @@ export function Results2025() {
|
||||
<Details />
|
||||
<Team />
|
||||
<MobileCongratulations />
|
||||
|
||||
<div className="px-[10px] pt-[10px] snap-start">
|
||||
<Feedback />
|
||||
</div>
|
||||
@@ -118,40 +133,3 @@ function Button({ scrollYProgress }: { scrollYProgress: MotionValue<number> }) {
|
||||
</motion.button>
|
||||
);
|
||||
}
|
||||
|
||||
function BgGradientTablet({
|
||||
scrollYProgress,
|
||||
}: {
|
||||
scrollYProgress: MotionValue<number>;
|
||||
}) {
|
||||
return (
|
||||
<div className="fixed top-0 bottom-0 h-[100vh] inset-0 overflow-hidden z-[-1] md:block hidden">
|
||||
<motion.img
|
||||
style={{
|
||||
opacity: useTransform(
|
||||
scrollYProgress,
|
||||
[0.5, 0.53, 0.57, 0.6, 0.8, 0.9],
|
||||
[1, 0, 0, 1, 1, 0]
|
||||
),
|
||||
}}
|
||||
src="/img/pages/results/tablet_bg.png"
|
||||
alt=""
|
||||
draggable={false}
|
||||
className="w-full h-full max-md:hidden"
|
||||
/>
|
||||
<motion.img
|
||||
style={{
|
||||
opacity: useTransform(
|
||||
scrollYProgress,
|
||||
[0.4, 0.45, 0.48, 0.53, 0.85, 0.9],
|
||||
[1, 0, 0, 1, 1, 0]
|
||||
),
|
||||
}}
|
||||
src="/img/pages/results/mobile_bg.png"
|
||||
alt=""
|
||||
draggable={false}
|
||||
className="w-full h-full max-md:block hidden"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ export default function ResultsTeamWheel() {
|
||||
return (
|
||||
<div
|
||||
ref={wheelRef}
|
||||
className={`lg:size-[66.806vw] md:size-[160vw] size-[220.222vw] size- [267.222vw] relative ${
|
||||
className={`lg:size-[66.806vw] md:size-[160vw] size-[220.222vw] relative ${
|
||||
isDragging ? "cursor-grabbing" : isSpinning ? "" : "cursor-grab"
|
||||
} bg-[url('/img/pages/results/components/wheel.svg')] bg-cover bg-center mx-auto rounded-full z-[10]`}
|
||||
onMouseDown={handleMouseDown}
|
||||
|
||||
-367
@@ -1,367 +0,0 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import React, { useState } from "react";
|
||||
import CardsSwiper from "./CardsSwiper";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import ResultsTeamWheel from "../ResultsTeamWheel";
|
||||
import QuestionFormModal from "@/components/modals/QuestionFormModal";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
|
||||
const cards = [
|
||||
{
|
||||
id: 0,
|
||||
video: "/videos/pages/results/reels/Движение.mp4",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
video: "/videos/pages/results/reels/Технобилд.mp4",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
video: "/videos/pages/results/reels/WOW.mp4",
|
||||
},
|
||||
];
|
||||
|
||||
const idToTitle = {
|
||||
0: "Форум недвижимости «Движение»",
|
||||
1: "Международный строительный форум 100+ TechnoBuild",
|
||||
2: "Фестиваль маркетинга и креатива WOW FEST",
|
||||
};
|
||||
|
||||
export default function ResultsEventsReelsWithAchievementsMobile() {
|
||||
const [currentCardId, setCurrentCardId] = useState(0);
|
||||
|
||||
function handleCardChange(newCardId: number) {
|
||||
setCurrentCardId(newCardId);
|
||||
}
|
||||
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col mt-[15.625vw]">
|
||||
<h2 className="text-[5.208vw] text-white text-center leading-[95%] tracking-[-0.02em] max-md:line2">
|
||||
Самые яркие <br /> события за 2025 год
|
||||
</h2>
|
||||
<div className="flex flex-col">
|
||||
<CardsSwiper
|
||||
cards={cards}
|
||||
onChange={handleCardChange}
|
||||
className="md:mt-[6.25vw] mt-[11.111vw]"
|
||||
/>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.span
|
||||
key={currentCardId}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="accent text-center md:mt-[3.125vw] mt-[5.556vw] md:w-[45.104vw] max-md:h-[20.833vw] w-[90vw] mx-auto"
|
||||
>
|
||||
{idToTitle[currentCardId as keyof typeof idToTitle]}
|
||||
</motion.span>
|
||||
</AnimatePresence>
|
||||
|
||||
<Legenda />
|
||||
<GraffView />
|
||||
<Modules />
|
||||
<Details />
|
||||
<Team />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModuleCard({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
bgImage,
|
||||
className,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
children?: React.ReactNode;
|
||||
bgImage?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`relative border-[2px] border-[#FFFFFF1A] rounded-[3.125vw] md:p-[4.167vw] p-[5.556vw] bg-[#FFFFFF1A] ba ckdrop-blur-[24px] overflow-hidden ${className} ${
|
||||
bgImage ? "bg-cover bg-center bg-no-repeat" : ""
|
||||
}`}
|
||||
style={{
|
||||
willChange: "transform",
|
||||
}}
|
||||
>
|
||||
{bgImage && (
|
||||
<div
|
||||
className="absolute top-0 left-0 w-full h-full bg-cover bg-center bg-no-repeat"
|
||||
style={{ backgroundImage: `url(${bgImage})` }}
|
||||
/>
|
||||
)}
|
||||
<h3 className="headline1 md:mb-[1.042vw] mb-[2.222vw] relative z-[2] whitespace-pre-line">
|
||||
{title}
|
||||
</h3>
|
||||
{bgImage && (
|
||||
<div className="absolute left-[-2px] top-[-2px] rounded-[3.125vw] w-[calc(100%+4px)] h-[calc(100%+4px)] bg-[linear-gradient(to_bottom,#00000050,#FFFFFF00)] z-[1]"></div>
|
||||
)}
|
||||
|
||||
{description && <div className="text1 opacity-60">{description}</div>}
|
||||
{children && <div className="mt-[1.042vw]">{children}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Legenda() {
|
||||
return (
|
||||
<>
|
||||
<h2 className="text-[5.208vw] md:mt-[13.021vw] mt-[27.778vw] md:mb-[6.25vw] mb-[11.111vw] text-white text-center leading-[95%] tracking-[-0.02em] max-md:line2">
|
||||
Но победы <br /> были не только наши!
|
||||
</h2>
|
||||
<div className="relative w-full max-md:px-[2.778vw] max-md:grid max-md:grid-cols-2 max-md:grid-rows-[47.778vw_78.889vw] max-md:gap-[1.111vw]">
|
||||
<video
|
||||
src="/videos/pages/results/legenda.MOV"
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="md:absolute top-[3.255vw] md:max-lg:left-[1.302vw] md:w-[31.25vw] w-full max-md:h-full md:rounded-[3.125vw] rounded-[4.444vw] object-cover md:aspect-[240/192] aspect-square"
|
||||
/>
|
||||
<img
|
||||
src="/img/pages/results/achievments/Tablet/office-r.png"
|
||||
className="md:absolute top-[25.479vw] right-[5.953vw] md:size-[31.25vw] w-full md:rounded-[3.125vw] rounded-[4.444vw]"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="/img/pages/results/achievments/Tablet/office.png"
|
||||
className="md:size-[58.333vw] size-full object-cover mx-auto max-md:col-span-2 max-md:object-top md:rounded-[3.125vw] rounded-[4.444vw]"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<div className="md:w-[64.583vw] max-md:col-span-2 w-full mx-auto md:mt-[3.125vw] mt-[5.556vw] flex flex-col md:gap-[1.563vw] gap-[3.333vw] justify-center items-center max-md:accent">
|
||||
<h3 className="accent md:text-center text-left">
|
||||
Звание лучшего офиса продаж второй год подряд забрал наш партнер{" "}
|
||||
</h3>
|
||||
<span className="text-[1.823vw] md:w-[80%] w-full font leading-[135%] opacity-60 md:text-center text-left max-md:text1">
|
||||
ГК Легенда для которой мы укомплектовали офис продаж ЖК Северный
|
||||
Порт интерактивными инструментами продаж
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function GraffLabel({
|
||||
title,
|
||||
className,
|
||||
}: {
|
||||
title: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`bg-[#FFFFFF29] py-[1.563vw] backdrop-blur-[24px] px-[3.125vw] rounded-full md:btnm btns w-max ${className}`}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function GraffView() {
|
||||
return (
|
||||
<>
|
||||
<span className="headline2 text-[#FFFFFF60] text-center md:mt-[13.021vw] mt-[27.778vw]">
|
||||
Год был удачным не только снаружи,
|
||||
<br /> но и внутри компании
|
||||
</span>
|
||||
|
||||
<div className="w-full md:mt-[3.125vw] mt-[3.333vw]">
|
||||
<h2 className="text-[5.208vw] text-white text-center leading-[95%] tracking-[-0.02em] max-md:line2 max-md:w-[94.444vw] mx-auto !font-medium">
|
||||
Запустили новый продукт <br className="max-md:hidden" />
|
||||
GRAFF.estate View
|
||||
</h2>
|
||||
|
||||
<div className="w-full h-full relative">
|
||||
<img
|
||||
src="/img/pages/results/general/booking_3.png"
|
||||
className="object-cover md:h-[70.125vw] h-[127.778vw] md:ml-[29.948vw] ml-[18.611vw] md:mt-[6.25vw] mt-[11.111vw] md:mb-[2.083vw] mb-[4.444vw] "
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<h3 className="accent text-white md:text-center text-left md:w-[64.583vw] w-[94.444vw] mx-auto max-md:mb-[3.333vw]">
|
||||
Показываем виды из любой квартиры еще
|
||||
<br className="max-md:hidden" /> не построенного жилого комплекса
|
||||
</h3>
|
||||
|
||||
<div className="md:absolute top-[22.786vw] left-[2.604vw] flex flex-col md:gap-[0.521vw] gap-[1.111vw] max-md:mb-[1.111vw] max-md:px-[2.778vw] text-white">
|
||||
<GraffLabel title="Динамическое изменение перспективы" />
|
||||
<GraffLabel title="Высокое качество визуализации" />
|
||||
</div>
|
||||
|
||||
<div className="md:absolute top-[22.786vw] right-[2.604vw] md:max-w-[58.135vw] max-w-[90vw] flex-wrap flex md:justify-end md:gap-[0.521vw] max-md:px-[2.778vw] text-white">
|
||||
<GraffLabel
|
||||
title="Выбор квартиры"
|
||||
className=" max-md:mr-[1.111vw]"
|
||||
/>
|
||||
<GraffLabel title="Выбор вида" />
|
||||
|
||||
{/* Переносит flex элементы на строку ниже */}
|
||||
<div className="[flex-basis:100%]" />
|
||||
|
||||
<GraffLabel
|
||||
title="Интеграция с CRM"
|
||||
className=" max-md:mt-[1.111vw] max-md:mr-[1.111vw]"
|
||||
/>
|
||||
<GraffLabel
|
||||
title="Кастомизация продукта"
|
||||
className=" max-md:mt-[1.111vw]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Modules() {
|
||||
return (
|
||||
<div className="w-full md:mt-[13.021vw] mt-[27.778vw] md:px-[2.083vw] px-[2.778vw]">
|
||||
<h2 className="text-[5.208vw] text-white text-center leading-[95%] tracking-[-0.02em] md:mb-[6.25vw] mb-[11.111vw] max-md:line2 !font-medium">
|
||||
Добавили 6 новых модулей
|
||||
<br className="max-md:hidden" /> в приложение
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-2 grid-cols-1 md:gap-[1.563vw] gap-[2.222vw] md:grid-rows-[repeat(3,39.714vw)] grid-rows-[83.333vw_74.444vw_72.222vw_67.222vw_72.222vw_58.333vw]">
|
||||
<ModuleCard
|
||||
title="Цифровой город"
|
||||
description="Маршруты до значимых точек города в режиме онлайн"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/results/reels/modules/1.png"
|
||||
alt=""
|
||||
className="absolute bottom-[0] left-1/2 -translate-x-1/2 md:w-[29.167vw] w-[62.222vw] object-cover rounded-t-[1.111vw]"
|
||||
/>
|
||||
</ModuleCard>
|
||||
|
||||
<ModuleCard
|
||||
title="Сценарии жизни"
|
||||
description="Визуализация подстраивается под конкретного пользователя. Приложение показывает интересные ему локации в ЖК, подходящие типы планировок"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/results/reels/modules/2.png"
|
||||
alt=""
|
||||
className="absolute md:bottom-[4.167vw] bottom-[5.556vw] md:right-[4.167vw] right-[5.556vw] md:w-[14.583vw] w-[29.167vw] object-cover"
|
||||
/>
|
||||
</ModuleCard>
|
||||
|
||||
<ModuleCard
|
||||
title="Конфигуратор отделки"
|
||||
bgImage="/img/pages/results/reels/modules/3.png"
|
||||
/>
|
||||
|
||||
<ModuleCard
|
||||
title="Инженерные системы"
|
||||
description="Покажем клиенту все внутренние составляющие и технологичность проекта"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/results/reels/modules/4.png"
|
||||
alt=""
|
||||
className="absolute md:bottom-[4.167vw] bottom-[5.556vw] md:right-[4.167vw] right-[5.556vw] md:w-[52.552vw] w-[110vw] object-cover"
|
||||
/>
|
||||
</ModuleCard>
|
||||
|
||||
<ModuleCard
|
||||
title="Смена сезонов"
|
||||
bgImage="/img/pages/results/reels/modules/5.png"
|
||||
></ModuleCard>
|
||||
|
||||
<ModuleCard title="Покупка парковочных мест и локеров">
|
||||
<img
|
||||
src="/img/pages/results/reels/modules/6.png"
|
||||
alt=""
|
||||
className="absolute md:bottom-[4.167vw] bottom-[5.556vw] md:right-[4.167vw] right-[5.556vw] md:w-[38.802vw] w-[83.333vw] object-cover"
|
||||
/>
|
||||
</ModuleCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Details() {
|
||||
return (
|
||||
<div className="w-full md:mt-[13.021vw] mt-[27.778vw] md:px-[2.083vw] px-[2.778vw]">
|
||||
<div className="text-[5.208vw] text-white text-center leading-[95%] tracking-[-0.02em] md:mb-[6.25vw] mb-[11.111vw] max-md:line2 ">
|
||||
Кредо этого года: внимание к деталям, <br /> стремление к идеалу
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 grid-cols-1 gap-[1.563vw] md:grid-rows-[repeat(2,60.417vw)] grid-rows-[83.333vw_95vw_100vw]">
|
||||
<ModuleCard
|
||||
title="Облака по расписанию"
|
||||
description="Интегрировали API, передающее направление и скорость ветра в реальном времени именно в той локации, где находится проект"
|
||||
className="overflow-clip"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/results/reels/details/cloud.png"
|
||||
alt=""
|
||||
className="absolute md:bottom-[9.635vw] bottom-[5vw] md:right-[-10.75vw] right-[-25vw] md:w-[60.026vw] w-[128.056vw] object-cover"
|
||||
/>
|
||||
<img
|
||||
src="/img/pages/results/reels/details/cloud.png"
|
||||
alt=""
|
||||
className="absolute md:bottom-[1.042vw] bottom-[-15.222vw] md:left-[-28.188vw] left-[-57.778vw] md:w-[60.026vw] w-[128.056vw] object-cover"
|
||||
/>
|
||||
</ModuleCard>
|
||||
<ModuleCard
|
||||
title="Добавили в приложения птиц"
|
||||
description="Они не только летают в небе, но и садятся на землю или деревья вокруг ЖК"
|
||||
>
|
||||
<video
|
||||
src="/img/pages/results/reels/details/birds.mp4"
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="absolute md:bottom-[11.849vw] bottom-[5.556vw] md:right-[11.849vw] right-[5.556vw] md:size-[23.438vw] size-[50vw] object-cover md:rounded-[2.604vw] rounded-[5.556vw]"
|
||||
/>
|
||||
</ModuleCard>
|
||||
<ModuleCard
|
||||
title={"Новая модель\n интерактивного стола"}
|
||||
description="Более тонкая и изящная"
|
||||
className="md:col-span-2"
|
||||
>
|
||||
<img
|
||||
src="/img/pages/results/reels/details/table.png"
|
||||
alt=""
|
||||
className="absolute bottom-[0] md:right-[3.472vw] md:w-[59.115vw] w-[80.333vw] h-auto object-cover"
|
||||
/>
|
||||
</ModuleCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Team() {
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
return (
|
||||
<div className="w-full mt-[13.021vw]">
|
||||
<h2 className="text-[5.208vw] mx-auto text-white text-center leading-[95%] tracking-[-0.02em] mb-[6.25vw] max-md:line2 max-md:w-[94.444vw]">
|
||||
Наш штат расширился на 30%, теперь <br />
|
||||
<span className="text-gradient">в команде более 70 человек</span>
|
||||
</h2>
|
||||
<div className="md:w-[140vw] md:ml-[-20vw] md:h-[93vw] w-[267.222vw] ml-[-82.805vw] h-[130vw] mx-auto overflow-hidden">
|
||||
<ResultsTeamWheel />
|
||||
<div className="absolute bottom-0 left-0 w-full h-[50vw] bg-[linear-gradient(to_bottom,#00000000,#0F1011_90%)] z-[10]" />
|
||||
<button
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<QuestionFormModal products={["Интерактивная презентация"]} />
|
||||
)
|
||||
}
|
||||
className="btnl z-[11] w-max bg-[radial-gradient(circle_at_right_top,#FF79D2_0%,#C932E8_20%,#7A55FF_60%)] md:px-[4.688vw] px-[10.111vw] md:py-[2.604vw] py-[5.556vw] md:rounded-[2.083vw] rounded-[4.444vw] absolute md:bottom-[20.063vw] bottom-[5vw] left-1/2 -translate-x-1/2"
|
||||
>
|
||||
Оставить заявку
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
"use client";
|
||||
import QuestionFormModal from "@/components/modals/QuestionFormModal";
|
||||
import useDiscreteScroll from "@/hooks/useDiscreteScroll";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { motion, useScroll, useTransform } from "framer-motion";
|
||||
import React, { useRef } from "react";
|
||||
|
||||
export default function ResultsGarlandMobile() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { setModal } = useModalStore();
|
||||
const { isMd } = useMediaQueries();
|
||||
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ["start start", "end end"],
|
||||
});
|
||||
const discreteScroll = useDiscreteScroll(scrollYProgress, [0, 0.5, 0.7]);
|
||||
|
||||
const ANIMATION_BREAKPOINTS = {
|
||||
opacity: [0, 1],
|
||||
top: [!isMd ? "120vw" : "85vw", "0vw"],
|
||||
height: [!isMd ? "25vh" : "25vh", "100vh"],
|
||||
paddingInline: [!isMd ? "2.778vw" : "2.214vw", "0vw"],
|
||||
radius: [!isMd ? "4.444vw" : "2.083vw", "0vw"],
|
||||
width: [!isMd ? "100%" : "65vw", "100%"],
|
||||
};
|
||||
|
||||
const overlayOpacity = useTransform(
|
||||
discreteScroll,
|
||||
[0.5, 0.7],
|
||||
ANIMATION_BREAKPOINTS.opacity
|
||||
);
|
||||
const videoTop = useTransform(
|
||||
discreteScroll,
|
||||
[0.5, 0.7],
|
||||
ANIMATION_BREAKPOINTS.top
|
||||
);
|
||||
const videoHeight = useTransform(
|
||||
discreteScroll,
|
||||
[0.5, 0.7],
|
||||
ANIMATION_BREAKPOINTS.height
|
||||
);
|
||||
const videoPadding = useTransform(
|
||||
discreteScroll,
|
||||
[0.5, 0.7],
|
||||
ANIMATION_BREAKPOINTS.paddingInline
|
||||
);
|
||||
const videoRadius = useTransform(
|
||||
discreteScroll,
|
||||
[0.5, 0.7],
|
||||
ANIMATION_BREAKPOINTS.radius
|
||||
);
|
||||
|
||||
const videoWidth = useTransform(
|
||||
discreteScroll,
|
||||
[0.5, 0.7],
|
||||
ANIMATION_BREAKPOINTS.width
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="relative h-[250vh]">
|
||||
<div className="sticky top-0 min-h-[100vh] flex flex-col items-center justify-start">
|
||||
<img
|
||||
src="/img/pages/results/components/garland/garland_tablet.svg"
|
||||
alt="garland"
|
||||
draggable={false}
|
||||
className="absolute inset-0 w-full z-10 max-md:scale-[2]"
|
||||
/>
|
||||
|
||||
<h1 className="md:text-[6.25vw] text-[11.111vw] md:mb-[3.125vw] mb-[6.667vw] text-white text-center md:mt-[57.292vw] mt-[60.056vw] leading-[95%] tracking-[-0.02em] font-medium">
|
||||
Подводим <br className="md:hidden block" /> итоги 2025 года <br /> в
|
||||
GRAFF.estate
|
||||
</h1>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setModal(
|
||||
<QuestionFormModal products={["Интерактивная презентация"]} />
|
||||
);
|
||||
}}
|
||||
className="btnl
|
||||
bg-[radial-gradient(circle_at_right_top,#FF79D2_0%,#C932E8_20%,#7A55FF_60%)]
|
||||
md:px-[4.167vw] px-[4.444vw] md:py-[2.604vw] py-[4.722vw] md:rounded-[2.083vw] mb-[6.667vw] rounded-[4.444vw] mx-auto font-medium"
|
||||
>
|
||||
Стать первыми в 2026
|
||||
</button>
|
||||
|
||||
<motion.div
|
||||
style={{ opacity: overlayOpacity }}
|
||||
className="absolute inset-0 bg-gradient-to-b from-[#0f101190] to-[#1a1b1c00]"
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
style={{
|
||||
height: videoHeight,
|
||||
top: videoTop,
|
||||
paddingInline: videoPadding,
|
||||
}}
|
||||
className="absolute left-0 w-full z-[20]"
|
||||
>
|
||||
<motion.video
|
||||
style={{
|
||||
borderRadius: videoRadius,
|
||||
width: videoWidth,
|
||||
}}
|
||||
src="/videos/pages/about/hero_video.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
className=" h-full object-cover md:mx-auto"
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
"use client";
|
||||
import React from "react";
|
||||
import CardsSwiper from "./CardsSwiper";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
|
||||
export default function ResultsGeneralMobile() {
|
||||
const { isMd } = useMediaQueries();
|
||||
|
||||
const cards = [
|
||||
{
|
||||
id: 1,
|
||||
image: isMd
|
||||
? "/img/pages/results/general/tablet/slide_1.png"
|
||||
: "/img/pages/results/general/mobile/slide_1.png",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
image: isMd
|
||||
? "/img/pages/results/general/tablet/slide_2.png"
|
||||
: "/img/pages/results/general/mobile/slide_2.png",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
image: isMd
|
||||
? "/img/pages/results/general/tablet/slide_3.png"
|
||||
: "/img/pages/results/general/mobile/slide_3.png",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
image: isMd
|
||||
? "/img/pages/results/general/tablet/slide_4.png"
|
||||
: "/img/pages/results/general/mobile/slide_4.png",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
image: isMd
|
||||
? "/img/pages/results/general/tablet/slide_5.png"
|
||||
: "/img/pages/results/general/mobile/slide_5.png",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col">
|
||||
<div className="text-center md:mb-[6.25vw] mb-[6.667vw] md:mt-[13.021vw] mt-[27.778vw]">
|
||||
<span className="accent">В 2025 году</span>
|
||||
</div>
|
||||
<CardsSwiper cards={cards} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
"use client";
|
||||
import React, { useRef } from "react";
|
||||
import { ResultsProjectsItem } from "../Desktop/ResultsProjects";
|
||||
import { motion, MotionValue, useScroll, useTransform } from "framer-motion";
|
||||
import ShiftText from "../ShiftText";
|
||||
|
||||
export default function ResultsProjectsMobile() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const latestContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { scrollYProgress: projectsScroll } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ["start start", "end start"],
|
||||
});
|
||||
const { scrollYProgress: latestScroll } = useScroll({
|
||||
target: latestContainerRef,
|
||||
offset: ["start start", "end start"],
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={containerRef} className="relative h-[250vh]">
|
||||
<Projects projectsScroll={projectsScroll} />
|
||||
</div>
|
||||
|
||||
<div ref={latestContainerRef} className="relative h-[180vh]">
|
||||
<LatestProjects latestScroll={latestScroll} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Projects({ projectsScroll }: { projectsScroll: MotionValue<number> }) {
|
||||
return (
|
||||
<div className="sticky top-0 h-[100vh] flex flex-col items-center md:pt-[12.021vw] pt-[23.778vw] pb-[13.889vw]">
|
||||
<ShiftText
|
||||
className="left-1/2 overflow-clip text-[5.208vw] pb-[5vw] text-white text-center leading-[95%] tracking-[-0.02em] max-md:line2 !font-medium"
|
||||
// style={{ x: "-50%" }}
|
||||
viewportPadding={0}
|
||||
paragraphs={[
|
||||
<>
|
||||
Закончили разработку
|
||||
<br />и сдали 24 проекта
|
||||
</>,
|
||||
<>
|
||||
И теперь у нас более
|
||||
<br /> 70 проектов
|
||||
</>,
|
||||
]}
|
||||
shiftBreakpoints={[0, 0.3]}
|
||||
scrollProgress={projectsScroll}
|
||||
/>
|
||||
|
||||
<motion.div className="w-full md:h-[calc(100%-10.778vw)] h-[calc(100%-20.778vw)] grid grid-rows-3 md:gap-[1.563vw] gap-[2.222vw] origin-center mt-[5vw]">
|
||||
<motion.div
|
||||
style={{
|
||||
translateX: useTransform(projectsScroll, [0.1, 1], [0, -300]),
|
||||
}}
|
||||
className="flex md:gap-[1.563vw] gap-[2.222vw] justify-center"
|
||||
>
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/10.png" />
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/2.png" />
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/3.png" />
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/7.png" />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
style={{
|
||||
translateX: useTransform(projectsScroll, [0.1, 1], [0, 300]),
|
||||
}}
|
||||
className="flex md:gap-[1.563vw] gap-[2.222vw] justify-center"
|
||||
>
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/2.png" />
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/6.png" />
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/main.png" />
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/7.png" />
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/2.png" />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
style={{
|
||||
translateX: useTransform(projectsScroll, [0.1, 1], [0, -300]),
|
||||
}}
|
||||
className="flex md:gap-[1.563vw] gap-[2.222vw] justify-center"
|
||||
>
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/3.png" />
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/10.png" />
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/11.png" />
|
||||
<ResultsProjectsItem src="/img/pages/results/projects/6.png" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LatestProjects({
|
||||
latestScroll,
|
||||
}: {
|
||||
latestScroll: MotionValue<number>;
|
||||
}) {
|
||||
return (
|
||||
<div className="sticky h-[100vh] top-0 flex flex-col items-center ">
|
||||
<h2 className="text-[5.208vw] md:mt-[13.021vw] mt-[27.778vw] md:mb-[6.25vw] mb-[6.667vw] text-white text-center leading-[95%] tracking-[-0.02em] max-md:line2 !font-medium">
|
||||
Последними <br /> из которых стали
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col md:gap-[1.042vw] gap-[2.222vw] md:px-[4.818vw] px-[2.778vw] h-full pb-[2.778vw]">
|
||||
<div className="w-[94.444vw] p-[4.444vw] flex-1 flex flex-col items-center justify-between bg-center bg-cover no-repeat rounded-[4.444vw] bg-[url('/img/pages/results/projects/badayevskiy.jpg')]">
|
||||
<motion.img
|
||||
style={{
|
||||
opacity: useTransform(latestScroll, [0, 0.1], [0, 1]),
|
||||
x: useTransform(latestScroll, [0, 0.1], [-100, 0]),
|
||||
}}
|
||||
src="/img/pages/results/projects/badayevskiy_logo.svg"
|
||||
alt=""
|
||||
className="w-[56.111vw]"
|
||||
/>
|
||||
<motion.img
|
||||
style={{
|
||||
opacity: useTransform(latestScroll, [0, 0.1], [0, 1]),
|
||||
x: useTransform(latestScroll, [0, 0.1], [100, 0]),
|
||||
}}
|
||||
src="/img/pages/results/projects/badayevskiy_builder.svg"
|
||||
alt=""
|
||||
className="w-[19.444vw]"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[94.444vw] p-[4.444vw] flex-1 flex flex-col items-center justify-between md:bg-[top_0%_left_150%] bg-[top_0%_left_57%] bg-cover no-repeat rounded-[4.444vw] bg-[url('/img/pages/results/projects/rozhdestvenka.png')]">
|
||||
<motion.img
|
||||
style={{
|
||||
opacity: useTransform(latestScroll, [0, 0.1], [0, 1]),
|
||||
x: useTransform(latestScroll, [0, 0.1], [-100, 0]),
|
||||
}}
|
||||
src="/img/pages/results/projects/rozhdestvenka_logo.svg"
|
||||
alt=""
|
||||
className="w-[68.611vw]"
|
||||
/>
|
||||
<motion.img
|
||||
style={{
|
||||
opacity: useTransform(latestScroll, [0, 0.1], [0, 1]),
|
||||
x: useTransform(latestScroll, [0, 0.1], [100, 0]),
|
||||
}}
|
||||
src="/img/pages/results/projects/rozhdestvenka_builder.svg"
|
||||
alt=""
|
||||
className="w-[26.111vw]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user