// App shell: Nav, locale switcher, consultation modal, mobile menu, sticky CTAs, and composition.
const LOCALES = [
{ id: "en", label: "EN" },
{ id: "ar", label: "AR" },
{ id: "ru", label: "RU" },
{ id: "zh", label: "ZH" },
];
function useLocale() {
const [locale, setLocale] = useState(() => {
try { return localStorage.getItem("cb_locale") || "en"; } catch { return "en"; }
});
useEffect(() => {
window.__cbLocale = locale;
try { localStorage.setItem("cb_locale", locale); } catch {}
const dir = window.CBI18N[locale].dir;
document.documentElement.setAttribute("dir", dir);
document.documentElement.setAttribute("lang", locale);
}, [locale]);
return [locale, setLocale];
}
function Nav({ locale, setLocale, onBook, openMenu }) {
const [scrolled, setScrolled] = useState(false);
const t = window.CBI18N[locale];
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 24);
window.addEventListener("scroll", onScroll);
onScroll();
return () => window.removeEventListener("scroll", onScroll);
}, []);
return (
);
}
function LocaleSwitcher({ locale, setLocale }) {
return (
{IconSet.globe}
{LOCALES.map(l => (
setLocale(l.id)}
>{l.label}
))}
);
}
// ─── Consultation modal ───────────────────────────────────────────────
function ConsultationModal({ open, onClose }) {
const [step, setStep] = useState(0);
const [form, setForm] = useState({ service: "", jurisdiction: "", name: "", contact: "", message: "" });
useEffect(() => {
if (open) { setStep(0); setForm({ service: "", jurisdiction: "", name: "", contact: "", message: "" }); }
}, [open]);
useEffect(() => {
if (!open) return;
const onKey = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open, onClose]);
if (!open) return null;
const services = window.CBDATA.services;
const jurs = window.CBDATA.jurisdictions;
const next = () => setStep(s => Math.min(s + 1, 3));
const back = () => setStep(s => Math.max(s - 1, 0));
const submit = () => setStep(3);
return (
{ if (e.target.classList.contains("cb-modal")) onClose(); }}>
{IconSet.close}
Book a free consultation
{[0, 1, 2].map(i => )}
{step === 0 && (
What's the focus?
Pick the closest fit — your consultant will refine.
{services.map(s => (
{ setForm(f => ({ ...f, service: s.id })); next(); }} className={`cb-modal__choice ${form.service === s.id ? "is-on" : ""}`}>
{IconSet[SERVICE_ICON[s.id]]}
{s.name}
))}
)}
{step === 1 && (
Where are you setting up?
If you're not sure yet, pick "Not sure" — that's what we're here for.
{[...jurs, { id: "not-sure", name: "Not sure yet" }].map(j => (
{ setForm(f => ({ ...f, jurisdiction: j.id })); next(); }} className={`cb-modal__choice cb-modal__choice--text ${form.jurisdiction === j.id ? "is-on" : ""}`}>
{j.name}
))}
{IconSet.arrowL} Back
)}
{step === 2 && (
)}
{step === 3 && (
{IconSet.check}
You're booked.
A Commitbiz advisor will WhatsApp you within one business day to confirm a 30-minute consultation. No fixed fees promised — we'll size the engagement together.
Focus {services.find(s => s.id === form.service)?.name || "—"}
Where {form.jurisdiction === "not-sure" ? "Not sure yet" : (window.CBDATA.jurisdictions.find(j => j.id === form.jurisdiction)?.name || "—")}
Reaching you on {form.contact || "—"}
Close
)}
);
}
// ─── Mobile menu ───────────────────────────────────────────────────────
function MobileMenu({ open, onClose, locale, setLocale, onBook }) {
if (!open) return null;
const t = window.CBI18N[locale];
const links = [
["#services", t.nav.services],
["#jurisdictions", t.nav.jurisdictions],
["#estimator", t.nav.estimator],
["#guides", t.nav.guides],
["#offices", t.nav.offices],
["#about", t.nav.about],
["#contact", t.nav.contact],
];
return (
{IconSet.close}
{links.map(([h, l]) => {l}{IconSet.arrowUpRight} )}
{ onClose(); onBook(); }} className="cb-mm__cta">{t.nav.consult}
);
}
// ─── Sticky CTAs ────────────────────────────────────────────────────────
function StickyCTAs({ onBook }) {
const c = window.CBDATA.contact;
return (
);
}
// ─── App ────────────────────────────────────────────────────────────────
function App() {
const [locale, setLocale] = useLocale();
const [modal, setModal] = useState(false);
const [menu, setMenu] = useState(false);
const openBook = useCallback(() => setModal(true), []);
const openEstimate = useCallback(() => {
const el = document.getElementById("estimator");
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
}, []);
return (
setMenu(true)} />
setModal(false)} />
setMenu(false)} locale={locale} setLocale={setLocale} onBook={openBook} />
);
}
ReactDOM.createRoot(document.getElementById("root")).render( );