// SALVO — Part 2: Science, Product, Athletes, Pricing, Form, CTA, Footer, App const { useState, useEffect, useRef, useCallback } = React; // ===== SCIENCE HORIZONTAL SCROLL ===== const ScienceSection = () => { const outerRef = useRef(null); const trackRef = useRef(null); const [progress, setProgress] = useState(0); const [activePanel, setActivePanel] = useState(0); const isMobile = window.innerWidth < 768; useEffect(() => { if (isMobile) return; const handleScroll = () => { const outer = outerRef.current; if (!outer) return; const rect = outer.getBoundingClientRect(); const scrolled = -rect.top; const total = rect.height - window.innerHeight; const p = Math.max(0, Math.min(1, scrolled / total)); setProgress(p); setActivePanel(Math.min(3, Math.floor(p * 4))); if (trackRef.current) { const maxTranslate = -(trackRef.current.scrollWidth - window.innerWidth); trackRef.current.style.transform = `translateX(${p * maxTranslate}px)`; } }; window.addEventListener('scroll', handleScroll, { passive: true }); return () => window.removeEventListener('scroll', handleScroll); }, []); const panels = [ { tag: "01 — L'ancien modèle", title: "L'ANCIEN MODÈLE\nA ÉCHOUÉ", body: "Depuis 30 ans, la science du sport vous a dit que les crampes viennent de la déshydratation et des déficits électrolytiques. C'est faux.", stat: "Niveaux d'électrolytes identiques entre athlètes qui crampent et ceux qui ne crampent pas.", source: "Schwellnus et al., 2008", visual: (
{/* Two bar charts side by side */} {[['Crampes', 142], ['Pas de crampes', 138]].map(([label, val], i) => { const [ref, inView] = useInView(); return (
{val} mEq/L
{label}
); })}
Aucune différence
significative (p=0.82)
), }, { tag: "02 — Le vrai mécanisme", title: "LES CRAMPES\nVIENNENT DES NERFS", body: "Les crampes d'effort ne viennent pas du muscle. Elles viennent d'un motoneurone α en état d'hyperexcitabilité — un neurone qui tire sans s'arrêter.", stat: "Le muscle reçoit un signal continu. Il se contracte. Il ne peut pas s'arrêter.", source: "Schwellnus, 2009 — Altered Neuromuscular Control Hypothesis", visual: (
{/* Neuron impulse visual */} {Array.from({ length: 12 }).map((_, i) => (
))}
Motoneurone α — hyperexcitabilité
), }, { tag: "03 — La solution TRP", title: "LES CANAUX TRP\nCOUPENT LE SIGNAL", body: "Activés en bouche, les agonistes TRP envoient un signal inhibiteur via le nerf vague. Le motoneurone se calme. La crampe cède en 60-90 secondes.", stat: "3 agonistes TRP. Un signal. Zéro compromis.", source: "MacAuley & Best, 2007 — Bean & Strichartz, MIT", visual: (
{[ { name: 'Capsaïcine', sub: 'Piment', color: '#FF6B35' }, { name: 'Gingérols', sub: 'Gingembre', color: '#FF8C5A' }, { name: 'Cinnam.', sub: 'Cannelle', color: '#FFA875' }, ].map((ing, i) => { const [ref, inView] = useInView(); return (
{ing.name}
{ing.sub}
); })}
), }, { tag: "04 — La validation", title: "VALIDATION :\n$86.4M D'IPO NASDAQ", body: "Flex Pharma a levé $86.4M en IPO sur le Nasdaq avec Hotshot — basé sur le même mécanisme TRP. Prouvé. Breveté. SALVO va plus loin : double action électrolytique.", stat: "SALVO = mécanisme TRP + 900mg sodium. Hotshot n'a pas de sodium. Nous, oui.", source: "Flex Pharma IPO 2015 — NASDAQ: FLKS", visual: (
{[ { label: 'IPO Nasdaq', val: '$86.4M' }, { label: 'Mécanisme TRP', val: 'Breveté' }, { label: 'Double action', val: 'SALVO only' }, ].map((item, i) => (
{item.val}
{item.label}
))}
Flex Pharma prouve le mécanisme.
SALVO va plus loin.
), }, ]; if (isMobile) { return (
La science

Le mécanisme TRP

{panels.map((p, i) => (
{p.tag}
{p.visual}

{p.title}

{p.body}

{p.stat}
— {p.source}
))}
); } return (
{/* Header */}
La science

Le mécanisme TRP

{/* Progress dots */}
{panels.map((_, i) => (
))}
{/* Track */}
{panels.map((p, i) => (
{p.tag}
{p.visual}

{p.title}

{p.body}

{p.stat}
— {p.source}
))}
{/* Progress bar */}
{/* Scroll cue */}
0.95 ? 0 : 0.5, transition: 'opacity 0.3s' }}> Continuez à défiler →
); }; // ===== PRODUCT SECTION ===== const ProductSection = () => { const [ref, inView] = useInView(); const sachetRef = useRef(null); const [angle, setAngle] = useState(0); useEffect(() => { let raf; let start = null; const animate = (ts) => { if (!start) start = ts; const elapsed = ts - start; setAngle((elapsed / 15000) * 360); raf = requestAnimationFrame(animate); }; raf = requestAnimationFrame(animate); return () => cancelAnimationFrame(raf); }, []); const specs = [ { label: '30 ML', sub: 'Format shot', pos: { top: '8%', right: '-10%' } }, { label: 'TRP × 3', sub: 'Capsaïcine · Gingérols · Cinnam.', pos: { top: '32%', right: '-18%' } }, { label: '900MG', sub: 'Sodium', pos: { bottom: '22%', right: '-14%' } }, { label: '200MG', sub: 'Potassium', pos: { bottom: '8%', left: '-12%' } }, { label: 'ZÉRO SUCRE', sub: 'Formule pure', pos: { top: '32%', left: '-18%' } }, { label: '60-90s', sub: 'Onset TRP', pos: { top: '8%', left: '-10%' } }, ]; return (
Produit

