import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom/client';
import { HashRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom';
// --- DATA ---
const PORTFOLIO_DATA = [
{
id: 'chase-2026',
title: 'YAMAHA 125 YZ',
thumbnail: 'yamaha.jpg',
gear: 'Drone FPV 3.5" + GoPro Hero 13',
status: 'À VENIR PROCHAINEMENT EN 2026',
hasTimer: true,
isUnknown: true,
targetDate: undefined
},
{
id: 'secret-2026',
title: '?????',
thumbnail: 'https://images.unsplash.com/photo-1508614589041-895b88991e3e?auto=format&fit=crop&w=1920&q=80',
gear: 'DJI AIR 3S',
status: 'À VENIR',
hasTimer: true,
isUnknown: false,
targetDate: '2026-04-07T00:00:00'
}
];
// --- UTILS ---
const useReveal = () => {
const { pathname } = useLocation();
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('active');
}
});
}, { threshold: 0.1 });
const elements = document.querySelectorAll('.reveal');
elements.forEach(el => observer.observe(el));
setTimeout(() => {
elements.forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.top < window.innerHeight) el.classList.add('active');
});
}, 500);
return () => observer.disconnect();
}, [pathname]);
};
const CountdownTimer: React.FC<{ targetDate?: string, isUnknown?: boolean }> = ({ targetDate, isUnknown }) => {
const [timeLeft, setTimeLeft] = useState({ days: 0, hours: 0, minutes: 0, seconds: 0 });
useEffect(() => {
if (isUnknown || !targetDate) return;
const target = new Date(targetDate).getTime();
const timer = setInterval(() => {
const now = new Date().getTime();
const distance = target - now;
if (distance < 0) { clearInterval(timer); return; }
setTimeLeft({
days: Math.floor(distance / (1000 * 60 * 60 * 24)),
hours: Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
minutes: Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)),
seconds: Math.floor((distance % (1000 * 60)) / 1000)
});
}, 1000);
return () => clearInterval(timer);
}, [targetDate, isUnknown]);
const Block = ({ v, l }: { v: string | number, l: string }) => (
{isUnknown ? '??' : v.toString().padStart(2, '0')}
{l}
);
return (
);
};
// --- COMPONENTS ---
const Navbar = () => {
const [scrolled, setScrolled] = useState(false);
const location = useLocation();
useEffect(() => {
const handle = () => setScrolled(window.scrollY > 50);
window.addEventListener('scroll', handle);
return () => window.removeEventListener('scroll', handle);
}, []);
const navLinks = [
{ name: 'Portfolio', path: '/portfolio' },
{ name: 'Prestations', path: '/prestations' },
{ name: 'À Propos', path: '/a-propos' },
{ name: 'Contact', path: '/contact' },
];
return (
{navLinks.map((link) => (
{link.name}
))}
);
};
const Home = () => {
useReveal();
return (
BASTY FPV
Cinematic FPV Pilot & Creative Visuals
DÉCOUVRIR LE TRAVAIL
ENTER THE FPV WORLD
);
};
const Portfolio = () => {
useReveal();
return (
RÉALISATIONS
SHOWCASE
{PORTFOLIO_DATA.map(proj => (
{proj.hasTimer && (
)}
{proj.status}
{proj.title}
{proj.gear}
))}
);
};
const Prestations = () => {
useReveal();
const list = [
{ title: 'FPV CINÉMATIQUE', icon: '🎬', desc: 'Prises de vues immersives et fluides pour publicités, clips et storytelling.', items: ['Drone FPV 3.5 Pouces', '4K 120 FPS / D-LOG M', 'Stabilisation ReelSteady GO'] },
{ title: 'CHASE CAM / SUIVI', icon: '🏎️', desc: 'Tracking dynamique de véhicules, sportifs ou événements en mouvement.', items: ['Suivi haute vitesse', 'Proximité millimétrée', 'Action synchronisée'] },
{ title: 'EXPLORATION CINEWHOOP', icon: '🏛️', desc: 'Visites virtuelles en intérieur (immobilier, industrie). Vol sécurisé.', items: ['Protection d\'hélices', 'Passage en espaces réduits', 'Storytelling intérieur'] },
{ title: 'MONTAGE & ÉDITION', icon: '✂️', desc: 'Post-production complète sur Adobe : montage, sound design et colorgrading.', items: ['Adobe Premiere Pro', 'Sound Design Immersif', 'Multi-formats'] }
];
return (
MISSIONS & CAPACITÉS
NOSSERVICES
{list.map((s, i) => (
{s.icon}
{s.title}
{s.desc}
{s.items.map((item, idx) => (
{item}
))}
))}
);
};
const About = () => {
useReveal();
return (
THE CREATIVE MIND
BASTIANBÉLON
Expert en immersion dynamique, basé dans les Hauts-de-France .
Mon approche du FPV dépasse le simple pilotage : je conçois chaque vol comme une partition cinématographique. Alliant précision technique et vision artistique, je transforme des environnements complexes en récits visuels fluides.
Spécialisé dans le Somme (80) et le Pas-de-Calais (62) , j'accompagne marques et créatifs pour capturer l'impossible, du suivi haute vitesse au CineWhoop intérieur chirurgical.
LOGICIEL
Adobe Premiere Pro
ReelSteady / RSGO
DRONES
FPV 3.5 POUCES
DJI AIR 3S
DJI MINI 3 PRO
Perspective aérienne — 2025
);
};
const Contact = () => {
useReveal();
const [status, setStatus] = useState('idle');
const [form, setForm] = useState({ name: '', email: '', message: '', location: '', zip: '' });
const handleSubmit = async (e) => {
e.preventDefault(); setStatus('sending');
try {
const res = await fetch("https://api.web3forms.com/submit", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ access_key: "3006180d-83af-44b8-b7f2-b0ace60a4a4a", ...form })
});
if ((await res.json()).success) setStatus('sent'); else setStatus('error');
} catch { setStatus('error'); }
};
if (status === 'sent') return (
READY FOR TAKE OFF.
Message transmis. Bastian revient vers vous sous 48h.
setStatus('idle')} className="text-[10px] font-black border-b border-white pb-2 uppercase tracking-widest">Nouvelle Demande
);
return (
PRE-FLIGHT BRIEFING
PARLONSPROJET
Zone : Somme (80) & Pas-de-Calais (62)
);
};
// --- APP ---
const App = () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
const timeout = setTimeout(() => document.body.classList.add('loaded'), 1500);
return () => clearTimeout(timeout);
}, [pathname]);
return (
);
};
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render( );