|
|
@@ -4,44 +4,41 @@ import Sidebar from '@/src/components/Sidebar.tsx';
|
|
|
import Footer from '@/src/components/Footer.tsx';
|
|
|
import { CONTENT } from '../constants.ts';
|
|
|
import { useLanguage } from '@/src/contexts/LanguageContext.tsx';
|
|
|
-import { useTheme } from '@/src/contexts/ThemeContext.tsx';
|
|
|
import { useLocation } from 'react-router-dom';
|
|
|
import { useEffect } from 'react';
|
|
|
+import { ExperienceItem } from '../types';
|
|
|
|
|
|
const CruiseExperience: React.FC = () => {
|
|
|
const { language } = useLanguage();
|
|
|
- const { itineraries } = useTheme();
|
|
|
const location = useLocation();
|
|
|
|
|
|
const t = CONTENT[language];
|
|
|
|
|
|
- // Get section from query parameters
|
|
|
- const section = new URLSearchParams(location.search).get('section');
|
|
|
-
|
|
|
// Scroll to section when location changes with better timing
|
|
|
useEffect(() => {
|
|
|
const params = new URLSearchParams(location.search);
|
|
|
const section = params.get('section');
|
|
|
|
|
|
- // Always scroll to top first if no section is specified
|
|
|
- if (!section) {
|
|
|
- window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
|
- } else {
|
|
|
- // Use setTimeout to ensure DOM is fully rendered before attempting scroll
|
|
|
- const timeoutId = setTimeout(() => {
|
|
|
+ const scrollToElement = () => {
|
|
|
+ // Always scroll to top first if no section is specified
|
|
|
+ if (!section) {
|
|
|
+ window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
|
+ } else {
|
|
|
const element = document.getElementById(`experience-${section}`);
|
|
|
if (element) {
|
|
|
element.scrollIntoView({
|
|
|
behavior: 'smooth',
|
|
|
- block: 'start',
|
|
|
- // inline: 'nearest'
|
|
|
+ block: 'start'
|
|
|
});
|
|
|
}
|
|
|
- }, 300);
|
|
|
-
|
|
|
- // Cleanup timeout to prevent memory leaks
|
|
|
- return () => clearTimeout(timeoutId);
|
|
|
- }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Use requestAnimationFrame to ensure DOM is fully rendered
|
|
|
+ const animationFrameId = requestAnimationFrame(scrollToElement);
|
|
|
+
|
|
|
+ // Cleanup animation frame to prevent memory leaks
|
|
|
+ return () => cancelAnimationFrame(animationFrameId);
|
|
|
}, [location.search]);
|
|
|
|
|
|
return (
|
|
|
@@ -52,15 +49,15 @@ const CruiseExperience: React.FC = () => {
|
|
|
{/* Hero Section */}
|
|
|
<section className="relative h-[60vh] md:h-[70vh] w-full overflow-hidden bg-vista-darkblue">
|
|
|
<div className="absolute inset-0 bg-gradient-to-br from-vista-darkblue/95 to-vista-darkblue/60 z-10"></div>
|
|
|
- <div className="absolute inset-0 bg-cover bg-center opacity-40" style={{ backgroundImage: `url(https://picsum.photos/1920/1080?random=25)` }}></div>
|
|
|
- <div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48cGF0aCBkPSJNMTAgMTBoMjB2MjBIMTB6TTMwIDEwaDIwdjIwSDMweiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjAuNSIvPjwvcGF0dGVybj48L3N2Zz4=')] opacity-10 z-20"></div>
|
|
|
+ <div className="absolute inset-0 bg-cover bg-center opacity-40" style={{ backgroundImage: `url(https://picsum.photos/1920/1080?random=25)` }} aria-hidden="true"></div>
|
|
|
+ <div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48cGF0aCBkPSJNMTAgMTBoMjB2MjBIMTB6TTMwIDEwaDIwdjIwSDMweiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjAuNSIvPjwvcGF0dGVybj48L3N2Zz4=')] opacity-10 z-20" aria-hidden="true"></div>
|
|
|
<div className="relative z-30 h-full flex flex-col justify-center items-center text-center px-4 py-12">
|
|
|
<span className="text-vista-gold uppercase tracking-widest text-xs sm:text-sm font-bold mb-4">{t.nav.menu[3]?.title || 'CRUISE EXPERIENCE'}</span>
|
|
|
<h1 className="text-3xl sm:text-4xl md:text-5xl lg:text-7xl font-serif text-white mb-4 sm:mb-6 leading-tight">{t.nav.menu[3]?.title || 'Cruise Experience'}</h1>
|
|
|
<p className="text-white/90 max-w-2xl sm:max-w-3xl text-base sm:text-lg md:text-xl mb-6 sm:mb-8 leading-relaxed">{language === 'zh' ? '享受豪华游轮带来的全方位优质体验' : 'Enjoy the comprehensive premium experience of luxury cruises'}</p>
|
|
|
- <a href="#experience-service" className="px-8 sm:px-10 py-3 sm:py-4 bg-vista-gold text-white font-bold tracking-widest uppercase text-xs sm:text-sm hover:bg-opacity-90 transition-all duration-300 shadow-lg hover:shadow-xl">
|
|
|
- {t.nav.book}
|
|
|
- </a>
|
|
|
+ <a href="#experience-service" className="px-8 sm:px-10 py-3 sm:py-4 bg-vista-gold text-white font-bold tracking-widest uppercase text-xs sm:text-sm hover:bg-opacity-90 transition-all duration-300 shadow-lg hover:shadow-xl" aria-label={`${t.nav.book} - ${t.shipsPage.experience.service.title}`}>
|
|
|
+ {t.nav.book}
|
|
|
+ </a>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
@@ -78,13 +75,13 @@ const CruiseExperience: React.FC = () => {
|
|
|
</p>
|
|
|
</div>
|
|
|
|
|
|
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
|
|
|
- {t.shipsPage.experience.service.items.map((item: any, index: number) => (
|
|
|
- <div key={index} className="bg-white rounded-lg p-8 shadow-md hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-vista-gold transform hover:-translate-y-2">
|
|
|
- <div className="w-16 h-16 bg-vista-gold rounded-full flex items-center justify-center mb-6">
|
|
|
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10" role="list">
|
|
|
+ {t.shipsPage.experience.service.items.map((item: ExperienceItem, index: number) => (
|
|
|
+ <div key={index} className="bg-white rounded-lg p-8 shadow-md hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-vista-gold transform hover:-translate-y-2" role="listitem" aria-labelledby={`service-item-title-${index}`}>
|
|
|
+ <div className="w-16 h-16 bg-vista-gold rounded-full flex items-center justify-center mb-6" aria-hidden="true">
|
|
|
<span className="text-white text-2xl font-bold">{index + 1}</span>
|
|
|
</div>
|
|
|
- <h3 className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
|
|
|
+ <h3 id={`service-item-title-${index}`} className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
|
|
|
<p className="text-vista-darkblue/70 leading-relaxed">
|
|
|
{item.desc}
|
|
|
</p>
|
|
|
@@ -108,14 +105,14 @@ const CruiseExperience: React.FC = () => {
|
|
|
</p>
|
|
|
</div>
|
|
|
|
|
|
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
|
|
|
- {t.shipsPage.experience.dining.items.map((item: any, index: number) => (
|
|
|
- <div key={index} className="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
|
|
|
- <div className="h-48 bg-gradient-to-br from-vista-darkblue to-vista-gold flex items-center justify-center">
|
|
|
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10" role="list">
|
|
|
+ {t.shipsPage.experience.dining.items.map((item: ExperienceItem, index: number) => (
|
|
|
+ <div key={index} className="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2" role="listitem" aria-labelledby={`dining-item-title-${index}`}>
|
|
|
+ <div className="h-48 bg-gradient-to-br from-vista-darkblue to-vista-gold flex items-center justify-center" aria-hidden="true">
|
|
|
<span className="text-white text-4xl">🍽️</span>
|
|
|
</div>
|
|
|
<div className="p-8">
|
|
|
- <h3 className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
|
|
|
+ <h3 id={`dining-item-title-${index}`} className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
|
|
|
<p className="text-vista-darkblue/70 leading-relaxed">
|
|
|
{item.desc}
|
|
|
</p>
|
|
|
@@ -140,13 +137,13 @@ const CruiseExperience: React.FC = () => {
|
|
|
</p>
|
|
|
</div>
|
|
|
|
|
|
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
|
|
|
- {t.shipsPage.experience.activities.items.map((item: any, index: number) => (
|
|
|
- <div key={index} className="bg-white rounded-lg p-8 shadow-md hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-vista-gold transform hover:-translate-y-2">
|
|
|
- <div className="w-16 h-16 bg-vista-gold rounded-full flex items-center justify-center mb-6">
|
|
|
- <span className="text-white text-2xl">�</span>
|
|
|
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10" role="list">
|
|
|
+ {t.shipsPage.experience.activities.items.map((item: ExperienceItem, index: number) => (
|
|
|
+ <div key={index} className="bg-white rounded-lg p-8 shadow-md hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-vista-gold transform hover:-translate-y-2" role="listitem" aria-labelledby={`activity-item-title-${index}`}>
|
|
|
+ <div className="w-16 h-16 bg-vista-gold rounded-full flex items-center justify-center mb-6" aria-hidden="true">
|
|
|
+ <span className="text-white text-2xl">⚓</span>
|
|
|
</div>
|
|
|
- <h3 className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
|
|
|
+ <h3 id={`activity-item-title-${index}`} className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
|
|
|
<p className="text-vista-darkblue/70 leading-relaxed">
|
|
|
{item.desc}
|
|
|
</p>
|
|
|
@@ -161,4 +158,4 @@ const CruiseExperience: React.FC = () => {
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-export default CruiseExperience;
|
|
|
+export default React.memo(CruiseExperience);
|