/* ============================================================ HÔTEL OMÉGA — Refonte App React (montée dans #app) ============================================================ */ const { useState, useEffect, useRef, useMemo } = React; /* ---------- DATA ---------- */ const IMG = { room: "assets/room-double.jpg", view: "assets/view-window.jpg", bath: "assets/bathroom.jpg", facade: "assets/facade-night.jpg?v=2", }; /* Rooms — translatable fields read via t(), structural data here */ const ROOM_DATA = [ { id: "D", num: "—", type: "Double", capacity: 2, price: 250, available: true, img: IMG.room, featureKeys: ["room.feat.ac", "room.feat.wifi", "room.feat.bath", "room.feat.linen"], }, { id: "T", num: "—", type: "Family", capacity: 4, price: 300, available: true, img: IMG.bath, featureKeys: ["room.feat.ac", "room.feat.wifi", "room.feat.bath", "room.feat.linen"], }, ]; function getRooms(t) { return ROOM_DATA.map(r => ({ ...r, name: t(`room.${r.id}.name`), bed: t(`room.${r.id}.bed`), desc: t(`room.${r.id}.desc`), placeholder: t(`room.${r.id}.placeholder`), features: r.featureKeys.map(k => t(k)), })); } function getHeroSlides(t) { return [ { eyebrow: t("hero.s1.eyebrow"), t1: t("hero.s1.t1"), tEm: t("hero.s1.tEm"), t2: t("hero.s1.t2"), sub: t("hero.s1.sub"), img: IMG.facade }, { eyebrow: t("hero.s2.eyebrow"), t1: t("hero.s2.t1"), tEm: t("hero.s2.tEm"), t2: t("hero.s2.t2"), sub: t("hero.s2.sub"), img: IMG.room }, { eyebrow: t("hero.s3.eyebrow"), t1: t("hero.s3.t1"), tEm: t("hero.s3.tEm"), t2: t("hero.s3.t2"), sub: t("hero.s3.sub"), img: IMG.view }, { eyebrow: t("hero.s4.eyebrow"), t1: t("hero.s4.t1"), tEm: t("hero.s4.tEm"), t2: t("hero.s4.t2"), sub: t("hero.s4.sub"), img: IMG.bath }, ]; } function getNavLinks(t) { return [ { id: "home", label: t("nav.home") }, { id: "rooms", label: t("nav.rooms") }, { id: "experience", label: t("nav.experience") }, { id: "about", label: t("nav.about") }, { id: "faq", label: t("nav.faq") }, { id: "contact", label: t("nav.contact") }, ]; } /* ---------- CURRENCY ---------- */ /* Indicative rates BRL → EUR / USD. Update periodically. Last refresh: January 2026. */ const FX = { EUR: 0.165, // 1 BRL ≈ 0.165 EUR USD: 0.180, // 1 BRL ≈ 0.180 USD }; /* Display a price with discreet EUR/USD conversion underneath */ function Price({ brl, layout = "row", showFrom = false }) { const [t] = window.useT(); const eur = Math.round(brl * FX.EUR); const usd = Math.round(brl * FX.USD); return (
{showFrom && {t("rooms.from")}} {brl} R$ {t("cur.approx")} {eur}{t("cur.eur")} · {t("cur.approx")} {usd}{t("cur.usd")}
); } /* ---------- DEFAULTS for Tweaks ---------- */ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accentHue": 78, "accentChroma": 0.11, "density": "comfortable", "showOmegaWatermark": true, "showImages": true, "heroVariant": "calm" }/*EDITMODE-END*/; /* ---------- PLACEHOLDER IMAGE ---------- */ function Placeholder({ label, ratio = "4/3", small = false, src = null }) { // If images are turned off via tweak, use striped placeholder const imgsOn = typeof document !== "undefined" && document.documentElement.dataset.images !== "off"; if (src && imgsOn) { return (
{label}
); } return (
{label}
); } /* ---------- HERO SLIDER ---------- */ function HeroSlider({ openBooking, go }) { const [t] = window.useT(); const slides = getHeroSlides(t); const [idx, setIdx] = useState(0); const total = slides.length; useEffect(() => { const id = setInterval(() => setIdx(i => (i + 1) % total), 6500); return () => clearInterval(id); }, [total]); return (
{slides.map((s, i) => (
))}
{t("hero.foundedIn")}
{t("hero.location")}
{slides.map((s, i) => (
{s.eyebrow}

{s.t1}{s.tEm}{s.t2}

{s.sub}
))}
{slides.map((_, i) => (
{String(idx + 1).padStart(2, "0")} / {String(total).padStart(2, "0")}
); } /* ---------- OMEGA MARK ---------- */ function OmegaMark({ size = 1, animated = false }) { return ( Ω ); } /* ---------- LANGUAGE PICKER ---------- */ const LANGS = [ { code: "fr", label: "Français", flag: ( ), }, { code: "en", label: "English", flag: ( ), }, { code: "pt", label: "Português", flag: ( ), }, ]; function LangPicker() { const [, lang, setLangFn] = window.useT(); const [open, setOpen] = useState(false); const ref = useRef(null); useEffect(() => { const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener("mousedown", onDoc); return () => document.removeEventListener("mousedown", onDoc); }, []); const current = LANGS.find(l => l.code === lang) || LANGS[0]; const pick = (code) => { setLangFn(code); setOpen(false); }; return (
{open && ( )}
); } /* ---------- NAV ---------- */ function Nav({ current, go, openBooking }) { const [t] = window.useT(); const links = getNavLinks(t); const [scrolled, setScrolled] = useState(false); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 24); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); return ( ); } /* ---------- HERO ---------- */ function Hero({ go, openBooking, variant }) { const [t] = window.useT(); return (
{t("hero.metaLeft")}
{t("hero.metaRight")}
{t("hero.eyebrow")}

{t("hero.title1")}{t("hero.titleEm")}
{t("hero.title2")}

{t("hero.sub1")}
{t("hero.sub2")}
{t("hero.foot.rooms")}
{t("hero.foot.roomsVal")}
{t("rooms.from")}
250 R$ {t("rooms.night")}
{t("hero.foot.rating")}
9.4 / 10
{t("hero.foot.scroll")}
); } /* ---------- INTRO STRIP ---------- */ function Intro() { const [t] = window.useT(); return (
{t("intro.eyebrow")}

{t("intro.quote")}

{t("intro.sign")}
); } /* ---------- ROOMS PREVIEW (home) ---------- */ function RoomsPreview({ openBooking, go, setSelected }) { const [t] = window.useT(); const rooms = getRooms(t); return (
{t("rooms.eyebrow")}

{t("rooms.preview.title1")}{t("rooms.preview.titleEm")}{t("rooms.preview.title2")}
{t("rooms.preview.title3")}

{rooms.map((r, i) => (
N° {r.num}
{!r.available &&
{t("rooms.full")}
}
{r.type} · {r.capacity} {t("rooms.shortPersons")}

{r.name}

{r.desc}

))}
); } /* ---------- EXPERIENCE ---------- */ function getExpItems(t) { return [1, 2, 3, 4, 5, 6].map(i => ({ n: String(i).padStart(2, "0"), t: t(`exp.${i}.t`), d: t(`exp.${i}.d`), k: t(`exp.${i}.k`), })); } function Experience() { const [t] = window.useT(); const items = getExpItems(t); return (
{t("exp.eyebrow")}

{t("exp.title1")}{t("exp.titleEm")}{t("exp.title2")}

{items.map(e => (
{e.n}
{e.k}

{e.t}

{e.d}

))}
); } /* ---------- ROOMS PAGE ---------- */ function RoomsPage({ openBooking, setSelected }) { const [t] = window.useT(); const rooms = getRooms(t); return (
{t("rooms.eyebrow")}

{t("rooms.title1")}{t("rooms.titleEm")}{t("rooms.title2")}

{t("rooms.sub")}

{rooms.map((r, i) => (
N° {r.num}
{r.type} · {r.capacity} {t("rooms.persons")}

{r.name}

{r.desc}

{t("rooms.bed")} {r.bed}
    {r.features.map(f => (
  • · {f}
  • ))}
))}
); } /* ---------- ABOUT PAGE ---------- */ function AboutPage() { const [t] = window.useT(); return (
{t("about.eyebrow")}

{t("about.title1")}{t("about.titleEm")}

{t("about.sub")}

{t("about.h1")}

{t("about.p1")}

{t("about.h2")}

{t("about.p2")}

{t("about.h3")}

{t("about.p3")}

{[ { n: "2026", l: t("about.num1.l") }, { n: "02", l: t("about.num2.l") }, { n: "9.4", l: t("about.num3.l") }, { n: "12", l: t("about.num4.l") }, ].map(x => (
{x.n}
{x.l}
))}
); } /* ---------- EXPERIENCE PAGE ---------- */ function ExperiencePage() { const [t] = window.useT(); const items = getExpItems(t); return (
{t("exp.eyebrow")}

{t("exp.page.title1")}{t("exp.page.titleEm")}{t("exp.page.title2")}

{t("exp.page.sub")}

{items.map(e => (
{e.n}
{e.k}

{e.t}

{e.d}

))}
); } /* ---------- LOCATION MAP (custom SVG, always works offline) ---------- */ function LocationMap() { const [t] = window.useT(); const lat = 3.8420; const lng = -51.8336; const coordsStr = `${lat}, ${lng}`; const addressStr = "744 Rua Getúlio Vargas, Oiapoque, AP, Brasil"; // Multiple map providers — at least one will work for any user const osmUrl = `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lng}#map=16/${lat}/${lng}`; const appleUrl = `https://maps.apple.com/?ll=${lat},${lng}&q=Hotel+Omega&z=16`; const wazeUrl = `https://www.waze.com/ul?ll=${lat}%2C${lng}&navigate=yes`; const geoUri = `geo:${lat},${lng}?q=${lat},${lng}(Hotel+Omega)`; const [copied, setCopied] = useState(""); const copy = (text, key) => { const fallback = () => { const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); try { document.execCommand("copy"); } catch (e) {} document.body.removeChild(ta); }; if (navigator.clipboard?.writeText) navigator.clipboard.writeText(text).catch(fallback); else fallback(); setCopied(key); setTimeout(() => setCopied(""), 2000); }; return (
Rio Oyapock GUYANE FR. ↑ Saint-Georges BRASIL · AP Pont Oyapock Aéroport OYK Embarcadère CENTRO Ω HÔTEL OMÉGA OIAPOQUE · AP N 3°50'31"N · 51°50'01"W
{t("loc.coords")}
{coordsStr}
{t("loc.address")}
{addressStr}
📍 {t("loc.openMyApp")} OpenStreetMap ↗ Apple Maps ↗ Waze ↗
); } /* ---------- LOCATION SECTION (used inside ContactPage) ---------- */ function LocationSection() { const [t] = window.useT(); const landmarks = [ { key: "loc.lm.airport", dist: "8 min", note: "loc.lm.airportNote" }, { key: "loc.lm.border", dist: "12 min", note: "loc.lm.borderNote" }, { key: "loc.lm.river", dist: "5 min", note: "loc.lm.riverNote" }, { key: "loc.lm.center", dist: "3 min", note: "loc.lm.centerNote" }, ]; const routes = [ { key: "loc.r.cayenne", dur: "loc.r.cayenneDur", note: "loc.r.cayenneNote" }, { key: "loc.r.macapa", dur: "loc.r.macapaDur", note: "loc.r.macapaNote" }, { key: "loc.r.belem", dur: "loc.r.belemDur", note: "loc.r.belemNote" }, ]; return (
{t("loc.eyebrow")}

{t("loc.title1")}{t("loc.titleEm")}{t("loc.title2")}

{t("loc.lead")}

{t("loc.aroundTitle")}

    {landmarks.map((l, i) => (
  • {l.dist}
    {t(l.key)}
    {t(l.note)}
  • ))}

{t("loc.routesTitle")}

    {routes.map((r, i) => (
  • {t(r.key)}
    {t(r.dur)}
    {t(r.note)}
  • ))}
Ω
{t("loc.shuttleEyebrow")}

{t("loc.shuttleTitle")}

{t("loc.shuttleDesc")}

); } /* ---------- CONTACT PAGE ---------- */ function ContactPage() { const [t] = window.useT(); const addr = t("contact.addressVal").split("\n"); return (
{t("contact.eyebrow")}

{t("contact.title1")}{t("contact.titleEm")}{t("contact.title2")}

{t("contact.address")}
{addr[0]}
{addr[1]}
{t("contact.phone")}
+594 6 94 20 36 24
{t("contact.email")}
tadaniel722@gmail.com
{t("contact.social")}
{t("contact.socialVal")}
{t("contact.front")}
{t("contact.frontVal")}
); } /* ---------- FOOTER ---------- */ function Footer() { const [t] = window.useT(); // Triple-click on the discreet Ω reveals admin const clickRef = useRef({ count: 0, timer: null }); const onOmegaClick = () => { const c = clickRef.current; c.count += 1; if (c.timer) clearTimeout(c.timer); if (c.count >= 3) { c.count = 0; window.dispatchEvent(new CustomEvent("omega-secret-admin")); } else { c.timer = setTimeout(() => { c.count = 0; }, 700); } }; return ( ); } /* ---------- PIX QR CODE ---------- */ function PixBox({ amount, txid, description }) { const [t] = window.useT(); const [copied, setCopied] = useState(false); const cfg = window.PIX_CONFIG || {}; const payload = useMemo(() => { if (!window.buildPixPayload || !cfg.key) return ""; try { return window.buildPixPayload({ key: cfg.key, name: cfg.name, city: cfg.city, amount: amount, txid: txid, description: description, }); } catch (e) { console.error("Pix payload error:", e); return ""; } }, [amount, txid, description]); const matrix = useMemo(() => { if (!payload || !window.generateQRMatrix) return null; try { return window.generateQRMatrix(payload); } catch (e) { console.error("QR error:", e); return null; } }, [payload]); const copy = () => { if (!payload) return; const fallback = () => { const ta = document.createElement("textarea"); ta.value = payload; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); try { document.execCommand("copy"); } catch (e) {} document.body.removeChild(ta); }; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(payload).catch(fallback); } else { fallback(); } setCopied(true); setTimeout(() => setCopied(false), 2000); }; if (!payload || !matrix) { return
{t("book.pixNote")}
; } // Render QR as SVG const n = matrix.length; const cell = 6; const margin = 4; const sz = (n + 2 * margin) * cell; const rects = []; for (let r = 0; r < n; r++) { for (let c = 0; c < n; c++) { if (matrix[r][c]) { rects.push( ); } } } return (
{rects}
{t("pix.scanHere")}
{t("pix.title")}

