Created slider components for both desktop and mobile. Created support types

This commit is contained in:
2026-01-22 14:47:02 +05:00
parent fe75e9aea0
commit 39d5525aa2
13 changed files with 609 additions and 716 deletions
+9 -31
View File
@@ -6,20 +6,14 @@ import {
dubaiMarinaDescriptionBadges,
sliderBadgesCategory,
} from "../data/aboutDubaiMarina";
import {
AnimatePresence,
motion,
useInView,
useScroll,
useTransform,
} from "motion/react";
import { AnimatePresence, motion, useScroll, useTransform } from "motion/react";
import useWindowSize from "../hooks/useWindowSize";
import TextBox from "./ui/TextBox";
import SliderMobile from "./SliderMobile";
import clsx from "clsx";
import Slider from "./Slider";
import PlusIcon from "./icons/map/PlusIcon";
import EqualIcon from "./icons/EqualIcon";
import AboutSlider from "./AboutSlider/AboutSlider";
// import FullScreenButton from "./FullScreenButton";
function AboutDubaiMarina() {
@@ -37,22 +31,12 @@ function AboutDubaiMarina() {
});
const firstSectionOpacity = useTransform(scrollYProgress, [0, 0.2], [1, 0]);
const secondSectionY = useTransform(
scrollYProgress,
[0, 0.4],
["100dvh", "0dvh"]
);
const isSliderInView = useInView(sliderRef, {
once: true,
amount: 0.1,
});
// const isMapInView = useInView(mapRef, {
// once: true,
// margin: `0px 0px ${-window.innerHeight / 2}px 0px`,
// });
return (
<div className="relative bg-white" ref={containerRef}>
<motion.section
@@ -153,6 +137,7 @@ function AboutDubaiMarina() {
))}
</div>
</div>
<div className="flex flex-col items-center pt-[4.444vw] px-8 gap-[4.444vw] max-md:px-[4.444vw] max-2xl:pr-[3.125vw] max-2xl:-mr-[3.125vw] max-2xl:p-6 max-2xl:items-start max-2xl:gap-[4.167vw]">
<h1 className="font-mixcase-unmixed 2xl:text-[3.889vw] text-[#0D1922] tracking-[-0.05em] max-md:text-2xl max-2xl:text-[7.292vw] max-2xl:self-center">
Dubai, <span className="text-[#0D192266]">within reach</span>
@@ -199,19 +184,11 @@ function AboutDubaiMarina() {
ref={sliderRef}
className="flex flex-col gap-8 max-md:hidden"
>
<motion.div
key={`slider-${selectedCategorySlider}`}
initial={{ opacity: 0, y: 40 }}
animate={
isSliderInView
? { opacity: 1, y: 0 }
: { opacity: 0, y: 140 }
}
exit={{ opacity: 0, y: -40 }}
transition={{ duration: 0.6, ease: "easeIn" }}
>
<Slider categoryName={selectedCategorySlider} />
</motion.div>
<div className="2xl:mx-auto 2xl:w-[63.333vw] 2xl:h-[27.639vw] md:w-[calc(100%-3.125vw)] md:h-[51.823vw] mb-[1vw] max-md:hidden">
<AboutSlider
items={dubaiMarinaSlider[selectedCategorySlider]}
/>
</div>
<motion.div
key={`badges-${selectedCategorySlider}`}
className="flex gap-[0.556vw] w-[63.333vw] flex-wrap justify-center max-2xl:w-[93.75vw]"
@@ -236,6 +213,7 @@ function AboutDubaiMarina() {
</div>
</AnimatePresence>
</div>
<div className="text-center w-full flex flex-col items-center gap-[2.222vw] bg-white max-md:gap-[6.667vw] max-2xl:gap-[3.125vw]">
<h1 className="font-mixcase-unmixed text-[3.889vw] text-[#0D1922] w-[44.861vw] leading-[100%] tracking-[-0.05em] pt-[8.056vw] max-md:text-[6.667vw] max-md:w-full max-2xl:text-[7.292vw] max-2xl:w-[84.115vw]">
Dubai's first-ever combinable Apartments
+302 -322
View File
@@ -1,338 +1,318 @@
import { useRef, useState } from "react"
import { AnimatePresence, motion, useInView, useScroll } from "motion/react"
import AboutHQScrollSlider from "./AboutHQ/AboutHQSlider"
import { marasiDriveMapCards } from "../data/aboutMarasiDrive"
import { useRef, useState } from "react";
import { useScroll } from "motion/react";
import AboutHQScrollSlider from "./AboutHQ/AboutHQSlider";
import { marasiDriveMapCards } from "../data/aboutMarasiDrive";
import clsx from "clsx"
import EqualIcon from "./icons/EqualIcon"
import PlusIcon from "./icons/map/PlusIcon"
import MarasiDriveMapMobile from "./MarasiDriveMapMobile"
import MarasiDriveMapCard from "./MarasiDriveMapCard"
import BrochureButton from "./ui/BrochureButton"
import AboutHQSliderTablet from "./AboutHQ/AboutHQSliderTablet"
import AboutHQSliderMobile from "./AboutHQ/AboutHQSliderMobile"
import { hqDescription, hqSlider } from "../data/aboutHQ"
import UnitTypesSlider from "./AboutHQ/UnitTypesSlider"
import UnitTypesSliderMobile from "./AboutHQ/UnitTypesSliderMobile"
import clsx from "clsx";
import EqualIcon from "./icons/EqualIcon";
import PlusIcon from "./icons/map/PlusIcon";
import MarasiDriveMapMobile from "./MarasiDriveMapMobile";
import MarasiDriveMapCard from "./MarasiDriveMapCard";
import BrochureButton from "./ui/BrochureButton";
import AboutHQSliderTablet from "./AboutHQ/AboutHQSliderTablet";
import AboutHQSliderMobile from "./AboutHQ/AboutHQSliderMobile";
import { hqDescription, hqSlider } from "../data/aboutHQ";
import AboutSlider from "./AboutSlider/AboutSlider";
import AboutSliderMobile from "./AboutSlider/AboutSliderMobile";
import { HQTabs } from "../types/HQ";
export default function AboutHQ() {
const target = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target,
});
const [selectedCategorySlider, setSelectedCategorySlider] =
useState<keyof typeof hqSlider>("Modular Office Collection");
const target = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target,
});
const [selectedCategorySlider, setSelectedCategorySlider] = useState<HQTabs>(
"Modular Office Collection"
);
const sliderRef = useRef(null);
const isSliderInView = useInView(sliderRef, {
once: true,
amount: 0.1,
});
return (
return (
<div className="relative bg-white">
{/* Hero */}
<div className="2xl:sticky relative 2xl:top-[4.444vw] ">
<section className="w-full 2xl:h-[calc(100dvh-4.444vw)] relative md:h-[calc(100dvh-4rem)] h-[calc(100dvh-3.15rem)] md:max-h-[125vw] max-md:max-h-[162.222vw] flex flex-col justify-between bg-white 2xl:p-[2.222vw] 2xl:pt-[5vw] md:max-2xl:p-6 md:max-2xl:pt-10 p-4 pt-8 overflow-hidden">
<div className="2xl:size-[100vw] md:size-[118.75vw] size-[137.5vw] rounded-full absolute 2xl:top-1/2 2xl:-translate-y-1/2 2xl:left-[36.667vw] md:top-[46.484vw] md:left-1/2 max-2xl:-translate-x-1/2 left-1/2 top-[48.611vw] -translate-y-0 bg-[#F3F3F2]" />
<img
src="/images/about-complex/hq/main_bg.png"
alt=""
className="absolute 2xl:h-[103%] 2xl:left-[46.528vw] md:bottom-0 md:right-[-35vw] md:h-[85%] max-md:h-auto max-md:bottom-0 max-md:left-0"
/>
{/* Hero */}
<div className="2xl:sticky relative 2xl:top-[4.444vw] ">
<section className="w-full 2xl:h-[calc(100dvh-4.444vw)] relative md:h-[calc(100dvh-4rem)] h-[calc(100dvh-3.15rem)] md:max-h-[125vw] max-md:max-h-[162.222vw] flex flex-col justify-between bg-white 2xl:p-[2.222vw] 2xl:pt-[5vw] md:max-2xl:p-6 md:max-2xl:pt-10 p-4 pt-8 overflow-hidden">
<div className="2xl:size-[100vw] md:size-[118.75vw] size-[137.5vw] rounded-full absolute 2xl:top-1/2 2xl:-translate-y-1/2 2xl:left-[36.667vw] md:top-[46.484vw] md:left-1/2 max-2xl:-translate-x-1/2 left-1/2 top-[48.611vw] -translate-y-0 bg-[#F3F3F2]"/>
<img src="/images/about-complex/hq/main_bg.png" alt="" className="absolute 2xl:h-[103%] 2xl:left-[46.528vw] md:bottom-0 md:right-[-35vw] md:h-[85%] max-md:h-auto max-md:bottom-0 max-md:left-0" />
<div className="2xl:space-y-[1.667vw] md:max-2xl:space-y-6 space-y-4 relative">
<h1 className="2xl:text-[5vw] md:max-2xl:text-7xl text-[32px] leading-none tracking-[-0.07em] font-mixcase-unmixed font-medium whitespace-pre-line">
{`Rove Home HQ —
<div className="2xl:space-y-[1.667vw] md:max-2xl:space-y-6 space-y-4 relative">
<h1 className="2xl:text-[5vw] md:max-2xl:text-7xl text-[32px] leading-none tracking-[-0.07em] font-mixcase-unmixed font-medium whitespace-pre-line">
{`Rove Home HQ —
Work looks different
here`}
</h1>
</div>
<div className="2xl:space-y-[1.111vw] md:max-2xl:space-y-6 relative max-md:hidden">
<img src="/images/about-complex/hq/logo.svg" alt="" className=" md:max-2xl:flex hidden mb-[6.25vw]" />
<h4 className="text-h4 text-[#00BED7] font-medium whitespace-pre-line">
{`Welcome to the office you
actually want to show up for`}
</h4>
<p className="text-s opacity-70 2xl:w-1/4 md:max-2xl:w-1/3">
HQ by Rove was born out of a question: what if the
office could feel alive again? Now, the first
ever hospitality-branded office building in Dubai is here to
answer it. Starting in Marasi Bay
</p>
</div>
<img src="/images/about-complex/hq/logo.svg" alt="" className="absolute 2xl:top-[1.806vw] 2xl:right-[2.222vw] 2xl:w-[6.111vw] md:max-2xl:hidden w-[17.778vw] max-md:left-1/2 max-md:top-[39.444vw] max-md:-translate-x-1/2" />
</section>
<section>
<div className="space-y-6 bg-white px-4 py-8 md:hidden">
<h4 className="text-h4 text-[#00BED7] font-medium whitespace-pre-line ">
{`Welcome to the office you
actually want to show up for`}
</h4>
<p className="text-s opacity-70 2xl:w-1/4 md:max-2xl:w-1/3">
HQ by Rove was born out of a question: what if the
office could feel alive again? Now, the first
ever hospitality-branded office building in Dubai is here to
answer it. Starting in Marasi Bay
</p>
</div>
</section>
</div>
{/* Slider */}
<div className="2xl:sticky relative 2xl:top-[-134vw]">
<section
className="bg-white w-full overflow-clip 2xl:h-[calc(27.639vw*7)] 2xl:pt-[9.444vw] 2xl:px-[2.222vw] 2xl:pb-[2.222vw] md:max-2xl:pt-[104px] md:max-2xl:px-6 md:max-2xl:pb-8 pt-16 px-4 pb-8"
ref={target}
>
<div className="flex flex-col 2xl:gap-[2.222vw] 2xl:sticky top-[5vw]">
<div className="flex flex-col items-center 2xl:gap-[2.222vw]">
<h2 className="font-mixcase-unmixed text-h1 text-center whitespace-pre-line max-md:mb-[24px] md:max-2xl:mb-8 opacity-100">
<span className="opacity-40">More than an office,</span> <br className="2xl:hidden" /> a lifestyle.
</h2>
<p className="text-s opacity-70 whitespace-pre-line text-center max-md:whitespace-normal max-md:mb-12 md:max-2xl:mb-16">
{`Living rooms became boardrooms, kitchens became creative hubs.
But as the world returned, the office didnt keep up. HQ by Rove is the
answer - an office with a living touch.`}
</p>
</div>
<AboutHQScrollSlider
scrollYProgress={scrollYProgress}
/>
<AboutHQSliderTablet />
<AboutHQSliderMobile />
</div>
</section>
</div>
{/* Features */}
<div className="flex relative bg-white flex-col items-center pt-[4.444vw] px-8 gap-[4.444vw] max-md:px-[4.444vw] max-2xl:pr-[3.125vw] max-2xl:-mr-[3.125vw] max-2xl:p-6 max-2xl:items-start max-2xl:gap-[4.167vw]">
<h1 className="font-mixcase-unmixed 2xl:text-[3.889vw] text-[#0D1922] tracking-[-0.05em] max-md:text-2xl max-2xl:text-[7.292vw] max-2xl:self-center">
Dubai, <span className="text-[#0D192266]">within reach</span>
</h1>
<UnitTypesSliderMobile />
<div
className="z-1 flex w-full text-[#0D1922]/40 gap-[1.111vw] max-md:hidden
max-2xl:overflow-x-auto max-2xl:self-start max-2xl:snap-x max-2xl:snap-mandatory
max-2xl:[scrollbar-width:none] max-2xl:mt-[4.167vw]"
>
{hqDescription.map((descriptionItem) => (
<div
key={descriptionItem.title}
className="2xl:flex-1 text-center flex flex-col gap-[1.111vw] cursor-pointer hover:text-[#0D1922B2] transition-all duration-200
max-2xl:w-[45.833vw] max-2xl:h-[12.76vw] max-2xl:shrink-0"
onClick={() =>
setSelectedCategorySlider(descriptionItem.title )
}
>
<div className="h-[2px] bg-gray-300 w-full"></div>
<h5
className={clsx(
"text-h5 tracking-[-0.02em] mt-[0.556vw] font-[500]",
descriptionItem.title === selectedCategorySlider &&
"text-[#00BED7]"
)}
>
{descriptionItem.title}
</h5>
<p
className={clsx(
"text-s leading-[125%] tracking-[-0.02em] w-[16.389vw] self-center max-2xl:w-[30.729vw] ",
descriptionItem.title === selectedCategorySlider &&
"text-[#0D1922]"
)}
>
{descriptionItem.description}
</p>
</div>
))}
</div>
<AnimatePresence mode="wait">
<div
ref={sliderRef}
className="flex flex-col gap-8 max-md:hidden"
>
<motion.div
key={`slider-${selectedCategorySlider}`}
initial={{ opacity: 0, y: 40 }}
animate={
isSliderInView
? { opacity: 1, y: 0 }
: { opacity: 0, y: 140 }
}
exit={{ opacity: 0, y: -40 }}
transition={{ duration: 0.6, ease: "easeIn" }}
>
<UnitTypesSlider categoryName={selectedCategorySlider} />
</motion.div>
</div>
</AnimatePresence>
</div>
</div>
{/* Apartments */}
<div className="text-center relative w-full flex flex-col items-center gap-[2.222vw] bg-white max-md:gap-[6.667vw] max-2xl:gap-[3.125vw]">
<h1 className="font-mixcase-unmixed text-[3.889vw] text-[#0D1922] w -[44.861vw] leading-[100%] tracking-[-0.05em] pt-[8.056vw] max-md:text-[6.667vw] max-md:w-full max-2xl:text-[7.292vw] max-2xl:w-[84.115vw]">
{`Modular Office Collection:`}
<p className="opacity-40">
Combinable units
</p>
</h1>
<p className="text-s text-[#0D1922B2] leading-[140%] tracking-[-0.02em] whitespace-pre-line">
{`Enjoy the option to combine 2 offices and create a
larger space and configuration.`}
</p>
<div className="flex gap-4 relative max-md:flex-col max-md:gap-[2.222vw] max-md:w-full max-md:px-[4.444vw] max-2xl:mt-[7.292vw]">
<div
className="w-[19.028vw] text-[#0D1922] h-[27.778vw] p-[1.667vw] rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Studio.png)] bg-[length:16.806vw_16.806vw] bg-no-repeat bg-center
max-md:w-full max-md:h-[75.556vw] max-md:bg-[length:47.778vw_47.778vw] max-md:p-[4.444vw] max-2xl:w-[25.521vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Studio
</h4>
</div>
<div
className="w-10 h-10 rounded-full bg-[#0D1922] flex items-center justify-center absolute left-[18.500vw] self-center
max-md:top-[71.111vw] max-md:left-[45vw] max-2xl:top-[14.583vw] max-2xl:left-[24vw]"
>
<span className="w-5 h-5 text-white">
<PlusIcon />
</span>
</div>
<div
className="w-[19.028vw] text-[#0D1922] h-[27.778vw] p-6 rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Studio.png)] bg-[length:16.806vw_16.806vw] bg-no-repeat bg-center
max-md:w-full max-md:h-[75.556vw] max-md:bg-[length:47.778vw_47.778vw] max-md:p-[4.444vw] max-2xl:w-[25.521vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Studio
</h4>
</div>
<div
className="w-10 h-10 rounded-full bg-[#0D1922] flex items-center justify-center absolute left-[38.333vw] self-center
max-md:top-[148.611vw] max-md:left-[45vw] max-2xl:top-[14.583vw] max-2xl:left-[52vw]"
>
<span className="w-5 h-5 text-white">
<EqualIcon />
</span>
</div>
<div
className="w-[30.972vw] text-[#0D1922] h-[27.778vw] p-6 rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Deluxe.png)] bg-[length:25vw_25vw] bg-no-repeat bg-center
max-md:w-full max-md:h-[111.111vw] max-md:bg-[length:71.111vw_71.111vw] max-md:p-[4.444vw] max-2xl:w-[40.625vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Deluxe
</h4>
</div>
</div>
<h5 className="2xl:hidden text-h5 text-[#0D1922B2] max-md:block">
or
</h5>
<div className="flex gap-4 relative max-md:flex-col max-md:gap-[2.222vw] max-md:w-full max-md:px-[4.444vw]">
<div
className="w-[19.028vw] text-[#0D1922] h-[27.778vw] p-6 rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Studio.png)] bg-[length:16.806vw_16.806vw] bg-no-repeat bg-center
max-md:w-full max-md:h-[75.556vw] max-md:bg-[length:38.889vw_52.5vw] max-md:p-[4.444vw] max-2xl:w-[25.521vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Studio
</h4>
</div>
<div
className="w-10 h-10 rounded-full bg-[#0D1922] flex items-center justify-center absolute left-[18.500vw] self-center
max-md:top-[71.111vw] max-md:left-[45vw] max-2xl:top-[14.583vw] max-2xl:left-[24vw]"
>
<span className="w-5 h-5 text-white">
<PlusIcon />
</span>
</div>
<div
className="w-[30.972vw] text-[#0D1922] h-[27.778vw] p-6 rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Deluxe.png)] bg-[length:25vw_25vw] bg-no-repeat bg-center
max-md:w-full max-md:h-[75.556vw] max-md:bg-[length:71.111vw_71.111vw] max-md:p-[4.444vw] max-2xl:w-[33.073vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Deluxe
</h4>
</div>
<div
className="w-10 h-10 rounded-full bg-[#0D1922] flex items-center justify-center absolute left-[50.300vw] self-center
max-md:top-[148.611vw] max-md:left-[45vw] max-2xl:top-[14.583vw] max-2xl:left-[59vw]"
>
<span className="w-5 h-5 text-white">
<EqualIcon />
</span>
</div>
<div
className="w-[43.125vw] text-[#0D1922] h-[27.778vw] p-6 rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Executive.png)] bg-no-repeat bg-center
max-md:w-full max-md:h-[111.111vw] 2xl:bg-[length:36.111vw-36.111vw] md:bg-[length:30.208vw_30.208vw] bg-[length:91.111vw_91.111vw] max-md:p-[4.444vw] max-2xl:w-[33.073vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Executive
</h4>
</div>
</div>
</div>
{/* Map */}
<section className="bg-white relative w-full overflow-clip flex flex-col gap-[3.333vw] 2xl:pt-[9.444vw] 2xl:px-[2.222vw] 2xl:pb-[2.222vw] md:max-2xl:pt-[104px] md:max-2xl:px-6 pt-16 px-4 pb-8 md:max-2xl:pb-6 md:max-2xl:gap-[6.25vw]">
<div className="flex flex-col gap-[2.222vw] items-center max-md:gap-[8.889vw] md:max-2xl:gap-[4.167vw]">
<h2 className="text-h1 font-mixcase-unmixed">
Seamlessly connected
</h2>
<p className="text-s opacity-70 whitespace-pre-line text-center max-md:mb-[48px] max-md:whitespace-normal">
{`From the Dubai Canal at your doorstep to Sheikh Zayed Road in minutes, with
Downtown, DIFC and d3 all close by, youre never far from the citys pulse.`}
<div className="2xl:space-y-[1.111vw] md:max-2xl:space-y-6 relative max-md:hidden">
<img
src="/images/about-complex/hq/logo.svg"
alt=""
className=" md:max-2xl:flex hidden mb-[6.25vw]"
/>
<h4 className="text-h4 text-[#00BED7] font-medium whitespace-pre-line">
{`Welcome to the office you
actually want to show up for`}
</h4>
<p className="text-s opacity-70 2xl:w-1/4 md:max-2xl:w-1/3">
HQ by Rove was born out of a question: what if the office could
feel alive again? Now, the first ever hospitality-branded office
building in Dubai is here to answer it. Starting in Marasi Bay
</p>
</div>
<div className="grid grid-cols-6 grid-rows-5 gap-x-[1.111vw] gap-y-[0.556vw] max-2xl:hidden">
{marasiDriveMapCards.map((card) => (
<MarasiDriveMapCard {...card} key={card.title} />
))}
<div className="col-start-3 col-span-full row-start-1 row-span-full">
<img
src="/images/about-complex/marasi-drive/map/map.png"
alt=""
className="object-cover size-full 2xl:rounded-[1.667vw]"
<img
src="/images/about-complex/hq/logo.svg"
alt=""
className="absolute 2xl:top-[1.806vw] 2xl:right-[2.222vw] 2xl:w-[6.111vw] md:max-2xl:hidden w-[17.778vw] max-md:left-1/2 max-md:top-[39.444vw] max-md:-translate-x-1/2"
/>
</section>
<section>
<div className="space-y-6 bg-white px-4 py-8 md:hidden">
<h4 className="text-h4 text-[#00BED7] font-medium whitespace-pre-line ">
{`Welcome to the office you
actually want to show up for`}
</h4>
<p className="text-s opacity-70 2xl:w-1/4 md:max-2xl:w-1/3">
HQ by Rove was born out of a question: what if the office could
feel alive again? Now, the first ever hospitality-branded office
building in Dubai is here to answer it. Starting in Marasi Bay
</p>
</div>
</section>
</div>
{/* Slider */}
<div className="2xl:sticky relative 2xl:top-[-134vw]">
<section
className="bg-white w-full overflow-clip 2xl:h-[calc(27.639vw*7)] 2xl:pt-[9.444vw] 2xl:px-[2.222vw] 2xl:pb-[2.222vw] md:max-2xl:pt-[104px] md:max-2xl:px-6 md:max-2xl:pb-8 pt-16 px-4 pb-8"
ref={target}
>
<div className="flex flex-col 2xl:gap-[2.222vw] 2xl:sticky top-[5vw]">
<div className="flex flex-col items-center 2xl:gap-[2.222vw]">
<h2 className="font-mixcase-unmixed text-h1 text-center whitespace-pre-line max-md:mb-[24px] md:max-2xl:mb-8 opacity-100">
<span className="opacity-40">More than an office,</span>{" "}
<br className="2xl:hidden" /> a lifestyle.
</h2>
<p className="text-s opacity-70 whitespace-pre-line text-center max-md:whitespace-normal max-md:mb-12 md:max-2xl:mb-16">
{`Living rooms became boardrooms, kitchens became creative hubs.
But as the world returned, the office didnt keep up. HQ by Rove is the
answer - an office with a living touch.`}
</p>
</div>
<AboutHQScrollSlider scrollYProgress={scrollYProgress} />
<AboutHQSliderTablet />
<AboutHQSliderMobile />
</div>
</section>
</div>
{/* Features */}
<div className="flex relative bg-white flex-col items-center pt-[4.444vw] px-8 gap-[4.444vw] max-md:px-[4.444vw] max-2xl:items-start max-2xl:gap-[4.167vw]">
<h1 className="font-mixcase-unmixed 2xl:text-[3.889vw] text-[#0D1922] tracking-[-0.05em] max-md:text-2xl max-2xl:text-[7.292vw] max-2xl:self-center">
Dubai, <span className="text-[#0D192266]">within reach</span>
</h1>
<AboutSliderMobile description={hqDescription} />
<div
className="z-1 flex w-full text-[#0D1922]/40 gap-[1.111vw] max-md:hidden
max-2xl:overflow-x-auto max-2xl:self-start max-2xl:snap-x max-2xl:snap-mandatory
max-2xl:[scrollbar-width:none] max-2xl:mt-[4.167vw]"
>
{hqDescription.map((descriptionItem) => (
<div
key={descriptionItem.title}
className="2xl:flex-1 text-center flex flex-col gap-[1.111vw] cursor-pointer hover:text-[#0D1922B2] transition-all duration-200
max-2xl:w-[45.833vw] max-2xl:h-[12.76vw] max-2xl:shrink-0"
onClick={() => setSelectedCategorySlider(descriptionItem.title)}
>
<div className="h-[2px] bg-gray-300 w-full"></div>
<h5
className={clsx(
"text-h5 tracking-[-0.02em] mt-[0.556vw] font-[500]",
descriptionItem.title === selectedCategorySlider &&
"text-[#00BED7]"
)}
>
{descriptionItem.title}
</h5>
<p
className={clsx(
"text-s leading-[125%] tracking-[-0.02em] w-[16.389vw] self-center max-2xl:w-[30.729vw] ",
descriptionItem.title === selectedCategorySlider &&
"text-[#0D1922]"
)}
>
{descriptionItem.description}
</p>
</div>
))}
</div>
<div className="2xl:mx-auto 2xl:w-[63.333vw] 2xl:h-[27.639vw] md:w-[calc(100%-3.125vw)] md:h-[51.823vw] mb-[1vw] max-md:hidden">
<AboutSlider items={hqSlider[selectedCategorySlider]} />
</div>
</div>
{/* Apartments */}
<div className="text-center relative w-full flex flex-col items-center gap-[2.222vw] bg-white max-md:gap-[6.667vw] max-2xl:gap-[3.125vw]">
<h1 className="font-mixcase-unmixed text-[3.889vw] text-[#0D1922] w -[44.861vw] leading-[100%] tracking-[-0.05em] pt-[8.056vw] max-md:text-[6.667vw] max-md:w-full max-2xl:text-[7.292vw] max-2xl:w-[84.115vw]">
{`Modular Office Collection:`}
<p className="opacity-40">Combinable units</p>
</h1>
<p className="text-s text-[#0D1922B2] leading-[140%] tracking-[-0.02em] whitespace-pre-line">
{`Enjoy the option to combine 2 offices and create a
larger space and configuration.`}
</p>
<div className="flex gap-4 relative max-md:flex-col max-md:gap-[2.222vw] max-md:w-full max-md:px-[4.444vw] max-2xl:mt-[7.292vw]">
<div
className="w-[19.028vw] text-[#0D1922] h-[27.778vw] p-[1.667vw] rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Studio.png)] bg-[length:16.806vw_16.806vw] bg-no-repeat bg-center
max-md:w-full max-md:h-[75.556vw] max-md:bg-[length:47.778vw_47.778vw] max-md:p-[4.444vw] max-2xl:w-[25.521vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Studio
</h4>
</div>
<div
className="w-10 h-10 rounded-full bg-[#0D1922] flex items-center justify-center absolute left-[18.500vw] self-center
max-md:top-[71.111vw] max-md:left-[45vw] max-2xl:top-[14.583vw] max-2xl:left-[24vw]"
>
<span className="w-5 h-5 text-white">
<PlusIcon />
</span>
</div>
<div
className="w-[19.028vw] text-[#0D1922] h-[27.778vw] p-6 rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Studio.png)] bg-[length:16.806vw_16.806vw] bg-no-repeat bg-center
max-md:w-full max-md:h-[75.556vw] max-md:bg-[length:47.778vw_47.778vw] max-md:p-[4.444vw] max-2xl:w-[25.521vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Studio
</h4>
</div>
<div
className="w-10 h-10 rounded-full bg-[#0D1922] flex items-center justify-center absolute left-[38.333vw] self-center
max-md:top-[148.611vw] max-md:left-[45vw] max-2xl:top-[14.583vw] max-2xl:left-[52vw]"
>
<span className="w-5 h-5 text-white">
<EqualIcon />
</span>
</div>
<div
className="w-[30.972vw] text-[#0D1922] h-[27.778vw] p-6 rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Deluxe.png)] bg-[length:25vw_25vw] bg-no-repeat bg-center
max-md:w-full max-md:h-[111.111vw] max-md:bg-[length:71.111vw_71.111vw] max-md:p-[4.444vw] max-2xl:w-[40.625vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Deluxe
</h4>
</div>
</div>
<h5 className="2xl:hidden text-h5 text-[#0D1922B2] max-md:block">or</h5>
<div className="flex gap-4 relative max-md:flex-col max-md:gap-[2.222vw] max-md:w-full max-md:px-[4.444vw]">
<div
className="w-[19.028vw] text-[#0D1922] h-[27.778vw] p-6 rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Studio.png)] bg-[length:16.806vw_16.806vw] bg-no-repeat bg-center
max-md:w-full max-md:h-[75.556vw] max-md:bg-[length:38.889vw_52.5vw] max-md:p-[4.444vw] max-2xl:w-[25.521vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Studio
</h4>
</div>
<div
className="w-10 h-10 rounded-full bg-[#0D1922] flex items-center justify-center absolute left-[18.500vw] self-center
max-md:top-[71.111vw] max-md:left-[45vw] max-2xl:top-[14.583vw] max-2xl:left-[24vw]"
>
<span className="w-5 h-5 text-white">
<PlusIcon />
</span>
</div>
<div
className="w-[30.972vw] text-[#0D1922] h-[27.778vw] p-6 rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Deluxe.png)] bg-[length:25vw_25vw] bg-no-repeat bg-center
max-md:w-full max-md:h-[75.556vw] max-md:bg-[length:71.111vw_71.111vw] max-md:p-[4.444vw] max-2xl:w-[33.073vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Deluxe
</h4>
</div>
<div
className="w-10 h-10 rounded-full bg-[#0D1922] flex items-center justify-center absolute left-[50.300vw] self-center
max-md:top-[148.611vw] max-md:left-[45vw] max-2xl:top-[14.583vw] max-2xl:left-[59vw]"
>
<span className="w-5 h-5 text-white">
<EqualIcon />
</span>
</div>
<div
className="w-[43.125vw] text-[#0D1922] h-[27.778vw] p-6 rounded-2xl bg-[#F3F3F2] text-left bg-[url(/images/about-complex/hq/combinable/Executive.png)] bg-no-repeat bg-center
max-md:w-full max-md:h-[111.111vw] 2xl:bg-[length:36.111vw-36.111vw] md:bg-[length:30.208vw_30.208vw] bg-[length:91.111vw_91.111vw] max-md:p-[4.444vw] max-2xl:w-[33.073vw] max-2xl:h-[34.375vw]"
>
<h4 className="text-h4 tracking-[-0.02em] font-[500] max-md:text-h5">
Executive
</h4>
</div>
</div>
</div>
{/* Map */}
<section className="bg-white relative w-full overflow-clip flex flex-col gap-[3.333vw] 2xl:pt-[9.444vw] 2xl:px-[2.222vw] 2xl:pb-[2.222vw] md:max-2xl:pt-[104px] md:max-2xl:px-6 pt-16 px-4 pb-8 md:max-2xl:pb-6 md:max-2xl:gap-[6.25vw]">
<div className="flex flex-col gap-[2.222vw] items-center max-md:gap-[8.889vw] md:max-2xl:gap-[4.167vw]">
<h2 className="text-h1 font-mixcase-unmixed">Seamlessly connected</h2>
<p className="text-s opacity-70 whitespace-pre-line text-center max-md:mb-[48px] max-md:whitespace-normal">
{`From the Dubai Canal at your doorstep to Sheikh Zayed Road in minutes, with
Downtown, DIFC and d3 all close by, youre never far from the citys pulse.`}
</p>
</div>
<div className="grid grid-cols-6 grid-rows-5 gap-x-[1.111vw] gap-y-[0.556vw] max-2xl:hidden">
{marasiDriveMapCards.map((card) => (
<MarasiDriveMapCard {...card} key={card.title} />
))}
<div className="col-start-3 col-span-full row-start-1 row-span-full">
<img
src="/images/about-complex/marasi-drive/map/map.png"
alt=""
className="object-cover size-full 2xl:rounded-[1.667vw]"
/>
</div>
</div>
<MarasiDriveMapMobile />
</section>
{/* Brochures */}
<section className="bg-white relative w-full overflow-clip flex items-stretch justify-center gap-[1.111vw] 2xl:px-[10.278vw] 2xl:pt-[9.444vw] 2xl:pb-[15vw] md:max-2xl:py-[104px] md:mx-2xl:px-6 max-md:pt-20 max-md:pb-12 px-4 max-2xl:flex-col-reverse md:max-2xl:gap-[6.25vw] md:max-2xl:px-6 max-mdmb-[13.333vw]">
<div className="flex-shrink-0 2xl:w-[39.167vw] 2xl:h-[19.861vw] md:w-full md:h-[52.083vw] w-[91.111vw] h-[111.111vw]">
<img
src="/images/about-complex/hq/brochures.jpg"
className="object-cover size-full rounded-[1.667vw] max-md:mt-[48px] max-md:aspect-[328/400] max-md:rounded-[6.667vw] md:max-2xl:rounded-[3.125vw] md:max-2xl:aspect-[720/400]"
alt=""
/>
</div>
<div className="px-[2.778vw] flex flex-col gap-[3.333vw] max-2xl:px-0 md:max-2xl:gap-[6.25vw]">
<div className="space-y-[1.111vw]">
<h2 className="whitespace-pre-line text-h2 font-medium max-2xl:mb-4">
{`Work looks different here`}
</h2>
<p className="opacity-40 text-s max-md:mb-[32px] md:max-2xl:max-w-[57.943vw]">
{`HQ by Rove sets its own standard. A place designed with people at
its heart, where hospitality meets productivity, and work finds its flow.`}
</p>
</div>
<div className="space-y-[1.111vw]">
<h5 className="text-h5 font-medium max-md:mb-[16px] md:max-2xl:mb-[2.083vw]">
Download our brochures
</h5>
<div className="space-y-[0.833vw] max-md:space-y-[3.333vw] md:max-2xl:space-y-[1.563vw]">
<BrochureButton
title={"Main Brochure"}
link="/files/marasi-drive/Main Brochure.pdf"
/>
<BrochureButton
title={"Technical Brochure"}
link="/files/marasi-drive/Amenties Brochure.pdf"
/>
</div>
</div>
<MarasiDriveMapMobile />
</section>
{/* Brochures */}
<section className="bg-white relative w-full overflow-clip flex items-stretch justify-center gap-[1.111vw] 2xl:px-[10.278vw] 2xl:pt-[9.444vw] 2xl:pb-[15vw] md:max-2xl:py-[104px] md:mx-2xl:px-6 max-md:pt-20 max-md:pb-12 px-4 max-2xl:flex-col-reverse md:max-2xl:gap-[6.25vw] md:max-2xl:px-6 max-mdmb-[13.333vw]">
<div className="flex-shrink-0 2xl:w-[39.167vw] 2xl:h-[19.861vw] md:w-full md:h-[52.083vw] w-[91.111vw] h-[111.111vw]">
<img
src="/images/about-complex/hq/brochures.jpg"
className="object-cover size-full rounded-[1.667vw] max-md:mt-[48px] max-md:aspect-[328/400] max-md:rounded-[6.667vw] md:max-2xl:rounded-[3.125vw] md:max-2xl:aspect-[720/400]"
alt=""
/>
</div>
<div className="px-[2.778vw] flex flex-col gap-[3.333vw] max-2xl:px-0 md:max-2xl:gap-[6.25vw]">
<div className="space-y-[1.111vw]">
<h2 className="whitespace-pre-line text-h2 font-medium max-2xl:mb-4">
{`Work looks different here`}
</h2>
<p className="opacity-40 text-s max-md:mb-[32px] md:max-2xl:max-w-[57.943vw]">
{`HQ by Rove sets its own standard. A place designed with people at
its heart, where hospitality meets productivity, and work finds its flow.`}
</p>
</div>
<div className="space-y-[1.111vw]">
<h5 className="text-h5 font-medium max-md:mb-[16px] md:max-2xl:mb-[2.083vw]">
Download our brochures
</h5>
<div className="space-y-[0.833vw] max-md:space-y-[3.333vw] md:max-2xl:space-y-[1.563vw]">
<BrochureButton
title={"Main Brochure"}
link="/files/marasi-drive/Main Brochure.pdf"
/>
<BrochureButton
title={"Technical Brochure"}
link="/files/marasi-drive/Amenties Brochure.pdf"
/>
</div>
</div>
</div>
</section>
</div>
</section>
</div>
)
}
);
}
-136
View File
@@ -1,136 +0,0 @@
import { useEffect, useState } from "react";
import ArrowLeftIcon from "../icons/ArrowLeftIcon";
import ArrowRightIcon from "../icons/ArrowRightIcon";
import { hqSlider } from "../../data/aboutHQ";
import { AnimatePresence, motion } from "motion/react";
import { useSwipeable } from "react-swipeable";
import Button from "../ui/Button";
function UnitTypesSlider({
categoryName,
}: {
categoryName: "Modular Office Collection" | "A La Carte" | "Loft Office Collection";
}) {
const [currentSlide, setCurrentSlide] = useState(0);
const [direction, setDirection] = useState(-1);
const handleNextSlide = () => {
if (currentSlide < hqSlider[categoryName].length - 1) {
setDirection(1);
setCurrentSlide(currentSlide + 1);
}
};
const handlePreviousSlide = () => {
if (currentSlide > 0) {
setDirection(-1);
setCurrentSlide(currentSlide - 1);
}
};
const handlers = useSwipeable({
onSwipedLeft: handleNextSlide,
onSwipedRight: handlePreviousSlide,
preventScrollOnSwipe: true,
touchEventOptions: {
passive: false,
},
trackMouse: true,
});
useEffect(() => {
setCurrentSlide(0);
}, [categoryName]);
useEffect(() => {
if(currentSlide === 0) {
setDirection(1);
}
else if(currentSlide === hqSlider[categoryName].length - 1) {
setDirection(-1);
}
}, [currentSlide]);
return (
<div
{...handlers}
className="relative w-[63.333vw] h-[27.639vw] rounded-3xl overflow-hidden max-md:w-[91.111vw] max-md:h-[110.556vw] max-2xl:w-[93.75vw] max-2xl:h-[51.823vw]"
>
<AnimatePresence custom={direction} initial={false}>
<motion.div
key={currentSlide}
className="absolute inset-0 bg-cover bg-no-repeat bg-center before:absolute before:inset-0 before:bg-[#0D1922]/20 before:z-[1]"
style={{
backgroundImage: `url(${hqSlider[categoryName][currentSlide].image})`,
}}
custom={direction}
initial={{ x: direction > 0 ? "100%" : "-100%" }}
animate={{ x: 0, transition: { duration: 0.5, ease: "easeInOut" } }}
exit={{
x: direction < 0 ? "100%" : "-100%",
transition: { duration: 0.8, ease: "easeInOut" },
}}
/>
</AnimatePresence>
<div className="z-[1] flex relative flex-col justify-between p-6 w-full h-full">
<div className="flex flex-col gap-4">
<AnimatePresence mode="wait">
<motion.h3
key={`title-${currentSlide}`}
className="text-h3 max-md:text-h5 text-white"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
{hqSlider[categoryName][currentSlide].title}
</motion.h3>
</AnimatePresence>
<AnimatePresence mode="wait">
<motion.p
key={`desc-${currentSlide}`}
className="text-s w-[19.861vw] tracking-[-0.02em] leading-[140%] text-white max-md:w-full max-md:text-caption-m max-2xl:w-[37.24vw]"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
{hqSlider[categoryName][currentSlide].description}
</motion.p>
</AnimatePresence>
</div>
<div className="flex items-center gap-[0.556vw] relative max-md:gap-[2.222vw]">
<Button
variant="secondary"
onlyIcon
onClick={handlePreviousSlide}
disabled={currentSlide === 0}
className="disabled:!bg-[#fff] disabled:!bg-opacity-80 disabled:!cursor-default hover:bg-[#F3F3F2] transition-all duration-200"
>
<span className="size-5">
<ArrowLeftIcon />
</span>
</Button>
<div className="text-s text-white">
{currentSlide + 1}/{hqSlider[categoryName].length}
</div>
<Button
variant="secondary"
onlyIcon
onClick={handleNextSlide}
disabled={
currentSlide === hqSlider[categoryName].length - 1
}
className="disabled:!bg-[#fff] disabled:!bg-opacity-80 disabled:!cursor-default hover:bg-[#F3F3F2] transition-all duration-200"
>
<span className="size-5">
<ArrowRightIcon />
</span>
</Button>
</div>
</div>
</div>
);
}
export default UnitTypesSlider;
@@ -1,87 +0,0 @@
import { useScroll, useTransform, motion } from 'motion/react';
import { useRef } from 'react'
import UnitTypesSlider from './UnitTypesSlider';
export default function UnitTypesSliderMobile() {
const sliderMobileRef = useRef(null);
const { scrollYProgress } = useScroll({
target: sliderMobileRef,
offset: ["start start", "end end"],
});
const opacityFirstSlide = useTransform(
scrollYProgress,
[0, 1 / 3],
[1, 0]
);
const opacitySecondSlide = useTransform(
scrollYProgress,
[ 2 / 3, 1],
[ 1, 0]
);
const opacityThirdSlide = useTransform(
scrollYProgress,
[ 2 / 3, 1],
[ 1, 1]
)
return (
<div
className="md:hidden flex flex-col gap-[6.667vw]"
ref={sliderMobileRef}
>
<motion.div
className="sticky top-[18vw] h-full flex flex-col gap-[6.667vw]"
style={{ opacity: opacityFirstSlide }}
>
<div className="h-[2px] bg-gray-300 w-full"></div>
<h5
className={
"text-h5 tracking-[-0.02em] mt-[0.556vw] font-[500] text-[#00BED7] text-center р-10"
}
>
Modular Office Collection
</h5>
<p className="text-m text-center text-[#0D1922]/70 mt-[-2.222vw]">
Spaces that easily expand and adapt
</p>
<UnitTypesSlider categoryName="Modular Office Collection" />
</motion.div>
<motion.div
className="sticky h-full top-[18vw] flex flex-col gap-[6.667vw] bg-white"
style={{ opacity: opacitySecondSlide }}
>
<div className="h-[2px] bg-gray-300 w-full"></div>
<h5
className={
"text-h5 tracking-[-0.02em] mt-[0.556vw] font-[500] text-[#00BED7] text-center"
}
>
A La Carte
</h5>
<p className="text-m text-center text-[#0D1922]/70 mt-[-2.222vw]">
Bespoke offices designed around you
</p>
<UnitTypesSlider categoryName="A La Carte" />
</motion.div>
<motion.div
className="sticky h-full top-[18vw] flex flex-col gap-[6.667vw] bg-white"
style={{ opacity: opacityThirdSlide }}
>
<div className="h-[2px] bg-gray-300 w-full"></div>
<h5
className={
"text-h5 tracking-[-0.02em] mt-[0.556vw] font-[500] text-[#00BED7] text-center"
}
>
Loft Office Collection
</h5>
<p className="text-m text-center text-[#0D1922]/70 mt-[-2.222vw] h-10">
Double-height ceilings and the warm atmosphere of home
</p>
<UnitTypesSlider categoryName="Loft Office Collection" />
</motion.div>
{/* <div className="h-[40vw]"></div> */}
</div>
);
}
+167
View File
@@ -0,0 +1,167 @@
import { AboutSliderProps, ControlsProps } from "../../types/AboutSlider";
import { ReactNode, useEffect, useRef, useState } from "react";
import { AnimatePresence, motion } from "motion/react";
import ArrowRightIcon from "../icons/ArrowRightIcon";
import ArrowLeftIcon from "../icons/ArrowLeftIcon";
import { useSwipeable } from "react-swipeable";
import clsx from "clsx";
export default function AboutSlider({ items, className }: AboutSliderProps) {
const totalSlides = items.length || 0;
const containerRef = useRef<HTMLDivElement>(null);
const [currentSlide, setCurrentSlide] = useState(0);
const [containerWidth, setContainerWidth] = useState(0);
function handleNext() {
if (currentSlide === totalSlides - 1) return;
setCurrentSlide((currentSlide + 1) % totalSlides);
scroll(1);
}
function handlePrevious() {
if (currentSlide === 0) return;
setCurrentSlide((currentSlide - 1 + totalSlides) % totalSlides);
scroll(-1);
}
function scroll(direction: 1 | -1) {
if (!containerRef.current) return;
containerRef.current.scrollLeft += direction * containerWidth;
}
const swipeHandlers = useSwipeable({
onSwipedLeft: handleNext,
onSwipedRight: handlePrevious,
preventScrollOnSwipe: true,
touchEventOptions: {
passive: false,
},
trackMouse: true,
});
function refCallback(el: HTMLDivElement | null) {
if (!el) return;
containerRef.current = el;
setContainerWidth(el.clientWidth || 0);
// Передаем элемент в ref от useSwipeable
if (swipeHandlers.ref && typeof swipeHandlers.ref === "function") {
swipeHandlers.ref(el);
}
}
useEffect(() => {
setCurrentSlide(0);
if (!containerRef.current) return;
containerRef.current.scrollTo({ left: 0, behavior: "instant" });
}, [items]);
return (
<AnimatePresence mode="wait">
<motion.div
key={items[0]?.title || "default"}
initial={{ opacity: 0, y: "2%" }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: "-2%" }}
transition={{ duration: 0.25 }}
className="relative size-full overflow-hidden 2xl:rounded-[1.667vw] md:rounded-[3.125vw] rounded-[4.444vw]"
>
<motion.div
key={currentSlide}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.25 }}
className="absolute 2xl:top-[1.667vw] 2xl:left-[1.667vw] md:top-[3.125vw] md:left-[3.125vw] top-[4.444vw] left-[4.444vw] z-[3]"
>
<h2 className="text-h3 max-md:text-h5 text-white font-medium 2xl:mb-[1.111vw] md:mb-[2.083vw] mb-[4.444vw]">
{items[currentSlide] && items[currentSlide].title}
</h2>
<p className="2xl:max-w-[19.861vw] md:max-w-[37.24vw] max-w-[95%] text-s max-md:text-caption-m text-white">
{items[currentSlide] && items[currentSlide].description}
</p>
</motion.div>
<div
ref={refCallback}
className={clsx(
"relative size-full overflow-hidden flex snap-x snap-mandatory scroll-smooth ",
className
)}
onMouseDown={swipeHandlers.onMouseDown}
>
{items.map((item, index) => (
<div key={index} className="w-full flex-shrink-0 snap-center">
<img
draggable={false}
className="w-full h-full object-cover"
src={item.image}
alt={item.title}
/>
</div>
))}
</div>
<div className="absolute 2xl:bottom-[1.667vw] 2xl:left-[1.667vw] md:bottom-[3.125vw] md:left-[3.125vw] bottom-[6.667vw] left-[6.667vw] z-[3]">
<Controls
handleNext={handleNext}
handlePrevious={handlePrevious}
currentSlide={currentSlide}
totalSlides={totalSlides}
/>
</div>
{/* Затенение */}
<div className="absolute pointer-events-none inset-0 bg-[radial-gradient(ellipse_at_top_left,rgba(0,0,0,0.3)_0%,rgba(0,0,0,0)_100%)] z-[2]" />
</motion.div>
</AnimatePresence>
);
}
function Controls({
handleNext,
handlePrevious,
currentSlide,
totalSlides,
}: ControlsProps) {
function ControlButton({
children,
onClick,
disabled,
}: {
children: ReactNode;
onClick: () => void;
disabled: boolean;
}) {
return (
<button
className="2xl:size-[2.778vw] md:size-[5.208vw] size-[11.111vw] bg-white text-black 2xl:rounded-[0.833vw] md:rounded-[1.563vw] rounded-[3.333vw] flex items-center justify-center disabled:opacity-50"
onClick={onClick}
disabled={disabled}
>
<div className="2xl:size-[1.389vw] md:size-[2.604vw] size-[5.556vw]">
{children}
</div>
</button>
);
}
return (
<div className="flex items-center 2xl:gap-[0.556vw] md:gap-[1.042vw] gap-[2.222vw]">
<ControlButton onClick={handlePrevious} disabled={currentSlide === 0}>
<ArrowLeftIcon />
</ControlButton>
<span className="text-s text-white">
{currentSlide + 1} / {totalSlides}
</span>
<ControlButton
onClick={handleNext}
disabled={currentSlide === totalSlides - 1}
>
<ArrowRightIcon />
</ControlButton>
</div>
);
}
@@ -0,0 +1,58 @@
import { motion } from "motion/react";
import { useRef } from "react";
import { hqSlider } from "../../data/aboutHQ";
import AboutSlider from "./AboutSlider";
import { AboutSliderMobileProps } from "../../types/AboutSlider";
import { dubaiMarinaSlider } from "../../data/aboutDubaiMarina";
export default function AboutSliderMobile({
description,
}: AboutSliderMobileProps) {
const sliderMobileRef = useRef(null);
// const { scrollYProgress } = useScroll({
// target: sliderMobileRef,
// offset: ["start start", "end end"],
// });
const data =
typeof description === typeof hqSlider ? hqSlider : dubaiMarinaSlider;
// const slidesCount = description.length;
// const transforms = Array.from({ length: slidesCount }, (_, index) =>
// useTransform(
// scrollYProgress,
// [index / slidesCount, (index + 1) / slidesCount],
// [1, 0]
// )
// );
return (
<div
className="md:hidden flex flex-col gap-[6.667vw]"
ref={sliderMobileRef}
>
{description.map((item, index) => (
<motion.div
key={index}
className="sticky top-[18vw] h-full flex flex-col gap-[6.667vw] bg-white"
>
<div className="h-[2px] bg-gray-300 w-full"></div>
<h5
className={
"text-h5 tracking-[-0.02em] mt-[0.556vw] font-[500] text-[#00BED7] text-center р-10"
}
>
{item.title}
</h5>
<p className="text-m text-center text-[#0D1922]/70 mt-[-2.222vw]">
{item.description}
</p>
<div className="w-full h-[110.556vw]">
<AboutSlider items={data[item.title as keyof typeof data]} />
</div>
</motion.div>
))}
</div>
);
}
-127
View File
@@ -1,127 +0,0 @@
import { useEffect, useState } from "react";
import ArrowLeftIcon from "./icons/ArrowLeftIcon";
import ArrowRightIcon from "./icons/ArrowRightIcon";
import { dubaiMarinaSlider } from "../data/aboutDubaiMarina";
import { AnimatePresence, motion } from "motion/react";
import { useSwipeable } from "react-swipeable";
import Button from "./ui/Button";
function Slider({
categoryName,
}: {
categoryName: "Wellness" | "Fitness" | "Community" | "Convenience";
}) {
const [currentSlide, setCurrentSlide] = useState(0);
const [direction, setDirection] = useState(-1);
const handleNextSlide = () => {
if (currentSlide < dubaiMarinaSlider[categoryName].length - 1) {
setDirection(1);
setCurrentSlide(currentSlide + 1);
}
};
const handlePreviousSlide = () => {
if (currentSlide > 0) {
setDirection(-1);
setCurrentSlide(currentSlide - 1);
}
};
const handlers = useSwipeable({
onSwipedLeft: handleNextSlide,
onSwipedRight: handlePreviousSlide,
preventScrollOnSwipe: true,
touchEventOptions: {
passive: false,
},
trackMouse: true,
});
useEffect(() => {
setCurrentSlide(0);
}, [categoryName]);
return (
<div
{...handlers}
className="relative w-[63.333vw] h-[27.639vw] rounded-3xl overflow-hidden max-md:w-[91.111vw] max-md:h-[110.556vw] max-2xl:w-[93.75vw] max-2xl:h-[51.823vw]"
>
<AnimatePresence custom={direction} initial={false}>
<motion.div
key={currentSlide}
className="absolute inset-0 bg-cover bg-no-repeat bg-center before:absolute before:inset-0 before:bg-[#0D1922]/20 before:z-[1]"
style={{
backgroundImage: `url(${dubaiMarinaSlider[categoryName][currentSlide].image})`,
}}
custom={direction}
initial={{ x: direction > 0 ? "100%" : "-100%" }}
animate={{ x: 0, transition: { duration: 0.5, ease: "easeInOut" } }}
exit={{
x: direction > 0 ? "100%" : "-100%",
transition: { duration: 0.8, ease: "easeInOut" },
}}
/>
</AnimatePresence>
<div className="z-[1] flex relative flex-col justify-between p-6 w-full h-full">
<div className="flex flex-col gap-4">
<AnimatePresence mode="wait">
<motion.h3
key={`title-${currentSlide}`}
className="text-h3 max-md:text-h5 text-white"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
{dubaiMarinaSlider[categoryName][currentSlide].title}
</motion.h3>
</AnimatePresence>
<AnimatePresence mode="wait">
<motion.p
key={`desc-${currentSlide}`}
className="text-s w-[19.861vw] tracking-[-0.02em] leading-[140%] text-white max-md:w-full max-md:text-caption-m max-2xl:w-[37.24vw]"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
{dubaiMarinaSlider[categoryName][currentSlide].description}
</motion.p>
</AnimatePresence>
</div>
<div className="flex items-center gap-[0.556vw] relative max-md:gap-[2.222vw]">
<Button
variant="secondary"
onlyIcon
onClick={handlePreviousSlide}
disabled={currentSlide === 0}
className="disabled:!bg-[#fff] disabled:!bg-opacity-80 disabled:!cursor-default hover:bg-[#F3F3F2] transition-all duration-200"
>
<span className="size-5">
<ArrowLeftIcon />
</span>
</Button>
<div className="text-s text-white">
{currentSlide + 1}/{dubaiMarinaSlider[categoryName].length}
</div>
<Button
variant="secondary"
onlyIcon
onClick={handleNextSlide}
disabled={
currentSlide === dubaiMarinaSlider[categoryName].length - 1
}
className="disabled:!bg-[#fff] disabled:!bg-opacity-80 disabled:!cursor-default hover:bg-[#F3F3F2] transition-all duration-200"
>
<span className="size-5">
<ArrowRightIcon />
</span>
</Button>
</div>
</div>
</div>
);
}
export default Slider;
+16 -5
View File
@@ -1,6 +1,8 @@
import { motion, useScroll, useTransform } from "framer-motion";
import Slider from "./Slider";
import { useRef } from "react";
import AboutSlider from "./AboutSlider/AboutSlider";
import { dubaiMarinaSlider } from "../data/aboutDubaiMarina";
function SliderMobile() {
const sliderMobileRef = useRef(null);
const { scrollYProgress } = useScroll({
@@ -45,7 +47,9 @@ function SliderMobile() {
<p className="text-m text-center text-[#0D1922]/70 mt-[-2.222vw]">
Unlock your inner zen in our wellness playground
</p>
<Slider categoryName="Wellness" />
<div className="w-full h-[110.556vw]">
<AboutSlider items={dubaiMarinaSlider["Wellness"]} />
</div>
</motion.div>
<motion.div
className="sticky h-full top-[18vw] flex flex-col gap-[6.667vw] bg-white"
@@ -62,7 +66,9 @@ function SliderMobile() {
<p className="text-m text-center text-[#0D1922]/70 mt-[-2.222vw]">
Cancel all your membership. Your new home has it all
</p>
<Slider categoryName="Fitness" />
<div className="w-full h-[110.556vw]">
<AboutSlider items={dubaiMarinaSlider["Fitness"]} />
</div>
</motion.div>
<motion.div
className="sticky h-full top-[18vw] flex flex-col gap-[6.667vw] bg-white"
@@ -79,7 +85,9 @@ function SliderMobile() {
<p className="text-m text-center text-[#0D1922]/70 mt-[-2.222vw] h-10">
Connect. Engage. Thrive.
</p>
<Slider categoryName="Community" />
<div className="w-full h-[110.556vw]">
<AboutSlider items={dubaiMarinaSlider["Community"]} />
</div>
</motion.div>
<motion.div
className="sticky h-full top-[18vw] flex flex-col gap-[6.667vw] z-1 bg-white"
@@ -96,7 +104,10 @@ function SliderMobile() {
<p className="text-m text-center text-[#0D1922]/70 mt-[-2.222vw] h-10">
Your smart living hub
</p>
<Slider categoryName="Convenience" />
<div className="w-full h-[110.556vw]">
<AboutSlider items={dubaiMarinaSlider["Convenience"]} />
</div>
</motion.div>
<div className="h-[40vw]"></div>
</div>
+7 -5
View File
@@ -1,3 +1,5 @@
import { DMDescription, DMSlider, DMBadges } from "../types/DubaiMarina";
export const dubaiMarinaDescriptionBadges = [
"Fullyfurnished apartments",
"Vibrant art installations",
@@ -27,7 +29,7 @@ export const dubaiMarinaFeatures = [
},
] as const;
export const dubaiMarinaDescription = [
export const dubaiMarinaDescription: DMDescription = [
{
title: "Wellness",
description: "Unlock your inner zen in our wellness playground",
@@ -44,9 +46,9 @@ export const dubaiMarinaDescription = [
title: "Convenience",
description: "Your smart living hub",
},
] as const;
];
export const dubaiMarinaSlider = {
export const dubaiMarinaSlider: DMSlider = {
Wellness: [
{
title: "Indoor Infinity Pool",
@@ -102,9 +104,9 @@ export const dubaiMarinaSlider = {
image: "/images/about-complex/dubai-marina/design.jpg",
},
],
} as const;
};
export const sliderBadgesCategory = {
export const sliderBadgesCategory: DMBadges = {
Wellness: [
"Vitality Pool",
"Reflexology Pool",
+5 -3
View File
@@ -1,3 +1,5 @@
import { HQDescription, HQSlider } from "../types/HQ";
export const hqFeatures = [
{
name: "Reception Lounge",
@@ -33,7 +35,7 @@ export const hqFeatures = [
},
] as const;
export const hqDescription = [
export const hqDescription: HQDescription = [
{
title: "Modular Office Collection",
description: "Spaces that easily expand and adapt",
@@ -46,9 +48,9 @@ export const hqDescription = [
title: "Loft Office Collection",
description: "Double-height ceilings and the warm atmosphere of home",
},
] as const;
];
export const hqSlider = {
export const hqSlider: HQSlider = {
"Modular Office Collection": [
{
title: "Studio",
+26
View File
@@ -0,0 +1,26 @@
import { DMDescription } from "./DubaiMarina";
import { HQDescription } from "./HQ";
export type AboutSliderItem = {
title: string;
description: string;
image: string;
};
export interface ControlsProps {
handleNext: () => void;
handlePrevious: () => void;
currentSlide: number;
totalSlides: number;
}
export interface AboutSliderProps {
items: AboutSliderItem[];
className?: string;
}
export interface AboutSliderMobileProps {
description: DMDescription | HQDescription;
}
+10
View File
@@ -0,0 +1,10 @@
import { AboutSliderItem } from "./AboutSlider";
export type DMTabs = "Wellness" | "Fitness" | "Community" | "Convenience"
export type DMDescription = {
title: DMTabs,
description: string
}[]
export type DMSlider = Record<DMTabs, AboutSliderItem[]>
export type DMBadges = Record<DMTabs, string[]>
+9
View File
@@ -0,0 +1,9 @@
import { AboutSliderItem } from "./AboutSlider";
export type HQTabs = "Modular Office Collection" | "A La Carte" | "Loft Office Collection"
export type HQDescription = {
title: HQTabs,
description: string
}[]
export type HQSlider = Record<HQTabs, AboutSliderItem[]>