/* ── DominiTrip UI Primitives ── */
/* ── Scroll Animation Hook ── */
function useScrollReveal(ref) {
const [visible, setVisible] = React.useState(false);
React.useEffect(() => {
if (!ref.current) return;
const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setVisible(true); obs.disconnect(); } }, { threshold: 0.12 });
obs.observe(ref.current);
return () => obs.disconnect();
}, [ref]);
return visible;
}
function AnimatedSection({ children, className = '', style = {}, delay = 0 }) {
const ref = React.useRef(null);
const visible = useScrollReveal(ref);
return (
{children}
);
}
/* ── Section Header ── */
function SectionHeader({ tag, title, subtitle, align = 'center', light = false }) {
const sectionHeaderStyles = {
wrapper: { textAlign: align, marginBottom: 48, maxWidth: 680, marginLeft: align === 'center' ? 'auto' : 0, marginRight: align === 'center' ? 'auto' : 0 },
tag: {
display: 'inline-block', fontFamily: 'var(--font-display)', fontSize: 13, fontWeight: 700,
textTransform: 'uppercase', letterSpacing: '0.12em', color: light ? 'rgba(255,255,255,0.7)' : 'var(--primary)',
background: light ? 'rgba(255,255,255,0.12)' : 'var(--primary-glow)',
padding: '6px 16px', borderRadius: 100, marginBottom: 14,
},
title: {
fontSize: 'clamp(28px, 4.5vw, 44px)', fontWeight: 800, lineHeight: 1.12,
color: light ? '#fff' : 'var(--text)', marginBottom: subtitle ? 14 : 0,
},
subtitle: { fontSize: 17, color: light ? 'rgba(255,255,255,0.75)' : 'var(--text-secondary)', lineHeight: 1.6, maxWidth: 560, margin: align === 'center' ? '0 auto' : 0 },
};
return (
{tag &&
{tag}}
{title}
{subtitle &&
{subtitle}
}
);
}
/* ── Button ── */
function Btn({ children, variant = 'primary', size = 'md', onClick, style = {}, icon, full }) {
const [hovered, setHovered] = React.useState(false);
const base = {
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
fontFamily: 'var(--font-display)', fontWeight: 600, cursor: 'pointer',
border: 'none', borderRadius: 'var(--radius-sm)', transition: 'all 0.25s ease',
textDecoration: 'none', whiteSpace: 'nowrap', width: full ? '100%' : 'auto',
};
const sizes = {
sm: { fontSize: 13, padding: '8px 18px' },
md: { fontSize: 15, padding: '12px 28px' },
lg: { fontSize: 17, padding: '16px 36px' },
};
const variants = {
primary: { background: 'var(--primary)', color: 'var(--text-on-primary)', transform: hovered ? 'translateY(-2px)' : 'none', boxShadow: hovered ? '0 8px 24px var(--primary-glow)' : 'none' },
accent: { background: 'var(--accent)', color: '#fff', transform: hovered ? 'translateY(-2px)' : 'none', boxShadow: hovered ? '0 8px 24px rgba(251,146,60,0.3)' : 'none' },
outline: { background: 'transparent', color: 'var(--primary)', border: '2px solid var(--primary)', transform: hovered ? 'translateY(-2px)' : 'none' },
ghost: { background: hovered ? 'var(--primary-glow)' : 'transparent', color: 'var(--primary)' },
white: { background: '#fff', color: 'var(--primary)', transform: hovered ? 'translateY(-2px)' : 'none', boxShadow: hovered ? '0 8px 24px rgba(0,0,0,0.15)' : 'none' },
whatsapp: { background: '#25D366', color: '#fff', transform: hovered ? 'translateY(-2px)' : 'none', boxShadow: hovered ? '0 8px 24px rgba(37,211,102,0.35)' : 'none' },
};
return (
);
}
/* ── Wave Divider ── */
function WaveDivider({ flip, color, style = {} }) {
return (
);
}
/* ── Badge ── */
function Badge({ children, style = {} }) {
return (
{children}
);
}
/* ── Star Rating ── */
function Stars({ rating = 5, size = 16 }) {
return (
{Array.from({ length: 5 }, (_, i) => (
))}
);
}
/* ── Card ── */
function Card({ children, style = {}, onClick, hoverable = true }) {
const [hovered, setHovered] = React.useState(false);
return (
setHovered(true)} onMouseLeave={() => setHovered(false)}
style={{
background: 'var(--surface)', borderRadius: 'var(--radius)',
boxShadow: hovered && hoverable ? 'var(--card-shadow-hover)' : 'var(--card-shadow)',
transition: 'all 0.35s cubic-bezier(0.22,1,0.36,1)',
transform: hovered && hoverable ? 'translateY(-4px)' : 'none',
overflow: 'hidden', cursor: onClick ? 'pointer' : 'default', ...style,
}}>{children}
);
}
/* ── SVG Icons ── */
const Icons = {
plane: ,
van: ,
hotel: ,
users: ,
clock: ,
wifi: ,
shield: ,
globe: ,
drink: ,
heart: ,
phone: ,
chevDown: ,
mapPin: ,
close: ,
menu: ,
calendar: ,
check: ,
arrowRight: ,
car: ,
tag: ,
};
/* ── Currency Formatter ── */
const CAD_RATE = 1.37;
function formatPrice(usd, currency = 'USD') {
if (currency === 'CAD') return `C$${Math.round(usd * CAD_RATE)}`;
return `$${usd}`;
}
function formatPriceLabel(usd, currency = 'USD') {
if (currency === 'CAD') return `C$${Math.round(usd * CAD_RATE)}`;
return `$${usd}`;
}
/* ── Currency Toggle Pill ── */
function CurrencyToggle({ currency, onChange }) {
const active = { background: 'var(--primary)', color: 'var(--text-on-primary)' };
const inactive = { background: 'transparent', color: 'var(--text-secondary)' };
const btnBase = {
padding: '4px 12px', borderRadius: 100, border: 'none', cursor: 'pointer',
fontFamily: 'var(--font-display)', fontSize: 12, fontWeight: 700, transition: 'all 0.2s',
};
return (
);
}
/* ── Export to window ── */
Object.assign(window, {
useScrollReveal, AnimatedSection, SectionHeader, Btn, WaveDivider,
Badge, Stars, Card, Icons, formatPrice, formatPriceLabel, CurrencyToggle, CAD_RATE,
});