/* ============================================================
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 (
);
}
return (
);
}
/* ---------- 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 && (
{LANGS.map(l => (
-
))}
)}
);
}
/* ---------- 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
);
}
/* ---------- 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 (
{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")}
);
}
/* ---------- 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) => (
{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 => (
))}
);
}
/* ---------- 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")}
);
}
/* ---------- 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 (
{t("loc.coords")}
{coordsStr}
{t("loc.address")}
{addressStr}
);
}
/* ---------- 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.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 (
{t("pix.scanHere")}
{t("pix.title")}
{t("pix.desc")}
- {t("pix.step1")}
- {t("pix.step2")}
- {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")}
- {t("book.confirmedNext1")}
- {t("book.confirmedNext2")}
- {t("book.confirmedNext3")}
{/* Print-only header (visible only when window.print) */}
Ω
HOTEL OMEGA
744 Rua Getúlio Vargas · Oiapoque · Amapá · Brasil
tadaniel722@gmail.com · +594 6 94 20 36 24
{t("book.printTitle")}
);
}
/* ---------- 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 (
<>
>
);
}
/* ---------- TWEAKS PANEL ---------- */
function TweakControls() {
const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
// Apply tweaks to :root
useEffect(() => {
const r = document.documentElement;
r.style.setProperty("--accent-h", t.accentHue);
r.style.setProperty("--accent-c", t.accentChroma);
r.dataset.density = t.density;
r.dataset.images = t.showImages ? "on" : "off";
r.dataset.heroVariant = t.heroVariant;
r.dataset.watermark = t.showOmegaWatermark ? "on" : "off";
}, [t]);
return (
setTweak("accentHue", v)}
/>
setTweak("accentChroma", v)}
/>
setTweak("density", v)}
/>
setTweak("heroVariant", v)}
/>
setTweak("showOmegaWatermark", v)}
/>
setTweak("showImages", v)}
/>
);
}
/* ---------- ROOT ---------- */
function App() {
const [view, setView] = useState("home");
const [bookingOpen, setBookingOpen] = useState(false);
const [selectedRoom, setSelectedRoom] = useState("D");
const go = (id, tab) => {
setView(id);
if (id === "policies" && tab) {
try {
window.history.replaceState(null, "", `#policies?tab=${tab}`);
} catch (_) {}
}
window.scrollTo({ top: 0, behavior: "instant" });
};
useEffect(() => {
window.__omegaGo = go;
return () => { delete window.__omegaGo; };
}, []);
const openBooking = () => setBookingOpen(true);
const closeBooking = () => setBookingOpen(false);
// Apply default tweak vars on mount (so it works without panel open)
useEffect(() => {
const r = document.documentElement;
r.style.setProperty("--accent-h", TWEAK_DEFAULTS.accentHue);
r.style.setProperty("--accent-c", TWEAK_DEFAULTS.accentChroma);
r.dataset.density = TWEAK_DEFAULTS.density;
r.dataset.images = TWEAK_DEFAULTS.showImages ? "on" : "off";
r.dataset.heroVariant = TWEAK_DEFAULTS.heroVariant;
r.dataset.watermark = TWEAK_DEFAULTS.showOmegaWatermark ? "on" : "off";
}, []);
/* ---------- ACCÈS ADMIN CACHÉ ----------
3 méthodes (toutes silencieuses) :
1. Tape "omega" au clavier n'importe où sur le site
2. Ajoute #admin (ou ?admin=1) à l'URL
3. Triple-clic sur le Ω discret du footer (handler dans Footer)
*/
useEffect(() => {
// 1. URL hash / query
const checkUrl = () => {
const h = window.location.hash.toLowerCase();
const q = window.location.search.toLowerCase();
if (h === "#admin" || q.includes("admin=1")) {
setView("admin");
}
};
checkUrl();
window.addEventListener("hashchange", checkUrl);
// 2. Keyboard sequence "omega"
const SEQ = "omega";
let buffer = "";
let timer = null;
const onKey = (e) => {
// Ignore if user is typing in an input
const tag = (e.target && e.target.tagName) || "";
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
const k = (e.key || "").toLowerCase();
if (!/^[a-z]$/.test(k)) { buffer = ""; return; }
buffer = (buffer + k).slice(-SEQ.length);
if (timer) clearTimeout(timer);
timer = setTimeout(() => { buffer = ""; }, 1500);
if (buffer === SEQ) {
buffer = "";
setView("admin");
}
};
window.addEventListener("keydown", onKey);
// 3. Custom event from footer triple-click
const onSecret = () => setView("admin");
window.addEventListener("omega-secret-admin", onSecret);
return () => {
window.removeEventListener("hashchange", checkUrl);
window.removeEventListener("keydown", onKey);
window.removeEventListener("omega-secret-admin", onSecret);
if (timer) clearTimeout(timer);
};
}, []);
return (
<>
{view === "home" && (
)}
{view === "rooms" && }
{view === "experience" && }
{view === "about" && }
{view === "contact" && }
{view === "faq" && }
{view === "policies" && }
{view === "admin" && }
{window.WhatsAppFAB && }
{window.CookiesBanner && }
>
);
}
ReactDOM.createRoot(document.getElementById("app")).render();