/* ============================================================
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 });