@ticketboothapp/booking 0.1.19 → 0.1.22
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 +2 -1
- package/src/components/BookingWidget.tsx +282 -26
- package/src/components/ManageBookingView.tsx +75 -23
- package/src/components/PostBookingDependentAddOnUpsell.tsx +1 -1
- 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/tsconfig.json +1 -1
- package/src/components/JobApplicationDialog.module.css +0 -440
- package/src/components/JobApplicationDialog.tsx +0 -620
- package/src/components/PickupLocationMap.tsx +0 -110
- 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/client-bottom-sheet.tsx +0 -14
- 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/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-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/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/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-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/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/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/hooks/use-bottom-sheet.tsx +0 -15
- package/src/hooks/use-simple-modal.tsx +0 -27
- package/src/hooks/useEmailSubscription.tsx +0 -103
- package/src/hooks/useEmbeddedInIframe.ts +0 -16
- package/src/hooks/useQuiz.tsx +0 -210
- package/src/providers/bottom-sheet-provider.tsx +0 -40
- package/src/types/fareharbor.d.ts +0 -12
- package/src/types/quiz.ts +0 -59
- /package/src/{app/photo-sessions → lib}/photo-packages.ts +0 -0
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import styles from './simple-top-of-fold.module.css';
|
|
2
|
-
import { ImageData } from '@/constants/images';
|
|
3
|
-
import HeroImage from '@/components/hero-image';
|
|
4
|
-
|
|
5
|
-
export type ImagePosition = 'top' | 'center' | 'bottom';
|
|
6
|
-
|
|
7
|
-
export interface SimpleTopOfFoldProps {
|
|
8
|
-
image: ImageData;
|
|
9
|
-
title: string;
|
|
10
|
-
subtitle: string;
|
|
11
|
-
subsubtitle?: string;
|
|
12
|
-
imagePosition?: ImagePosition;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default function SimpleTopOfFold(props: SimpleTopOfFoldProps) {
|
|
16
|
-
const { image, title, subtitle, subsubtitle, imagePosition = 'center' } = props;
|
|
17
|
-
return (
|
|
18
|
-
<div className={`${styles.topOfFold} global-top-fold`}>
|
|
19
|
-
<div className={`${styles.heroImageWrapper} ${styles[imagePosition]}`}>
|
|
20
|
-
<HeroImage
|
|
21
|
-
imageId={image.id}
|
|
22
|
-
alt={image.alt}
|
|
23
|
-
objectPosition={imagePosition}
|
|
24
|
-
/>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<div className={styles.heroOverlayContent}>
|
|
28
|
-
<h1 className={styles.heroTitle}>{title}</h1>
|
|
29
|
-
<h2 className={styles.heroSubtitle}>{subtitle}</h2>
|
|
30
|
-
{subsubtitle && <h3 className={styles.heroSubsubtitle}>{subsubtitle}</h3>}
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
.spacer {
|
|
2
|
-
display: block;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
.spacer-vertical {
|
|
6
|
-
width: 100%;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.spacer-horizontal {
|
|
10
|
-
height: 100%;
|
|
11
|
-
display: inline-block;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/* Vertical spacing */
|
|
15
|
-
.spacer-small {
|
|
16
|
-
height: var(--spacing-small);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.spacer-medium {
|
|
20
|
-
height: var(--spacing-medium);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.spacer-large {
|
|
24
|
-
height: var(--spacing-large);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/* Horizontal spacing */
|
|
28
|
-
.spacer-horizontal.spacer-small {
|
|
29
|
-
width: var(--spacing-small);
|
|
30
|
-
height: auto;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.spacer-horizontal.spacer-medium {
|
|
34
|
-
width: var(--spacing-medium);
|
|
35
|
-
height: auto;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.spacer-horizontal.spacer-large {
|
|
39
|
-
width: var(--spacing-large);
|
|
40
|
-
height: auto;
|
|
41
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import './spacer.css';
|
|
2
|
-
|
|
3
|
-
const SPACER_SIZES = {
|
|
4
|
-
S: 'small',
|
|
5
|
-
M: 'medium',
|
|
6
|
-
L: 'large'
|
|
7
|
-
} as const;
|
|
8
|
-
|
|
9
|
-
type SpacerSize = keyof typeof SPACER_SIZES;
|
|
10
|
-
|
|
11
|
-
interface SpacerProps {
|
|
12
|
-
size?: SpacerSize;
|
|
13
|
-
horizontal?: boolean;
|
|
14
|
-
className?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const Spacer = ({ className = '', size = 'M', horizontal = false }: SpacerProps) => {
|
|
18
|
-
const spacerClass = `spacer spacer-${SPACER_SIZES[size]} ${horizontal ? 'spacer-horizontal' : 'spacer-vertical'} ${className}`;
|
|
19
|
-
|
|
20
|
-
return <div className={spacerClass} />;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export default Spacer;
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
.container {
|
|
2
|
-
display: flex;
|
|
3
|
-
align-items: center;
|
|
4
|
-
gap: 8px;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.starRating {
|
|
8
|
-
display: flex;
|
|
9
|
-
gap: 2px;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.starRating.gradientSpacing {
|
|
13
|
-
gap: 2px;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/* Add extra spacing around the large center star */
|
|
17
|
-
.starRating.gradientSpacing .starLarge {
|
|
18
|
-
margin: 0 4px; /* Additional margin on left and right of the center star */
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.star {
|
|
22
|
-
width: 16px;
|
|
23
|
-
height: 16px;
|
|
24
|
-
opacity: 0.3;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.star.filled {
|
|
28
|
-
opacity: 1;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.verified {
|
|
32
|
-
width: 16px;
|
|
33
|
-
height: 16px;
|
|
34
|
-
position: relative;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.verified:hover::after {
|
|
38
|
-
content: "Trustindex verifies that the original source of the review is Google.";
|
|
39
|
-
position: absolute;
|
|
40
|
-
bottom: 100%;
|
|
41
|
-
left: 50%;
|
|
42
|
-
transform: translateX(-50%);
|
|
43
|
-
background: rgba(0, 0, 0, 0.8);
|
|
44
|
-
color: white;
|
|
45
|
-
padding: 8px 12px;
|
|
46
|
-
border-radius: 4px;
|
|
47
|
-
font-size: 12px;
|
|
48
|
-
white-space: nowrap;
|
|
49
|
-
z-index: 1000;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.align-left {
|
|
53
|
-
justify-content: flex-start;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.align-center {
|
|
57
|
-
justify-content: center;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.align-right {
|
|
61
|
-
justify-content: flex-end;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.starSmall {
|
|
65
|
-
transform: scale(0.8);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.starMedium {
|
|
69
|
-
transform: scale(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.starLarge {
|
|
73
|
-
transform: scale(1.4);
|
|
74
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import StarIcon from '@/assets/icons/star.svg';
|
|
2
|
-
import VerifiedIcon from '@/assets/icons/verified.svg';
|
|
3
|
-
import styles from './star-rating.module.css';
|
|
4
|
-
|
|
5
|
-
interface StarRatingProps {
|
|
6
|
-
rating: number; // Expects a number between 0-5
|
|
7
|
-
showVerified?: boolean;
|
|
8
|
-
align?: 'left' | 'center' | 'right';
|
|
9
|
-
useGradientSize?: boolean; // New prop to control star size pattern
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default function StarRating({
|
|
13
|
-
rating,
|
|
14
|
-
showVerified = true,
|
|
15
|
-
align = 'left',
|
|
16
|
-
useGradientSize = false
|
|
17
|
-
}: StarRatingProps) {
|
|
18
|
-
// Helper function to get size class based on star position
|
|
19
|
-
const getStarSizeClass = (position: number) => {
|
|
20
|
-
if (!useGradientSize) return '';
|
|
21
|
-
|
|
22
|
-
if (position === 3) return styles.starLarge;
|
|
23
|
-
if (position === 2 || position === 4) return styles.starMedium;
|
|
24
|
-
return styles.starSmall;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div className={`${styles.container} ${styles[`align-${align}`]}`}>
|
|
29
|
-
<div className={`${styles.starRating} ${useGradientSize ? styles.gradientSpacing : ''}`}>
|
|
30
|
-
{[1, 2, 3, 4, 5].map((star) => (
|
|
31
|
-
<StarIcon
|
|
32
|
-
key={star}
|
|
33
|
-
className={`
|
|
34
|
-
${styles.star}
|
|
35
|
-
${star <= rating ? styles.filled : styles.empty}
|
|
36
|
-
${getStarSizeClass(star)}
|
|
37
|
-
`}
|
|
38
|
-
/>
|
|
39
|
-
))}
|
|
40
|
-
</div>
|
|
41
|
-
{showVerified && (
|
|
42
|
-
<div className={styles.verified} title="Trustindex verifies that the original source of the review is Google.">
|
|
43
|
-
<VerifiedIcon />
|
|
44
|
-
</div>
|
|
45
|
-
)}
|
|
46
|
-
</div>
|
|
47
|
-
);
|
|
48
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import styles from "./title-subtitle.module.css"
|
|
2
|
-
import containerStyles from "@/styles/container.module.css"
|
|
3
|
-
|
|
4
|
-
export interface TitleSubtitleProps {
|
|
5
|
-
title?: string
|
|
6
|
-
subtitle?: string
|
|
7
|
-
description?: string
|
|
8
|
-
className?: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const createTitleSubtitleParams = (props: TitleSubtitleProps) => props;
|
|
12
|
-
|
|
13
|
-
export default function TitleSubtitle({
|
|
14
|
-
title,
|
|
15
|
-
subtitle,
|
|
16
|
-
description,
|
|
17
|
-
className
|
|
18
|
-
}: TitleSubtitleProps) {
|
|
19
|
-
return (
|
|
20
|
-
<div className={`${styles.titleSubtitle} ${className}`}>
|
|
21
|
-
{title && <h2>{title}</h2>}
|
|
22
|
-
{subtitle && <h3 className={styles.titleSubtitleSubtitle}>{subtitle}</h3>}
|
|
23
|
-
{description && (
|
|
24
|
-
<p className={styles.titleSubtitleDescription}
|
|
25
|
-
dangerouslySetInnerHTML={{__html: description}}
|
|
26
|
-
/>
|
|
27
|
-
)}
|
|
28
|
-
</div>
|
|
29
|
-
)
|
|
30
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
4
|
-
import { REVIEWS, Review } from '@/constants/reviews';
|
|
5
|
-
|
|
6
|
-
interface TranslatableReviewsProps {
|
|
7
|
-
children: (reviews: Review[], showOriginal: boolean, toggleOriginal: () => void) => React.ReactNode;
|
|
8
|
-
language: 'en' | 'es' | 'fr';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default function TranslatableReviews({ children, language }: TranslatableReviewsProps) {
|
|
12
|
-
const [translatedReviews, setTranslatedReviews] = useState<Review[]>([]);
|
|
13
|
-
const [showOriginal, setShowOriginal] = useState(false);
|
|
14
|
-
const [isTranslating, setIsTranslating] = useState(false);
|
|
15
|
-
|
|
16
|
-
const toggleOriginal = () => {
|
|
17
|
-
setShowOriginal(!showOriginal);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
if ((language === 'es' || language === 'fr') && translatedReviews.length === 0 && !isTranslating) {
|
|
22
|
-
translateReviews();
|
|
23
|
-
}
|
|
24
|
-
}, [language, translatedReviews.length, isTranslating]);
|
|
25
|
-
|
|
26
|
-
const translateReviews = async () => {
|
|
27
|
-
setIsTranslating(true);
|
|
28
|
-
try {
|
|
29
|
-
// Use Google Translate API to translate the reviews
|
|
30
|
-
const translated = await Promise.all(
|
|
31
|
-
REVIEWS.map(async (review) => {
|
|
32
|
-
try {
|
|
33
|
-
// For now, we'll use a simple approach with Google Translate
|
|
34
|
-
// In production, you might want to use the Google Translate API
|
|
35
|
-
const response = await fetch(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${language}&dt=t&q=${encodeURIComponent(review.review)}`);
|
|
36
|
-
const data = await response.json();
|
|
37
|
-
const translatedText = data[0]?.map((item: any) => item[0]).join('') || review.review;
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
...review,
|
|
41
|
-
review: translatedText
|
|
42
|
-
};
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.error('Translation error for review:', error);
|
|
45
|
-
return review; // Fallback to original
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
);
|
|
49
|
-
setTranslatedReviews(translated);
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error('Error translating reviews:', error);
|
|
52
|
-
setTranslatedReviews(REVIEWS); // Fallback to original
|
|
53
|
-
} finally {
|
|
54
|
-
setIsTranslating(false);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Return original reviews for English or when showing original
|
|
59
|
-
if (language === 'en' || showOriginal) {
|
|
60
|
-
return <>{children(REVIEWS, showOriginal, toggleOriginal)}</>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Return translated reviews for Spanish or French
|
|
64
|
-
if (translatedReviews.length > 0) {
|
|
65
|
-
return <>{children(translatedReviews, showOriginal, toggleOriginal)}</>;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Show loading state while translating
|
|
69
|
-
if (isTranslating) {
|
|
70
|
-
return <>{children(REVIEWS, showOriginal, toggleOriginal)}</>;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Fallback to original
|
|
74
|
-
return <>{children(REVIEWS, showOriginal, toggleOriginal)}</>;
|
|
75
|
-
}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
.value-props-container {
|
|
2
|
-
padding: var(--spacing-medium);
|
|
3
|
-
background-color: var(--light-orange-background);
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.value-props-inner {
|
|
7
|
-
max-width: 1200px;
|
|
8
|
-
margin: 0 auto;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.title-subtitle-container {
|
|
12
|
-
padding: var(--spacing-small);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.value-props-grid {
|
|
16
|
-
display: grid;
|
|
17
|
-
grid-template-columns: repeat(2, 1fr);
|
|
18
|
-
gap: 16px;
|
|
19
|
-
max-width: 1200px;
|
|
20
|
-
margin: 0 auto;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.value-prop-card:last-child:nth-child(odd) {
|
|
24
|
-
grid-column: 1 / -1;
|
|
25
|
-
max-width: 350px;
|
|
26
|
-
margin: 0 auto;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@media (min-width: 768px) {
|
|
30
|
-
.value-props-grid {
|
|
31
|
-
gap: 24px;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.value-prop-card {
|
|
35
|
-
padding: 24px;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.value-prop-icon-wrapper {
|
|
39
|
-
width: 56px;
|
|
40
|
-
height: 56px;
|
|
41
|
-
margin: 0 auto 20px;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.value-prop-heading {
|
|
45
|
-
font-size: 20px;
|
|
46
|
-
margin-bottom: 12px;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
@media (min-width: 1024px) {
|
|
51
|
-
.value-props-grid {
|
|
52
|
-
grid-template-columns: repeat(3, 1fr);
|
|
53
|
-
gap: 32px;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.value-prop-card {
|
|
57
|
-
padding: 32px;
|
|
58
|
-
min-height: 280px;
|
|
59
|
-
gap: 16px;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.value-prop-icon-wrapper {
|
|
63
|
-
width: 64px;
|
|
64
|
-
height: 64px;
|
|
65
|
-
margin: 0 auto 24px;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.value-prop-heading {
|
|
69
|
-
font-size: 28px;
|
|
70
|
-
margin-bottom: 16px;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.value-prop-card:last-child:nth-child(odd) {
|
|
74
|
-
grid-column: auto;
|
|
75
|
-
max-width: none;
|
|
76
|
-
margin: 0;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.value-prop-card:nth-last-child(2):nth-child(4),
|
|
80
|
-
.value-prop-card:last-child:nth-child(5) {
|
|
81
|
-
transform: translateX(50%);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.value-prop-card {
|
|
86
|
-
background: white;
|
|
87
|
-
padding: 8px 16px;
|
|
88
|
-
border-radius: 16px;
|
|
89
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
90
|
-
min-height: 200px;
|
|
91
|
-
display: flex;
|
|
92
|
-
flex-direction: column;
|
|
93
|
-
align-items: center;
|
|
94
|
-
justify-content: center;
|
|
95
|
-
gap: 8px;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.value-prop-icon-wrapper {
|
|
99
|
-
width: 48px;
|
|
100
|
-
height: 48px;
|
|
101
|
-
margin: 0 auto 8px;
|
|
102
|
-
text-align: center;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.value-prop-icon {
|
|
106
|
-
width: 48px;
|
|
107
|
-
height: 48px;
|
|
108
|
-
|
|
109
|
-
@media (min-width: 768px) {
|
|
110
|
-
width: 80px;
|
|
111
|
-
height: 80px;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.value-prop-icon-wrapper {
|
|
116
|
-
@media (min-width: 768px) {
|
|
117
|
-
width: 80px;
|
|
118
|
-
height: 80px;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.value-prop-heading {
|
|
123
|
-
font-size: 18px;
|
|
124
|
-
margin-bottom: 8px;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@media (max-width: 768px) {
|
|
128
|
-
.value-props-grid {
|
|
129
|
-
display: grid !important;
|
|
130
|
-
grid-template-columns: repeat(3, 1fr) !important;
|
|
131
|
-
gap: 8px !important;
|
|
132
|
-
max-width: none !important;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.value-prop-card {
|
|
136
|
-
padding: 8px 8px !important;
|
|
137
|
-
min-height: 120px !important;
|
|
138
|
-
gap: 4px !important;
|
|
139
|
-
grid-column: auto !important;
|
|
140
|
-
max-width: none !important;
|
|
141
|
-
margin: 0 !important;
|
|
142
|
-
transform: none !important;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.value-prop-description {
|
|
146
|
-
display: none;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.value-prop-icon-wrapper {
|
|
150
|
-
width: 48px;
|
|
151
|
-
height: 48px;
|
|
152
|
-
margin: 0 auto 6px;
|
|
153
|
-
display: flex;
|
|
154
|
-
align-items: center;
|
|
155
|
-
justify-content: center;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.value-prop-heading {
|
|
159
|
-
font-size: 14px;
|
|
160
|
-
margin-bottom: 4px;
|
|
161
|
-
text-align: center;
|
|
162
|
-
word-wrap: break-word;
|
|
163
|
-
hyphens: auto;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.value-prop-icon {
|
|
167
|
-
margin: 0 !important;
|
|
168
|
-
display: block !important;
|
|
169
|
-
width: 48px !important;
|
|
170
|
-
height: 48px !important;
|
|
171
|
-
flex-shrink: 0 !important;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.value-prop-icon svg {
|
|
175
|
-
margin: 0 !important;
|
|
176
|
-
display: block !important;
|
|
177
|
-
width: 48px !important;
|
|
178
|
-
height: 48px !important;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.value-prop-icon div {
|
|
182
|
-
width: 48px !important;
|
|
183
|
-
height: 48px !important;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import './value-props.css';
|
|
4
|
-
import dynamic from 'next/dynamic';
|
|
5
|
-
import TitleSubtitle from '@/components/title-subtitle';
|
|
6
|
-
import { VALUE_PROPS_KEYS, animations } from '@/constants/value-props';
|
|
7
|
-
|
|
8
|
-
// Dynamically import Lottie with no SSR
|
|
9
|
-
const Lottie = dynamic(() => import("lottie-react"), { ssr: false });
|
|
10
|
-
|
|
11
|
-
interface ValuePropsProps {
|
|
12
|
-
title?: string;
|
|
13
|
-
description?: string;
|
|
14
|
-
filterKeys?: string[];
|
|
15
|
-
className?: string;
|
|
16
|
-
strings: any;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export default function ValueProps({ title, description, filterKeys, className, strings }: ValuePropsProps) {
|
|
20
|
-
// Generate value props dynamically from strings
|
|
21
|
-
const generateValueProps = () => {
|
|
22
|
-
const valuePropsMap = {
|
|
23
|
-
[VALUE_PROPS_KEYS.SMALL_GROUPS]: { animationKey: 'smallGroups' as const },
|
|
24
|
-
[VALUE_PROPS_KEYS.COZY_BLANKETS]: { animationKey: 'cozyBlankets' as const },
|
|
25
|
-
[VALUE_PROPS_KEYS.HOT_DRINKS]: { animationKey: 'hotDrinks' as const },
|
|
26
|
-
[VALUE_PROPS_KEYS.PHOTO_TOURS]: { animationKey: 'photoTours' as const },
|
|
27
|
-
[VALUE_PROPS_KEYS.LOCAL_GUIDES]: { animationKey: 'localGuides' as const },
|
|
28
|
-
[VALUE_PROPS_KEYS.CONVENIENT_PICKUP]: { animationKey: 'convenientPickup' as const },
|
|
29
|
-
[VALUE_PROPS_KEYS.DIRECT_ACCESS]: { animationKey: 'directAccess' as const },
|
|
30
|
-
[VALUE_PROPS_KEYS.AVOID_PAID_PARKING]: { animationKey: 'avoidPaidParking' as const },
|
|
31
|
-
[VALUE_PROPS_KEYS.FLEXIBLE_CANCELLATION]: { animationKey: 'flexibleCancellation' as const },
|
|
32
|
-
[VALUE_PROPS_KEYS.MULTIPLE_LOCATIONS]: { animationKey: 'multipleLocations' as const },
|
|
33
|
-
[VALUE_PROPS_KEYS.NO_PARKING_STRUGGLES]: { animationKey: 'noParkingStruggles' as const },
|
|
34
|
-
[VALUE_PROPS_KEYS.YOUR_PRIVATE_GROUP]: { animationKey: 'threeFriends' as const },
|
|
35
|
-
[VALUE_PROPS_KEYS.CUSTOMIZED_ITINERARY]: { animationKey: 'map' as const },
|
|
36
|
-
[VALUE_PROPS_KEYS.DOORSTEP_PICKUP]: { animationKey: 'convenientPickup' as const },
|
|
37
|
-
[VALUE_PROPS_KEYS.CAPTURE_THE_MOMENT]: { animationKey: 'photoTours' as const },
|
|
38
|
-
[VALUE_PROPS_KEYS.COZY_COMFORT]: { animationKey: 'cozyBlankets' as const },
|
|
39
|
-
[VALUE_PROPS_KEYS.PERSONAL_GUIDES]: { animationKey: 'localGuides' as const }
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
return Object.entries(valuePropsMap).map(([key, { animationKey }]) => ({
|
|
43
|
-
key,
|
|
44
|
-
animationKey,
|
|
45
|
-
title: strings.valueProps[key as keyof typeof strings.valueProps].title,
|
|
46
|
-
description: strings.valueProps[key as keyof typeof strings.valueProps].description
|
|
47
|
-
}));
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const allValueProps = generateValueProps();
|
|
51
|
-
const displayedProps = filterKeys
|
|
52
|
-
? allValueProps.filter(prop => filterKeys.includes(prop.key))
|
|
53
|
-
: allValueProps;
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<section className={`value-props-container ${className}`}>
|
|
57
|
-
<div className="value-props-inner">
|
|
58
|
-
<div className="title-subtitle-container">
|
|
59
|
-
{title && (
|
|
60
|
-
<TitleSubtitle
|
|
61
|
-
title={title}
|
|
62
|
-
description={description}
|
|
63
|
-
/>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
<div className="value-props-grid">
|
|
67
|
-
{displayedProps.map((prop) => (
|
|
68
|
-
<div key={prop.key} className="value-prop-card">
|
|
69
|
-
<div className="value-prop-icon-wrapper">
|
|
70
|
-
<Lottie
|
|
71
|
-
animationData={animations[prop.animationKey]}
|
|
72
|
-
loop={true}
|
|
73
|
-
className="value-prop-icon"
|
|
74
|
-
/>
|
|
75
|
-
</div>
|
|
76
|
-
<h3 className="value-prop-heading">
|
|
77
|
-
{prop.title}
|
|
78
|
-
</h3>
|
|
79
|
-
<p className="value-prop-description">
|
|
80
|
-
{prop.description}
|
|
81
|
-
</p>
|
|
82
|
-
</div>
|
|
83
|
-
))}
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
</section>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { QuizQuestion } from "@/types/quiz";
|
|
2
|
-
import { IMAGES } from "./images";
|
|
3
|
-
|
|
4
|
-
export const QUIZ_QUESTIONS: QuizQuestion[] = [
|
|
5
|
-
{
|
|
6
|
-
id: "what_lakes",
|
|
7
|
-
text: "What do you want to see during your visit to Banff National Park? Select all that apply.",
|
|
8
|
-
subtitle: "It is possible to see multiple lakes in one day, we'll recommend the best way to combine them.",
|
|
9
|
-
answers: [
|
|
10
|
-
{
|
|
11
|
-
id: "moraine_lake",
|
|
12
|
-
text: "Moraine Lake",
|
|
13
|
-
image: IMAGES.MORAINE_LAKE_SUNRISE
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: "lake_louise",
|
|
17
|
-
text: "Lake Louise",
|
|
18
|
-
image: IMAGES.LAKE_LOUISE
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: "emerald_lake",
|
|
22
|
-
text: "Emerald Lake",
|
|
23
|
-
image: IMAGES.EMERALD_LAKE
|
|
24
|
-
},
|
|
25
|
-
{ id: "other_custom", text: "Other (you want a custom itinerary)" }
|
|
26
|
-
],
|
|
27
|
-
isMultiSelect: true
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: "sunrise",
|
|
31
|
-
text: "Do you want to see the infamous Moraine Lake sunrise?",
|
|
32
|
-
answers: [
|
|
33
|
-
{ id: "yes", text: "Yes, I want to see the sunrise", subtext: "🐦 Early bird gets the worm!" },
|
|
34
|
-
{ id: "no", text: "No, I don't want to see the sunrise", subtext: "😴 I need my beauty rest" }
|
|
35
|
-
]
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: "what_activities",
|
|
39
|
-
text: "What activities are you interested in? Select all that apply.",
|
|
40
|
-
subtitle: "It's possible to do multiple activities in one day! We'll recommend the best way to combine them.",
|
|
41
|
-
answers: [
|
|
42
|
-
{ id: "banff_bucket_list", text: "Banff Bucket List" },
|
|
43
|
-
{ id: "hiking", text: "Hiking" },
|
|
44
|
-
{ id: "photography", text: "Photography" },
|
|
45
|
-
{ id: "canoeing", text: "Canoeing" },
|
|
46
|
-
{ id: "relaxation", text: "Relaxation" }
|
|
47
|
-
],
|
|
48
|
-
isMultiSelect: true
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
id: "hiking",
|
|
52
|
-
text: "What type of hikes are you interested in?",
|
|
53
|
-
answers: [
|
|
54
|
-
{ id: "easy_short_hikes", text: "Easy short hikes", subtext: "<=1 hour or 2.5km (1.5mi)" },
|
|
55
|
-
{ id: "moderate_hikes", text: "Moderate hikes", subtext: "1-2 hours or 3-6km (2-4mi)" },
|
|
56
|
-
{ id: "challenging_hikes", text: "Challenging hikes", subtext: ">2 hours or >6km (4mi)" },
|
|
57
|
-
{ id: "expert_hikes", text: "Expert hikes", subtext: ">5 hours or >10km (6mi)" }
|
|
58
|
-
],
|
|
59
|
-
showIf: (answers) => {
|
|
60
|
-
const activities = answers["what_activities"];
|
|
61
|
-
return Array.isArray(activities) ? activities.includes("hiking") : activities === "hiking";
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
];
|