// Cozmo Yachts — main App const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA } = React; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": "marine-amber", "heroHeadline": "Your private yacht on the Dubai coastline.", "displayFont": "Cormorant Garamond", "showPromos": true }/*EDITMODE-END*/; const PALETTES = { "marine-amber": { label: "Marine + Amber", "--noir": "#0A1622", "--ink": "#16212B", "--paper": "#F7FAFB", "--sand": "#EFE9DC", "--marine": "#11507A", "--sun": "#E8A317", "--muted": "#5B6B78", "--line": "#DCE3E8", }, "midnight": { label: "Deep midnight", "--noir": "#050D17", "--ink": "#0A1A29", "--paper": "#F4F6F9", "--sand": "#E6E9EE", "--marine": "#0E3E64", "--sun": "#D89B1E", "--muted": "#5A6878", "--line": "#D8DCE2", }, "warm-sand": { label: "Warm sand", "--noir": "#1A1812", "--ink": "#2A2419", "--paper": "#FAF6EE", "--sand": "#EFE4CC", "--marine": "#7C5D2B", "--sun": "#D88B14", "--muted": "#6B6151", "--line": "#E0D6BD", }, }; const FONTS = ["Cormorant Garamond", "EB Garamond", "Playfair Display"]; function buildWAMessage({ yacht, date, time, guests, duration, topic }) { const lines = ["Hi Cozmo Yachts,"]; if (topic) { lines.push("", `I'd like to enquire about: ${topic}.`); } else if (yacht) { lines.push("", `I'd like to enquire about chartering the ${yacht.name}${yacht.sub ? ` (${yacht.sub})` : ""}.`); } else { lines.push("", "I'd like to enquire about a charter."); } if (date) lines.push("", `Date: ${new Date(date).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short', year: 'numeric' })}`); if (time) lines.push(`Departure: ${time}`); if (guests) lines.push(`Guests: ${guests}`); if (duration) lines.push(`Duration: ${duration}`); lines.push("", "Could you confirm availability and share an indicative quote? Thanks."); return lines.join("\n"); } function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); // Apply palette CSS vars useEffectA(() => { const p = PALETTES[t.palette] || PALETTES["marine-amber"]; Object.entries(p).forEach(([k, v]) => { if (k.startsWith("--")) document.documentElement.style.setProperty(k, v); }); }, [t.palette]); useEffectA(() => { document.documentElement.style.setProperty("--serif", `"${t.displayFont}", "Cormorant Garamond", Georgia, serif`); // ensure font is loaded if (!document.getElementById(`gf-${t.displayFont}`)) { const link = document.createElement("link"); link.id = `gf-${t.displayFont}`; link.rel = "stylesheet"; const fam = t.displayFont.replace(/ /g, "+"); link.href = `https://fonts.googleapis.com/css2?family=${fam}:ital,wght@0,300;0,400;0,500;1,400&display=swap`; document.head.appendChild(link); } }, [t.displayFont]); const [scrolled, setScrolled] = useStateA(false); const [lang, setLang] = useStateA("EN"); const [heroIdx, setHeroIdx] = useStateA(0); const [shortlist, setShortlist] = useStateA([]); const [toast, setToast] = useStateA(null); const [modal, setModal] = useStateA(null); useEffectA(() => { const onScroll = () => setScrolled(window.scrollY > 80); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); useEffectA(() => { if (!toast) return; const id = setTimeout(() => setToast(null), 2400); return () => clearTimeout(id); }, [toast]); function handleFavorite(yacht) { setShortlist(prev => { if (prev.includes(yacht.slug)) { setToast(`Removed ${yacht.name} from shortlist`); return prev.filter(s => s !== yacht.slug); } else { setToast(`${yacht.name} added to shortlist`); return [...prev, yacht.slug]; } }); } function handleEnquire(yachtOrTopic) { setModal({ kind: "enquiry", yacht: yachtOrTopic }); } function handleAvailabilitySubmit(payload) { setModal({ kind: "availability", payload }); } function handleCallWhatsApp({ topic }) { setModal({ kind: "enquiry", topic }); } function handleShortlistClick() { const yachts = FLEET.filter(y => shortlist.includes(y.slug)); setModal({ kind: "shortlist", yachts }); } return (
{t.showPromos && }
); } /* -- Modal -- */ function Modal({ modal, onClose, shortlist }) { let body = null; let title = null; if (modal.kind === "enquiry") { const yacht = modal.yacht; const topic = modal.topic; const msg = buildWAMessage({ yacht, topic }); const waUrl = `https://wa.me/971529440222?text=${encodeURIComponent(msg)}`; title = ( <>

Open WhatsApp with this message?

We'll pre-fill the details below. Edit anything before you send.

); body = ( <>
{msg}
{Icons.whatsapp} Open WhatsApp {Icons.phone} Call instead
Concierge replies typically within 15 minutes between 09:00–23:00 GST. Quote is indicative — final rate confirmed against live availability.
); } else if (modal.kind === "availability") { const p = modal.payload; const msg = buildWAMessage(p); const waUrl = `https://wa.me/971529440222?text=${encodeURIComponent(msg)}`; title = ( <>

Checking availability

Confirm your enquiry and we'll match you to vessels available for these dates.

); body = ( <>
Date {new Date(p.date).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' })}
Departure {p.time}
Guests {p.guests}
Duration {p.duration}
{msg}
{Icons.whatsapp} Send via WhatsApp {Icons.phone} Call concierge
For {p.guests} guest{p.guests > 1 ? "s" : ""}, {FLEET.filter(y => y.guests >= p.guests).length} vessel{FLEET.filter(y => y.guests >= p.guests).length === 1 ? "" : "s"} match. Rates vary by season; final quote confirmed at booking.
); } else if (modal.kind === "shortlist") { const yachts = modal.yachts; const msg = "Hi Cozmo Yachts,\n\nI've shortlisted these vessels and would love a comparative quote:\n\n" + yachts.map(y => `· ${y.name} (${y.lengthFt}ft · up to ${y.guests} guests)`).join("\n") + "\n\nWhich are available for the coming weekend? Thanks."; const waUrl = `https://wa.me/971529440222?text=${encodeURIComponent(msg)}`; title = ( <>

Your shortlist

{yachts.length} vessel{yachts.length === 1 ? "" : "s"} ready to compare.

); body = ( <>
{yachts.map(y => (
{y.name} {y.lengthFt}ft · {y.guests} guests
))}
{msg}
{Icons.whatsapp} Send shortlist on WhatsApp
); } return (
e.stopPropagation()}> {title} {body}
); } /* -- Tweaks wrap -- */ function TweaksPanelWrap({ t, setTweak }) { return ( setTweak("palette", v)} options={[ { value: "marine-amber", label: "Marine" }, { value: "midnight", label: "Midnight" }, { value: "warm-sand", label: "Sand" }, ]} /> setTweak("heroHeadline", v)} /> setTweak("displayFont", v)} options={FONTS} /> setTweak("showPromos", v)} /> ); } // Patch: TweakColor needs to accept optionValues; the starter expects values to equal options. // We'll handle via small wrapper above—if not supported, fallback: ReactDOM.createRoot(document.getElementById("root")).render();