This commit is contained in:
2026-01-22 15:08:31 +05:00
26 changed files with 1041 additions and 435 deletions
+8 -22
View File
@@ -26,7 +26,7 @@ export default function AboutHQ() {
);
return (
<div className="relative bg-white">
<section 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">
@@ -40,8 +40,8 @@ export default function AboutHQ() {
<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`}
Work looks different
here`}
</h1>
</div>
@@ -55,7 +55,7 @@ export default function AboutHQ() {
{`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">
<p className="opacity-70 text-s 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
@@ -68,20 +68,6 @@ export default function AboutHQ() {
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 */}
@@ -96,10 +82,10 @@ export default function AboutHQ() {
<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">
<p className="text-center whitespace-pre-line opacity-70 text-s 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.`}
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} />
@@ -313,6 +299,6 @@ export default function AboutHQ() {
</div>
</div>
</section>
</div>
</section>
);
}
+17 -17
View File
@@ -53,7 +53,7 @@ function AboutMarasiDrive() {
{`A home for the young
and young in heart`}
</h4>
<p className="text-s opacity-70 2xl:w-1/4 md:max-2xl:w-1/3">
<p className="opacity-70 text-s 2xl:w-1/4 md:max-2xl:w-1/3">
The dynamic essence of Rove comes to life at our new location in
Marasi Drive, Business Bay. Enjoy an urban living experience
beyond the ordinary.
@@ -62,12 +62,12 @@ function AboutMarasiDrive() {
</section>
<section>
<div className="space-y-6 bg-white px-4 py-8 md:hidden">
<div className="px-4 py-8 space-y-6 bg-white md:hidden">
<h4 className="text-h4 text-[#00BED7] font-medium whitespace-pre-line ">
{`A home for the young
and young in heart`}
</h4>
<p className="text-s opacity-70">
<p className="opacity-70 text-s">
The dynamic essence of Rove comes to life at our new location in
Marasi Drive, Business Bay. Enjoy an urban living experience
beyond the ordinary.
@@ -79,11 +79,11 @@ function AboutMarasiDrive() {
<div className="2xl:sticky relative 2xl:top-[-134vw]">
<section className="bg-white w-full overflow-clip 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 flex flex-col 2xl:gap-[4.444vw] md:max-2xl:gap-12 gap-10">
<div className="flex flex-col 2xl:gap-[2.222vw] md:max-2xl:gap-8 gap-6 items-center">
<h2 className="font-mixcase-unmixed text-h1 text-center max-md:whitespace-pre-line">
<h2 className="text-center font-mixcase-unmixed text-h1 max-md:whitespace-pre-line">
{`What makes
a Rove Home?`}
</h2>
<p className="opacity-70 text-s whitespace-pre-line text-center max-md:whitespace-normal">
<p className="text-center whitespace-pre-line opacity-70 text-s max-md:whitespace-normal">
{`Experience the difference with Rove Home where modern amenities, trendy
interiors, and smart features cater to your unique style. Rove Home is your
destination for artful inspiration and cleverly activated spaces.`}
@@ -118,11 +118,11 @@ function AboutMarasiDrive() {
</section>
<section className="bg-white w-full overflow-clip flex flex-col 2xl:gap-[4.444vw] gap-12 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">
<div className="flex flex-col 2xl:gap-[2.222vw] md:max-2xl:gap-8 gap-6 items-center">
<h2 className="font-mixcase-unmixed text-h1 text-center max-md:whitespace-pre-line">
<h2 className="text-center font-mixcase-unmixed text-h1 max-md:whitespace-pre-line">
{`Expandable living
solutions`}
</h2>
<p className="text-s opacity-70 whitespace-pre-line text-center max-md:whitespace-normal">
<p className="text-center whitespace-pre-line opacity-70 text-s max-md:whitespace-normal">
{`ORI introduces a revolutionary solution to apartment living,
where space is not just a constraint but an opportunity.`}
</p>
@@ -158,10 +158,10 @@ function AboutMarasiDrive() {
</section>
<section className="bg-white w-full overflow-clip flex flex-col 2xl:gap-[4.444vw] gap-12 2xl:pt-[9.444vw] 2xl:pb-[15vw] 2xl:px-[2.222vw] md:max-2xl:pt-[104px] md:max-2xl:px-6 md:max-2xl:pb-8 pt-16 px-4 max-md:pb-8 max-md:gap-0">
<div className="flex flex-col 2xl:gap-[2.222vw] md:max-2xl:gap-8 gap-6 items-center ">
<h2 className="font-mixcase-unmixed text-h1 text-center">
<h2 className="text-center font-mixcase-unmixed text-h1">
Inspired interiors
</h2>
<p className="text-s opacity-70 whitespace-pre-line text-center max-md:whitespace-normal max-md:mb-10">
<p className="text-center whitespace-pre-line opacity-70 text-s max-md:whitespace-normal max-md:mb-10">
{`Smart, flexible designs maximize every inch. The ORI Cloud Bed
expands space by 33%, while the Flexibed transforms living areas
into bedrooms. Multipurpose layouts adapt effortlessly—blending
@@ -173,14 +173,14 @@ function AboutMarasiDrive() {
</section>
</div>
<div className="2xl:sticky relative">
<div className="relative 2xl:sticky">
<section className="bg-white w-full overflow-clip flex flex-col 2xl:gap-[4.444vw] gap-12 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">
<div className="flex flex-col items-center 2xl:gap-[2.222vw] md:max-2xl:gap-8 gap-6">
<h2 className="font-mixcase-unmixed text-h1 text-center whitespace-pre-line">
<h2 className="text-center whitespace-pre-line font-mixcase-unmixed text-h1">
{`A home for the young
and young in heart`}
</h2>
<p className="text-s opacity-70 whitespace-pre-line text-center max-md:whitespace-normal">
<p className="text-center whitespace-pre-line opacity-70 text-s max-md:whitespace-normal">
{`The dynamic essence of Rove comes to life at our new
location in Marasi Drive, Business Bay. Enjoy an urban
living experience beyond the ordinary.`}
@@ -201,7 +201,7 @@ function AboutMarasiDrive() {
<h2 className="font-mixcase-unmixed text-h1 text-center whitespace-pre-line max-md:mb-[24px] md:max-2xl:mb-8">
{`Explore the neighbourhood`}
</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">
<p className="text-center whitespace-pre-line opacity-70 text-s max-md:whitespace-normal max-md:mb-12 md:max-2xl:mb-16">
{`With Dubai's trendiest spots right at your doorstep, explore nearby
entertainment and dining experiences in just 15 minutes. Live your best life
at Rove Home Marasi Drive!`}
@@ -229,7 +229,7 @@ function AboutMarasiDrive() {
{marasiDriveMapCards.map((card) => (
<MarasiDriveMapCard {...card} key={card.title} />
))}
<div className="col-start-3 col-span-full row-start-1 row-span-full">
<div className="col-span-full col-start-3 row-span-full row-start-1">
<img
src="/images/about-complex/marasi-drive/map/map.png"
alt=""
@@ -249,7 +249,7 @@ function AboutMarasiDrive() {
</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">
<h2 className="font-medium whitespace-pre-line text-h2 max-2xl:mb-4">
{`Live Different
with Rove Home`}
</h2>
@@ -268,8 +268,8 @@ function AboutMarasiDrive() {
link="/files/marasi-drive/Main Brochure.pdf"
/>
<BrochureButton
title={"Amenties Brochure"}
link="/files/marasi-drive/Amenties Brochure.pdf"
title={"Amenities Brochure"}
link="/files/marasi-drive/Amenities Brochure.pdf"
/>
<BrochureButton
title={"Technical Brochure"}
+20 -20
View File
@@ -14,19 +14,19 @@ interface FloorPopupProps {
onSelect: (floor: string) => void;
}
function getAmentiesCount(complexName: string, title: string) {
const amenties = projects.find(
function getAmenitiesCount(complexName: string, title: string) {
const amenities = projects.find(
(proj) => proj.slug === complexName
)?.amentiesFloors;
return amenties?.find((amenty) => amenty.title === title);
)?.amenitiesFloors;
return amenities?.find((amenity) => amenity.title === title);
}
function FloorPopup({ title, complexName, data, onSelect }: FloorPopupProps) {
const { setPopup } = usePopupStore();
const amentiesCount = useMemo(
const amenitiesCount = useMemo(
() =>
Number.isNaN(+title.at(-1)!)
? getAmentiesCount(complexName, title)
? getAmenitiesCount(complexName, title)
: null,
[title, complexName]
);
@@ -35,16 +35,16 @@ function FloorPopup({ title, complexName, data, onSelect }: FloorPopupProps) {
<div className="flex flex-col 2xl:gap-y-[0.556vw] gap-y-2">
<div className="flex 2xl:gap-[0.556vw] gap-2">
<p className="text-h5 font-medium">
{amentiesCount ? title : `${title.split(" ").at(-1)} floor`}
{amenitiesCount ? title : `${title.split(" ").at(-1)} floor`}
</p>
{complexName === "marasi-drive" && !amentiesCount && (
{complexName === "marasi-drive" && !amenitiesCount && (
<p className="text-s opacity-40">{title.split(" ")[0]} Wing</p>
)}
</div>
<div className="flex 2xl:gap-[0.278vw] gap-1">
<p className="2xl:px-[0.556vw] 2xl:py-[0.278vw] px-2 py-0.5 bg-[#F3F3F2] 2xl:rounded-[0.278vw] rounded text-caption-s opacity-70">
{amentiesCount !== null
? `${amentiesCount?.total} Amenties`
{amenitiesCount !== null
? `${amenitiesCount?.total} Amenities`
: `${
complexName === "marasi-drive" && data
? data[
@@ -57,7 +57,7 @@ function FloorPopup({ title, complexName, data, onSelect }: FloorPopupProps) {
: data?.others.totalUnits
} apartments`}
</p>
{!amentiesCount && (
{!amenitiesCount && (
<div className="2xl:px-[0.556vw] 2xl:py-[0.278vw] px-2 py-0.5 bg-[#30B216] bg-opacity-[8%] 2xl:rounded-[0.278vw] rounded flex 2xl:gap-[0.278vw] gap-1">
<span className="2xl:size-[0.833vw] size-3 text-[#30B216]">
<HumanIcon />
@@ -66,12 +66,12 @@ function FloorPopup({ title, complexName, data, onSelect }: FloorPopupProps) {
</div>
)}
</div>
{((amentiesCount?.indoor && amentiesCount?.outdoor) ||
!amentiesCount) && (
{((amenitiesCount?.indoor && amenitiesCount?.outdoor) ||
!amenitiesCount) && (
<hr className="border-[#E2E2DC] 2xl:h-[0.069vw] h-px" />
)}
<div className="flex flex-col 2xl:gap-y-[0.556vw] gap-y-2">
{!amentiesCount && data ? (
{!amenitiesCount && data ? (
Object.entries(
data[
title.split(" ")[0] === "East"
@@ -92,20 +92,20 @@ function FloorPopup({ title, complexName, data, onSelect }: FloorPopupProps) {
))
) : (
<>
{amentiesCount?.indoor && (
{amenitiesCount?.indoor && (
<div className="flex 2xl:gap-[0.556vw] gap-2">
<p className="bg-[#00BED7] rounded-full flex justify-center items-center font-mono text-caption-s text-white 2xl:size-[1.111vw] size-4">
{amentiesCount.indoor}
{amenitiesCount.indoor}
</p>
<p className="text-caption-m opacity-70">Indoor Amenties</p>
<p className="text-caption-m opacity-70">Indoor Amenities</p>
</div>
)}
{amentiesCount?.outdoor && (
{amenitiesCount?.outdoor && (
<div className="flex 2xl:gap-[0.556vw] gap-2">
<p className="bg-[#00BED7] rounded-full flex justify-center items-center font-mono text-caption-s text-white 2xl:size-[1.111vw] size-4">
{amentiesCount.outdoor}
{amenitiesCount.outdoor}
</p>
<p className="text-caption-m opacity-70">Outdoor Amenties</p>
<p className="text-caption-m opacity-70">Outdoor Amenities</p>
</div>
)}
</>
@@ -0,0 +1,142 @@
import { useState } from "react";
import { AmenitiesFloorData } from "../../types/Floor";
import Badge from "../ui/Badge";
import Button from "../ui/Button";
import PlayIcon from "../icons/PlayIcon";
import useModalStore from "../../stores/useModalStore";
import VideoModal from "../VideoModal";
import ViewToggleButtons from "./ViewToggleButtons";
import AmentitiesBadge from "../AmentitiesCard";
import AmenitiesBadge from "../icons/AmenitiesBadge";
import AmentitiesContentSlider from "../AmentitiesContentSlider";
interface AmenitiesFloorViewProps {
floor: AmenitiesFloorData;
}
function AmenitiesFloorView({ floor }: AmenitiesFloorViewProps) {
const { setModal } = useModalStore();
const [currentView, setCurrentView] = useState<"exterior" | "interior">(
"exterior"
);
const hasInteriorView = !!floor.images.interior;
const currentImage =
currentView === "interior" && floor.images.interior
? floor.images.interior
: floor.images.main;
// Split amenities into indoor and outdoor if counts are provided
const hasIndoorOutdoorSplit =
floor.amenitiesCount.indoor !== undefined &&
floor.amenitiesCount.outdoor !== undefined;
const indoorAmenities = hasIndoorOutdoorSplit
? floor.amenitiesList.slice(0, floor.amenitiesCount.indoor)
: [];
const outdoorAmenities = hasIndoorOutdoorSplit
? floor.amenitiesList.slice(floor.amenitiesCount.indoor)
: floor.amenitiesList;
return (
<div className="2xl:space-y-[2.222vw] space-y-8">
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">{floor.displayName}</p>
<Badge
variant="secondary"
text={`${floor.amenitiesCount.total} Amenities`}
/>
</div>
{hasIndoorOutdoorSplit && (
<div className="flex items-center 2xl:gap-[1.667vw] gap-6">
<AmenitiesBadge
count={floor.amenitiesCount.indoor!}
type="Indoor"
/>
<AmenitiesBadge
count={floor.amenitiesCount.outdoor!}
type="Outdoor"
/>
</div>
)}
<ViewToggleButtons
currentView={currentView}
onViewChange={setCurrentView}
hasInteriorView={hasInteriorView}
/>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
src="/images/floor-plans/compass.png"
className="absolute top-0 left-0 size-[7.222vw]"
alt=""
/>
<img src={currentImage} alt={floor.name} className="w-full" />
{floor.video && (
<Button
variant="cta"
className="absolute 2xl:top-[1.667vw] 2xl:right-[1.667vw] md:max-2xl:top-6 md:max-2xl:right-6 top-4 right-4"
onlyIcon
size="small"
onClick={() => setModal(<VideoModal src={floor.video!} />)}
>
<span className="2xl:size-[1.111vw] size-4">
<PlayIcon />
</span>
</Button>
)}
</div>
</div>
{hasIndoorOutdoorSplit && indoorAmenities.length > 0 && (
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Indoor Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 2xl:gap-[1.111vw] gap-4">
{indoorAmenities.map((amenity, index) => (
<AmentitiesBadge key={index} title={amenity.title} />
))}
</div>
</div>
)}
{hasIndoorOutdoorSplit && indoorAmenities.length > 0 && (
<hr className="border-[#E2E2DC] 2xl:h-[0.069vw] h-px" />
)}
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">
{hasIndoorOutdoorSplit ? "Outdoor Amenities" : "Amenities"}
</p>
<div
className={
hasIndoorOutdoorSplit
? "grid md:grid-cols-4 grid-cols-3 2xl:gap-x-[1.111vw] 2xl:gap-y-[1.667vw] gap-x-4 gap-y-6"
: "flex flex-wrap 2xl:gap-[0.556vw] gap-2"
}
>
{outdoorAmenities.map((amenity, index) => (
<AmentitiesBadge key={index} title={amenity.title} />
))}
</div>
</div>
{floor.images.content.length > 1 ? (
<AmentitiesContentSlider srcs={floor.images.content} />
) : (
floor.images.content.length === 1 && (
<img
src={floor.images.content[0]}
alt={floor.name}
className="w-full 2xl:rounded-[1.111vw] rounded-2xl select-none"
/>
)
)}
</div>
);
}
export default AmenitiesFloorView;
@@ -0,0 +1,46 @@
import { FloorData } from "../../types/Floor";
import { Unit } from "../../types/IUnit";
import { ComplexName } from "../../types/ComplexName";
import { FloorsData } from "../FloorSelect";
import ResidentialFloorView from "./ResidentialFloorView";
import AmenitiesFloorView from "./AmenitiesFloorView";
interface FloorPlanViewerProps {
floor: FloorData;
complexName: ComplexName;
unitsOnFloor?: Unit[];
floorsData?: FloorsData[];
selectedFloor: string;
onFloorSelect: (floor: string) => void;
}
function FloorPlanViewer({
floor,
complexName,
unitsOnFloor,
floorsData,
selectedFloor,
onFloorSelect,
}: FloorPlanViewerProps) {
if (floor.type === "residential") {
return (
<ResidentialFloorView
floor={floor}
complexName={complexName}
unitsOnFloor={unitsOnFloor}
floorsData={floorsData}
selectedFloor={selectedFloor}
onFloorSelect={onFloorSelect}
/>
);
}
if (floor.type === "amenities") {
return <AmenitiesFloorView floor={floor} />;
}
// This should never happen with proper TypeScript typing
return null;
}
export default FloorPlanViewer;
@@ -0,0 +1,283 @@
import { useState } from "react";
import { ResidentialFloorData } from "../../types/Floor";
import { Unit } from "../../types/IUnit";
import { ComplexName } from "../../types/ComplexName";
import { FloorsData } from "../FloorSelect";
import Badge from "../ui/Badge";
import Select from "../ui/Select";
import UnitTypeBadge from "../UnitTypeBadge";
import Button from "../ui/Button";
import { usePopupStore } from "../../stores/usePopupStore";
import { isMobile } from "react-device-detect";
// Import floor plan components
import FloorPlanMarasiDriveEast from "../FloorPlanMarasiDriveEast";
import FloorPlanMarasiDriveWestLower from "../FloorPlanMarasiDriveWestLower";
import FloorPlanMarasiDriveWestUpper from "../FloorPlanMarasiDriveWestUpper";
import FloorPlanDubaiMarina7_38 from "../FloorPlanDubaiMarina7_38";
import FloorPlanDubaiMarina7_38Comb from "../FloorPlanDubaiMarina7_38Comb";
import FloorPlanDubaiMarina39_40 from "../FloorPlanDubaiMarina39_40";
import FloorPlanDubaiMarina41_42 from "../FloorPlanDubaiMarina41_42";
interface ResidentialFloorViewProps {
floor: ResidentialFloorData;
complexName: ComplexName;
unitsOnFloor?: Unit[];
floorsData?: FloorsData[];
selectedFloor: string;
onFloorSelect: (floor: string) => void;
}
function ResidentialFloorView({
floor,
complexName,
unitsOnFloor,
floorsData,
selectedFloor,
onFloorSelect,
}: ResidentialFloorViewProps) {
const { setPopup, setPosition } = usePopupStore();
const [isCombinable, setIsCombinable] = useState(false);
// Marasi Drive specific logic
if (complexName === "marasi-drive") {
const floorNumber = floor.floorNumber;
const wing = floor.wing || selectedFloor.split(" ")[0];
const currentFloorData = floorsData?.find(
(item) => item.floor === floorNumber
);
const totalUnits =
(currentFloorData?.East?.totalUnits || 0) +
(currentFloorData?.West?.totalUnits || 0);
const wingData =
currentFloorData?.[selectedFloor.split(" ")[0] as "West" | "East"];
return (
<div className="2xl:space-y-[1.111vw] space-y-4" onScroll={() => setPopup(null)}>
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-4">
<p className="font-medium text-h4">{floorNumber} floor</p>
<div className="flex items-center 2xl:gap-[0.278vw] gap-1">
<Badge variant="secondary" text={`${totalUnits} Apartments`} />
</div>
</div>
<div className="2xl:space-y-[0.833vw] space-y-2">
<div className="flex items-center 2xl:gap-[1.111vw] gap-2">
<Select
options={
floorsData?.flatMap((item) => [
`East ${item.floor}`,
`West ${item.floor}`,
]) || []
}
defaultOption={selectedFloor?.toString() || ""}
onSelect={onFloorSelect}
className="2xl:w-[8.333vw] md:max-2xl:w-[120px] w-full"
maxOptionsCount={7}
/>
<div className="bg-[#E2E2DC] w-px 2xl:h-[1.667vw] h-1.5"></div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-2 max-md:hidden">
<UnitTypeBadge
type="Studio Flex"
count={wingData?.types["Studio Flex"] || 0}
/>
<UnitTypeBadge
type="Studio"
count={wingData?.types["Studio Squared"] || 0}
/>
<UnitTypeBadge
type="1 Bedroom"
count={wingData?.types["1 BR Squared"] || 0}
/>
<UnitTypeBadge
type="2 Bedroom"
count={wingData?.types["2 BR Squared"] || 0}
/>
</div>
</div>
</div>
<div
className="2xl:p-[4.444vw] p-4 bg-[#F3F3F2] 2xl:rounded-[0.833vw] rounded-lg"
onMouseMove={(e) =>
!isMobile && setPosition({ x: e.clientX, y: e.clientY })
}
>
{unitsOnFloor && wing === "East" && (
<FloorPlanMarasiDriveEast
unitsOnFloor={unitsOnFloor}
selectedFloor={floorNumber.toString()}
/>
)}
{wing === "West" && unitsOnFloor && (
<>
{floorNumber < 24 ? (
<FloorPlanMarasiDriveWestLower
unitsOnFloor={unitsOnFloor}
selectedFloor={floorNumber.toString()}
/>
) : (
<FloorPlanMarasiDriveWestUpper
unitsOnFloor={unitsOnFloor}
selectedFloor={floorNumber.toString()}
/>
)}
</>
)}
</div>
</div>
);
}
// Dubai Marina specific logic
if (complexName === "dubai-marina") {
const floorNumber = floor.floorNumber;
const currentFloorData = floorsData?.find(
(item) => item.floor === floorNumber
);
const isSpecialFloor =
selectedFloor === "39-40" || selectedFloor === "41-42";
return (
<div className="2xl:space-y-[1.111vw] space-y-4" onScroll={() => setPopup(null)}>
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-4">
<p className="font-medium text-h4">{selectedFloor} floor</p>
<div className="flex items-center 2xl:gap-[0.278vw] gap-1">
<Badge
variant="secondary"
text={`${currentFloorData?.others.totalUnits || 0} Apartments`}
/>
{!isSpecialFloor && <Badge variant="primary" text="Combinable" />}
</div>
</div>
<div className="2xl:space-y-[0.833vw] space-y-2">
<div className="flex items-center 2xl:gap-[1.111vw] gap-2">
<Select
options={
floorsData?.map((item) => {
if (item.floor === 39) {
return "39-40";
}
if (item.floor === 41) {
return "41-42";
}
return item.floor.toString();
}) || []
}
defaultOption={selectedFloor?.toString() || ""}
onSelect={onFloorSelect}
className="2xl:w-[8.333vw] md:max-xl:w-[120px] w-full"
maxOptionsCount={7}
/>
<div className="bg-[#E2E2DC] w-px 2xl:h-[1.667vw] h-1.5"></div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-2 max-md:hidden">
<UnitTypeBadge
type="Studio"
count={
floorsData?.find(
(item) =>
item.floor ===
parseInt(selectedFloor!.split(" ").at(-1)!)
)?.others.types["Studio2"] || 0
}
/>
<UnitTypeBadge
type="1 Bedroom"
count={
floorsData?.find(
(item) =>
item.floor ===
parseInt(selectedFloor!.split(" ").at(-1)!)
)?.others.types["One Bedroom2"] || 0
}
/>
<UnitTypeBadge
type="1 Bedroom Loft"
count={
floorsData?.find(
(item) =>
item.floor ===
parseInt(selectedFloor!.split(" ").at(-1)!)
)?.others.types["One Bedroom Loft"] || 0
}
/>
<UnitTypeBadge
type="2 Bedroom Loft"
count={
floorsData?.find(
(item) =>
item.floor ===
parseInt(selectedFloor!.split(" ").at(-1)!)
)?.others.types["Two Bedroom Loft"] || 0
}
/>
</div>
</div>
{!isSpecialFloor && (
<div className="flex gap-2 justify-center items-center">
<Button
variant={!isCombinable ? "cta" : "primary"}
onClick={() => setIsCombinable(false)}
>
Standard
</Button>
<Button
variant={isCombinable ? "cta" : "primary"}
onClick={() => setIsCombinable(true)}
>
Combinable
</Button>
</div>
)}
<div
className="2xl:py-[1.667vw] 2xl:px-[1.111vw] max-2xl:p-4 bg-[#F3F3F2] 2xl:rounded-[0.833vw] rounded-lg relative 2xl:space-y-[2.222vw] space-y-8"
onMouseMove={(e) =>
!isMobile && setPosition({ x: e.clientX, y: e.clientY })
}
>
{selectedFloor && unitsOnFloor && (
<>
{+selectedFloor >= 7 && +selectedFloor < 39 && (
<>
{!isCombinable ? (
<FloorPlanDubaiMarina7_38
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
/>
) : (
<FloorPlanDubaiMarina7_38Comb
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
/>
)}
</>
)}
{selectedFloor === "39-40" && (
<FloorPlanDubaiMarina39_40
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
chosenUnit={null}
/>
)}
{selectedFloor === "41-42" && (
<FloorPlanDubaiMarina41_42
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
chosenUnit={null}
/>
)}
</>
)}
</div>
</div>
</div>
);
}
// Default fallback
return <div>Unsupported complex: {complexName}</div>;
}
export default ResidentialFloorView;
@@ -0,0 +1,47 @@
import clsx from "clsx";
import Button from "../ui/Button";
interface ViewToggleButtonsProps {
currentView: "exterior" | "interior";
onViewChange: (view: "exterior" | "interior") => void;
hasInteriorView: boolean;
}
function ViewToggleButtons({
currentView,
onViewChange,
hasInteriorView,
}: ViewToggleButtonsProps) {
if (!hasInteriorView) {
return null;
}
return (
<div className="flex 2xl:gap-[0.556vw] gap-2">
<Button
variant={currentView === "exterior" ? "primary" : "secondary"}
size="small"
onClick={() => onViewChange("exterior")}
className={clsx(
"2xl:px-[1.111vw] 2xl:py-[0.556vw] px-4 py-2",
currentView === "exterior" && "!bg-[#0D1922] !text-white"
)}
>
Exterior View
</Button>
<Button
variant={currentView === "interior" ? "primary" : "secondary"}
size="small"
onClick={() => onViewChange("interior")}
className={clsx(
"2xl:px-[1.111vw] 2xl:py-[0.556vw] px-4 py-2",
currentView === "interior" && "!bg-[#0D1922] !text-white"
)}
>
Interior View
</Button>
</div>
);
}
export default ViewToggleButtons;
@@ -13,7 +13,7 @@ function GroundDubaiMarina() {
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Ground Level</p>
<Badge variant="secondary" text="14 Amenties" />
<Badge variant="secondary" text="14 Amenities" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
@@ -15,7 +15,7 @@ function PodiumDubaiMarina() {
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Podium</p>
<Badge variant="secondary" text="14 Amenties" />
<Badge variant="secondary" text="14 Amenities" />
</div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-6">
<AmenitiesBadge count={3} type="Indoor" />
@@ -30,7 +30,7 @@ function RooftopDubaiMarina() {
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Sky 44 - Rooftop</p>
<Badge variant="secondary" text="14 Amenties" />
<Badge variant="secondary" text="14 Amenities" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
@@ -21,7 +21,7 @@ function GroundMarasiDrive() {
<div className="space-y-[1.667vw]">
<div className="space-y-[0.556vw] border-b border-[#E2E2DC] pb-[1.667vw]">
<p className="text-h4 font-medium">Ground Level</p>
<Badge variant="secondary" text="7 Amenties" />
<Badge variant="secondary" text="7 Amenities" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
@@ -52,7 +52,7 @@ function GroundMarasiDrive() {
</div>
</div>
<div className="space-y-[1.667vw]">
<p className="font-medium text-h4">Amenties</p>
<p className="font-medium text-h4">Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 gap-[1.111vw]">
<AmentitiesBadge icon={<RoveCafe />} title="Rove Café" />
<AmentitiesBadge icon={<LoungingSpaceIcon />} title="Lobby Lounge" />
@@ -41,7 +41,7 @@ function PodiumMarasiDrive() {
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Podium Level</p>
<Badge variant="secondary" text="27 Amenties" />
<Badge variant="secondary" text="27 Amenities" />
</div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-6">
<AmenitiesBadge count={13} type="Indoor" />
@@ -76,7 +76,7 @@ function PodiumMarasiDrive() {
</div>
</div>
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Indoor Amenties</p>
<p className="font-medium text-h4">Indoor Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 2xl:gap-[1.111vw] gap-4">
<AmentitiesBadge icon={<LoungeIcon />} title="Indoor Lounge" />
<AmentitiesBadge icon={<MonkeyBarsIcon />} title="Monkey Bars" />
@@ -110,7 +110,7 @@ function PodiumMarasiDrive() {
</div>
<hr className="border-[#E2E2DC] h-[0.069vw]" />
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Outdoor Amenties</p>
<p className="font-medium text-h4">Outdoor Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 2xl:gap-x-[1.111vw] 2xl:gap-y-[1.667vw] gap-x-4 gap-y-6">
<AmentitiesBadge
icon={<UrbanBeachPoolIcon />}
@@ -23,7 +23,7 @@ function RooftopMarasiDrive() {
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Rooftop</p>
<Badge variant="secondary" text="10 Amenties" />
<Badge variant="secondary" text="10 Amenities" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
@@ -54,7 +54,7 @@ function RooftopMarasiDrive() {
</div>
</div>
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Amenties</p>
<p className="font-medium text-h4">Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 2xl:gap-[1.111vw] gap-4">
<AmentitiesBadge icon={<StargazingIcon />} title="Stargazing Point" />
<AmentitiesBadge icon={<BBQTerraceIcon />} title="BBQ Terrace" />
@@ -32,7 +32,7 @@ function SkyGardenMarasiDrive() {
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Sky Garden</p>
<Badge variant="secondary" text="15 Amenties" />
<Badge variant="secondary" text="15 Amenities" />
</div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-4">
<AmenitiesBadge count={3} type="Indoor" />
@@ -67,7 +67,7 @@ function SkyGardenMarasiDrive() {
</div>
</div>
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Indoor Amenties</p>
<p className="font-medium text-h4">Indoor Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 2xl:gap-[1.111vw] gap-4">
<AmentitiesBadge icon={<PoolIcon />} title="Indoor Lap Pool" />
<AmentitiesBadge icon={<WellnessIcon />} title="Wellness Features" />
@@ -76,7 +76,7 @@ function SkyGardenMarasiDrive() {
</div>
<hr className="border-[#E2E2DC] h-[0.069vw]" />
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Outdoor Amenties</p>
<p className="font-medium text-h4">Outdoor Amenities</p>
<div className="grid grid-cols-3 2xl:gap-x-[1.111vw] 2xl:gap-y-[1.667vw] gap-x-4 gap-y-6">
<AmentitiesBadge icon={<PingPongIcon />} title="Padel Pong" />
<AmentitiesBadge icon={<SunLoungeIcon />} title="Sun Lounging Deck" />
+1 -1
View File
@@ -37,7 +37,7 @@ export default function BrochuresDropdown() {
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ bounce: 0, duration: 0.3 }}
className="max-2xl:hidden p-[1.667vw] flex gap-[1.111vw] justify-stretch items-stretch fixed top-[calc(3.889vw+20px)] left-[40vw] w-[50vw] rounded-[1.111vw] bg-white shadow-[0_2px_8px_rgba(0,0,0,0.15)]"
className="max-2xl:hidden p-[1.111vw] flex gap-[1.111vw] justify-stretch items-stretch fixed top-[calc(3.889vw+20px)] left-[21%] rounded-[1.111vw] bg-white shadow-[0_2px_8px_rgba(0,0,0,0.15)]"
>
{projectBrochures.map((project, index) => (
<ProjectBrochuresList
@@ -16,13 +16,15 @@ export default function ProjectBrochuresList({
const isMobile = variant === "mobile";
return (
<div className="flex-1 space-y-4">
<p className="font-medium text-s">{projectTitle}</p>
<div
className={clsx(
"space-y-3",
isMobile ? "flex-1" : "2xl:w-[15.278vw] w-[220px]"
)}
>
<p className="font-medium whitespace-nowrap text-h5">{projectTitle}</p>
<div
className={clsx(
"flex flex-col",
isMobile ? "gap-2" : "gap-[0.556vw]"
)}
className={clsx("flex flex-col", isMobile ? "gap-2" : "gap-[0.556vw]")}
>
{brochures.map((brochure, index) => (
<BrochureButton
+2 -2
View File
@@ -22,10 +22,10 @@ export default function BrochureButton({
<Button
variant="primary"
size="large"
className="w-full !justify-between group"
className="w-full !justify-between !2xl:px-[1.389vw] !px-5 !2xl:py-[0.972vw] !py-3.5 group"
onClick={handleDownload}
>
<span className="text-nowrap text-caption-m duration-300">{title}</span>
<span className="duration-300 text-nowrap text-btn-m">{title}</span>
<span className="2xl:size-[1.389vw] size-5 opacity-70 group-hover:opacity-100 transition-opacity duration-300">
<DownloadIcon />
</span>