Anatomie d'un
shot SALVO

{/* Sachet + floating specs */}
{/* Specs */} {specs.map((spec, i) => (
{spec.label}
{spec.sub}
{/* Connector line */}
))} {/* Sachet SVG */}
{/* Body */} {/* Top crimp */} {/* Tear notch */} {/* Horizontal seal lines */} {/* Center logo area */} {/* Brand text */} SALVO {/* Tagline */} FIRE WHEN READY {/* Shot details */} ANTI-CRAMPE · 30ML {/* Shine */}
); }; // ===== ATHLETES SECTION ===== const AthletesSection = () => { const communities = ['HYROX', 'UTMB', 'IRONMAN', 'Tour de France Amateur', 'Tennis Pro Circuit', 'CrossFit Open', 'Triathlon France', 'Trail Blanc']; const testimonials = [ { name: 'Laurent M.', sport: 'Trail Ultra', text: 'Au km 72 de l\'UTMB, la crampe habituelle. J\'ai pris SALVO. 90 secondes plus tard, j\'avançais encore.', placeholder: true }, { name: 'Sarah K.', sport: 'HYROX', text: 'La chaleur du platform fait tout. J\'ai arrêté de cramper dans les sleds. Rien d\'autre n\'avait marché.', placeholder: true }, { name: 'Pierre D.', sport: 'Triathlon Ironman', text: 'Le vélo et la course enchaînés — mes mollets lâchaient toujours à T2. Plus depuis SALVO.', placeholder: true }, ]; return (
Communauté

Built with athletes

{/* Marquee */}
{[...communities, ...communities].map((c, i) => ( {c} · ))}
{/* Testimonials */}
{testimonials.map((t, i) => (
{ e.currentTarget.style.borderColor = 'rgba(255,107,53,0.3)'; e.currentTarget.style.transform = 'translateY(-4px)'; e.currentTarget.style.boxShadow = '0 12px 40px rgba(0,0,0,0.3)'; }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = 'none'; }} >
{t.name}
{t.sport}

"{t.text}"

{t.placeholder && (
Testimonial early athletes program
)}
))}
); }; // ===== PRICING SECTION ===== const PricingSection = ({ onSelect }) => { const packs = [ { name: 'Découverte', shots: 6, price: 19, per: '3.17', desc: 'Testez le mécanisme TRP avant votre prochaine course.', cta: 'Sélectionner' }, { name: 'Abonnement', shots: 18, price: 49, per: '2.72', desc: 'Le pack de l\'athlète régulier. Renouvelable. Annulable.', cta: 'Sélectionner', featured: true }, { name: 'Équipe', shots: 36, price: 89, per: '2.47', desc: 'Pour les clubs, les staffs, les athlètes qui ne transigent pas.', cta: 'Sélectionner' }, ]; return (
Pré-commande

Choisissez votre pack

