Replace main page with contact page
Build And Push Image / docker (push) Successful in 2m2s
Details
Build And Push Image / docker (push) Successful in 2m2s
Details
- Contact page is now the homepage - Removed simple landing page - All animations and optimizations included
This commit is contained in:
parent
d1c6e87225
commit
90dbc75123
690
src/app/page.tsx
690
src/app/page.tsx
|
|
@ -1,18 +1,686 @@
|
||||||
import Link from 'next/link';
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { useMediaQuery } from '@react-hook/media-query';
|
||||||
|
import { ChevronDown, Phone, Mail } from 'lucide-react';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
} from '@/components/ui/form';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
firstName: z.string().min(1, 'First name is required'),
|
||||||
|
lastName: z.string().min(1, 'Last name is required'),
|
||||||
|
email: z.string().email('Invalid email address'),
|
||||||
|
phone: z.string().min(1, 'Phone is required'),
|
||||||
|
message: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [contactTop, setContactTop] = useState(0);
|
||||||
|
const [windowHeight, setWindowHeight] = useState(0);
|
||||||
|
|
||||||
|
// Animation values as refs to avoid re-renders
|
||||||
|
const logoPositionRef = useRef('center');
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
const chevronRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const contactSectionRef = useRef<HTMLDivElement>(null);
|
||||||
|
const logoRef = useRef<HTMLDivElement>(null);
|
||||||
|
const animationFrameRef = useRef<number | null>(null);
|
||||||
|
const lastScrollY = useRef(0);
|
||||||
|
const lastFrameTime = useRef(0);
|
||||||
|
const ticking = useRef(false);
|
||||||
|
|
||||||
|
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||||
|
const isDesktop = useMediaQuery("(min-width: 1280px)");
|
||||||
|
|
||||||
|
// Manage form state
|
||||||
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
message: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||||
|
console.log(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memoized logo dimensions based on screen size
|
||||||
|
const logoWidth = useMemo(() =>
|
||||||
|
isMobile ? 240 : isDesktop ? 316 : 280,
|
||||||
|
[isMobile, isDesktop]
|
||||||
|
);
|
||||||
|
|
||||||
|
const logoHeight = useMemo(() =>
|
||||||
|
isMobile ? 115 : isDesktop ? 151 : 134,
|
||||||
|
[isMobile, isDesktop]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Memoized animation constants
|
||||||
|
const animationConstants = useMemo(() => ({
|
||||||
|
mobile: {
|
||||||
|
targetTopPosition: 10,
|
||||||
|
startYFactor: 0.30, // Moved up from 0.35 to account for single-line button
|
||||||
|
finalScale: 0.5,
|
||||||
|
fadeThreshold: 5
|
||||||
|
},
|
||||||
|
desktop: {
|
||||||
|
targetTopPosition: 20,
|
||||||
|
logoSpeed: 0.4,
|
||||||
|
maxScroll: 500,
|
||||||
|
scaleReduction: 0.6,
|
||||||
|
fadeThreshold: 10
|
||||||
|
}
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
// Set initial dimensions on mount
|
||||||
|
setWindowHeight(window.innerHeight);
|
||||||
|
if (contactSectionRef.current) {
|
||||||
|
setContactTop(contactSectionRef.current.offsetTop);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Helper function to update element styles directly
|
||||||
|
const updateElementStyle = (element: HTMLElement | null, styles: Partial<CSSStyleDeclaration>) => {
|
||||||
|
if (!element) return;
|
||||||
|
Object.assign(element.style, styles);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Separated mobile animation logic
|
||||||
|
const calculateMobileLogoStyles = (
|
||||||
|
scrollY: number,
|
||||||
|
windowHeight: number,
|
||||||
|
contactTop: number,
|
||||||
|
logoHeight: number
|
||||||
|
): Partial<CSSStyleDeclaration> => {
|
||||||
|
const { targetTopPosition, startYFactor, finalScale } = animationConstants.mobile;
|
||||||
|
const startY = windowHeight * startYFactor;
|
||||||
|
const endY = targetTopPosition + (logoHeight * finalScale) / 2;
|
||||||
|
const totalDistance = startY - endY;
|
||||||
|
const animationEndScroll = contactTop;
|
||||||
|
|
||||||
|
if (scrollY >= animationEndScroll) {
|
||||||
|
logoPositionRef.current = 'top';
|
||||||
|
const scrollPastEnd = scrollY - animationEndScroll;
|
||||||
|
const fixedY = endY - scrollPastEnd;
|
||||||
|
const mobileScale = finalScale;
|
||||||
|
|
||||||
|
return {
|
||||||
|
position: 'fixed',
|
||||||
|
top: `${fixedY}px`,
|
||||||
|
left: '50%',
|
||||||
|
transform: `translate3d(-50%, 0, 0) scale3d(${mobileScale}, ${mobileScale}, 1) translateZ(0)`,
|
||||||
|
transformOrigin: 'center',
|
||||||
|
willChange: scrollY > 0 ? 'transform' : 'auto',
|
||||||
|
backfaceVisibility: 'hidden',
|
||||||
|
transition: 'none',
|
||||||
|
zIndex: '50'
|
||||||
|
};
|
||||||
|
} else if (scrollY > 0) {
|
||||||
|
logoPositionRef.current = 'animating';
|
||||||
|
const progress = scrollY / animationEndScroll;
|
||||||
|
const currentY = startY - (totalDistance * progress);
|
||||||
|
const mobileScale = 1 - ((1 - finalScale) * progress);
|
||||||
|
|
||||||
|
return {
|
||||||
|
position: 'fixed',
|
||||||
|
top: `${currentY}px`,
|
||||||
|
left: '50%',
|
||||||
|
transform: `translate3d(-50%, 0, 0) scale3d(${mobileScale}, ${mobileScale}, 1) translateZ(0)`,
|
||||||
|
transformOrigin: 'center',
|
||||||
|
willChange: 'transform',
|
||||||
|
backfaceVisibility: 'hidden',
|
||||||
|
transition: 'none',
|
||||||
|
zIndex: '50'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
logoPositionRef.current = 'center';
|
||||||
|
return {
|
||||||
|
position: 'fixed',
|
||||||
|
top: `${startY}px`,
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate3d(-50%, 0, 0) scale3d(1, 1, 1) translateZ(0)',
|
||||||
|
transformOrigin: 'center',
|
||||||
|
willChange: 'auto',
|
||||||
|
backfaceVisibility: 'hidden',
|
||||||
|
transition: 'none',
|
||||||
|
zIndex: '50'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Separated desktop animation logic
|
||||||
|
const calculateDesktopLogoStyles = (
|
||||||
|
scrollY: number,
|
||||||
|
windowHeight: number,
|
||||||
|
logoHeight: number
|
||||||
|
): Partial<CSSStyleDeclaration> => {
|
||||||
|
const { targetTopPosition, logoSpeed, maxScroll, scaleReduction } = animationConstants.desktop;
|
||||||
|
const centerY = windowHeight / 2;
|
||||||
|
const totalDistance = centerY - targetTopPosition - logoHeight / 2;
|
||||||
|
const logoYPosition = -(scrollY * logoSpeed);
|
||||||
|
const maxUpwardMovement = -totalDistance;
|
||||||
|
const animatedY = Math.max(logoYPosition, maxUpwardMovement);
|
||||||
|
|
||||||
|
const scrollProgress = Math.min(scrollY / maxScroll, 1);
|
||||||
|
const scale = 1 - (scaleReduction * scrollProgress);
|
||||||
|
|
||||||
|
logoPositionRef.current = scrollY > 10 ? 'animating' : 'center';
|
||||||
|
|
||||||
|
return {
|
||||||
|
position: 'fixed',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: `translate3d(-50%, calc(-50% + ${animatedY}px), 0) scale3d(${scale}, ${scale}, 1) translateZ(0)`,
|
||||||
|
transformOrigin: 'center',
|
||||||
|
willChange: scrollY > 0 ? 'transform' : 'auto',
|
||||||
|
backfaceVisibility: 'hidden',
|
||||||
|
transition: 'none',
|
||||||
|
zIndex: '50'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update button and chevron opacity
|
||||||
|
const updateControlsOpacity = (scrollY: number, isMobile: boolean) => {
|
||||||
|
const fadeThreshold = isMobile ? animationConstants.mobile.fadeThreshold : animationConstants.desktop.fadeThreshold;
|
||||||
|
const opacity = scrollY > fadeThreshold ? 0 : 1;
|
||||||
|
|
||||||
|
if (buttonRef.current) {
|
||||||
|
buttonRef.current.style.opacity = String(opacity);
|
||||||
|
buttonRef.current.style.pointerEvents = opacity > 0 ? 'auto' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chevronRef.current) {
|
||||||
|
chevronRef.current.style.opacity = String(opacity);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLogoPosition = (timestamp: number = performance.now()) => {
|
||||||
|
// Throttle to 60fps (16ms minimum between frames)
|
||||||
|
if (timestamp - lastFrameTime.current < 16) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastFrameTime.current = timestamp;
|
||||||
|
|
||||||
|
if (!contactSectionRef.current) return;
|
||||||
|
|
||||||
|
const scrollY = window.scrollY;
|
||||||
|
const currentWindowHeight = window.innerHeight;
|
||||||
|
const currentContactTop = contactSectionRef.current.offsetTop;
|
||||||
|
|
||||||
|
// Update cached values if needed
|
||||||
|
if (currentContactTop !== contactTop) {
|
||||||
|
setContactTop(currentContactTop);
|
||||||
|
}
|
||||||
|
if (currentWindowHeight !== windowHeight) {
|
||||||
|
setWindowHeight(currentWindowHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and apply logo styles based on device type
|
||||||
|
const logoStyles = isMobile
|
||||||
|
? calculateMobileLogoStyles(scrollY, currentWindowHeight, currentContactTop, logoHeight)
|
||||||
|
: calculateDesktopLogoStyles(scrollY, currentWindowHeight, logoHeight);
|
||||||
|
|
||||||
|
updateElementStyle(logoRef.current, logoStyles);
|
||||||
|
|
||||||
|
// Update controls opacity
|
||||||
|
updateControlsOpacity(scrollY, isMobile);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Animated scroll to form
|
||||||
|
const scrollToForm = () => {
|
||||||
|
if (!contactSectionRef.current) {
|
||||||
|
console.error('Contact section ref not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Starting scroll animation to:', contactSectionRef.current.offsetTop);
|
||||||
|
|
||||||
|
// Use native smooth scrolling
|
||||||
|
window.scrollTo({
|
||||||
|
top: contactSectionRef.current.offsetTop,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add scroll listener for bidirectional animation with RAF
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
lastScrollY.current = window.scrollY;
|
||||||
|
|
||||||
|
if (!ticking.current) {
|
||||||
|
ticking.current = true;
|
||||||
|
animationFrameRef.current = requestAnimationFrame((timestamp) => {
|
||||||
|
updateLogoPosition(timestamp);
|
||||||
|
ticking.current = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
setWindowHeight(window.innerHeight);
|
||||||
|
if (contactSectionRef.current) {
|
||||||
|
setContactTop(contactSectionRef.current.offsetTop);
|
||||||
|
}
|
||||||
|
updateLogoPosition();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial position update
|
||||||
|
updateLogoPosition();
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (animationFrameRef.current) {
|
||||||
|
cancelAnimationFrame(animationFrameRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isMobile, isDesktop, logoHeight, windowHeight]);
|
||||||
|
|
||||||
|
// Store if initial position has been set
|
||||||
|
const initialPositionSet = useRef(false);
|
||||||
|
|
||||||
|
// Calculate initial logo position to prevent teleport and flicker
|
||||||
|
const initialLogoStyle = useMemo(() => {
|
||||||
|
// Only set initial styles once and before JS takes over
|
||||||
|
if (!mounted || initialPositionSet.current) return {};
|
||||||
|
|
||||||
|
// For mobile, position at the calculated start position
|
||||||
|
if (isMobile && windowHeight > 0) {
|
||||||
|
const initialTop = windowHeight * animationConstants.mobile.startYFactor;
|
||||||
|
initialPositionSet.current = true;
|
||||||
|
|
||||||
|
return {
|
||||||
|
position: 'fixed' as const,
|
||||||
|
top: `${initialTop}px`,
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate3d(-50%, 0, 0) translateZ(0)',
|
||||||
|
willChange: 'auto',
|
||||||
|
backfaceVisibility: 'hidden' as const,
|
||||||
|
zIndex: 50
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// For desktop, center the logo properly
|
||||||
|
if (!isMobile && mounted) {
|
||||||
|
initialPositionSet.current = true;
|
||||||
|
|
||||||
|
return {
|
||||||
|
position: 'fixed' as const,
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate3d(-50%, -50%, 0) translateZ(0)',
|
||||||
|
willChange: 'auto',
|
||||||
|
backfaceVisibility: 'hidden' as const,
|
||||||
|
zIndex: 50
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}, [isMobile, mounted, windowHeight, animationConstants.mobile.startYFactor]);
|
||||||
|
|
||||||
|
// Don't render until mounted to avoid hydration mismatch
|
||||||
|
if (!mounted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#1b233b] flex items-center justify-center">
|
<div className="w-full bg-[#1b233b]">
|
||||||
<div className="text-center">
|
{/* Hero Section - Full Viewport Height */}
|
||||||
<h1 className="text-6xl font-['Palatino',_serif] text-[#C6AE97] mb-4">PORT AMADOR</h1>
|
<section className="relative h-screen flex flex-col items-center justify-center">
|
||||||
<p className="text-xl text-white font-['bill_corporate_medium'] font-light mb-8">PANAMA</p>
|
{/* Single Port Amador Logo with dynamic positioning - Always rendered */}
|
||||||
<Link
|
<div
|
||||||
href="/contact"
|
ref={logoRef}
|
||||||
className="inline-block bg-[#C6AE97] text-[#1B233B] px-8 py-3 rounded-[5px] font-['bill_corporate_medium'] font-medium text-lg hover:bg-[#D4C1AC] transition-colors"
|
className="z-50"
|
||||||
|
style={initialLogoStyle}
|
||||||
>
|
>
|
||||||
VIEW CONTACT PAGE
|
<Image
|
||||||
</Link>
|
src="/logo.png"
|
||||||
</div>
|
alt="Port Amador"
|
||||||
|
width={logoWidth}
|
||||||
|
height={logoHeight}
|
||||||
|
priority
|
||||||
|
style={{
|
||||||
|
width: `${logoWidth}px`,
|
||||||
|
height: `${logoHeight}px`,
|
||||||
|
objectFit: 'contain'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Button with fade out on scroll */}
|
||||||
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
onClick={scrollToForm}
|
||||||
|
className={`fixed z-30 px-6 py-3 bg-[#C6AE97] text-[#1B233B] font-['bill_corporate_medium'] font-normal text-sm md:text-base uppercase tracking-wider rounded-md hover:bg-[#D4C1AC] transition-colors whitespace-nowrap`}
|
||||||
|
style={{
|
||||||
|
bottom: '120px',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
transition: 'opacity 0.3s ease-out'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
CONNECT WITH US
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Chevron Down - with fade out on scroll */}
|
||||||
|
<div
|
||||||
|
ref={chevronRef}
|
||||||
|
className="fixed z-20"
|
||||||
|
style={{
|
||||||
|
bottom: '40px',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
transition: 'opacity 0.3s ease-out'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChevronDown
|
||||||
|
className="text-[#C6AE97] animate-bounce"
|
||||||
|
size={32}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Contact Section - Desktop Layout with Marina Image */}
|
||||||
|
<section
|
||||||
|
ref={contactSectionRef}
|
||||||
|
className="w-full relative"
|
||||||
|
>
|
||||||
|
{isMobile ? (
|
||||||
|
// Mobile Layout - Stacked with Image
|
||||||
|
<div className="flex flex-col min-h-screen pt-[150px]">
|
||||||
|
{/* Form Section */}
|
||||||
|
<div className="px-8 pb-12">
|
||||||
|
<h2 className="font-['Palatino',_serif] text-[#C6AE97] text-[40px] mb-8 font-normal text-center">
|
||||||
|
Connect with us
|
||||||
|
</h2>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="firstName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="First Name*"
|
||||||
|
{...field}
|
||||||
|
className="bg-transparent border-b border-t-0 border-l-0 border-r-0 border-white/60 text-white placeholder:text-white/70 focus:border-white rounded-none px-0 py-2 font-['bill_corporate_medium'] font-light text-[16px]"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="lastName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Last Name*"
|
||||||
|
{...field}
|
||||||
|
className="bg-transparent border-b border-t-0 border-l-0 border-r-0 border-white/60 text-white placeholder:text-white/70 focus:border-white rounded-none px-0 py-2 font-['bill_corporate_medium'] font-light text-[16px]"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Email*"
|
||||||
|
type="email"
|
||||||
|
{...field}
|
||||||
|
className="bg-transparent border-b border-t-0 border-l-0 border-r-0 border-white/60 text-white placeholder:text-white/70 focus:border-white rounded-none px-0 py-2 font-['bill_corporate_medium'] font-light text-[16px]"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="phone"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Phone number*"
|
||||||
|
{...field}
|
||||||
|
className="bg-transparent border-b border-t-0 border-l-0 border-r-0 border-white/60 text-white placeholder:text-white/70 focus:border-white rounded-none px-0 py-2 font-['bill_corporate_medium'] font-light text-[16px]"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="message"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="Message"
|
||||||
|
{...field}
|
||||||
|
className="min-h-[80px] bg-transparent border-b border-t-0 border-l-0 border-r-0 border-white/60 text-white placeholder:text-white/70 focus:border-white rounded-none px-0 py-2 font-['bill_corporate_medium'] font-light text-[16px] resize-none"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="pt-6">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-[#C6AE97] text-[#1B233B] hover:bg-[#D4C1AC] font-['bill_corporate_medium'] font-medium text-[16px] uppercase tracking-wider py-3 rounded-[5px]"
|
||||||
|
>
|
||||||
|
SUBMIT
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Marina Image Section */}
|
||||||
|
<div className="relative w-full h-[300px] mt-8 px-4">
|
||||||
|
<div className="relative w-full h-full">
|
||||||
|
<Image
|
||||||
|
src="/marina.png"
|
||||||
|
alt="Port Amador Marina"
|
||||||
|
fill
|
||||||
|
className="object-cover object-center"
|
||||||
|
sizes="(max-width: 768px) 90vw, 100vw"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer Section */}
|
||||||
|
<div className="px-8 py-8 mt-auto">
|
||||||
|
<div className="flex justify-between items-end">
|
||||||
|
<div className="flex flex-col space-y-0">
|
||||||
|
<a href="tel:+13109132597" className="font-['bill_corporate_medium'] font-light text-[14px] text-[#C6AE97] hover:text-[#D4C1AC] transition-colors">
|
||||||
|
+1 310 913 2597
|
||||||
|
</a>
|
||||||
|
<a href="mailto:am@portamador.com" className="font-['bill_corporate_medium'] font-light text-[14px] text-[#C6AE97] hover:text-[#D4C1AC] transition-colors">
|
||||||
|
am@portamador.com
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-[#C6AE97] text-[14px] font-['bill_corporate_medium'] font-light">
|
||||||
|
© Port Amador 2025
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// Desktop Layout - Matching Figma exactly
|
||||||
|
<div className="min-h-screen flex flex-col relative pt-[200px]">
|
||||||
|
<div className="w-full max-w-[1600px] mx-auto flex items-stretch flex-1">
|
||||||
|
{/* Left Side - Marina Image - Aligned with form content */}
|
||||||
|
<div className="w-[45%] relative">
|
||||||
|
<div className="absolute top-0 bottom-0 left-[80px] right-[40px]">
|
||||||
|
<Image
|
||||||
|
src="/marina.png"
|
||||||
|
alt="Port Amador Marina"
|
||||||
|
fill
|
||||||
|
className="object-cover object-center"
|
||||||
|
sizes="45vw"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Side - Form Section */}
|
||||||
|
<div className="w-[55%] flex flex-col justify-center py-[40px] pl-[40px] pr-[80px]">
|
||||||
|
{/* Heading */}
|
||||||
|
<h2 className="font-['Palatino',_serif] text-[#C6AE97] text-[72px] leading-none mb-12 font-normal">
|
||||||
|
Connect with us
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||||
|
{/* First Row - First Name and Last Name */}
|
||||||
|
<div className="grid grid-cols-2 gap-6">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="firstName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="First Name*"
|
||||||
|
className="bg-transparent border-b border-t-0 border-l-0 border-r-0 border-white/60 text-white placeholder:text-white/70 focus:border-white rounded-none px-0 pb-1 pt-0 font-['bill_corporate_medium'] font-light text-[16px] focus:outline-none focus:ring-0"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="lastName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="Last Name*"
|
||||||
|
className="bg-transparent border-b border-t-0 border-l-0 border-r-0 border-white/60 text-white placeholder:text-white/70 focus:border-white rounded-none px-0 pb-1 pt-0 font-['bill_corporate_medium'] font-light text-[16px] focus:outline-none focus:ring-0"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Second Row - Email and Phone */}
|
||||||
|
<div className="grid grid-cols-2 gap-6">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
{...field}
|
||||||
|
placeholder="Email*"
|
||||||
|
className="bg-transparent border-b border-t-0 border-l-0 border-r-0 border-white/60 text-white placeholder:text-white/70 focus:border-white rounded-none px-0 pb-1 pt-0 font-['bill_corporate_medium'] font-light text-[16px] focus:outline-none focus:ring-0"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="phone"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="Phone number*"
|
||||||
|
className="bg-transparent border-b border-t-0 border-l-0 border-r-0 border-white/60 text-white placeholder:text-white/70 focus:border-white rounded-none px-0 pb-1 pt-0 font-['bill_corporate_medium'] font-light text-[16px] focus:outline-none focus:ring-0"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Message Field */}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="message"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
{...field}
|
||||||
|
placeholder="Message"
|
||||||
|
className="min-h-[60px] bg-transparent border-b border-t-0 border-l-0 border-r-0 border-white/60 text-white placeholder:text-white/70 focus:border-white rounded-none px-0 pb-1 pt-0 font-['bill_corporate_medium'] font-light text-[16px] resize-none focus:outline-none focus:ring-0"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<div className="pt-4">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-[#C6AE97] text-[#1B233B] hover:bg-[#D4C1AC] font-['bill_corporate_medium'] font-medium text-[18px] uppercase tracking-[0.05em] h-[50px] rounded-[3px]"
|
||||||
|
>
|
||||||
|
SUBMIT
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer - at bottom of page */}
|
||||||
|
<div className="w-full max-w-[1600px] mx-auto px-[80px] pb-8 pt-12">
|
||||||
|
<div className="flex justify-between items-end">
|
||||||
|
<div className="flex flex-col space-y-0 text-[#C6AE97]">
|
||||||
|
<a href="tel:+13109132597" className="font-['bill_corporate_medium'] font-light text-[14px] hover:text-[#D4C1AC] transition-colors">
|
||||||
|
+1 310 913 2597
|
||||||
|
</a>
|
||||||
|
<a href="mailto:am@portamador.com" className="font-['bill_corporate_medium'] font-light text-[14px] hover:text-[#D4C1AC] transition-colors">
|
||||||
|
am@portamador.com
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-[#C6AE97] text-[14px] font-['bill_corporate_medium'] font-light">
|
||||||
|
© Port Amador 2025
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue