/* global React */
const { useState, useRef, useEffect } = React;
// ── Base de connaissances étendue ────────────────────────────────────────────
const KNOWLEDGE = [
// BILLETS / PRIX
{
keys: ["billet","ticket","acheter","prix","tarif","combien","euro","payer","early","bird","standard","last","chance","place","porte","entrée","entree","accès","acces","pass"],
answer: "🎫 Voici les tarifs :\n• **Early Bird** — 20€ (offre limitée)\n• **Regular** — 25€\n• **Last Chance** — 30€\n• **Sur place le 20 juin** — 35€\n\nAchète ton billet sur **Weezevent** depuis la page Billetterie du site !\n\n💡 Les tables VIP sont à 600€ (bouteille 🍾 incluse) — réservables sur le plan 3D."
},
// DATE / HORAIRES
{
keys: ["date","quand","jour","juin","2026","samedi","horaire","heure","ouverture","fermeture","ouvre","ferme","11h","23h","programme","planning"],
answer: "📅 Summer Sky c'est le **samedi 20 juin 2026** !\n\n🕐 Portes ouvertes à **11h00**\n🌙 Fermeture à **22h30**\n🏊 Piscine ouverte de **11h à 23h**\n\nLe festival dure toute la journée — prévois ta tenue de piscine ☀️"
},
// TRAJET DEPUIS PARIS
{
keys: ["paris","capitale","idf","ile de france","montparnasse","tgv","train","sncf","gare","rail","rail","ratp"],
answer: "🚂 **Depuis Paris :**\n\n🚄 TGV Montparnasse → Le Mans en **55 min** (directs toutes les heures)\nGare du Mans → Site : **navettes festival toutes les 20 min** ⏱️\n\n🚗 En voiture depuis Paris :\nA11 direction Le Mans → **sortie 9** (Yvré-l'Évêque)\nCompter **2h30 depuis Paris** selon le trafic.\n\n💡 Le train + navette est l'option la plus rapide et sans stress !"
},
// TRAJET DEPUIS NANTES
{
keys: ["nantes","bretagne","pays de la loire","ouest","angers","rennes","tours","le havre","rouen"],
answer: "🚗 **Depuis Nantes :**\nA11 direction Le Mans → **sortie 9**\nCompter environ **1h30**\n\n🚂 **En train :**\nNantes → Le Mans en environ **1h10**\nPuis navette festival depuis la gare du Mans 🚌"
},
// TRAJET VOITURE / GPS
{
keys: ["voiture","gps","itinéraire","itineraire","route","autoroute","a11","a28","sortie","conduire","conduire","stationner","km","kilometre","kilom","direction","comment","venir","aller","acceder","accéder","rejoindre"],
answer: "🚗 **En voiture :**\n• A11 → **sortie 9** (Yvré-l'Évêque)\n• A28 → **sortie 23**\n• GPS : **1610 route de Feumusson, 72530 Yvré-l'Évêque**\n\n🅿️ Parking gratuit sur site — **2 000 places**\nSignalétique orange **Pôle 10** à suivre sur place.\n\n⏱️ Distances :\n• Paris — 2h30\n• Nantes — 1h30\n• Rennes — 2h\n• Tours — 1h"
},
// NAVETTE / BUS
{
keys: ["navette","bus","shuttle","minibus","transport commun","transports","collectif"],
answer: "🚌 **Navettes festival :**\nDépart **Gare du Mans** → Site\nFréquence : **toutes les 20 minutes**\nHoraires : de 10h30 jusqu'à la fin du festival\n\nLes navettes sont incluses dans le billet festival — présente juste ton ticket ! 🎫"
},
// TAXI / VTC / UBER
{
keys: ["taxi","uber","vtc","bolt","chauffeur","dépose","depose","drop"],
answer: "🚕 **Taxi & VTC :**\nZone de dépose et reprise prévue à l'entrée du site.\nAdresse GPS pour le chauffeur : **1610 route de Feumusson, 72530 Yvré-l'Évêque**\n\nTu peux aussi combiner train + navette depuis la Gare du Mans 🚂"
},
// VÉLO / MARCHE
{
keys: ["velo","vélo","vélos","bike","piste","cyclable","marche","pied","rando"],
answer: "🚲 **À vélo :**\nPiste cyclable depuis Le Mans jusqu'au site !\nParking vélo sécurisé disponible sur place.\n\n🦶 **À pied depuis Le Mans :** environ 50 min — on recommande plutôt la navette 😄"
},
// LIEU / ADRESSE
{
keys: ["lieu","adresse","où","plongeoir","yvre","yvré","pole","pôle","europeen","européen","sarthe","72","72530","localisation","site","endroit","festival"],
answer: "📍 **Le Plongeoir du Pôle Européen**\n1610 route de Feumusson\n72530 Yvré-l'Évêque · Sarthe\n\n🗺️ À 10 min du Mans !\nSuis la signalétique orange **Pôle 10** en arrivant.\n\nRetrouve la carte interactive sur la page **Infos** du site ✅"
},
// PARKING
{
keys: ["parking","garer","stationnement","place voiture","se garer","park"],
answer: "🅿️ **Parking 100% gratuit** sur site !\n2 000 places disponibles.\nSuivre signalétique orange **Pôle 10**.\n\n🤝 Le covoiturage est encouragé — moins de voitures, plus d'ambiance !"
},
// LINEUP / ARTISTES
{
keys: ["lineup","artiste","artistes","musique","qui joue","concert","scene","scène","dj","r2","leto","milane","maad","nelsao","karlson","iccy","ash","affiche","tête","tete","set","show","perform"],
answer: "🎵 **Programme du 20 juin 2026 :**\n\n11h — 🏊 Ouverture + Pool open\n12h — DJ Ash\n14h — DJ Nelsao\n16h30 — DJ Karlson\n18h30 — DJ M-ADD · Golden hour 🌅\n19h — DJ Milane\n20h30 — DJ Iccy\n21h — **R2** ⭐ (tête d'affiche)\n22h — **Leto** ⭐ (tête d'affiche)\n\n📌 Programme indicatif — mis à jour 48h avant !"
},
// PISCINE
{
keys: ["piscine","maillot","nager","baignade","swim","plonger","eau","bassin","nage","plongeoir","bain","bronzer","transats","transat","soleil","serviette"],
answer: "🏊 **La piscine :**\nOuverte de **11h à 23h** toute la journée !\n\n• Maillot de bain **obligatoire** pour nager\n• Vestiaires et casiers sur place\n• Transats et chaises longues disponibles\n• Bar cocktails bord de piscine 🍹\n\nPense à ta crème solaire — la journée sera longue ☀️"
},
// TABLES VIP
{
keys: ["vip","table vip","forfait","bouteille","réserver","reserver","reservation","vip table","600","privatiser","privatisation","service","dédié","dedie","premium"],
answer: "⭐ **Tables VIP — 600€/table :**\n\n• Placement **bord de piscine** garanti\n• 🍾 **1 bouteille incluse** dans le forfait\n• Accueil personnalisé + service dédié\n• Bar premium avec accès prioritaire\n• Terrasse vue scène\n\n📌 Choisis ta table en 3D depuis la page **Billetterie** du site !\nLes tables se réservent directement sur **Weezevent**."
},
// TABLES LOUNGE
{
keys: ["lounge","table lounge","transat","coussin","bean","libre","gratuit","sans réservation","sans reservation","pouf"],
answer: "🏖️ **Tables Lounge — accès libre :**\nPas de réservation — premiers arrivés, premiers servis !\nElles sont disponibles dès l'ouverture à **11h**.\n\n💡 Conseil : arrive tôt pour choisir ton coin et profiter de la meilleure place !"
},
// FOOD / RESTAURATION
{
keys: ["food","manger","restauration","boire","nourriture","truck","food truck","végé","vegetarien","végétarien","gluten","sans gluten","halal","repas","déjeuner","diner","dîner","bar","cocktail","boisson","faim","soif"],
answer: "🍔 **Restauration & boissons :**\n\n• **Food Trucks** variés sur site\n• **Bars cocktails** (dont bord de piscine 🍹)\n• Options **végétariennes** disponibles\n• Options **sans gluten** disponibles\n\n💡 Les boissons et la nourriture extérieures sont interdites — tout est prévu sur place !"
},
// MÉTÉO / TENUE
{
keys: ["meteo","météo","pluie","soleil","chaud","froid","tenue","vêtement","vetement","quoi mettre","quoi porter","habits","s'habiller"],
answer: "☀️ Le 20 juin en Sarthe, on attend une belle journée d'été !\n\n👙 **Ce qu'on recommande :**\n• Maillot de bain (obligatoire pour la piscine)\n• Lunettes de soleil\n• Crème solaire\n• Une tenue légère pour la soirée\n• Les chaussures de type claquettes sont parfaites !\n\nEn cas de météo capricieuse, une veste légère peut être utile le soir 🌙"
},
// ANIMAUX
{
keys: ["animaux","chien","chat","animal","pet","toutou","chiot","nac"],
answer: "🐾 Désolé, **les animaux ne sont pas autorisés** sur le site du festival.\nCette règle s'applique à tous les animaux sans exception."
},
// ÂGE MINIMUM
{
keys: ["age","âge","mineur","enfant","ans","17","18","jeune","autorisation","parental","parentale","accompagne","accompagné","naissance"],
answer: "🔞 **Âge minimum : 17 ans**\nLes mineurs (17 ans) doivent être accompagnés d'un **parent ou tuteur légal**.\n\nUne pièce d'identité peut être demandée à l'entrée !"
},
// INTERDITS / SÉCURITÉ
{
keys: ["interdit","verre","bouteille","alcool","extérieur","exterieur","fouille","sécurité","securite","médical","medical","drogue","substance","règlement","reglement","canette","nourriture ext"],
answer: "🚫 **Interdit sur le site :**\n• Bouteilles en verre\n• Boissons & nourriture extérieures\n• Animaux\n• Substances illicites\n\n✅ **À savoir :**\n• Fouilles à l'entrée\n• Équipe médicale présente toute la journée\n• Vigiles et personnel de sécurité sur site"
},
// ACCESSIBILITÉ / PMR
{
keys: ["handicap","pmr","fauteuil","roulant","accessibilite","accessibilité","mobilité","mobilite","reduit","réduit","accessible","valide"],
answer: "♿ Le site est **accessible aux personnes à mobilité réduite**.\nParking PMR disponible à l'entrée.\n\nPour toute demande spécifique, contacte-nous via la page **Contact** du site ou sur Instagram **@summersky.off** 💙"
},
// CONTACT / RÉSEAUX
{
keys: ["contact","mail","email","instagram","tiktok","réseau","reseau","social","joindre","message","question","organisateur","organisation","info"],
answer: "📲 **Nous contacter :**\n\n• Instagram : **@summersky.off**\n• TikTok : **@summersky.off**\n• Page Contact du site\n\nOn répond à toutes les questions rapidement ! 😊"
},
// BILLETS NOMINATIFS / REVENTE
{
keys: ["nominatif","revente","transfert","offrir","donner","cadeau","preter","prêter","changer nom"],
answer: "✅ Les billets **ne sont pas nominatifs** !\nTu peux offrir ou revendre ton billet librement jusqu'à la veille de l'événement.\n\nGarde juste le QR code du billet valide pour l'entrée 🎫"
},
// REMBOURSEMENT / ANNULATION
{
keys: ["remboursement","rembourser","annuler","annulation","cancel","perdu","billet perdu"],
answer: "💳 Pour toute question de **remboursement ou d'annulation**, c'est géré directement via **Weezevent** (la plateforme de billetterie).\n\nContacte leur support via ton espace Weezevent ou écris-nous sur Instagram **@summersky.off** et on t'aide !"
},
// GREETINGS
{
keys: ["bonjour","salut","hello","bonsoir","hey","coucou","yo","slt","bjr","bj","hi","ola","hola","wesh","wsh"],
answer: "Salut ! 👋 Moi c'est **Sunny**, ton assistant Summer Sky ☼\n\nJe peux t'aider sur :\n🎫 Billets · 📅 Dates · 📍 Trajet · 🎵 Lineup · ⭐ VIP · 🏊 Piscine\n\nPose-moi ta question ou clique sur un sujet ci-dessous !"
},
// MERCI / FIN
{
keys: ["merci","thanks","cool","super","génial","genial","parfait","nickel","top","ok","okok","excellent","trop bien"],
answer: "Avec plaisir ! 🌞 On se retrouve le **20 juin au Plongeoir** — ça va être une journée de folie ! ☀️🎵🏊\n\nD'autres questions ? Je suis là !"
},
// AU REVOIR
{
keys: ["au revoir","bye","ciao","tchao","à bientôt","a bientot","bonne journée","bonne journee","bisous"],
answer: "À très bientôt ! 👋☀️\nRDV le **20 juin 2026** au Plongeoir du Pôle Européen — prépare ton maillot 🏊"
},
];
const FALLBACK = "Hmmm, je n'ai pas bien compris 🤔\n\nEssaie de me poser une question sur :\n🎫 Billets & prix · 📍 Comment venir · 🚂 Transports\n🎵 Line-up · ⭐ VIP · 🏊 Piscine · 📅 Horaires\n\nOu contacte-nous sur **@summersky.off** sur Instagram !";
const QUICK = [
{ label: "🎫 Billets & prix", q: "prix billets tarif" },
{ label: "📍 Comment venir", q: "adresse comment venir trajet" },
{ label: "🚂 Depuis Paris", q: "depuis paris train tgv" },
{ label: "🎵 Line-up", q: "lineup artistes programme" },
{ label: "⭐ Tables VIP", q: "vip table forfait" },
{ label: "🏊 Piscine", q: "piscine maillot baignade" },
];
// ── Matching par score (meilleure correspondance) ───────────────────────────
function normalize(s) { return s.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); }
function getResponse(input) {
const q = normalize(input);
const words = q.split(/\s+/);
let bestScore = 0, bestAnswer = null;
for (const item of KNOWLEDGE) {
let score = 0;
for (const key of item.keys) {
const nk = normalize(key);
if (q.includes(nk)) score += nk.includes(" ") ? 3 : 1; // expressions > mots simples
}
if (score > bestScore) { bestScore = score; bestAnswer = item.answer; }
}
return bestScore > 0 ? bestAnswer : FALLBACK;
}
// ── Composant ────────────────────────────────────────────────────────────────
function ChatBot() {
const [open, setOpen] = useState(false);
const [msgs, setMsgs] = useState([]);
const [input, setInput] = useState("");
const [typing, setTyping] = useState(false);
const [pinged, setPinged] = useState(false);
const bottomRef = useRef(null);
const inputRef = useRef(null);
useEffect(() => {
const t = setTimeout(() => setPinged(true), 4000);
return () => clearTimeout(t);
}, []);
useEffect(() => {
if (open && msgs.length === 0) {
setMsgs([{ from: "bot", text: "Salut ! 👋 Moi c'est **Sunny**, ton assistant Summer Sky ☼\nPose-moi une question ou choisis un sujet ci-dessous !", time: new Date() }]);
}
if (open) { setPinged(false); setTimeout(() => inputRef.current?.focus(), 300); }
}, [open]);
useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [msgs, typing]);
function sendMessage(text) {
if (!text.trim()) return;
setMsgs(prev => [...prev, { from: "user", text: text.trim(), time: new Date() }]);
setInput("");
setTyping(true);
setTimeout(() => {
setTyping(false);
setMsgs(prev => [...prev, { from: "bot", text: getResponse(text), time: new Date() }]);
}, 600 + Math.random() * 500);
}
function handleKey(e) {
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(input); }
}
function formatText(text) {
return text.split("\n").map((line, i, arr) => (
}
}