{t("pix.desc")}

  1. {t("pix.step1")}
  2. {t("pix.step2")}
  3. {t("pix.step3")}
{t("pix.amount")} {Number(amount).toFixed(2)} R$
{t("pix.to")} {cfg.name}
{t("pix.copyCode")}
{payload}
); } /* ---------- BOOKING CONFIRMATION (printable + emailable) ---------- */ function BookingConfirmation({ room, dates, guest, nights, total, pay, close, bookingRef }) { const [t, lang] = window.useT(); // Stable reference number — use the one from BookingDrawer if provided (so it matches DB + emails) const ref = useMemo(() => { if (bookingRef) return bookingRef; const seed = (room?.id || "X") + (dates.ci || "") + (guest.email || "") + Date.now(); let h = 0; for (let i = 0; i < seed.length; i++) h = ((h << 5) - h + seed.charCodeAt(i)) | 0; const code = Math.abs(h).toString(36).toUpperCase().slice(0, 6).padStart(6, "0"); const ymd = (dates.ci || "").replace(/-/g, "").slice(2); return `OMG-${ymd}-${code}`; }, [bookingRef]); const eur = Math.round(total * FX.EUR); const usd = Math.round(total * FX.USD); const payLabel = pay === "card" ? t("book.card") : pay === "paypal" ? "PayPal" : "Pix"; const fmtDate = (s) => { if (!s) return "—"; try { const d = new Date(s + "T00:00:00"); return d.toLocaleDateString(lang === "fr" ? "fr-FR" : lang === "pt" ? "pt-BR" : "en-US", { weekday: "short", day: "2-digit", month: "long", year: "numeric" }); } catch (_) { return s; } }; const onPrint = () => { document.body.classList.add("is-printing"); setTimeout(() => { window.print(); setTimeout(() => document.body.classList.remove("is-printing"), 500); }, 50); }; const onEmail = () => { const subject = t("book.emailSubject"); const lines = [ t("book.emailBody"), "", `${t("book.confirmedRef")}: ${ref}`, "", `${t("book.guestLbl")}: ${guest.first} ${guest.last}`, `${t("book.contactLbl")}: ${guest.email}${guest.phone ? " · " + guest.phone : ""}`, "", `${t("book.stayLbl")}:`, ` ${t("book.recapRoom")}: ${room?.name || ""} (${room?.type || ""})`, ` ${t("book.checkin")}: ${fmtDate(dates.ci)}`, ` ${t("book.checkout")}: ${fmtDate(dates.co)}`, ` ${t("book.nights")}: ${nights}`, "", `${t("book.payLbl")}: ${payLabel}`, `${t("book.recapTotal")}: ${total} R$ (≈ ${eur}€ · ≈ ${usd}$)`, "", "— Hotel Omega", "744 Rua Getúlio Vargas, Oiapoque, AP, Brasil", "tadaniel722@gmail.com", ].join("\n"); const url = `mailto:${guest.email}?cc=tadaniel722@gmail.com&subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(lines)}`; window.location.href = url; }; return (
Ω
{t("book.confirmedEyebrow")}

{t("book.confirmedTitle")}

{t("book.confirmedText1")}{guest.email}{t("book.confirmedText2")}

{t("book.confirmedRef")} {ref}
{t("book.guestLbl")}
{t("book.first")} / {t("book.last")} {guest.first} {guest.last}
{t("book.email")} {guest.email}
{guest.phone && (
{t("book.phone")} {guest.phone}
)}
{t("book.stayLbl")}
{t("book.recapRoom")} {room?.name} ({room?.type})
{t("book.checkin")} {fmtDate(dates.ci)}
{t("book.checkout")} {fmtDate(dates.co)}
{t("book.nights")} {nights}
{t("book.payLbl")}
{t("book.payLbl")} {payLabel}
{t("book.recapTotal")} {total} R$ · ≈ {eur}€ · ≈ {usd}$
{guest.req && ( <>
{t("book.requests")}
{guest.req}
)}
{t("book.confirmedNext")}
  1. {t("book.confirmedNext1")}
  2. {t("book.confirmedNext2")}
  3. {t("book.confirmedNext3")}
{/* Print-only header (visible only when window.print) */}
); } /* ---------- BOOKING DRAWER ---------- */ function BookingDrawer({ open, close, selected, setSelected }) { const [step, setStep] = useState(1); const [dates, setDates] = useState({ ci: "", co: "" }); const [guest, setGuest] = useState({ first: "", last: "", email: "", phone: "", req: "" }); const [pay, setPay] = useState("card"); const [done, setDone] = useState(false); const [bookingError, setBookingError] = useState(null); const [submitting, setSubmitting] = useState(false); const [bookingRef, setBookingRef] = useState(null); useEffect(() => { if (open) { setStep(1); setDone(false); setBookingError(null); setSubmitting(false); setBookingRef(null); } }, [open]); const [t] = window.useT(); const localizedRooms = getRooms(t); const room = localizedRooms.find(r => r.id === selected) || null; const nights = useMemo(() => { if (!dates.ci || !dates.co) return 0; const a = new Date(dates.ci), b = new Date(dates.co); const d = Math.max(0, Math.round((b - a) / 864e5)); return d; }, [dates]); const total = room && nights > 0 ? room.price * nights : 0; const canNext1 = !!room; const canNext2 = dates.ci && dates.co && nights > 0; const canConfirm = guest.first && guest.last && guest.email; return ( <>