This commit is contained in:
2023-12-13 16:03:51 +05:00
parent f198ff9dfe
commit 3842ec4327
34 changed files with 1239 additions and 512 deletions
+2
View File
@@ -18,6 +18,7 @@
"react-circular-progressbar": "^2.1.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.2.0",
"react-input-mask": "^2.0.4",
"react-rangeslider": "^2.2.0",
"react-router-dom": "^6.18.0",
"react-swipeable": "^7.0.1",
@@ -27,6 +28,7 @@
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@types/react-input-mask": "^3.0.5",
"@types/react-rangeslider": "^2.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

+556 -361
View File
@@ -22,7 +22,12 @@ import VideoSliderMobile from "./components/VideoSliderMobile";
// import { isMobile } from "react-device-detect";
import IProject from "./types/IProject";
import api from "./utils/api";
import ArrowRightIcon from "./components/icons/ArrowRightIcon";
import MailIcon from "./components/icons/MailIcon";
import PhoneIcon from "./components/icons/PhoneIcon";
import VKIcon from "./components/icons/VKIcon";
import YouTubeIcon from "./components/icons/YouTubeIcon";
import TelegramIcon from "./components/icons/TelegramIcon";
import FeedbackForm from "./components/FeedbackForm";
function App() {
const [selectedVideo, setSelectedVideo] = useState<string>(
@@ -49,283 +54,320 @@ function App() {
}, []);
return (
<div className="min-h-screen 2xl:px-10 xl:px-8 sm:px-6 px-4 overflow-x-clip">
<div className="relative conatiner mx-auto 2xl:max-w-screen-2xl">
<div className="flex justify-between py-6 2xl:mb-28 xl:mb-[88px] sm:mb-12 mb-14">
<div>
<img
src="/images/Logo.svg"
alt=""
className="sm:block hidden max-h-fit"
/>
<img
src="/images/LogoMobile.svg"
alt=""
className="sm:hidden block"
/>
</div>
<div className="flex sm:gap-8 gap-2">
<Button>
<span className="sm:inline hidden">Оставить заявку</span>
<span className="sm:hidden inline">Связаться</span>
</Button>
<div className="flex gap-1">
<button className="px-3 border-gradient rounded-full font-gilroy font-medium h-10 flex items-center justify-center w-12">
<span className="absolute">RU</span>
</button>
<button className="px-3 rounded-full font-gilroy font-medium leading-none h-10 flex items-center justify-center w-12">
<span className="absolute">EN</span>
</button>
<>
<div className="min-h-screen 2xl:px-10 xl:px-8 sm:px-6 px-4 overflow-x-clip">
<div className="relative conatiner mx-auto 2xl:max-w-screen-2xl">
<div className="flex justify-between py-6 2xl:mb-28 xl:mb-[88px] sm:mb-12 mb-14">
<div>
<img
src="/images/Logo.svg"
alt=""
className="sm:block hidden max-h-fit"
/>
<img
src="/images/LogoMobile.svg"
alt=""
className="sm:hidden block"
/>
</div>
</div>
</div>
<div className="flex sm:gap-8 gap-2">
<Button>
<span className="sm:inline hidden">Оставить заявку</span>
<span className="sm:hidden inline">Связаться</span>
</Button>
<div className="flex flex-col 2xl:gap-14 gap-6 2xl:mb-[120px] xl:mb-20 sm:mb-12 mb-8">
<h1 className="max-w-[1255px] font-gilroy 2xl:text-8xl sm:text-[64px] text-[40px] leading-none text-gradient font-medium">
Интерактивный инструмент
<br />
продаж{" "}
<span style={{ WebkitTextFillColor: "#fff" }}>
для застройщиков
</span>
</h1>
<p className="xl:text-xl text-base font-gilroy max-w-[650px] font-medium leading-[120%]">
Помогаем девелоперам эффективно демонстрировать свой объект.
Продавать больше и быстрее.
</p>
<div className="absolute -z-10 top-0 right-0 opacity-90 blur-[10px]">
<BlendingHeader />
</div>
</div>
<div className="2xl:mb-40 sm:mb-[120px] mb-20">
<video
src="https://graff.estate/videos/showreel_1080p_4000k_h264.mp4"
muted
autoPlay
loop
playsInline
preload="metadata"
className="aspect-video"
/>
</div>
<div className="flex flex-col 2xl:gap-16 xl:gap-10 gap-8 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="grid xl:grid-cols-4 xl:gap-0 sm:gap-6 gap-4">
<div className="col-span-full">
<Heading2>
Помогаем продавать{" "}
<span style={{ WebkitTextFillColor: "#fff" }}>
проще и{" "}
<span
className="text-[#52587A] custom-line-through"
style={{ WebkitTextFillColor: "#52587A" }}
>
быстрее
</span>{" "}
дороже
</span>
</Heading2>
</div>
<div className="relative flex items-center gap-4 col-span-full h-[400px]">
<p className="2xl:text-2xl text-sm font-gilroy font-medium min-w-[496px] ">
Мы собрали статистику{" "}
<span className="text-gradient font-semibold">за 13 лет</span>{" "}
работы
<br />
с застройщиками,{" "}
<span className="text-gradient font-semibold">
реализовав 31 проект
</span>
</p>
<div className="">
<img
src="/images/Map.png"
alt=""
className="relative top-8 -z-10"
/>
<div className="flex gap-1">
<button className="px-3 border-gradient rounded-full font-gilroy font-medium h-10 flex items-center justify-center w-12">
<span className="absolute">RU</span>
</button>
<button className="px-3 rounded-full font-gilroy font-medium leading-none h-10 flex items-center justify-center w-12">
<span className="absolute">EN</span>
</button>
</div>
</div>
</div>
<p className="text-gradient font-gilroy font-medium text-5xl w-fit">
Graff.estate
</p>
<div className="grid xl:grid-cols-4 sm:grid-cols-2 grid-cols-1 xl:gap-0 gap-3">
<div className="px-8 py-4 flex flex-col gap-4 border-l border-[#3D425C]">
<p className="2xl:text-base text-sm">На</p>
<p className="2xl:text-8xl text-[64px] font-gilroy font-medium leading-none">
18
<span className="2xl:text-2xl text-xl ml-1 font-semibold leading-[135%]">
%
</span>
</p>
<p className="2xl:text-base text-sm">
увеличивает конверсию из консультации в бронирование
</p>
</div>
<div className="px-8 py-4 flex flex-col gap-4 border-l border-[#3D425C]">
<p className="2xl:text-base text-sm">На</p>
<p className="2xl:text-8xl text-[64px] font-gilroy font-medium leading-none">
12
<span className="2xl:text-2xl text-xl ml-1 font-semibold leading-[135%]">
%
</span>
</p>
<p className="2xl:text-base text-sm">
увеличивает конверсию из бронирования в продажу
</p>
</div>
<div className="px-8 py-4 flex flex-col gap-4 border-l border-[#3D425C]">
<p className="2xl:text-base text-sm">До</p>
<p className="2xl:text-8xl text-[64px] font-gilroy font-medium leading-none">
2
<span className="2xl:text-2xl text-xl ml-1 font-semibold leading-[135%]">
раз
</span>
</p>
<p className="2xl:text-base text-sm">
сокращает время
<br />
реализации проекта
</p>
</div>
<div className="px-8 py-4 flex flex-col gap-4 border-l border-[#3D425C]">
<p className="2xl:text-base text-sm">До</p>
<p className="2xl:text-8xl text-[64px] font-gilroy font-medium leading-none">
4
<span className="2xl:text-2xl text-xl ml-1 font-semibold leading-[135%]">
раз
</span>
</p>
<p className="2xl:text-base text-sm">
сокращает время на подготовку рекламных материалов
</p>
<div className="flex flex-col 2xl:gap-14 gap-6 2xl:mb-[120px] xl:mb-20 sm:mb-12 mb-8">
<h1 className="max-w-[1255px] font-gilroy 2xl:text-8xl sm:text-[64px] text-[40px] leading-none text-gradient font-medium">
Интерактивный инструмент
<br />
продаж{" "}
<span style={{ WebkitTextFillColor: "#fff" }}>
для застройщиков
</span>
</h1>
<p className="xl:text-xl text-base font-gilroy max-w-[650px] font-medium leading-[120%]">
Помогаем девелоперам эффективно демонстрировать свой объект.
Продавать больше и быстрее.
</p>
<div className="absolute -z-10 top-0 right-0 opacity-90 blur-[10px]">
<BlendingHeader />
</div>
</div>
</div>
<div className="sm:grid grid-cols-2 xl:gap-x-8 sm:gap-x-6 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="2xl:mt-[220px] xl:mt-40 sm:mt-[104px]">
<ExampleCard
title="ЖК «Life Резиденция»"
company="ГК Паритет Девелопмент"
image="/images/Aivaz1.jpg"
/>
<ExampleCard
title="ЖК «Айвазовский City»"
company="ЭНКО"
image="/images/Aivaz3.jpg"
<div className="2xl:mb-40 sm:mb-[120px] mb-20">
<video
src="https://graff.estate/videos/showreel_1080p_4000k_h264.mp4"
muted
autoPlay
loop
playsInline
preload="metadata"
className="aspect-video"
/>
</div>
<div>
<ExampleCard
title="Авторский квартал Машаров"
company="Сибинтел Девелопмент"
image="/images/Aivaz2.jpg"
/>
<div className="flex flex-col 2xl:gap-16 xl:gap-10 gap-8 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="grid xl:grid-cols-4 xl:gap-0 sm:gap-6 gap-4">
<div className="col-span-full">
<Heading2>
Помогаем <br className="sm:hidden" /> продавать{" "}
<br className="2xl:hidden" />{" "}
<span style={{ WebkitTextFillColor: "#fff" }}>
проще <br className="sm:hidden" /> и{" "}
<span
style={{ WebkitTextFillColor: "#52587A" }}
className="relative custom-line-through"
>
быстрее
</span>{" "}
<br className="sm:hidden" />
дороже
</span>
</Heading2>
</div>
<div className="relative flex items-center gap-4 col-span-full h-[400px]">
<p className="2xl:text-2xl text-sm font-gilroy font-medium min-w-[496px] ">
Мы собрали статистику{" "}
<span className="text-gradient font-semibold">за 13 лет</span>{" "}
работы
<br />
с застройщиками,{" "}
<span className="text-gradient font-semibold">
реализовав 31 проект
</span>
</p>
<ExampleCard
title="ЖК «Сюжеты»"
company="Fortis Development"
image="/images/Aivaz4.jpg"
/>
</div>
</div>
<div className="xl:grid flex flex-col grid-cols-2 xl:gap-4 sm:gap-10 gap-8 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="flex flex-col gap-20">
<div className="xl:flex flex-col grid sm:grid-cols-2 xl:gap-8 gap-3">
<Heading2>
Функциональные
<br />
<span style={{ WebkitTextFillColor: "#fff" }}>возможности</span>
</Heading2>
<p className="xl:w-2/3 xl:text-base text-sm">
Интерактивная презентация увлекает покупателей и предоставляет
актуальную информацию о жилом комплексе, отвечая на все вопросы
и показывая важные особенности и преимущества объекта
</p>
<div className="">
<img
src="/images/Map.png"
alt=""
className="relative top-8 -z-10"
/>
</div>
</div>
</div>
<div className="pr-6 xl:block hidden">
<FeatureItem
title="Виртуальный тур по жилому комплексу"
desc="Клиент лично оценит угол инсоляции, малые архитектурные формы и ландшафт, перемещаясь по комплексу с помощью тапа."
video="https://graff.estate/videos/features/virtual_tour.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
<p className="text-gradient font-gilroy font-medium text-5xl w-fit">
Graff.estate
</p>
<div className="grid xl:grid-cols-4 sm:grid-cols-2 grid-cols-1 xl:gap-0 gap-3">
<div className="2xl:px-8 px-6 py-4 flex flex-col gap-4 border-l border-[#3D425C]">
<p className="2xl:text-base text-sm">На</p>
<p className="2xl:text-8xl text-[64px] font-gilroy font-medium leading-none">
18
<span className="2xl:text-2xl text-xl ml-1 font-semibold leading-[135%]">
%
</span>
</p>
<p className="2xl:text-base text-sm">
увеличивает конверсию из консультации в бронирование
</p>
</div>
<div className="2xl:px-8 px-6 py-4 flex flex-col gap-4 border-l border-[#3D425C]">
<p className="2xl:text-base text-sm">На</p>
<p className="2xl:text-8xl text-[64px] font-gilroy font-medium leading-none">
12
<span className="2xl:text-2xl text-xl ml-1 font-semibold leading-[135%]">
%
</span>
</p>
<p className="2xl:text-base text-sm">
увеличивает конверсию из бронирования в продажу
</p>
</div>
<div className="2xl:px-8 px-6 py-4 flex flex-col gap-4 border-l border-[#3D425C]">
<p className="2xl:text-base text-sm">До</p>
<p className="2xl:text-8xl text-[64px] font-gilroy font-medium leading-none">
2
<span className="2xl:text-2xl text-xl ml-1 font-semibold leading-[135%]">
раз
</span>
</p>
<p className="2xl:text-base text-sm">
сокращает время
<br />
реализации проекта
</p>
</div>
<div className="2xl:px-8 px-6 py-4 flex flex-col gap-4 border-l border-[#3D425C]">
<p className="2xl:text-base text-sm">До</p>
<p className="2xl:text-8xl text-[64px] font-gilroy font-medium leading-none">
4
<span className="2xl:text-2xl text-xl ml-1 font-semibold leading-[135%]">
раз
</span>
</p>
<p className="2xl:text-base text-sm">
сокращает время на подготовку рекламных материалов
</p>
</div>
</div>
</div>
<div className="sm:grid grid-cols-2 xl:gap-x-8 sm:gap-x-6 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="2xl:mt-[220px] xl:mt-40 sm:mt-[104px]">
<ExampleCard
title="ЖК «Life Резиденция»"
company="ГК Паритет Девелопмент"
image="/images/Aivaz1.jpg"
/>
<FeatureItem
title="Вся инфрастуктура на одном экране"
desc="Возможность оценить инфраструктуру района покажет важные для клиента точки интереса и время, за которое он сможет до них дойти."
video="https://graff.estate/videos/features/nks_infra.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
<ExampleCard
title="ЖК «Айвазовский City»"
company="ЭНКО"
image="/images/Aivaz3.jpg"
/>
<FeatureItem
title="Конфигуратор интерьера"
desc="Клиент может свободно выбирать мебель и дизайн с помощью конфигуратора интерьера. Возможно выбрать стиль всей квартиры или изменить отдельные детали."
video="https://graff.estate/videos/features/uralsky.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
</div>
<div>
<ExampleCard
title="Авторский квартал Машаров"
company="Сибинтел Девелопмент"
image="/images/Aivaz2.jpg"
/>
<FeatureItem
title="Параметрический поиск квартир"
desc="Фильтр позволит отметить конкретные преимущества, определить количество комнат, желаемый этаж, цену, и получить выборку подходящих вариантов."
video="https://graff.estate/videos/features/parametric.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Любой рендер за несколько секунд"
desc="Когда для рекламы вам понадобится любой объект с любого ракурса, просто сделайте фотографию внутри презентации."
video="https://graff.estate/videos/features/render.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Формирование вишлиста"
desc="Клиент может добавить варианты квартир в избранное, сравнить их между собой по основным параметрам и выбрать свою будущую квартиру."
video="https://graff.estate/videos/features/wish.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Интеграция с CRM-системой"
desc="Приложение передает информацию о клиенте в CRM-систему застройщика и получает актуальную информацию по ценам и статусам квартир."
video="https://graff.estate/videos/features/integra_crm.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Отправка коммерческого предложения"
desc="Коммерческое предложение с выбранными квартирами может быть отправлено клиенту на почту или распечатано и отдано лично в руки."
video="https://graff.estate/videos/features/send.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Интерактивная инсоляция"
desc="Функция позволяет в режиме реального времени увидеть уровень освещенности выбранной квартиры, а если вы изучаете экстерьер жилого комплекса – функция покажет архитектурную подсветку."
video=""
/>
<FeatureItem
title="Подбор квартир на генплане"
desc="Сделать генплан удобным инструментом выбора квартиры поможет подсветка выбранных квартир прямо на фасаде Жилого комплекса."
video=""
<ExampleCard
title="ЖК «Сюжеты»"
company="Fortis Development"
image="/images/Aivaz4.jpg"
/>
</div>
</div>
<div className="aspect-video sticky top-[25%] xl:block hidden">
<FeatureVideoViewBox video={selectedVideo} />
<div className="xl:grid flex flex-col grid-cols-2 xl:gap-4 sm:gap-10 gap-8 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="flex flex-col gap-20">
<div className="xl:flex flex-col grid sm:grid-cols-2 xl:gap-8 gap-3">
<Heading2>
Функциональные
<br />
<span style={{ WebkitTextFillColor: "#fff" }}>
возможности
</span>
</Heading2>
<p className="xl:w-2/3 xl:text-base text-sm">
Интерактивная презентация увлекает покупателей и предоставляет
актуальную информацию о жилом комплексе, отвечая на все
вопросы и показывая важные особенности и преимущества объекта
</p>
</div>
<div className="pr-6 xl:block hidden">
<FeatureItem
title="Виртуальный тур по жилому комплексу"
desc="Клиент лично оценит угол инсоляции, малые архитектурные формы и ландшафт, перемещаясь по комплексу с помощью тапа."
video="https://graff.estate/videos/features/virtual_tour.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Вся инфрастуктура на одном экране"
desc="Возможность оценить инфраструктуру района покажет важные для клиента точки интереса и время, за которое он сможет до них дойти."
video="https://graff.estate/videos/features/nks_infra.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Конфигуратор интерьера"
desc="Клиент может свободно выбирать мебель и дизайн с помощью конфигуратора интерьера. Возможно выбрать стиль всей квартиры или изменить отдельные детали."
video="https://graff.estate/videos/features/uralsky.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Параметрический поиск квартир"
desc="Фильтр позволит отметить конкретные преимущества, определить количество комнат, желаемый этаж, цену, и получить выборку подходящих вариантов."
video="https://graff.estate/videos/features/parametric.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Любой рендер за несколько секунд"
desc="Когда для рекламы вам понадобится любой объект с любого ракурса, просто сделайте фотографию внутри презентации."
video="https://graff.estate/videos/features/render.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Формирование вишлиста"
desc="Клиент может добавить варианты квартир в избранное, сравнить их между собой по основным параметрам и выбрать свою будущую квартиру."
video="https://graff.estate/videos/features/wish.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Интеграция с CRM-системой"
desc="Приложение передает информацию о клиенте в CRM-систему застройщика и получает актуальную информацию по ценам и статусам квартир."
video="https://graff.estate/videos/features/integra_crm.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Отправка коммерческого предложения"
desc="Коммерческое предложение с выбранными квартирами может быть отправлено клиенту на почту или распечатано и отдано лично в руки."
video="https://graff.estate/videos/features/send.mp4"
handleHoverStart={(video) => setSelectedVideo(video)}
/>
<FeatureItem
title="Интерактивная инсоляция"
desc="Функция позволяет в режиме реального времени увидеть уровень освещенности выбранной квартиры, а если вы изучаете экстерьер жилого комплекса – функция покажет архитектурную подсветку."
video=""
/>
<FeatureItem
title="Подбор квартир на генплане"
desc="Сделать генплан удобным инструментом выбора квартиры поможет подсветка выбранных квартир прямо на фасаде Жилого комплекса."
video=""
/>
</div>
</div>
<div className="aspect-video sticky top-[25%] xl:block hidden">
<FeatureVideoViewBox video={selectedVideo} />
</div>
<VideoSliderMobile />
</div>
<VideoSliderMobile />
</div>
<div className="relative 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="grid grid-cols-2 gap-4 mb-16">
<div className="flex flex-col gap-8">
<Heading2>
Graff.estate stream 
<br />
<span style={{ WebkitTextFillColor: "#fff" }}>
удаленная
<br />
демонстрация
<br />
жилого комплекса
</span>
</Heading2>
<Button>Узнать больше</Button>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col gap-12">
<p>
Высокий уровень графики и полное погружение покупателя в
процесс выбора квартиры
</p>
<img src="/images/Datamining.jpg" alt="" />
</div>
<div className="">
<p>
Местоположение и устройство значения не имеют. Нужен только
интернет
</p>
</div>
</div>
</div>
<div className="relative 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="grid grid-cols-4 mb-16">
{/* <div className="grid grid-cols-4 mb-16">
<Heading2 className="col-span-3">
Graff.estate stream —{" "}
<span style={{ WebkitTextFillColor: "#fff" }}>
@@ -362,142 +404,295 @@ function App() {
Демонстрация технологии
</p>
<Button icon={<ArrowRightIcon />}>Узнать больше</Button>
</div> */}
<div>
<StreamButton
icon="/icons/Revolution.svg"
title="МФК «Re:volution towers»"
location="Россия, Екатеринбург"
background="/images/stream/nks.jpg"
link="https://stream.graff.tech/?build=nksJukovaDev&location=a1"
/>
<StreamButton
icon="/icons/Residence.svg"
title="ЖК «Life Резиденция»"
location="Россия, Тюмень"
background="/images/stream/liferes.jpg"
link="https://stream.graff.tech/?build=lifeResidence&location=a1"
/>
<StreamButton
icon="/icons/Aivaz.svg"
title="ЖК «Айвазовский City»"
location="Россия, Тюмень"
background="/images/stream/aivaz.jpg"
link="https://stream.graff.tech/?build=Ivazowsky&location=a1"
/>
</div>
<div className="absolute -bottom-[2%] -right-[30%] -z-10 blur-[18px]">
<BlendStream />
</div>
</div>
<div>
<StreamButton
icon="/icons/Revolution.svg"
title="МФК «Re:volution towers»"
location="Россия, Екатеринбург"
background="/images/stream/nks.jpg"
link="https://stream.graff.tech/?build=nksJukovaDev&location=a1"
/>
<StreamButton
icon="/icons/Residence.svg"
title="ЖК «Life Резиденция»"
location="Россия, Тюмень"
background="/images/stream/liferes.jpg"
link="https://stream.graff.tech/?build=lifeResidence&location=a1"
/>
<StreamButton
icon="/icons/Aivaz.svg"
title="ЖК «Айвазовский City»"
location="Россия, Тюмень"
background="/images/stream/aivaz.jpg"
link="https://stream.graff.tech/?build=Ivazowsky&location=a1"
/>
<div className="grid grid-cols-2 xl:gap-4 gap-3 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="flex flex-col justify-between">
<div className="flex flex-col gap-8">
<Heading2>
Анализируем
<br />
<span style={{ WebkitTextFillColor: "#fff" }}>
поведение
<br />
пользователей
</span>
</Heading2>
<p className="xl:w-2/3 2xl:text-base text-sm">
Система внутренней аналитики программы собирает информацию о
поведении пользователя и эффективности работы менеджеров для
создания отчета, содержащего необходимые метрики
</p>
</div>
<div className="pb-8 border-b border-[#3D425C] -mt-0.5">
<p className="xl:w-4/5 2xl:text-xl text-base font-gilroy font-medium">
Полученный отчет позволяет сделать процесс демонстрации
жилого комплекса еще эффективнее
</p>
</div>
</div>
<img src="/images/Analysis.jpg" alt="" className="w-full" />
</div>
<div className="absolute -bottom-[2%] -right-[30%] -z-10 blur-[18px]">
<BlendStream />
<div className="grid grid-cols-2 gap-4 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="flex flex-col justify-between">
<div className="flex flex-col gap-8">
<Heading2>
Экскурсия{" "}
<span style={{ WebkitTextFillColor: "#fff" }}>
в виртуальной реальности
</span>
</Heading2>
<Button>Записаться в шоу-рум</Button>
</div>
<p className="font-gilroy text-2xl">
Клиент полностью погружается в воссозданную реальность,
чувствует удобство и уровень комфорта
</p>
</div>
<div className="flex flex-col gap-10">
<div className="grid grid-cols-2 gap-4">
<p>
Достаточно надеть шлем виртуальной реальности, чтобы
прогуляться, оценить и ощутить пространство
</p>
<img
src="/images/VR.png"
alt=""
className="px-6 pt-8 pb-5 border-t border-[#3D425C]"
/>
</div>
<img src="/images/VR2.jpg" alt="" />
</div>
</div>
</div>
{/* <div className="relative grid grid-cols-2 gap-4 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="flex flex-col gap-8">
<Heading2>
Экскурсия
<br />
<span style={{ WebkitTextFillColor: "#fff" }}>
в виртуальной реальности
</span>
</Heading2>
<p className="2xl:text-base text-sm">
Клиенту достаточно надеть шлем виртуальной реальности, чтобы
прогуляться, оценить и ощутить пространство{" "}
</p>
<Button className="sm:block hidden">Записаться в шоу-рум</Button>
</div>
<div className=""></div>
</div> */}
<div className="grid grid-cols-2 gap-4 2xl:mb-40 sm:mb-[120px] mb-20">
<div>
<div className="flex flex-col gap-8 mb-[204px]">
<div className="relative flex flex-col gap-16 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="flex flex-col gap-8">
<Heading2>
Анализируем
Оцените эффективность интерактивного
<br />
<span style={{ WebkitTextFillColor: "#fff" }}>
поведение пользователей
инструмента продаж
</span>
</Heading2>
<p className="w-2/3">
Система внутренней аналитики программы собирает информацию о
поведении пользователя и эффективности работы менеджеров для
создания отчета, содержащего необходимые метрики
</p>
<div className="grid grid-cols-2 gap-4">
<p className="2xl:w-2/3 w-1/2 2xl:text-base text-sm">
Мы изучили отраслевую аналитику на рынке продаж новых квартир
и на основе данных собрали калькулятор эффективности продукта
</p>
<p className="2xl:w-2/3 w-1/2 2xl:text-base text-sm">
При использовании проектного финансирования, главное это
время. Быстрее наполняются эскроу-счета меньше процент за
использование заемных денег
</p>
</div>
</div>
<div className="pb-8 border-b border-[#3D425C] -mt-0.5">
<p className="w-4/5 text-xl font-gilroy font-medium">
Полученный отчет позволяет сделать процесс демонстрации
жилого комплекса еще эффективнее
</p>
<Calc />
<div className="absolute right-0 top-0 blur-[18px] -z-10">
<BlendingCalc />
</div>
</div>
<img src="/images/Analysis.jpg" alt="" className="w-full" />
</div>
<div className="relative flex flex-col gap-16 2xl:mb-40 sm:mb-[120px] mb-20">
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-16 2xl:mb-40 sm:mb-[120px] mb-20">
<Heading2>Проекты</Heading2>
<div className="grid xl:grid-cols-3 sm:grid-cols-2 2xl:gap-8 xl:gap-6 gap-3">
{projects.map((project, index) => (
<ProjectCard key={index} {...project} />
))}
</div>
</div>
<div className="relative flex flex-col gap-16 2xl:mb-40 sm:mb-[120px] mb-20">
<Heading2>
Оцените эффективность интерактивного
<br />
Наши клиенты{" "}
<span style={{ WebkitTextFillColor: "#fff" }}>
инструмента продаж
в девелопменте
</span>
</Heading2>
<div className="grid grid-cols-2 gap-4">
<p className="w-2/3">
Мы изучили отраслевую аналитику на рынке продаж новых квартир и
на основе данных собрали калькулятор эффективности продукта
</p>
<p className="w-2/3">
При использовании проектного финансирования, главное это
время. Быстрее наполняются эскроу-счета меньше процент за
использование заемных денег
</p>
<img
src="/images/clients-logos.png"
alt=""
className="sm:block hidden"
/>
<img
src="/images/clients-logos-mobile.png"
alt=""
className="sm:hidden block"
/>
<div className="absolute top-0 right-0 blur-[34px]">
<BlendingClients />
</div>
</div>
<Calc />
<div className="lg:mt-40 sm:mt-[120px] mt-[88px]">
<div className="grid lg:grid-cols-4 grid-cols-1 lg:gap-4 gap-6">
<div className="col-span-1">
<div className="grid lg:grid-cols-1 sm:grid-cols-2 lg:gap-6 gap-4">
<p className="2xl:text-[64px] xl:text-5xl text-[40px] font-gilroy text-gradient font-medium w-fit leading-none">
Свяжитесь
<br />с нами
</p>
<p className="2xl:text-xl lg:text-lg font-gilroy font-semibold leading-tight">
Хотите увеличить конверсию?
<br />
Давайте обсудим детали!
</p>
</div>
</div>
<div className="absolute right-0 top-0 blur-[18px]">
<BlendingCalc />
<div className="lg:col-span-3">
<FeedbackForm />
</div>
</div>
</div>
</div>
<div className="flex flex-col gap-16 2xl:mb-40 sm:mb-[120px] mb-20">
<Heading2>Проекты</Heading2>
<div className="grid xl:grid-cols-3 sm:grid-cols-2 2xl:gap-8 xl:gap-6 gap-3">
{projects.map((project, index) => (
<ProjectCard key={index} {...project} />
))}
</div>
</div>
<div className="mt-[104px] relative">
<div className="absolute flex justify-center items-center w-full -top-8 left-32 blur-md">
<img src="/images/shapes/shape-2.svg" alt="" className="" />
</div>
<div className="relative flex flex-col gap-16 2xl:mb-40 sm:mb-[120px] mb-20">
<Heading2>
Наши клиенты{" "}
<span style={{ WebkitTextFillColor: "#fff" }}>в девелопменте</span>
</Heading2>
<img
src="/images/clients-logos.png"
alt=""
className="sm:block hidden"
/>
<img
src="/images/clients-logos-mobile.png"
alt=""
className="sm:hidden block"
/>
<div className="absolute top-0 right-0 blur-[34px]">
<BlendingClients />
<div className="relative grid lg:grid-cols-4 gap-4">
<div className="flex gap-4">
<p className="2xl:text-xl font-gilroy font-semibold">
Горячая линия
</p>
<div className="w-full h-px bg-[#3D425C]"></div>
</div>
<div className="space-y-2 2xl:pr-4 xl:pr-2">
<a
href="mailto:info@graff.tech"
className="2xl:h-16 h-14 px-6 py-4 2xl:text-base text-sm border rounded-full font-medium flex justify-between items-center w-full border-[#52587A] opacity-80 hover:opacity-100 transition-all"
>
<span>Написать</span>
<MailIcon className="lg:w-8 lg:h-8 w-6 h-6" />
</a>
<a
href="tel:88007700067"
className="2xl:h-16 h-14 px-6 py-4 2xl:text-base text-sm border rounded-full font-medium flex justify-between items-center w-full border-[#52587A] opacity-80 hover:opacity-100 transition-all"
>
<span>Позвонить</span>
<PhoneIcon className="lg:w-8 lg:h-8 w-6 h-6" />
</a>
</div>
<div className="sm:col-span-2 flex sm:justify-end lg:mt-0 mt-10">
<div className="lg:w-auto sm:w-1/2 w-full flex justify-between 2xl:gap-8 lg:gap-6 gap-4">
<p className="2xl:text-xl font-gilroy font-semibold 2xl:-mt-1.5 -mt-1">
Социальные
<br />
сети
</p>
<div className="flex gap-2 h-fit">
<a
href="https://www.youtube.com/@GRAFFtech"
target="_blank"
className="group border border-[#3D425C] xl:p-4 p-3 rounded-full hover:border-[#52587A] transition-all"
>
<YouTubeIcon className="2xl:w-8 2xl:h-8 w-6 h-6" />
</a>
<a
href="https://vk.com/graff.interactive"
target="_blank"
className="group border border-[#3D425C] xl:p-4 p-3 rounded-full hover:border-[#52587A] transition-all"
>
<VKIcon className="2xl:w-8 2xl:h-8 w-6 h-6" />
</a>
<a
href="https://t.me/GRAFFinteractive"
target="_blank"
className="border rounded-full border-[#52587A] xl:p-4 p-3 opacity-80 hover:opacity-100 transition-all"
>
<TelegramIcon className="2xl:w-8 2xl:h-8 w-6 h-6" />
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="mt-[50px] relative border-t border-[#3D425C] text-sm">
<div className="container mx-auto xl:px-8 max-w-[1600px]">
<div className="grid lg:grid-cols-4">
<div className="sm:col-span-2 lg:order-none order-last py-6 xl:px-0 px-6 flex sm:flex-row flex-col sm:gap-6 gap-4 lg:border-t-0 border-t border-[#3D425C]">
<div>
<img src="/images/Logo.svg" alt="" className="max-h-fit" />
</div>
<div className="flex flex-col sm:gap-1 gap-4">
<p className="flex sm:flex-row flex-col sm:gap-4 gap-1">
<a href="#" className="">
Политика конфиденциальности
</a>
<a href="https://graff.estate" className="">
graff.estate
</a>
</p>
<p className="text-xs text-[#C5C7CE]">
© 2023 GRAFF interactive. Все права защищены.
</p>
</div>
</div>
<div className="col-span-1 lg:border-l sm:border-b-0 border-b border-[#3D425C] xl:px-8 sm:px-6 px-4 py-6 flex flex-col justify-center">
<div className="flex justify-between items-center">
<div className="text-[#EBEBEB] flex flex-col gap-1">
<a href="mailto:info@graff.tech">info@graff.tech</a>
<a href="tel:88007700067">8 800 770 00 67</a>
</div>
<div className="w-12 h-12 border border-[#3D425C] rounded-full flex justify-center items-center">
RU
</div>
</div>
</div>
<div className="col-span-1 sm:border-l border-[#3D425C] xl:pl-8 xl:pr-0 sm:px-6 px-4 py-6 flex flex-col justify-center">
<div className="flex justify-between items-center">
<div className="text-[#EBEBEB] flex flex-col gap-1">
<a href="mailto:waseem@graff.tech">waseem@graff.tech</a>
<a href="tel:+971509388902">+971 50 938 8902</a>
</div>
<div className="w-12 h-12 border border-[#3D425C] rounded-full flex justify-center items-center">
UAE
</div>
</div>
</div>
</div>
</div>
</div>
</>
);
}
+6 -1
View File
@@ -4,6 +4,8 @@ interface ButtonProps {
children: ReactNode;
icon?: JSX.Element;
color?: "primary" | "secondary";
width?: "fit" | "full";
disabled?: boolean;
className?: string;
handleClick?: () => void;
}
@@ -12,18 +14,21 @@ function Button({
children,
color = "primary",
icon,
width = "fit",
disabled = false,
className,
handleClick,
}: ButtonProps) {
return (
<button
disabled={disabled}
onClick={handleClick}
className={`group relative px-6 py-2 rounded-full ${
(color === "primary" ? "bg-gradient" : "") ||
(color === "secondary" ? "outline outline-1 outline-[#3D425C]" : "")
} ${
icon ? "pr-4" : ""
} flex justify-between gap-1 items-center overflow-hidden w-fit ${className}`}
} flex justify-between gap-1 items-center overflow-hidden w-${width} ${className}`}
>
<span className="group-hover:opacity-10 opacity-0 bg-black transition-opacity absolute top-0 left-0 w-full h-full"></span>
<span className="relative font-gilroy font-medium">{children}</span>
+74 -47
View File
@@ -157,7 +157,7 @@ function Calc() {
}, [diffImplementationPeriod]);
return (
<div className="relative flex flex-col gap-16">
<div className="relative flex flex-col sm:gap-16 gap-8">
<div className="grid xl:grid-cols-4 sm:grid-cols-3">
<div className="xl:col-auto sm:col-span-full xl:block sm:grid grid-cols-2">
<CalcSelect
@@ -175,11 +175,10 @@ function Calc() {
}
}}
/>
<div className="border xl:border-t-0 xl:border-l sm:border-l-0 border-[#3D425C] 2xl:p-6 p-4">
<div className="border xl:border-t-0 xl:border-l sm:border-l-0 sm:border-t border-t-0 border-[#3D425C] 2xl:p-6 p-4 flex items-center">
<p className="text-[#52587A] text-xs leading-[120%]">
Установлены усредненные показатели по региону.
<br />
Источник:{" "}
<br className="2xl:block xl:hidden sm:block hidden" /> Источник:{" "}
<a
href="https://наш.дом.рф"
target="_blank"
@@ -200,7 +199,7 @@ function Calc() {
{(selectedRegion?.areaInComplex || 1500).toLocaleString()}
</p>
</div>
<div className="2xl:px-8 2xl:py-6 px-6 py-4 flex flex-col justify-between border border-l-0 xl:border-t border-t-0 border-[#3D425C]">
<div className="2xl:px-8 2xl:py-6 px-6 py-4 flex flex-col justify-between gap-6 border sm:border-l-0 border-l xl:border-t border-t-0 border-[#3D425C]">
<p className="text-sm">
Средняя площадь
<br />
@@ -210,7 +209,7 @@ function Calc() {
{(selectedRegion?.areaApartment || 100).toLocaleString()}
</p>
</div>
<div className="2xl:px-8 2xl:py-6 px-6 py-4 flex flex-col justify-between border border-l-0 xl:border-t border-t-0 border-[#3D425C]">
<div className="2xl:px-8 2xl:py-6 px-6 py-4 flex flex-col justify-between gap-6 border sm:border-l-0 border-l xl:border-t border-t-0 border-[#3D425C]">
<p className="text-sm">
Средняя стоимость
<br />
@@ -222,42 +221,50 @@ function Calc() {
</div>
</div>
<div className="grid grid-cols-4">
<div className="flex flex-col gap-6 mr-8 border-b border-[#3D425C]">
<div className="flex justify-between">
<p className="font-gilroy font-medium 2xl:text-base text-sm">
Очных консультаций в месяц
</p>
<p className="font-gilroy font-medium text-[#798FFF] 2xl:text-base text-sm">
{consultations}
</p>
</div>
<div className="py-[9px]">
<Slider
min={10}
max={1000}
step={10}
value={consultations}
onChange={(value) => setConsultations(value)}
tooltip={false}
/>
<div className="grid xl:grid-cols-4 grid-cols-2">
<div className="xl:flex flex-col xl:gap-6 xl:mr-8 xl:border-b border-[#3D425C] xl:col-auto col-span-full sm:grid grid-cols-2 gap-12 xl:mb-0 sm:mb-6 mb-8">
<div className="flex flex-col gap-6">
<div className="flex justify-between">
<p className="font-gilroy font-medium 2xl:text-base text-sm leading-none">
Очных консультаций в месяц
</p>
<p className="font-gilroy font-medium text-[#798FFF] 2xl:text-base text-sm leading-none">
{consultations}
</p>
</div>
<div className="py-[9px]">
<Slider
min={10}
max={1000}
step={10}
value={consultations}
onChange={(value) => setConsultations(value)}
tooltip={false}
/>
</div>
</div>
<Button
icon={isToolEnabled ? <CloseIcon /> : <ArrowRightIcon />}
icon={
isToolEnabled ? (
<CloseIcon className="2xl:w-8 w-6 2xl:h-8 h-6" />
) : (
<ArrowRightIcon className="2xl:w-8 2xl:h-8" />
)
}
color={isToolEnabled ? "secondary" : "primary"}
handleClick={() => setIsToolEnabled((prev) => !prev)}
className="px-6 py-4 w-full"
className="px-6 py-4 w-full h-fit self-center sm:flex hidden"
>
{isToolEnabled ? "Отключить" : "Включить"} инструмент
</Button>
</div>
<div className="col-span-3 grid grid-cols-2">
<div>
<div className="2xl:px-8 2xl:py-6 px-6 py-4 border border-[#3D425C] flex flex-col gap-4">
<div className="col-span-3 grid xl:grid-cols-2">
<div className="xl:block sm:grid grid-cols-2">
<div className="2xl:px-8 2xl:py-6 sm:px-6 sm:py-4 p-4 border border-[#3D425C] flex flex-col gap-4">
<p className="text-sm">Срок реализации</p>
<div className="grid grid-cols-2">
<div className="grid xl:grid-cols-2 gap-4">
<p
className={`2xl:text-5xl text-[32px] font-gilroy font-medium flex items-end gap-1.5 w-fit ${
isToolEnabled ? "text-gradient" : ""
@@ -268,7 +275,7 @@ function Calc() {
? implementationPeriod
: oldImplementationPeriod}
</span>
<span className="2xl:text-2xl text-xl leading-none">
<span className="2xl:text-2xl xl:text-xl text-2xl xl:leading-none">
{isToolEnabled
? implementationPeriodEnding
: oldImplementationPeriodEnding}
@@ -284,18 +291,18 @@ function Calc() {
<span className="text-[#798FFF]">
{diffImplementationPeriod} {diffImplementationPeriodEnding}
</span>{" "}
вы сократили срок
вы сократили
<br />
реализации проекта
срок реализации проекта
</p>
</div>
</div>
<div className="2xl:px-8 2xl:py-6 px-6 py-4 border border-t-0 border-[#3D425C] flex flex-col gap-4">
<div className="2xl:px-8 2xl:py-6 sm:px-6 sm:py-4 p-4 border xl:border-t-0 sm:border-t border-t-0 xl:border-l sm:border-l-0 border-l border-[#3D425C] flex flex-col gap-4">
<p className="text-sm">Месячный доход</p>
<div className="grid grid-cols-2">
<div className="grid xl:grid-cols-2 gap-4">
<p
className={`2xl:text-5xl text-[32px] font-gilroy font-medium flex items-end gap-1.5 w-fit ${
className={`2xl:text-5xl text-[32px] font-gilroy font-medium flex items-end gap-1.5 w-fit ${
isToolEnabled ? "text-gradient" : ""
}`}
>
@@ -305,7 +312,7 @@ function Calc() {
<span
className={`${
isToolEnabled ? "text-gradient" : ""
} 2xl:text-2xl text-xl leading-none`}
} 2xl:text-2xl xl:text-xl text-2xl xl:leading-none`}
>
млн руб.
</span>
@@ -319,16 +326,18 @@ function Calc() {
<span className="text-[#798FFF]">
{diffMonthlyIncome} млн руб.
</span>{" "}
в месяц вы заработали больше
в месяц
<br />
вы заработали больше
</p>
</div>
</div>
</div>
<div className="2xl:px-8 2xl:py-6 px-6 py-4 border border-l-0 border-[#3D425C] flex flex-col gap-4">
<div className="2xl:px-8 2xl:py-6 sm:px-6 sm:py-4 p-4 border xl:border-l-0 xl:border-t border-t-0 border-[#3D425C] flex flex-col gap-4">
<p className="text-sm">Статистика продаж</p>
<div className="flex flex-col 2xl:gap-3 gap-1.5">
<div className="flex gap-4">
<div className="flex flex-col 2xl:gap-3 gap-1.5 justify-around">
<div className="flex flex-col 2xl:gap-3 sm:gap-1.5 gap-2">
<div className="flex sm:gap-4 gap-2">
<div className="flex flex-col 2xl:gap-3 sm:gap-1.5 gap-2 justify-around">
<p className="2xl:text-sm text-xs">100%</p>
<p className="2xl:text-sm text-xs">
{isToolEnabled ? 48 : 30}%
@@ -337,7 +346,7 @@ function Calc() {
{isToolEnabled ? 42 : 30}%
</p>
</div>
<div className="w-full flex flex-col 2xl:gap-3 gap-1.5 justify-around">
<div className="w-full flex flex-col 2xl:gap-3 sm:gap-1.5 gap-2 justify-around">
<div className="bg-[#212431] rounded-full flex justify-center">
<div
className={`2xl:py-3.5 py-2 rounded-full w-full transition-all ${
@@ -374,12 +383,15 @@ function Calc() {
</div>
</div>
</div>
<div className="flex flex-col 2xl:gap-3 gap-1.5 justify-around">
<div className="flex flex-col 2xl:gap-3 sm:gap-1.5 gap-2 justify-around">
<p className="2xl:text-sm text-xs text-[#8088A7] whitespace-nowrap">
Консультаций в офисе
<span className="sm:inline hidden">
Консультаций в офисе
</span>
<span className="sm:hidden inline">Консультации</span>
</p>
<p className="2xl:text-sm text-xs text-[#8088A7] whitespace-nowrap">
Бронь квартиры
Бронь<span className="sm:inline hidden"> квартиры</span>
</p>
<p className="2xl:text-sm text-xs text-[#8088A7] whitespace-nowrap">
Продажа
@@ -390,6 +402,21 @@ function Calc() {
</div>
</div>
</div>
<Button
icon={
isToolEnabled ? (
<CloseIcon className="2xl:w-8 w-6 2xl:h-8 h-6" />
) : (
<ArrowRightIcon className="2xl:w-8 2xl:h-8" />
)
}
color={isToolEnabled ? "secondary" : "primary"}
handleClick={() => setIsToolEnabled((prev) => !prev)}
className="px-6 py-4 w-full h-fit self-center sm:hidden flex"
>
{isToolEnabled ? "Отключить" : "Включить"} инструмент
</Button>
</div>
);
}
+2 -2
View File
@@ -24,12 +24,12 @@ function FeatureItem({
>
<div className="relative border-b border-[#3D425C] py-16 flex justify-between items-center h-36 cursor-default overflow-hidden ">
<div className="overflow-hidden py-2">
<p className="group-hover:opacity-0 group-hover:-translate-y-[125%] transition-all text-[32px] font-gilroy font-medium leading-none duration-300">
<p className="group-hover:opacity-0 group-hover:-translate-y-[125%] transition-all 2xl:text-[32px] xl:text-2xl text-xl font-gilroy font-medium leading-none duration-300">
{title}
</p>
</div>
<p className="group-hover:opacity-100 group-hover:translate-y-0 opacity-0 translate-y-[100%] absolute transition-all w-4/5 duration-300">
<p className="group-hover:opacity-100 group-hover:translate-y-0 opacity-0 translate-y-[100%] absolute transition-all w-4/5 duration-300 2xl:text-base text-sm">
{desc}
</p>
+174
View File
@@ -0,0 +1,174 @@
import ky from "ky";
import { ChangeEvent, FormEvent, useState } from "react";
import InputMask from "react-input-mask";
import AsteriskIcon from "./icons/AsteriskIcon";
import SendIcon from "./icons/SendIcon";
import CheckGradientIcon from "./icons/CheckGradientIcon";
import LoaderIcon from "./icons/LoaderIcon";
import Button from "./Button";
function FeedbackForm() {
const [name, setName] = useState<string>("");
const [phone, setPhone] = useState<string>("");
const [email, setEmail] = useState<string>("");
const [description, setDescription] = useState<string>("");
const [isSend, setIsSend] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
async function sendMail(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
setIsLoading(true);
try {
await ky
.post(`https://estate.graff.tech/api/mail1`, {
json: {
fullname: name,
phone,
email,
request: description,
},
})
.json();
setIsSend(true);
setIsLoading(false);
} catch (error) {
setIsLoading(false);
if (error instanceof Error) {
alert(error.message);
}
}
}
return (
<form
className="grid lg:grid-cols-3 sm:grid-cols-2 relative"
onSubmit={(e) => void sendMail(e)}
>
<div className="relative col-span-1">
<input
required
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="feedback-field bg-transparent border border-[#3D425C] rounded-none lg:p-6 lg:pt-14 p-4 pt-12 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full"
/>
<p className="feedback-placeholder lg:text-base text-sm absolute lg:top-4 top-5 left-0 w-full lg:p-6 p-4 opacity-50 transition-all pointer-events-none flex justify-between">
<span>Имя</span>
<AsteriskIcon />
</p>
</div>
<div className="relative">
<InputMask
required
type="tel"
mask={"+999999999999999"}
maskChar={null}
value={phone}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setPhone(e.target.value)
}
className={[
"feedback-field bg-transparent border rounded-none sm:border-l-0 sm:border-t border-t-0 border-l border-[#3D425C] lg:p-6 lg:pt-14 p-4 pt-12 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full",
].join(" ")}
/>
<p className="feedback-placeholder lg:text-base text-sm absolute lg:top-4 top-5 left-0 w-full lg:p-6 p-4 opacity-50 transition-all pointer-events-none flex justify-between">
<span>Телефон</span>
<AsteriskIcon />
</p>
</div>
<div className="relative lg:col-span-1 sm:col-span-2 col-span-1">
<input
required
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="feedback-field bg-transparent border rounded-none lg:border-l-0 lg:border-t border-t-0 border-[#3D425C] lg:p-6 lg:pt-14 p-4 pt-12 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full"
/>
<p className="feedback-placeholder lg:text-base text-sm absolute lg:top-4 top-5 left-0 w-full lg:p-6 p-4 opacity-50 transition-all pointer-events-none flex justify-between">
<span>Email</span>
<AsteriskIcon />
</p>
</div>
<div className="relative lg:col-span-3 sm:col-span-2 h-[194px]">
<textarea
placeholder="Опишите вашу задачу"
value={description}
onChange={(e) => setDescription(e.target.value)}
className="feedback-field bg-transparent resize-none border rounded-none border-t-0 border-[#3D425C] lg:p-6 p-4 h-full outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full"
></textarea>
</div>
<div className="2xl:pt-6 2xl:pr-6 pt-4 sm:pr-4 lg:order-none order-last flex items-center">
<Button
width="full"
disabled={isLoading}
icon={
isLoading ? (
<LoaderIcon className="relative 2xl:w-8 2xl:h-8 w-6 h-6 animate-spin" />
) : (
<SendIcon className="relative 2xl:w-8 2xl:h-8 w-6 h-6" />
)
}
className="py-4"
>
Отправить
</Button>
</div>
<div className="border sm:border-t-0 border-t border-[#3D425C] 2xl:p-6 p-4 sm:mt-0 mt-6 flex items-center">
<div className="text-xs leading-tight">
Нажимая кнопку отправить, вы принимаете{" "}
<a className="text-[#798FFF] cursor-pointer opacity-95 hover:opacity-100 transition-all">
условия использования
</a>{" "}
и{" "}
<a className="text-[#798FFF] cursor-pointer opacity-95 hover:opacity-100 transition-all">
политику конфиденциальности
</a>
</div>
</div>
<div className="border border-t-0 sm:border-l-0 border-[#3D425C] 2xl:p-6 p-4 text-xs flex items-center gap-2">
<div className="flex gap-2">
<div className="">
<AsteriskIcon />
</div>
<p></p>
<p className="leading-tight">
Звездочкой отмечены обязательные
<br />
для заполнения поля
</p>
</div>
</div>
{isSend && (
<div className="absolute top-0 left-0 w-full h-full bg-[#14161F] border border-[#3D425C] p-6 flex flex-col justify-between">
<p className="text-gradient text-xl font-gilroy leading-tight font-semibold flex items-center gap-2">
<span>Заявка отправлена</span>
<CheckGradientIcon className="lg:w-8 lg:h-8 w-6 h-6" />
</p>
<div className="flex flex-col gap-2">
<p className="font-gilroy leading-snug lg:text-2xl text-xl font-semibold">
Спасибо за подачу заявки!
</p>
<p className="lg:w-1/2 sm:w-2/3 lg:text-base text-sm">
Мы ценим ваш интерес к нашей компании и в ближайшее время свяжемся
с вами для уточнения деталей проекта.
</p>
</div>
</div>
)}
</form>
);
}
export default FeedbackForm;
+1 -1
View File
@@ -5,7 +5,7 @@ interface Heading2Props {
className?: string;
}
function Heading2({ children, className }: Heading2Props) {
function Heading2({ children, className = "" }: Heading2Props) {
return (
<h2
className={`2xl:text-[64px] xl:text-5xl text-[40px] text-gradient font-gilroy font-medium leading-none w-fit ${className}`}
+7 -2
View File
@@ -1,4 +1,5 @@
import useModalStore from "../stores/useModalStore";
import CloseIcon from "./icons/CloseIcon";
function ModalContainer() {
const [modal, setModal] = useModalStore((state) => [
@@ -9,12 +10,16 @@ function ModalContainer() {
if (modal) {
return (
<div
onClick={() => setModal(null)}
className={`min-h-screen p-8 absolute top-0 left-0 w-full flex justify-center items-center bg-black bg-opacity-30 overflow-auto cursor-pointer transition-opacity`}
// onClick={() => setModal(null)}
className={`min-h-screen p-8 absolute top-0 left-0 w-full flex justify-center items-center bg-black bg-opacity-30 overflow-auto transition-opacity`}
>
<div onClick={(e) => e.stopPropagation()} className="cursor-default">
{modal}
</div>
<button onClick={() => setModal(null)} className="absolute top-4 right-4">
<CloseIcon />
</button>
</div>
);
}
+3 -2
View File
@@ -4,6 +4,7 @@ import TouchScreenIcon from "./icons/TouchScreenIcon";
import VRIcon from "./icons/VRIcon";
import MobileIcon from "./icons/MobileIcon";
import IProject from "../types/IProject";
import { format } from "date-fns";
function ProjectCard({
name,
@@ -11,7 +12,7 @@ function ProjectCard({
city,
image,
stage = 6,
releaseYear = 2023,
releaseDate = format(new Date(), "yyyy-MM-dd"),
devices = [],
}: IProject) {
const stagePercentage = Math.round((100 / 6) * stage);
@@ -50,7 +51,7 @@ function ProjectCard({
) : (
<div className="bg-gradient py-2.5 px-4 rounded-full">
<p className="font-gilroy font-medium leading-none">
{releaseYear}
{new Date(releaseDate).getFullYear()}
</p>
</div>
)}
+32 -25
View File
@@ -16,34 +16,41 @@ function StreamButton({
link,
}: StreamButton) {
return (
<a
href={link}
target="_blank"
className="group relative grid grid-cols-4 gap-4 h-36 border-b border-[#3D425C] cursor-pointer"
>
<div className="col-span-2 flex items-center gap-12">
<img src={icon} alt="" />
<p className="text-2xl font-gilroy font-medium">{title}</p>
</div>
<div className="flex items-center">
<p>{location}</p>
</div>
<div className="flex items-center justify-between">
<p>Демоверсия</p>
<ArrowIcon />
</div>
<div
className="group-hover:opacity-100 opacity-0 transition-opacity duration-300 absolute top-0 left-0 w-full h-full bg-cover bg-center bg-no-repeat"
style={{ backgroundImage: `url(${background})` }}
<>
<a
href={link}
target="_blank"
className="group relative xl:grid hidden grid-cols-4 gap-4 h-36 border-b border-[#3D425C] cursor-pointer"
>
<div className="w-full h-full bg-black bg-opacity-40 flex flex-col gap-2 justify-center items-center">
<p className="text-2xl font-gilroy font-medium">
начать демонстрацию
</p>
<p>{title}</p>
<div className="col-span-2 flex items-center xl:gap-12 gap-6">
<img src={icon} alt="" />
<p className="xl:text-2xl text-xl font-gilroy font-medium">{title}</p>
</div>
<div className="flex items-center xl:text-base text-sm">
<p>{location}</p>
</div>
<div className="xl:flex hidden items-center justify-between">
<p>Демоверсия</p>
<ArrowIcon />
</div>
<div
className="group-hover:opacity-100 opacity-0 transition-opacity duration-300 absolute top-0 left-0 w-full h-full bg-cover bg-center bg-no-repeat"
style={{ backgroundImage: `url(${background})` }}
>
<div className="w-full h-full bg-black bg-opacity-40 flex flex-col gap-2 justify-center items-center">
<p className="text-2xl font-gilroy font-medium">
начать демонстрацию
</p>
<p>{title}</p>
</div>
</div>
</a>
<div className="xl:hidden grid grid-cols-2">
<div className="">test 1</div>
<div className="">test 2</div>
</div>
</a>
</>
);
}
+8 -5
View File
@@ -104,7 +104,10 @@ function VideoSliderMobile() {
}, [activeIndex]);
return (
<div {...handlers} className="xl:hidden flex flex-col sm:gap-6 gap-4 border-b border-[#3D425C] pb-5">
<div
{...handlers}
className="xl:hidden flex flex-col sm:gap-6 gap-4 border-b border-[#3D425C] pb-5"
>
<div
// ref={videosContainerRef}
className={`relative flex sm:gap-[88px] items-start transition-all duration-500`}
@@ -112,17 +115,17 @@ function VideoSliderMobile() {
>
{items.map((item, index) => (
<motion.video
ref={videoRefs[index]}
key={index}
ref={videoRefs[index]}
src={item.video}
muted
loop
playsInline
preload="metadata"
className={`relative aspect-video transition-all duration-500 sm:w-[calc(100%-88px)] ${
index === activeIndex
? ""
: "sm:scale-[70%] scale-[80%] sm:-translate-y-[15%] -translate-y-[10%] sm:-translate-x-[calc(15%+88px-12px)] -translate-x-[calc(10%-8px)]"
index !== activeIndex
? "sm:scale-[70%] scale-[80%] sm:-translate-y-[15%] -translate-y-[10%] sm:-translate-x-[calc(15%+88px-12px)] -translate-x-[calc(10%-8px)]"
: ""
}`}
/>
))}
@@ -0,0 +1,18 @@
function AsteriskIcon() {
return (
<svg
width="12"
height="13"
viewBox="0 0 12 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.81534 12.2727L4.9858 7.58523L1.02273 10.0994L0 8.30966L4.17614 6.13636L0 3.96307L1.02273 2.1733L4.9858 4.6875L4.81534 0H6.8608L6.69034 4.6875L10.6534 2.1733L11.6761 3.96307L7.5 6.13636L11.6761 8.30966L10.6534 10.0994L6.69034 7.58523L6.8608 12.2727H4.81534Z"
fill="white"
/>
</svg>
);
}
export default AsteriskIcon;
@@ -0,0 +1,41 @@
interface IconProps {
className?: string;
}
function CheckGradientIcon({ className }: IconProps) {
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g id="Icon/Check">
<path
id="Vector 1836 (Stroke)"
fillRule="evenodd"
clipRule="evenodd"
d="M26.3298 9.78103C26.819 10.3314 26.7694 11.1742 26.2191 11.6634L14.2191 22.3301C13.6914 22.7991 12.8896 22.7755 12.3904 22.2763L5.72378 15.6097C5.20308 15.089 5.20308 14.2447 5.72378 13.724C6.24448 13.2033 7.0887 13.2033 7.60939 13.724L13.3871 19.5017L24.4474 9.6703C24.9978 9.18107 25.8406 9.23065 26.3298 9.78103Z"
fill="url(#paint0_linear_53_10278)"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_53_10278"
x1="5.33325"
y1="32.1907"
x2="29.4088"
y2="29.927"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.167052" stopColor="#798FFF" />
<stop offset="0.963542" stopColor="#D375FF" />
</linearGradient>
</defs>
</svg>
);
}
export default CheckGradientIcon;
+6 -1
View File
@@ -1,4 +1,8 @@
function CloseIcon() {
interface IconProps {
className?: string;
}
function CloseIcon({ className }: IconProps) {
return (
<svg
width="32"
@@ -6,6 +10,7 @@ function CloseIcon() {
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g opacity="0.8">
<path
@@ -0,0 +1,39 @@
interface IconProps {
className?: string;
}
function LoaderIcon({ className }: IconProps) {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g id="Icon/Circle">
<path
id="Ellipse 221"
d="M18.9999 12C19.5523 12 20.0064 11.5505 19.9376 11.0025C19.745 9.46994 19.1116 8.01808 18.1039 6.82871C16.8797 5.38372 15.1826 4.41989 13.3144 4.10872C11.4463 3.79755 9.52839 4.15922 7.90189 5.12938C6.27539 6.09953 5.04582 7.61525 4.43194 9.40685C3.81806 11.1985 3.85968 13.1497 4.54941 14.9135C5.23914 16.6773 6.53224 18.1392 8.19863 19.0391C9.86502 19.9391 11.7966 20.2186 13.6498 19.828C15.1751 19.5066 16.5658 18.7483 17.6578 17.6559C18.0483 17.2653 17.9652 16.6317 17.529 16.2929C17.0927 15.9542 16.4693 16.0409 16.0629 16.4149C15.2735 17.1413 14.2989 17.6472 13.2373 17.8709C11.8475 18.1638 10.3988 17.9541 9.14904 17.2792C7.89928 16.6043 6.92948 15.5079 6.4122 14.1851C5.89491 12.8623 5.86369 11.3989 6.32409 10.0552C6.78449 8.71152 7.70665 7.57476 8.92649 6.84716C10.1463 6.11956 11.5847 5.84832 12.9858 6.08169C14.3869 6.31506 15.6597 7.03791 16.5778 8.12163C17.2791 8.94938 17.7387 9.94667 17.9167 11.0045C18.0083 11.5492 18.4476 12 18.9999 12Z"
fill="url(#paint0_angular_0_1327)"
/>
</g>
<defs>
<radialGradient
id="paint0_angular_0_1327"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(12 12) rotate(45) scale(7.9196)"
>
<stop offset="0.874517" stopColor="white" />
<stop offset="0.982613" stopColor="white" stopOpacity="0" />
</radialGradient>
</defs>
</svg>
);
}
export default LoaderIcon;
+31
View File
@@ -0,0 +1,31 @@
interface IconProps {
className?: string;
}
function MailIcon({ className }: IconProps) {
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g id="Icon/Mail" opacity="0.8">
<g id="Vector">
<path
d="M4 11.3111C4 10.7929 4.5653 10.4728 5.00965 10.7394L15.314 16.9216C15.7363 17.1749 16.2637 17.1749 16.686 16.9216L26.9903 10.739C27.4347 10.4724 28 10.7924 28 11.3106V22.6665C28 23.4029 27.403 23.9998 26.6667 23.9998H5.33333C4.59695 23.9998 4 23.4029 4 22.6665V11.3111Z"
fill="white"
/>
<path
d="M4.73055 7.90483C4.15076 7.55696 4.3974 6.6665 5.07354 6.6665H26.9265C27.6026 6.6665 27.8492 7.55696 27.2695 7.90483L16.686 14.2549C16.2638 14.5083 15.7362 14.5083 15.314 14.2549L4.73055 7.90483Z"
fill="white"
/>
</g>
</g>
</svg>
);
}
export default MailIcon;
+26
View File
@@ -0,0 +1,26 @@
interface IconProps {
className?: string;
}
function PhoneIcon({ className }: IconProps) {
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g id="Icon/Phone" opacity="0.8">
<path
id="phone"
d="M13.2467 8.50638L10.4411 5.70883C9.93919 5.20838 9.12547 5.20839 8.62358 5.70883L6.29613 8.02957C3.39974 10.9176 7.6513 17.8 10.9553 21.0945C14.2395 24.3692 21.0664 28.5923 23.9628 25.7043L26.2902 23.3835C26.7921 22.8831 26.7921 22.0717 26.2902 21.5713L23.4846 18.7737C22.9827 18.2733 22.169 18.2733 21.6671 18.7737L19.5362 20.8984C19.4067 21.0276 19.2417 21.1047 19.0714 21.0372C18.6101 20.8542 17.4325 20.137 14.6538 17.4066C11.8643 14.6655 11.1485 13.4312 10.9713 12.9337C10.9044 12.7461 10.992 12.5669 11.1332 12.4261L13.2473 10.318C13.7492 9.8176 13.7486 9.00683 13.2467 8.50638Z"
fill="white"
/>
</g>
</svg>
);
}
export default PhoneIcon;
+28
View File
@@ -0,0 +1,28 @@
interface IconProps {
className?: string;
}
function SendIcon({ className }: IconProps) {
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g id="Icon/Send">
<path
id="Vector 164 (Stroke)"
fillRule="evenodd"
clipRule="evenodd"
d="M25.2649 8.4215C25.432 7.92033 25.2874 7.36779 24.8963 7.01269C24.5051 6.65759 23.9412 6.56689 23.4585 6.78145L6.56115 14.2914C4.82296 15.0639 5.043 17.5979 6.88835 18.0593L10.0482 18.8492C10.6608 19.0024 11.3097 18.8572 11.7987 18.4577L19.8248 11.8996C20.0112 11.7473 20.2583 11.9935 20.1068 12.1805L14.0759 19.62C13.5817 20.2296 13.4898 21.0719 13.8407 21.7738L15.8654 25.8233C16.6623 27.417 18.9882 27.2516 19.5517 25.5613L25.2649 8.4215Z"
fill="white"
/>
</g>
</svg>
);
}
export default SendIcon;
@@ -0,0 +1,28 @@
interface IconProps {
className?: string;
}
function TelegramIcon({ className }: IconProps) {
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g id="Icon/Telegram" opacity="0.8">
<path
id="Path-3"
fillRule="evenodd"
clipRule="evenodd"
d="M4.35909 15.8481C10.7925 12.8123 15.0929 10.8501 17.2259 9.88748C23.3497 7.1478 24.6226 6.6665 25.4483 6.6665C25.6203 6.6665 26.0331 6.70353 26.3083 6.92566C26.5148 7.11078 26.5836 7.36994 26.618 7.55505C26.6524 7.74016 26.6868 8.14741 26.6524 8.48062C26.3083 12.2199 24.8978 21.3645 24.1409 25.5481C23.8313 27.3252 23.2121 27.9176 22.6272 27.9916C21.3543 28.1027 20.3566 27.066 19.1181 26.2145C17.1915 24.8447 16.0906 23.9932 14.1984 22.6603C12.031 21.1054 13.4415 20.2539 14.6801 18.884C14.9897 18.5138 20.6662 12.9974 20.7694 12.4791C20.7694 12.405 20.8038 12.1829 20.6662 12.0718C20.5286 11.9608 20.3566 11.9978 20.219 12.0348C20.0126 12.0718 16.9163 14.2932 10.8957 18.6619C10.0012 19.3283 9.20995 19.6245 8.48748 19.6245C7.69621 19.6245 6.18246 19.1432 5.04716 18.7359C3.67103 18.2546 2.57013 17.9955 2.67334 17.181C2.77655 16.7367 3.327 16.2924 4.35909 15.8481Z"
fill="white"
/>
</g>
</svg>
);
}
export default TelegramIcon;
+28
View File
@@ -0,0 +1,28 @@
interface IconProps {
className?: string;
}
function VKIcon({ className }: IconProps) {
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g id="Icon/VK" opacity="0.8">
<path
id="Vector"
fillRule="evenodd"
clipRule="evenodd"
d="M5.44687 8H2.38363C1.50842 8 1.33337 8.39579 1.33337 8.83368C1.33337 9.61432 2.37137 13.4863 6.16892 18.6072C8.70003 22.1044 12.2665 24 15.5118 24C17.4592 24 17.6998 23.5789 17.6998 22.8539V20.2105C17.6998 19.3684 17.8836 19.2 18.5007 19.2C18.9558 19.2 19.7347 19.4189 21.5516 21.1048C23.6285 23.1032 23.9707 24 25.1391 24H28.2024C29.0776 24 29.5152 23.5789 29.2623 22.7478C28.9866 21.92 27.9949 20.7183 26.6786 19.2943C25.9645 18.4825 24.8932 17.6076 24.5694 17.1705C24.1143 16.6088 24.2455 16.3587 24.5694 15.8594C24.5694 15.8594 28.3013 10.8008 28.6916 9.08379C28.8859 8.45895 28.6916 8 27.7639 8H24.7015C23.9226 8 23.5638 8.39579 23.3695 8.83368C23.3695 8.83368 21.8116 12.4867 19.6052 14.8598C18.891 15.5469 18.5663 15.7659 18.1768 15.7659C17.9825 15.7659 17.7007 15.5469 17.7007 14.9229V9.08379C17.7007 8.33432 17.474 8 16.8255 8H12.0118C11.5252 8 11.2329 8.34779 11.2329 8.6779C11.2329 9.38779 12.3357 9.552 12.4494 11.5495V15.8905C12.4494 16.8421 12.2709 17.0147 11.8805 17.0147C10.8425 17.0147 8.31669 13.3448 6.81833 9.14611C6.52425 8.32842 6.22931 8 5.44687 8Z"
fill="white"
/>
</g>
</svg>
);
}
export default VKIcon;
@@ -0,0 +1,28 @@
interface IconProps {
className?: string;
}
function YouTubeIcon({ className }: IconProps) {
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g id="Icon/YouTube" opacity="0.8">
<path
id="Exclude"
fillRule="evenodd"
clipRule="evenodd"
d="M26.4187 7.2304C27.566 7.54067 28.4695 8.45509 28.7762 9.61601C29.3333 11.7204 29.3333 16.1109 29.3333 16.1109C29.3333 16.1109 29.3333 20.5015 28.7762 22.6058C28.4695 23.7666 27.566 24.6811 26.4187 24.9913C24.339 25.5554 16 25.5554 16 25.5554C16 25.5554 7.66111 25.5554 5.58145 24.9913C4.43418 24.6811 3.53051 23.7666 3.22388 22.6058C2.66663 20.5015 2.66663 16.1109 2.66663 16.1109C2.66663 16.1109 2.66663 11.7204 3.22388 9.61601C3.53051 8.45509 4.43418 7.54067 5.58145 7.2304C7.66111 6.6665 16 6.6665 16 6.6665C16 6.6665 24.339 6.6665 26.4187 7.2304ZM13.7777 12.2219V19.9997L20.4444 16.1109L13.7777 12.2219Z"
fill="white"
/>
</g>
</svg>
);
}
export default YouTubeIcon;
@@ -5,6 +5,7 @@ import api from "../../utils/api";
import Button from "../Button";
import IProject from "../../types/IProject";
import useModalStore from "../../stores/useModalStore";
import { format } from "date-fns";
function CreateProjectModal() {
const [project, setProject] = useState<IProject>({
@@ -12,6 +13,7 @@ function CreateProjectModal() {
company: "",
city: "",
image: "",
releaseDate: format(new Date(), "yyyy-MM-dd"),
devices: [],
});
@@ -157,34 +159,19 @@ function CreateProjectModal() {
</div>
<div className="flex flex-col gap-1">
<label className="text-sm">Год релиза</label>
<select
<label className="text-sm">Дата релиза</label>
<input
type="date"
required
value={project.releaseYear || ""}
className="border border-neutral-500 px-3 py-2 rounded-lg outline-none"
value={project.releaseDate}
onChange={(e) =>
setProject((prev) => ({ ...prev, releaseYear: +e.target.value }))
setProject((prev) => ({
...prev,
releaseDate: e.target.value,
}))
}
>
<option value="" disabled>
Выберите год релиза
</option>
<option value={2024}>2024</option>
<option value={2023}>2023</option>
<option value={2022}>2022</option>
<option value={2021}>2021</option>
<option value={2020}>2020</option>
<option value={2019}>2019</option>
<option value={2018}>2018</option>
<option value={2017}>2017</option>
<option value={2016}>2016</option>
<option value={2015}>2015</option>
<option value={2014}>2014</option>
<option value={2013}>2013</option>
<option value={2012}>2012</option>
<option value={2011}>2011</option>
<option value={2010}>2010</option>
</select>
className="border border-neutral-500 px-3 py-2 rounded-lg outline-none"
/>
</div>
<div className="flex flex-col gap-1">
@@ -281,9 +268,7 @@ function CreateProjectModal() {
</div>
<div className="col-span-full flex justify-end">
<Button className="text-white outline-none">
Добавить проект
</Button>
<Button className="text-white outline-none">Добавить проект</Button>
</div>
</form>
</div>
@@ -5,6 +5,7 @@ import api from "../../utils/api";
import Button from "../Button";
import IProject from "../../types/IProject";
import useModalStore from "../../stores/useModalStore";
import { format, parseISO } from "date-fns";
interface EditProjectModalProps {
projectId: string;
@@ -16,6 +17,7 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
company: "",
city: "",
image: "",
releaseDate: "2023-01-01",
devices: [],
});
@@ -72,6 +74,7 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
async function getProject() {
try {
const project: IProject = await api.get(`projects/${projectId}`).json();
project.releaseDate = format(parseISO(project.releaseDate), "yyyy-MM-dd");
setProject(project);
} catch (error) {
@@ -177,34 +180,19 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
</div>
<div className="flex flex-col gap-1">
<label className="text-sm">Год релиза</label>
<select
<label className="text-sm">Дата релиза</label>
<input
type="date"
required
value={project.releaseYear || ""}
className="border border-neutral-500 px-3 py-2 rounded-lg outline-none"
value={project.releaseDate}
onChange={(e) =>
setProject((prev) => ({ ...prev, releaseYear: +e.target.value }))
setProject((prev) => ({
...prev,
releaseDate: e.target.value,
}))
}
>
<option value="" disabled>
Выберите год релиза
</option>
<option value={2024}>2024</option>
<option value={2023}>2023</option>
<option value={2022}>2022</option>
<option value={2021}>2021</option>
<option value={2020}>2020</option>
<option value={2019}>2019</option>
<option value={2018}>2018</option>
<option value={2017}>2017</option>
<option value={2016}>2016</option>
<option value={2015}>2015</option>
<option value={2014}>2014</option>
<option value={2013}>2013</option>
<option value={2012}>2012</option>
<option value={2011}>2011</option>
<option value={2010}>2010</option>
</select>
className="border border-neutral-500 px-3 py-2 rounded-lg outline-none"
/>
</div>
<div className="flex flex-col gap-1">
+22 -4
View File
@@ -60,10 +60,6 @@ body {
/* Custom Line Through */
.custom-line-through {
position: relative;
}
.custom-line-through::before {
content: "";
position: absolute;
@@ -91,3 +87,25 @@ body {
.button {
@apply 2xl:text-[40px] xl:text-2xl sm:text-base text-sm;
}
/* Feedback Form */
.feedback-field:focus ~ .feedback-placeholder {
top: 0;
}
.feedback-field:focus ~ .feedback-placeholder-2 {
opacity: 0;
}
.feedback-field:valid ~ .feedback-placeholder {
top: 0;
}
.feedback-field:valid ~ .feedback-placeholder-2 {
opacity: 0;
}
.feedback-field::placeholder {
@apply lg:text-base text-sm font-semibold text-[#77787d];
}
+5
View File
@@ -3,6 +3,7 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from "./App.tsx";
import "./index.css";
import ProjectsPage from "./pages/ProjectsPage.tsx";
import TestPage from "./pages/TestPage.tsx";
const router = createBrowserRouter([
{
@@ -13,6 +14,10 @@ const router = createBrowserRouter([
path: "/projects",
element: <ProjectsPage />,
},
{
path: "/test",
element: <TestPage />,
},
]);
ReactDOM.createRoot(document.getElementById("root")!).render(
+11
View File
@@ -0,0 +1,11 @@
function TestPage() {
return (
<video
src="https://graff.estate/videos/features/nks_infra.mp4"
preload="metadata"
className="video-loader"
></video>
);
}
export default TestPage;
+2 -2
View File
@@ -7,8 +7,8 @@ interface IProject {
city: string;
image: string;
stage?: number;
releaseYear?: number;
devices?: Device[];
releaseDate: string;
devices: Device[];
}
export default IProject;
+3 -3
View File
@@ -1,7 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
});
+30 -1
View File
@@ -346,6 +346,13 @@
dependencies:
"@types/react" "*"
"@types/react-input-mask@^3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@types/react-input-mask/-/react-input-mask-3.0.5.tgz#9fbe9a984b3299419a6071dbf697ac2cae2abd2d"
integrity sha512-vQ1x6ykwjDrDrJZq1zw5/uQ+nqGHUV6bWscsVZJ/qsNwNXWxZm7KRBHLJ5k6TQt3MHjhpoYHzPH6FwjVSZODHA==
dependencies:
"@types/react" "*"
"@types/react-rangeslider@^2.2.7":
version "2.2.7"
resolved "https://registry.yarnpkg.com/@types/react-rangeslider/-/react-rangeslider-2.2.7.tgz#13f8edcec3f09b6c6b7669828e2765d294ce0c53"
@@ -1049,6 +1056,13 @@ inherits@2:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
dependencies:
loose-envify "^1.0.0"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@@ -1164,7 +1178,7 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
loose-envify@^1.1.0, loose-envify@^1.4.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -1429,6 +1443,14 @@ react-dom@^18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react-input-mask@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-input-mask/-/react-input-mask-2.0.4.tgz#9ade5cf8196f4a856dbf010820fe75a795f3eb14"
integrity sha512-1hwzMr/aO9tXfiroiVCx5EtKohKwLk/NT8QlJXHQ4N+yJJFyUuMT+zfTpLBwX/lK3PkuMlievIffncpMZ3HGRQ==
dependencies:
invariant "^2.2.4"
warning "^4.0.2"
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -1738,6 +1760,13 @@ vite@^4.4.5:
optionalDependencies:
fsevents "~2.3.2"
warning@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
dependencies:
loose-envify "^1.0.0"
which@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+2 -2
View File
@@ -22,8 +22,8 @@ const projectSchema = new Schema(
type: Number,
default: 1,
},
releaseYear: {
type: Number,
releaseDate: {
type: Date,
required: true,
},
devices: {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB