HQ About page + Marasi fixes

This commit is contained in:
2026-01-21 16:13:17 +05:00
parent 440704593b
commit 54b99f978e
29 changed files with 841 additions and 154 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

+267 -35
View File
@@ -1,28 +1,43 @@
import { useRef } from "react"
import MarasiDriveNeighboursSliderMobile from "./MarasiDriveNeighboursSliderMobile"
import MarasiDriveNeighboursSliderTablet from "./MarasiDriveNeighboursSliderTablet"
import { useScroll } from "motion/react"
import ScrollSlider from "./ScrollSlider"
import { marasiDriveFeatures } from "../data/aboutMarasiDrive"
import { useRef, useState } from "react"
import { AnimatePresence, motion, useInView, 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"
export default function AboutHQ() {
return (
<div className="relative ">
<AboutHQHero/>
<AboutHQSlider/>
</div>
)
}
const target = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target,
});
const [selectedCategorySlider, setSelectedCategorySlider] =
useState<keyof typeof hqSlider>("Modular Office Collection");
const sliderRef = useRef(null);
const isSliderInView = useInView(sliderRef, {
once: true,
amount: 0.1,
});
function AboutHQHero() {
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">
<section className="w-full 2xl:h-[calc(100dvh-4.444vw)] relative md:h-[calc(100dvh-4rem)] h-[calc(100dvh-3.15rem)] 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:right-[-10vw] md:bottom-0 md:right-[-35vw] md:h-[85%] max-md:h-auto max-md:bottom-0 max-md:left-0" />
<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">
@@ -63,26 +78,18 @@ function AboutHQHero() {
</p>
</div>
</section>
</div>
)
}
function AboutHQSlider() {
const target = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target,
});
return (
{/* Slider */}
<div className="2xl:sticky relative 2xl:top-[-134vw]">
<section
className="bg-white w-full overflow-clip 2xl:h-[calc(27.639vw*5)] 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"
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-[4.444vw] 2xl:sticky top-[5vw]">
<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> a lifestyle.
<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.
@@ -90,17 +97,242 @@ function AboutHQSlider() {
answer - an office with a living touch.`}
</p>
</div>
<ScrollSlider
<AboutHQScrollSlider
scrollYProgress={scrollYProgress}
images={marasiDriveFeatures}
/>
{/* TODO: update */}
<MarasiDriveNeighboursSliderTablet />
<MarasiDriveNeighboursSliderMobile />
<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>
{/* 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>
</div>
</section>
</div>
)
}
+42
View File
@@ -0,0 +1,42 @@
import { motion, AnimatePresence, useTransform, MotionValue, useMotionValueEvent } from 'motion/react';
import { hqFeatures } from "../../data/aboutHQ"
import { useState } from 'react';
export default function AboutHQSlider({ scrollYProgress }: { scrollYProgress: MotionValue<number> }) {
const percentageOfScroll = useTransform(scrollYProgress, [0.2, 0.8], [0, 100]);
const offset = useTransform(scrollYProgress, [1/5, 4/5], ["4%", "-128%"]);
const imageBaseWidth = "20.139vw";
const imageExpandedWidth = "36.944vw";
const [activeImageIndex, setActiveImageIndex] = useState(1);
useMotionValueEvent(percentageOfScroll, "change", (latest) => {
const thresholds = [10, 30, 50, 70, 90];
const newIndex = thresholds.findIndex(threshold => latest <= threshold) + 1;
setActiveImageIndex(newIndex === 0 ? 6 : newIndex);
});
return (
<motion.div
className="flex items-center 2xl:gap-[4.965vw] gap-[9.375vw] 2xl:min-h-[27.639vw] max-2xl:hidden"
style={{ x: offset }}
>
<AnimatePresence>
{hqFeatures.map(({ name, image }, index) => (
<motion.div
key={name}
className="flex flex-col shrink-0 items-center 2xl:gap-[1.667vw] gap-6"
animate={{ width: index === activeImageIndex ? imageExpandedWidth : imageBaseWidth }}
>
<img
src={image}
alt={name}
className="2xl:rounded-[1.667vw] rounded-[3.125vw] aspect-[290/205] object-cover w-full pointer-events-none"
/>
<p className={"text-h4 text-center font-medium"}>{name}</p>
</motion.div>
))}
</AnimatePresence>
</motion.div>
);
}
@@ -0,0 +1,40 @@
import { hqFeatures } from "../../data/aboutHQ";
import { useRef } from "react";
import CustomScrollBar from "../ui/ScrollBar";
function AboutHQSliderMobile() {
const hqFeaturesRef = useRef<HTMLDivElement>(null);
return (
<>
<div
ref={hqFeaturesRef}
className="max-md:flex hidden max-md:flex-nowrap max-md:overflow-x-scroll max-md:gap-x-[8px] max-md:overflow-y-hidden max-md:justify-start max-md:snap-x max-md:snap-mandatory [&::-webkit-scrollbar]:h-[1.111vw] [&::-webkit-scrollbar]:w-[none] [&::-webkit-scrollbar-thumb]:bg-transparent [&::-webkit-scrollbar-thumb]:w-4 [&::-webkit-scrollbar-thumb]:rounded-full max-md:-mx-4 max-md:px-4"
>
{hqFeatures.map(({ image, name }) => (
<div
key={name}
className="relative md:max-2xl:w-[30.208vw] max-md:w-full max-md:max-w-[520px] max-md:flex-shrink-0 max-md:snap-center"
>
<img
src={image}
alt={name}
className="object-cover object-center 2xl:rounded-[1.667vw] max-md:!aspect-[328/430] max-md:rounded-2xl max-md:max-h-[690px]"
/>
<div className="md:hidden text-black text-h5 mx-auto text-center text-nowrap mt-4 mb-6 font-medium">
{name}
</div>
</div>
))}
</div>
<CustomScrollBar
containerRef={hqFeaturesRef}
inlinePadding={16}
trackStyle="md:hidden"
thumbStyle="md:hidden"
/>
</>
);
}
export default AboutHQSliderMobile;
@@ -0,0 +1,100 @@
import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";
import { useSwipeable } from "react-swipeable";
import { hqFeatures } from "../../data/aboutHQ";
import Button from "../ui/Button";
import ChevronLeftIcon from "../icons/ChevronLeftIcon";
import clsx from "clsx";
import ChevronRightIcon from "../icons/ChevronRightIcon";
function AboutHQSliderTablet() {
const [currentIndex, setCurrentIndex] = useState(1);
const handlers = useSwipeable({
onSwipedLeft: () =>
setCurrentIndex(
Math.min(currentIndex + 1, hqFeatures.length - 1)
),
onSwipedRight: () => setCurrentIndex(Math.max(currentIndex - 1, 0)),
preventScrollOnSwipe: true,
touchEventOptions: {
passive: false,
},
trackMouse: true,
});
return (
<div
className="relative hidden md:max-2xl:block min-h-[calc(47.135vw*11/9)]"
{...handlers}
>
<motion.div
className="flex items-center gap-[9.375vw]"
animate={{
transform: `translateX(calc(-31.641vw - 24px - ${
currentIndex - 1
} * 47.135vw))`,
}}
>
<AnimatePresence>
{hqFeatures.map(({ image, name }, index) => (
<motion.div
animate={{
width: index === currentIndex ? "69.141vw" : "37.76vw",
}}
exit={{
width: index !== currentIndex ? "69.141vw" : "37.76vw",
}}
key={name}
className="shrink-0 flex flex-col"
>
<motion.img
src={image}
alt={name}
className="object-cover rounded-[3.125vw] pointer-events-none aspect-[12/9] object-center"
animate={{
marginBottom: index !== currentIndex ? "32px" : "24px",
}}
onClick={() => setCurrentIndex(index)}
/>
<p
className={clsx(
"text-h4 text-center font-medium",
index !== currentIndex && "opacity-40"
)}
>
{name}
</p>
</motion.div>
))}
</AnimatePresence>
</motion.div>
<Button
onlyIcon
disabled={currentIndex === 0}
onClick={() => setCurrentIndex(Math.max(currentIndex - 1, 0))}
className="absolute left-[calc((100%-69.141vw)/2-56px)] bottom-1/2"
>
<span className="size-5">
<ChevronLeftIcon />
</span>
</Button>
<Button
onlyIcon
disabled={currentIndex === hqFeatures.length - 1}
className="absolute right-[calc((100%-69.141vw)/2-56px)] bottom-1/2"
onClick={() =>
setCurrentIndex(
Math.min(currentIndex + 1, hqFeatures.length - 1)
)
}
>
<span className="size-5">
<ChevronRightIcon />
</span>
</Button>
</div>
);
}
export default AboutHQSliderTablet;
+136
View File
@@ -0,0 +1,136 @@
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;
@@ -0,0 +1,87 @@
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>
);
}
+1 -1
View File
@@ -28,7 +28,7 @@ function AboutMarasiDrive() {
return (
<div className="relative">
<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)] 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">
<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-[140vw] md:size-[130vw] size-[120vw] rounded-full absolute 2xl:top-1/2 2xl:left-[104vw] md:top-[100%] left-1/2 top-[100%] md:left-1/2 -translate-x-1/2 -translate-y-1/2 bg-[#F3F3F2]" />
<img
src="/images/about-complex/marasi-drive/main_bg.png"
+1 -1
View File
@@ -6,7 +6,7 @@ function MarasiDriveMapMobile() {
const containerRef = useRef<HTMLDivElement>(null);
return (
<div className="hidden relative max-md:flex flex-col">
<div className="hidden relative max-2xl:flex flex-col">
<div className="max-md:aspect-[328/544] md:max-2xl:aspect[1/1] max-2xl:mb-4">
<img
src="/images/about-complex/marasi-drive/map/map.png"
-58
View File
@@ -1,58 +0,0 @@
import { motion, AnimatePresence, useTransform, MotionValue } from 'motion/react';
interface ScrollSliderProps {
scrollYProgress: MotionValue<number>;
images: { name: string; image: string }[];
}
export default function ScrollSlider({ scrollYProgress, images }: ScrollSliderProps) {
const x = useTransform(scrollYProgress, [0, 1 / 3], ["4.236vw", "-20.868vw"]);
const width1 = useTransform(
scrollYProgress,
[0, 1 / 3],
["20.139vw", "20.139vw"]
);
const width2 = useTransform(
scrollYProgress,
[0, 1 / 3],
["36.944vw", "20.139vw"]
);
const width3 = useTransform(
scrollYProgress,
[0, 1 / 3],
["20.139vw", "36.944vw"]
);
const width4 = useTransform(
scrollYProgress,
[0, 1 / 3],
["20.139vw", "20.139vw"]
);
return (
<motion.div
className="flex items-center 2xl:gap-[4.965vw] gap-[9.375vw] 2xl:min-h-[27.639vw] max-2xl:hidden"
style={{ x }}
>
<AnimatePresence>
{images.map(({ name, image }, index) => (
<motion.div
key={name}
className="flex flex-col shrink-0 items-center 2xl:gap-[1.667vw] gap-6"
style={{ width: [width1, width2, width3, width4][index] }}
>
<img
src={image}
alt={name}
className="2xl:rounded-[1.667vw] rounded-[3.125vw] aspect-[290/205] object-cover w-full pointer-events-none"
/>
<p className={"text-h4 text-center font-medium"}>{name}</p>
</motion.div>
))}
</AnimatePresence>
</motion.div>
);
}
+108
View File
@@ -0,0 +1,108 @@
export const hqFeatures = [
{
name: "Reception Lounge",
image: "/images/about-complex/hq/features/reception_lounge.png",
},
{
name: "Beauty Lounge",
image: "/images/about-complex/hq/features/beauty_lounge.png",
},
{
name: "Spa Pod",
image: "/images/about-complex/hq/features/spa_pod.png",
},
{
name: "Rentable Space",
image: "/images/about-complex/hq/features/rentable_space.png",
},
{
name: "Upresso Animated",
image: "/images/about-complex/hq/features/upresso_animated.png",
},
{
name: "Podium",
image: "/images/about-complex/hq/features/podium.png",
},
{
name: "Foodhall",
image: "/images/about-complex/hq/features/foodhall.png",
},
{
name: "Spa Pool",
image: "/images/about-complex/hq/features/spa_pool.png",
},
] as const;
export const hqDescription = [
{
title: "Modular Office Collection",
description: "Spaces that easily expand and adapt",
},
{
title: "A La Carte",
description: "Bespoke offices designed around you",
},
{
title: "Loft Office Collection",
description: "Double-height ceilings and the warm atmosphere of home",
},
] as const;
export const hqSlider = {
"Modular Office Collection": [
{
title: "Studio",
description: "Efficient spaces that merge intelligent design with everyday practicality, available in open and closed layouts to suit your style.",
image: "/images/about-complex/hq/slider/modularOffice/Studio.png",
},
{
title: "Deluxe",
description: "Double the size, enjoy dedicated meeting rooms, upgraded furnishings, and the option to tailor.",
image: "/images/about-complex/hq/slider/modularOffice/Deluxe.png",
},
{
title: "Executive",
description: "Triple-sized with expanded seating, generous storage, and versatile layouts - available in open, semi-open, or closed configurations to suit growing teams.",
image: "/images/about-complex/hq/slider/modularOffice/Executive.png",
},
{
title: "Simplex Edge",
description: "Nestled on the edge, most expansive setting, with generous layouts premium furniture, and a statement-making workspace.",
image: "/images/about-complex/hq/slider/modularOffice/SimplexEdge.png",
},
],
"A La Carte": [
{
title: "Semi-Olympic Leisure Pool",
description:
"Overlooking the city, enjoy a leisurely swim, do your daily 25-meter laps or just unwind at one of the cozy cabanas,",
image: "/images/about-complex/dubai-marina/fitness1.jpg",
},
{
title: "Boutique Fitness Studio",
description:
"Elevate your fitness regime at flagship Crank Ride & Shape studios blending fitness, fun, and thrill vibes.",
image: "/images/about-complex/dubai-marina/fitness2.jpg",
},
],
"Loft Office Collection": [
{
title: "Loft Edge",
description:
"Soaring double-height ceilings, curated design details, and a warm living touch that makes the office feel like home.",
image: "/images/about-complex/hq/slider/loftOffice/LoftEdge.png",
},
{
title: "Penthouse Lofts",
description:
"Enjoy more of space, style, and comfort. With integrated stair seating, a private kitchenette, and an in-office washroom, these lofts blend thoughtful design with premium interiors to create a workspace that feels truly complete.",
image: "/images/about-complex/hq/slider/loftOffice/PenthouseLofts.png",
},
{
title: "Presedential Loft",
description:
"Step into a panoramic Presidential Loft Office on the highest floors - spacious, light-filled, and beautifully designed with a living touch",
image: "/images/about-complex/hq/slider/loftOffice/PresidentialLoft.png",
}
]
};