stream demo page

This commit is contained in:
2026-04-10 14:05:28 +05:00
parent cad1fb73a1
commit ebbec70586
36 changed files with 689 additions and 86 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 MiB

After

Width:  |  Height:  |  Size: 5.5 MiB

+12
View File
@@ -0,0 +1,12 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="11.4286" fill="#91FF00"/>
<g clip-path="url(#clip0_11466_14924)">
<path d="M25.1817 5.99486V25.0373C25.1817 26.3151 24.8259 27.384 24.1142 28.2441C23.4025 29.1042 22.3671 29.5359 21.008 29.5392H18.9066C17.5475 29.5392 16.5121 29.1075 15.8004 28.2441C15.0887 27.3807 14.7329 26.3118 14.7329 25.0373V11.3552L11.8074 12.4381V25.1717C11.7924 26.029 11.9963 26.8755 12.399 27.6274C12.7936 28.345 13.3309 28.9698 13.9762 29.4615C14.661 29.9802 15.4287 30.3729 16.245 30.6221C17.1075 30.8922 18.0047 31.0282 18.9066 31.0254H21.0104C21.9127 31.0281 22.8103 30.8922 23.6732 30.6221C24.489 30.3726 25.2563 29.9799 25.9408 29.4615C26.5861 28.9698 27.1235 28.345 27.518 27.6274C27.9213 26.8757 28.1252 26.0291 28.1096 25.1717V4.95264L25.1817 5.99486Z" fill="#0F1011"/>
<path d="M23.3118 33.9696H16.6066V34.9723H23.3118V33.9696Z" fill="#0F1011"/>
</g>
<defs>
<clipPath id="clip0_11466_14924">
<rect width="30.5882" height="30.5882" fill="white" transform="translate(4.70605 4.70605)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

