{"id":5877,"date":"2024-05-28T13:49:06","date_gmt":"2024-05-28T17:49:06","guid":{"rendered":"https:\/\/nettoyagethermopompegatineau.ca\/?page_id=5877"},"modified":"2026-08-20T17:45:55","modified_gmt":"2026-08-20T21:45:55","slug":"thank-you","status":"publish","type":"page","link":"https:\/\/nettoyagethermopompegatineau.ca\/en\/merci\/","title":{"rendered":"Thank you"},"content":{"rendered":"<section class=\"bde-section-5877-100 bde-section\">\n  \n  \n\t\n\n\n\n<div class=\"section-container\"><div class=\"bde-div-5877-101 bde-div\">\n  \n  \n\t\n\n\n\n<h1 class=\"bde-heading-5877-108 bde-heading\">\nMerci pour votre demande!\n<\/h1><div class=\"bde-text-5877-105 bde-text\">\nObtenez votre soumission instantan\u00e9ment<br>Utilisez notre outil de r\u00e9servation en ligne pour obtenir votre prix et choisir une plage horaire d\u00e8s maintenant.\n<\/div><div class=\"bde-shortcode-5877-117 bde-shortcode\">\t<style>\/* Air-FP \u2014 r\u00e9servation en ligne : CSS de l'app, cadr\u00e9 sous #airfp-app.\n   G\u00e9n\u00e9r\u00e9 depuis le fichier source de l'app. Chaque r\u00e8gle a \u00e9t\u00e9 pr\u00e9fix\u00e9e pour\n   \u00e9viter que les styles de l'app repeignent le site, et inversement.\n   :root et body sont devenus #airfp-app ; la barre fixe est devenue collante. *\/\n\n  #airfp-app{\n    --bg:#F5F7F6; --surface:#FFFFFF; --ink:#14201E; --ink-soft:#55645F;\n    --line:#DDE4E1; --teal:#0C8479; --teal-dark:#095F58; --teal-tint:#E3F3F1;\n    --green:#4C8C4A; --green-tint:#EAF3E9; --radius:14px;\n    --font-display:'Fraunces', serif; --font-body:'Inter', system-ui, sans-serif; --font-mono:'IBM Plex Mono', monospace;\n  }\n  #airfp-app *{box-sizing:border-box;}\n  #airfp-app{margin:0; background:var(--bg); color:var(--ink); font-family:var(--font-body); -webkit-font-smoothing:antialiased;}\n  #airfp-app h1, #airfp-app h2, #airfp-app h3{font-family:var(--font-display); font-weight:600; margin:0;}\n  #airfp-app .wrap{max-width:760px; margin:0 auto; padding:28px 20px 140px;}\n  \/* ---------- header + progress (duct motif) ---------- *\/\n  #airfp-app header.top{padding:6px 0 20px;}\n  #airfp-app .brand{display:flex; align-items:center; gap:12px; font-family:var(--font-display); font-size:20px; font-weight:600; color:var(--teal-dark); margin-bottom:22px;}\n  #airfp-app .brand img.wordmark{height:34px; width:auto;}\n  #airfp-app .brand-sub{font-size:13px; font-weight:500; color:var(--ink-soft); font-family:var(--font-body); border-left:1.5px solid var(--line); padding-left:12px;}\n  #airfp-app .multi-banner{background:var(--teal-tint); border:1.5px solid var(--teal); border-radius:10px; padding:12px 16px; margin-bottom:22px; font-size:13px; line-height:1.5;}\n  #airfp-app .multi-banner strong{display:block; color:var(--teal-dark); font-size:13.5px; margin-bottom:3px;}\n  #airfp-app .multi-banner span{color:var(--ink-soft);}\n  #airfp-app .duct{position:relative; display:flex; justify-content:space-between; align-items:center; margin:0 4px;}\n  #airfp-app .duct-track{position:absolute; left:14px; right:14px; top:14px; height:2px; background:var(--line); z-index:0;}\n  #airfp-app .duct-fill{position:absolute; left:14px; top:14px; height:2px; background:var(--teal); z-index:1; transition:width .4s ease; overflow:hidden;}\n  #airfp-app .duct-fill::after{content:''; position:absolute; inset:0; background:repeating-linear-gradient(90deg, rgba(255,255,255,.55) 0 6px, transparent 6px 14px); animation:flow 1.1s linear infinite;}\n  @media (prefers-reduced-motion:reduce){ #airfp-app .duct-fill::after{animation:none;} }\n  @keyframes flow{to{background-position:20px 0;}}\n  #airfp-app .step-dot{position:relative; z-index:2; width:28px; height:28px; border-radius:50%; background:var(--surface); border:2px solid var(--line); display:flex; align-items:center; justify-content:center; font-family:var(--font-mono); font-size:12px; color:var(--ink-soft); transition:all .25s;}\n  #airfp-app .step-dot.done{background:var(--teal); border-color:var(--teal); color:#fff;}\n  #airfp-app .step-dot.active{border-color:var(--teal); color:var(--teal); box-shadow:0 0 0 4px var(--teal-tint);}\n  \/* ---------- steps ---------- *\/\n  #airfp-app .step{display:none;}\n  #airfp-app .step.active{display:block; animation:rise .3s ease;}\n  @keyframes rise{from{opacity:0; transform:translateY(6px);} to{opacity:1; transform:none;}}\n  #airfp-app .eyebrow{font-family:var(--font-mono); font-size:12px; letter-spacing:.06em; color:var(--teal); text-transform:uppercase; margin-bottom:6px;}\n  #airfp-app .step h2{font-size:26px; margin-bottom:6px;}\n  #airfp-app .step p.sub{color:var(--ink-soft); margin:0 0 24px; font-size:15px;}\n  \/* chips *\/\n  #airfp-app .chips{display:flex; gap:10px; flex-wrap:wrap; margin-bottom:22px;}\n  #airfp-app .chip{padding:10px 18px; border-radius:999px; border:1.5px solid var(--line); background:var(--surface); font-size:14px; font-weight:500; cursor:pointer; transition:all .15s;}\n  #airfp-app .chip:hover{border-color:var(--teal);}\n  #airfp-app .chip.selected{background:var(--teal); border-color:var(--teal); color:#fff;}\n  #airfp-app input[type=text], #airfp-app input[type=email], #airfp-app input[type=tel]{\n    width:100%; padding:13px 14px; border:1.5px solid var(--line); border-radius:10px; font-family:var(--font-body); font-size:15px; background:var(--surface); margin-bottom:16px;\n  }\n  #airfp-app input:focus{outline:none; border-color:var(--teal); box-shadow:0 0 0 3px var(--teal-tint);}\n  #airfp-app label.field-label{display:block; font-size:13px; font-weight:500; color:var(--ink-soft); margin-bottom:6px;}\n  \/* service cards *\/\n  #airfp-app .grid{display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-bottom:20px;}\n  @media(max-width:560px){ #airfp-app .grid{grid-template-columns:1fr;} }\n  #airfp-app .card{border:1.5px solid var(--line); border-radius:var(--radius); padding:16px; background:var(--surface); cursor:pointer; transition:all .15s; position:relative;}\n  #airfp-app .card .badge{position:absolute; top:12px; right:12px; background:var(--green); color:#fff; font-size:10.5px; font-weight:600; padding:4px 9px; border-radius:999px; max-width:120px; text-align:center; line-height:1.2;}\n  #airfp-app .card:hover{border-color:var(--teal); transform:translateY(-1px);}\n  #airfp-app .card.selected{border-color:var(--teal); background:var(--teal-tint);}\n  #airfp-app .card .icon{width:34px; height:34px; color:var(--teal); margin-bottom:10px;}\n  #airfp-app .card h3{font-size:15px; margin-bottom:4px;}\n  #airfp-app .card p{font-size:13px; color:var(--ink-soft); margin:0 0 10px; line-height:1.4;}\n  #airfp-app .card .price{font-family:var(--font-mono); font-size:13px; color:var(--teal-dark);}\n  #airfp-app .price-note{font-size:11px; color:var(--ink-soft); font-style:italic; margin-top:5px; line-height:1.4;}\n  #airfp-app .price-old{text-decoration:line-through; color:var(--ink-soft); font-weight:400;}\n  #airfp-app .price-promo{color:var(--green); font-weight:600;}\n  \/* option rows (configurateur) *\/\n  #airfp-app .option-row{display:flex; justify-content:space-between; align-items:center; padding:14px 16px; border:1.5px solid var(--line); border-radius:10px; margin-bottom:10px; background:var(--surface);}\n  #airfp-app .option-row.on{border-color:var(--teal); background:var(--teal-tint);}\n  #airfp-app .option-row .txt strong{display:block; font-size:14px;}\n  #airfp-app .option-row .txt span{font-size:12px; color:var(--ink-soft);}\n  #airfp-app .option-row .delta{font-family:var(--font-mono); font-size:13px; color:var(--teal-dark); white-space:nowrap; margin-left:12px;}\n  #airfp-app .toggle{width:20px; height:20px; border-radius:6px; border:1.5px solid var(--line); flex-shrink:0; margin-left:10px; position:relative;}\n  #airfp-app .option-row.on .toggle{background:var(--teal); border-color:var(--teal);}\n  #airfp-app .option-row.on .toggle::after{content:'\u2713'; color:#fff; font-size:13px; position:absolute; inset:0; display:flex; align-items:center; justify-content:center;}\n  #airfp-app .empty-note{font-size:13px; color:var(--ink-soft); font-style:italic; padding:14px 0;}\n  \/* extras *\/\n  #airfp-app .extra-card{display:flex; gap:14px; border:1.5px solid var(--line); border-radius:var(--radius); padding:16px; margin-bottom:12px; background:var(--surface); align-items:center;}\n  #airfp-app .extra-card .txt{flex:1;}\n  #airfp-app .extra-card h3{font-size:15px; margin-bottom:3px;}\n  #airfp-app .extra-card p{font-size:13px; color:var(--ink-soft); margin:0;}\n  #airfp-app .btn-add{font-size:13px; font-weight:600; padding:9px 16px; border-radius:8px; border:1.5px solid var(--teal); color:var(--teal); background:#fff; cursor:pointer; white-space:nowrap;}\n  #airfp-app .btn-add.on{background:var(--teal); color:#fff;}\n  #airfp-app .qty-stepper{display:flex; align-items:center; gap:12px;}\n  #airfp-app .qty-btn{width:30px; height:30px; border-radius:8px; border:1.5px solid var(--teal); background:#fff; color:var(--teal); font-size:16px; font-weight:600; cursor:pointer; line-height:1;}\n  #airfp-app .qty-val{font-family:var(--font-mono); font-weight:600; font-size:15px; min-width:14px; text-align:center;}\n  \/* slots *\/\n  #airfp-app .day-tabs{display:flex; gap:8px; overflow-x:auto; padding-bottom:10px; margin-bottom:16px;}\n  #airfp-app .day-tab{flex-shrink:0; padding:10px 16px; border-radius:10px; border:1.5px solid var(--line); background:var(--surface); font-size:13px; cursor:pointer; text-align:center; min-width:78px;}\n  #airfp-app .day-tab.selected{border-color:var(--teal); background:var(--teal-tint);}\n  #airfp-app .day-tab .d{font-weight:600; font-family:var(--font-mono); font-size:12px; text-transform:uppercase; color:var(--ink-soft);}\n  #airfp-app .day-tab .n{font-size:16px; font-family:var(--font-display); font-weight:600;}\n  #airfp-app .slot-grid{display:grid; grid-template-columns:1fr 1fr; gap:10px;}\n  @media(max-width:480px){ #airfp-app .slot-grid{grid-template-columns:1fr;} }\n  #airfp-app .slot-section-title{grid-column:1\/-1; font-family:var(--font-mono); font-size:11.5px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; color:var(--ink-soft); margin:4px 0 -2px;}\n  #airfp-app .slot-section-title:first-child{margin-top:0;}\n  #airfp-app .slot-section-title.eco{color:var(--green);}\n  #airfp-app .slot{border:1.5px solid var(--line); border-radius:10px; padding:12px 14px; cursor:pointer; background:var(--surface); position:relative;}\n  #airfp-app .slot:hover{border-color:var(--teal);}\n  #airfp-app .slot.selected{border-color:var(--teal); background:var(--teal-tint);}\n  #airfp-app .slot.eco{border-color:var(--green); background:var(--green-tint);}\n  #airfp-app .slot.eco.selected{background:var(--green-tint); box-shadow:0 0 0 2px var(--green);}\n  #airfp-app .slot .time{font-family:var(--font-mono); font-weight:600; font-size:15px;}\n  #airfp-app .slot .note{font-size:11.5px; color:var(--ink-soft); margin-top:3px;}\n  #airfp-app .slot .slot-date{font-family:var(--font-mono); font-size:10.5px; font-weight:600; text-transform:uppercase; color:var(--ink-soft); margin-bottom:3px;}\n  #airfp-app .badge-eco{display:inline-block; font-size:10.5px; font-weight:600; color:var(--green); background:#fff; border:1px solid var(--green); padding:2px 7px; border-radius:999px; margin-top:6px;}\n  \/* summary *\/\n  #airfp-app .summary-block{border:1.5px solid var(--line); border-radius:var(--radius); padding:18px 20px; margin-bottom:14px; background:var(--surface);}\n  #airfp-app .summary-block h3{font-size:13px; text-transform:uppercase; letter-spacing:.04em; color:var(--ink-soft); font-family:var(--font-mono); font-weight:500; margin-bottom:10px;}\n  #airfp-app .summary-row{display:flex; justify-content:space-between; font-size:14px; padding:5px 0;}\n  #airfp-app .summary-row .v{font-family:var(--font-mono);}\n  #airfp-app .check-row{display:flex; align-items:flex-start; gap:10px; padding:14px; border:1.5px dashed var(--line); border-radius:10px; margin-top:6px; cursor:pointer;}\n  #airfp-app .check-row input{margin-top:2px;}\n  #airfp-app .check-row span{font-size:13px; color:var(--ink-soft);}\n  \/* footer bar \u2014 \u00e9tait position:fixed dans l'app autonome ; devient collante\n     pour rester dans le flux de la page qui l'accueille. *\/\n  #airfp-app .footbar{position:sticky; bottom:0; background:var(--surface); border-top:1.5px solid var(--line); padding:14px 20px; display:flex; justify-content:space-between; align-items:center; z-index:10;}\n  #airfp-app .footbar .total{font-family:var(--font-mono);}\n  #airfp-app .footbar .total .amt{font-size:19px; font-weight:600; color:var(--teal-dark);}\n  #airfp-app .footbar .total .dur{font-size:12px; color:var(--ink-soft); display:block;}\n  #airfp-app .btns{display:flex; gap:10px;}\n  #airfp-app .btn{padding:12px 22px; border-radius:10px; font-size:14px; font-weight:600; cursor:pointer; border:none; font-family:var(--font-body);}\n  #airfp-app .btn-primary{background:var(--teal); color:#fff;}\n  #airfp-app .btn-primary:disabled{background:#B9CBC8; cursor:not-allowed;}\n  #airfp-app .btn-ghost{background:transparent; color:var(--ink-soft); border:1.5px solid var(--line);}\n  #airfp-app .final-msg{text-align:center; padding:40px 20px;}\n  #airfp-app .final-msg .icon{width:56px; height:56px; color:var(--teal); margin:0 auto 16px;}\n\n\/* ---------- Cadrage : neutralise les styles globaux du th\u00e8me ----------\n   Breakdance applique color et font-size directement sur h1-h6 et body, avec des\n   s\u00e9lecteurs \u00e0 balise nue. Dans l'app autonome, la couleur des titres venait de\n   l'h\u00e9ritage depuis body ; une r\u00e8gle directe bat l'h\u00e9ritage, donc on la r\u00e9affirme\n   ici, sinon les titres de l'app prennent la couleur du site. *\/\n#airfp-app h1, #airfp-app h2, #airfp-app h3,\n#airfp-app h4, #airfp-app h5, #airfp-app h6 { color: var(--ink); letter-spacing: normal; text-transform: none; }\n#airfp-app p { margin: 0; }\n#airfp-app button { font-family: var(--font-body); text-transform: none; letter-spacing: normal; }\n#airfp-app input { color: var(--ink); }\n#airfp-app label { display: block; }\n\/* Le theme pose body { font-size: 1.2rem; line-height: 1.4 } : ces valeurs\n   descendaient dans l'app, qui elle attend les defauts du navigateur\n   (16px \/ normal). Mesure faite sur la version autonome de l'app. *\/\n#airfp-app { box-sizing: border-box; text-align: left; font-size: 16px; line-height: normal; }\n<\/style>\n\t<div id=\"airfp-app\">\n\n<div class=\"wrap\">\n  <header class=\"top\">\n    <div class=\"brand\"><img decoding=\"async\" class=\"wordmark\" src=\"https:\/\/nettoyagethermopompegatineau.ca\/wp-content\/uploads\/2026\/01\/Logo_AirFP.png\" alt=\"Air-FP\"><span class=\"brand-sub\">R\u00e9servation en ligne<\/span><\/div>\n    <div class=\"duct\">\n      <div class=\"duct-track\"><\/div>\n      <div class=\"duct-fill\" id=\"ductFill\" style=\"width:0%\"><\/div>\n      <div class=\"step-dot\" data-n=\"1\">1<\/div>\n      <div class=\"step-dot\" data-n=\"2\">2<\/div>\n      <div class=\"step-dot\" data-n=\"3\">3<\/div>\n      <div class=\"step-dot\" data-n=\"4\">4<\/div>\n      <div class=\"step-dot\" data-n=\"5\">5<\/div>\n      <div class=\"step-dot\" data-n=\"6\">6<\/div>\n    <\/div>\n  <\/header>\n\n  <div class=\"multi-banner\" id=\"multiBanner\">\n    <strong>Vous \u00eates propri\u00e9taire d'un immeuble multilogement?<\/strong>\n    <span>Contactez-nous directement par courriel, texto ou t\u00e9l\u00e9phone afin d'obtenir une soumission personnalis\u00e9e et de profiter de nos tarifs pr\u00e9f\u00e9rentiels pour les immeubles multilogements.<\/span>\n  <\/div>\n\n  <!-- STEP 1 \u2014 Localisation -->\n  <section class=\"step\" data-step=\"1\">\n    <div class=\"eyebrow\">\u00c9tape 1 \u00b7 Localisation<\/div>\n    <h2>O\u00f9 intervenons-nous?<\/h2>\n    <p class=\"sub\">\u00c7a nous permet de v\u00e9rifier la disponibilit\u00e9 de nos \u00e9quipes dans votre secteur.<\/p>\n    <label class=\"field-label\">Adresse compl\u00e8te<\/label>\n    <input type=\"text\" id=\"inpAdresse\" name=\"address-line1\" autocomplete=\"address-line1\" placeholder=\"123 rue Principale\">\n    <label class=\"field-label\">Ville<\/label>\n    <input type=\"text\" id=\"inpVille\" name=\"address-level2\" autocomplete=\"address-level2\" placeholder=\"Gatineau\">\n    <label class=\"field-label\">Code postal<\/label>\n    <input type=\"text\" id=\"inpCodePostal\" name=\"postal-code\" autocomplete=\"postal-code\" placeholder=\"J8X 0A0\">\n    <label class=\"field-label\">Type de logement<\/label>\n    <div class=\"chips\" id=\"chipsLogement\">\n      <div class=\"chip\" data-v=\"maison\">Maison<\/div>\n      <div class=\"chip\" data-v=\"condo\">Condo<\/div>\n      <div class=\"chip\" data-v=\"semi\">Semi-d\u00e9tach\u00e9<\/div>\n      <div class=\"chip\" data-v=\"appartement\">Appartement<\/div>\n    <\/div>\n  <\/section>\n\n  <!-- STEP 2 \u2014 Service + configurateur -->\n  <section class=\"step\" data-step=\"2\">\n    <div class=\"eyebrow\">\u00c9tape 2 \u00b7 Service<\/div>\n    <h2>Que nettoyons-nous?<\/h2>\n    <p class=\"sub\">Choisissez un service pour voir le prix et la dur\u00e9e estim\u00e9e.<\/p>\n    <div class=\"grid\" id=\"serviceGrid\"><\/div>\n    <div id=\"configArea\"><\/div>\n  <\/section>\n\n  <!-- STEP 3 \u2014 Extras -->\n  <section class=\"step\" data-step=\"3\">\n    <div class=\"eyebrow\">\u00c9tape 3 \u00b7 Extras (optionnel)<\/div>\n    <h2>Pendant qu'on est chez vous<\/h2>\n    <p class=\"sub\">Ajoutez un service compl\u00e9mentaire au m\u00eame rendez-vous \u2014 \u00e9vitez une visite s\u00e9par\u00e9e.<\/p>\n    <div id=\"extrasArea\"><\/div>\n  <\/section>\n\n  <!-- STEP 4 \u2014 Plages -->\n  <section class=\"step\" data-step=\"4\">\n    <div class=\"eyebrow\">\u00c9tape 4 \u00b7 Disponibilit\u00e9s<\/div>\n    <h2>Choisissez une plage<\/h2>\n    <p class=\"sub\">Les plages en <span style=\"color:var(--green); font-weight:600;\">vert<\/span> profitent d'un rabais de 10$ \u2014 notre \u00e9quipe est d\u00e9j\u00e0 dans votre secteur ce jour-l\u00e0.<\/p>\n    <div id=\"ecoSection\"><\/div>\n    <p class=\"slot-section-title\" style=\"margin-top:16px;\">Ou choisissez une journ\u00e9e pr\u00e9cise<\/p>\n    <div class=\"day-tabs\" id=\"dayTabs\"><\/div>\n    <div class=\"slot-grid\" id=\"slotGrid\"><\/div>\n  <\/section>\n\n  <!-- STEP 5 \u2014 Coordonn\u00e9es -->\n  <section class=\"step\" data-step=\"5\">\n    <div class=\"eyebrow\">\u00c9tape 5 \u00b7 Coordonn\u00e9es<\/div>\n    <h2>Pour vous confirmer le rendez-vous<\/h2>\n    <p class=\"sub\">On envoie une confirmation par courriel et texto d\u00e8s que c'est r\u00e9serv\u00e9.<\/p>\n    <label class=\"field-label\">Nom complet<\/label>\n    <input type=\"text\" id=\"inpNom\" name=\"name\" autocomplete=\"name\" placeholder=\"Pr\u00e9nom et nom\">\n    <label class=\"field-label\">Courriel<\/label>\n    <input type=\"email\" id=\"inpCourriel\" name=\"email\" autocomplete=\"email\" placeholder=\"vous@courriel.com\">\n    <label class=\"field-label\">T\u00e9l\u00e9phone<\/label>\n    <input type=\"tel\" id=\"inpTel\" name=\"tel\" autocomplete=\"tel\" placeholder=\"819 000-0000\">\n    <label class=\"check-row\">\n      <input type=\"checkbox\" id=\"chkAttente\">\n      <span>Me contacter si une disponibilit\u00e9 se lib\u00e8re plus t\u00f4t que la plage choisie.<\/span>\n    <\/label>\n  <\/section>\n\n  <!-- STEP 6 \u2014 R\u00e9sum\u00e9 -->\n  <section class=\"step\" data-step=\"6\">\n    <div class=\"eyebrow\">\u00c9tape 6 \u00b7 R\u00e9sum\u00e9<\/div>\n    <h2>V\u00e9rifiez votre r\u00e9servation<\/h2>\n    <p class=\"sub\">Une derni\u00e8re v\u00e9rification avant l'envoi.<\/p>\n    <div id=\"summaryArea\"><\/div>\n    <div class=\"summary-block\" id=\"promoBlock\">\n      <h3>Code promotionnel (optionnel)<\/h3>\n      <div style=\"display:flex;gap:8px;\">\n        <input type=\"text\" id=\"inpCodePromo\" placeholder=\"Code promotionnel\" style=\"flex:1;text-transform:uppercase;\">\n        <button type=\"button\" class=\"btn btn-ghost\" id=\"btnAppliquerPromo\">Appliquer<\/button>\n      <\/div>\n      <p class=\"sub\" id=\"promoMsg\"><\/p>\n    <\/div>\n    <div id=\"finalArea\"><\/div>\n  <\/section>\n<\/div>\n\n<div class=\"footbar\">\n  <div class=\"total\">\n    <span class=\"amt\" id=\"totalAmt\">0,00 $<\/span>\n  <\/div>\n  <div class=\"btns\">\n    <button class=\"btn btn-ghost\" id=\"btnBack\">Pr\u00e9c\u00e9dent<\/button>\n    <button class=\"btn btn-primary\" id=\"btnNext\" disabled>Suivant<\/button>\n  <\/div>\n<\/div>\n\n<\/div>\n\t<script>window.AIRFP = {\"endpoint\":\"https:\\\/\\\/nettoyagethermopompegatineau.ca\\\/en\\\/wp-json\\\/airfp\\\/v1\\\/reservation\",\"nonce\":\"86e52e86e6\",\"prefill\":[]};<\/script>\n\t<script>\n\/* ============================================================\n   CONFIGURATION \u2014 \u00e0 ajuster avant mise en ligne\n   ============================================================ *\/\nconst SUPABASE_URL = 'https:\/\/hlcvuhwqlqytmnleqgqw.supabase.co';\nconst SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhsY3Z1aHdxbHF5dG1ubGVxZ3F3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODQ3NDA3NjAsImV4cCI6MjEwMDMxNjc2MH0.vli-DqA-nNXdRos3fr9jQINoPvlcE4Ca0dJoV_oUQO4';\n\/\/ TODO PO: colle ici l'URL de ton webhook Make.com pour la soumission finale.\n\/* Le webhook du client n'est plus appele depuis le navigateur : WordPress s'en\n   charge au moment d'enregistrer la reservation (voir airfp-reservation.php).\n   Un appel serveur ne se perd pas si le visiteur ferme l'onglet, et il n'y a\n   qu'un seul endroit a maintenir. Le guard `if(WEBHOOK_URL)` plus bas neutralise\n   l'appel d'origine sans qu'on touche au reste du code. *\/\nconst WEBHOOK_URL = '';\n\n\/* ============================================================\n   Google Places Autocomplete \u2014 adresse\n   ============================================================ *\/\nfunction initGoogleAutocomplete(){\n  const input = document.getElementById('inpAdresse');\n  if(!input || !window.google) return;\n  const autocomplete = new google.maps.places.Autocomplete(input, {\n    componentRestrictions: { country: 'ca' },\n    fields: ['address_components', 'geometry'],\n    types: ['address']\n  });\n  autocomplete.addListener('place_changed', () => {\n    const place = autocomplete.getPlace();\n    if(!place.address_components) return;\n    let streetNumber='', route='', ville='', codePostal='';\n    place.address_components.forEach(c=>{\n      if(c.types.includes('street_number')) streetNumber = c.long_name;\n      if(c.types.includes('route')) route = c.long_name;\n      if(c.types.includes('locality')) ville = c.long_name;\n      if(c.types.includes('postal_code')) codePostal = c.long_name;\n    });\n    const adresseFormatee = `${streetNumber} ${route}`.trim();\n    input.value = adresseFormatee;\n    state.adresse = adresseFormatee;\n    state.ville = ville;\n    state.codePostal = codePostal;\n    document.getElementById('inpVille').value = ville;\n    document.getElementById('inpCodePostal').value = codePostal;\n    if(place.geometry && place.geometry.location){\n      state.adresseLat = place.geometry.location.lat();\n      state.adresseLng = place.geometry.location.lng();\n    }\n    validateStep();\n  });\n}\n\n\/* ============================================================\n   Helper Supabase (lecture seule \u2014 services & extras uniquement)\n   ============================================================ *\/\nasync function sb(path){\n  const res = await fetch(`${SUPABASE_URL}\/rest\/v1\/${path}`, {\n    headers:{ apikey: SUPABASE_ANON_KEY, Authorization:`Bearer ${SUPABASE_ANON_KEY}` }\n  });\n  if(!res.ok) throw new Error('Erreur Supabase '+res.status);\n  return res.json();\n}\n\nasync function sbInsert(table, row){\n  const res = await fetch(`${SUPABASE_URL}\/rest\/v1\/${table}`, {\n    method:'POST',\n    headers:{\n      apikey: SUPABASE_ANON_KEY, Authorization:`Bearer ${SUPABASE_ANON_KEY}`,\n      'Content-Type':'application\/json', 'Prefer':'return=minimal'\n    },\n    body: JSON.stringify(row)\n  });\n  if(!res.ok) throw new Error(`Erreur Supabase (${table}) ${res.status}`);\n}\nfunction uuid(){\n  return (crypto.randomUUID ? crypto.randomUUID() :\n    'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(\/[xy]\/g, c=>{\n      const r = Math.random()*16|0, v = c==='x'?r:(r&0x3|0x8);\n      return v.toString(16);\n    }));\n}\n\n\/* ============================================================\n   Ic\u00f4nes (monoline, par mot-cl\u00e9)\n   ============================================================ *\/\nfunction iconFor(nom){\n  const n = nom.toLowerCase();\n  if(n.includes('combo')) return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"9\" cy=\"12\" r=\"6\"\/><circle cx=\"15\" cy=\"12\" r=\"6\"\/><\/svg>`;\n  if(n.includes('fan') || n.includes('salle de bain')) return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"12\" cy=\"12\" r=\"1.8\"\/><path d=\"M12 10c0-3 2-5 5-5s2 3-1 4M12 14c0 3-2 5-5 5s-2-3 1-4M14 12c3 0 5 2 5 5s-3 2-4-1M10 12c-3 0-5-2-5-5s3-2 4 1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg>`;\n  if(n.includes('s\u00e9cheuse')) return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M12 2c2 3-2 4 0 7s-2 4 0 7\" stroke-linecap=\"round\"\/><path d=\"M6 12c1 2-1 2 0 4M18 12c-1 2 1 2 0 4\" stroke-linecap=\"round\"\/><\/svg>`;\n  if(n.includes('thermopompe')) return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"7\" width=\"16\" height=\"10\" rx=\"2\"\/><path d=\"M8 7v10M12 3v4M12 17v4M16 7v10\" stroke-linecap=\"round\"\/><\/svg>`;\n  if(n.includes('\u00e9changeur')) return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M4 8h11l-3-3M20 16H9l3 3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg>`;\n  return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M3 8h18M3 12h12M3 16h15\" stroke-linecap=\"round\"\/><\/svg>`;\n}\n\n\/* ============================================================\n   \u00c9tat\n   ============================================================ *\/\nconst state = {\n  step:1, adresse:'', ville:'', codePostal:'', adresseLat:null, adresseLng:null, typeLogement:null,\n  services:[], selectedService:null, options:[], selectedOptions:new Set(),\n  superficie:null, superficiePrice:null, paliersSuperficie:[],\n  extras:[], selectedExtras:new Map(),\n  selectedSlot:null,\n  nom:'', courriel:'', telephone:'', listeAttente:false,\n  codePromo:'', codePromoValide:false, codePromoRabais:0, sousTotal:0\n};\n\n\/* ============================================================\n   Navigation\n   ============================================================ *\/\nfunction goStep(n){\n  if(n<1||n>6) return;\n  document.querySelectorAll('.step').forEach(s=>s.classList.toggle('active', +s.dataset.step===n));\n  document.querySelectorAll('.step-dot').forEach(d=>{\n    const dn = +d.dataset.n;\n    d.classList.toggle('done', dn<n);\n    d.classList.toggle('active', dn===n);\n    d.textContent = dn<n ? '\u2713' : dn;\n  });\n  document.getElementById('ductFill').style.width = ((n-1)\/5*100)+'%';\n  document.getElementById('multiBanner').style.display = n===1 ? 'block' : 'none';\n  document.getElementById('btnBack').style.visibility = n===1 ? 'hidden':'visible';\n  document.getElementById('btnNext').textContent = n===6 ? 'Confirmer la r\u00e9servation' : 'Suivant';\n  state.step = n;\n  validateStep();\n  if(n===3) renderExtras();\n  if(n===4) renderSlots();\n  if(n===6) renderSummary();\n}\n\nfunction validateStep(){\n  const btn = document.getElementById('btnNext');\n  let ok = true;\n  if(state.step===1) ok = state.adresse.trim().length>3 && state.ville.trim().length>1 && \/^[a-zA-Z][0-9][a-zA-Z]\\s?[0-9][a-zA-Z][0-9]$\/.test(state.codePostal.trim()) && state.typeLogement;\n  if(state.step===2) ok = !!state.selectedService && (!state.selectedService.demande_superficie || state.superficiePrice!=null);\n  if(state.step===4) ok = !!state.selectedSlot;\n  if(state.step===5) ok = state.nom.trim().length>1 && \/\\S+@\\S+\\.\\S+\/.test(state.courriel) && state.telephone.trim().length>6;\n  btn.disabled = !ok;\n}\n\nfunction updateTotal(){\n  let prix = 0, duree = 0;\n  if(state.selectedService){\n    prix += effectivePrice(state.selectedService);\n    duree += Number(state.selectedService.duree_min_base);\n  }\n  state.options.forEach(o=>{\n    if(state.selectedOptions.has(o.id)){ prix += Number(o.ajustement_prix); duree += Number(o.ajustement_duree_min); }\n  });\n  state.extras.forEach(e=>{\n    const qty = state.selectedExtras.get(e.id)||0;\n    if(qty>0){ const unit = (e.prix_upsell!=null ? Number(e.prix_upsell) : effectivePrice(e)); prix += unit*qty; duree += Number(e.duree_min_base)*qty; }\n  });\n  if(state.selectedSlot && state.selectedSlot.eco) prix -= Number(state.selectedSlot.montant_rabais || 10);\n  state.sousTotal = prix; \/\/ avant le rabais du code promo\n  if(state.codePromoValide) prix -= Number(state.codePromoRabais || 0);\n  document.getElementById('totalAmt').textContent = prix.toFixed(2).replace('.',',')+' $';\n  state.dureeInterne = duree; \/\/ usage interne seulement (webhook \/ logique de plages) \u2014 jamais affich\u00e9 au client\n}\n\n\/* ============================================================\n   Code promo partenaire\n   ============================================================ *\/\nasync function validerCodePromo(){\n  const msgEl = document.getElementById('promoMsg');\n  const code = document.getElementById('inpCodePromo').value.trim();\n  if(!code){\n    state.codePromoValide = false; state.codePromoRabais = 0; state.codePromo = '';\n    msgEl.textContent = ''; updateTotal(); if(state.step===6) renderSummary();\n    return;\n  }\n  msgEl.textContent = 'V\u00e9rification\u2026'; msgEl.style.color = '';\n  try{\n    const res = await fetch(`${SUPABASE_URL}\/functions\/v1\/valider_code_promo`, {\n      method:'POST',\n      headers:{ apikey: SUPABASE_ANON_KEY, Authorization:`Bearer ${SUPABASE_ANON_KEY}`, 'Content-Type':'application\/json' },\n      body: JSON.stringify({ code, montant: state.sousTotal })\n    });\n    const data = await res.json();\n    if(data.valide){\n      state.codePromo = code.toUpperCase();\n      state.codePromoValide = true;\n      state.codePromoRabais = Number(data.rabais || 0);\n      msgEl.textContent = data.message || 'Code appliqu\u00e9.';\n      msgEl.style.color = 'var(--green)';\n    } else {\n      state.codePromoValide = false; state.codePromoRabais = 0;\n      msgEl.textContent = data.message || \"Ce code promo n'est pas valide.\";\n      msgEl.style.color = '#c0392b';\n    }\n  }catch(err){\n    state.codePromoValide = false; state.codePromoRabais = 0;\n    msgEl.textContent = \"Impossible de v\u00e9rifier le code pour l'instant.\";\n    msgEl.style.color = '#c0392b';\n  }\n  updateTotal();\n  if(state.step===6) renderSummary();\n}\n\nfunction effectivePrice(s){\n  if(s.demande_superficie) return state.superficiePrice!=null ? state.superficiePrice : Number(s.prix_base);\n  return (s.prix_promo!=null && Number(s.prix_promo) < Number(s.prix_base)) ? Number(s.prix_promo) : Number(s.prix_base);\n}\n\n\/* ============================================================\n   \u00c9tape 1 \u2014 Localisation\n   ============================================================ *\/\ndocument.getElementById('inpAdresse').addEventListener('input', e=>{ state.adresse = e.target.value; validateStep(); });\ndocument.getElementById('inpVille').addEventListener('input', e=>{ state.ville = e.target.value; validateStep(); });\ndocument.getElementById('inpCodePostal').addEventListener('input', e=>{ state.codePostal = e.target.value; validateStep(); });\ndocument.querySelectorAll('#chipsLogement .chip').forEach(c=>{\n  c.addEventListener('click', ()=>{\n    document.querySelectorAll('#chipsLogement .chip').forEach(x=>x.classList.remove('selected'));\n    c.classList.add('selected'); state.typeLogement = c.dataset.v; validateStep();\n  });\n});\n\n\/* ============================================================\n   \u00c9tape 2 \u2014 Services + configurateur\n   ============================================================ *\/\nasync function loadServices(){\n  try{\n    state.services = await sb('services?select=*&actif=eq.true&categorie=eq.principal&order=ordre.asc');\n    const optRows = await sb('configurateur_options?select=service_id');\n    state.servicesAvecOptions = new Set(optRows.map(r=>r.service_id));\n  }catch(err){\n    document.getElementById('serviceGrid').innerHTML = `<p class=\"empty-note\">Impossible de charger les services (${err.message}). V\u00e9rifie la configuration Supabase en haut du fichier.<\/p>`;\n    return;\n  }\n  const grid = document.getElementById('serviceGrid');\n  grid.innerHTML = state.services.map(s=>`\n    <div class=\"card\" data-id=\"${s.id}\">\n      ${s.badge_texte ? `<div class=\"badge\">${s.badge_texte}<\/div>` : ''}\n      <div class=\"icon\">${iconFor(s.nom)}<\/div>\n      <h3>${s.nom}<\/h3>\n      <p>${s.description||''}<\/p>\n      <div class=\"price\">${priceLabel(s)}<\/div>\n    <\/div>`).join('');\n  grid.querySelectorAll('.card').forEach(card=>{\n    card.addEventListener('click', ()=>selectService(card.dataset.id));\n  });\n}\n\nfunction extraPriceLabel(e){\n  if(e.prix_upsell!=null){\n    return `<span class=\"price-old\">${Number(e.prix_base).toFixed(0)} $<\/span> <span class=\"price-promo\">${Number(e.prix_upsell).toFixed(0)} $ prix combin\u00e9!<\/span>`;\n  }\n  return priceLabel(e);\n}\n\nfunction priceLabel(s){\n  const prefix = s.prefixe_prix!=null ? s.prefixe_prix : (state.servicesAvecOptions.has(s.id) ? '\u00e0 partir de ' : '');\n  const promo = s.prix_promo!=null && Number(s.prix_promo) < Number(s.prix_base);\n  if(promo){\n    return `${prefix}<span class=\"price-old\">${Number(s.prix_base).toFixed(0)} $<\/span> <span class=\"price-promo\">${Number(s.prix_promo).toFixed(0)} $<\/span>`;\n  }\n  return `${prefix}${Number(s.prix_base).toFixed(0)} $`;\n}\n\nasync function selectService(id){\n  document.querySelectorAll('#serviceGrid .card').forEach(c=>c.classList.toggle('selected', c.dataset.id===id));\n  state.selectedService = state.services.find(s=>s.id===id);\n  state.selectedOptions = new Set();\n  state.selectedExtras = new Map();\n  state.superficie = null; state.superficiePrice = null; state.paliersSuperficie = [];\n  const area = document.getElementById('configArea');\n  area.innerHTML = '<p class=\"empty-note\">Chargement des options\u2026<\/p>';\n\n  if(state.selectedService.demande_superficie){\n    state.paliersSuperficie = await sb(`paliers_superficie?select=*&service_id=eq.${id}&order=pieds_carres_min.asc`);\n    area.innerHTML = `\n      <label class=\"field-label\">Superficie habitable (pieds carr\u00e9s)<\/label>\n      <input type=\"text\" id=\"inpSuperficie\" placeholder=\"ex: 1800\">\n      <p class=\"empty-note\" id=\"superficieResult\"><\/p>`;\n    document.getElementById('inpSuperficie').addEventListener('input', e=>{\n      const val = parseInt(e.target.value, 10);\n      const resEl = document.getElementById('superficieResult');\n      if(!val || val<=0){ state.superficie=null; state.superficiePrice=null; resEl.textContent=''; updateTotal(); validateStep(); return; }\n      state.superficie = val;\n      const palier = state.paliersSuperficie.find(p=> val>=p.pieds_carres_min && (p.pieds_carres_max==null || val<=p.pieds_carres_max));\n      if(palier && palier.necessite_validation){\n        state.superficiePrice = null;\n        resEl.textContent = `\u00c0 partir de ${Number(palier.prix).toFixed(0)} $ \u2014 une soumission en personne est n\u00e9cessaire pour cette superficie. Contactez-nous directement plut\u00f4t que de continuer la r\u00e9servation en ligne.`;\n      }else if(palier){\n        state.superficiePrice = Number(palier.prix);\n        resEl.textContent = `Prix pour ${val} pi\u00b2 : ${Number(palier.prix).toFixed(0)} $`;\n      }else{\n        state.superficiePrice = null;\n        resEl.textContent = `Aucun palier configur\u00e9 pour ${val} pi\u00b2 \u2014 validation manuelle requise, contacte-nous directement.`;\n      }\n      updateTotal(); validateStep();\n    });\n    updateTotal(); validateStep();\n    return;\n  }\n\n  state.options = await sb(`configurateur_options?select=*&service_id=eq.${id}`);\n  if(!state.options.length){\n    area.innerHTML = `<p class=\"empty-note\">Aucune option additionnelle configur\u00e9e pour ce service \u2014 le prix de base s'applique. (Ajoute des lignes dans la table configurateur_options pour offrir des variantes.)<\/p>`;\n  }else{\n    area.innerHTML = state.options.map(o=>`\n      <div class=\"option-row\" data-id=\"${o.id}\">\n        <div class=\"txt\"><strong>${o.nom_option}<\/strong>${o.requiert_validation_manuelle?'<span>N\u00e9cessite une validation de notre \u00e9quipe<\/span>':''}<\/div>\n        <div class=\"delta\">${o.ajustement_prix>=0?'+':''}${Number(o.ajustement_prix).toFixed(0)} $<\/div>\n        <div class=\"toggle\"><\/div>\n      <\/div>`).join('');\n    area.querySelectorAll('.option-row').forEach(row=>{\n      row.addEventListener('click', ()=>{\n        const id = row.dataset.id;\n        if(state.selectedOptions.has(id)) state.selectedOptions.delete(id); else state.selectedOptions.add(id);\n        row.classList.toggle('on');\n        updateTotal();\n      });\n    });\n  }\n  updateTotal(); validateStep();\n}\n\n\/* ============================================================\n   \u00c9tape 3 \u2014 Extras contextuels\n   ============================================================ *\/\nasync function renderExtras(){\n  const area = document.getElementById('extrasArea');\n  if(!state.selectedService){ area.innerHTML='<p class=\"empty-note\">Choisis d\\'abord un service \u00e0 l\\'\u00e9tape pr\u00e9c\u00e9dente.<\/p>'; return; }\n  area.innerHTML = '<p class=\"empty-note\">Chargement\u2026<\/p>';\n  const links = await sb(`service_upsells?select=upsell_service_id,condition_type_logement,prix_upsell,label_override,quantite_max&service_id=eq.${state.selectedService.id}`);\n  const relevant = links.filter(l=>!l.condition_type_logement || l.condition_type_logement===state.typeLogement);\n  if(!relevant.length){\n    state.extras = [];\n    area.innerHTML = '<p class=\"empty-note\">Aucun extra sugg\u00e9r\u00e9 pour ce service \u2014 passez \u00e0 l\\'\u00e9tape suivante.<\/p>';\n    return;\n  }\n  const ids = [...new Set(relevant.map(l=>l.upsell_service_id))];\n  const rawServices = await sb(`services?select=*&id=in.(${ids.join(',')})`);\n  state.extras = relevant.map(l=>{\n    const base = rawServices.find(s=>s.id===l.upsell_service_id);\n    return { ...base, nom: l.label_override || base.nom, prix_upsell: l.prix_upsell, quantite_max: l.quantite_max };\n  });\n  area.innerHTML = state.extras.map(e=>{\n    if(e.quantite_max && e.quantite_max>1){\n      const qty = state.selectedExtras.get(e.id)||0;\n      return `<div class=\"extra-card\" data-id=\"${e.id}\">\n        <div class=\"icon\" style=\"width:26px;height:26px;color:var(--teal);\">${iconFor(e.nom)}<\/div>\n        <div class=\"txt\"><h3>${e.nom}<\/h3><p>${e.description||''} \u00b7 ${extraPriceLabel(e)} \/ unit\u00e9<\/p><\/div>\n        <div class=\"qty-stepper\">\n          <button class=\"qty-btn\" data-act=\"dec\">\u2212<\/button>\n          <span class=\"qty-val\">${qty}<\/span>\n          <button class=\"qty-btn\" data-act=\"inc\">+<\/button>\n        <\/div>\n      <\/div>`;\n    }\n    return `<div class=\"extra-card\" data-id=\"${e.id}\">\n      <div class=\"icon\" style=\"width:26px;height:26px;color:var(--teal);\">${iconFor(e.nom)}<\/div>\n      <div class=\"txt\"><h3>${e.nom}<\/h3><p>${e.description||''} \u00b7 ${extraPriceLabel(e)}<\/p><\/div>\n      <button class=\"btn-add\">Ajouter<\/button>\n    <\/div>`;\n  }).join('');\n  area.querySelectorAll('.extra-card').forEach(card=>{\n    const id = card.dataset.id;\n    const extra = state.extras.find(e=>e.id===id);\n    const btn = card.querySelector('.btn-add');\n    if(btn){\n      if(state.selectedExtras.get(id)>0){ btn.classList.add('on'); btn.textContent='Ajout\u00e9 \u2713'; }\n      btn.addEventListener('click', ()=>{\n        const on = (state.selectedExtras.get(id)||0)>0;\n        if(on){ state.selectedExtras.delete(id); btn.classList.remove('on'); btn.textContent='Ajouter'; }\n        else{ state.selectedExtras.set(id,1); btn.classList.add('on'); btn.textContent='Ajout\u00e9 \u2713'; }\n        updateTotal();\n      });\n    }\n    card.querySelectorAll('.qty-btn').forEach(qbtn=>{\n      qbtn.addEventListener('click', ()=>{\n        let qty = state.selectedExtras.get(id)||0;\n        qty = qbtn.dataset.act==='inc' ? Math.min(extra.quantite_max, qty+1) : Math.max(0, qty-1);\n        if(qty>0) state.selectedExtras.set(id, qty); else state.selectedExtras.delete(id);\n        card.querySelector('.qty-val').textContent = qty;\n        updateTotal();\n      });\n    });\n  });\n}\n\n\/* ============================================================\n   \u00c9tape 4 \u2014 Plages r\u00e9elles via l'Edge Function \"disponibilites\"\n   (quarts de travail, jobs d\u00e9j\u00e0 planifi\u00e9s, temps de trajet r\u00e9el\n   Google Maps, et rabais \"client voisin\" de 10$ calcul\u00e9s c\u00f4t\u00e9 serveur)\n   ============================================================ *\/\nconst JOURS = ['DIM','LUN','MAR','MER','JEU','VEN','SAM'];\n\nfunction servicesPourApi(){\n  const arr = [{ service_id: state.selectedService.id, quantite: 1 }];\n  state.extras.forEach(e=>{\n    const qty = state.selectedExtras.get(e.id)||0;\n    if(qty>0) arr.push({ service_id: e.id, quantite: qty });\n  });\n  return arr;\n}\nfunction optionsPourApi(){\n  return [...state.selectedOptions].map(id=>({ option_id: id, quantite: 1 }));\n}\nfunction adresseComplete(){\n  return `${state.adresse}, ${state.ville}, QC ${state.codePostal}`;\n}\n\nlet plagesParJour = {}, joursDisponibles = [], tousPlages = [], plageParCle = {};\n\nasync function renderSlots(){\n  const tabs = document.getElementById('dayTabs');\n  const grid = document.getElementById('slotGrid');\n  const ecoSection = document.getElementById('ecoSection');\n  tabs.innerHTML = '';\n  ecoSection.innerHTML = '';\n  grid.innerHTML = '<p class=\"empty-note\">Recherche des disponibilit\u00e9s\u2026<\/p>';\n\n  let data;\n  try{\n    const res = await fetch(`${SUPABASE_URL}\/functions\/v1\/disponibilites`, {\n      method:'POST',\n      headers:{ apikey: SUPABASE_ANON_KEY, Authorization:`Bearer ${SUPABASE_ANON_KEY}`, 'Content-Type':'application\/json' },\n      body: JSON.stringify({ services: servicesPourApi(), options: optionsPourApi(), adresse: adresseComplete() })\n    });\n    data = await res.json();\n    if(!res.ok) throw new Error(data.erreur || 'Erreur de disponibilit\u00e9s');\n  }catch(err){\n    grid.innerHTML = `<p class=\"empty-note\">Impossible de charger les disponibilit\u00e9s (${err.message}). R\u00e9essaie ou contacte-nous directement.<\/p>`;\n    return;\n  }\n\n  state.validationRequise = !!data.validation_requise;\n  tousPlages = data.plages || [];\n  plagesParJour = {};\n  plageParCle = {};\n  tousPlages.forEach(p=>{\n    (plagesParJour[p.date] = plagesParJour[p.date]||[]).push(p);\n    plageParCle[`${p.date}|${p.heure_debut}`] = p;\n  });\n  joursDisponibles = Object.keys(plagesParJour).sort();\n\n  if(!joursDisponibles.length){\n    grid.innerHTML = `<p class=\"empty-note\">Aucune plage disponible pour le moment \u2014 contacte-nous directement.<\/p>`;\n    return;\n  }\n\n  renderEcoSection();\n\n  \/\/ Aucune journ\u00e9e n'est s\u00e9lectionn\u00e9e par d\u00e9faut et la grille reste vide tant\n  \/\/ que le client n'a pas cliqu\u00e9 sur un onglet \u2014 \u00e7a \u00e9vite de surcharger l'\u00e9cran\n  \/\/ d'entr\u00e9e de jeu (les plages \u00e9cologiques ci-dessus suffisent comme aper\u00e7u).\n  grid.innerHTML = '';\n\n  tabs.innerHTML = joursDisponibles.map((d)=>{\n    const dt = new Date(d+'T00:00:00');\n    return `<div class=\"day-tab\" data-d=\"${d}\">\n      <div class=\"d\">${JOURS[dt.getDay()]}<\/div><div class=\"n\">${dt.getDate()}<\/div>\n    <\/div>`;\n  }).join('');\n  tabs.querySelectorAll('.day-tab').forEach(t=>{\n    t.addEventListener('click', ()=>{\n      tabs.querySelectorAll('.day-tab').forEach(x=>x.classList.remove('selected'));\n      t.classList.add('selected');\n      renderSlotGrid(t.dataset.d);\n    });\n  });\n}\n\nfunction trierChrono(a,b){\n  return a.date===b.date ? a.heure_debut.localeCompare(b.heure_debut) : a.date.localeCompare(b.date);\n}\n\nfunction slotCardHtml(p, showDate){\n  const cle = `${p.date}|${p.heure_debut}`;\n  const dt = new Date(p.date+'T00:00:00');\n  return `<div class=\"slot ${p.rabais?'eco':''}\" data-cle=\"${cle}\">\n    ${showDate?`<div class=\"slot-date\">${JOURS[dt.getDay()]} ${dt.getDate()}<\/div>`:''}\n    <div class=\"time\">${p.heure_debut}<\/div>\n    <div class=\"note\">Plage disponible<\/div>\n    ${p.rabais?`<span class=\"badge-eco\">-${Number(p.montant_rabais).toFixed(0)} $<\/span>`:''}\n  <\/div>`;\n}\n\nfunction attacherClicsSlots(container){\n  container.querySelectorAll('.slot').forEach(el=>{\n    el.addEventListener('click', ()=>{\n      document.querySelectorAll('section[data-step=\"4\"] .slot').forEach(x=>x.classList.remove('selected'));\n      el.classList.add('selected');\n      const p = plageParCle[el.dataset.cle];\n      state.selectedSlot = {\n        date: new Date(p.date+'T00:00:00'),\n        dateStr: p.date,\n        time: p.heure_debut,\n        heure_debut: p.heure_debut,\n        heure_fin: p.heure_fin,\n        van_id: p.van_id,\n        tech_id: p.tech_id,\n        eco: p.rabais,\n        montant_rabais: p.montant_rabais\n      };\n      updateTotal(); validateStep();\n    });\n  });\n}\n\nfunction renderEcoSection(){\n  const container = document.getElementById('ecoSection');\n  const eco = tousPlages.filter(p=>p.rabais).sort(trierChrono);\n  if(!eco.length){ container.innerHTML=''; return; }\n\n  \/\/ Un aper\u00e7u des disponibilit\u00e9s les plus proches (sans rabais), pour donner\n  \/\/ une option imm\u00e9diate m\u00eame si le client ne clique sur aucune journ\u00e9e.\n  const normal = tousPlages.filter(p=>!p.rabais).sort(trierChrono).slice(0,6);\n\n  let html = `<div class=\"slot-section-title eco\">\ud83c\udf31 Plages \u00e9cologiques \u2014 v\u00e9hicule d\u00e9j\u00e0 dans le secteur<\/div>\n    <div class=\"slot-grid\">${eco.map(p=>slotCardHtml(p,true)).join('')}<\/div>`;\n  if(normal.length){\n    html += `<div class=\"slot-section-title\">Autres disponibilit\u00e9s les plus proches<\/div>\n      <div class=\"slot-grid\">${normal.map(p=>slotCardHtml(p,true)).join('')}<\/div>`;\n  }\n  container.innerHTML = html;\n  attacherClicsSlots(container);\n}\n\nfunction renderSlotGrid(dateISO){\n  const grid = document.getElementById('slotGrid');\n  const plages = plagesParJour[dateISO] || [];\n  if(!plages.length){\n    grid.innerHTML = `<p class=\"empty-note\">Aucune plage disponible cette journ\u00e9e-l\u00e0 \u2014 essaie un autre jour.<\/p>`;\n    return;\n  }\n  const eco = plages.filter(p=>p.rabais);\n  const normal = plages.filter(p=>!p.rabais);\n  let html = '';\n  if(eco.length){\n    html += `<div class=\"slot-section-title eco\">\ud83c\udf31 Plages \u00e9cologiques<\/div>`;\n    html += eco.map(p=>slotCardHtml(p,false)).join('');\n  }\n  if(normal.length){\n    if(eco.length) html += `<div class=\"slot-section-title\">Autres disponibilit\u00e9s<\/div>`;\n    html += normal.map(p=>slotCardHtml(p,false)).join('');\n  }\n  grid.innerHTML = html;\n  attacherClicsSlots(grid);\n}\n\n\/* ============================================================\n   \u00c9tape 5 \u2014 Coordonn\u00e9es\n   ============================================================ *\/\ndocument.getElementById('inpNom').addEventListener('input', e=>{ state.nom=e.target.value; validateStep(); });\ndocument.getElementById('inpCourriel').addEventListener('input', e=>{ state.courriel=e.target.value; validateStep(); });\ndocument.getElementById('inpTel').addEventListener('input', e=>{ state.telephone=e.target.value; validateStep(); });\ndocument.getElementById('chkAttente').addEventListener('change', e=>{ state.listeAttente=e.target.checked; });\n\n\/* ============================================================\n   \u00c9tape 6 \u2014 R\u00e9sum\u00e9 + soumission\n   ============================================================ *\/\nfunction fmtDate(d){ return d.toLocaleDateString('fr-CA',{weekday:'long', day:'numeric', month:'long'}); }\n\nfunction renderSummary(){\n  const opts = state.options.filter(o=>state.selectedOptions.has(o.id));\n  const extras = state.extras.filter(e=>(state.selectedExtras.get(e.id)||0)>0);\n  document.getElementById('summaryArea').innerHTML = `\n    <div class=\"summary-block\">\n      <h3>Localisation<\/h3>\n      <div class=\"summary-row\"><span>${state.adresse}, ${state.ville} ${state.codePostal}<\/span><span class=\"v\">${state.typeLogement}<\/span><\/div>\n    <\/div>\n    <div class=\"summary-block\">\n      <h3>Service<\/h3>\n      <div class=\"summary-row\"><span>${state.selectedService.nom}${state.selectedService.demande_superficie?` (${state.superficie} pi\u00b2)`:''}<\/span><span class=\"v\">${effectivePrice(state.selectedService).toFixed(0)} $<\/span><\/div>\n      ${opts.map(o=>`<div class=\"summary-row\"><span>+ ${o.nom_option}<\/span><span class=\"v\">${Number(o.ajustement_prix).toFixed(0)} $<\/span><\/div>`).join('')}\n      ${extras.map(e=>{ const qty=state.selectedExtras.get(e.id); return `<div class=\"summary-row\"><span>+ ${e.nom}${qty>1?` \u00d7${qty}`:''}<\/span><span class=\"v\">${extraPriceLabel(e)}<\/span><\/div>`; }).join('')}\n    <\/div>\n    <div class=\"summary-block\">\n      <h3>Rendez-vous<\/h3>\n      <div class=\"summary-row\"><span>${fmtDate(state.selectedSlot.date)}<\/span><span class=\"v\">${state.selectedSlot.time}<\/span><\/div>\n      ${state.selectedSlot.eco?`<div class=\"summary-row\"><span style=\"color:var(--green)\">Rabais d\u00e9placement<\/span><span class=\"v\" style=\"color:var(--green)\">-${Number(state.selectedSlot.montant_rabais).toFixed(0)} $<\/span><\/div>`:''}\n      ${state.codePromoValide?`<div class=\"summary-row\"><span style=\"color:var(--green)\">Code promotionnel (${state.codePromo})<\/span><span class=\"v\" style=\"color:var(--green)\">-${Number(state.codePromoRabais).toFixed(0)} $<\/span><\/div>`:''}\n    <\/div>\n    <div class=\"summary-block\">\n      <h3>Coordonn\u00e9es<\/h3>\n      <div class=\"summary-row\"><span>${state.nom}<\/span><\/div>\n      <div class=\"summary-row\"><span>${state.courriel}<\/span><\/div>\n      <div class=\"summary-row\"><span>${state.telephone}<\/span><\/div>\n    <\/div>\n  `;\n}\n\nasync function submitBooking(){\n  if(!state.selectedSlot) return;\n  const notesPrix = [state.selectedService.note_prix, ...state.extras.filter(e=>(state.selectedExtras.get(e.id)||0)>0).map(e=>e.note_prix)]\n    .filter(Boolean);\n\n  const parts = [];\n  const principalPrix = state.selectedService.demande_superficie ? state.superficiePrice : effectivePrice(state.selectedService);\n  let principal = `${state.selectedService.nom}${state.selectedService.demande_superficie?` (${state.superficie} pi\u00b2)`:''} - ${principalPrix.toFixed(0)} $`;\n  parts.push(principal);\n  state.options.forEach(o=>{ if(state.selectedOptions.has(o.id)) parts.push(`${o.nom_option} - ${Number(o.ajustement_prix).toFixed(0)} $`); });\n  state.extras.forEach(e=>{\n    const qty = state.selectedExtras.get(e.id)||0;\n    if(qty>0){\n      const unit = e.prix_upsell!=null ? Number(e.prix_upsell) : effectivePrice(e);\n      const total = unit*qty;\n      parts.push(`${e.nom}${qty>1?` \u00d7${qty}`:''} - ${total.toFixed(0)} $`);\n    }\n  });\n  if(state.selectedSlot.eco) parts.push(`Rabais d\u00e9placement - -${Number(state.selectedSlot.montant_rabais).toFixed(0)} $`);\n  if(state.codePromoValide) parts.push(`Code promotionnel (${state.codePromo}) - -${Number(state.codePromoRabais).toFixed(0)} $`);\n  const servicesTexte = parts.join(', ');\n\n  const finalArea = document.getElementById('finalArea');\n  const btnNext = document.getElementById('btnNext');\n  btnNext.disabled = true; btnNext.textContent = 'Confirmation en cours\u2026';\n\n  let resultat;\n  try{\n    const res = await fetch(`${SUPABASE_URL}\/functions\/v1\/reserver`, {\n      method:'POST',\n      headers:{ apikey: SUPABASE_ANON_KEY, Authorization:`Bearer ${SUPABASE_ANON_KEY}`, 'Content-Type':'application\/json' },\n      body: JSON.stringify({\n        date: state.selectedSlot.dateStr,\n        heure_debut: state.selectedSlot.heure_debut,\n        van_id: state.selectedSlot.van_id,\n        tech_id: state.selectedSlot.tech_id,\n        services: servicesPourApi(),\n        options: optionsPourApi(),\n        client: {\n          nom: state.nom, courriel: state.courriel, telephone: state.telephone,\n          adresse: adresseComplete(), type_logement: state.typeLogement\n        },\n        notes: servicesTexte,\n        code_promo: state.codePromoValide ? state.codePromo : undefined\n      })\n    });\n    resultat = await res.json();\n    if(!res.ok){\n      finalArea.innerHTML = `<div class=\"final-msg\"><h3>Cette plage n'est plus disponible<\/h3><p class=\"sub\">${resultat.erreur || 'Un probl\u00e8me est survenu.'} Retourne \u00e0 l'\u00e9tape pr\u00e9c\u00e9dente pour choisir une autre plage.<\/p><\/div>`;\n      btnNext.disabled = false; btnNext.textContent = 'Confirmer la r\u00e9servation';\n      return;\n    }\n  }catch(err){\n    finalArea.innerHTML = `<div class=\"final-msg\"><h3>Un probl\u00e8me est survenu<\/h3><p class=\"sub\">${err.message} \u2014 r\u00e9essaie ou contacte-nous directement.<\/p><\/div>`;\n    btnNext.disabled = false; btnNext.textContent = 'Confirmer la r\u00e9servation';\n    return;\n  }\n\n  const payload = {\n    nom: state.nom, telephone: state.telephone, courriel: state.courriel,\n    adresse: adresseComplete(), ville: state.ville, code_postal: state.codePostal,\n    adresse_lat: state.adresseLat, adresse_lng: state.adresseLng,\n    date: resultat.date, heure: resultat.heure_debut,\n    services: servicesTexte, total: resultat.prix_estime,\n    type_logement: state.typeLogement, service_id: state.selectedService.id,\n    superficie: state.superficie,\n    options: [...state.selectedOptions], extras: Object.fromEntries(state.selectedExtras),\n    rabais_deplacement: resultat.rabais, montant_rabais: resultat.montant_rabais,\n    code_promo: resultat.code_promo_applique ? state.codePromo : null,\n    montant_rabais_code: resultat.montant_rabais_code || 0,\n    liste_attente: state.listeAttente, notes_prix: notesPrix,\n    job_id: resultat.job_id, validation_requise: resultat.validation_requise\n  };\n\n  if(WEBHOOK_URL){\n    try{\n      const res = await fetch(WEBHOOK_URL, { method:'POST', headers:{'Content-Type':'application\/json'}, body:JSON.stringify(payload) });\n      if(!res.ok) console.warn('Webhook de confirmation a r\u00e9pondu', res.status, '(r\u00e9servation quand m\u00eame confirm\u00e9e)');\n    }catch(err){\n      console.warn('Webhook de confirmation injoignable (r\u00e9servation quand m\u00eame confirm\u00e9e) :', err.message);\n    }\n  }\n\n  \/* Copie de la reservation vers WordPress : elle s'y enregistre comme une\n     soumission Breakdance et declenche le courriel de notification.\n     Volontairement non bloquant : si WordPress ne repond pas, le rendez-vous\n     est deja pris et la confirmation s'affiche quand meme. *\/\n  if (window.AIRFP && window.AIRFP.endpoint) {\n    try {\n      const resWp = await fetch(window.AIRFP.endpoint, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application\/json', 'X-WP-Nonce': window.AIRFP.nonce },\n        body: JSON.stringify(payload)\n      });\n      if (!resWp.ok) console.warn('Enregistrement WordPress a repondu', resWp.status, '(rendez-vous quand meme pris)');\n    } catch (err) {\n      console.warn('Enregistrement WordPress injoignable (rendez-vous quand meme pris) :', err.message);\n    }\n  }\n\n  finalArea.innerHTML = `<div class=\"final-msg\"><h3>R\u00e9servation confirm\u00e9e<\/h3><p class=\"sub\">Un courriel et un texto de confirmation s'en viennent.${resultat.validation_requise?' Notre \u00e9quipe validera un d\u00e9tail de votre demande et vous contactera au besoin.':''}<\/p><\/div>`;\n  document.querySelector('.footbar').style.display='none';\n}\n\n\/* ============================================================\n   Boutons navigation\n   ============================================================ *\/\ndocument.getElementById('btnNext').addEventListener('click', ()=>{\n  if(state.step===6){ submitBooking(); return; }\n  goStep(state.step+1);\n});\ndocument.getElementById('btnBack').addEventListener('click', ()=>{\n  if(state.step===3){ state.selectedExtras = new Map(); updateTotal(); }\n  goStep(state.step-1);\n});\n\ndocument.getElementById('btnAppliquerPromo').addEventListener('click', validerCodePromo);\n\n\/* Preremplissage depuis le formulaire de soumission du site.\n   On passe par un evenement \"input\" pour reutiliser les ecouteurs deja en\n   place : state et validateStep() restent synchronises, aucun couplage aux\n   internes de l'app. *\/\n(function prefillDepuisLeSite(){\n  const d = (window.AIRFP && window.AIRFP.prefill) || {};\n  [['nom','inpNom'], ['courriel','inpCourriel'], ['tel','inpTel']].forEach(function(paire){\n    const val = d[paire[0]];\n    if (!val) return;\n    const el = document.getElementById(paire[1]);\n    if (!el) return;\n    el.value = val;\n    el.dispatchEvent(new Event('input', { bubbles: true }));\n  });\n})();\n\n\/* Init *\/\nloadServices();\ngoStep(1);\n<\/script>\n\t\t\t<script src=\"https:\/\/maps.googleapis.com\/maps\/api\/js?key=AIzaSyBCj8FDHUVVVT5WTSdw3B5VM1A-Z7YeKVM&#038;libraries=places&#038;callback=initGoogleAutocomplete\" async defer><\/script>\n\t\t<\/div><div class=\"bde-button-5877-116 bde-button afi-btn-sans-icone\">\n        \n    \n    \n    \n    \n            \n                    \n            \n            \n\n    \n    \n    \n    \n    \n    <a class=\"breakdance-link button-atom button-atom--secondary bde-button__button\" href=\"https:\/\/nettoyagethermopompegatineau.ca\" target=\"_self\" data-type=\"url\"  >\n\n    \n        <span class=\"button-atom__text\">Revenir \u00e0 l'accueil<\/span>\n\n        \n        \n                <\/a>\n\n    \n\n\n<\/div>\n<\/div><\/div>\n<\/section>","protected":false},"excerpt":{"rendered":"<p>Merci pour votre demande! Obtenez votre soumission instantan\u00e9mentUtilisez notre outil de r\u00e9servation en ligne pour obtenir votre prix et choisir une plage horaire d\u00e8s maintenant. R\u00e9servation en ligne 1 2 3 4 5 6 Vous \u00eates propri\u00e9taire d&rsquo;un immeuble multilogement? Contactez-nous directement par courriel, texto ou t\u00e9l\u00e9phone afin d&rsquo;obtenir une soumission personnalis\u00e9e et de profiter [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"_breakdance_hide_in_design_set":false,"_breakdance_tags":"","footnotes":""},"class_list":["post-5877","page","type-page","status-publish","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/nettoyagethermopompegatineau.ca\/en\/wp-json\/wp\/v2\/pages\/5877","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nettoyagethermopompegatineau.ca\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/nettoyagethermopompegatineau.ca\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/nettoyagethermopompegatineau.ca\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nettoyagethermopompegatineau.ca\/en\/wp-json\/wp\/v2\/comments?post=5877"}],"version-history":[{"count":5,"href":"https:\/\/nettoyagethermopompegatineau.ca\/en\/wp-json\/wp\/v2\/pages\/5877\/revisions"}],"predecessor-version":[{"id":13949,"href":"https:\/\/nettoyagethermopompegatineau.ca\/en\/wp-json\/wp\/v2\/pages\/5877\/revisions\/13949"}],"wp:attachment":[{"href":"https:\/\/nettoyagethermopompegatineau.ca\/en\/wp-json\/wp\/v2\/media?parent=5877"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}