feat: add about page for dubai-marina
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user