PageSourceSearch

https://www.cartegriseminute.fr/js/script_all_in_one.js?t=1788524965

js cartegriseminute.fr collected 2026-09-26 11:57:34 UTC 39,724 bytes, 796 lines download raw bytes

1document.addEventListener("DOMContentLoaded", () => {
2    // --- Trouve tous les formulaires GRAND et MINI, et instancie chacun séparément ---
3    const miniForms = Array.from(document.querySelectorAll("#mini-calculateur form"))
4        .filter(f => f.querySelector("#demarche"));
5    const grandForms = Array.from(document.querySelectorAll("form"))
6        .filter(f => !f.closest("#mini-calculateur") && f.querySelector("#demarche"));
7
8    grandForms.forEach(f => initCalc({ form: f, kind: "grand" }));
9    miniForms.forEach(f => initCalc({ form: f, kind: "mini" }));
10
11    function initCalc({ form, kind }) {
12        // Évite double binding si le script est inclus plusieurs fois
13        if (!form || form.dataset.cg_bound === "1") return;
14        form.dataset.cg_bound = "1";
15
16        const IN_MINI = kind === "mini";
17        const CONTAINER = IN_MINI ? (form.closest("#mini-calculateur") || form) : form;
18
19        // === Helpers SCOPÉS AU FORM ===
20        const $id  = (id) => form.querySelector(`#${id}`);
21        const $qs  = (sel, base = form) => (base || form).querySelector(sel);
22        const $all = (sel, base = form) => Array.from((base || form).querySelectorAll(sel));
23        const getNear = (id) =>
24            CONTAINER.querySelector(`#${id}`) || form.querySelector(`#${id}`) || document.getElementById(id);
25
26        const setReq = (el, on) =>
27            el?.querySelectorAll("input,select,textarea")
28                .forEach(i => on ? i.setAttribute("required","") : i.removeAttribute("required"));
29
30        const show = (id) => { const el = $id(id); if (!el) return; el.style.display = "";  setReq(el, true);  };
31        const hide = (id) => { const el = $id(id); if (!el) return; el.style.display = "none"; setReq(el, false); };
32
33        // === Références (dans CE form uniquement) ===
34        const details       = $id("div_champs_sivin");
35        const demarche      = $id("demarche");
36        const trigger       = $id("details");               // <- toggle "immat inconnue"
37        const genreSel      = $id("type_vehicule");
38        const typeSel       = $id("type_vehicule");
39        const ptacSel       = $id("ptac");
40        const immatInput    = $id("immatriculation");
41        const toggleWrapper = $id("toggle-wrapper");
42        const puissanceInput = form.querySelector('#puissance_fiscale, input[name="puissance_fiscale"]');
43        if (puissanceInput && puissanceInput.type !== "hidden") {
44            puissanceInput.type = "number";
45            puissanceInput.inputMode = "numeric";
46            puissanceInput.min = "0";
47            puissanceInput.step = "1";
48        }
49        const co2Input = form.querySelector('#co2, input[name="co2"]');
50        if (co2Input && co2Input.type !== "hidden") {
51            co2Input.type = "number";
52            co2Input.inputMode = "numeric";
53            co2Input.min = "0";
54            co2Input.step = "1";
55        }
56
57        if (!document.getElementById("cg-number-input-style")) {
58            const style = document.createElement("style");
59            style.id = "cg-number-input-style";
60            style.textContent = `
61.form-details input[type="number"] {
62    width: 100%;
63    padding: 17px 12px;
64    border: 1px solid #ccc;
65    border-radius: 5px;
66    font-family: 'Roboto', sans-serif;
67    font-size: 16px;
68    margin-bottom: 15px;
69    transition: border-color 0.3s ease;
70    height: 53px;
71    box-sizing: border-box;
72    text-align: center;
73    color: #004399;
74}
75.form-details input[type="number"]::-webkit-outer-spin-button,
76.form-details input[type="number"]::-webkit-inner-spin-button {
77    -webkit-appearance: none;
78    margin: 0;
79}
80.form-details input[type="number"] {
81    -moz-appearance: textfield;
82}
83#bloc_masse_ordre_marche .detail-label {
84    width: 65%;
85}
86#bloc_masse_ordre_marche .indication {
87    width: 35%;
88}
89`;
90            document.head.appendChild(style);
91        }
92
93        // === Règles ===
94        const ALL = [
95            "bloc_neuf_occasion","bloc_genre","bloc_energie","bloc_puissance","bloc_date","bloc_co2",
96            "bloc_masse_ordre_marche","bloc_date_vente","bloc_heure_vente","bloc_ptac","bloc_j3",
97            "bloc_collection","bloc_adr4",
98        ];
99        const RULES = {
100            1: ["bloc_genre","bloc_neuf_occasion","bloc_energie","bloc_puissance","bloc_date"],
101            "1_import": ["bloc_genre","bloc_energie","bloc_puissance","bloc_date","bloc_co2","bloc_masse_ordre_marche"],
102            "-1": ["bloc_genre","bloc_energie","bloc_puissance","bloc_co2","bloc_masse_ordre_marche"],
103            2: [], 3: [], 6: [], 7: [], 8: [],
104            4: [],
105            10:["bloc_genre","bloc_energie","bloc_puissance","bloc_date"],
106            9: ["bloc_date_vente","bloc_heure_vente"],
107        };
108        const HIDE_TRIGGER_FOR = new Set(["-1","2","3","4","6","7","8","9","10","1_import"]);
109
110        const normalizeTarifDemarcheValue = (value) =>
111            value === "1_import" || value === "1_succession" ? "1" : value;
112
113        // === UI helpers ===
114        const isOpen = () => details && getComputedStyle(details).display !== "none";
115        const openDetails  = () =>
115 { if (!details) return; details.style.display = "flex"; details.setAttribute("aria-hidden","false"); };
116        const closeDetails = () => { if (!details) return; details.style.display = "none"; details.setAttribute("aria-hidden","true"); };
117        const hideAll = () => ALL.forEach(hide);
118
119        // === Métier ===
120        const J3_GENRES = new Set(["7","10","11"]); // QM, TM, CYCL
121        const shouldShowJ3 = (val) => J3_GENRES.has(String(val));
122
123        function applyJ3() {
124            if (!isOpen()) return;
125            const val = genreSel?.value || "";
126            if (shouldShowJ3(val)) { show("bloc_j3"); show("bloc_collection"); }
127            else {
128                hide("bloc_j3"); hide("bloc_collection");
129                const j3 = $id("j3_marchandise"); if (j3) j3.value = "";
130                $all('input[name="collection"]').forEach(input => {
131                    input.checked = false;
132                    const label = form.querySelector(`label[for="${input.id}"]`);
133                    if (label) label.classList.remove("active");
134                });
135            }
136        }
137
138        function applyImmatDisable() {
139            const d = demarche?.value || "";
140            const isOccasionImportee = $qs('input[name="neuf_occasion"][value="occasion_importee"]')?.checked;
141            const nocHidden = $id("noc_hidden")?.value;
142            let mustDisable = false;
143
144            if (d === "1" || d === "1_succession") {
145                mustDisable = trigger?.checked === true || isOccasionImportee === true || nocHidden === "3";
146            }
147            else mustDisable = d === "-1" || d === "10" || d === "1_import" || isOccasionImportee;
148
149            if (immatInput) {
150                if (mustDisable) { immatInput.value = ""; immatInput.disabled = true; }
151                else immatInput.disabled = false;
152            }
153        }
154
155        function updatePtac() {
156            const d = demarche?.value || "";
157            const needGenre = !!RULES[d]?.includes("bloc_genre");
158            const val = typeSel?.value;
159            const opt1 = form.querySelector('#ptac option[value="1"]');
160            if (isOpen() && needGenre && (val === "12" || val === "4")) {
161                show("bloc_ptac");
162                if (opt1) opt1.disabled = val === "12"; // CAM >3.5t
163                if (val === "12" && ptacSel?.value === "1") ptacSel.value = "";
164            } else {
165                hide("bloc_ptac");
166                if (ptacSel) ptacSel.value = "";
167            }
168        }
169
170        function resetDetailsValues() {
171            $all("#div_champs_sivin input, #div_champs_sivin select, #div_champs_sivin textarea").forEach(el => {
172                if (["departement","immatriculation","demarche"].includes(el.id)) return;
173                if (el.type === "radio" || el.type === "checkbox") el.checked = false;
174                else el.value = "";
175                el.classList?.remove("error");
176            });
177            $all("#div_champs_sivin label.active").forEach(l => l.classList.remove("active"));
178            const optLe35 = form.querySelector('#ptac option[value="1"]'); if (optLe35) optLe35.disabled = false;
179        }
180
181        function isSIV(immat) {
182            if (!immat) return false;
183            const s = String(immat).toUpperCase().replace(/[^A-Z0-9]/g,"");
184            return /^[A-Z]{2}\d{3}[A-Z]{2}$/.test(s) || /^WW\d{3,4}[A-Z]{2}$/.test(s);
185        }
186
187        function ensureDefaultDateIfNeuf() {
188            const d = demarche?.value || "";
189            const dateInput = $id("mise_en_circulation");
190            if (d === "-1" && dateInput && !dateInput.value) {
191                const t = new Date(); const yyyy=t.getFullYear(); const mm=String(t.getMonth()+1).padStart(2,"0"); const dd=String(t.getDate()).padStart(2,"0");
192                dateInput.value = `${yyyy}-${mm}-${dd}`;
193            }
194        }
195
196        function applyAdr4() {
197            const d = demarche?.value || "";
198            if (d !== "2") { hide("bloc_adr4"); return; }
199            const siv = isSIV(immatInput?.value || "");
200            if (siv) { if (!isOpen()) openDetails(); show("bloc_adr4"); setReq($id("bloc_adr4"), false); }
201            else {
202                hide("bloc_adr4"); const cb = $id("adr4"); if (cb) cb.checked = false;
203                if ($all(".row-container").every(w => getComputedStyle(w).display === "none")) closeDetails();
204            }
205        }
206
207        function applyForCurrent() {
208            const d = demarche?.value || "";
209            const rulesKey = d === "1_import" || d === "1_succession" ? "1" : d;
210
211            if (toggleWrapper) {
212                toggleWrapper.style.display = (!d || d === "0") ? "none" : (HIDE_TRIGGER_FOR.has(d) ? "none" : "inline-flex");
213                if (!d || HIDE_TRIGGER_FOR.has(d)) { const chk = $id("details"); if (chk) chk.checked = false; }
214            }
215
216            hideAll();
217            const hasBlocks = !!(RULES[rulesKey] && RULES[rulesKey].length);
218            if (!hasBlocks) { closeDetails(); if (d === "2") applyAdr4(); return; }
219
220            if (d === "1_import") { if (!isOpen()) openDetails(); }
221            else if (d !== "1" && d !== "1_succession" && !isOpen()) { openDetails(); }
222            if (!isOpen()) return;
223
224            const rules = RULES[rulesKey] || [];
225            rules.forEac
225h(show);
226
227            if (d === "1_import") hide("bloc_neuf_occasion");
228            if (d === "1" || d === "1_succession") {
229                hide("bloc_neuf_occasion");
230                const fr  = $qs('input[name="neuf_occasion"][value="occasion_fr"]');
231                const imp = $qs('input[name="neuf_occasion"][value="occasion_importee"]');
232                if (fr) fr.value = "0"; if (imp) imp.value = "0";
233                hide("bloc_co2");
234                hide("bloc_masse_ordre_marche");
235            }
236            if (d === "1_import") {
237                show("bloc_co2");
238                show("bloc_masse_ordre_marche");
239            }
240
241            applyJ3(); updatePtac(); applyAdr4();
242        }
243
244        function forceUnknownImmat() {
245            if (toggleWrapper) toggleWrapper.style.display = "inline-flex";
246            if (trigger) {
247                trigger.checked = true;
248                trigger.dispatchEvent(new Event("change", { bubbles: true }));
249                return;
250            }
251            if (!isOpen()) openDetails();
252            applyForCurrent();
253        }
254
255        // === AAA & util ===
256        const ensureHidden = (name) => {
257            let el = form.querySelector(`input[name="${name}"]`);
258            if (!el) { el = document.createElement("input"); el.type = "hidden"; el.name = name; form.appendChild(el); }
259            return el;
260        };
261        const hf_chevaux = ensureHidden("chevaux_fiscaux");
262        const hf_date    = ensureHidden("date_immatriculation");
263        const hf_marque  = ensureHidden("marque");
264        const hf_modele  = ensureHidden("modele");
265        const hf_siv_id  = ensureHidden("siv_id");
266
267        const isWWPlate = (s) => /^WW[\s-]*\d{3,4}[\s-]*[A-Z]{2}$/i.test(String(s || "").trim());
268        const shouldUseAAA = () => {
269            const d = demarche?.value;
270            const immat = immatInput?.value?.trim();
271            if (!immat) return false;
272            if (isWWPlate(immat)) return false;
273            if (d === "-1" || d === "10") return false;
274            if (form.dataset.aaaFetched === "1") return false;
275            return true;
276        };
277
278        // Variable pour stocker le jeton de sécurité
279        let securityToken = "";
280        let securityTokenExpiresAt = 0;
281        let securityTokenPromise = null;
282        const TOKEN_REFRESH_MARGIN = 120;
283
284        function loadSecurityToken(forceRefresh = false) {
285            const now = Math.floor(Date.now() / 1000);
286            if (!forceRefresh && securityToken && securityTokenExpiresAt > now + TOKEN_REFRESH_MARGIN) {
287                return Promise.resolve(securityToken);
288            }
289            if (!forceRefresh && securityTokenPromise) {
290                return securityTokenPromise;
291            }
292
293            securityTokenPromise = fetch((typeof url_site !== "undefined" ? url_site : "/") + "js/jQuery/init_session.php", {
294                method: "GET",
295                headers: { "Accept": "application/json" },
296                credentials: "include"
297            })
298            .then(res => res.json())
299            .then(response => {
300                securityToken = response.token || "";
301                securityTokenExpiresAt = Number(response.expires_at) || now + 3600;
302                console.log("Session initialisée.");
303                return securityToken;
304            })
305            .catch(err => {
306                console.error("Impossible d'initialiser la session de sécurité.", err);
307                return securityToken;
308            })
309            .finally(() => {
310                securityTokenPromise = null;
311            });
312
313            return securityTokenPromise;
314        }
315
316        // On initialise la session au chargement, puis on rafraîchit le token si besoin avant chaque appel protégé.
317        loadSecurityToken(true);
318
319        /*function fetchAAA(immat) {
320          return new Promise((resolve, reject) => {
321            $.ajax({
322              dataType: "jsonp",
323              url: (typeof url_site !== "undefined" ? url_site : "/") + "js/jQuery/getDonneesImmatriculation.php",
324              data: { immatriculation: immat },
325              success: (response) => resolve(response || {}),
326              error: (xhr) => reject(xhr),
327            });
328          });
329        }*/
330        /* STOP jsonp. C'est une vieille technique; passer en POST rend l'appel impossible via la barre d'adresse du navigateur */
331        async function fetchAAA(immat) {
332            const token = await loadSecurityToken();
333
334            return new Promise((resolve, reject) => {
335
336                // Si le token n'est pas encore arrivé (page chargée trop vite), on attend ou on bloque
337                if (!token) {
338                    console.warn("Token de sécurité manquant");
339                    // Optionnel : retenter de le chercher ou rejeter
340                }
341
342                $.ajax({
343                    type: "POST",
344                    dataType: "json",
345                    url: (typeof url_site !== "undefined" ? url_site : "/") + "js/jQuery/getDonneesImmatriculation_v2.php",
346                    data: {
347                        immatriculation: immat,
348                        token: token
349                    },
350                    xhrFields: {
351                        withCredentials: true // Important pour passer les cookies de session (Token)
352                    },
353                    success: (response) => resolve(response || {}),
354                    error: (xhr) => reject(xhr),
355                });
356            });
357        }
358
359        function ensureVNDefaults() {
360            const d = demarche?.value || "";
361            let noc = form.querySelector("#noc_hidden");
362            if (!noc) { noc = document.createElement("input"); noc.type="hidden"; noc.name="neuf_occasion"; noc.id="noc_hidden"; form.appendChild(noc); }
363
364            if (d === "-1") {
365                noc.value = "1";
366                ensureHidden("ptac").value = "1";
367                ensureHidden("reception").value = "1";
368                const cp = ensureHidden("codepostal");
369                if (!cp.value) cp.value = $id("departement")?.value || "";
370            }
371            if (d === "1" || d === "1_succession") noc.value = "2";
372            if (d === "1_import") noc.value = "3";
373        }
374
375        function setCollectionChoice(value) {
376            $all('input[name="collection"]').forEach(input => {
377                const checked = input.value === value;
378                input.checked = checked;
379                const label = form.querySelector(`label[for="${input.id}"]`);
380                if (label) label.classList.toggle("active", checked);
381            });
382        }
383
384        function syncSpecialDemarcheFlags() {
385            ensureHidden("succession").value = demarche?.value === "1_succession" ? "oui" : "non";
386            if (demarche?.value === "7") setCollectionChoice("oui");
387        }
388
389        function syncDateForBack() {
390            const dateInput = $id("mise_en_circulation");
391            const hidden = ensureHidden("date_immatriculation");
392            if (!dateInput || !hidden || !dateInput.value) return;
393            const parts = dateInput.value.split("-");
394            if (parts.length === 3) hidden.value = `${parts[0]}-${parts[1]}-${parts[2]}`;
395        }
396
397        function syncCO2ForBack() {
398            const visible = $id("co2");
399            const hidden = ensureHidden("co2");
400            hidden.type = "text";
401            hidden.value = visible && visible.value ? visible.value : "";
402        }
403
404        function ensureReception() { const r = ensureHidden("reception"); if (!r.value) r.value = "1"; }
405
406        function applyImmatName() {
407            const d = demarche?.value || "";
408            if (!immatInput) return;
409            immatInput.setAttribute("name", d === "9" ? "immatriculation_declaration_cession" : "immatriculation");
410        }
411
412        function applyAAA(resp) {
413            const sivId = resp?.siv_id ?? resp?.id ?? "";
414            hf_siv_id.value = sivId ? String(sivId) : "";
415        }
416
417        function needsMoreUserInputAfterAAA() {
418            const g = typeSel?.value || "";
419            const blocPtac = $id("bloc_ptac");
420            const blocJ3 = $id("bloc_j3");
421            const blocCollection = $id("bloc_collection");
422
423            const ptacVisible = blocPtac && getComputedStyle(blocPtac).display !== "none";
424            const j3Visible = blocJ3 && getComputedStyle(blocJ3).display !== "none";
425            const collVisible = blocCollection && getComputedStyle(blocCollection).display !== "none";
426
427            const needPTAC = (g === "12" || g === "4") && ptacVisible && !ptacSel?.value;
428            const needJ3 = j3Visible && !$id("j3_marchandise")?.value;
429            const needCollection = collVisible && !$all('input[name="collection"]:checked').length;
430            return needPTAC || needJ3 || needCollection;
431        }
432
433        /*function loadCryptoJS() {
434            return new Promise((resolve, reject) => {
435                if (window.CryptoJS) {
436                    resolve(); // Déjà chargé
437                    return;
438                }
439                const script = document.createElement('script');
440                script.src = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js";
441                script.onload = () => resolve();
442                script.onerror = () => reject(new Error("Erreur chargement CryptoJS"));
443                document.head.appendChild(script);
444            });
445        }*/
446
447        async function calculateTarifAndRedirect() {
448            ensureVNDefaults(); ensureReception(); syncDateForBack(); syncCO2ForBack(); syncSpecialDemarcheFlags();
449
450            const loader = getNear("loader"); if (loader) loader.style.display = "flex";
451
452            const originalDemarcheValue = demarche?.value || "";
453            const originalDemarcheLabel = demarche?.options?.[demarche.selectedIndex]?.text?.trim() || "";
454            const serializedDemarcheValue = normalizeTarifDemarcheValue(originalDemarcheValue);
455            let s = "";
456
457            if (demarche && serializedDemarcheValue !== originalDemarcheValue) demarche.value = serializedDemarcheValue;
458            $all('input[name="neuf_occasion"]:not(#noc_hidden)').forEach(r => (r.disabled = true));
459            try {
460                s = $(form).serialize();
461            } finally {
462                $all('input[name="neuf_occasion"]:not(#noc_hidden)').forEach(r => (r.disabled = false));
463                if (demarche && serializedDemarcheValue !== originalDemarcheValue) demarche.value = originalDemarcheValue;
464            }
465
466            const failSafe = setTimeout(() => {
467                if (loader && loader.style.display === "flex") {
468                    loader.style.display = "none";
469                    const box = getNear("resultat");
470                    if (box) { box.innerHTML = "Le serveur ne répond pas ou a retourné une erreur. Merci de réessayer."; box.style.display = "block"; }
471                }
472            }, 6000);
473
474            // OPTIMISATION : On s'assure que le token est chargé et encore valide.
475            const token = await loadSecurityToken();
476
477            if (!token) {
478                console.warn("Token de sécurité manquant");
479            }
480
481            $.ajax({
482                type: "POST",
483                dataType: "json",
484                url: (typeof url_site !== "undefined" ? url_site : "/") + "js/jQuery/getTarifCalculateur_v3.php",
485                data: {
486                    param: s,
487                    token: token // Le jeton CSRF en clair suffit
488                },
489                xhrFields: {
490                    withCredentials: true
491                },
492                success: function (response) {
493                    clearTimeout(failSafe);
494                    if (loader) loader.style.display = "none";
495
496                    if (!response || response.erreur) {
497                        const box = getNear("resultat");
498                        const msg = " " + (response?.erreur || "Erreur inconnue du serveur") + " ";
499                        if (box) { box.innerHTML = msg; box.style.display = "block"; } else { alert(msg); }
500                        return;
501                    }
502
503                    const action = typeof url_site_form_result !== "undefined" ? url_site_form_result :
504                        typeof url_site !== "undefined" ? url_site : "/";
505
506                    const f = document.createElement("form");
507                    f.method = "post"; f.action = action; f.style.display = "none"; document.body.appendChild(f);
508
509                    const add = (n, v) => { const i = document.createElement("input"); i.type="hidden"; i.name=n; i.value = v ?? ""; f.appendChild(i); };
510                    add("warning", response.message);
511                    add("total", response.tarif);
512                    add("y1", response.y1); add("y2", response.y2); add("y3", response.y3); add("y4", response.y4); add("y5", response.y5);
513                    add("id", response.id);
514                    add("cg_data_cgm", s); // Tu envoies bien 's' brut ici
515                    add("demarche_source", originalDemarcheValue);
516                    add("demarche_label", originalDemarcheLabel);
517                    add("referer", form.querySelector('input[name="referer"]')?.value || "");
518                    add("interface", form.querySelector('input[name="interface"]')?.value || "");
519                    f.submit();
520                },
521                error: function (xhr) {
522                    clearTimeout(failSafe);
523                    if (loader) loader.style.display = "none";
524                    const box = getNear("resultat");
525                    const msg = "ERREUR réseau\nCode " + xhr.status;
526                    if (box) { box.innerHTML = msg; box.style.display = "block"; } else { alert(msg); }
527                },
528            });
529        }
530
531        async function handleSmartSubmit(e) {
532            if (form.dataset.autosubmit === "1") return; // 2e clic
533            e.preventDefault();
534
535            const loader = getNear("loader"); if (loader) loader.style.display = "flex";
536
537            const d = demarche?.value || "";
538            const immat = immatInput?.value?.trim() || "";
539
540            // Déclaration de cession
541            if (d === "9") {
542                const action = "https://www.declaration-cession-vehicule.fr/enregistrement-cession.php";
543                const f = document.createElement("form");
544                f.method = "post"; f.action = action; f.style.display = "none"; document.body.appendChild(f);
545                const add = (n,v) => { const i=document.createElement("input"); i.type="hidden"; i.name=n; i.value=v||""; f.appendChild(i); };
546                add("immatriculation_declaration_cession", form.querySelector('[name="immatriculation_declaration_cession"]')?.value || "");
547                add("date_declaration_cession",           form.querySelector('[name="date_declaration_cession"]')?.value || "");
548                add("heure_declaration_cession",          form.querySelector('[name="heure_declaration_cession"]')?.value || "");
549                add("interface",                           form.querySelector('[name="interface"]')?.value || "");
550                add("marque",                              form.querySelector('[name="marque"]')?.value || "");
551                add("modele",                              form.querySelector('[name="modele"]')?.value || "");
552                f.submit();
553                return;
554            }
555
556            ensureDefaultDateIfNeuf();
557            ensureVNDefaults();
558
559            // Domicile sans immat => direct
560            if (d === "2" && !immat) { calculateTarifAndRedirect(); return; }
561
562            // Pas d'appel VIN => direct
563            if (!shouldUseAAA()) { calculateTarifAndRedirect(); return; }
564
565            try {
566                const resp = await fetchAAA(immat);
567                if (resp.erreur && d !== "2") {
568                    if (loader) loader.style.display = "none";
569                    const errMsg = String(resp.erreur || "");
570                    const box = getNear("resultat");
571                    if (box) { box.innerHTML = errMsg; box.style.display = "block"; } else { alert(errMsg); }
572                    form.dataset.aaaFetched = "1";
573                    const isUnknown = /inconnue|inconnu/i.test(errMsg);
574                    if (isUnknown) forceUnknownImmat();
575                    return;
576                }
577                applyAAA(resp);
578                form.dataset.aaaFetched = "1";
579
580                applyForCurrent();
581                if (!needsMoreUserInputAfterAAA()) { calculateTarifAndRedirect(); return; }
582                if (!isOpen()) openDetails();
583            } catch (err) {
584                if (loader) loader.style.display = "none";
585                if (d === "2") calculateTarifAndRedirect();
586                else { if (!isOpen()) openDetails(); }
587            }
588        }
589
590        // === EVENTS (scopés au form) ===
591        form.addEventListener("submit", handleSmartSubmit, true);
592
593        demarche?.addEventListener("change", () => {
594            resetDetailsValues();
595            if (demarche.value === "2" && immatInput) { immatInput.value = ""; immatInput.classList.remove("error"); }
596            if (trigger) trigger.checked = false;         // on remet le toggle à 0
597            closeDetails(); applyForCurrent(); applyImmatDisable(); applyImmatName(); syncSpecialDemarcheFlags();
598        });
599
600        $all('input[name="neuf_occasion"]').forEach(r =>
601            r.addEventListener("change", () => {
602                if (["1","1_succession"].includes(demarche?.value) && isOpen()) applyForCurrent();
603                applyImmatDisable();
604            })
605        );
606
607        immatInput?.addEventListener("input", applyAdr4);
608        immatInput?.addEventListener("blur",  applyAdr4);
609
610        immatInput?.addEventListener("input", () => {
611            form.dataset.aaaFetched = "0";
612            hf_siv_id.value = "";
613            const box = getNear("resultat"); if (box) box.style.display = "none";
614        });
615
616        $all('input[name="neuf_ou_occasion"]').forEach(r =>
617            r.addEventListener("change", () => { if (["1","1_succession"].includes(demarche?.value) && isOpen()) applyForCurrent(); })
618        );
619
620        form.addEventListener("submit", () => {
621            $all(".row-container").forEach(w => { if (getComputedStyle(w).display === "none") setReq(w, false); });
622        });
623
624        $all('.form-details input[type="radio"]').forEach(radio => {
625            radio.addEventListener("change", function () {
626                const g = this.name;
627                $all(`input[name="${g}"]`).forEach(i => form.querySelector(`label[for="${i.id}"]`)?.classList.remove("active"));
628                form.querySelector(`label[for="${this.id}"]`)?.classList.add("active");
629            });
630        });
631
632        genreSel?.addEventListener("change", () => { applyJ3(); updatePtac(); });
633        typeSel?.addEventListener("change", updatePtac);
634
635        // 🚀 Toggle "Immatriculation inconnue" (MANQUANT AVANT)
636        trigger?.addEventListener("change", () => {
637            const d = demarche?.value || "";
638            if (!toggleWrapper || toggleWrapper.style.display === "none") return;
639
640            if (trigger.checked) {
641                resetDetailsValues();
642                openDetails();
643                applyForCurrent();                     // affiche les blocs dynamiques selon la démarche
644                details?.scrollIntoView({ behavior: "smooth", block: "nearest" });
645                applyImmatDisable();                   // désactive l'input immat
646            } else {
647                closeDetails();
648                hideAll();
649                if (immatInput) immatInput.value = ""; // on vide l’immat si on repasse en mode connu
650                applyImmatDisable();                   // réactive si besoin
651            }
652        });
653
654        // === INITS ===
655        closeDetails();
656        if (toggleWrapper) toggleWrapper.style.display = "none";
657
658        // Remplit les départements (dans CE form seulement)
659        (function populateDepartements() {
660            const sel = $id("departement"); if (!sel) return;
661            const data = [
662                { code: "01", nom: "Ain" }, { code: "02", nom: "Aisne" }, { code: "03", nom: "Allier" },
663                { code: "04", nom: "Alpes-de-Haute-Provence" }, { code: "05", nom: "Hautes-Alpes" },
664                { code: "06", nom: "Alpes-Maritimes" }, { code: "07", nom: "Ardèche" }, { code: "08", nom: "Ardennes" },
665                { code: "09", nom: "Ariège" }, { code: "10", nom: "Aube" }, { code: "11", nom: "Aude" },
666                { code: "12", nom: "Aveyron" }, { code: "13", nom: "Bouches-du-Rhône" }, { code: "14", nom: "Calvados" },
667                { code: "15", nom: "Cantal" }, { code: "16", nom: "Charente" }, { code: "17", nom: "Charente-Maritime" },
668                { code: "18", nom: "Cher" }, { code: "19", nom: "Corrèze" }, { code: "2A", nom: "Corse-du-Sud" },
669                { code: "2B", nom: "Haute-Corse" }, { code: "21", nom: "Côte-d'Or" }, { code: "22", nom: "Côtes-d'Armor" },
670                { code: "23", nom: "Creuse" }, { code: "24", nom: "Dordogne" }, { code: "25", nom: "Doubs" },
671                { code: "26", nom: "Drôme" }, { code: "27", nom: "Eure" }, { code: "28", nom: "Eure-et-Loir" },
672                { code: "29", nom: "Finistère" }, { code: "30", nom: "Gard" }, { code: "31", nom: "Haute-Garonne" },
673                { code: "32", nom: "Gers" }, { code: "33", nom: "Gironde" }, { code: "34", nom: "Hérault" },
674                { code: "35", nom: "Ille-et-Vilaine" }, { code: "36", nom: "Indre" }, { code: "37", nom: "Indre-et-Loire" },
675                { code: "38", nom: "Isère" }, { code: "39", nom: "Jura" }, { code: "40", nom: "Landes" },
676                { code: "41", nom: "Loir-et-Cher" }, { code: "42", nom: "Loire" }, { code: "43", nom: "Haute-Loire" },
677                { code: "44", nom: "Loire-Atlantique" }, { code: "45", nom: "Loiret" }, { code: "46", nom: "Lot" },
678                { code: "47", nom: "Lot-et-Garonne" }, { code: "48", nom: "Lozère" }, { code: "49", nom: "Maine-et-Loire" },
679                { code: "50", nom: "Manche" }, { code: "51", nom: "Marne" }, { code: "52", nom: "Haute-Marne" },
680                { code: "53", nom: "Mayenne" }, { code: "54", nom: "Meurthe-et-Moselle" }, { code: "55", nom: "Meuse" },
681                { code: "56", nom: "Morbihan" }, { code: "57", nom: "Moselle" }, { code: "58", nom: "Nièvre" },
682                { code: "59", nom: "Nord" }, { code: "60", nom: "Oise" }, { code: "61", nom: "Orne" },
683                { code: "62", nom: "Pas-de-Calais" }, { code: "63", nom: "Puy-de-Dôme" }, { code: "64", nom: "Pyrénées-Atlantiques" },
684                { code: "65", nom: "Hautes-Pyrénées" }, { code: "66", nom: "Pyrénées-Orientales" }, { code: "67", nom: "Bas-Rhin" },
685                { code: "68", nom: "Haut-Rhin" }, { code: "69", nom: "Rhône" }, { code: "70", nom: "Haute-Saône" },
686                { code: "71", nom: "Saône-et-Loire" }, { code: "72", nom: "Sarthe" }, { code: "73", nom: "Savoie" },
687                { code: "74", nom: "Haute-Savoie" }, { code: "75", nom: "Paris" }, { code: "76", nom: "Seine-Maritime" },
688                { code: "77", nom: "Seine-et-Marne" }, { code: "78", nom: "Yvelines" }, { code: "79", nom: "Deux-Sèvres" },
689                { code: "80", nom: "Somme" }, { code: "81", nom: "Tarn" }, { code: "82", nom: "Tarn-et-Garonne" },
690                { code: "83", nom: "Var" }, { code: "84", nom: "Vaucluse" }, { code: "85", nom: "Vendée" },
691                { code: "86", nom: "Vienne" }, { code: "87", nom: "Haute-Vienne" }, { code: "88", nom: "Vosges" },
692                { code: "89", nom: "Yonne" }, { code: "90", nom: "Territoire de Belfort" }, { code: "91", nom: "Essonne" },
693                { code: "92", nom: "Hauts-de-Seine" }, { code: "93", nom: "Seine-Saint-Denis" }
693, { code: "94", nom: "Val-de-Marne" },
694                { code: "95", nom: "Val-d'Oise" }, { code: "971", nom: "Guadeloupe" }, { code: "972", nom: "Martinique" },
695                { code: "973", nom: "Guyane" }, { code: "974", nom: "La Réunion" }, { code: "976", nom: "Mayotte" },
696            ];
697            sel.innerHTML = '<option value="" selected disabled hidden>Sélectionner votre département</option>';
698            data.forEach(d => sel.appendChild(new Option(`${d.code} - ${d.nom}`, d.code)));
699        })();
700
701        (function populateGenreOptions() {
702            const sel = $id("type_vehicule"); if (!sel) return;
703            const prev = sel.value;
704            const GENRE = [
705                { v: "1", t: "Voiture (VP)" },
706                { v: "2", t: "Véhicule société / utilitaire (CTTE / Deriv-VP)" },
707                { sep: true },
708                { v: "5", t: "Motocyclette (MTL, MTT1, MTT2, MTTE, MOTO)" },
709                { v: "6", t: "Vélomoteur et Cyclomoteur <= 50cm3 (CL)" },
710                { sep: true },
711                { v: "12", t: "Camion (CAM) > 3,5t" },
712                { v: "13", t: "Bus(TCP) > 3,5t" },
713                { v: "14", t: "Tracteur Routier (TRR) > 3,5t" },
714                { v: "4", t: "Véhicule spécialisé (VASP)" },
715                { sep: true },
716                { v: "15", t: "Remorque (REM) ou semi-remorque (SREM)" },
717                { v: "16", t: "Caravane (RESP)" },
718                { sep: true },
719                { v: "10", t: "Tricycles à moteur (TM)" },
720                { v: "11", t: "Cyclomoteur carrossé à 3 roues (voiturette) (CYCL)" },
721                { v: "7", t: "Quadricycles à moteur: quad, voiturette (QM)" },
722                { sep: true },
723                { v: "8", t: "Engin agricole (TRA, Quad, MAGA)" },
724            ];
725            sel.innerHTML = '<option value="" selected disabled hidden>Sélectionner</option>';
726            GENRE.forEach(x => {
727                const o = document.createElement("option");
728                if (x.sep) { o.value="0"; o.textContent="──────────"; o.disabled=true; o.hidden=true; }
729                else { o.value=x.v; o.textContent=x.t; }
730                sel.appendChild(o);
731            });
732            if (prev && sel.querySelector(`option[value="${prev}"]`)) sel.value = prev;
733        })();
734
735        // GRAND uniquement : auto sélection de la démarche par URL (CGM + CGO)
736        (function autoSelectDemarcheByURL() {
737            if (!demarche) return;
738            try {
739                const params = new URLSearchParams(window.location.search);
740                const forced = params.get("demarche");
741                if (forced) {
742                    const opt = demarche.querySelector(`option[value="${forced}"]`);
743                    if (opt) { demarche.value = forced; demarche.dispatchEvent(new Event("change")); return; }
744                }
745            } catch (e) {}
746            const url = window.location.href.toLowerCase();
747            const mappingCGM = {
748                "vos-demarches-changement-de-titulaire-carte-grise": "1",
749                "vos-demarches-carte-grise-heritage": "1_succession",
750                "vos-demarches-carte-grise-vehicule-neuf": "-1",
751                "vos-demarches-changement-d-adresse-carte-grise": "2",
752                "vos-demarches-changement-statut-matrimonial": "3",
753                "vos-demarches-duplicata-carte-grise": "4",
754                "vos-demarches-carte-grise-import-etranger": "1_import",
755                "vos-demarches-carte-grise-collection": "7",
756                "vos-demarches-immatriculation-voiture-location": "8",
757            };
758            const mappingCGO = {
759                "mode_emploi_changement_proprietaire": "1",
760                "mode_emploi_changement_domicile": "2",
761                "mode_emploi_duplicata": "4",
762                "mode_emploi_achat_vehicule_neuf": "-1",
763                "importation_vehicule_etranger": "1_import",
764                "certificat-cession": "9",
765            };
766            const mappings = [mappingCGM, mappingCGO];
767            let selectedValue = null;
768            for (const m of mappings) {
769                for (const key in m) { if (url.includes(key)) { selectedValue = m[key]; break; } }
770                if (selectedValue) break;
771            }
772            if (selectedValue) {
773                const opt = demarche.querySelector(`option[value="${selectedValue}"]`);
774                if (opt) { demarche.value = selectedValue; demarche.dispatchEvent(new Event("change")); }
775            }
776        })();
777
778        // MINI uniquement : pré-sélection du département via ?dept=XX
779        if (IN_MINI) {
780            (function preselectDepartementFromURLForMini() {
781                try {
782                    const params = new URLSearchParams(window.location.search);
783                    const dept = params.get("dept");
784                    const sel = $id("departement");
785                    if (!dept || !sel) return;
786                    const opt = sel.querySelector(`option[value="${dept}"]`);
787                    if (opt) { sel.value = dept; sel.dispatchEvent(new Event("change")); }
788                } catch(e) { /* silencieux */ }
789            })();
790        }
791
792        // Si le toggle est déjà coché au chargement (rare), on aligne l'UI
793        syncSpecialDemarcheFlags();
794        if (trigger?.checked) { openDetails(); applyForCurrent(); applyImmatDisable(); }
795    } // fin initCalc
796});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.