/* =========================================================
JETX LLC — Shared components (multi-page)
========================================================= */
const { useState, useEffect, useRef } = React;
/* ---------- counter that animates when triggered ---------- */
function useCountUp(target, options = {}) {
const { duration = 1600, decimals = 0, trigger = true } = options;
const [value, setValue] = useState(0);
const startRef = useRef(null);
const rafRef = useRef(null);
useEffect(() => {
if (!trigger) return;
const step = (ts) => {
if (!startRef.current) startRef.current = ts;
const p = Math.min((ts - startRef.current) / duration, 1);
const eased = 1 - Math.pow(1 - p, 5);
setValue(target * eased);
if (p < 1) rafRef.current = requestAnimationFrame(step);
};
rafRef.current = requestAnimationFrame(step);
return () => rafRef.current && cancelAnimationFrame(rafRef.current);
}, [target, duration, trigger]);
return decimals === 0 ? Math.round(value) : value.toFixed(decimals);
}
/* ---------- Reveal on scroll ---------- */
function Reveal({ children, className = "", delay = 0, as = "div", ...rest }) {
const ref = useRef(null);
const [shown, setShown] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
(entries) => entries.forEach((e) => {
if (e.isIntersecting) { setTimeout(() => setShown(true), delay); io.unobserve(el); }
}),
{ threshold: 0.12, rootMargin: "0px 0px -60px 0px" }
);
io.observe(el);
return () => io.disconnect();
}, [delay]);
const Tag = as;
return {children} ;
}
/* ---------- NAV ---------- */
const NAV_ITEMS = [
{ label: "Home", href: "index.html" },
{ label: "About", href: "about.html" },
{ label: "Technology", href: "projects.html" },
{ label: "Portfolio", href: "gallery.html" },
{ label: "News", href: "news.html" },
{ label: "Contact", href: "contactus.html" },
];
function Nav({ current }) {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 40);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
const here = current || (location.pathname.split("/").pop() || "index.html");
return (
{open && (
)}
);
}
/* ---------- SUBHERO (interior pages) ---------- */
function SubHero({ crumb, title, lede, image, flip, mirror }) {
const [entered, setEntered] = useState(false);
useEffect(() => { const id = setTimeout(() => setEntered(true), 60); return () => clearTimeout(id); }, []);
return (
{image
?
: {title}
}
{crumb}
{title}
{lede &&
{lede}
}
);
}
/* ---------- PARTNERS / PROGRAMS LOGO STRIP ---------- */
function PartnersStrip({ heading = "Validated with", note }) {
const logos = [
{ src: "assets/logos/army-white.png", label: "U.S. Army" },
{ src: "assets/logos/usaf-white.png", label: "U.S. Air Force" },
{ src: "assets/logos/sbir-white.png", label: "SBIR · STTR" },
{ src: "assets/logos/xtech-white.png", label: "Army xTech" },
{ src: "assets/logos/ucf-white.png", label: "University of Central Florida" },
{ src: "assets/logos/ttu-white.png", label: "Tennessee Tech" },
{ src: "assets/logos/cymstar-white.png", label: "CymSTAR" },
];
return (
{heading}
{note && {note} }
);
}
/* ---------- SOCIAL ICONS ---------- */
function SocialIcon({ name }) {
const p = { width: 18, height: 18, viewBox: "0 0 24 24", fill: "currentColor" };
if (name === "X") return (
);
if (name === "LinkedIn") return (
);
if (name === "YouTube") return (
);
if (name === "Instagram") return (
);
return null;
}
/* ---------- FOOTER ---------- */
function Footer() {
const socials = [
{ l: "X", href: "https://twitter.com/JETXAERO" },
{ l: "LinkedIn", href: "https://www.linkedin.com/company/jetxus/" },
{ l: "YouTube", href: "https://www.youtube.com/channel/UC3EqOdS30VVZnpXG4Lb_88Q" },
{ l: "Instagram", href: "https://www.instagram.com/jetx_us/" },
];
return (
);
}
/* ---------- Investor CTA strip ---------- */
function InvestorCTA() {
return (
Partnership · Collaboration
Partner with JETX.
We build the propulsion and platform technology behind the next generation of eVTOL
aircraft — for civil and military use. Let's collaborate on defense programs, AAM
platforms and concept development.
Start a conversation →
);
}
/* ---------- Propulsion diagram (spinning EDF) ---------- */
function PropulsionDiagram() {
return (
{[60, 120, 180].map((r) => (
))}
{[0, 60, 120, 180, 240, 300].map((deg) => (
))}
VECTOR
VENTRAL
VECTOR
EDF · NON-TILTING
FLUIDIC + FLAP VECTORING
{[[20,20],[380,20],[20,380],[380,380]].map(([x,y]) => (
))}
);
}
/* ---------- Propulsion hardware slider (auto-advancing) ---------- */
function PropulsionSlider() {
const slides = [
{ src: "assets/concepts/prop-fan-cascade.png", k: "01", l: "Electric Ducted Fan + Cascade" },
{ src: "assets/concepts/prop-edf-cone.png", k: "02", l: "Embedded EDF Module" },
{ src: "assets/concepts/prop-twin-nozzle.png", k: "03", l: "Twin Fluidic Nozzle Unit" },
{ src: "assets/concepts/prop-twin-pods.png", k: "04", l: "Vectoring Thruster Pods" },
];
const [i, setI] = useState(0);
const [paused, setPaused] = useState(false);
useEffect(() => {
if (paused) return;
const id = setInterval(() => setI((p) => (p + 1) % slides.length), 4200);
return () => clearInterval(id);
}, [paused, slides.length]);
return (
setPaused(true)}
onMouseLeave={() => setPaused(false)}
aria-label="JETX propulsion hardware"
>
{slides.map((s, idx) => (
))}
{[[16,16],[null,16],[16,null],[null,null]].map(([x,y], n) => (
))}
{slides[i].k}
{slides[i].l}
{slides.map((s, idx) => (
setI(idx)}
aria-label={`Show ${s.l}`}
/>
))}
);
}
/* ---------- Service-card image slider (auto-advancing) ---------- */
function ServiceSlider({ slides, fit = "cover" }) {
const [i, setI] = useState(0);
const [paused, setPaused] = useState(false);
useEffect(() => {
if (paused) return;
const id = setInterval(() => setI((p) => (p + 1) % slides.length), 4000);
return () => clearInterval(id);
}, [paused, slides.length]);
return (
setPaused(true)}
onMouseLeave={() => setPaused(false)}
>
{slides.map((src, idx) => (
))}
{slides.map((src, idx) => (
setI(idx)}
aria-label={`Show image ${idx + 1}`}
/>
))}
);
}
/* ---------- Page shell: Nav + content + Footer + Tweaks ---------- */
const JETX_TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"accent": "#FFFFFF",
"density": "comfort"
}/*EDITMODE-END*/;
function PageShell({ current, children }) {
const [t, setTweak] = window.useTweaks(JETX_TWEAK_DEFAULTS);
useEffect(() => {
const map = { "#FFFFFF": "white", "#E40521": "red", "#1A66D6": "blue" };
document.body.dataset.accent = map[t.accent] || "white";
document.body.dataset.density = t.density === "tight" ? "tight" : "comfortable";
}, [t]);
return (
{children}
setTweak("accent", v)} />
setTweak("density", v)} />
);
}
function mountPage(current, Content) {
ReactDOM.createRoot(document.getElementById("root")).render(
);
}
Object.assign(window, {
useCountUp, Reveal, Nav, SubHero, PartnersStrip, Footer, InvestorCTA, PropulsionDiagram, PropulsionSlider, ServiceSlider, PageShell, mountPage,
});