/* Sections — DCAF microsite. Each section is a stand-alone React component
that reads from props.t (translated content tree) and props.lang.
*/
const { useState, useEffect, useRef } = React;
// ─── DCAF logo (inline) ───
function DCAFLogo({ height = 18, color = "currentColor" }) {
return (
);
}
// ─── Reveal on scroll ───
function Reveal({ children, delay = 0, as = "div", className = "" }) {
const ref = useRef(null);
const [shown, setShown] = useState(false);
useEffect(() => {
if (!ref.current) return;
let done = false;
const mark = () => { if (!done) { done = true; setShown(true); } };
// Safety: if IntersectionObserver doesn't fire (some preview iframes),
// show after a short delay anyway. The cascade still feels staggered.
const fallback = setTimeout(mark, 350);
let obs;
try {
obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { mark(); obs.disconnect(); } }, { threshold: 0.05, rootMargin: '0px 0px -10% 0px' });
obs.observe(ref.current);
} catch (e) { mark(); }
return () => { clearTimeout(fallback); if (obs) obs.disconnect(); };
}, []);
const Tag = as;
return {children};
}
const ArrowR = ({ size = 14 }) => (
);
// ─── Header ───
function SiteHeader({ t, lang, setLang, theme }) {
const [scrolled, setScrolled] = useState(false);
const [onPaper, setOnPaper] = useState(false);
useEffect(() => {
const onScroll = () => {
setScrolled(window.scrollY > 40);
// detect background of element under header center
const el = document.elementFromPoint(window.innerWidth / 2, 60);
if (el) {
const closest = el.closest('.facility, .enquiry');
setOnPaper(!!closest);
}
};
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
return (
);
}
// ─── Hero ───
function Hero({ t, lang, heroImg }) {
const dir = lang === 'ar' ? 'rtl' : 'ltr';
return (
Live · 24h ops · OMDW
Position
{t.hero.coords[0]}
{t.hero.coords[1]}
{t.hero.coords[2]}
● {t.hero.bearing}
{t.hero.eyebrow}
{t.hero.h1a}{t.hero.h1b}{t.hero.h1c}
scroll
);
}
// ─── Ticker ───
function Ticker({ t }) {
// duplicate so the loop is seamless
const items = [...t.ticker, ...t.ticker, ...t.ticker];
return (
{items.map((line, i) => (
{line}
))}
);
}
// ─── JV strip ───
function JVStrip({ t, lang }) {
return (
{t.jv.left}
{t.jv.leftEm}
"{t.jv.center}"
{t.jv.right}
{t.jv.rightEm}
);
}
// ─── Services ───
function Services({ t }) {
return (
{t.services.eyebrow}
{t.services.h2a}{t.services.h2b}{t.services.h2c}
{t.services.lede}
);
}
// ─── Facility specs ───
function Facility({ t }) {
return (
{t.facility.eyebrow}
{t.facility.h2}
{t.facility.lede}
{t.facility.specs.map((s, i) => (
/ {s.num}
{s.label}{s.desc}
{s.value}{s.unit.includes('m²') ? '' : ''} {s.unit}
))}
{t.facility.chips.map((c, i) => (
{c}
))}
);
}
// ─── Charter fleet ───
function Charter({ t }) {
const [active, setActive] = useState('g650');
return (
{t.charter.eyebrow}
{t.charter.h2}
{t.charter.lede}
{t.charter.fleet.map((f, i) => (
setActive(f.id)}>
{f.img ?

: (
)}
{f.tag}
{f.name}
{f.range}
PAX{f.pax}
BEDS{f.beds}
WI-FI{f.wifi}
))}
{t.charter.note}
);
}
// ─── Credentials ───
function Credentials({ t }) {
return (
{t.credentials.eyebrow}
{t.credentials.h2a}{t.credentials.h2b}{t.credentials.h2c}
{t.credentials.lede}
{t.credentials.quoteSrc}
"{t.credentials.quote}"
{t.credentials.badges.map((b, i) => (
{b.num}{b.numEm && {b.numEm}}
{b.title}{b.sub}
))}
);
}
// ─── Enquiry (multi-step form) ───
function Enquiry({ t, lang }) {
const [step, setStep] = useState(0);
const [submitted, setSubmitted] = useState(false);
const [data, setData] = useState({ service: '', name: '', company: '', email: '', phone: '', aircraft: '', from: 'OMDW', to: '', date: '', pax: '', message: '' });
const L = t.enquiry.labels;
const set = (k, v) => setData(d => ({ ...d, [k]: v }));
const canNext = () => {
if (step === 0) return !!data.service;
if (step === 1) return data.from && data.to && data.date;
if (step === 2) return data.name && data.email;
return false;
};
const ref = String(Math.floor(100000 + Math.random() * 900000));
return (
{t.enquiry.eyebrow}
{t.enquiry.h2a}{t.enquiry.h2b}.
{t.enquiry.lede}
{t.enquiry.contact.map((c, i) => (
{c.k}
{c.v}
))}
Direct response
Our 24-hour Dubai desk handles every enquiry — no offshore call centre, no IVR. Average first response is under an hour.
{submitted ? (
{t.enquiry.success.h}
{t.enquiry.success.p}DCAF-{ref}
) : (
{t.enquiry.steps.map((s, i) => (
))}
{step === 0 && (
{t.enquiry.svcOptions.map(o => (
))}
)}
{step === 1 && (
)}
{step === 2 && (
)}
{step > 0 ?
:
}
{step < 2 ? (
) : (
)}
)}
);
}
// ─── Footer ───
function Footer({ t }) {
return (
);
}
// ─── Sticky CTA ───
function StickyCTA({ t }) {
const [show, setShow] = useState(false);
useEffect(() => {
const onScroll = () => setShow(window.scrollY > window.innerHeight * 0.6);
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
return (
);
}
Object.assign(window, { SiteHeader, Hero, Ticker, JVStrip, Services, Facility, Charter, Credentials, Enquiry, Footer, StickyCTA, DCAFLogo, ArrowR, Reveal });