feat: add about page for dubai-marina

This commit is contained in:
2025-05-14 16:38:43 +05:00
parent 34e249b8f8
commit d6cba1ef2c
35 changed files with 813 additions and 86 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ import TwitterIcon from './icons/TwitterIcon';
function Footer() {
return (
<footer className='2xl:px-[2.222vw] 2xl:pb-[2.222vw] 2xl:pt-[2.778vw] md:max-2xl:p-6 px-4 py-6 grid 2xl:grid-cols-6 md:max-2xl:grid-cols-4 grid-cols-2 2xl:grid-rows-2 2xl:gap-x-[1.667vw] 2xl:gap-y-[1.111vw] max-2xl:gap-y-6 2xl:rounded-t-[1.667vw] rounded-t-3xl outline outline-[#E2E2DC] bg-white'>
<footer className='z-1 2xl:px-[2.222vw] 2xl:pb-[2.222vw] 2xl:pt-[2.778vw] md:max-2xl:p-6 px-4 py-6 grid 2xl:grid-cols-6 md:max-2xl:grid-cols-4 grid-cols-2 2xl:grid-rows-2 2xl:gap-x-[1.667vw] 2xl:gap-y-[1.111vw] max-2xl:gap-y-6 2xl:rounded-t-[1.667vw] rounded-t-3xl outline outline-[#E2E2DC] bg-white'>
<img
src='/images/logo.svg'
className='2xl:w-[5.972vw] w-[86px] cursor-pointer'
+53 -48
View File
@@ -1,17 +1,17 @@
// import { sequenceVideos } from "../data/sequenceVideos";
import { useState, useRef } from "react";
import gsap from "gsap";
import { useSwipeable } from "react-swipeable";
import { motion, AnimatePresence } from "motion/react";
import Button from "./ui/Button";
import ArrowRightIcon from "./icons/map/ArrowRightIcon";
import ArrowLeftIcon from "./icons/ArrowLeftIcon";
import Compass from "./Compass";
import { useNavigate } from "react-router";
import InfoIcon from "./icons/InfoIcon";
import FullScreenButton from "./FullScreenButton";
import PrivacyPolicyButton from "./PrivacyPolicyButton";
import DisclaimerButton from "./DisclaimerButton";
import { useState, useRef } from 'react';
import gsap from 'gsap';
import { useSwipeable } from 'react-swipeable';
import { motion, AnimatePresence } from 'motion/react';
import Button from './ui/Button';
import ArrowRightIcon from './icons/map/ArrowRightIcon';
import ArrowLeftIcon from './icons/ArrowLeftIcon';
import Compass from './Compass';
import { useNavigate } from 'react-router';
import InfoIcon from './icons/InfoIcon';
import FullScreenButton from './FullScreenButton';
import PrivacyPolicyButton from './PrivacyPolicyButton';
import DisclaimerButton from './DisclaimerButton';
interface SequenceSliderProps {
complexName: string;
@@ -25,8 +25,8 @@ function SequenceSlider({ complexName }: SequenceSliderProps) {
const [isAnimating, setIsAnimating] = useState(false);
const handlers = useSwipeable({
onSwipedLeft: () => handleSwipe("next"),
onSwipedRight: () => handleSwipe("prev"),
onSwipedLeft: () => handleSwipe('next'),
onSwipedRight: () => handleSwipe('prev'),
preventScrollOnSwipe: true,
touchEventOptions: {
passive: false,
@@ -37,7 +37,7 @@ function SequenceSlider({ complexName }: SequenceSliderProps) {
const [imageLoaded, setImageLoaded] = useState(0);
const [isShowVideo, setIsShowVideo] = useState(true);
const directionRef = useRef<"next" | "prev">("next");
const directionRef = useRef<'next' | 'prev'>('next');
const [isFullScreen, setIsFullScreen] = useState(false);
@@ -45,7 +45,7 @@ function SequenceSlider({ complexName }: SequenceSliderProps) {
setImageLoaded((prev) => prev + 1);
}
function handleSwipe(direction: "next" | "prev") {
function handleSwipe(direction: 'next' | 'prev') {
if (imageLoaded < FRAME_COUNT) return;
directionRef.current = direction;
setIsShowVideo(false);
@@ -53,18 +53,18 @@ function SequenceSlider({ complexName }: SequenceSliderProps) {
function handleLoadVideo() {}
function animate(direction: "next" | "prev") {
function animate(direction: 'next' | 'prev') {
setIsAnimating(true);
const targetIndex =
currentIndex + (direction === "next" ? FRAME_STEP : -FRAME_STEP); // -1, -2
currentIndex + (direction === 'next' ? FRAME_STEP : -FRAME_STEP); // -1, -2
gsap.to(
{ value: currentIndex },
{
value: targetIndex,
duration: 1,
ease: "power2.inOut",
ease: 'power2.inOut',
onUpdate: function () {
const currentValue = Math.round(this.targets()[0].value);
@@ -103,21 +103,21 @@ function SequenceSlider({ complexName }: SequenceSliderProps) {
handlers.ref(el);
rootRef.current = el;
}}
className="relative h-full overflow-hidden"
className='relative h-full overflow-hidden'
>
<AnimatePresence>
{imageLoaded < FRAME_COUNT && (
<motion.div
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 bg-white"
className='absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 bg-white'
>
<img
src={`/images/loader.png`}
alt=""
className="w-16 h-16 animate-spin"
alt=''
className='w-16 h-16 animate-spin'
/>
<p className="text-[#00BED7] text-m">
<p className='text-[#00BED7] text-m'>
{Math.round((imageLoaded / FRAME_COUNT) * 100)}%
</p>
</motion.div>
@@ -128,10 +128,10 @@ function SequenceSlider({ complexName }: SequenceSliderProps) {
<img
key={index}
src={`/images/sequence/compressed-${
window.innerWidth < 768 ? "mobile" : "desktop"
window.innerWidth < 768 ? 'mobile' : 'desktop'
}/${index}.jpg`}
alt=""
className="absolute object-cover w-full h-full pointer-events-none"
alt=''
className='absolute object-cover w-full h-full pointer-events-none'
style={{
opacity: index === currentIndex ? 1 : 0,
}}
@@ -144,13 +144,13 @@ function SequenceSlider({ complexName }: SequenceSliderProps) {
<motion.video
key={currentIndex}
src={`/videos/sequence/${
window.innerWidth < 768 ? "mobile" : "desktop"
window.innerWidth < 768 ? 'mobile' : 'desktop'
}/${Math.floor(currentIndex / FRAME_STEP) + 1}.mp4`}
autoPlay
muted
loop
playsInline
className="absolute object-cover w-full h-full"
className='absolute object-cover w-full h-full'
onLoad={handleLoadVideo}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
@@ -166,54 +166,59 @@ function SequenceSlider({ complexName }: SequenceSliderProps) {
</AnimatePresence>
{imageLoaded === FRAME_COUNT && (
<>
<div className="absolute flex 2xl:gap-[0.556vw] justify-between gap-2 2xl:left-[2.222vw] 2xl:right-[2.222vw] 2xl:top-[2.222vw] max-w-full md:max-2xl:left-6 md:max-2xl:right-6 md:max-2xl:top-6 left-4 right-4 top-4">
<div className='absolute flex 2xl:gap-[0.556vw] justify-between gap-2 2xl:left-[2.222vw] 2xl:right-[2.222vw] 2xl:top-[2.222vw] max-w-full md:max-2xl:left-6 md:max-2xl:right-6 md:max-2xl:top-6 left-4 right-4 top-4'>
<Button
variant="secondary"
className="!bg-white"
onClick={() => navigate("/")}
variant='secondary'
className='!bg-white'
onClick={() => navigate('/')}
>
<span className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5">
<span className='2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5'>
<ArrowLeftIcon />
</span>
<span className="max-md:hidden">Map</span>
<span className='max-md:hidden'>Map</span>
</Button>
<Button variant="secondary" size="small" className="max-md:hidden">
<span className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5">
<Button
variant='secondary'
size='small'
className='max-md:hidden'
onClick={() => navigate('./about')}
>
<span className='2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5'>
<InfoIcon />
</span>
<span>About</span>
</Button>
</div>
<p className="absolute text-xl font-bold text-white -translate-x-1/2 select-none left-1/2 top-9 max-md:hidden">
<p className='absolute text-xl font-bold text-white -translate-x-1/2 select-none left-1/2 top-9 max-md:hidden'>
ROVE Home Marasi Drive
</p>
<Button
onlyIcon
variant="secondary"
className="absolute top-1/2 -translate-y-1/2 2xl:left-[31.111vw] md:max-2xl:left-[8.854vw] left-4 !bg-[#0D1922]/40 backdrop-blur-md"
variant='secondary'
className='absolute top-1/2 -translate-y-1/2 2xl:left-[31.111vw] md:max-2xl:left-[8.854vw] left-4 !bg-[#0D1922]/40 backdrop-blur-md'
roundedFull
disabled={isAnimating || !isShowVideo}
onClick={() => handleSwipe("prev")}
onClick={() => handleSwipe('prev')}
>
<span className="2xl:w-[1.111vw] 2xl:h-[1.111vw] w-4 h-4 text-white">
<span className='2xl:w-[1.111vw] 2xl:h-[1.111vw] w-4 h-4 text-white'>
<ArrowLeftIcon />
</span>
</Button>
<Button
onlyIcon
variant="secondary"
className="absolute top-1/2 -translate-y-1/2 2xl:right-[31.111vw] md:max-2xl:right-[8.854vw] right-4 !bg-[#0D1922]/40 backdrop-blur-md"
variant='secondary'
className='absolute top-1/2 -translate-y-1/2 2xl:right-[31.111vw] md:max-2xl:right-[8.854vw] right-4 !bg-[#0D1922]/40 backdrop-blur-md'
roundedFull
disabled={isAnimating || !isShowVideo}
onClick={() => handleSwipe("next")}
onClick={() => handleSwipe('next')}
>
<span className="2xl:w-[1.111vw] 2xl:h-[1.111vw] w-4 h-4 text-white">
<span className='2xl:w-[1.111vw] 2xl:h-[1.111vw] w-4 h-4 text-white'>
<ArrowRightIcon />
</span>
</Button>
<Compass imgStyle={{ transform: `rotate(${currentIndex}deg)` }} />
<div className="absolute 2xl:bottom-[2.222vw] 2xl:right-[2.222vw] 2xl:left-[2.222vw] max-w-full flex justify-end items-center 2xl:gap-[0.556vw] gap-2 md:max-2xl:bottom-6 md:max-2xl:left-6 md:max-2xl:right-6 bottom-4 left-4 right-4">
<div className='absolute 2xl:bottom-[2.222vw] 2xl:right-[2.222vw] 2xl:left-[2.222vw] max-w-full flex justify-end items-center 2xl:gap-[0.556vw] gap-2 md:max-2xl:bottom-6 md:max-2xl:left-6 md:max-2xl:right-6 bottom-4 left-4 right-4'>
<DisclaimerButton />
<PrivacyPolicyButton />
<FullScreenButton
+97
View File
@@ -0,0 +1,97 @@
import { useState } from 'react';
import ArrowLeftIcon from './icons/ArrowLeftIcon';
import ArrowRightIcon from './icons/map/ArrowRightIcon';
import { dubaiMarinaWellnessSlides } from '../data/aboutDubaiMarina';
import { AnimatePresence, motion } from 'motion/react';
function Slider() {
const [currentSlide, setCurrentSlide] = useState(0);
const [direction, setDirection] = useState(-1);
const handleNextSlide = () => {
if (currentSlide < dubaiMarinaWellnessSlides.length - 1) {
setDirection(1);
setCurrentSlide(currentSlide + 1);
}
};
const handlePreviousSlide = () => {
if (currentSlide > 0) {
setDirection(-1);
setCurrentSlide(currentSlide - 1);
}
};
return (
<div className='relative w-[63.333vw] h-[27.639vw] rounded-3xl overflow-hidden'>
<AnimatePresence custom={direction}>
<motion.div
key={currentSlide}
className='absolute inset-0 bg-cover bg-no-repeat bg-center'
style={{
backgroundImage: `url(${dubaiMarinaWellnessSlides[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='relative z-10 w-full h-full flex flex-col justify-between p-6'>
<div className='flex flex-col gap-4'>
<AnimatePresence mode='wait'>
<motion.h3
key={`title-${currentSlide}`}
className='text-subheadline-m'
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
{dubaiMarinaWellnessSlides[currentSlide].title}
</motion.h3>
</AnimatePresence>
<AnimatePresence mode='wait'>
<motion.p
key={`desc-${currentSlide}`}
className='text-s w-[19.861vw] tracking-[-0.07em] leading-[140%]'
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
{dubaiMarinaWellnessSlides[currentSlide].description}
</motion.p>
</AnimatePresence>
</div>
<div className='flex items-center gap-[0.556vw] relative'>
<div
className='w-10 h-10 rounded-xl text-[#0D1922] bg-white flex items-center justify-center cursor-pointer hover:bg-[#F3F3F2] transition-all duration-200'
onClick={handlePreviousSlide}
>
<span className='w-5 h-5'>
<ArrowLeftIcon />
</span>
</div>
<div className='text-s'>
{currentSlide + 1}/{dubaiMarinaWellnessSlides.length}
</div>
<div
className='w-10 h-10 rounded-xl text-[#0D1922] bg-white flex items-center justify-center cursor-pointer hover:bg-[#F3F3F2] transition-all duration-200'
onClick={handleNextSlide}
>
<span className='w-5 h-5'>
<ArrowRightIcon />
</span>
</div>
</div>
</div>
</div>
);
}
export default Slider;
+9
View File
@@ -0,0 +1,9 @@
function TextBox({ text }: { text: string }) {
return (
<div className='text-caption-m px-4 py-1.5 border border-[#E2E2DC] rounded-[40px] w-fit text-[#0D1922]'>
{text}
</div>
);
}
export default TextBox;
+20
View File
@@ -0,0 +1,20 @@
function EqualIcon() {
return (
<svg
viewBox='0 0 20 20'
fill='currentColor'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M16.6654 7.64815C16.6654 7.29019 16.3752 7 16.0172 7H10.647H3.98018C3.62222 7 3.33203 7.29019 3.33203 7.64815C3.33203 8.00611 3.62222 8.2963 3.98018 8.2963H10.647H16.0172C16.3752 8.2963 16.6654 8.00611 16.6654 7.64815Z'
fill='currentColor'
/>
<path
d='M16.6654 12.2966C16.6654 11.9386 16.3752 11.6484 16.0172 11.6484H10.647H3.98018C3.62222 11.6484 3.33203 11.9386 3.33203 12.2966C3.33203 12.6545 3.62222 12.9447 3.98018 12.9447H10.647H16.0172C16.3752 12.9447 16.6654 12.6545 16.6654 12.2966Z'
fill='currentColor'
/>
</svg>
);
}
export default EqualIcon;