+5
View File
@@ -0,0 +1,5 @@
import StreamDemo from "@/components/pages/StreamDemo/StreamDemo";
export default function StreamDemoPage() {
return <StreamDemo />;
}
+10 -1
View File
@@ -6,6 +6,7 @@ import { LenisProvider } from "@/lib/LenisProvider";
import PopupModal from "@/components/modals/TelegramPopup"; import PopupModal from "@/components/modals/TelegramPopup";
import CookiesPopup from "@/components/modals/CookiesPopup"; import CookiesPopup from "@/components/modals/CookiesPopup";
import ResultsPopup from "@/components/modals/ResultsPopup"; import ResultsPopup from "@/components/modals/ResultsPopup";
import CasePopup from "@/components/modals/CasePopup";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -81,7 +82,15 @@ export default function RootLayout({
<LenisProvider> <LenisProvider>
<PopupModal /> <PopupModal />
<CookiesPopup /> <CookiesPopup />
<ResultsPopup /> <CasePopup
logo="img/cases/logos/UpsideTowers.svg"
title={"Цифровой двойник\nАпсайд Тауэрс"}
subtitle={
"Рассказываем, как наш интерактивный\nинструмент усилил продажи"
}
bgAccentColor="#2C3EFF80"
link="/upside-towers"
/>
{children} {children}
<ModalContainer /> <ModalContainer />
</LenisProvider> </LenisProvider>
+1 -1
View File
@@ -79,7 +79,7 @@ export function Footer() {
<span className="text1 text-[#7A7A7A]">Реквизиты:</span> <span className="text1 text-[#7A7A7A]">Реквизиты:</span>
<div className="headline2 max-md:leading-4 font-medium text-[#7A7A7A]"> <div className="headline2 max-md:leading-4 font-medium text-[#7A7A7A]">
<p>ИНН: 6679174128</p> <p>ИНН: 6679174128</p>
<p>КПП: 667901001</p> <p>КПП: 667101001</p>
<p>ООО &quot;ГРАФФ.ЭСТЕЙТ&quot;</p> <p>ООО &quot;ГРАФФ.ЭСТЕЙТ&quot;</p>
<p>ОГРН 1246600010140</p> <p>ОГРН 1246600010140</p>
</div> </div>
+12
View File
@@ -134,6 +134,12 @@ export function Header() {
href={"/projects"} href={"/projects"}
text={"Проекты"} text={"Проекты"}
/> />
{/* <HeaderLink
className="max-md:hidden btnm"
href={"/results"}
text={"Итоги 2025"}
target="_blank"
/> */}
<div className="md:justify-end flex flex-1 justify-center"> <div className="md:justify-end flex flex-1 justify-center">
<Link <Link
href={"/form"} href={"/form"}
@@ -193,6 +199,12 @@ export function Header() {
text={"Проекты"} text={"Проекты"}
className="accent" className="accent"
/> />
{/* <HeaderLink
href={"/results"}
text={"Итоги 2025"}
className="accent"
target="_blank"
/> */}
</div> </div>
<hr className="border-[#37393B]" /> <hr className="border-[#37393B]" />
<div className="space-y-[10px]"> <div className="space-y-[10px]">
+235
View File
@@ -0,0 +1,235 @@
/* eslint-disable @next/next/no-img-element */
"use client";
import { usePopupStore } from "@/stores/usePopupStore";
import { usePathname } from "next/navigation";
import React, { useEffect, useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { useOnClickOutside } from "usehooks-ts";
import clsx from "clsx";
import ArrowMoreIcon from "../icons/ArrowMoreIcon";
import { useMediaQueries } from "@/hooks/useMediaQueries";
interface ICasePopup {
logo: string;
title: string;
subtitle: string;
link: string;
className?: string;
bgAccentColor?: string;
}
const DEFAULT_DURATION = 0.5;
export default function CasePopup({
logo,
title,
subtitle,
className,
bgAccentColor,
link,
}: ICasePopup) {
const { isLg, isMd } = useMediaQueries();
const responsive = <T,>(lg: T, md: T, fallback: T): T => {
if (isLg) return lg;
if (isMd) return md;
return fallback;
};
const {
isShowCasePopup,
isShowCookiesPopup,
isShowPopup,
setIsShowCasePopup,
} = usePopupStore();
const pathname = usePathname();
const [isOpen, setIsOpen] = useState<boolean>(true);
const popupRef = useRef<HTMLDivElement>(null);
useOnClickOutside(popupRef, () => setIsOpen(false));
useEffect(() => {
if (
usePopupStore.getState().isShowCasePopup === undefined &&
!isShowCookiesPopup &&
!isShowPopup
) {
const timeout = setTimeout(() => {
setIsShowCasePopup(true);
}, 1000);
return () => clearTimeout(timeout);
}
}, [isShowCookiesPopup, isShowPopup]);
function handlePopupClick() {
if (!isOpen) setIsOpen(true);
}
return (
<>
{isShowCasePopup && pathname === "/" && (
<motion.div
ref={popupRef}
initial={{ y: -40 }}
animate={{
width: isOpen
? responsive("20.833vw", "35vw", "75vw")
: responsive("3.333vw", "7vw", "14.733vw"),
height: isOpen
? responsive("15.967vw", "28.125vw", "63vw")
: responsive("5vw", "10.25vw", "22.733vw"),
padding: isOpen
? responsive("1.111vw", "2.083vw", "4vw")
: responsive("0.278vw", "0.521vw", "1vw"),
}}
transition={{
duration: DEFAULT_DURATION,
ease: "easeInOut",
}}
onClick={handlePopupClick}
className={clsx(
"bg-[#37393B99] flex flex-col items-start justify-start fixed z-[100] bottom-0 lg:left-[1.944vw] md:left-[3.125vw] left-[2.778vw] lg:rounded-[1.111vw] md:rounded-[2.083vw] rounded-[4.444vw] backdrop-blur-[5px] overflow-hidden",
className
)}
>
{/* Градиент на фоне */}
<div
style={{
background: `linear-gradient(155deg, ${bgAccentColor} 0%, transparent 50%)`,
}}
className="w-full h-full absolute inset-0"
/>
{/* Логотип */}
<motion.img
src={logo}
animate={{
left: isOpen
? responsive("1.111vw", "2.083vw", "4.444vw")
: responsive("0.278vw", "0.521vw", "1.111vw"),
top: isOpen
? responsive("1.111vw", "2.083vw", "4.444vw")
: responsive("0.278vw", "0.521vw", "1.111vw"),
}}
transition={{
duration: DEFAULT_DURATION,
ease: "easeInOut",
}}
alt=""
className="lg:size-[2.778vw] md:size-[6vw] size-[12.511vw] absolute z-[2]"
/>
<motion.div
initial={{ opacity: 0, height: "0" }}
animate={{
opacity: isOpen ? 0 : 1,
height: isOpen ? "0" : "auto",
marginTop: isOpen ? "0vw" : responsive("3vw", "6.2vw", "13.5vw"),
}}
transition={{
duration: DEFAULT_DURATION,
ease: "easeInOut",
}}
className="lg:mt-[3vw] lg:text-[0.62vw] md:text-[1.302vw] text-[2.778vw] leading-[120%] text-center"
>
Смотреть <br /> кейс
</motion.div>
{/* Контент */}
<AnimatePresence mode="wait">
{isOpen && (
<>
{/* Заголовок */}
<motion.div
key="title"
initial={{
opacity: 0,
marginLeft: isOpen
? responsive("4vw", "7.375vw", "16vw")
: "0",
}}
animate={{
opacity: 1,
marginLeft: isOpen
? responsive("4vw", "7.375vw", "16vw")
: "0",
}}
exit={{
opacity: 0,
}}
transition={{
duration: DEFAULT_DURATION,
ease: "easeInOut",
}}
className="whitespace-pre-line text-white font-medium -tracking-[0.02em] z-[2] overflow-hidden
lg:text-[1.111vw] lg:leading-[1.667vw] lg:-translate-y-[0.278vw] lg:mb-[0.5561vw]
md:text-[2.083vw] md:leading-[2.925vw] md:-translate-y-[0.45vw] md:mb-[2.083vw]
text-[4.444vw] leading-[6.667vw] -translate-y-[0.833vw] mb-[4.444vw]"
>
{title}
</motion.div>
{/* Описание */}
<motion.div
key="subtitle"
initial={{ opacity: 0 }}
animate={{
opacity: 1,
}}
exit={{
opacity: 0,
}}
transition={{
duration: DEFAULT_DURATION,
ease: "easeInOut",
}}
className="whitespace-pre-line text-white -tracking-[0.02em] z-[2] overflow-hidden
lg:text-[0.833vw] lg:leading-[135%] lg:mb-[1.667vw]
md:text-[1.563vw] md:leading-[135%] md:mb-[2.083vw]
text-[3.333vw] leading-[135%] mb-[4.444vw]"
>
{subtitle}
</motion.div>
<motion.a
href={link}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
duration: DEFAULT_DURATION,
ease: "easeInOut",
}}
className="btns w-full flex gap-2 text-center justify-center items-center font-medium bg-gradient-saturated z-[2]
lg:py-[0.694vw] lg:rounded-[0.972vw] lg:mb-[0.278vw]
md:py-[1.042vw] md:rounded-[1.563vw] md:mb-[0.521vw]
py-[3.333vw] rounded-[3.333vw] mb-[2.222vw]"
>
Смотреть кейс
<div className="text-white lg:size-[1.389vw] size-4">
<ArrowMoreIcon />
</div>
</motion.a>
<motion.button
onClick={() => setIsOpen(false)}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
duration: DEFAULT_DURATION,
ease: "easeInOut",
}}
className="btns w-full flex gap-2 text-center justify-center items-center font-medium bg-[#37393B99] z-[2]
lg:py-[0.954vw] lg:rounded-[0.972vw]
md:py-[1.563vw] md:rounded-[1.563vw]
py-[3.333vw] rounded-[3.333vw]"
>
Посмотрю позже
</motion.button>
</>
)}
</AnimatePresence>
</motion.div>
)}
</>
);
}
@@ -0,0 +1,17 @@
import React from "react";
export default function ArticleBannerWrapper({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<div
className={`lg:w-[43.7vw] md:max-lg:w-[47.135vw] max-md:w-full max-md:h-[116.667vw] max-md:mt-3 lg:rounded-[1.111vw] rounded-2xl lg:p-[1.111vw] md:p-[2.083vw] p-[4.444vw] overflow-hidden ${className}`}
>
{children}
</div>
);
}
+55 -5
View File
@@ -1,3 +1,4 @@
/* eslint-disable @next/next/no-img-element */
"use client"; "use client";
import { useCheckAuthQuery } from "@/queries/checkAuth"; import { useCheckAuthQuery } from "@/queries/checkAuth";
@@ -8,6 +9,8 @@ import { useSearchParams } from "next/navigation";
import TelegramIcon from "@/icons/TgIcon"; import TelegramIcon from "@/icons/TgIcon";
import { ArticleCard } from "./ArticleCard"; import { ArticleCard } from "./ArticleCard";
import { DraftsList } from "./DraftsList"; import { DraftsList } from "./DraftsList";
import ArticleBannerWrapper from "./ArticleBannerWrapper";
import ArrowMoreIcon from "@/components/icons/ArrowMoreIcon";
export function ArticlesList() { export function ArticlesList() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@@ -24,14 +27,61 @@ export function ArticlesList() {
<div className="pt-2 space-y-2"> <div className="pt-2 space-y-2">
{auth && <p className="text-[#7A7A7A] text1 pl-3">Опубликованное</p>} {auth && <p className="text-[#7A7A7A] text1 pl-3">Опубликованное</p>}
<div className="lg:gap-x-[0.833vw] md:gap-x-3 gap-y-6 md:flex flex-wrap col-start-2"> <div className="lg:gap-x-[0.833vw] md:gap-x-3 gap-y-6 md:flex flex-wrap col-start-2">
{articles && {articles && (
articles.map((article) => ( <>
<ArticleCard <ArticleCard
key={article.id} key={articles[0].id}
{...article} {...articles[0]}
className="lg:w-[21.435vw] lg:aspect-[308.67/352] md:max-lg:w-[47.135vw] max-md:w-full" className="lg:w-[21.435vw] lg:aspect-[308.67/352] md:max-lg:w-[47.135vw] max-md:w-full"
/> />
))}
{/* Баннер с итогами года, зафиксирован на втором месте */}
<ArticleBannerWrapper className="bg-[#37393B99] relative flex flex-col items-start">
<div className="heading2 max-md:text-[6.667vw] lg:w-[25.361vw] md:w-[32vw] relative z-[2] font-medium">
Итоги года GRAFF.estate: рассказываем о том, как прошел 2025
год для нас и&nbsp;наших{" "}
<br className="md:hidden max-md:hidden" /> партнеров
</div>
<Link
href="/results"
target="_blank"
className="btnm flex items-center gap-[8px] lg:py-[1.111vw] md:py-[2.083vw] py-[4.444vw] lg:px-[1.5vw] md:px-[2.083vw] px-[4.444vw] lg:rounded-[1.111vw] md:rounded-[2.083vw] rounded-[4.444vw] font-medium mt-auto bg-gradient-saturated z-[4] realtive"
>
Итоги 2025
<div className="text-white lg:size-[1.111vw] size-4">
<ArrowMoreIcon />
</div>
</Link>
<div className="absolute w-full h-[20vw] bottom-0 left-0 hidden max-md:block bg-[linear-gradient(to_top,#2A37BD,transparent_100%)] z-[3]" />
<img
src="/img/pages/blog/banner_results.png"
alt=""
className="absolute lg:bottom-[-6vw] md:bottom-[-10vw] lg:right-[-2vw] right-[-2vw] lg:w-[30.333vw] md:w-[45vw] object-cover lg:z-[2] md:z-[1] z-[2]"
/>
<div
className="absolute inset-0
lg:bg-[radial-gradient(ellipse_at_bottom_right,#2A37BD,transparent)]
md:bg-[radial-gradient(ellipse_at_bottom_right,#2A37BD,transparent_40%)]
bg-[radial-gradient(ellipse_at_bottom_right,#2A37BD,transparent_80%)] z-[1]"
/>
</ArticleBannerWrapper>
{articles[1] &&
articles
.slice(1)
.map((article) => (
<ArticleCard
key={article.id}
{...article}
className="lg:w-[21.435vw] lg:aspect-[308.67/352] md:max-lg:w-[47.135vw] max-md:w-full"
/>
))}
</>
)}
<div className="bg-[#37393B99] nth-[3n+1]:m-auto nth-[3n]:w-[21.25vw] max-lg:hidden lg:rounded-[1.111vw] rounded-2xl p-[1.111vw] w-[43.611vw] h-[24.444vw] bg-[url(/img/pages/blog/tg/into_lapenko.jpg)] bg-no-repeat bg-right-bottom bg-[length:calc(582.85/628*100%)] flex flex-col justify-between"> <div className="bg-[#37393B99] nth-[3n+1]:m-auto nth-[3n]:w-[21.25vw] max-lg:hidden lg:rounded-[1.111vw] rounded-2xl p-[1.111vw] w-[43.611vw] h-[24.444vw] bg-[url(/img/pages/blog/tg/into_lapenko.jpg)] bg-no-repeat bg-right-bottom bg-[length:calc(582.85/628*100%)] flex flex-col justify-between">
<div className="text1 font-medium lg:max-w-[25.069vw]"> <div className="text1 font-medium lg:max-w-[25.069vw]">
Мы еще не перенесли сюда все публикации, но все самое горячее Мы еще не перенесли сюда все публикации, но все самое горячее
@@ -12,11 +12,13 @@ export function StreamingProject({
index, index,
current, current,
count, count,
className,
}: Pick<IProject, "city" | "title" | "image" | "company"> & { }: Pick<IProject, "city" | "title" | "image" | "company"> & {
href: string; href: string;
index: number; index: number;
current: number; current: number;
count: number; count: number;
className?: string;
}) { }) {
return ( return (
<div <div
@@ -30,7 +32,7 @@ export function StreamingProject({
: index === (current - 1 + count) % count : index === (current - 1 + count) % count
? "max-md:[transform:translateX(calc(-7.5%-20px))]" ? "max-md:[transform:translateX(calc(-7.5%-20px))]"
: "" : ""
}`} } ${className}`}
> >
<div <div
className="rounded-2xl group-hover:scale-110 absolute inset-0 transition-transform duration-500 after:absolute after:inset-0 lg:after:[background-image:linear-gradient(to_top,#00000099,transparent)] after:[background-image:linear-gradient(to_bottom,#00000099,transparent)]" className="rounded-2xl group-hover:scale-110 absolute inset-0 transition-transform duration-500 after:absolute after:inset-0 lg:after:[background-image:linear-gradient(to_top,#00000099,transparent)] after:[background-image:linear-gradient(to_bottom,#00000099,transparent)]"
@@ -1,56 +1,73 @@
/* eslint-disable @next/next/no-img-element */
"use client"; "use client";
import { ItemActions } from "@/components/ItemActions"; import { ItemActions } from "@/components/ItemActions";
import { IProject } from "@/types/IProject"; import { IProject } from "@/types/IProject";
import { ProgressPie } from "@/ui/ProgressPie"; import { ProgressPie } from "@/ui/ProgressPie";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { cases } from "@/consts/cases";
import Link from "next/link";
export function ProjectCard(project: IProject) { export function ProjectCard(project: IProject) {
const { city, company, image, title, stage } = project; const { city, company, image, title, stage } = project;
const stagePercentage = Math.round((100 / 6) * stage!); const stagePercentage = Math.round((100 / 6) * stage!);
const hasCase = !!cases[title];
return ( return (
<motion.div <Link href={hasCase ? cases[title].link : ""}>
initial={{ opacity: 0 }} <motion.div
whileInView={{ opacity: 1 }} initial={{ opacity: 0 }}
viewport={{ once: true, margin: "-100px" }} whileInView={{ opacity: 1 }}
transition={{ duration: 1, ease: [0.58, 0.12, 0.27, 0.98], delay: 0.2 }} viewport={{ once: true, margin: "-100px" }}
className="group aspect-square lg:rounded-[1.111vw] rounded-2xl relative flex items-end lg:p-[1.111vw] p-4 overflow-hidden" transition={{ duration: 1, ease: [0.58, 0.12, 0.27, 0.98], delay: 0.2 }}
> className="group aspect-square lg:rounded-[1.111vw] rounded-2xl relative flex items-end lg:p-[1.111vw] p-4 overflow-hidden"
<ItemActions item={project} /> >
<div {hasCase && <CaseBadge logo={cases[title].logo} />}
className="group-hover:scale-110 absolute top-0 left-0 w-full h-full transition-transform duration-500 bg-center bg-no-repeat bg-cover" <ItemActions item={project} />
style={{ <div
backgroundImage: `url(${process.env.NEXT_PUBLIC_S3_BUCKET}${image})`, className="group-hover:scale-110 absolute top-0 left-0 w-full h-full transition-transform duration-500 bg-center bg-no-repeat bg-cover"
}} style={{
/> backgroundImage: `url(${process.env.NEXT_PUBLIC_S3_BUCKET}${image})`,
<div className="bg-gradient-card absolute top-0 left-0 w-full h-full" /> }}
<div className="relative flex flex-col lg:gap-[0.278vw] gap-1"> />
<p className="heading2 font-medium">{title}</p> <div className="bg-gradient-card absolute top-0 left-0 w-full h-full" />
<div className="flex flex-wrap lg:gap-[0.278vw] gap-1"> <div className="relative flex flex-col lg:gap-[0.278vw] gap-1">
{company && ( <p className="heading2 font-medium">{title}</p>
<div className="lg:px-[0.556vw] lg:py-[0.417vw] px-2 py-1.5 font-medium lg:rounded-[1.181vw] rounded-[17px] bg-[#37393B99] [backdrop-filter:blur(12px)] flex items-center lg:gap-[0.278vw] gap-1 btns"> <div className="flex flex-wrap lg:gap-[0.278vw] gap-1">
<div {company && (
className="lg:w-[0.556vw] lg:h-[0.556vw] w-2 h-2 rounded-full aspect-square" <div className="lg:px-[0.556vw] lg:py-[0.417vw] px-2 py-1.5 font-medium lg:rounded-[1.181vw] rounded-[17px] bg-[#37393B99] [backdrop-filter:blur(12px)] flex items-center lg:gap-[0.278vw] gap-1 btns">
style={{ <div
backgroundColor: company.color ?? "#ffffff", className="lg:w-[0.556vw] lg:h-[0.556vw] w-2 h-2 rounded-full aspect-square"
}} style={{
/> backgroundColor: company.color ?? "#ffffff",
{company.title} }}
/>
{company.title}
</div>
)}
<div className="lg:px-[0.556vw] lg:py-[0.417vw] px-2 py-1.5 font-medium lg:rounded-[1.181vw] rounded-[17px] bg-[#37393B99] [backdrop-filter:blur(12px)] flex items-center btns">
{city}
</div> </div>
)} {stage! < 6 && (
<div className="lg:px-[0.556vw] lg:py-[0.417vw] px-2 py-1.5 font-medium lg:rounded-[1.181vw] rounded-[17px] bg-[#37393B99] [backdrop-filter:blur(12px)] flex items-center btns"> <div className="bg-[#37393B99] lg:px-[0.833vw] lg:py-[0.556vw] px-3 py-2 lg:rounded-[1.181vw] rounded-[17px] w-fit flex items-center lg:gap-[0.278vw] gap-1 [backdrop-filter:blur(4px)]">
{city} <ProgressPie value={stagePercentage} />
<p className="btns font-medium">{stagePercentage}%</p>
</div>
)}
</div> </div>
{stage! < 6 && (
<div className="bg-[#37393B99] lg:px-[0.833vw] lg:py-[0.556vw] px-3 py-2 lg:rounded-[1.181vw] rounded-[17px] w-fit flex items-center lg:gap-[0.278vw] gap-1 [backdrop-filter:blur(4px)]">
<ProgressPie value={stagePercentage} />
<p className="btns font-medium">{stagePercentage}%</p>
</div>
)}
</div> </div>
</div> </motion.div>
</motion.div> </Link>
);
}
function CaseBadge({ logo }: { logo: string }) {
return (
<div className="absolute lg:top-[0.278vw] lg:right-[0.278vw] md:top-[0.521vw] md:right-[0.521vw] top-[1.111vw] right-[1.111vw] flex flex-col items-center justify-center z-[1] lg:p-[0.278vw] md:p-[0.521vw] p-[1.111vw] bg-[#37393B99] backdrop-blur-[4px] lg:rounded-[1.111vw] md:rounded-[2.083vw] rounded-[4.444vw]">
<img src={logo} alt="" className="w-full mb-[0.139vw] object-cover" />
<span className="lg:text-[0.694vw] md:text-[1.302vw] text-[2.778vw] leading-[120%] text-center">
Смотреть <br /> кейс
</span>
</div>
); );
} }
@@ -1,21 +1,24 @@
'use client'; "use client";
import { useGetProjectsQuery } from '@/queries/getProjects'; import { useGetProjectsQuery } from "@/queries/getProjects";
import { ProjectsSection } from './ProjectsSection'; import { ProjectsSection } from "./ProjectsSection";
import { TagsFilters } from './TagsFilters'; import { TagsFilters } from "./TagsFilters";
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from "next/navigation";
export function ProjectsList() { export function ProjectsList() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const { data: projects } = useGetProjectsQuery(searchParams.getAll('tags')); const { data: projects } = useGetProjectsQuery(searchParams.getAll("tags"));
return ( return (
<div className="grid lg:grid-cols-[1fr_4fr_1fr] items-end gap-x-[34px] relative"> <div className="grid lg:grid-cols-[1fr_4fr_1fr] items-end gap-x-[34px] relative">
<TagsFilters tags={searchParams.getAll('tags')} type="project" /> <TagsFilters tags={searchParams.getAll("tags")} type="project" />
<div className="lg:col-start-2 lg:max-xl:col-end-4 self-start"> <div className="lg:col-start-2 lg:max-xl:col-end-4 self-start">
{projects && projects.length ? ( {projects && projects.length ? (
<ProjectsSection projects={projects} /> <ProjectsSection
projects={projects}
prefferedCompanies={["Upside Девелопмент"]}
/>
) : ( ) : (
<p className="h3 font-medium text-center h-screen"> <p className="h3 font-medium text-center h-screen">
Проекты не найдены Проекты не найдены
@@ -22,6 +22,12 @@ export function ProjectsSection({
}) { }) {
const pathname = usePathname(); const pathname = usePathname();
const sortedProjects = [...projects].sort((a, b) => {
const aPreferred = prefferedCompanies.includes(a.company?.title ?? "");
const bPreferred = prefferedCompanies.includes(b.company?.title ?? "");
return aPreferred === bPreferred ? 0 : aPreferred ? -1 : 1;
});
return ( return (
<div <div
className={`grid ${ className={`grid ${
@@ -29,14 +35,14 @@ export function ProjectsSection({
} md:max-lg:grid-cols-2 gap-3`} } md:max-lg:grid-cols-2 gap-3`}
> >
{showCases {showCases
? projects.map((project) => ( ? sortedProjects.map((project) => (
<CasesCard <CasesCard
key={project.id} key={project.id}
project={project} project={project}
prefferedCompanies={prefferedCompanies} prefferedCompanies={prefferedCompanies}
/> />
)) ))
: projects.map((project) => ( : sortedProjects.map((project) => (
<ProjectCard key={project.id} {...project} /> <ProjectCard key={project.id} {...project} />
))} ))}
@@ -0,0 +1,121 @@
"use client";
import React, { useState } from "react";
import BR from "@/components/Layout/LineBreak";
import { useGetProjectsQuery } from "@/queries/getProjects";
import { StreamingProject } from "../MainPage/Streaming/StreamingProject";
import { useSwipeable } from "react-swipeable";
import { useMediaQueries } from "@/hooks/useMediaQueries";
import Link from "next/link";
export default function AvailableDemos() {
const { isMd, isLg } = useMediaQueries();
const { data: streamingProjects } = useGetProjectsQuery(
"Удаленная демонстрация"
);
const [current, setCurrent] = useState(0);
const handlers = useSwipeable({
onSwipedLeft: () =>
setCurrent(
(prev) => (prev + 1) % Math.min(streamingProjects!.length + 1, 4)
),
onSwipedRight: () =>
setCurrent(
(prev) =>
(prev + Math.min(streamingProjects!.length, 4)) %
Math.min(streamingProjects!.length + 1, 4)
),
trackMouse: true,
preventScrollOnSwipe: true,
touchEventOptions: { passive: false },
});
return (
<div>
<div className="flex lg:flex-row flex-col lg:mb-[4.444vw] md:mb-[8.333vw] md:gap-[3.125vw]">
<h2 className="line2 max-md:line1 w-full max-md:mb-[5.556vw]">
Доступные <BR lg sm /> демонстрации
</h2>
{!isLg && !isMd && (
<div
className="lg:grid md:flex grid-cols-4 gap-3 px-5 md:-mx-5 md:overflow-auto [scrollbar-width:none] relative max-md:aspect-[340/344] [transform-style:preserve-3d] items-stretch mb-[5.556vw]"
{...handlers}
>
{streamingProjects
?.slice(0, 3)
.map((project, index, { length }) => (
<StreamingProject
key={project.id}
{...project}
index={index}
current={current}
count={length + 1}
href="/"
/>
))}
<div
className={`bg-gradient-to-r from-[#FFFFFF14] to-[#FFFFFF00] [background:linear-gradient(to_right,#FFFFFF14,#FFFFFF00)] p-0.5 lg:rounded-[1.111vw] rounded-2xl flex flex-1 justify-center !duration-500 items-center md:min-w-[300px] group max-md:absolute self-stretch max-md:h-full transition-[scale,transform] will-change-[transform,scale] select-none max-md:w-[calc(100%-40px)] max-md:bg-[#0F101199] max-md:[backdrop-filter:blur(40px)] ${
streamingProjects &&
(Math.min(streamingProjects!.length + 1, 4) - 1 === current
? "max-md:[transform:translateZ(40px)]"
: "max-md:[scale:85%]")
} ${
streamingProjects &&
(Math.min(streamingProjects!.length + 1, 4) - 1 ===
(current + 1) % Math.min(streamingProjects!.length + 1, 4)
? "max-md:translate-x-[calc(7.5%+20px)]"
: Math.min(streamingProjects!.length + 1, 4) - 1 ===
(current - 1 + Math.min(streamingProjects!.length + 1, 4)) %
Math.min(streamingProjects!.length + 1, 4)
? "max-md:translate-x-[calc(-7.5%-20px)]"
: "")
}`}
>
<div className="md:bg-[#0F1011] h-full w-full lg:rounded-[1.111vw] rounded-2xl flex items-center p-6">
<div className="flex flex-col items-center space-y-6">
<p className="heading2 font-medium text-center">
Расскажем и покажем как это работает на&nbsp;созвоне
</p>
<Link
href={"/form"}
className="btnm font-medium group-hover:scale-105 duration-500 lg:px-[1.667vw] lg:py-[1.181vw] px-6 py-[17px] transition-transform lg:rounded-[0.833vw] rounded-xl bg-gradient"
>
Оставить заявку
</Link>
</div>
</div>
</div>
</div>
)}
<p className="lg:headline1 headline2 text-[#7A7A7A] w-full pr-[8.333vw] md:max-w-[75vw]">
Клиент из любой точки мира может посмотреть жилой комплекс, даже на
нулевом этапе строительства. Он выберет лучшую планировку и оценит вид
из окон своей будущей квартиры.
</p>
</div>
{(isLg || isMd) && (
<div className="flex md:-mx-[2.604vw] md:w-[calc(100%+5.208vw)] md:px-[2.604vw] lg:gap-[0.833vw] md:gap-[1.563vw] lg:h-[27.5vw] md:h-[51.563vw] md:overflow-x-scroll hide-scrollbars">
{streamingProjects?.slice(0, 3).map((project, index, { length }) => (
<div
key={project.id}
className={`w-full ${index === 0 ? "flex-auto" : "flex-1"}`}
>
<StreamingProject
key={project.id}
{...project}
index={index}
current={current}
count={length + 1}
href="/"
className="w-full"
/>
</div>
))}
</div>
)}
</div>
);
}
@@ -0,0 +1,50 @@
/* eslint-disable @next/next/no-img-element */
"use client";
import React from "react";
import BR from "@/components/Layout/LineBreak";
import { Button } from "@/ui/Button";
import QuestionFormModal from "@/components/modals/QuestionFormModal";
import { useModalStore } from "@/stores/useModalStore";
export default function RequestForDemo() {
const { setModal } = useModalStore();
return (
<div
className="
flex max-md:flex-col flex-row lg:gap-[0.833vw] md:gap-[2.865vw] gap-[11.111vw]
lg:-mx-[1.389vw] lg:w-[calc(100%+2.778vw)] lg:pl-[1.389vw]
"
>
<div className="flex flex-col lg:max-w-[31.944vw] min-h-full">
<h2 className="line2 max-md:mb-[6.667vw]">
Запись <BR lg md /> на удаленную
<BR /> демонстрацию
</h2>
<div className="flex flex-col lg:gap-[2.222vw] md:gap-[4.167vw] mt-auto">
<p className="lg:headline1 headline2 text-[#7A7A7A]">
Запись на демонстрацию может быть оформлена в виде блока на сайте
застройщика или жилого комплекса.
</p>
<Button
onClick={() => {
setModal(
<QuestionFormModal products={["Удаленная демонстрация"]} />
);
}}
className="max-md:hidden btnl bg-gradient-saturated lg:py-[1.389vw] lg:px-[2.222vw] md:py-[2.604vw] md:px-[4.167vw] md:rounded-[2.083vw] lg:rounded-[1.111vw]"
>
Оставить заявку
</Button>
</div>
</div>
<img
src="/img/pages/stream-demo/showreel.png"
alt=""
className="lg:h-[44.444vw] md:h-[57.292vw] h-[122.222vw] max-md:rounded-[4.444vw] object-cover"
/>
</div>
);
}
@@ -0,0 +1,18 @@
"use client";
import React from "react";
import AvailableDemos from "./AvailableDemos";
import RequestForDemo from "./RequestForDemo";
import StreamPlayer from "../StreamPage/StreamPlayer";
export default function StreamDemo() {
return (
<div className="lg:space-y-[9.722vw] md:space-y-[13.021vw] space-y-[27.778vw]">
<AvailableDemos />
<RequestForDemo />
<div className="lg:h-[44.444vw] ">
<StreamPlayer className="h-full" />
</div>
</div>
);
}
@@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import { VideoPlayer } from "@/ui/VideoPlayer"; import { VideoPlayer } from "@/ui/VideoPlayer";
export default function StreamPlayer() { export default function StreamPlayer({ className }: { className?: string }) {
return ( return (
<div className="w-full"> <div className={`w-full ${className}`}>
<VideoPlayer <VideoPlayer
src="/videos/pages/home/streaming.mp4" src="/videos/pages/home/streaming.mp4"
showMutingBtn showMutingBtn
+3 -1
View File
@@ -106,7 +106,9 @@ export default function WebDemo() {
// Загружаем iframe только после монтирования компонента // Загружаем iframe только после монтирования компонента
useEffect(() => { useEffect(() => {
setIframeSrc("/virtualTour/index.html"); setIframeSrc(
`${process.env.NEXT_PUBLIC_S3_BUCKET}graff-estate-irth-vrt/index.html`
);
}, []); }, []);
return ( return (
@@ -10,7 +10,7 @@ export default function CasesSelection() {
<div> <div>
<Projects <Projects
title={false} title={false}
prefferedCompanies={["Upside Development"]} prefferedCompanies={["Upside Девелопмент"]}
showCases={true} showCases={true}
/> />
</div> </div>
@@ -122,6 +122,7 @@ export default function UpsideArchitecture() {
src="/img/cases/UpsideTowers/architecture/appartament-card.webp" src="/img/cases/UpsideTowers/architecture/appartament-card.webp"
alt="" alt=""
className="lg:mb-[1.667vw] md:mb-[3.125vw] mb-[4.444vw]" className="lg:mb-[1.667vw] md:mb-[3.125vw] mb-[4.444vw]"
loading="lazy"
/> />
<p className="lg:text-[1.389vw] md:text-[2.083vw] text-[3.889vw] max-md:text-center leading-[135%] tracking-[-0.02em]"> <p className="lg:text-[1.389vw] md:text-[2.083vw] text-[3.889vw] max-md:text-center leading-[135%] tracking-[-0.02em]">
Актуальная информация о квартирах <BR sm md /> подгружается прямо Актуальная информация о квартирах <BR sm md /> подгружается прямо
@@ -131,13 +132,15 @@ export default function UpsideArchitecture() {
<div className="lg:w-[72.708vw] md:w-[88.542vw] w-[94.444vw] lg:h-[40.972vw] md:h-[49.74vw] mx-auto relative"> <div className="lg:w-[72.708vw] md:w-[88.542vw] w-[94.444vw] lg:h-[40.972vw] md:h-[49.74vw] mx-auto relative">
<img <img
src="/img/cases/UpsideTowers/architecture/appartament-tablet.webp" src="/img/cases/UpsideTowers/architecture/appartament-tablet.png"
alt="" alt=""
loading="lazy"
/> />
<img <img
src="/img/cases/UpsideTowers/architecture/appartament-tablet-content.webp" src="/img/cases/UpsideTowers/architecture/appartament-tablet-content.png"
alt="" alt=""
className="absolute lg:top-[2.1vw] md:top-[2.6vw] top-[2.7vw] lg:left-[14.167vw] md:left-[17.8vw] left-[19.056vw] lg:w-[52.872vw] md:w-[64.104vw] w-[68vw] object-cover" className="absolute lg:top-[2.1vw] md:top-[2.6vw] top-[2.7vw] lg:left-[14.167vw] md:left-[17.8vw] left-[19.056vw] lg:w-[52.872vw] md:w-[64.104vw] w-[68vw] object-cover"
loading="lazy"
/> />
</div> </div>
</div> </div>
@@ -184,6 +187,7 @@ export default function UpsideArchitecture() {
src="/img/cases/UpsideTowers/architecture/dvor.webp" src="/img/cases/UpsideTowers/architecture/dvor.webp"
alt="" alt=""
className="lg:size-[5.139vw] md:size-[7.292vw] size-[15.556vw]" className="lg:size-[5.139vw] md:size-[7.292vw] size-[15.556vw]"
loading="lazy"
/> />
<span className="lg:text-[1.389vw] md:text-[2.083vw] text-[3.889vw]"> <span className="lg:text-[1.389vw] md:text-[2.083vw] text-[3.889vw]">
Двор Двор
@@ -194,6 +198,7 @@ export default function UpsideArchitecture() {
src="/img/cases/UpsideTowers/architecture/MOPs.webp" src="/img/cases/UpsideTowers/architecture/MOPs.webp"
alt="" alt=""
className="lg:size-[5.139vw] md:size-[7.292vw] size-[15.556vw]" className="lg:size-[5.139vw] md:size-[7.292vw] size-[15.556vw]"
loading="lazy"
/> />
<span className="lg:text-[1.389vw] md:text-[2.083vw] text-[3.889vw]"> <span className="lg:text-[1.389vw] md:text-[2.083vw] text-[3.889vw]">
МОПы МОПы
@@ -204,6 +209,7 @@ export default function UpsideArchitecture() {
src="/img/cases/UpsideTowers/architecture/appartaments.webp" src="/img/cases/UpsideTowers/architecture/appartaments.webp"
alt="" alt=""
className="lg:size-[5.139vw] md:size-[7.292vw] size-[15.556vw]" className="lg:size-[5.139vw] md:size-[7.292vw] size-[15.556vw]"
loading="lazy"
/> />
<span className="lg:text-[1.389vw] md:text-[2.083vw] text-[3.889vw]"> <span className="lg:text-[1.389vw] md:text-[2.083vw] text-[3.889vw]">
Квартиры Квартиры
@@ -240,7 +246,7 @@ export default function UpsideArchitecture() {
avatar="/img/cases/UpsideTowers/avatars/5.png" avatar="/img/cases/UpsideTowers/avatars/5.png"
className="absolute top-[18.056vw] right-[3.472vw] z-[2]" className="absolute top-[18.056vw] right-[3.472vw] z-[2]"
> >
<p>Насколько светло будет в кухне зимним утром?</p> <p>Насколько светло будет в кухне летним утром?</p>
</UpsideComment> </UpsideComment>
<video <video
@@ -255,6 +261,7 @@ export default function UpsideArchitecture() {
src="/img/cases/UpsideTowers/architecture/seasons.webp" src="/img/cases/UpsideTowers/architecture/seasons.webp"
alt="" alt=""
className="w-[69.792vw] h-[39.236vw] absolute bottom-0 right-0 z-[2] rounded-[1.181vw] shadow-[0_0_10px_0_#00000040]" className="w-[69.792vw] h-[39.236vw] absolute bottom-0 right-0 z-[2] rounded-[1.181vw] shadow-[0_0_10px_0_#00000040]"
loading="lazy"
/> />
<UpsideComment <UpsideComment
@@ -265,9 +272,8 @@ export default function UpsideArchitecture() {
</UpsideComment> </UpsideComment>
</div> </div>
<Subtitle className="z-[2] relative text-[#7A7A7A] text-center"> <Subtitle className="z-[2] relative text-center">
Эти вопросы решаются движением ползунка. <BR md lg />{" "} Смена сезонов и времени суток <br /> за секунды
<span className="text-white">Это высший уровень доверия!</span>
</Subtitle> </Subtitle>
</div> </div>
</div> </div>
@@ -351,6 +357,7 @@ export default function UpsideArchitecture() {
src="/img/cases/UpsideTowers/DisplacementCards/4.webp" src="/img/cases/UpsideTowers/DisplacementCards/4.webp"
alt="" alt=""
className=" absolute lg:top-0 lg:w-[8.519vw] lg:left-[3.792vw] md:w-[9.245vw] w-[18.667vw] md:top-[3vw] top-[8.056vw] md:left-1/2 lg:-translate-x-0 md:-translate-x-1/2 z-[2]" className=" absolute lg:top-0 lg:w-[8.519vw] lg:left-[3.792vw] md:w-[9.245vw] w-[18.667vw] md:top-[3vw] top-[8.056vw] md:left-1/2 lg:-translate-x-0 md:-translate-x-1/2 z-[2]"
loading="lazy"
/> />
<div className="w-full h-full absolute z-[1] bottom-0 left-0 bg-[linear-gradient(45deg,#0F101150_40%,#0F101100_80%)]"></div> <div className="w-full h-full absolute z-[1] bottom-0 left-0 bg-[linear-gradient(45deg,#0F101150_40%,#0F101100_80%)]"></div>
@@ -370,6 +377,7 @@ export default function UpsideArchitecture() {
src="/img/cases/UpsideTowers/DisplacementCards/5_full.webp" src="/img/cases/UpsideTowers/DisplacementCards/5_full.webp"
alt="" alt=""
className="lg:hidden block absolute lg:top-0 md:top-[3vw] top-[6vw] md:w-[20.182vw] w-[42.778vw] md:left-1/2 md:-translate-x-1/2 z-[1]" className="lg:hidden block absolute lg:top-0 md:top-[3vw] top-[6vw] md:w-[20.182vw] w-[42.778vw] md:left-1/2 md:-translate-x-1/2 z-[1]"
loading="lazy"
/> />
<div className="w-full h-full absolute z-[1] bottom-0 left-0 bg-[linear-gradient(45deg,#0F101150_40%,#0F101100_80%)]"></div> <div className="w-full h-full absolute z-[1] bottom-0 left-0 bg-[linear-gradient(45deg,#0F101150_40%,#0F101100_80%)]"></div>
@@ -49,6 +49,7 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/info_full.webp" src="/img/cases/UpsideTowers/features-cards/info_full.webp"
alt="" alt=""
className="absolute md:w-[18.229vw] w-[36.889vw] md:bottom-[3.906vw] bottom-[0] left-1/2 -translate-x-1/2 z-[1]" className="absolute md:w-[18.229vw] w-[36.889vw] md:bottom-[3.906vw] bottom-[0] left-1/2 -translate-x-1/2 z-[1]"
loading="lazy"
/> />
</UpsideFeaturesItem> </UpsideFeaturesItem>
<UpsideFeaturesItem> <UpsideFeaturesItem>
@@ -59,6 +60,7 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/favorites_full.webp" src="/img/cases/UpsideTowers/features-cards/favorites_full.webp"
alt="" alt=""
className="absolute md:w-[18.229vw] w-[36.889vw] md:bottom-[3.906vw] bottom-[0] left-1/2 -translate-x-1/2 z-[1]" className="absolute md:w-[18.229vw] w-[36.889vw] md:bottom-[3.906vw] bottom-[0] left-1/2 -translate-x-1/2 z-[1]"
loading="lazy"
/> />
</UpsideFeaturesItem> </UpsideFeaturesItem>
<UpsideFeaturesItem className="bg-[url('/img/cases/UpsideTowers/DisplacementCards/2.webp')]"> <UpsideFeaturesItem className="bg-[url('/img/cases/UpsideTowers/DisplacementCards/2.webp')]">
@@ -90,16 +92,16 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/scenarios_full.webp" src="/img/cases/UpsideTowers/features-cards/scenarios_full.webp"
alt="" alt=""
className="absolute md:w-[18.229vw] w-[36.889vw] md:bottom-[3.906vw] bottom-[0] left-1/2 -translate-x-1/2 z-[1]" className="absolute md:w-[18.229vw] w-[36.889vw] md:bottom-[3.906vw] bottom-[0] left-1/2 -translate-x-1/2 z-[1]"
loading="lazy"
/> />
</UpsideFeaturesItem> </UpsideFeaturesItem>
<UpsideFeaturesItem> <UpsideFeaturesItem>
<div className="mt-auto relative z-[2]"> <div className="mt-auto relative z-[2]">Статичные рендеры</div>
Рекламная графика <br /> за несколько секунд
</div>
<img <img
src="/img/cases/UpsideTowers/features-cards/graphics.webp" src="/img/cases/UpsideTowers/features-cards/graphics.png"
alt="" alt=""
className="absolute md:top-[5.469vw] left-1/2 md:w-[18.323vw] w-[30.556vw] -translate-x-1/2 z-[1]" className="absolute md:top-[5.469vw] left-1/2 md:w-[18.323vw] w-[35.556vw] -translate-x-1/2 z-[1]"
loading="lazy"
/> />
</UpsideFeaturesItem> </UpsideFeaturesItem>
<UpsideFeaturesItem className="col-span-2 flex-col gap-[0.556vw]"> <UpsideFeaturesItem className="col-span-2 flex-col gap-[0.556vw]">
@@ -118,6 +120,7 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/stream.webp" src="/img/cases/UpsideTowers/features-cards/stream.webp"
alt="" alt=""
className="md:w-[8.724vw] w-[18.611vw]" className="md:w-[8.724vw] w-[18.611vw]"
loading="lazy"
/> />
<span> <span>
GRAFF.estate GRAFF.estate
@@ -130,6 +133,7 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/summary.webp" src="/img/cases/UpsideTowers/features-cards/summary.webp"
alt="" alt=""
className="md:w-[7.292vw] w-[15.556vw]" className="md:w-[7.292vw] w-[15.556vw]"
loading="lazy"
/> />
<span> <span>
Саммаризация <br /> Саммаризация <br />
@@ -141,6 +145,7 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/voice.webp" src="/img/cases/UpsideTowers/features-cards/voice.webp"
alt="" alt=""
className="md:w-[7.292vw] w-[15.556vw]" className="md:w-[7.292vw] w-[15.556vw]"
loading="lazy"
/> />
<span> <span>
Управление <br /> Управление <br />
@@ -189,6 +194,7 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/info.webp" src="/img/cases/UpsideTowers/features-cards/info.webp"
alt="" alt=""
className="absolute w-[21.181vw] bottom-0 left-1/2 -translate-x-1/2 z-[1]" className="absolute w-[21.181vw] bottom-0 left-1/2 -translate-x-1/2 z-[1]"
loading="lazy"
/> />
</UpsideFeaturesItem> </UpsideFeaturesItem>
</div> </div>
@@ -202,6 +208,7 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/favorites.webp" src="/img/cases/UpsideTowers/features-cards/favorites.webp"
alt="" alt=""
className="absolute bottom-0 left-1/2 -translate-x-1/2 z-[1] w-[21.181vw]" className="absolute bottom-0 left-1/2 -translate-x-1/2 z-[1] w-[21.181vw]"
loading="lazy"
/> />
</UpsideFeaturesItem> </UpsideFeaturesItem>
@@ -240,16 +247,16 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/scenarios.webp" src="/img/cases/UpsideTowers/features-cards/scenarios.webp"
alt="" alt=""
className="absolute bottom-0 left-1/2 -translate-x-1/2 z-[1] w-[21.181vw]" className="absolute bottom-0 left-1/2 -translate-x-1/2 z-[1] w-[21.181vw]"
loading="lazy"
/> />
</UpsideFeaturesItem> </UpsideFeaturesItem>
<UpsideFeaturesItem className="w-[23.472vw]"> <UpsideFeaturesItem className="w-[23.472vw]">
<div className="mt-auto relative z-[2]"> <div className="mt-auto relative z-[2]">Статичные рендеры</div>
Рекламная графика <br /> за несколько секунд
</div>
<img <img
src="/img/cases/UpsideTowers/features-cards/graphics.webp" src="/img/cases/UpsideTowers/features-cards/graphics.png"
alt="" alt=""
className="absolute top-[3.056vw] left-1/2 w-[12.222vw] -translate-x-1/2 z-[1]" className="absolute top-[3.056vw] left-1/2 w-[15.556vw] -translate-x-1/2 z-[1]"
loading="lazy"
/> />
</UpsideFeaturesItem> </UpsideFeaturesItem>
<UpsideFeaturesItem className="w-[47.456vw] flex-col gap-[0.556vw]"> <UpsideFeaturesItem className="w-[47.456vw] flex-col gap-[0.556vw]">
@@ -268,6 +275,7 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/stream.webp" src="/img/cases/UpsideTowers/features-cards/stream.webp"
alt="" alt=""
className="w-[5.347vw]" className="w-[5.347vw]"
loading="lazy"
/> />
<span className="text-center">Удаленная демонстрация</span> <span className="text-center">Удаленная демонстрация</span>
</div> </div>
@@ -276,6 +284,7 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/summary.webp" src="/img/cases/UpsideTowers/features-cards/summary.webp"
alt="" alt=""
className="w-[4.444vw]" className="w-[4.444vw]"
loading="lazy"
/> />
<span> <span>
Саммаризация <br /> Саммаризация <br />
@@ -287,6 +296,7 @@ export default function UpsideFeatures() {
src="/img/cases/UpsideTowers/features-cards/voice.webp" src="/img/cases/UpsideTowers/features-cards/voice.webp"
alt="" alt=""
className="w-[4.444vw]" className="w-[4.444vw]"
loading="lazy"
/> />
<span> <span>
Управление <br /> Управление <br />
@@ -6,13 +6,13 @@ import UpsideHeroCard from "./components/UpsideHeroCard";
export default function UpsideHero() { export default function UpsideHero() {
return ( return (
<div> <div>
<h1 className="lg:text-[6.667vw] md:text-[6.25vw] text-[8.889vw] lg:mb-[0] md:mb-[6.25vw] mb-[11.111vw] font-medium leading-[85%] tracking-[-0.04em] lg:-mx-[1vw] text-center z-[2]"> <h1 className="lg:text-[6.667vw] md:text-[6.25vw] text-[8.889vw] lg:mb-[5vw] md:mb-[6.25vw] mb-[11.111vw] font-medium leading-[85%] tracking-[-0.04em] lg:-mx-[1vw] text-center z-[2]">
Интерактивная презентация <br /> жилого комплекса{" "} Интерактивная презентация <br /> жилого комплекса{" "}
<span className="text-[#23FF00]">Апсайд Тауэрс</span> <span className="text-[#23FF00]">Апсайд Тауэрс</span>
</h1> </h1>
<img <img
src="/img/cases/UpsideTowers/hero/hero.png" src="/img/cases/UpsideTowers/hero/hero.avif"
alt="" alt=""
fetchPriority="high" fetchPriority="high"
className="lg:w-[69.444vw] lg:h-[59.444vw] object-cover mx-auto z-[1] overflow-visible" className="lg:w-[69.444vw] lg:h-[59.444vw] object-cover mx-auto z-[1] overflow-visible"
@@ -25,7 +25,7 @@ export default function UpsideHero() {
> >
<p className="lg:text-[1.667vw] md:text-[2.344vw] text-[5vw] leading-[135%] tracking-[0]"> <p className="lg:text-[1.667vw] md:text-[2.344vw] text-[5vw] leading-[135%] tracking-[0]">
Upside Upside
<br /> Development <br /> Девелопмент
</p> </p>
</UpsideHeroCard> </UpsideHeroCard>
@@ -54,7 +54,7 @@ export default function UpsideHero() {
<img <img
src="/img/cases/UpsideTowers/hero/awards.png" src="/img/cases/UpsideTowers/hero/awards.png"
alt="" alt=""
className="lg:w-[11.389vw] object-cover" className="lg:w-[15.972vw] object-cover"
/> />
</UpsideHeroCard> </UpsideHeroCard>
</div> </div>
@@ -12,7 +12,7 @@ export default function UpsideImplementation() {
в существующую в существующую
<BR lg /> экосистему <BR lg /> экосистему
</span>{" "} </span>{" "}
Upside Development Upside Девелопмент
</h2> </h2>
<p className="z-[2] md:text-[2.083vw] text-[3.889vw] leading-[110%] tracking-[-0.02em] text-center lg:hidden md:mb-[5.208vw] mb-[8.889vw] opacity-60"> <p className="z-[2] md:text-[2.083vw] text-[3.889vw] leading-[110%] tracking-[-0.02em] text-center lg:hidden md:mb-[5.208vw] mb-[8.889vw] opacity-60">
@@ -28,6 +28,7 @@ export default function UpsideLivingExperience() {
src="/img/cases/UpsideTowers/living-experience/1.webp" src="/img/cases/UpsideTowers/living-experience/1.webp"
alt="" alt=""
className="bottom-0 left-0 w-full absolute" className="bottom-0 left-0 w-full absolute"
loading="lazy"
/> />
</ParallaxCard> </ParallaxCard>
@@ -42,6 +43,7 @@ export default function UpsideLivingExperience() {
<img <img
src="/img/cases/UpsideTowers/living-experience/2.webp" src="/img/cases/UpsideTowers/living-experience/2.webp"
alt="" alt=""
loading="lazy"
/> />
</ParallaxCard> </ParallaxCard>
</div> </div>
@@ -51,7 +53,7 @@ export default function UpsideLivingExperience() {
<div className="flex bg-[linear-gradient(0deg,#0F1011EE_0%,#0F101100_100%)] lg:h-[31.736vw] lg:p-[1.667vw] z-[2] relative"> <div className="flex bg-[linear-gradient(0deg,#0F1011EE_0%,#0F101100_100%)] lg:h-[31.736vw] lg:p-[1.667vw] z-[2] relative">
<div className="lg:mt-auto max-md:mt-[6.667vw] flex lg:flex-row flex-col md:gap-[3.125vw] justify-between"> <div className="lg:mt-auto max-md:mt-[6.667vw] flex lg:flex-row flex-col md:gap-[3.125vw] justify-between">
<div className="lg:text-[2.5vw] md:text-[3.125vw] text-[5.556vw] lg:w-[60.5vw] leading-[110%] tracking-[-0.02em] max-md:mb-[6.667vw] "> <div className="lg:text-[2.5vw] md:text-[3.125vw] text-[5.556vw] lg:w-[60.5vw] leading-[110%] tracking-[-0.02em] max-md:mb-[6.667vw] ">
Upside Development, представляя премиальный проект Апсайд Тауэрс, Upside Девелопмент, представляя премиальный проект Апсайд Тауэрс,
столкнулся с классическими ограничениями рынка недвижимости: столкнулся с классическими ограничениями рынка недвижимости:
статичные картинки, планы БТИ и даже видео-туры не давали статичные картинки, планы БТИ и даже видео-туры не давали
достаточной эмоциональной связи и не отвечали на все вопросы достаточной эмоциональной связи и не отвечали на все вопросы
@@ -74,6 +76,7 @@ export default function UpsideLivingExperience() {
<img <img
src="/img/cases/UpsideTowers/living-experience/bg.webp" src="/img/cases/UpsideTowers/living-experience/bg.webp"
loading="lazy"
className="lg:h-full md:h-[78.125vw] h-[166.667vw] max-lg:object-cover lg:absolute md:relative scale-x-[-1] z-[1] lg:rounded-0 md:rounded-[2.083vw] rounded-[4.444vw]" className="lg:h-full md:h-[78.125vw] h-[166.667vw] max-lg:object-cover lg:absolute md:relative scale-x-[-1] z-[1] lg:rounded-0 md:rounded-[2.083vw] rounded-[4.444vw]"
alt="" alt=""
/> />
@@ -12,6 +12,7 @@ export default function UpsideValue() {
src="/img/cases/UpsideTowers/value/1.webp" src="/img/cases/UpsideTowers/value/1.webp"
alt="" alt=""
className="lg:rounded-[0.556vw] md:rounded-[1.042vw] rounded-[2.222vw] lg:mb-[4.444vw] md:mb-[13.021vw] mb-[27.778vw] lg:h-[54.653vw] md:h-[78.125vw] h-[94.444vw] object-cover" className="lg:rounded-[0.556vw] md:rounded-[1.042vw] rounded-[2.222vw] lg:mb-[4.444vw] md:mb-[13.021vw] mb-[27.778vw] lg:h-[54.653vw] md:h-[78.125vw] h-[94.444vw] object-cover"
loading="lazy"
/> />
<Title className="lg:mb-[4.444vw] md:mb-[5.208vw] mb-[11.111vw]"> <Title className="lg:mb-[4.444vw] md:mb-[5.208vw] mb-[11.111vw]">
@@ -25,6 +26,7 @@ export default function UpsideValue() {
src="/img/cases/UpsideTowers/value/table_short.webp" src="/img/cases/UpsideTowers/value/table_short.webp"
alt="" alt=""
className="mx-auto lg:w-[52.639vw] md:w-full relative z-[1]" className="mx-auto lg:w-[52.639vw] md:w-full relative z-[1]"
loading="lazy"
/> />
{/* Плашки для десктопа */} {/* Плашки для десктопа */}
@@ -36,6 +38,7 @@ export default function UpsideValue() {
src="/img/cases/UpsideTowers/value/pointer.svg" src="/img/cases/UpsideTowers/value/pointer.svg"
alt="" alt=""
className="ml-auto size-[4.167vw] rounded-[1.042vw]" className="ml-auto size-[4.167vw] rounded-[1.042vw]"
loading="lazy"
/> />
</div> </div>
{/* Плашки для десктопа */} {/* Плашки для десктопа */}
@@ -47,6 +50,7 @@ export default function UpsideValue() {
src="/img/cases/UpsideTowers/value/like.svg" src="/img/cases/UpsideTowers/value/like.svg"
alt="" alt=""
className="ml-auto size-[4.167vw] rounded-[1.042vw]" className="ml-auto size-[4.167vw] rounded-[1.042vw]"
loading="lazy"
/> />
</div> </div>
@@ -99,6 +103,7 @@ export default function UpsideValue() {
src="/img/cases/UpsideTowers/value/2.webp" src="/img/cases/UpsideTowers/value/2.webp"
alt="" alt=""
className="absolute lg:h-full md:h-[78.125vw] h-[166.667vw] object-cover lg:rounded-[1.111vw] rounded-[4.444vw]" className="absolute lg:h-full md:h-[78.125vw] h-[166.667vw] object-cover lg:rounded-[1.111vw] rounded-[4.444vw]"
loading="lazy"
/> />
<div className="w-full h-[50%] absolute bottom-0 left-0 bg-[linear-gradient(to_top,#0F1011BF_0%,#0F101100_80%)]" /> <div className="w-full h-[50%] absolute bottom-0 left-0 bg-[linear-gradient(to_top,#0F1011BF_0%,#0F101100_80%)]" />
@@ -144,6 +149,7 @@ export default function UpsideValue() {
src="/img/cases/UpsideTowers/value/3.webp" src="/img/cases/UpsideTowers/value/3.webp"
className="absolute h-full object-cover" className="absolute h-full object-cover"
alt="" alt=""
loading="lazy"
/> />
<div className="w-full h-[50%] absolute bottom-0 left-0 bg-[linear-gradient(to_top,#0F1011_0%,#0F101100_100%)]" /> <div className="w-full h-[50%] absolute bottom-0 left-0 bg-[linear-gradient(to_top,#0F1011_0%,#0F101100_100%)]" />
@@ -153,6 +159,7 @@ export default function UpsideValue() {
src="/img/cases/UpsideTowers/value/table-small.webp" src="/img/cases/UpsideTowers/value/table-small.webp"
alt="" alt=""
className="lg:w-[9.028vw] md:w-[16.927vw] w-[36.111vw]" className="lg:w-[9.028vw] md:w-[16.927vw] w-[36.111vw]"
loading="lazy"
/> />
<Subtitle className="z-[2] relative text-[#7A7A7A] max-md:w-[94.444vw] text-center"> <Subtitle className="z-[2] relative text-[#7A7A7A] max-md:w-[94.444vw] text-center">
@@ -170,6 +177,7 @@ export default function UpsideValue() {
src="/img/cases/UpsideTowers/value/schema.webp" src="/img/cases/UpsideTowers/value/schema.webp"
alt="" alt=""
className="lg:w-[18.264vw] md:w-[34.245vw] w-[73.056vw]" className="lg:w-[18.264vw] md:w-[34.245vw] w-[73.056vw]"
loading="lazy"
/> />
<Subtitle className="z-[2] relative text-[#7A7A7A] max-md:w-[94.444vw] text-center"> <Subtitle className="z-[2] relative text-[#7A7A7A] max-md:w-[94.444vw] text-center">
@@ -24,6 +24,7 @@ export default function UpsideComment({
<img <img
src={avatar} src={avatar}
alt="" alt=""
loading="lazy"
style={{ style={{
width: `${avatarSize}px`, width: `${avatarSize}px`,
height: `${avatarSize}px`, height: `${avatarSize}px`,
+6
View File
@@ -0,0 +1,6 @@
export const cases: Record<string, { logo: string; link: string }> = {
"ЖК «Апсайд Тауэрс»": {
logo: "/img/cases/logos/UpsideTowers.svg",
link: "/upside-towers",
}
}
+5
View File
@@ -11,6 +11,9 @@ interface IPopupStore {
setIsShowResultsPopup: (isShowResultsPopup: boolean) => void; setIsShowResultsPopup: (isShowResultsPopup: boolean) => void;
resultsPopupState: "expanded" | "collapsed"; resultsPopupState: "expanded" | "collapsed";
setResultsPopupState: (resultsPopupState: "expanded" | "collapsed") => void; setResultsPopupState: (resultsPopupState: "expanded" | "collapsed") => void;
isShowCasePopup: boolean | undefined;
setIsShowCasePopup: (isShowCasePopup: boolean) => void;
} }
export const usePopupStore = create<IPopupStore>()( export const usePopupStore = create<IPopupStore>()(
@@ -28,6 +31,8 @@ export const usePopupStore = create<IPopupStore>()(
resultsPopupState: "expanded", resultsPopupState: "expanded",
setResultsPopupState: (resultsPopupState: "expanded" | "collapsed") => setResultsPopupState: (resultsPopupState: "expanded" | "collapsed") =>
set({ resultsPopupState }), set({ resultsPopupState }),
isShowCasePopup: undefined,
setIsShowCasePopup: (isShowCasePopup: boolean) => set({ isShowCasePopup }),
}), }),
{ {
name: 'popup', name: 'popup',
+6 -3
View File
@@ -1,20 +1,23 @@
import Link from 'next/link'; import Link from "next/link";
export function HeaderLink({ export function HeaderLink({
href, href,
text, text,
className = '', target,
className = "",
}: { }: {
href: string; href: string;
text: string; text: string;
className?: string; className?: string;
target?: string;
}) { }) {
return ( return (
<Link <Link
href={href} href={href}
className={`lg:rounded-[1.111vw] lg:px-[1.667vw] lg:py-[1.111vw] md:max-lg:px-6 md:max-lg:py-4 outline-none px-2 py-2 font-medium text-nowrap hover:bg-[#232425] active:bg-white active:text-black rounded-2xl transition-colors flex items-center${ className={`lg:rounded-[1.111vw] lg:px-[1.667vw] lg:py-[1.111vw] md:max-lg:px-6 md:max-lg:py-4 outline-none px-2 py-2 font-medium text-nowrap hover:bg-[#232425] active:bg-white active:text-black rounded-2xl transition-colors flex items-center${
className ? ' ' + className : '' className ? " " + className : ""
}`} }`}
target={target}
> >
{text} {text}
</Link> </Link>