@ticketboothapp/booking 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/BookingWidget.tsx +282 -26
- package/src/components/ManageBookingView.tsx +75 -23
- package/src/components/booking/BookingProductGrid.tsx +1 -1
- package/src/components/booking/Calendar.module.css +3 -3
- package/src/components/booking/CheckoutForm.tsx +1 -1
- package/src/components/booking/InfoTooltip.tsx +2 -13
- package/src/components/booking/PickupLocationSelector.tsx +2 -2
- package/src/components/booking/PriceBreakdown.tsx +11 -34
- package/src/index.ts +3 -1
- package/src/app/photo-sessions/photo-packages.ts +0 -75
- package/src/assets/icons/minus.svg +0 -7
- package/src/assets/icons/partner-logos/getyourguide.svg +0 -8
- package/src/assets/icons/plus.svg +0 -3
- package/src/colours.css +0 -23
- package/src/components/BookingDetails.module.css +0 -1591
- package/src/components/BookingDetails.tsx +0 -2264
- package/src/components/JobApplicationDialog.module.css +0 -440
- package/src/components/JobApplicationDialog.tsx +0 -620
- package/src/components/PhoneInputWithCountry.module.css +0 -131
- package/src/components/PhoneInputWithCountry.tsx +0 -44
- package/src/components/PickupLocationDialog.module.css +0 -360
- package/src/components/PickupLocationDialog.tsx +0 -357
- package/src/components/PickupLocationMap.tsx +0 -110
- package/src/components/PostBookingDependentAddOnUpsell.module.css +0 -174
- package/src/components/PostBookingDependentAddOnUpsell.tsx +0 -407
- package/src/components/accordion.css +0 -27
- package/src/components/accordion.tsx +0 -29
- package/src/components/analytics/AnalyticsConsentRestore.tsx +0 -19
- package/src/components/analytics/AnalyticsScripts.tsx +0 -106
- package/src/components/analytics/CookieConsentBanner.css +0 -86
- package/src/components/analytics/CookieConsentBanner.tsx +0 -102
- package/src/components/bottom-sheet.module.css +0 -78
- package/src/components/bottom-sheet.tsx +0 -60
- package/src/components/breadcrumb.module.css +0 -40
- package/src/components/breadcrumb.tsx +0 -36
- package/src/components/button.css +0 -245
- package/src/components/button.tsx +0 -152
- package/src/components/client-bottom-sheet.tsx +0 -14
- package/src/components/colorable-svg.tsx +0 -29
- package/src/components/conditional-footer.tsx +0 -27
- package/src/components/contact-us.module.css +0 -147
- package/src/components/contact-us.tsx +0 -49
- package/src/components/email-signup.css +0 -151
- package/src/components/email-signup.tsx +0 -63
- package/src/components/faq-wrapper.module.css +0 -47
- package/src/components/faq-wrapper.tsx +0 -15
- package/src/components/footer.css +0 -187
- package/src/components/footer.tsx +0 -143
- package/src/components/global-simple-modal.tsx +0 -33
- package/src/components/google-review-summary.module.css +0 -77
- package/src/components/google-review-summary.tsx +0 -50
- package/src/components/hero-image.css +0 -13
- package/src/components/hero-image.tsx +0 -44
- package/src/components/image.css +0 -29
- package/src/components/image.tsx +0 -113
- package/src/components/language-aware-link.tsx +0 -72
- package/src/components/language-switcher.module.css +0 -124
- package/src/components/language-switcher.tsx +0 -75
- package/src/components/map-section.css +0 -59
- package/src/components/map-section.tsx +0 -63
- package/src/components/navbar.module.css +0 -152
- package/src/components/navbar.tsx +0 -125
- package/src/components/parallax-provider.tsx +0 -11
- package/src/components/product-tag.module.css +0 -30
- package/src/components/product-tag.tsx +0 -34
- package/src/components/product-theme-pages/best-option.module.css +0 -70
- package/src/components/product-theme-pages/best-option.tsx +0 -35
- package/src/components/product-theme-pages/extended-tour-options.module.css +0 -22
- package/src/components/product-theme-pages/extended-tour-options.tsx +0 -11
- package/src/components/product-theme-pages/image-modal.tsx +0 -248
- package/src/components/product-theme-pages/photo-gallery.module.css +0 -200
- package/src/components/product-theme-pages/photo-gallery.tsx +0 -90
- package/src/components/product-theme-pages/product-theme-page-layout.module.css +0 -13
- package/src/components/product-theme-pages/product-theme-page-layout.tsx +0 -67
- package/src/components/product-theme-pages/top-of-fold.module.css +0 -179
- package/src/components/product-theme-pages/top-of-fold.tsx +0 -80
- package/src/components/product-tile/image-only-product-tile-desktop.module.css +0 -106
- package/src/components/product-tile/image-only-product-tile-desktop.tsx +0 -56
- package/src/components/product-tile/image-only-product-tile-mobile.module.css +0 -122
- package/src/components/product-tile/image-only-product-tile-mobile.tsx +0 -89
- package/src/components/product-tile/image-only-product-tile.tsx +0 -44
- package/src/components/product-tile/product-tile-card.module.css +0 -84
- package/src/components/product-tile/product-tile-card.tsx +0 -61
- package/src/components/review-highlights-section.css +0 -85
- package/src/components/review-highlights-section.tsx +0 -127
- package/src/components/season-closure-overlay.module.css +0 -99
- package/src/components/season-closure-overlay.tsx +0 -98
- package/src/components/simple-modal.tsx +0 -69
- package/src/components/simple-top-of-fold.module.css +0 -76
- package/src/components/simple-top-of-fold.tsx +0 -34
- package/src/components/spacer.css +0 -41
- package/src/components/spacer.tsx +0 -23
- package/src/components/star-rating.module.css +0 -74
- package/src/components/star-rating.tsx +0 -48
- package/src/components/terms/TermsContent.tsx +0 -178
- package/src/components/title-subtitle.module.css +0 -10
- package/src/components/title-subtitle.tsx +0 -30
- package/src/components/translatable-reviews.tsx +0 -75
- package/src/components/value-pill.module.css +0 -59
- package/src/components/value-pill.tsx +0 -46
- package/src/components/value-props.css +0 -185
- package/src/components/value-props.tsx +0 -88
- package/src/constants/booking-guide-quiz.ts +0 -64
- package/src/constants/contact-info.ts +0 -2
- package/src/constants/faq.ts +0 -44
- package/src/constants/images.ts +0 -556
- package/src/constants/json-ld/faq-json-ld.tsx +0 -170
- package/src/constants/json-ld/homepage-json-ld.tsx +0 -138
- package/src/constants/json-ld/job-posting-json-ld.tsx +0 -92
- package/src/constants/json-ld/organization-json-ld.tsx +0 -62
- package/src/constants/json-ld/page-json-ld.tsx +0 -6
- package/src/constants/json-ld/product-json-ld.tsx +0 -154
- package/src/constants/json-ld/review-json-ld.tsx +0 -377
- package/src/constants/navigation-links/footer-links.ts +0 -48
- package/src/constants/navigation-links/nav-bar-links.ts +0 -41
- package/src/constants/navigation-links/navigation-link.ts +0 -6
- package/src/constants/pill-values.ts +0 -210
- package/src/constants/products.ts +0 -155
- package/src/constants/quiz-recommendations.ts +0 -506
- package/src/constants/reviews.ts +0 -75
- package/src/constants/staff.ts +0 -197
- package/src/constants/value-props.ts +0 -58
- package/src/data/dap-descriptions/session-couples-families-friends.en.json +0 -61
- package/src/data/dap-descriptions/session-elopements.en.json +0 -60
- package/src/data/dap-descriptions/session-proposals.en.json +0 -60
- package/src/data/product-descriptions/afternoon-delight.en.json +0 -35
- package/src/data/product-descriptions/emerald-lake-escape.en.json +0 -68
- package/src/data/product-descriptions/lake-louise-adventure.en.json +0 -74
- package/src/data/product-descriptions/moraine-lake-adventure.en.json +0 -78
- package/src/data/product-descriptions/moraine-lake-sunrise-lake-louise-golden-hour.en.json +0 -65
- package/src/data/product-descriptions/moraine-lake-sunrise.en.json +0 -64
- package/src/data/product-descriptions/private-tour.en.json +0 -80
- package/src/data/product-descriptions/two-lakes-combo.en.json +0 -65
- package/src/data/products-config.json +0 -101
- package/src/hooks/use-bottom-sheet.tsx +0 -15
- package/src/hooks/use-simple-modal.tsx +0 -27
- package/src/hooks/useBookingSourceMetadataFromLocation.ts +0 -21
- package/src/hooks/useEmailSubscription.tsx +0 -103
- package/src/hooks/useEmbeddedInIframe.ts +0 -16
- package/src/hooks/useIsBookingLaunchLive.ts +0 -49
- package/src/hooks/useQuiz.tsx +0 -210
- package/src/providers/bottom-sheet-provider.tsx +0 -40
- package/src/providers/dependent-add-on-dialog-provider.tsx +0 -105
- package/src/radius.css +0 -5
- package/src/spacing.css +0 -7
- package/src/strings/en.json +0 -1774
- package/src/strings/es.json +0 -1573
- package/src/strings/fr.json +0 -1573
- package/src/strings/index.js +0 -23
- package/src/text-style.css +0 -97
- package/src/types/fareharbor.d.ts +0 -12
- package/src/types/quiz.ts +0 -59
- package/src/utils/currency-converter.ts +0 -101
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
.footer-logo-container {
|
|
2
|
-
display: flex;
|
|
3
|
-
align-items: center;
|
|
4
|
-
justify-content: center;
|
|
5
|
-
width: fit-content;
|
|
6
|
-
padding: 0 20px;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.footer-via-via-name {
|
|
10
|
-
display: flex;
|
|
11
|
-
align-items: center;
|
|
12
|
-
gap: 0.5rem;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.footer-logo-letter {
|
|
16
|
-
width: min(15vw, 60px);
|
|
17
|
-
height: min(15vw, 60px);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/* Target all SVG paths consistently */
|
|
21
|
-
.footer-logo-letter path {
|
|
22
|
-
fill: var(--accent-white) !important;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/* For hover states - match opacity across all letters */
|
|
26
|
-
.footer-container a:hover .footer-logo-letter path {
|
|
27
|
-
fill: var(--accent-white-70) !important; /* Back to using the CSS variable */
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/* Adjust sizes for mobile */
|
|
31
|
-
@media (max-width: 768px) {
|
|
32
|
-
.footer-logo-letter {
|
|
33
|
-
width: min(12vw, 50px);
|
|
34
|
-
height: min(12vw, 50px);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/* Further adjust for very small screens */
|
|
39
|
-
@media (max-width: 360px) {
|
|
40
|
-
.footer-logo-letter {
|
|
41
|
-
width: min(10vw, 40px);
|
|
42
|
-
height: min(10vw, 40px);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.footer-container {
|
|
47
|
-
background-color: var(--accent-orange);
|
|
48
|
-
display: flex;
|
|
49
|
-
flex-direction: column;
|
|
50
|
-
justify-content: center;
|
|
51
|
-
align-items: center;
|
|
52
|
-
padding: 1rem;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.footer-container p {
|
|
56
|
-
color: var(--accent-white);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.footer-container a {
|
|
60
|
-
color: var(--accent-white);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.footer-container .footer-icon {
|
|
64
|
-
width: 36px;
|
|
65
|
-
height: 36px;
|
|
66
|
-
color: var(--accent-white);
|
|
67
|
-
fill: var(--accent-white);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.footer-container .footer-icon:hover {
|
|
71
|
-
color: var(--accent-white-70);
|
|
72
|
-
fill: var(--accent-white-70);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/* Target SVG paths to override inline fill attributes */
|
|
76
|
-
.footer-container .footer-icon path {
|
|
77
|
-
fill: var(--accent-white) !important;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.footer-container .footer-icon:hover path {
|
|
81
|
-
fill: var(--accent-white-70) !important;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* Scale down Google G icon to match other social icons */
|
|
85
|
-
.footer-container a[href*="maps.app.goo.gl"] .footer-icon {
|
|
86
|
-
transform: scale(0.92);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/* Desktop hover */
|
|
90
|
-
@media (min-width: 1024px) {
|
|
91
|
-
.footer-container a:hover {
|
|
92
|
-
color: var(--accent-white-70);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/* Mobile styles */
|
|
97
|
-
@media screen and (max-width: 768px) {
|
|
98
|
-
.footer-container .button {
|
|
99
|
-
width: 70%;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.footer-links-container {
|
|
104
|
-
display: flex;
|
|
105
|
-
flex-wrap: wrap;
|
|
106
|
-
justify-content: center;
|
|
107
|
-
gap: 1rem;
|
|
108
|
-
width: 70%;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/* Mobile: strict 2 columns */
|
|
112
|
-
@media (max-width: 767px) {
|
|
113
|
-
.footer-links-container {
|
|
114
|
-
display: grid;
|
|
115
|
-
grid-template-columns: repeat(2, 1fr);
|
|
116
|
-
gap: 0.75rem;
|
|
117
|
-
width: 90%;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.footer-links-container a {
|
|
121
|
-
font-size: 0.8rem;
|
|
122
|
-
min-height: 2.5rem;
|
|
123
|
-
display: flex;
|
|
124
|
-
align-items: center;
|
|
125
|
-
justify-content: center;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/* Center the last item if it's alone */
|
|
129
|
-
.footer-links-container > a:last-child:nth-child(2n - 1) {
|
|
130
|
-
grid-column: 1 / -1;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/* Desktop: 5 columns */
|
|
135
|
-
@media (min-width: 768px) {
|
|
136
|
-
.footer-links-container {
|
|
137
|
-
display: flex;
|
|
138
|
-
gap: 2rem;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.footer-links-container a {
|
|
142
|
-
flex: 0 0 calc(20% - 1.6rem);
|
|
143
|
-
min-width: fit-content;
|
|
144
|
-
max-width: calc(20% - 1.6rem);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.footer-links-container a {
|
|
149
|
-
text-align: center;
|
|
150
|
-
white-space: normal;
|
|
151
|
-
text-decoration: none;
|
|
152
|
-
text-transform: uppercase;
|
|
153
|
-
font-family: 'Poppins', sans-serif;
|
|
154
|
-
color: var(--accent-white);
|
|
155
|
-
line-height: 1.2;
|
|
156
|
-
padding: 0.25rem;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.footer-company-text {
|
|
160
|
-
font-size: 0.75rem;
|
|
161
|
-
text-transform: uppercase;
|
|
162
|
-
text-align: center;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/* Mobile styles for company text */
|
|
166
|
-
@media (max-width: 768px) {
|
|
167
|
-
.footer-company-text {
|
|
168
|
-
font-size: 0.6rem;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.footer-moraine-magic-icon {
|
|
173
|
-
width: 60%;
|
|
174
|
-
max-width: 400px;
|
|
175
|
-
height: auto;
|
|
176
|
-
margin: 0 auto;
|
|
177
|
-
display: block;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/* Mobile styles for moraine magic icon */
|
|
181
|
-
@media (max-width: 768px) {
|
|
182
|
-
.footer-moraine-magic-icon {
|
|
183
|
-
width: 80%;
|
|
184
|
-
max-width: 500px;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import './footer.css';
|
|
4
|
-
import { getStrings } from '@/strings';
|
|
5
|
-
import FacebookIcon from '@/assets/icons/facebook.svg';
|
|
6
|
-
import InstagramIcon from '@/assets/icons/instagram.svg';
|
|
7
|
-
import TikTokIcon from '@/assets/icons/tiktok.svg';
|
|
8
|
-
import GoogleIcon from '@/assets/icons/google-g-white.svg';
|
|
9
|
-
import Spacer from './spacer';
|
|
10
|
-
import Button, { ButtonHoverColor } from './button';
|
|
11
|
-
import { getFooterLinks } from '@/constants/navigation-links/footer-links';
|
|
12
|
-
import VIcon from '@/assets/icons/viavia-v.svg';
|
|
13
|
-
import IIcon from '@/assets/icons/viavia-i.svg';
|
|
14
|
-
import AIcon from '@/assets/icons/viavia-a.svg';
|
|
15
|
-
import Link from 'next/link';
|
|
16
|
-
import LanguageAwareLink from './language-aware-link';
|
|
17
|
-
import { useBottomSheet } from '@/providers/bottom-sheet-provider';
|
|
18
|
-
import ContactUs from './contact-us';
|
|
19
|
-
import { useEffect, Suspense } from 'react';
|
|
20
|
-
import { usePathname, useSearchParams } from 'next/navigation';
|
|
21
|
-
import MoraineMagicIcon from '@/assets/moraine-magic-white.svg';
|
|
22
|
-
import EmailSignup from './email-signup';
|
|
23
|
-
|
|
24
|
-
function ContactHandler() {
|
|
25
|
-
const { openSheet } = useBottomSheet();
|
|
26
|
-
const pathname = usePathname();
|
|
27
|
-
const searchParams = useSearchParams();
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (window.location.hash === '#contact') {
|
|
31
|
-
// Detect language from pathname inside the effect
|
|
32
|
-
const language = pathname?.startsWith('/es') ? 'es' : pathname?.startsWith('/fr') ? 'fr' : 'en';
|
|
33
|
-
const strings = getStrings(language);
|
|
34
|
-
|
|
35
|
-
// Prevent default scroll behavior
|
|
36
|
-
const handleHashChange = (e: HashChangeEvent) => {
|
|
37
|
-
if (e.newURL.includes('#contact')) {
|
|
38
|
-
e.preventDefault();
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
window.addEventListener('hashchange', handleHashChange);
|
|
43
|
-
openSheet(<ContactUs strings={strings} />);
|
|
44
|
-
// Removed the history.replaceState line to keep the hash in URL
|
|
45
|
-
|
|
46
|
-
return () => {
|
|
47
|
-
window.removeEventListener('hashchange', handleHashChange);
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
}, [pathname, searchParams, openSheet]);
|
|
51
|
-
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export default function Footer() {
|
|
56
|
-
const { openSheet } = useBottomSheet();
|
|
57
|
-
const pathname = usePathname();
|
|
58
|
-
|
|
59
|
-
// Detect language from pathname
|
|
60
|
-
const language = pathname?.startsWith('/es') ? 'es' : pathname?.startsWith('/fr') ? 'fr' : 'en';
|
|
61
|
-
const strings = getStrings(language);
|
|
62
|
-
|
|
63
|
-
const handleContactClick = () => {
|
|
64
|
-
openSheet(<ContactUs strings={strings} />);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<div className={`footer-container`}>
|
|
69
|
-
<Suspense>
|
|
70
|
-
<ContactHandler />
|
|
71
|
-
</Suspense>
|
|
72
|
-
<LanguageAwareLink href="/" className="footer-logo-container">
|
|
73
|
-
<div className="footer-via-via-name">
|
|
74
|
-
<VIcon className="footer-logo-letter" />
|
|
75
|
-
<IIcon className="footer-logo-letter" />
|
|
76
|
-
<AIcon className="footer-logo-letter" />
|
|
77
|
-
</div>
|
|
78
|
-
<Spacer horizontal = {true} />
|
|
79
|
-
<div className="footer-via-via-name">
|
|
80
|
-
<VIcon className="footer-logo-letter" />
|
|
81
|
-
<IIcon className="footer-logo-letter" />
|
|
82
|
-
<AIcon className="footer-logo-letter" />
|
|
83
|
-
</div>
|
|
84
|
-
</LanguageAwareLink>
|
|
85
|
-
<Spacer />
|
|
86
|
-
<div style={{ display: 'flex', gap: '1rem', marginBottom: '1rem' }}>
|
|
87
|
-
<a href="https://instagram.com/viaviamorainelake" target="_blank" rel="noopener noreferrer">
|
|
88
|
-
<InstagramIcon className="footer-icon" />
|
|
89
|
-
</a>
|
|
90
|
-
<a href="https://www.facebook.com/profile.php?id=61557238871790" target="_blank" rel="noopener noreferrer">
|
|
91
|
-
<FacebookIcon className="footer-icon" />
|
|
92
|
-
</a>
|
|
93
|
-
<a href="https://www.tiktok.com/@viaviamorainelake" target="_blank" rel="noopener noreferrer">
|
|
94
|
-
<TikTokIcon className="footer-icon" />
|
|
95
|
-
</a>
|
|
96
|
-
<a href="https://maps.app.goo.gl/FxRqxTjgN43ttWac9" target="_blank" rel="noopener noreferrer">
|
|
97
|
-
<GoogleIcon className="footer-icon" />
|
|
98
|
-
</a>
|
|
99
|
-
</div>
|
|
100
|
-
<Spacer />
|
|
101
|
-
<FooterLinks strings={strings} />
|
|
102
|
-
<Spacer size='L' />
|
|
103
|
-
<Button
|
|
104
|
-
onClick={handleContactClick}
|
|
105
|
-
variant="secondary"
|
|
106
|
-
>
|
|
107
|
-
{strings.footer.contactUs}
|
|
108
|
-
</Button>
|
|
109
|
-
<Spacer />
|
|
110
|
-
<p onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} style={{ cursor: 'pointer' }}>
|
|
111
|
-
↑ <a><u>{strings.footer.returnToTop}</u></a> ↑
|
|
112
|
-
</p>
|
|
113
|
-
<Spacer />
|
|
114
|
-
<MoraineMagicIcon className="footer-moraine-magic-icon" />
|
|
115
|
-
<Spacer />
|
|
116
|
-
<EmailSignup strings={strings} source="footer" />
|
|
117
|
-
<Spacer />
|
|
118
|
-
<p><LanguageAwareLink href="/terms-conditions">{strings.footer["terms-and-conditions"]}</LanguageAwareLink></p>
|
|
119
|
-
<p><LanguageAwareLink href="/privacy-policy">{strings.footer["privacy-policy"]}</LanguageAwareLink></p>
|
|
120
|
-
<p><LanguageAwareLink href="/sitemap.xml">{strings.footer["sitemap"]}</LanguageAwareLink></p>
|
|
121
|
-
<Spacer />
|
|
122
|
-
<p>{strings.footer.copyright}</p>
|
|
123
|
-
<p className="footer-company-text" style={{ textTransform: 'uppercase', textAlign: 'center' }}>{strings.footer.company}</p>
|
|
124
|
-
</div>
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function FooterLinks({ strings }: { strings: any }) {
|
|
129
|
-
const footerLinks = getFooterLinks(strings);
|
|
130
|
-
return (
|
|
131
|
-
<div className="footer-links-container">
|
|
132
|
-
{Object.values(footerLinks).map((link) => (
|
|
133
|
-
link.external ? (
|
|
134
|
-
<a key={link.name} href={link.url} target="_blank" rel="noopener noreferrer">
|
|
135
|
-
{link.name}
|
|
136
|
-
</a>
|
|
137
|
-
) : (
|
|
138
|
-
<LanguageAwareLink key={link.name} href={link.path ?? link.url ?? '/'}>{link.name}</LanguageAwareLink>
|
|
139
|
-
)
|
|
140
|
-
))}
|
|
141
|
-
</div>
|
|
142
|
-
)
|
|
143
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useSimpleModal } from '@/hooks/use-simple-modal';
|
|
4
|
-
import SimpleModal from './simple-modal';
|
|
5
|
-
import { useEffect } from 'react';
|
|
6
|
-
|
|
7
|
-
export default function GlobalSimpleModal() {
|
|
8
|
-
const { isModalOpen, closeModal } = useSimpleModal();
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
const handleBookNowClick = (e: Event) => {
|
|
12
|
-
const target = e.target as HTMLElement;
|
|
13
|
-
const link = target.closest('a[href="#book-now"]');
|
|
14
|
-
if (link) {
|
|
15
|
-
e.preventDefault();
|
|
16
|
-
window.dispatchEvent(new CustomEvent('openSimpleModal'));
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
document.addEventListener('click', handleBookNowClick);
|
|
21
|
-
|
|
22
|
-
return () => {
|
|
23
|
-
document.removeEventListener('click', handleBookNowClick);
|
|
24
|
-
};
|
|
25
|
-
}, []);
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<SimpleModal
|
|
29
|
-
isOpen={isModalOpen}
|
|
30
|
-
onClose={closeModal}
|
|
31
|
-
/>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
.googleReviewSummary {
|
|
2
|
-
display: flex;
|
|
3
|
-
align-items: center;
|
|
4
|
-
flex-direction: column;
|
|
5
|
-
padding: 1rem;
|
|
6
|
-
transition: transform 0.2s ease, filter 0.2s ease;
|
|
7
|
-
cursor: pointer;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.googleReviewSummary:hover {
|
|
11
|
-
transform: scale(1.05);
|
|
12
|
-
filter: brightness(1.1);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.googleReviewSummary:active {
|
|
16
|
-
transform: scale(1.02);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.centerSection {
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-direction: column;
|
|
22
|
-
align-items: center;
|
|
23
|
-
position: relative;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.logoRow {
|
|
27
|
-
display: flex;
|
|
28
|
-
align-items: center;
|
|
29
|
-
justify-content: center;
|
|
30
|
-
gap: 0;
|
|
31
|
-
margin-bottom: 0.5rem;
|
|
32
|
-
flex-direction: row;
|
|
33
|
-
width: 100%;
|
|
34
|
-
position: relative;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.sideLogo {
|
|
38
|
-
filter: brightness(0) invert(1);
|
|
39
|
-
position: absolute;
|
|
40
|
-
z-index: 0;
|
|
41
|
-
opacity: 0.7;
|
|
42
|
-
flex-shrink: 0;
|
|
43
|
-
top: 50%;
|
|
44
|
-
transform: translateY(5%);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.sideLogo:first-child {
|
|
48
|
-
left: 5%;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.sideLogo:last-child {
|
|
52
|
-
right: 5%;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.googleIcon {
|
|
56
|
-
--google-blue: var(--accent-white);
|
|
57
|
-
--google-red: var(--accent-white);
|
|
58
|
-
--google-yellow: var(--accent-white);
|
|
59
|
-
--google-green: var(--accent-white);
|
|
60
|
-
--google-lightblue: var(--accent-white);
|
|
61
|
-
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.5));
|
|
62
|
-
position: relative;
|
|
63
|
-
z-index: 2;
|
|
64
|
-
margin-bottom: -2.5px;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.googleReviewsCount {
|
|
68
|
-
font-size: 16px;
|
|
69
|
-
color: var(--accent-white);
|
|
70
|
-
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
|
71
|
-
font-family: 'Poppins', sans-serif;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.googleReviewSummary :global(.starRating) {
|
|
75
|
-
position: relative;
|
|
76
|
-
z-index: 1;
|
|
77
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import GoogleIcon from '@/assets/icons/google-g.svg';
|
|
2
|
-
import TripAdvisorIcon from '@/assets/icons/partner-logos/tripadvisor.svg';
|
|
3
|
-
import GetYourGuideIcon from '@/assets/icons/partner-logos/getyourguide.svg';
|
|
4
|
-
import styles from './google-review-summary.module.css';
|
|
5
|
-
import StarRating from './star-rating';
|
|
6
|
-
|
|
7
|
-
export default function GoogleReviewSummary({ strings }: { strings: any }) {
|
|
8
|
-
const scrollToReviews = () => {
|
|
9
|
-
const reviewsSection = document.getElementById('reviews-section');
|
|
10
|
-
if (reviewsSection) {
|
|
11
|
-
const elementTop = reviewsSection.offsetTop;
|
|
12
|
-
const offset = 100; // Adjust this value to control how high the section appears
|
|
13
|
-
window.scrollTo({
|
|
14
|
-
top: elementTop - offset,
|
|
15
|
-
behavior: 'smooth'
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div
|
|
22
|
-
className={styles.googleReviewSummary}
|
|
23
|
-
onClick={scrollToReviews}
|
|
24
|
-
style={{ cursor: 'pointer' }}
|
|
25
|
-
title="Click to see all reviews"
|
|
26
|
-
>
|
|
27
|
-
<div className={styles.centerSection}>
|
|
28
|
-
<div className={styles.logoRow}>
|
|
29
|
-
<TripAdvisorIcon
|
|
30
|
-
width={28}
|
|
31
|
-
height={28}
|
|
32
|
-
className={styles.sideLogo}
|
|
33
|
-
/>
|
|
34
|
-
<GoogleIcon
|
|
35
|
-
width={42}
|
|
36
|
-
height={42}
|
|
37
|
-
className={styles.googleIcon}
|
|
38
|
-
/>
|
|
39
|
-
<GetYourGuideIcon
|
|
40
|
-
width={28}
|
|
41
|
-
height={28}
|
|
42
|
-
className={styles.sideLogo}
|
|
43
|
-
/>
|
|
44
|
-
</div>
|
|
45
|
-
<StarRating rating={5} showVerified={false} useGradientSize={true} />
|
|
46
|
-
</div>
|
|
47
|
-
<p className={styles.googleReviewsCount}>{strings.home.hero.googleReviewsCount}</p>
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import ViaViaImage from './image';
|
|
4
|
-
import './hero-image.css';
|
|
5
|
-
import { useEffect, useRef } from 'react';
|
|
6
|
-
|
|
7
|
-
interface HeroImageProps {
|
|
8
|
-
imageId: string;
|
|
9
|
-
alt: string;
|
|
10
|
-
objectPosition?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const HeroImage = ({ imageId, alt, objectPosition }: HeroImageProps) => {
|
|
14
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
15
|
-
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const container = containerRef.current;
|
|
18
|
-
if (!container) return;
|
|
19
|
-
|
|
20
|
-
const handleScroll = () => {
|
|
21
|
-
const scrolled = Math.max(0, Math.min(window.scrollY, container.offsetHeight));
|
|
22
|
-
const rate = scrolled * 0.5;
|
|
23
|
-
container.style.transform = `translate3d(0, ${rate}px, 0)`;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
27
|
-
return () => window.removeEventListener('scroll', handleScroll);
|
|
28
|
-
}, []);
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div className="hero-image-container">
|
|
32
|
-
<div className="parallax-container" ref={containerRef}>
|
|
33
|
-
<ViaViaImage
|
|
34
|
-
imageId={imageId}
|
|
35
|
-
context="HERO"
|
|
36
|
-
alt={alt}
|
|
37
|
-
style={objectPosition ? { objectPosition } : undefined}
|
|
38
|
-
/>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export default HeroImage;
|
package/src/components/image.css
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
.image-container {
|
|
2
|
-
position: relative;
|
|
3
|
-
width: 100%;
|
|
4
|
-
height: 100%;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.image-natural {
|
|
8
|
-
position: relative;
|
|
9
|
-
width: 100%;
|
|
10
|
-
height: auto;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/* Make sure images are responsive within their containers */
|
|
14
|
-
.image-container img {
|
|
15
|
-
width: 100%;
|
|
16
|
-
height: 100%;
|
|
17
|
-
object-fit: cover;
|
|
18
|
-
/* This centers the image content vertically when cropped */
|
|
19
|
-
object-position: 50% 50%;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.image-natural img {
|
|
23
|
-
display: block;
|
|
24
|
-
width: 100%;
|
|
25
|
-
height: auto;
|
|
26
|
-
max-width: 100%;
|
|
27
|
-
max-height: 100%;
|
|
28
|
-
object-fit: contain;
|
|
29
|
-
}
|
package/src/components/image.tsx
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import Image from 'next/image';
|
|
4
|
-
import './image.css';
|
|
5
|
-
|
|
6
|
-
interface ImageWidths {
|
|
7
|
-
mobile: number;
|
|
8
|
-
tablet: number;
|
|
9
|
-
desktop: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface ImageWidthsMap {
|
|
13
|
-
HERO: ImageWidths;
|
|
14
|
-
GALLERY: ImageWidths;
|
|
15
|
-
THUMBNAIL: ImageWidths;
|
|
16
|
-
MODAL: ImageWidths;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const IMAGE_WIDTHS: ImageWidthsMap = {
|
|
20
|
-
HERO: {
|
|
21
|
-
mobile: 768,
|
|
22
|
-
tablet: 1024,
|
|
23
|
-
desktop: 1920
|
|
24
|
-
},
|
|
25
|
-
GALLERY: {
|
|
26
|
-
mobile: 400,
|
|
27
|
-
tablet: 600,
|
|
28
|
-
desktop: 800
|
|
29
|
-
},
|
|
30
|
-
THUMBNAIL: {
|
|
31
|
-
mobile: 200,
|
|
32
|
-
tablet: 300,
|
|
33
|
-
desktop: 400
|
|
34
|
-
},
|
|
35
|
-
MODAL: {
|
|
36
|
-
mobile: 1024,
|
|
37
|
-
tablet: 1280,
|
|
38
|
-
desktop: 1920
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
type ImageContext = 'HERO' | 'GALLERY' | 'THUMBNAIL' | 'MODAL';
|
|
43
|
-
|
|
44
|
-
interface ImageProps {
|
|
45
|
-
imageId: string;
|
|
46
|
-
context?: ImageContext;
|
|
47
|
-
alt: string;
|
|
48
|
-
className?: string;
|
|
49
|
-
maintainAspectRatio?: boolean;
|
|
50
|
-
style?: React.CSSProperties;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Pre-compute sizes strings for each context to ensure consistency
|
|
54
|
-
const SIZES = {
|
|
55
|
-
HERO: '(min-width: 1024px) 1920px, (min-width: 768px) 1024px, 768px',
|
|
56
|
-
GALLERY: '(min-width: 1024px) 800px, (min-width: 768px) 600px, 400px',
|
|
57
|
-
THUMBNAIL: '(min-width: 1024px) 400px, (min-width: 768px) 300px, 200px',
|
|
58
|
-
MODAL: '(min-width: 1024px) 1920px, (min-width: 768px) 1280px, 1024px'
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// Pre-compute quality values
|
|
62
|
-
const QUALITY = {
|
|
63
|
-
HERO: 85,
|
|
64
|
-
GALLERY: 75,
|
|
65
|
-
THUMBNAIL: 75,
|
|
66
|
-
MODAL: 85
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const ViaViaImage = ({
|
|
70
|
-
className = '',
|
|
71
|
-
imageId,
|
|
72
|
-
context = 'THUMBNAIL',
|
|
73
|
-
alt,
|
|
74
|
-
maintainAspectRatio = false,
|
|
75
|
-
style
|
|
76
|
-
}: ImageProps) => {
|
|
77
|
-
const normalizedContext = context.toUpperCase() as ImageContext;
|
|
78
|
-
const widths = IMAGE_WIDTHS[normalizedContext];
|
|
79
|
-
|
|
80
|
-
if (maintainAspectRatio) {
|
|
81
|
-
return (
|
|
82
|
-
<div className={`image-container image-natural ${className}`}>
|
|
83
|
-
<Image
|
|
84
|
-
src={imageId}
|
|
85
|
-
alt={alt}
|
|
86
|
-
fill={false}
|
|
87
|
-
width={widths.desktop}
|
|
88
|
-
height={widths.desktop * 9 / 16}
|
|
89
|
-
sizes={SIZES[normalizedContext]}
|
|
90
|
-
priority={normalizedContext === 'HERO'}
|
|
91
|
-
quality={QUALITY[normalizedContext]}
|
|
92
|
-
style={{ width: '100%', height: 'auto', ...style }}
|
|
93
|
-
/>
|
|
94
|
-
</div>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<div className={`image-container image-${normalizedContext.toLowerCase()} ${className}`}>
|
|
100
|
-
<Image
|
|
101
|
-
src={imageId}
|
|
102
|
-
alt={alt}
|
|
103
|
-
fill={true}
|
|
104
|
-
sizes={SIZES[normalizedContext]}
|
|
105
|
-
priority={normalizedContext === 'HERO'}
|
|
106
|
-
quality={QUALITY[normalizedContext]}
|
|
107
|
-
style={style}
|
|
108
|
-
/>
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export default ViaViaImage;
|