/* BoatsFun — Faire une offre (Phase 3). Catégorie → Date → Prix → Contact. Dépôt = 25 % du total 4h. Toujours "plus taxes". Speedboat : prix sur demande. */ (function () { const { useState, useMemo } = React; const NAVY = "#002244"; const TEAL = "#00B5E2"; const ORANGE = "#FF8200"; const MIST = "#F0F8FF"; // ───────────────────────────────────────────── // DATA — catégories & options de prix // ───────────────────────────────────────────── const CATEGORIES = [ { id: "speedboat", label: "Speedboat", icon: "⚡", desc: "Sortie rapide et excitante pour petits groupes en quête de sensations.", image: "assets/boats/categories/category-speedboat.png", // Pas d'options de prix — prix sur demande }, { id: "ponton", label: "Ponton", icon: "⛵", desc: "Stabilité et espace. Idéal pour familles et grands groupes.", image: "assets/boats/categories/category-ponton.png", weekday: [ { priceH: 140, label: "Très bonne chance" }, { priceH: 130, label: "Bonne chance" }, { priceH: 125, label: "Chance moyenne" }, { priceH: 120, label: "Offre agressive" }, ], weekend: [ { priceH: 170, label: "Très bonne chance" }, { priceH: 160, label: "Bonne chance" }, { priceH: 150, label: "Chance moyenne" }, { priceH: 140, label: "Offre agressive" }, ], }, { id: "cruiser", label: "Cruiser", icon: "🚤", desc: "Polyvalent et confortable pour une sortie en groupe.", image: "assets/boats/categories/category-cruiser.png", options: [ { priceH: 360, label: "Bonne chance" }, { priceH: 345, label: "Chance correcte" }, { priceH: 330, label: "Chance moyenne" }, { priceH: 320, label: "Offre agressive" }, ], }, { id: "yacht", label: "Yacht", icon: "🛥️", desc: "Expérience premium. Espace et équipements haut de gamme.", image: "assets/boats/categories/category-yacht.png", options: [ { priceH: 450, label: "Très bonne chance" }, { priceH: 440, label: "Bonne chance" }, { priceH: 430, label: "Chance moyenne" }, { priceH: 425, label: "Offre limite" }, ], }, ]; const TIME_SLOTS = ["Matin : 9h à 13h", "Après-midi : 14h à 18h", "Soir : 19h à 23h"]; // ───────────────────────────────────────────── // HELPERS // ───────────────────────────────────────────── function todayISO() { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; } function isWeekendDate(iso) { if (!iso) return false; const day = new Date(iso + "T00:00:00").getDay(); return day === 5 || day === 6 || day === 0; // ven/sam/dim } // Retourne un tableau d'options, ou null (speedboat = prix sur demande) function getPriceOptions(cat, date) { if (!cat || cat.id === "speedboat") return null; if (cat.id === "ponton") return isWeekendDate(date) ? cat.weekend : cat.weekday; return cat.options || null; } // ───────────────────────────────────────────── // CSS // ───────────────────────────────────────────── const styles = ` .bf .offer-page { padding-block: clamp(24px, 4vw, 48px) clamp(56px, 7vw, 96px); } .bf .offer-back { display:inline-flex; align-items:center; gap:8px; color:${NAVY}; opacity:.7; font-weight:500; font-size:14px; text-decoration:none; margin-bottom:20px; } .bf .offer-back:hover { opacity:1; } .bf .offer-head { max-width:720px; margin-bottom:32px; } .bf .offer-eyebrow { display:inline-block; background:${MIST}; color:${TEAL}; font-weight:700; font-size:12px; letter-spacing:.08em; text-transform:uppercase; padding:6px 12px; border-radius:999px; margin-bottom:14px; } .bf .offer-head h1 { font-family:'Poppins',sans-serif; color:${NAVY}; font-size:clamp(28px,3.6vw,40px); margin:0 0 10px; line-height:1.15; } .bf .offer-head p { color:${NAVY}; opacity:.8; font-size:15px; line-height:1.6; margin:0; } .bf .offer-grid { display:grid; grid-template-columns:minmax(0,1fr) 360px; gap:40px; align-items:start; } /* Left column — carousel + form card empilés */ .bf .offer-main-col { display:flex; flex-direction:column; gap:24px; min-width:0; } /* Carousel section header (hors carte blanche) */ .bf .offer-carousel-section { display:flex; flex-direction:column; } .bf .offer-carousel-section .offer-step-label { margin-bottom:6px; } .bf .offer-carousel-section .offer-step-sub { margin-bottom:12px; } /* Form card */ .bf .offer-card { background:#fff; border:1px solid rgba(0,34,68,.08); border-radius:20px; padding:28px; box-shadow:0 8px 24px rgba(0,34,68,.06); } /* Steps */ .bf .offer-step { margin-bottom:28px; padding-bottom:28px; border-bottom:1px solid rgba(0,34,68,.07); } .bf .offer-step:last-of-type { border-bottom:none; margin-bottom:0; padding-bottom:0; } .bf .offer-step-label { font-family:'Poppins',sans-serif; font-weight:700; color:${NAVY}; font-size:15px; margin-bottom:6px; letter-spacing:-.2px; } .bf .offer-step-sub { font-size:13px; color:${TEAL}; margin-bottom:14px; display:block; } .bf .offer-step-day { font-weight:400; font-size:13px; color:${TEAL}; } /* ── Carousel hero ── */ .bf .offer-carousel { border-radius:20px; overflow:hidden; box-shadow:0 8px 32px rgba(0,34,68,.16); } /* Zone image : bateau centré, respire, fond navy */ .bf .offer-hero-img-wrap { position:relative; height:300px; background:#001535; display:flex; align-items:center; justify-content:center; overflow:hidden; } .bf .offer-hero-img { max-width: calc(100% - 80px); max-height: calc(100% - 24px); width:auto; height:auto; object-fit:contain; display:block; } /* Zone info : séparée, fond navy foncé, texte blanc */ .bf .offer-hero-info { background:#002244; padding:14px 20px; display:flex; align-items:center; justify-content:space-between; gap:14px; } .bf .offer-hero-text { flex:1; min-width:0; } .bf .offer-hero-name { font-family:'Poppins',sans-serif; font-size:17px; font-weight:800; color:#fff; margin:0 0 2px; line-height:1.2; } .bf .offer-hero-desc { font-size:12px; color:rgba(255,255,255,.72); margin:0; line-height:1.4; } .bf .offer-hero-cta { flex-shrink:0; padding:10px 22px; background:${ORANGE}; color:#fff; font-weight:700; font-size:13px; border:none; border-radius:999px; cursor:pointer; transition:filter .15s, transform .15s; white-space:nowrap; } .bf .offer-hero-cta:hover { filter:brightness(1.08); transform:translateY(-1px); } .bf .offer-hero-cta.is-selected { background:${TEAL}; } /* Flèches sur la zone image */ .bf .offer-arrow { position:absolute; top:50%; transform:translateY(-50%); width:38px; height:38px; border:none; border-radius:50%; background:rgba(0,0,0,.42); color:#fff; font-size:20px; display:flex; align-items:center; justify-content:center; cursor:pointer; transition:background .15s; z-index:2; line-height:1; } .bf .offer-arrow:hover { background:rgba(0,0,0,.68); } .bf .offer-prev { left:12px; } .bf .offer-next { right:12px; } /* Miniatures */ .bf .offer-thumbs { display:flex; justify-content:center; gap:14px; margin:16px 20px 14px; } .bf .offer-thumb { flex:0 0 auto; width:104px; border-radius:12px; overflow:hidden; border:2.5px solid transparent; background:#001535; cursor:pointer; box-shadow:0 4px 12px rgba(0,34,68,.12); padding:0; transition:border-color .2s, box-shadow .2s, transform .15s; display:flex; flex-direction:column; } .bf .offer-thumb:hover { transform:translateY(-2px); } .bf .offer-thumb.is-active { border-color:${ORANGE}; box-shadow:0 6px 20px rgba(255,130,0,.3); } .bf .offer-thumb-img { width:100%; height:66px; object-fit:contain; background:#001535; display:block; padding:6px; box-sizing:border-box; } .bf .offer-thumb-label { font-size:11px; font-weight:700; color:${NAVY}; text-align:center; padding:4px 4px; background:${MIST}; } @media (max-width:1060px) { .bf .offer-hero-img-wrap { height:260px; } .bf .offer-hero-name { font-size:16px; } } @media (max-width:860px) { .bf .offer-hero-img-wrap { height:230px; } .bf .offer-thumbs { gap:10px; margin:14px 14px 10px; } .bf .offer-thumb { width:88px; } .bf .offer-thumb-img { height:56px; } } @media (max-width:640px) { .bf .offer-hero-img-wrap { height:190px; } .bf .offer-hero-info { flex-direction:column; align-items:flex-start; gap:10px; } .bf .offer-hero-cta { padding:8px 18px; } .bf .offer-thumbs { gap:8px; margin:12px 10px 8px; } .bf .offer-thumb { width:72px; } .bf .offer-thumb-img { height:46px; } .bf .offer-thumb-label { font-size:10px; } } /* Price section — intro, note, notice */ .bf .oprice-intro { font-size:14px; font-weight:700; color:${NAVY}; margin:0 0 6px; } .bf .oprice-note { font-size:13px; color:${NAVY}; opacity:.72; line-height:1.55; margin:0 0 12px; } .bf .oprice-notice { background:rgba(0,181,226,.07); border:1.5px solid rgba(0,181,226,.2); border-radius:12px; padding:12px 14px; font-size:12.5px; color:${NAVY}; line-height:1.6; margin-bottom:16px; } /* Form fields */ .bf .offer-field { margin-bottom:16px; } .bf .offer-field label { display:block; font-size:13px; font-weight:600; color:${NAVY}; margin-bottom:6px; } .bf .offer-field .req { color:${ORANGE}; } .bf .offer-input, .bf .offer-select { width:100%; box-sizing:border-box; min-height:48px; padding:0 14px; border:1.5px solid rgba(0,34,68,.12); border-radius:12px; font-family:'Inter',sans-serif; font-size:15px; color:${NAVY}; background:#fff; transition:border-color .15s, box-shadow .15s; } .bf .offer-input:focus, .bf .offer-select:focus { outline:none; border-color:${TEAL}; box-shadow:0 0 0 3px rgba(0,181,226,.18); } .bf .offer-input.has-error, .bf .offer-select.has-error { border-color:#d33; } .bf .offer-error { color:#d33; font-size:12px; margin-top:6px; } .bf .offer-hint { color:${NAVY}; opacity:.6; font-size:12px; margin-top:6px; } .bf .offer-row { display:grid; grid-template-columns:1fr 1fr; gap:14px; } /* Price option cards */ .bf .oprice-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:12px; } .bf .oprice-card { background:#fff; border:1.5px solid rgba(0,34,68,.1); border-radius:14px; padding:16px 14px; cursor:pointer; text-align:left; transition:border-color .2s, background .2s, box-shadow .2s; width:100%; box-sizing:border-box; } .bf .oprice-card:hover { border-color:${TEAL}; box-shadow:0 4px 14px rgba(0,181,226,.1); } .bf .oprice-card.is-active { border-color:${ORANGE}; background:rgba(255,130,0,.04); } .bf .oprice-top { display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:10px; gap:6px; } .bf .oprice-ph { font-family:'Poppins',sans-serif; font-size:22px; font-weight:800; color:${NAVY}; white-space:nowrap; } .bf .oprice-ph small { font-size:13px; font-weight:400; opacity:.65; } .bf .oprice-savings { font-size:11px; font-weight:700; color:#1a8f4a; background:rgba(26,143,74,.1); padding:3px 8px; border-radius:999px; white-space:nowrap; flex-shrink:0; margin-top:4px; } .bf .oprice-rows { margin-bottom:10px; } .bf .oprice-row { display:flex; justify-content:space-between; font-size:13px; color:${NAVY}; opacity:.8; padding:2px 0; } .bf .oprice-row b { font-weight:700; opacity:1; } .bf .oprice-label { font-size:11.5px; font-weight:700; color:${NAVY}; background:${MIST}; display:inline-block; padding:4px 10px; border-radius:999px; margin-bottom:10px; } .bf .oprice-cta { width:100%; min-height:36px; background:${NAVY}; color:#fff; border-radius:9px; font-weight:700; font-size:13px; display:flex; align-items:center; justify-content:center; transition:background .15s; } .bf .oprice-card.is-active .oprice-cta { background:${ORANGE}; } .bf .oprice-placeholder { color:${NAVY}; opacity:.45; font-size:14px; padding:20px 0; font-style:italic; } /* Speedboat — sur demande */ .bf .oprice-onrequest { background:${MIST}; border:1.5px solid rgba(0,181,226,.22); border-radius:16px; padding:24px 20px; text-align:center; } .bf .oprice-onrequest-icon { font-size:30px; margin-bottom:10px; } .bf .oprice-onrequest-title { font-family:'Poppins',sans-serif; font-size:15px; font-weight:700; color:${NAVY}; margin-bottom:8px; } .bf .oprice-onrequest-sub { font-size:13px; color:${NAVY}; opacity:.7; line-height:1.55; } /* Sidebar summary */ .bf .offer-summary { background:#fff; border:1px solid rgba(0,34,68,.08); border-radius:18px; padding:20px 22px; } .bf .offer-summary h3 { font-family:'Poppins',sans-serif; color:${NAVY}; font-size:17px; margin:0 0 12px; } .bf .offer-sumrow { display:flex; justify-content:space-between; padding:7px 0; border-bottom:1px dashed rgba(0,34,68,.08); font-size:14px; color:${NAVY}; } .bf .offer-sumrow:last-child { border-bottom:none; } .bf .offer-sumrow b { font-weight:700; } .bf .offer-sumrow.is-big { font-family:'Poppins',sans-serif; font-size:16px; font-weight:700; } .bf .offer-sumrow.is-big b { color:${ORANGE}; } /* Submit */ .bf .offer-submit { width:100%; min-height:54px; background:${ORANGE}; color:#fff; border:0; border-radius:14px; font-weight:700; font-size:16px; cursor:pointer; box-shadow:0 8px 20px rgba(255,130,0,.28); transition:transform .15s, box-shadow .15s; margin-top:8px; } .bf .offer-submit:hover { transform:translateY(-1px); box-shadow:0 12px 26px rgba(255,130,0,.36); } .bf .offer-submit:disabled { opacity:.5; cursor:not-allowed; transform:none; box-shadow:none; } /* Sidebar */ .bf .offer-aside { position:sticky; top:96px; display:flex; flex-direction:column; gap:16px; } .bf .offer-info { background:${NAVY}; color:#fff; border-radius:18px; padding:22px; } .bf .offer-info h3 { font-family:'Poppins',sans-serif; margin:0 0 12px; font-size:18px; } .bf .offer-info ul { list-style:none; padding:0; margin:0; } .bf .offer-info li { display:flex; gap:10px; padding:8px 0; font-size:14px; opacity:.92; line-height:1.5; } .bf .offer-info li::before { content:""; flex-shrink:0; width:6px; height:6px; border-radius:50%; background:${TEAL}; margin-top:8px; } .bf .offer-refund { background:#fff; border:1.5px solid ${ORANGE}; border-radius:18px; padding:20px; display:flex; gap:12px; align-items:flex-start; } .bf .offer-refund .badge { background:${ORANGE}; color:#fff; font-size:11px; font-weight:800; letter-spacing:.06em; padding:5px 9px; border-radius:6px; flex-shrink:0; } .bf .offer-refund p { margin:0; font-size:13px; color:${NAVY}; line-height:1.5; } .bf .offer-refund p strong { font-weight:700; } /* Responsive */ @media (max-width:1060px) { .bf .offer-grid { gap:28px; } } @media (max-width:860px) { .bf .offer-grid { grid-template-columns:1fr; gap:24px; } .bf .offer-aside { position:static; } } @media (max-width: 600px) { .bf .oprice-grid { grid-template-columns:1fr; } } @media (max-width: 500px) { .bf .offer-card { padding:20px; } .bf .offer-row { grid-template-columns:1fr; } } `; // ───────────────────────────────────────────── // COMPONENTS // ───────────────────────────────────────────── function PriceCard({ opt, selected, onSelect, maxPriceH }) { const total = opt.priceH * 4; const deposit = Math.round(total * 0.25); const savings = (maxPriceH && maxPriceH > opt.priceH) ? (maxPriceH - opt.priceH) * 4 : 0; return ( ); } // ───────────────────────────────────────────── // MAIN PAGE // ───────────────────────────────────────────── function BFPageOffer() { const [catId, setCatId] = useState(null); const [carouselIndex, setCarouselIndex] = useState(0); const category = useMemo(() => CATEGORIES.find(c => c.id === catId) || null, [catId]); const [form, setForm] = useState({ date: "", slot: "", persons: "", priceH: null, name: "", email: "", phone: "" }); const [errors, setErrors] = useState({}); const [submitting, setSubmitting] = useState(false); const weekend = useMemo(() => isWeekendDate(form.date), [form.date]); const priceOptions = useMemo(() => getPriceOptions(category, form.date), [category, form.date]); const maxPriceH = useMemo( () => priceOptions ? Math.max(...priceOptions.map(o => o.priceH)) : 0, [priceOptions] ); const total4h = form.priceH ? form.priceH * 4 : null; const deposit = total4h ? Math.round(total4h * 0.25) : null; const dayLabel = form.date ? (weekend ? "Weekend (ven – dim)" : "Semaine (lun – jeu)") : null; // Sélectionne une catégorie ET synchronise le carousel const handleCat = (id) => { setCatId(id); setCarouselIndex(CATEGORIES.findIndex(c => c.id === id)); setForm(f => ({ ...f, priceH: null })); setErrors(e => ({ ...e, cat: null, price: null })); }; const prevSlide = () => setCarouselIndex(i => (i + CATEGORIES.length - 1) % CATEGORIES.length); const nextSlide = () => setCarouselIndex(i => (i + 1) % CATEGORIES.length); const handleField = (key) => (e) => { const val = e.target.value; setForm(f => { const next = { ...f, [key]: val }; if (key === "date") next.priceH = null; return next; }); setErrors(e => ({ ...e, [key]: null })); }; const handlePrice = (priceH) => { setForm(f => ({ ...f, priceH })); setErrors(e => ({ ...e, price: null })); }; const validate = () => { const errs = {}; if (!catId) errs.cat = "Choisissez une catégorie."; if (!form.date) errs.date = "Date requise."; if (!form.slot) errs.slot = "Choisissez une plage horaire."; if (!form.persons) errs.persons = "Indiquez le nombre de personnes."; // Speedboat : pas de sélection de prix requise (sur demande) if (catId !== "speedboat" && !form.priceH) errs.price = "Choisissez une option de prix."; if (!form.name.trim()) errs.name = "Nom requis."; if (!form.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) errs.email = "Email invalide."; if (!form.phone.trim()) errs.phone = "Téléphone requis."; return errs; }; const handleSubmit = (e) => { e.preventDefault(); const errs = validate(); setErrors(errs); if (Object.keys(errs).length > 0) return; setSubmitting(true); const payload = { category: category.label, date: form.date, slot: form.slot, persons: form.persons, priceH: form.priceH, total4h, deposit, contact: { name: form.name, email: form.email, phone: form.phone }, submittedAt: new Date().toISOString() }; console.log("[BoatsFun] Offre soumise :", payload); setTimeout(() => { alert( `Offre envoyée — ${category.label} · ` + `${form.priceH ? form.priceH + " $/h" : "prix sur demande"} · ` + `${form.date} (${form.slot})\n` + `${total4h ? "Total 4h : " + total4h + " $ · Dépôt : " + deposit + " $\n" : ""}` + `${form.persons} pers.\n\n` + `Le propriétaire a 24h pour accepter. Si refusée, dépôt remboursé à 100 %.\n(HubSpot à brancher en V1+.)` ); setSubmitting(false); }, 200); }; const currentCat = CATEGORIES[carouselIndex]; return (
← Retour au catalogue
Proposez votre prix

Faire une offre

Choisissez la catégorie qui vous convient, sélectionnez vos dates et votre option de prix parmi nos choix prédéfinis.{" "} Les offres plus élevées ont plus de chances d'être acceptées rapidement par un propriétaire.

{/* ── COLONNE PRINCIPALE ── */}
{/* ── ÉTAPE 1 : CAROUSEL (hors carte blanche) ── */}
1 — Catégorie de bateau
Naviguez avec les flèches ou les miniatures, puis cliquez "Choisir".
{/* Zone image — bateau centré, respire */}
{currentCat.label}
{/* Zone info — séparée de l'image, texte toujours lisible */}
{currentCat.label}
{currentCat.desc}
{/* Miniatures */}
{CATEGORIES.map((c, idx) => ( ))}
{errors.cat &&
{errors.cat}
}
{/* ── FORM (étapes 2-4) ── */}
{/* ÉTAPE 2 — Date, horaire, personnes */}
2 — Date & horaire
{dayLabel &&
{dayLabel}
} {errors.date &&
{errors.date}
}
{errors.slot &&
{errors.slot}
}
{errors.persons &&
{errors.persons}
}
{/* ÉTAPE 3 — Option de prix */}
{"3 — Option de prix"} {catId === "ponton" && dayLabel && ( · {dayLabel} )}
{catId === "speedboat" ? ( /* Speedboat : prix sur demande */
💬
Options de prix à confirmer pour cette catégorie.
Notre équipe vous contactera après soumission pour définir le tarif adapté à votre sortie en Speedboat.
) : catId && priceOptions ? ( /* Ponton / Cruiser / Yacht : intro + notice + grille de prix */

Choisissez un prix de rabais.

Plus votre offre est basse, plus vos chances d'acceptation diminuent. Les offres plus élevées sont généralement acceptées plus rapidement par les propriétaires.

À titre indicatif, les offres tournent généralement entre 150 $/h et 200 $/h en semaine, et entre 150 $/h et 250 $/h en fin de semaine, selon la catégorie de bateau et le niveau d'offre choisi.
{priceOptions.map(opt => ( ))}
) : ( /* Aucune catégorie sélectionnée */
Choisissez une catégorie pour voir les options de prix.
)} {errors.price &&
{errors.price}
}
{/* ÉTAPE 4 — Coordonnées */}
4 — Vos coordonnées
{errors.name &&
{errors.name}
}
{errors.phone &&
{errors.phone}
}
{errors.email &&
{errors.email}
}
{/* /offer-main-col */} {/* ── SIDEBAR ── */}
); } window.BFPageOffer = BFPageOffer; })();