/* ============================================================ MAISON ATLAS — shared React components (exported to window) ============================================================ */ const { useState, useEffect, useRef, useCallback } = React; const T = (lang, key) => window.ATLAS.t(lang, key); /* ---- language hook: applies dir + body class globally ---- */ function useLang() { const [lang, setLangState] = useState(window.ATLAS.getLang()); useEffect(() => { const meta = window.ATLAS.langs.find(l => l.code === lang) || window.ATLAS.langs[0]; document.documentElement.lang = lang; document.documentElement.dir = meta.dir; document.body.classList.toggle('lang-ar', lang === 'ar'); window.ATLAS.setLang(lang); }, [lang]); return [lang, setLangState]; } /* ---- Flag icons (clean geometric SVG, rounded) ---- */ function Flag({ code, size = 22 }) { const w = size, h = Math.round(size * 0.72), r = Math.max(2, size * 0.16); const wrap = (kids) => ( ); const flags = { FR: , EN: , ES: , IT: , DE: , AR: , }; return wrap(flags[code] || ); } const LANG_NAMES = { FR: { fr: 'Français', en: 'French', ar: 'الفرنسية' }, EN: { fr: 'Anglais', en: 'English', ar: 'الإنجليزية' }, ES: { fr: 'Espagnol', en: 'Spanish', ar: 'الإسبانية' }, IT: { fr: 'Italien', en: 'Italian', ar: 'الإيطالية' }, DE: { fr: 'Allemand', en: 'German', ar: 'الألمانية' }, AR: { fr: 'Arabe', en: 'Arabic', ar: 'العربية' }, }; /* ---- brand mark: multicolor Moroccan khatim rosette ---- */ function BrandMark({ size = 42 }) { return ( ); } function Logo({ lang, href = '#/' }) { return ( GoToMorocco {T(lang, 'brandSub')} ); } /* ---- currency switch ---- */ function CurrencySwitch({ lang }) { const [cur, setCur] = useState(window.ATLAS.getCurrency()); const [open, setOpen] = useState(false); useEffect(() => window.ATLAS.subscribeCurrency(() => setCur(window.ATLAS.getCurrency())), []); const sym = (c) => lang === 'ar' ? c.symAr : c.sym; return (
{open && (
setOpen(false)}> {window.ATLAS.currencies.map(c => ( ))}
)}
); } /* ---- animated count-up (triggers when scrolled into view) ---- */ function CountUp({ end, suffix = '', dur = 1400 }) { const [v, setV] = useState(0); const ref = useRef(null); const started = useRef(false); useEffect(() => { const el = ref.current; if (!el) return; const run = () => { if (started.current) return; started.current = true; const t0 = performance.now(); const tick = (now) => { const p = Math.min(1, (now - t0) / dur); setV(Math.round(end * (1 - Math.pow(1 - p, 3)))); if (p < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); }; const io = new IntersectionObserver((es) => es.forEach(e => { if (e.isIntersecting) run(); }), { threshold: 0.4 }); io.observe(el); return () => io.disconnect(); }, [end, dur]); return {v}{suffix}; } /* ---- TripAdvisor rating bubbles + mark ---- */ function RatingBubbles({ value = 5, size = 15 }) { return ( {[0, 1, 2, 3, 4].map(i => { const cls = value >= i + 1 ? 'full' : value > i ? 'half' : 'empty'; return ; })} ); } function TripAdvisorMark({ small }) { return ( Tripadvisor ); } /* ---- language switch ---- */ function LangSwitch({ lang, setLang, compact }) { const [open, setOpen] = useState(false); const cur = window.ATLAS.langs.find(l => l.code === lang) || window.ATLAS.langs[0]; if (compact) { // inline pills (used in mobile menu) return (
{window.ATLAS.langs.map(l => ( ))}
); } return (
{open && (
setOpen(false)}> {window.ATLAS.langs.map(l => ( ))}
)}
); } /* ---- status badge ---- */ function StatusBadge({ status, lang }) { const map = { pending: 'pending', confirmed: 'confirmed', progress: 'progress', done: 'done', refused: 'refused', }; return ( {T(lang, 'st_' + status)} ); } /* ---- vehicle photo slot (persists across pages via shared id) ---- */ function VehicleSlot({ vehId, lang, height = 200, radius = 14 }) { const ref = useRef(null); useEffect(() => { if (ref.current) { ref.current.setAttribute('id', 'veh-' + vehId); ref.current.setAttribute('shape', 'rounded'); ref.current.setAttribute('radius', String(radius)); ref.current.setAttribute('placeholder', T(lang, 'veh_' + vehId + '_t')); var _src = window.ATLAS.photo('veh-' + vehId); if (_src) ref.current.setAttribute('src', _src); ref.current.style.width = '100%'; ref.current.style.height = height + 'px'; } }, [vehId, lang, height, radius]); return React.createElement('image-slot', { ref }); } /* ---- small inline icon set (geometric only) ---- */ function Ic({ name, size = 18 }) { const s = { width: size, height: size, display: 'block' }; const sw = 1.6; const common = { fill: 'none', stroke: 'currentColor', strokeWidth: sw, strokeLinecap: 'round', strokeLinejoin: 'round' }; const paths = { arrow: , check: , x: , user: , pin: , cal: , clock: , users: , car: , chat: , send: , search: , money: , grid: , list: , bag: , star: , globe: , plane: , plus: , phone: , }; return ; } Object.assign(window, { useLang, BrandMark, Logo, LangSwitch, CurrencySwitch, CountUp, RatingBubbles, TripAdvisorMark, StatusBadge, VehicleSlot, Ic, T, Flag, LANG_NAMES });