{packs.map((pack, i) => (
{ if (!pack.featured) { e.currentTarget.style.borderColor = 'rgba(255,107,53,0.3)'; e.currentTarget.style.transform = 'translateY(-4px)'; e.currentTarget.style.boxShadow = '0 16px 48px rgba(0,0,0,0.3)'; } else { e.currentTarget.style.boxShadow = '0 0 64px rgba(255,107,53,0.18)'; } }} onMouseLeave={e => { if (!pack.featured) { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = 'none'; } else { e.currentTarget.style.boxShadow = '0 0 48px rgba(255,107,53,0.1)'; } }} > {pack.featured && (
Le plus populaire
)}
{pack.name}
{pack.shots} shots
€{pack.price} €{pack.per}/shot

{pack.desc}

onSelect(pack.name)} style={{ width: '100%', justifyContent: 'center', padding: '13px 0' }} > {pack.cta}
))}
); }; // ===== FORM SECTION ===== const FormSection = ({ selectedPack, setSelectedPack }) => { const [form, setForm] = useState({ prenom: '', nom: '', email: '', pack: selectedPack || 'Abonnement', sport: '', pays: '', postal: '' }); const [errors, setErrors] = useState({}); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(false); useEffect(() => { if (selectedPack) setForm(f => ({ ...f, pack: selectedPack })); }, [selectedPack]); const validate = () => { const e = {}; if (!form.prenom.trim()) e.prenom = 'Requis'; if (!form.nom.trim()) e.nom = 'Requis'; if (!form.email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) e.email = 'Email invalide'; if (!form.sport) e.sport = 'Requis'; if (!form.pays.trim()) e.pays = 'Requis'; return e; }; const handleSubmit = (e) => { e.preventDefault(); const errs = validate(); if (Object.keys(errs).length > 0) { setErrors(errs); return; } setErrors({}); setLoading(true); setTimeout(() => { setLoading(false); setSuccess(true); }, 1800); }; const Field = ({ label, id, type = 'text', value, onChange, error, children }) => (
{children || ( e.target.style.borderColor = 'rgba(255,107,53,0.4)'} onBlur={e => e.target.style.borderColor = error ? 'rgba(255,107,53,0.4)' : 'var(--border)'} /> )} {error && {error}}
); const selectStyle = { background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--white)', fontFamily: "'IBM Plex Sans', sans-serif", fontSize: 15, padding: '12px 14px', outline: 'none', width: '100%', fontWeight: 300, cursor: 'pointer' }; return (
{success && (

Vous êtes dans
la première salve.

Confirmation envoyée à {form.email}. Livraison prévue Q4 2026.

setSuccess(false)} variant="ghost">Retour à la page
)}
Réservation

Rejoignez la première salve

setForm(f => ({...f, prenom: e.target.value}))} error={errors.prenom} /> setForm(f => ({...f, nom: e.target.value}))} error={errors.nom} />
setForm(f => ({...f, email: e.target.value}))} error={errors.email} />
setForm(f => ({...f, pays: e.target.value}))} error={errors.pays} /> setForm(f => ({...f, postal: e.target.value}))} error={errors.postal} />
Aucun prélèvement avant expédition · Annulable à tout moment · Livraison Q4 2026
); }; // ===== CTA BANNER ===== const CTABanner = ({ onReserve }) => { const [ref, inView] = useInView(); return (
{/* Line trace animation */}

La première salve
est ouverte.

250 athlètes fondateurs · Early bird €15

Réserver ma place →
); }; // ===== FOOTER ===== const Footer = () => { const cols = [ { title: 'Produit', links: ['Le shot SALVO', 'La science TRP', 'Dosage & utilisation', 'FAQ'] }, { title: 'Science', links: ['Mécanisme neuromusculaire', 'Agonistes TRP', 'Électrolytes', 'Études cliniques'] }, { title: 'Entreprise', links: ['Notre mission', 'Équipe', 'Presse', 'Contact'] }, ]; return (
{/* Brand col */}
SALVO

Le premier shot anti-crampe européen à double action. Conçu pour le 1 sur 7.

Fire When Ready.
{cols.map((col, i) => ( ))}
© 2026 SALVO. Tous droits réservés.

* « 1 athlète d'endurance sur 7 » — Estimation basée sur la prévalence des crampes d'effort récurrentes (Kantarowski 1990, Schwellnus 2008, Maughan & Shirreffs 2019). Les taux varient selon le sport, les conditions et le profil individuel.

); }; // ===== APP ===== const App = () => { const [showLoader, setShowLoader] = useState(() => !sessionStorage.getItem('salvo_loaded')); const [selectedPack, setSelectedPack] = useState(null); const handleLoaderDone = () => { sessionStorage.setItem('salvo_loaded', '1'); setShowLoader(false); }; const scrollToForm = (pack) => { if (pack) setSelectedPack(pack); const el = document.getElementById('preorder'); if (el) window.scrollTo({ top: el.offsetTop - 80, behavior: 'smooth' }); }; return (
{showLoader && }
); }; ReactDOM.createRoot(document.getElementById('root')).render();