/* global React */
// Shared UI components: Header, Footer, LanguageSwitcher, ProgramCard, Icon, FormField, FileDrop
const { useState, useEffect, useRef } = React;
// Tiny icon set drawn as inline SVG (kept generic + minimal per brand "premium minimalism")
function Arrow({ size = 16 }) {
return (
);
}
function ArrowLeft({ size = 16 }) {
return (
);
}
function Check({ size = 24 }) {
return (
);
}
function UploadIcon() {
return (
);
}
function InstagramIcon({ size = 16 }) {
return (
);
}
function LanguageSwitcher({ lang, setLang }) {
return (
{["en", "es", "ru"].map((code) => (
))}
);
}
// Mobile-only compact language picker. Default is whatever lang prop is set to
// (English by default elsewhere). Shows current code + a chevron; tap reveals
// the other two options in a small dropdown panel. Closes on outside click,
// Escape, or after picking an option.
function LanguageDropdown({ lang, setLang }) {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
if (!open) return;
const onDoc = (e) => {
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
};
const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
document.addEventListener("mousedown", onDoc);
document.addEventListener("keydown", onKey);
return () => {
document.removeEventListener("mousedown", onDoc);
document.removeEventListener("keydown", onKey);
};
}, [open]);
const others = ["en", "es", "ru"].filter((c) => c !== lang);
return (
{open && (
{others.map((code) => (
))}
)}
);
}
function Header({ lang, setLang, navigate, onDark, route }) {
const t = window.I18N[lang];
// Smart nav: on home page, scroll to the section; elsewhere, navigate to the dedicated page.
const goPrograms = () => {
if (route && route.name === "home") {
const el = document.getElementById("programs");
if (el) window.scrollTo({ top: el.offsetTop - 80, behavior: "smooth" });
} else {
navigate({ name: "home" });
setTimeout(() => {
const el = document.getElementById("programs");
if (el) window.scrollTo({ top: el.offsetTop - 80, behavior: "smooth" });
}, 60);
}
};
// "Our cases" lives as an in-page section between Programs and Partners on the home page.
// From any inner page we navigate home first, then scroll once the section mounts.
const goCases = () => {
if (route && route.name === "home") {
const el = document.getElementById("cases");
if (el) window.scrollTo({ top: el.offsetTop - 80, behavior: "smooth" });
} else {
navigate({ name: "home" });
setTimeout(() => {
const el = document.getElementById("cases");
if (el) window.scrollTo({ top: el.offsetTop - 80, behavior: "smooth" });
}, 60);
}
};
const isActive = (n) => route && route.name === n;
// Brand click: always returns the user to the very top of the home page,
// whether they're on home (where navigate() wouldn't trigger a hashchange)
// or on any inner page.
const goHomeTop = () => {
if (route && route.name === "home") {
window.scrollTo({ top: 0, behavior: "smooth" });
} else {
navigate({ name: "home" });
setTimeout(() => window.scrollTo({ top: 0, behavior: "auto" }), 60);
}
};
return (
);
}
// Intro splash — shown on first home view per session. Logo animates: "f1rst" fades up,
// then "steps" travels from bottom-left to its final position above the "t".
function IntroSplash({ lang, onDone }) {
const tagline = {
en: "Your path to professional football",
es: "Tu camino al fútbol profesional",
ru: "Твой путь в профессиональный футбол",
}[lang] || "Your path to professional football";
const [leaving, setLeaving] = useState(false);
useEffect(() => {
const leaveAt = setTimeout(() => setLeaving(true), 3000);
const removeAt = setTimeout(onDone, 3600);
return () => { clearTimeout(leaveAt); clearTimeout(removeAt); };
}, [onDone]);
const skip = () => { setLeaving(true); setTimeout(onDone, 450); };
return (
f1rststeps
{tagline}
);
}
// ---------- VIDEO CAROUSEL ----------
// Auto-playing, muted, looping carousel for short MP4s. Used on the home page
// "Our cases" section and on the full-year program "Program overview" section.
// Controls per active card: play/pause, sound, expand (modal on desktop, native
// fullscreen on small viewports), plus a click-to-seek scrubber with timecode.
function VideoCarousel({ videos, labels, className }) {
// videos: [{ src, title, subtitle? }]
const [active, setActive] = useState(0);
const [muted, setMuted] = useState(true);
const [playing, setPlaying] = useState(true);
const [progress, setProgress] = useState(0); // 0..1
const [duration, setDuration] = useState(0);
const [currentTime, setCurrentTime] = useState(0);
const [expanded, setExpanded] = useState(false);
const refs = useRef([]);
const expandedRef = useRef(null);
const scrubRef = useRef(null);
const total = videos.length;
const go = (delta) => setActive((i) => (i + delta + total) % total);
// Only the active card plays — others pause and rewind so they don't waste
// bandwidth or jump audibly when the user steps through them. Switching slides
// also resets the play/progress state so the new clip starts cleanly.
useEffect(() => {
refs.current.forEach((v, i) => {
if (!v) return;
v.muted = muted;
if (i === active) {
try { v.currentTime = 0; } catch (e) {}
if (playing) {
const p = v.play();
if (p && p.catch) p.catch(() => {});
} else {
v.pause();
}
} else {
v.pause();
try { v.currentTime = 0; } catch (e) {}
}
});
setProgress(0);
setCurrentTime(0);
}, [active]); // eslint-disable-line react-hooks/exhaustive-deps
// Honour play/pause + mute toggles on the active video without resetting time.
useEffect(() => {
const v = refs.current[active];
if (!v) return;
v.muted = muted;
if (playing) {
const p = v.play();
if (p && p.catch) p.catch(() => {});
} else {
v.pause();
}
}, [playing, muted, active]);
// Mobile gets native fullscreen (browser/OS chrome). Desktop gets a contained
// overlay so the page stays visible around it. In BOTH cases we pause the
// inline video while the expanded copy is active so its audio isn't duplicated.
const openExpanded = () => {
const inline = refs.current[active];
if (inline) { try { inline.pause(); } catch (e) {} }
const isMobile = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(max-width: 720px)").matches;
if (isMobile) {
if (inline) {
const req = inline.requestFullscreen || inline.webkitEnterFullscreen || inline.webkitRequestFullscreen;
if (req) {
// On mobile the same