upd
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 377 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 102 KiB |
+4
-4
@@ -94,12 +94,12 @@ html {
|
||||
@apply text-[clamp(14px,14px+(100vw-360px)/1240*2,16px)] leading-none;
|
||||
}
|
||||
|
||||
.headline1{
|
||||
@apply font-medium text-[1.667vw] leading-[1.944vw] tracking-[-0.02em] md:max-lg:text-[3.125vw] md:max-lg:leading-[3.646vw] max-md:text-[6.667vw] max-md:leading-[7.778vw]
|
||||
.headline1 {
|
||||
@apply font-medium text-[1.667vw] leading-[1.944vw] tracking-[-0.02em] md:max-lg:text-[3.125vw] md:max-lg:leading-[3.646vw] max-md:text-[6.667vw] max-md:leading-[7.778vw];
|
||||
}
|
||||
|
||||
.headline2{
|
||||
@apply font-medium text-[1.389vw] leading-[1.667vw] tracking-[-0.02em] md:max-lg:text-[2.083vw] md:max-lg:leading-[2.344vw] max-md:text-[4.444vw] max-md:leading-[4.444vw] max-md:font-normal
|
||||
.headline2 {
|
||||
@apply font-medium text-[1.389vw] leading-[1.667vw] tracking-[-0.02em] md:max-lg:text-[2.083vw] md:max-lg:leading-[2.344vw] max-md:text-base max-md:leading-[4.444vw] max-md:font-normal;
|
||||
}
|
||||
|
||||
.text-gradient {
|
||||
|
||||
@@ -15,7 +15,6 @@ import { useOnClickOutside } from "usehooks-ts";
|
||||
import { Products } from "./Products";
|
||||
import { useAuthStore } from "@/stores/useAuthStore";
|
||||
import { getQueryClient } from "@/lib/queryClient";
|
||||
import { LogoIcon } from "../icons/LogoIcon";
|
||||
import GraffIcon from "../icons/GraffIcon";
|
||||
import CloseIcon from "../icons/CloseIcon";
|
||||
import BurgerIcon from "../icons/BurgerIcon";
|
||||
@@ -66,13 +65,13 @@ export function Header() {
|
||||
<Link
|
||||
href={"/"}
|
||||
ref={logoRef}
|
||||
className="outline-none cursor-pointer max-lg:hidden"
|
||||
className="max-lg:hidden cursor-pointer outline-none"
|
||||
>
|
||||
<div className="w-[13.333vw] aspect-[4/1]">
|
||||
<LogoHorIcon />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="relative flex justify-center flex-1 m-auto">
|
||||
<div className="flex relative flex-1 justify-center m-auto">
|
||||
<AnimatePresence>
|
||||
<motion.nav
|
||||
ref={navRef}
|
||||
@@ -93,7 +92,7 @@ export function Header() {
|
||||
)}
|
||||
<div
|
||||
ref={productsBtnRef}
|
||||
className="flex items-center max-md:hidden"
|
||||
className="max-md:hidden flex items-center"
|
||||
>
|
||||
<button
|
||||
className={
|
||||
@@ -122,7 +121,7 @@ export function Header() {
|
||||
href={"/projects"}
|
||||
text={"Проекты"}
|
||||
/>
|
||||
<div className="flex justify-center flex-1 md:justify-end">
|
||||
<div className="md:justify-end flex flex-1 justify-center">
|
||||
<Link
|
||||
href={"/form"}
|
||||
className="btnm bg-gradient font-medium lg:px-[1.667vw] lg:py-[1.181vw] py-[17px] px-6 text-nowrap lg:rounded-[1.111vw] rounded-2xl flex items-center"
|
||||
@@ -130,7 +129,7 @@ export function Header() {
|
||||
Оставить заявку
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex md:hidden md:justify-enda" ref={burgerBtnRef}>
|
||||
<div className="md:hidden md:justify-enda flex" ref={burgerBtnRef}>
|
||||
<button
|
||||
className="!border-none p-[18px] hover:bg-[#232425] rounded-2xl active:opacity-50 outline-none cursor-pointer"
|
||||
onClick={() => setBurgerOpened((prev) => !prev)}
|
||||
@@ -188,10 +187,10 @@ export function Header() {
|
||||
<Products />
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-1 btnm opacity-60">Контакты:</p>
|
||||
<p className="btnm mb-1 opacity-60">Контакты:</p>
|
||||
<Link
|
||||
href={"tel:88007700067"}
|
||||
className="font-medium outline-none accent"
|
||||
className="accent font-medium outline-none"
|
||||
>
|
||||
8 800 770 00 67
|
||||
</Link>
|
||||
@@ -237,7 +236,7 @@ export function Header() {
|
||||
alt="кейс dprofile"
|
||||
className="absolute bottom-0 left-0 rotate-[2.56deg]"
|
||||
/>
|
||||
<p className="font-medium btnm">Смотреть кейс</p>
|
||||
<p className="btnm font-medium">Смотреть кейс</p>
|
||||
</a>
|
||||
) : (
|
||||
<div className="bg-[#B5F54E] p-[0.764vw] self-center rounded-[0.556vw] max-lg:hidden">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { motion } from "framer-motion";
|
||||
import React from "react";
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
|
||||
export default function AboutAchievementsCard({
|
||||
img,
|
||||
@@ -15,29 +16,79 @@ export default function AboutAchievementsCard({
|
||||
imageClassName?: string;
|
||||
labelClassName?: string;
|
||||
}) {
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
||||
const [cardTransform, setCardTransform] = useState({ x: 0, y: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
setMousePosition({ x: e.clientX, y: e.clientY });
|
||||
};
|
||||
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
return () => window.removeEventListener("mousemove", handleMouseMove);
|
||||
}, []);
|
||||
|
||||
const { isLg } = useMediaQueries();
|
||||
|
||||
useEffect(() => {
|
||||
if (!cardRef.current || !isLg) return;
|
||||
|
||||
const card = cardRef.current;
|
||||
const rect = card.getBoundingClientRect();
|
||||
const cardCenterX = rect.left + rect.width / 2;
|
||||
const cardCenterY = rect.top + rect.height / 2;
|
||||
|
||||
const deltaX = mousePosition.x - cardCenterX;
|
||||
const deltaY = mousePosition.y - cardCenterY;
|
||||
const distance = Math.hypot(deltaX, deltaY);
|
||||
|
||||
const maxDistance = 1000;
|
||||
const maxOffset = 20;
|
||||
|
||||
if (distance < maxDistance) {
|
||||
const strength = 1 - distance / maxDistance;
|
||||
const offsetX = (deltaX / distance) * strength * maxOffset;
|
||||
const offsetY = (deltaY / distance) * strength * maxOffset;
|
||||
|
||||
setCardTransform({
|
||||
x: isNaN(offsetX) ? 0 : offsetX,
|
||||
y: isNaN(offsetY) ? 0 : offsetY,
|
||||
});
|
||||
} else {
|
||||
setCardTransform({ x: 0, y: 0 });
|
||||
}
|
||||
}, [isLg, mousePosition]);
|
||||
|
||||
return (
|
||||
<div
|
||||
<motion.div
|
||||
ref={cardRef}
|
||||
animate={
|
||||
isLg
|
||||
? {
|
||||
x: cardTransform.x,
|
||||
y: cardTransform.y,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 150,
|
||||
damping: 15,
|
||||
mass: 0.1,
|
||||
}}
|
||||
className={
|
||||
"w-[15.486vw] aspect-[223/240] rounded-[16px] bg-[radial-gradient(circle,rgba(0,0,0,1)_-70%,rgba(34,36,37,1)_100%);] flex flex-col items-center justify-between p-[1.111vw] absolute overflow-hidden md:max-lg:p-[2.083vw] max-md:p-[4.444vw] " +
|
||||
"w-[15.486vw] aspect-[223/240] group rounded-[16px] bg-[radial-gradient(circle,rgba(0,0,0,1)_-70%,rgba(34,36,37,1)_100%);] flex flex-col items-center justify-between p-[1.111vw] absolute overflow-hidden md:max-lg:p-[2.083vw] max-md:p-[4.444vw] " +
|
||||
className
|
||||
}
|
||||
>
|
||||
<motion.img
|
||||
style={{
|
||||
scale: 1,
|
||||
}}
|
||||
whileHover={{
|
||||
scale: 1.15,
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
}}
|
||||
<img
|
||||
src={img}
|
||||
className={
|
||||
"z-10 w-[9.444vw] md:max-lg:w-[20.833vw] max-md:w-[36.944vw] my-auto " +
|
||||
"z-10 w-[9.444vw] md:max-lg:w-[20.833vw] max-md:w-[36.944vw] my-auto group-hover:scale-110 transition-all duration-300 " +
|
||||
imageClassName
|
||||
}
|
||||
alt=""
|
||||
alt={title}
|
||||
/>
|
||||
<p
|
||||
className={
|
||||
@@ -47,6 +98,6 @@ export default function AboutAchievementsCard({
|
||||
>
|
||||
{title}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,14 +10,14 @@ import { ArticleCard } from "../BlogPage/ArticleCard";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useGetArticlesQuery } from "@/queries/getArticles";
|
||||
import AchievementsCardsMobile from "./AchievementsCardsMobile";
|
||||
import { useParallax } from "@/hooks/useParallax";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export default function AboutExperience() {
|
||||
const searchParams = useSearchParams();
|
||||
const parallaxRef = useRef(null);
|
||||
const { data: articles } = useGetArticlesQuery(searchParams.getAll("tags"));
|
||||
|
||||
useParallax(parallaxRef);
|
||||
const parallaxRef = useRef(null);
|
||||
|
||||
const { data: articles } = useGetArticlesQuery(searchParams.getAll("tags"));
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -39,7 +39,10 @@ export default function AboutExperience() {
|
||||
md:max-lg:grid-cols-2 md:max-lg:grid-rows-4 md:max-lg:[grid-template-areas:'a_d'_'b_b'_'b_b'_'c_e']
|
||||
max-md:flex max-md:flex-col max-md:gap-[3.333vw]"
|
||||
>
|
||||
<div
|
||||
<motion.div
|
||||
initial={{ y: 100, opacity: 0 }}
|
||||
whileInView={{ y: 0, opacity: 1, repeatCount: 0 }}
|
||||
transition={{ delay: 0.4, bounce: 0, duration: 0.5 }}
|
||||
className="[grid-area:a] w-full aspect-[340/360] rounded-2xl border border-[#FFFFFF1A] flex flex-col items-center justify-between p-[1.667vw]
|
||||
md:max-lg:p-[3.125vw]
|
||||
max-md:p-[4.444vw] max-md:aspect-[340/320]"
|
||||
@@ -51,25 +54,31 @@ export default function AboutExperience() {
|
||||
max-md:w-[70vw]"
|
||||
alt=""
|
||||
/>
|
||||
<span className="text-center headline1">
|
||||
<span className="headline1 text-center">
|
||||
Архитектурные <br /> визуализации
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: 100, opacity: 0 }}
|
||||
whileInView={{ y: 0, opacity: 1, repeatCount: 0 }}
|
||||
transition={{ delay: 0.6, bounce: 0, duration: 0.5 }}
|
||||
className="relative overflow-hidden [grid-area:d] w-full aspect-[340/360] rounded-2xl border border-[#FFFFFF1A] flex flex-col-reverse items-center justify-between p-[1.667vw]
|
||||
md:max-lg:p-[3.125vw]
|
||||
max-md:p-[4.444vw] max-md:aspect-[340/320]"
|
||||
>
|
||||
<span className="z-10 text-center headline1">
|
||||
<span className="headline1 z-10 text-center">
|
||||
Удаленная демонстрация
|
||||
</span>
|
||||
<img
|
||||
src={"/img/pages/about/experience/demonstrartion.png"}
|
||||
alt=""
|
||||
className="absolute bottom-0 w-full max-lg:top-0"
|
||||
className="max-lg:top-0 absolute bottom-0 w-full"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: 100, opacity: 0 }}
|
||||
whileInView={{ y: 0, opacity: 1, repeatCount: 0 }}
|
||||
transition={{ bounce: 0, duration: 0.5 }}
|
||||
className="relative [grid-area:b] rounded-2xl
|
||||
max-lg:border max-lg:border-[#FFFFFF1A] md:max-lg:p-[3.125vw]
|
||||
max-md:p-[4.444vw] max-md:aspect-[340/320]"
|
||||
@@ -82,20 +91,26 @@ export default function AboutExperience() {
|
||||
<span className="absolute text-center headline1 bottom-[3.125vw] left-1/2 -translate-x-1/2 z-10">
|
||||
Интерактивные <br /> презентации
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: 100, opacity: 0 }}
|
||||
whileInView={{ y: 0, opacity: 1, repeatCount: 0 }}
|
||||
transition={{ delay: 0.8, bounce: 0, duration: 0.5 }}
|
||||
className="relative overflow-hidden [grid-area:c] w-full aspect-[340/360] rounded-2xl border border-[#FFFFFF1A] flex flex-col-reverse items-center justify-between p-[1.667vw]
|
||||
md:max-lg:p-[3.125vw]
|
||||
max-md:p-[4.444vw] max-md:aspect-[340/320]"
|
||||
>
|
||||
<span className="z-10 text-center headline1">Сайты</span>
|
||||
<span className="headline1 z-10 text-center">Сайты</span>
|
||||
<img
|
||||
src={"/img/pages/about/experience/sites.png"}
|
||||
alt=""
|
||||
className="absolute bottom-0 h-full max-lg:top-0 max-md:w-full"
|
||||
className="max-lg:top-0 max-md:w-full absolute bottom-0 h-full"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: 100, opacity: 0 }}
|
||||
whileInView={{ y: 0, opacity: 1, repeatCount: 0 }}
|
||||
transition={{ delay: 1, bounce: 0, duration: 0.5 }}
|
||||
className="[grid-area:e] w-full aspect-[340/360] rounded-2xl border border-[#FFFFFF1A] flex flex-col items-center justify-between p-[1.667vw]
|
||||
md:max-lg:p-[3.125vw]
|
||||
max-md:p-[4.444vw] max-md:aspect-[340/320]"
|
||||
@@ -107,10 +122,10 @@ export default function AboutExperience() {
|
||||
max-md:w-[71vw] "
|
||||
alt=""
|
||||
/>
|
||||
<span className="text-center headline1">
|
||||
<span className="headline1 text-center">
|
||||
Виртуальные туры <br /> по 360 сферам
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -121,7 +136,7 @@ export default function AboutExperience() {
|
||||
md:max-lg:text-[5.208vw]
|
||||
max-md:text-[8.889vw] max-md:mb-[11.111vw]"
|
||||
>
|
||||
Каждый год мы принимаем участие <br className="hidden max-lg:block" />{" "}
|
||||
Каждый год мы принимаем участие <br className="max-lg:block hidden" />{" "}
|
||||
<span className="text-transparent">
|
||||
в самых крупных российских выставках
|
||||
</span>
|
||||
@@ -131,7 +146,7 @@ export default function AboutExperience() {
|
||||
<div className="flex justify-between max-lg:flex-col md:max-lg:gap-[4.167vw]">
|
||||
<div className="w-[23.611vw] max-lg:w-full max-md:mb-[8.889vw]">
|
||||
<h3 className="headline1 mb-[0.833vw] mdLmax-lg:mb-[1.563vw] max-md:mb-[3.333vw]">
|
||||
За нашей спиной участие <br className="hidden lg:block" /> в 40
|
||||
За нашей спиной участие <br className="lg:block hidden" /> в 40
|
||||
выставках
|
||||
</h3>
|
||||
<p className="headline1 text-[#7A7A7A]">
|
||||
@@ -160,11 +175,11 @@ export default function AboutExperience() {
|
||||
|
||||
<div className="flex justify-between max-lg:flex-col md:max-lg:gap-[4.167vw] max-md:gap-[8.889vw]">
|
||||
<p className="w-[23.611vw] headline1 text-[#7A7A7A] max-lg:w-full">
|
||||
<span className="text-white headline1">
|
||||
<span className="headline1 text-white">
|
||||
Ближайшие мероприятия, <br />
|
||||
</span>
|
||||
где каждый девелопер может лично протестировать{" "}
|
||||
<br className="hidden md:max-lg:block" /> и оценить функционал
|
||||
<br className="md:max-lg:block hidden" /> и оценить функционал
|
||||
наших интерактивных макетов недвижимости
|
||||
</p>
|
||||
<div className="flex justify-between gap-x-3 max-md:flex-col max-md:gap-[3.333vw]">
|
||||
@@ -208,32 +223,32 @@ export default function AboutExperience() {
|
||||
<AboutAchievementsCard
|
||||
img={"/img/pages/about/experience/cards/table.png"}
|
||||
title="Наш продукт зарегистрирован и включен в реестр отечественного ПО"
|
||||
className="top-0 left-[16.389vw] max-lg:hidden "
|
||||
className="!top-0 left-[16.389vw] max-lg:hidden"
|
||||
/>
|
||||
<AboutAchievementsCard
|
||||
img={"/img/pages/about/experience/cards/wow.png"}
|
||||
title="Заняли 1 место на WOW AWARDS 2024 совместно с застройщиком Upside Development"
|
||||
className="top-[8.333vw] left-0 max-lg:hidden "
|
||||
className="!top-[8.333vw] left-0 max-lg:hidden"
|
||||
/>
|
||||
<AboutAchievementsCard
|
||||
img={"/img/pages/about/experience/cards/sk.png"}
|
||||
title="Мы резиденты Сколково"
|
||||
className="bottom-0 left-[8.194vw] max-lg:hidden "
|
||||
className="!bottom-0 left-[8.194vw] max-lg:hidden"
|
||||
/>
|
||||
<AboutAchievementsCard
|
||||
img={"/img/pages/about/experience/cards/map.png"}
|
||||
title="Наши интерактивные столы стоят уже в 16 городах России"
|
||||
className="top-0 right-[8.194vw] max-lg:hidden "
|
||||
className="!top-0 right-[8.194vw] max-lg:hidden"
|
||||
/>
|
||||
<AboutAchievementsCard
|
||||
img={"/img/pages/about/experience/cards/dp.png"}
|
||||
title="Каждый наш проект, получил одну или несколько наград на платформе Dprofile"
|
||||
className="bottom-[7.708vw] right-0 max-lg:hidden "
|
||||
className="!bottom-[7.708vw] right-0 max-lg:hidden"
|
||||
/>
|
||||
<AboutAchievementsCard
|
||||
img={"/img/pages/about/experience/cards/build_up.png"}
|
||||
title="В 2023 году мы выиграли Build UP в категории IT"
|
||||
className="bottom-0 right-[16.389vw] max-lg:hidden "
|
||||
className="!bottom-0 right-[16.389vw] max-lg:hidden"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,17 +18,18 @@ export function AboutHero() {
|
||||
max-lg:relative md:max-lg:aspect-[736/441]
|
||||
max-md:aspect-[340/512] max-md:rounded-b-[4.444vw]"
|
||||
></video>
|
||||
<div className="bg-[#00000033] absolute inset-0 max-lg:hidden h-dvh" />
|
||||
<div className="relative z-11 pt-[16.25vw] max-lg:pt-0 bg-clip-text">
|
||||
<h1
|
||||
className="text-center font-medium text-[6.667vw] leading-[95%] tracking-[-4%]
|
||||
md:max-lg:tracking-[-0.02em] md:max-lg:leading-[85%] md:max-lg:text-[6.25vw]
|
||||
max-md:text-[11.111vw] max-md:leading-[85%] max-md:tracking-[-0.04em]"
|
||||
>
|
||||
GRAFF.estate — <br className="hidden max-md:block" /> IT компания,
|
||||
<br /> разрабатывающая <br className="hidden md:max-lg:block" />{" "}
|
||||
GRAFF.estate — <br className="max-md:block hidden" /> IT компания,
|
||||
<br /> разрабатывающая <br className="md:max-lg:block hidden" />{" "}
|
||||
передовые
|
||||
<br className="md:max-lg:hidden" /> инструменты продаж{" "}
|
||||
<br className="hidden max-md:block" /> для{" "}
|
||||
<br className="max-md:block hidden" /> для{" "}
|
||||
<br className="max-lg:hidden" /> застройщиков
|
||||
</h1>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
import React, { useEffect } from "react";
|
||||
import { AboutHero } from "./AboutHero";
|
||||
import AboutExperience from "./AboutExperience";
|
||||
import { Statistics } from "../MainPage/Statistics";
|
||||
import AboutTeam from "./AboutTeam";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
export default function AboutMain() {
|
||||
useEffect(() => {
|
||||
@@ -23,6 +23,13 @@ export default function AboutMain() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const AboutExperience = dynamic(
|
||||
() => import("./AboutExperience").then((mod) => mod.default),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-[11.111vw] md:max-lg:space-y-[13.021vw] max-md:space-y-[27.778vw]">
|
||||
<AboutHero />
|
||||
|
||||
@@ -5,13 +5,14 @@ import { motion, useInView } from "framer-motion";
|
||||
|
||||
function TeamCarousel() {
|
||||
return (
|
||||
<div className="right-[-7.333vw] top-[-10.5vw] absolute w-[23.611vw] aspect-[1/1] md:max-lg:w-[44.141vw] md:max-lg:right-[-30vw] md:max-lg:">
|
||||
<div className="lg:right-[-11vw] lg:top-[-11vw] -right-1/3 -top-1/3 absolute lg:w-[32.158vw] w-[339.39px] aspect-square md:max-lg:w-[44.141vw]">
|
||||
<motion.div
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ ease: "linear", duration: 50, repeat: Infinity }}
|
||||
className="w-[23.611vw] aspect-[1/1] relative flex items-center justify-center "
|
||||
className="lg:w-[32.158vw] md:w-[44.141vw] w-[339.39px] aspect-square relative flex items-center justify-center"
|
||||
>
|
||||
{Array.from({ length: 20 }).map((_, index) => (
|
||||
<img src="/img/pages/about/team/3ds.png" alt="" className="" />
|
||||
{/* {Array.from({ length: 20 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
id="outer-team-circle"
|
||||
@@ -70,7 +71,7 @@ function TeamCarousel() {
|
||||
`}</style>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</motion.div> */}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
@@ -91,7 +92,7 @@ export default function AboutTeam() {
|
||||
<span className="text-transparent bg-clip-text">
|
||||
{" "}
|
||||
более 60 высококвалифицирова
|
||||
<br className="hidden max-md:block" />
|
||||
<br className="max-md:block hidden" />
|
||||
ных специалистов <br className="md:max-lg:hidden" />
|
||||
</span>{" "}
|
||||
в своем офисе в Екатеринбурге
|
||||
@@ -102,7 +103,7 @@ export default function AboutTeam() {
|
||||
max-md:w-full max-md:text-[4.444vw] max-md:leading-[5vw] max-md:mb-[13.333vw]"
|
||||
>
|
||||
Используем только собственные силы,{" "}
|
||||
<br className="hidden md:max-lg:block" /> не передавая задачи на аутсорс
|
||||
<br className="md:max-lg:block hidden" /> не передавая задачи на аутсорс
|
||||
</span>
|
||||
|
||||
<div
|
||||
@@ -165,7 +166,7 @@ export default function AboutTeam() {
|
||||
transition={{ delay: 0.6, bounce: "none" }}
|
||||
className="bg-gradient-to-r from-[#222324] to-[#171819] rounded-[1.111vw] p-[2.222vw] [grid-area:pm] flex items-end
|
||||
md:max-lg:p-[4.167vw] md:max-lg:rounded-[2.083vw]
|
||||
max-md:rounded-[4.444vw] max-md:p-[4.444vw]"
|
||||
max-md:rounded-[4.444vw] max-md:p-[4.444vw] relative overflow-hidden"
|
||||
>
|
||||
<div className="mt-auto space-y-[1.667vw] max-md:space-y-[3.333vw]">
|
||||
<span className="line1 md:max-lg:text-[12.5vw]">3</span>
|
||||
@@ -212,12 +213,12 @@ export default function AboutTeam() {
|
||||
</span>
|
||||
<h3 className="headline2">
|
||||
сотрудников в отделе <br /> 3D-моделирования, сколько{" "}
|
||||
<br className="hidden max-md:block" />{" "}
|
||||
<br className="hidden md:max-lg:block" /> точно,{" "}
|
||||
<br className="max-md:block hidden" />{" "}
|
||||
<br className="md:max-lg:block hidden" /> точно,{" "}
|
||||
<br className="max-lg:hidden" /> никто не считал
|
||||
</h3>
|
||||
</div>
|
||||
{/* <TeamCarousel /> */}
|
||||
<TeamCarousel />
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function AchievementsCardsMobile({
|
||||
) : (
|
||||
<div
|
||||
className={
|
||||
"w-[15.486vw] aspect-[223/240] rounded-[16px] bg-[radial-gradient(circle,rgba(0,0,0,1)_-70%,rgba(34,36,37,1)_100%);] flex flex-col items-center justify-between p-[1.111vw] md:max-lg:flex md:max-lg:top-0 md:max-lg:p-[2.083vw] max-md:p-[4.444vw] top-[8.333vw] left-0 max-lg:hidden relative w-full max-md:aspect-[165/240]"
|
||||
"aspect-[223/240] rounded-[16px] bg-[radial-gradient(circle,rgba(0,0,0,1)_-70%,rgba(34,36,37,1)_100%);] flex flex-col items-center justify-between p-[1.111vw] md:max-lg:flex md:max-lg:top-0 md:max-lg:p-[2.083vw] max-md:p-[4.444vw] top-[8.333vw] left-0 max-lg:hidden relative w-full max-md:aspect-[165/240]"
|
||||
}
|
||||
>
|
||||
<div className="flex w-[12.361vw] h-[12.361vw] overflow-hidden rounded-full aspect-square md:max-lg:w-[17.448vw] md:max-lg:h-[17.448vw] md:max-lg:my-auto">
|
||||
|
||||
@@ -79,23 +79,22 @@ export default function Accordeon({
|
||||
return (
|
||||
<motion.div
|
||||
ref={wrapperRef}
|
||||
className="w-full bg-[#37393B99] backdrop-blur-[20px] rounded-[1.111vw] py-[1.667vw] px-[1.944vw]"
|
||||
className="w-full bg-[#37393B99] backdrop-blur-[20px] rounded-[1.111vw] lg:p-[1.667vw] p-6"
|
||||
onClick={() => onOpenChange(title)}
|
||||
// layout // Enable layout animations
|
||||
initial={{ height: `${closedElHeight}vw` }}
|
||||
animate={{
|
||||
height: opened ? `${openedElHeight}vw` : `${closedElHeight}vw`,
|
||||
}}
|
||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||
>
|
||||
<motion.div className="flex items-center justify-between hover:cursor-pointer">
|
||||
<span className="text-[1.389vw] headline2">{title}</span>
|
||||
<motion.div className="hover:cursor-pointer flex justify-between items-center">
|
||||
<span className="headline2">{title}</span>
|
||||
<motion.div
|
||||
animate={{
|
||||
background: opened ? "#7A7A7A" : "#313233",
|
||||
}}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="size-[2.222vw] border-[0.208vw] border-[#272829] bg-[#313233] rounded-full flex items-center justify-center"
|
||||
className="lg:size-[2.222vw] lg:border-[0.208vw] border-[#272829] bg-[#313233] rounded-full flex items-center justify-center"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
@@ -112,7 +111,7 @@ export default function Accordeon({
|
||||
{opened && (
|
||||
<motion.ul
|
||||
ref={listRef}
|
||||
className="flex flex-col mt-[1.667vw] gap-[0.556vw]"
|
||||
className="flex flex-col lg:mt-[1.667vw] lg:gap-[0.556vw]"
|
||||
>
|
||||
{content.map((item, index) => (
|
||||
<motion.li
|
||||
@@ -140,7 +139,7 @@ export default function Accordeon({
|
||||
delay:
|
||||
(content.length - 1 - index) * (0.125 / content.length),
|
||||
duration: 0.2,
|
||||
ease: "easeIn", // Smooth exit
|
||||
ease: "easeIn",
|
||||
},
|
||||
}}
|
||||
className="text2"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { motion } from "framer-motion";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
|
||||
interface ControlBtnProps {
|
||||
title: string;
|
||||
@@ -7,7 +7,7 @@ interface ControlBtnProps {
|
||||
active?: boolean;
|
||||
progress?: number;
|
||||
className?: string;
|
||||
callback?: Function;
|
||||
callback: () => void;
|
||||
}
|
||||
|
||||
export default function WebHeroControlBtn({
|
||||
@@ -20,7 +20,7 @@ export default function WebHeroControlBtn({
|
||||
}: ControlBtnProps) {
|
||||
return (
|
||||
<motion.button
|
||||
onClick={() => callback && callback()}
|
||||
onClick={callback}
|
||||
style={{
|
||||
background: !active
|
||||
? "linear-gradient(#37393B99 0%, #37393B99 100%)"
|
||||
|
||||
+56
-25
@@ -1,4 +1,4 @@
|
||||
import { useParallax } from "@/hooks/useParallax";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { useRef } from "react";
|
||||
|
||||
@@ -12,19 +12,36 @@ export default function ContentSlideCards({
|
||||
index?: number;
|
||||
}) {
|
||||
const CardsRef = useRef(null);
|
||||
useParallax(CardsRef);
|
||||
|
||||
const { isLg, isMd } = useMediaQueries();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={id}
|
||||
initial={{
|
||||
maxWidth: index === 1 ? "48.125vw" : "23.611vw",
|
||||
maxWidth: isLg
|
||||
? index === 1
|
||||
? "48.125vw"
|
||||
: "23.611vw"
|
||||
: isMd
|
||||
? index === 1
|
||||
? "54.688vw"
|
||||
: "31.25vw"
|
||||
: "94.444vw",
|
||||
}}
|
||||
animate={{
|
||||
maxWidth: index === 2 ? "48.125vw" : "23.611vw",
|
||||
maxWidth: isLg
|
||||
? index === 2
|
||||
? "48.125vw"
|
||||
: "23.611vw"
|
||||
: isMd
|
||||
? index === 2
|
||||
? "54.688vw"
|
||||
: "31.25vw"
|
||||
: "94.444vw",
|
||||
}}
|
||||
transition={{ duration: 0.5, type: "just" }}
|
||||
className="p-[1.667vw] h-[37.361vw] relative flex items-center justify-center flex-shrink-0 snap-center w-full"
|
||||
className="lg:h-[37.361vw] md:h-[538px] h-[480px] relative flex items-center justify-center flex-shrink-0 snap-center w-full select-none"
|
||||
>
|
||||
<AnimatePresence>
|
||||
{active && (
|
||||
@@ -41,12 +58,10 @@ export default function ContentSlideCards({
|
||||
animate={{ x: 0, y: 0 }}
|
||||
exit={{ x: "4.167vw", y: "-4.167vw" }}
|
||||
transition={{ duration: 0.6, ease: "easeInOut" }}
|
||||
className="w-[13.403vw] h-[8.472vw] flex flex-col p-[1.389vw] justify-between bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute left-[5.25vw] bottom-[2.625vw]"
|
||||
className="lg:w-[13.403vw] md:w-[18.229vw] w-[140px] flex flex-col lg:gap-[0.694vw] gap-2 lg:p-[1.389vw] p-4 bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute lg:left-[5.25vw] lg:bottom-[2.625vw] left-0 bottom-0"
|
||||
>
|
||||
<span className="headline2 leading-[135%] tracking-[0] z-[3]">
|
||||
Для соцсетей:
|
||||
</span>
|
||||
<p className="text-[0.833vw] leading-[135%] tracking-[0] font-[400] text-[#FFFFFF60]">
|
||||
<span className="headline2 z-[3]">Для соцсетей:</span>
|
||||
<p className="text2 text-[#FFFFFF60]">
|
||||
Вертикальные изображения с идеально выстоенным кадром
|
||||
</p>
|
||||
</motion.div>
|
||||
@@ -56,12 +71,10 @@ export default function ContentSlideCards({
|
||||
animate={{ x: 0, y: 0 }}
|
||||
exit={{ x: "-4.167vw", y: "4.167vw" }}
|
||||
transition={{ duration: 0.6, ease: "easeInOut" }}
|
||||
className="w-[13.403vw] aspect-[166/80] flex flex-col p-[1.389vw] justify-between bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute right-[5.25vw] top-[2.625vw]"
|
||||
className="lg:w-[13.403vw] md:w-[18.229vw] w-[140px] flex flex-col lg:gap-[0.694vw] gap-2 lg:p-[1.389vw] p-4 bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute lg:right-[5.25vw] lg:top-[2.625vw] right-0 top-0"
|
||||
>
|
||||
<span className="headline2 leading-[135%] tracking-[0]">
|
||||
Наружной рекламы:
|
||||
</span>
|
||||
<p className="text-[0.833vw] leading-[135%] tracking-[0] font-[400] text-[#FFFFFF60]">
|
||||
<span className="headline2">Наружной рекламы:</span>
|
||||
<p className="text2 text-[#FFFFFF60]">
|
||||
Высокое разрешение для широкоформатной печати
|
||||
</p>
|
||||
</motion.div>
|
||||
@@ -72,12 +85,18 @@ export default function ContentSlideCards({
|
||||
<motion.img
|
||||
key="card_top"
|
||||
animate={{
|
||||
translateX: active ? "-60px" : "-10px",
|
||||
translateY: active ? "-60px" : "-10px",
|
||||
width: active ? "22.986vw" : "15.833vw",
|
||||
translateX: active ? "-3.819vw" : "-0.347vw",
|
||||
translateY: active ? "-3.819vw" : "-0.347vw",
|
||||
width: isLg
|
||||
? active
|
||||
? "23.063vw"
|
||||
: "15.882vw"
|
||||
: isMd
|
||||
? "24.919vw"
|
||||
: "191.38px",
|
||||
}}
|
||||
transition={{ duration: 0.2, ease: "easeInOut" }}
|
||||
className=" aspect-[331/222] absolute z-[3]"
|
||||
className="absolute z-[3] lg:rounded-[0.417vw] rounded-2xl"
|
||||
src="/img/pages/web/content/card_top.png"
|
||||
alt=""
|
||||
/>
|
||||
@@ -86,22 +105,34 @@ export default function ContentSlideCards({
|
||||
animate={{
|
||||
translateX: "0px",
|
||||
translateY: "0px",
|
||||
width: active ? "22.986vw" : "15.833vw",
|
||||
width: isLg
|
||||
? active
|
||||
? "23.063vw"
|
||||
: "15.882vw"
|
||||
: isMd
|
||||
? "24.919vw"
|
||||
: "191.38px",
|
||||
}}
|
||||
transition={{ duration: 0.4, ease: "easeInOut" }}
|
||||
className="w-[22.986vw] aspect-[331/222] absolute z-[2]"
|
||||
className="absolute z-[2] lg:rounded-[0.417vw] rounded-2xl"
|
||||
src="/img/pages/web/content/card_md.png"
|
||||
alt=""
|
||||
/>
|
||||
<motion.img
|
||||
key="card_bt"
|
||||
animate={{
|
||||
translateX: active ? "60px" : "10px",
|
||||
translateY: active ? "60px" : "10px",
|
||||
width: active ? "22.986vw" : "15.833vw",
|
||||
translateX: active ? "3.819vw" : "0.347vw",
|
||||
translateY: active ? "3.819vw" : "0.347vw",
|
||||
width: isLg
|
||||
? active
|
||||
? "23.063vw"
|
||||
: "15.882vw"
|
||||
: isMd
|
||||
? "24.919vw"
|
||||
: "191.38px",
|
||||
}}
|
||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||
className="w-[22.986vw] aspect-[331/222] absolute z-[1]"
|
||||
className="absolute z-[1] lg:rounded-[0.417vw] rounded-2xl"
|
||||
src="/img/pages/web/content/card_bt.png"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
+47
-19
@@ -1,4 +1,4 @@
|
||||
import { useParallax } from "@/hooks/useParallax";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { motion } from "framer-motion";
|
||||
import { useRef } from "react";
|
||||
|
||||
@@ -12,31 +12,45 @@ export default function ContentSlideHall({
|
||||
index?: number;
|
||||
}) {
|
||||
const HallRef = useRef(null);
|
||||
useParallax(HallRef);
|
||||
const { isLg, isMd } = useMediaQueries();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={id}
|
||||
initial={{
|
||||
maxWidth: index === 1 ? "48.125vw" : "23.611vw",
|
||||
maxWidth: isLg
|
||||
? index === 1
|
||||
? "48.125vw"
|
||||
: "23.611vw"
|
||||
: isMd
|
||||
? index === 1
|
||||
? "54.688vw"
|
||||
: "31.25vw"
|
||||
: "94.444vw",
|
||||
}}
|
||||
animate={{
|
||||
maxWidth: index === 2 ? "48.125vw" : "23.611vw",
|
||||
maxWidth: isLg
|
||||
? index === 2
|
||||
? "48.125vw"
|
||||
: "23.611vw"
|
||||
: isMd
|
||||
? index === 2
|
||||
? "54.688vw"
|
||||
: "31.25vw"
|
||||
: "94.444vw",
|
||||
}}
|
||||
transition={{ duration: 0.5, type: "just" }}
|
||||
className="p-[1.667vw] h-[37.361vw] relative flex items-center justify-center flex-shrink-0 snap-center w-full"
|
||||
className="lg:h-[37.361vw] md:h-[538px] h-[480px] relative flex items-center justify-center flex-shrink-0 snap-center w-full select-none"
|
||||
>
|
||||
<div ref={HallRef} className="absolute inset-0 w-full h-full z-[1]">
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: active ? 1 : 0,
|
||||
}}
|
||||
className="w-[13.403vw] h-[8.472vw] flex flex-col p-[1.389vw] justify-between bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute left-[5.25vw] bottom-[2.625vw]"
|
||||
className="lg:w-[13.403vw] md:w-[18.229vw] w-[140px] flex flex-col lg:gap-[0.694vw] gap-2 lg:p-[1.389vw] p-4 bg-[#37393B99] backdrop-blur-[5px] lg:rounded-[1.111vw] rounded-2xl absolute lg:left-[5.25vw] lg:bottom-[2.625vw] left-0 bottom-0"
|
||||
>
|
||||
<span className="headline2 leading-[135%] tracking-[0] z-[3]">
|
||||
По двору:
|
||||
</span>
|
||||
<p className="text-[0.833vw] leading-[135%] tracking-[0] font-[400] text-[#FFFFFF60] ">
|
||||
<span className="headline2 z-[3]">По двору:</span>
|
||||
<p className="text2 text-[#FFFFFF60] ">
|
||||
возможность ощутить ритм жизни Жилого комплекса и его атмосферу
|
||||
</p>
|
||||
</motion.div>
|
||||
@@ -45,12 +59,10 @@ export default function ContentSlideHall({
|
||||
animate={{
|
||||
opacity: active ? 1 : 0,
|
||||
}}
|
||||
className="w-[13.403vw] h-[8.472vw] flex flex-col p-[1.389vw] justify-between bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute right-[5.25vw] top-[2.625vw]"
|
||||
className="lg:w-[13.403vw] md:w-[18.229vw] w-[140px] flex flex-col lg:gap-[0.694vw] gap-2 lg:p-[1.389vw] p-4 bg-[#37393B99] backdrop-blur-[5px] lg:rounded-[1.111vw] rounded-2xl absolute lg:right-[5.25vw] lg:top-[2.625vw] right-0 top-0"
|
||||
>
|
||||
<span className="headline2 leading-[135%] tracking-[0]">
|
||||
По квартире:
|
||||
</span>
|
||||
<p className="text-[0.833vw] leading-[135%] tracking-[0] font-[400] text-[#FFFFFF60] ">
|
||||
<span className="headline2">По квартире:</span>
|
||||
<p className="text2 text-[#FFFFFF60] ">
|
||||
возможность оценить пространство в объеме и ощутить себя внутри
|
||||
</p>
|
||||
</motion.div>
|
||||
@@ -58,15 +70,31 @@ export default function ContentSlideHall({
|
||||
|
||||
<motion.div
|
||||
animate={{
|
||||
width: active ? "23.333vw" : "15.833vw",
|
||||
height: active ? "23.333vw" : "15.833vw",
|
||||
width: isLg
|
||||
? active
|
||||
? "23.333vw"
|
||||
: "15.833vw"
|
||||
: isMd
|
||||
? active
|
||||
? "29.688vw"
|
||||
: "228px"
|
||||
: "228px",
|
||||
height: isLg
|
||||
? active
|
||||
? "23.333vw"
|
||||
: "15.833vw"
|
||||
: isMd
|
||||
? active
|
||||
? "29.688vw"
|
||||
: "228px"
|
||||
: "228px",
|
||||
}}
|
||||
className="relative rounded-full overflow-clip"
|
||||
className="relative overflow-clip rounded-full"
|
||||
>
|
||||
<motion.img
|
||||
src="/img/pages/web/content/hall.png"
|
||||
alt=""
|
||||
className="object-cover h-full"
|
||||
className="aspect-square object-cover h-full"
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
+51
-34
@@ -1,3 +1,4 @@
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { useParallax } from "@/hooks/useParallax";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { useRef } from "react";
|
||||
@@ -12,33 +13,49 @@ export default function ContentSlideIphone({
|
||||
index?: number;
|
||||
}) {
|
||||
const IphoneRef = useRef(null);
|
||||
useParallax(IphoneRef);
|
||||
// useParallax(IphoneRef);
|
||||
|
||||
const { isMd, isLg } = useMediaQueries();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={id}
|
||||
initial={{
|
||||
maxWidth: index === 1 ? "48.125vw" : "23.611vw",
|
||||
maxWidth: isLg
|
||||
? index === 1
|
||||
? "48.125vw"
|
||||
: "23.611vw"
|
||||
: isMd
|
||||
? index === 1
|
||||
? "54.688vw"
|
||||
: "31.25vw"
|
||||
: "94.444vw",
|
||||
}}
|
||||
animate={{
|
||||
maxWidth: index === 2 ? "48.125vw" : "23.611vw",
|
||||
maxWidth: isLg
|
||||
? index === 2
|
||||
? "48.125vw"
|
||||
: "23.611vw"
|
||||
: isMd
|
||||
? index === 2
|
||||
? "54.688vw"
|
||||
: "31.25vw"
|
||||
: "94.444vw",
|
||||
}}
|
||||
transition={{ duration: 0.5, type: "just" }}
|
||||
className="p-[1.667vw] h-[37.361vw] relative flex-shrink-0 w-full"
|
||||
className="lg:h-[37.361vw] md:h-[538px] h-[480px] relative flex-shrink-0 w-full select-none"
|
||||
>
|
||||
<div ref={IphoneRef} className="absolute inset-0 w-full h-full">
|
||||
<div ref={IphoneRef} className="absolute inset-0">
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: active ? 1 : 0,
|
||||
top: active ? "1.528vw" : "0.528vw",
|
||||
left: active ? "5.694vw" : "20.694vw",
|
||||
top: isLg ? (active ? "1.528vw" : "0.528vw") : "0vw",
|
||||
left: isLg ? (active ? "5.694vw" : "20.694vw") : "0vw",
|
||||
}}
|
||||
className="w-[13.403vw] h-[8.472vw] flex flex-col p-[1.389vw] justify-between bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute"
|
||||
className="lg:w-[13.403vw] md:w-[18.229vw] w-[140px] flex flex-col lg:gap-[0.694vw] gap-2 lg:p-[1.389vw] p-4 justify-between bg-[#37393B99] backdrop-blur-[5px] lg:rounded-[1.111vw] rounded-2xl absolute"
|
||||
>
|
||||
<span className="headline2 leading-[135%] tracking-[0] z-[3]">
|
||||
Яркие видео
|
||||
</span>
|
||||
<p className="text-[0.833vw] leading-[135%] tracking-[0] font-[400] text-[#FFFFFF60] ">
|
||||
<span className="headline2 z-[3]">Яркие видео</span>
|
||||
<p className="text2 text-[#FFFFFF60] ">
|
||||
Дайте клиентам почувствовать атмосферу жизни в жилом комплексе
|
||||
</p>
|
||||
</motion.div>
|
||||
@@ -46,15 +63,13 @@ export default function ContentSlideIphone({
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: active ? 1 : 0,
|
||||
top: active ? "16.736vw" : "8.736vw",
|
||||
left: active ? "1.667vw" : "5.667vw",
|
||||
top: isLg ? (active ? "15.736vw" : "8.736vw") : "100%",
|
||||
left: isLg ? (active ? "1.667vw" : "5.667vw") : "100%",
|
||||
}}
|
||||
className="w-[13.403vw] h-[8.472vw] flex flex-col p-[1.389vw] justify-between bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute"
|
||||
className="max-lg:-translate-y-full max-lg:-translate-x-full lg:w-[13.403vw] md:w-[18.229vw] w-[140px] flex flex-col lg:p-[1.389vw] p-4 lg:gap-[0.694vw] gap-2 bg-[#37393B99] backdrop-blur-[5px] lg:rounded-[1.111vw] rounded-2xl absolute"
|
||||
>
|
||||
<span className="headline2 leading-[135%] tracking-[0]">
|
||||
Интерьеры
|
||||
</span>
|
||||
<p className="text-[0.833vw] leading-[135%] tracking-[0] font-[400] text-[#FFFFFF60] ">
|
||||
<span className="headline2">Интерьеры</span>
|
||||
<p className="text2 text-[#FFFFFF60] ">
|
||||
Поможем выгодно показать клиентам ваши дизайнерские решения
|
||||
</p>
|
||||
</motion.div>
|
||||
@@ -62,15 +77,19 @@ export default function ContentSlideIphone({
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: active ? 1 : 0,
|
||||
top: active ? "23.819vw" : "13.819vw",
|
||||
left: active ? "9.306vw" : "20.306vw",
|
||||
top: isLg ? (active ? "23.819vw" : "13.819vw") : "262.91px",
|
||||
left: isLg
|
||||
? active
|
||||
? "9.306vw"
|
||||
: "20.306vw"
|
||||
: isMd
|
||||
? "5.859vw"
|
||||
: "5px",
|
||||
}}
|
||||
className="w-[13.403vw] h-[8.472vw] flex flex-col p-[1.389vw] justify-between bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute"
|
||||
className="lg:w-[13.403vw] md:w-[18.229vw] w-[140px] flex flex-col lg:p-[1.389vw] p-4 lg:gap-[0.694vw] gap-2 bg-[#37393B99] backdrop-blur-[5px] lg:rounded-[1.111vw] rounded-2xl absolute"
|
||||
>
|
||||
<span className="headline2 leading-[135%] tracking-[0]">
|
||||
Экстерьер
|
||||
</span>
|
||||
<p className="text-[0.833vw] leading-[135%] tracking-[0] font-[400] text-[#FFFFFF60] ">
|
||||
<span className="headline2">Экстерьер</span>
|
||||
<p className="text2 text-[#FFFFFF60] ">
|
||||
Запечатлим лучшие места для досуга и отдыха в вашем проекте
|
||||
</p>
|
||||
</motion.div>
|
||||
@@ -78,15 +97,13 @@ export default function ContentSlideIphone({
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: active ? 1 : 0,
|
||||
top: active ? "14.583vw" : "14.583vw",
|
||||
right: active ? "1.736vw" : "5.583vw",
|
||||
top: isLg ? (active ? "14.583vw" : "14.583vw") : "105.91px",
|
||||
right: isLg ? (active ? "1.736vw" : "5.583vw") : "0px",
|
||||
}}
|
||||
className="w-[13.403vw] h-[8.472vw] flex flex-col p-[1.389vw] justify-between bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute"
|
||||
className="lg:w-[13.403vw] md:w-[18.229vw] w-[140px] flex flex-col lg:p-[1.389vw] p-4 lg:gap-[0.694vw] gap-2 bg-[#37393B99] backdrop-blur-[5px] lg:rounded-[1.111vw] rounded-2xl absolute"
|
||||
>
|
||||
<span className="headline2 leading-[135%] tracking-[0]">
|
||||
Архитектура
|
||||
</span>
|
||||
<p className="text-[0.833vw] leading-[135%] tracking-[0] font-[400] text-[#FFFFFF60] ">
|
||||
<span className="headline2">Архитектура</span>
|
||||
<p className="text2 text-[#FFFFFF60] ">
|
||||
Сделаем видеооблеты, чтобы показать клиентам жилой комплекс в
|
||||
инфраструктуре города
|
||||
</p>
|
||||
@@ -103,7 +120,7 @@ export default function ContentSlideIphone({
|
||||
: "/img/pages/web/content/iphone_disabled.png"
|
||||
}
|
||||
alt=""
|
||||
className="w-auto h-full mx-auto"
|
||||
className="mx-auto w-auto h-full lg:rounded-[0.417vw] rounded-2xl"
|
||||
/>
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
|
||||
+34
-13
@@ -4,10 +4,18 @@ import { useState, useEffect } from "react";
|
||||
import ContentSlideCards from "./ContentSlideCards";
|
||||
import ContentSlideHall from "./ContentSlideHall";
|
||||
import ContentSlideIphone from "./ContentSlideIphone";
|
||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
|
||||
export default function InfiniteSlider() {
|
||||
const offsetWidth = 23.661 + 0.486;
|
||||
const [slideOffset, setSlideOffset] = useState(-offsetWidth);
|
||||
const { isLg, isMd } = useMediaQueries();
|
||||
const offset = isLg ? 0 : isMd ? 10.677 : 95.833;
|
||||
const slideWidth = isLg
|
||||
? 23.661 + 0.486
|
||||
: isMd
|
||||
? 31.25 + 1.0415
|
||||
: 94.444 + 1.389;
|
||||
const [slideOffset, setSlideOffset] = useState(-slideWidth);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const [slides, setSlides] = useState([
|
||||
{ type: "hall", id: Math.random() },
|
||||
@@ -18,8 +26,8 @@ export default function InfiniteSlider() {
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setSlideOffset(-offsetWidth);
|
||||
}, [slides]);
|
||||
setSlideOffset(-slideWidth - offset);
|
||||
}, [offset, slideWidth, slides]);
|
||||
|
||||
function moveSlide(direction: "left" | "right") {
|
||||
if (isAnimating) return;
|
||||
@@ -30,19 +38,32 @@ export default function InfiniteSlider() {
|
||||
? [...slides.slice(1), { ...slides[2], id: Math.random() }]
|
||||
: [{ ...slides[2], id: Math.random() }, ...slides.slice(0, -1)]
|
||||
);
|
||||
setSlideOffset(direction === "right" ? 0 : -offsetWidth * 2);
|
||||
setSlideOffset(
|
||||
(prev) => prev + (direction === "right" ? 1 : -1) * slideWidth
|
||||
);
|
||||
}
|
||||
|
||||
const handlers = useSwipeable({
|
||||
onSwipedLeft: () => moveSlide("right"),
|
||||
onSwipedRight: () => moveSlide("left"),
|
||||
preventScrollOnSwipe: true,
|
||||
trackTouch: true,
|
||||
trackMouse: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className=" -mx-[1.389vw]">
|
||||
<div className="relative flex w-full overflow-hidden">
|
||||
<div className="lg:-mx-[1.389vw] md:-mx-[2.083vw] -mx-[2.778vw]">
|
||||
<div className="flex overflow-hidden relative w-full" {...handlers}>
|
||||
<div
|
||||
onTransitionEnd={() => setIsAnimating(false)}
|
||||
className="flex gap-[0.972vw]"
|
||||
className="flex lg:gap-[0.972vw] md:gap-[2.083vw] gap-[2.778vw]"
|
||||
style={{
|
||||
transform: `translateX(${slideOffset}vw)`,
|
||||
transitionDuration: `${
|
||||
slideOffset === 0 || slideOffset === -offsetWidth * 2 ? 0 : 0.5
|
||||
slideOffset === -offset ||
|
||||
slideOffset === -slideWidth * 2 - offset
|
||||
? 0
|
||||
: 0.5
|
||||
}s`,
|
||||
}}
|
||||
>
|
||||
@@ -78,18 +99,18 @@ export default function InfiniteSlider() {
|
||||
</div>
|
||||
<div className="flex gap-[0.833vw] absolute left-1/2 bottom-0 -translate-x-1/2 z-10">
|
||||
<button
|
||||
className=" w-[3.333vw] h-[3.333vw] rounded-[1.111vw] text-white bg-gradient"
|
||||
className="lg:rounded-[1.111vw] rounded-2xl text-white bg-gradient lg:p-[1.111vw] p-4"
|
||||
onClick={() => moveSlide("left")}
|
||||
>
|
||||
<div className="size-[1.111vw] mx-auto">
|
||||
<div className="lg:size-[1.111vw] size-4">
|
||||
<ArrowLeftIcon />
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
className=" w-[3.333vw] h-[3.333vw] rounded-[1.111vw] text-white bg-gradient"
|
||||
className="lg:rounded-[1.111vw] rounded-2xl text-white bg-gradient lg:p-[1.111vw] p-4"
|
||||
onClick={() => moveSlide("right")}
|
||||
>
|
||||
<div className="size-[1.111vw] mx-auto">
|
||||
<div className="lg:size-[1.111vw] size-4">
|
||||
<ArrowRightIcon />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
"use client";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import SearchIcon from "@/components/icons/SearchIcon";
|
||||
import GenPlanSearchIcon from "@/components/icons/GenPlanSearchIcon";
|
||||
import InfoIcon from "@/components/icons/InfoIcon";
|
||||
@@ -8,75 +8,60 @@ import TreeIcon from "@/components/icons/TreeIcon";
|
||||
import FavoriteIcon from "@/components/icons/FavoriteIcon";
|
||||
import ReportIcon from "@/components/icons/ReportIcon";
|
||||
import WebHeroControlBtn from "./PageComponents/WebHeroControlBtn";
|
||||
import { motion } from "framer-motion";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
export default function WebHero() {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
const [currentVideo, setCurrentVideo] = useState<number>(0);
|
||||
const [autoPlay, setAutoPlay] = useState<boolean>(true);
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const [playbackProgress, setPlaybackProgress] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const updateProgress = useCallback(() => {
|
||||
if (!videoRef.current) return;
|
||||
|
||||
videoRef.current.play();
|
||||
}, [currentVideo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!videoRef) return;
|
||||
|
||||
const updateProgress = () => {
|
||||
if (videoRef.current) {
|
||||
const { currentTime, duration } = videoRef.current;
|
||||
const progressPercent = duration ? (currentTime / duration) * 100 : 0;
|
||||
|
||||
setPlaybackProgress(progressPercent);
|
||||
}
|
||||
};
|
||||
videoRef.current &&
|
||||
videoRef.current.addEventListener("timeupdate", updateProgress);
|
||||
setPlaybackProgress(0);
|
||||
}, [currentVideo]);
|
||||
|
||||
return () => {
|
||||
videoRef.current?.removeEventListener("timeupdate", updateProgress);
|
||||
};
|
||||
}, [videoRef]);
|
||||
// Обработчик timeupdate напрямую в JSX через onTimeUpdate
|
||||
const handleTimeUpdate = useCallback(() => {
|
||||
updateProgress();
|
||||
}, [updateProgress]);
|
||||
|
||||
const onVideoEnd = () => {
|
||||
if (!autoPlay) return;
|
||||
setCurrentVideo((cur) => (cur + 1) % 5);
|
||||
};
|
||||
// Сбрасываем прогресс при переключении видео
|
||||
useEffect(() => {
|
||||
setPlaybackProgress(0);
|
||||
}, [currentVideo]);
|
||||
|
||||
const btnCallback = (video: number) => {
|
||||
if (autoPlay) {
|
||||
setPlaybackProgress(0);
|
||||
setCurrentVideo(video);
|
||||
setAutoPlay(false);
|
||||
} else if (video === currentVideo) {
|
||||
setAutoPlay(true);
|
||||
} else {
|
||||
setPlaybackProgress(0);
|
||||
setCurrentVideo(video);
|
||||
}
|
||||
};
|
||||
const onVideoEnd = () => setCurrentVideo((cur) => (cur + 1) % 6);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-center line1 leading-[85%] tracking-[-0.04em] font-medium md:max-lg:text-[6.25vw] md:max-lg:mb-[8.333vw] md:max-lg:leading-[95%] md:max-lg:tracking-[-0.04em]">
|
||||
Создаем интерактивные сайты для продажи недвижимости
|
||||
</h1>
|
||||
<div className="relative flex justify-center md:max-lg:flex-col">
|
||||
<div className="md:max-lg:flex-col flex relative justify-center">
|
||||
<div className="relative">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.video
|
||||
// key={currentVideo}
|
||||
muted
|
||||
key={currentVideo}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
ref={videoRef}
|
||||
onEnded={onVideoEnd}
|
||||
controls={false}
|
||||
loop={!autoPlay}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onLoadStart={() => setPlaybackProgress(0)}
|
||||
loop
|
||||
playsInline
|
||||
muted
|
||||
autoPlay
|
||||
src={`/videos/pages/web/hero_${currentVideo}.mp4`}
|
||||
className="absolute w-[50.694vw] aspect-[730/472] top-[4vw] left-[6.458vw]
|
||||
md:max-lg:w-[calc(87.2%)] md:max-lg:md:aspect-[585/345] md:max-lg:top-[1.5vw] md:max-lg:left-[6.458vw]"
|
||||
/>
|
||||
</AnimatePresence>
|
||||
<img
|
||||
draggable={false}
|
||||
className="w-[63.75vw] aspect-[918/553] z-10 mt-[3.125vw] relative md:max-lg:w-full md:max-lg:mt-0"
|
||||
@@ -85,56 +70,56 @@ export default function WebHero() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="md:max-lg:flex md:max-lg:w-full md:max-lg:gap-[1.042vw] md:max-lg:mt-[4.167vw] z-[1]">
|
||||
<div className="md:max-lg:flex md:max-lg:w-full md:max-lg:gap-[1.042vw] md:max-lg:mt-[4.167vw] z-10">
|
||||
<WebHeroControlBtn
|
||||
title={"Поиск"}
|
||||
icon={<SearchIcon />}
|
||||
className="top-[14.722vw] left-0"
|
||||
active={!autoPlay && currentVideo === 0}
|
||||
callback={() => btnCallback(0)}
|
||||
progress={playbackProgress}
|
||||
active={currentVideo === 0}
|
||||
callback={() => setCurrentVideo(0)}
|
||||
progress={currentVideo === 0 ? playbackProgress : 0}
|
||||
/>
|
||||
<WebHeroControlBtn
|
||||
title={"Поиск на генплане"}
|
||||
icon={<GenPlanSearchIcon />}
|
||||
className="top-[20.486vw] left-[8.194vw]"
|
||||
active={!autoPlay && currentVideo === 1}
|
||||
callback={() => btnCallback(1)}
|
||||
progress={playbackProgress}
|
||||
active={currentVideo === 1}
|
||||
callback={() => setCurrentVideo(1)}
|
||||
progress={currentVideo === 1 ? playbackProgress : 0}
|
||||
/>
|
||||
<WebHeroControlBtn
|
||||
title={"О проекте"}
|
||||
icon={<InfoIcon />}
|
||||
className="top-[17.5vw] left-[16.389vw]"
|
||||
active={!autoPlay && currentVideo === 2}
|
||||
callback={() => btnCallback(2)}
|
||||
progress={playbackProgress}
|
||||
active={currentVideo === 2}
|
||||
callback={() => setCurrentVideo(2)}
|
||||
progress={currentVideo === 2 ? playbackProgress : 0}
|
||||
/>
|
||||
|
||||
<WebHeroControlBtn
|
||||
title={"Окружение"}
|
||||
icon={<TreeIcon />}
|
||||
className="top-[17.5vw] right-[16.389vw]"
|
||||
active={!autoPlay && currentVideo === 3}
|
||||
callback={() => btnCallback(3)}
|
||||
progress={playbackProgress}
|
||||
active={currentVideo === 3}
|
||||
callback={() => setCurrentVideo(3)}
|
||||
progress={currentVideo === 3 ? playbackProgress : 0}
|
||||
/>
|
||||
<WebHeroControlBtn
|
||||
title={"Избранное"}
|
||||
icon={<FavoriteIcon />}
|
||||
className="top-[20.486vw] right-[8.194vw]"
|
||||
active={!autoPlay && currentVideo === 4}
|
||||
callback={() => btnCallback(4)}
|
||||
progress={playbackProgress}
|
||||
active={currentVideo === 4}
|
||||
callback={() => setCurrentVideo(4)}
|
||||
progress={currentVideo === 4 ? playbackProgress : 0}
|
||||
/>
|
||||
|
||||
<WebHeroControlBtn
|
||||
title={"Генерация и отправка КП"}
|
||||
icon={<ReportIcon />}
|
||||
className="top-[14.722vw] right-0"
|
||||
active={!autoPlay && currentVideo === 5}
|
||||
callback={() => btnCallback(5)}
|
||||
progress={playbackProgress}
|
||||
active={currentVideo === 5}
|
||||
callback={() => setCurrentVideo(5)}
|
||||
progress={currentVideo === 5 ? playbackProgress : 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"port": 3100,
|
||||
"appPort": 3000,
|
||||
"autoPlugins": true,
|
||||
"plugins": []
|
||||
}
|
||||
Reference in New Issue
Block a user