PageSourceSearch

https://cdn.weka.fr/weka.fr/v2.8.2/web//js/gridflex/home-site.js

js weka.fr collected 2026-10-01 12:14:52 UTC 25,992 bytes, 355 lines download raw bytes

1(function () {
2    "use strict";
3
4    /* ---------- données du donut ----------
5       Libellés, pourcentages et couleurs sont écrits en dur dans le HTML
6       (#wk-usages > .wk-usage : .wk-usage__pct, .wk-usage__dot[style=background],
7       .wk-usage__label). On les lit ici pour dessiner le donut et synchroniser
8       son centre avec la ligne survolée — même ordre que les lignes du HTML. */
9    /*function dotColor(el) {
10        if (!el) return "#005ea8";
11        var m = (el.getAttribute("style") || "").match(/background(?:-color)?\s*:\s*([^;]+)/i);
12        return m ? m[1].trim() : (el.style.backgroundColor || "#005ea8");
13    }
14    var usages = Array.prototype.map.call(
15        document.querySelectorAll("#wk-usages > .wk-usage"),
16        function (row) {
17            var lab = row.querySelector(".wk-usage__label");
18            var pct = row.querySelector(".wk-usage__pct");
19            return {
20                label: lab ? lab.textContent.trim() : "",
21                pct: pct ? parseFloat(pct.textContent) || 0 : 0,
22                c: dotColor(row.querySelector(".wk-usage__dot"))
23            };
24        }
25    );*/
26
27    /* ---------- donut animé + interactif ---------- */
28    // Coupe un libellé en 2 lignes équilibrées pour le centre du donut.
29    /*function splitLabel(s) {
30        var w = s.split(" ");
31        if (w.length === 1) return [s, ""];
32        var best = 1, bestDiff = Infinity;
33        for (var i = 1; i < w.length; i++) {
34            var diff = Math.abs(w.slice(0, i).join(" ").length - w.slice(i).join(" ").length);
35            if (diff < bestDiff) { bestDiff = diff; best = i; }
36        }
37        var l1 = w.slice(0, best).join(" "), l2 = w.slice(best).join(" ");
38        if (l2.charAt(0) === "/") { l1 += " /"; l2 = l2.slice(2); } // pas de ligne qui commence par "/"
39        return [l1, l2];
40    }*/
41
42    // p : avancement du balayage (0->1). activeIdx : segment mis en avant (défaut 0).
43    // dim : true => les autres segments passent en opacité .5. Segments et centre
44    // sont dérivés du tableau `usages` (même ordre / mêmes couleurs que les lignes).
45    /*function donut(p, activeIdx, dim) {
46        activeIdx = activeIdx || 0;
47        var R = 90, r = 54, cx = 110, cy = 110, end = -90 + 360 * p, acc = -90, segs = [];
48        function pt(ang, rad) {
49            return [cx + rad * Math.cos(ang * Math.PI / 180), cy + rad * Math.sin(ang * Math.PI / 180)];
50        }
51        usages.forEach(function (u, i) {
52            var a0 = acc, af = acc + u.pct * 3.6; acc = af;
53            var a1 = Math.min(af, end); if (a1 <= a0 + 0.15) return;
54            var large = (a1 - a0) > 180 ? 1 : 0;
55            var o0 = pt(a0, R), o1 = pt(a1, R), i0 = pt(a1, r), i1 = pt(a0, r);
56            var op = dim && i !== activeIdx ? "0.5" : "1";
57            segs.push('<path d="M ' + o0[0] + " " + o0[1] + " A " + R + " " + R + " 0 " + large + " 1 " + o1[0] + " " + o1[1] +
58                " L " + i0[0] + " " + i0[1] + " A " + r + " " + r + " 0 " + large + " 0 " + i1[0] + " " + i1[1] +
59                ' Z" fill="' + u.c + '" fill-opacity="' + op + '" style="transition:fill-opacity .18s ease"></path>');
60        });
61        var lines = splitLabel(usages[activeIdx].label);
62        return '<svg viewBox="0 0 220 220" style="width:210px;height:210px">' + segs.join("") +
63            '<text x="110" y="100" text-anchor="middle" style="font:800 32px Archivo,sans-serif;fill:#005ea8">' + Math.round(usages[activeIdx].pct * p) + "%</text>" +
64            '<text x="110" y="119" text-anchor="middle" style="font:600 10px Public Sans,sans-serif;fill:#6b7789">' + lines[0] + "</text>" +
65            '<text x="110" y="132" text-anchor="middle" style="font:600 10px Public Sans,sans-serif;fill:#6b7789">' + lines[1] + "</text></svg>";
66    }*/
67
68    /* ---------- compteurs de la section "manifeste" ----------
69       Les valeurs finales sont écrites en dur dans le HTML
70       (#wk-stats .wk-stat__n : "+10 000", "+4 M", "+200 / mois"…).
71       On les lit au chargement pour en extraire préfixe / cible / suffixe ;
72       le JS ne fait que rejouer l'effet de comptage 0 -> valeur au scroll. */
73    function fmt(v) {
74        return Math.round(v).toLocaleString("fr-FR").replace(/[  ]/g, " ");
75    }
76    // "+10 000"     -> { prefix: "+", target: 10000, suffix: "" }
77    // "+4 M"        -> { prefix: "+", target: 4,     suffix: " M" }
78    // "+200 / mois" -> { prefix: "+", target: 200,   suffix: " / mois" }
79    function parseStat(txt) {
80        var m = String(txt).match(/^(\D*?)(\d[\d\s.,]*\d|\d)([\s\S]*)$/);
81        if (!m) return { prefix: "", target: 0, suffix: String(txt) };
82        var n = parseFloat(m[2].replace(/[\s.]/g, "").replace(",", "."));
83        return { prefix: m[1], target: isNaN(n) ? 0 : n, suffix: m[3] };
84    }
85
86    /* ---------- hero chips : la puce active suit la question jouée dans le panneau démo ---------- */
87    var heroChipsEl = document.getElementById("wk-hero-chips");
88    // Appelé par le panneau démo à chaque nouvelle question : met à jour les
89    // styles des puces déjà présentes dans le HTML (aucune reconstruction).
90    function setActiveChip(i) {
91        var chips = heroChipsEl ? heroChipsEl.children : [];
92        if (!chips.length) return;
93        i = i % chips.length;
94        for (var k = 0; k < chips.length; k++) {
95            chips[k].classList.toggle("is-active", k === i);
96        }
97    }
98
99    /* ---------- compteurs + donut : animation au scroll (ex-componentDidMount) ---------- */
100    function easeOutCubic(p) { return 1 - Math.pow(1 - p, 3); }
101    // Stepper basé sur setInterval : fiable même quand requestAnimationFrame est gelé
102    // (onglet en arrière-plan, file://, throttling navigateur). Garantit toujours cb(1).
103    function animate(dur, cb) {
104        var start = Date.now();
105        cb(0);
106        var id = setInterval(function () {
107            var p = Math.min(1, (Date.now() - start) / dur);
108            cb(easeOutCubic(p));
109            if (p >= 1) clearInterval(id);
110        }, 16);
111    }
112
113    var statsEl = document.getElementById("wk-stats");
114    var donutEl = document.getElementById("wk-donut");
115
116    // Cibles lues UNE fois dans le HTML, avant toute animation.
117    var statNums  = statsEl ? statsEl.querySelectorAll(".wk-stat__n") : [];
118    var statSpecs = [];
119    for (var s = 0; s < statNums.length; s++) {
120        statSpecs.push(parseStat(statNums[s].textContent.trim()));
121    }
122
123    // Ne touche qu'au chiffre de chaque carte (déjà présente dans le HTML),
124    // pas au libellé qui reste celui écrit en dur.
125    function paintStats(sp) {
126        for (var i = 0; i < statNums.length && i < statSpecs.length; i++) {
127            var st = statSpecs[i];
128            statNums[i].textContent = st.prefix + fmt(st.target * sp) + st.suffix;
129        }
130    }
131    /*function paintDonut(p) { donutEl.innerHTML = donut(p, 0, false); }*/
132
133    // Met à jour le donut EN PLACE au survol d'une ligne (opacités + centre),
134    // sans reconstruire le SVG -> la transition d'opacité peut jouer.
135    /*function setDonutActive(idx, dim) {
136        var paths = donutEl.querySelectorAll("path");
137        for (var i = 0; i < paths.length; i++) {
138            paths[i].setAttribute("fill-opacity", dim && i !== idx ? "0.2" : "1");
139        }
140        var texts = donutEl.querySelectorAll("text");
141        var lines = splitLabel(usages[idx].label);
142        if (texts[0]) texts[0].textContent = usages[idx].pct + "%";
143        if (texts[1]) texts[1].textContent = lines[0];
144        if (texts[2]) texts[2].textContent = lines[1] || "";
145    }*/
146
147    // Le donut est un graphique généré, sans équivalent statique : il démarre
148    // à 0 puis se dessine au scroll. Les compteurs, eux, gardent leur valeur
149    // finale (écrite en dur) jusqu'à ce que l'animation de comptage démarre.
150    /*paintDonut(0);*/
151
152    var statsStarted = false, donutStarted = false, donutReady = false;
153    function runStats() { if (statsStarted) return; statsStarted = true; animate(1400, paintStats); }
154    /*function runDonut() {
155        if (donutStarted) return; donutStarted = true;
156        animate(1200, function (p) {
157            paintDonut(p);
158            if (p >= 1) donutReady = true; // balayage terminé : le survol peut agir
159        });
160    }*/
161
162    // Déclenche cb() la première fois que le bloc entre dans le viewport.
163    // On sonde la position sur un petit timer (getBoundingClientRect) : ça ne dépend
164    // ni des événements scroll ni d'IntersectionObserver, donc fiable partout.
165    function onEnterView(el, cb) {
166        if (!el) return;
167        function inView() {
168            var r = el.getBoundingClientRect();
169            var vh = window.innerHeight || document.documentElement.clientHeight;
170            // le haut du bloc est entré d'au moins 60px et le bloc n'est pas déjà remont
170é au-dessus
171            return r.top < vh - 60 && r.bottom > 0;
172        }
173        if (inView()) { cb(); return; }
174        var poll = setInterval(function () {
175            if (inView()) { clearInterval(poll); cb(); }
176        }, 200);
177    }
178    onEnterView(statsEl, runStats);
179    /*onEnterView(donutEl, runDonut);*/
180
181    /* ---------- survol des lignes d'usage <-> donut ---------- */
182    (function () {
183        var rows = document.querySelectorAll("#wk-usages > a");
184        for (var i = 0; i < rows.length; i++) {
185            (function (idx) {
186                rows[idx].addEventListener("mouseenter", function () {
187                    if (donutReady) setDonutActive(idx, true);   // segment idx opaque, autres à .5
188                });
189                rows[idx].addEventListener("mouseleave", function () {
190                    if (donutReady) setDonutActive(0, false);     // retour à l'état par défaut
191                });
192            })(i);
193        }
194    })();
195
196    /* ---------- démo : assistant interactif animé (ex-playDemo du fichier B) ---------- */
197    (function () {
198        var demoScroll = document.getElementById("wk-demo-scroll");
199        var demoInput = document.getElementById("wk-demo-input");
200        if (!demoScroll || !demoInput) return;
201
202        function esc(s) {
203            return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
204        }
205
206        var scenarios = [
207            {
208                q: "Le DGS peut-il exercer les fonctions d'officier d'état civil ?",
209                a: "Oui, mais uniquement par délégation du maire, et sous conditions strictes.\n\n" +
210                    "Le DGS doit être fonctionnaire titulaire de la commune : l'article R. 2122-10 du CGCT réserve la délégation d'officier d'état civil aux seuls fonctionnaires titulaires. La délégation doit faire l'objet d'un arrêté nominatif du maire, être publiée et transmise au contrôle de légalité pour devenir exécutoire.\n\n" +
211                    "Attention au détachement : un fonctionnaire détaché dans la commune ne peut pas recevoir cette délégation — les magistrats du parquet estiment qu'il n'est pas « fonctionnaire titulaire de la commune ». En revanche, aucune disposition ne limite la fonction à la filière administrative.\n\n" +
212                    "Points de vigilance : la délégation devient caduque dès que le maire ou le DGS cesse ses fonctions ; le maire conserve toujours la possibilité d'exercer personnellement ses fonctions d'officier d'état civil et reste responsable du contrôle des fonctions déléguées.",
213                srcs: [
214                    { t: "Délégation de fonctions et délégation de signature", ref: "Fiche WEKA n°5900", maj: "mai 2026", ex: "« La délégation doit faire l'objet d'un arrêté publié (CE, 12 mars 1975, Commune de Loges-Margueron). Pour être exécutoire, elle doit être transmise au contrôle de légalité et notifiée au délégataire. »" },
215                    { t: "La qualité d'officier de l'état civil", ref: "Fiche WEKA n°1240", maj: "avril 2026", ex: "« Seuls les fonctionnaires titulaires de la commune peuvent recevoir délégation dans les fonctions d'officier d'état civil, à l'exclusion des agents recrutés par voie de détachement. »" },
216                    { t: "Code général des collectivités territoriales", ref: "Art. R. 2122-10", maj: "en vigueur", ex: "« Le maire peut déléguer à un ou plusieurs fonctionnaires titulaires de la commune les fonctions qu'il exerce en tant qu'officier de l'état civil. »" }
217                ]
218            },
219            {
220                q: "Rédige un arrêté interdisant la circulation rues Paul Doumer, Jeanne d'Arc et George Sand du 10 au 14 avril pour la brocante annuelle",
221                a: "ARRÊTÉ MUNICIPAL N° [à compléter]\nPortant interdiction temporaire de circulation et de stationnement — Brocante annuelle\n\n" +
222                    "VU le Code général des collectivités territoriales, notamment l'article L. 2213-2 relatif aux pouvoirs de police du maire en matière de circulation et de stationnement ;\n" +
223                    "VU le Code de la route, notamment les articles R. 417-6 et R. 411-25 ;\n" +
224                    "VU la demande de l'association « Saint-Dié en folie » sollicitant l'organisation de sa brocante annuelle ;\n" +
225                    "CONSIDÉRANT la nécessité d'assurer la sécurité des participants et des visiteurs ;\n\n" +
226                    "ARRÊTE\n\n" +
227                    "Article 1 — Du jeudi 10 avril à 8h00 au lundi 14 avril à 22h00, la circulation de tous véhicules et le stationnement sont interdits rue Paul Doumer, rue Jeanne d'Arc et rue George Sand, sur toute leur longueur.\n\n" +
228                    "Article 2 — Sont autorisés à circuler : les véhicules de secours et de police ; les véhicules des exposants pour le montage (10 avril, 6h-8h) et le démontage ; les riverains sur justificatif.\n\n" +
229                    "Article 3 — La signalisation réglementaire sera mise en place par les services techniques. Les panneaux seront installés 48 heures avant le début de l'interdiction.",
230                srcs: [
231                    { t: "Comment gérer le commerce ambulant sur le territoire communal ?", ref: "Fiche WEKA n°7417", maj: "juin 2026", ex: "« Le maire tient de l'article L. 2213-2 du CGCT le pouvoir d'interdire à certaines heures l'accès de certaines voies, dans l'intérêt de la commodité et de la sûreté de la circulation. »" },
232                    { t: "Police municipale : règles relatives à l'arrêt et au stationnement", ref: "Fiche WEKA n°1773", maj: "mai 2026", ex: "« L'arrêté doit être affiché sur place 48 heures avant le début de l'interdiction et transmis au contrôle de légalité avant sa mise en application. »" },
233                    { t: "Code de la route", ref: "Art. R. 417-6 et R. 411-25", maj: "en vigueur", ex: "« Tout véhicule en stationnement gênant peut faire l'objet d'une mise en fourrière aux frais de son propriétaire. »" }
234                ]
235            },
236            {
237                q: "Calcule une indemnité de rupture conventionnelle : 6 ans d'ancienneté, 22 034,40 € brut, adjoint technique territorial",
238                a: "Rémunération mensuelle brute de référence : 22 034,40 € ÷ 12 = 1 836,20 €.\n\n" +
239                    "Montant plancher — avec 6 ans d'ancienneté, vous êtes dans la première tranche du barème (1/4 de mois par année) : (1 836,20 € ÷ 4) × 6 = 2 754,30 € brut.\n\n" +
240                    "Montant plafond — il ne peut excéder 1/12 de la rémunération brute annuelle par année d'ancienneté : 1 836,20 € × 6 = 11 017,20 € brut.\n\n" +
241                    "Votre marge de négociation se situe donc entre 2 754,30 € et 11 017,20 €.\n\n" +
242                    "Points importants : seules les années de services dans la fonction publique territoriale sont retenues ; le calcul se base sur la rémunération brute de l'année civile précédant la rupture ; certaines primes sont exclues (remboursements de frais, indemnités de jury ou d'enseignement, prime de mobilité géographique) ; l'indemnité bénéficie d'une exonération partielle de CSG/CRDS et d'impôt sur le revenu.",
243                srcs: [
244                    { t: "L'indemnité spécifique de rupture conventionnelle (ISRC)", ref: "Fiche WEKA n°13585", maj: "juin 2026", ex: "« Le montant de l'indemnité ne peut être inférieur à un quart de mois de rémunération brute par année d'ancienneté pour les dix premières années. »" },
245                    { t: "Décret n°2019-1593 du 31 décembre 2019", ref: "Texte consolidé", maj: "en vigueur", ex: "« Le montant maximum de l'indemnité ne peut excéder un douzième de la rémunération brute annuelle de référence par année d'ancienneté, dans la limite de vingt-quatre ans. »" },
246                    { t: "La rupture conventionnelle dans la fonction publique territoriale", ref: "Fiche WEKA n°13346", maj: "mai 2026", ex: "« Les deux parties négocient librement le montant de l'indemnité dans les bornes fixées par le décret, à l'issue d'au moins un entretien préalable. »" }
247                ]
248            },
249            {
250                q: "Complète cette clause de marché public sur les engagements écologiques",
251                a: "Votre clause constitue une bonne base ; voici des compléments pour la rendre opérationnelle.\n\n" +
252                    "Consommation énergétique — « Les parties s'engagent à optimiser leur consommation énergétique en privilégiant les matériels à faible consommation électrique pour les équipements de projection, audiovisuel et informatique, et veillent à une gestion raisonnée de l'éclairage, de la sonorisation et de la consommation d'eau potable. »\n\n" +
253                    "Gestion des déchets — « Les parties réduisent la production de déchets à la source en supprimant autant que possible les contenants et ustensiles jetables au profit de solutions réutilisables, mettent en place un tri sélectif avec signalétique adaptée et assurent une valorisation matière ou énergétique des déchets produits. »\n\n" +
254                    "Mobilités — « Les parties privilégient les mobilités douces, encouragent le covoiturage pour l'accès à l'événement et retiennent, dans la mesure du possible, des intervenants locaux afin de limiter les déplacements. »\n\n" +
255                    "Suivi — « Les parties mettent en place des indicateurs de suivi des actions déployées et établissent un bilan environnemental à l'issue de l'événement, dans une logique d'amélioration continue. »",
256                srcs: [
257                    { t: "Intégrer des enjeux de développement durable dans un marché public d'organisation d'événements", ref: "Fiche WEKA n°2174", maj: "juin 2026", ex: "« Les clauses environnementales doivent être formulées de manière mesurable et vérifiable pour être opposables au titulaire du marché. »" },
258                    { t: "Organiser un événement écoresponsable", ref: "Fiche WEKA n°7104", maj: "mai 2026", ex: "« La suppression des contenants et ustensiles jetables, au profit de solutions réutilisables, constitue le premier levier de réduction des déchets à la source. »" },
259                    { t: "Code de l'environnement", ref: "Art. L. 541-15-10", maj: "en vigueur", ex: "« Sont fixées des obligations en matière de réduction des déchets et de promotion de l'économie circulaire, notamment pour la restauration collective. »" }
260                ]
261            }
262        ];
263
264        var st = { typedQ: "", demoQ: "", demoA: "", qShown: false, thinking: false, aShown: false, chipsShown: false, srcShown: false, t
264yping: false, src: -1, curSrcs: null };
265        var timers = [];
266
267        function sd() { demoScroll.scrollTop = demoScroll.scrollHeight; }
268
269        function render() {
270            var parts = [];
271            if (st.qShown) {
272                parts.push('<div style="display:flex;justify-content:flex-end"><span style="background:#EEF2F6;color:#103A66;font:500 14px/1.4 \'Public Sans\',sans-serif;padding:10px 14px;border-radius:12px 12px 4px 12px">' + esc(st.demoQ) + "</span></div>");
273            }
274            if (st.thinking) {
275                parts.push('<div style="display:flex;gap:5px;padding:4px 2px">' +
276                    '<span style="width:7px;height:7px;border-radius:50%;background:#005ea8;animation:wkblink 1.1s infinite"></span>' +
277                    '<span style="width:7px;height:7px;border-radius:50%;background:#005ea8;animation:wkblink 1.1s .18s infinite"></span>' +
278                    '<span style="width:7px;height:7px;border-radius:50%;background:#005ea8;animation:wkblink 1.1s .36s infinite"></span></div>');
279            }
280            if (st.aShown) {
281                var chips = st.chipsShown ? '<span style="display:inline-flex;gap:4px;vertical-align:middle;margin-left:5px">' +
282                    '<span style="background:#005ea8;color:#fff;font:700 9px/1;padding:3px 5px;border-radius:4px;cursor:pointer">1</span>' +
283                    '<span style="background:#005ea8;color:#fff;font:700 9px/1;padding:3px 5px;border-radius:4px;cursor:pointer">2</span>' +
284                    '<span style="background:#005ea8;color:#fff;font:700 9px/1;padding:3px 5px;border-radius:4px;cursor:pointer">3</span></span>' : "";
285                parts.push('<p style="margin:0;font:400 13.5px/1.65 \'Public Sans\',sans-serif;color:#45525F;white-space:pre-wrap">' + esc(st.demoA) + chips + "</p>");
286            }
287            if (st.srcShown) {
288                var rows = (st.curSrcs || []).map(function (s, idx) {
289                    return '<div data-src="' + idx + '" style="display:flex;gap:8px;cursor:pointer;align-items:baseline">' +
290                        '<span style="color:#eb7a61;font-weight:800">›</span>' +
291                        '<span class="wk-demo-src" style="font:400 12px/1.4 \'Public Sans\',sans-serif;color:' + (st.src === idx ? "#005ea8" : "#45525F") + ";text-decoration:" + (st.src === idx ? "underline" : "none") + '">' + esc(s.t) + "</span></div>";
292                }).join("");
293                var active = "";
294                if (st.src >= 0 && st.curSrcs && st.curSrcs[st.src]) {
295                    var a = st.curSrcs[st.src];
296                    active = '<div style="margin-top:11px;background:#fff;border:1px solid #c4d9ec;border-radius:9px;padding:11px 13px">' +
297                        '<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:7px">' +
298                        '<span style="font:700 11.5px/1.3 Archivo,sans-serif;color:#103A66">' + esc(a.ref) + "</span>" +
299                        '<span style="flex:none;font:700 10px/1 \'Public Sans\',sans-serif;color:#0a929f;background:#E4F3F4;padding:3px 7px;border-radius:5px">' + esc(a.maj) + "</span></div>" +
300                        '<p style="margin:0 0 9px;font:400 11.5px/1.5 \'Public Sans\',sans-serif;color:#5a6675">' + esc(a.ex) + "</p>" +
301                        '<span style="font:700 11.5px/1 \'Public Sans\',sans-serif;color:#005ea8;cursor:pointer">Ouvrir la fiche complète →</span></div>';
302                }
303                parts.push('<div style="background:#F3F8FD;border:1px solid #d3e4f5;border-radius:12px;padding:14px">' +
304                    '<div style="font:700 12px/1 Archivo,sans-serif;color:#103A66;margin-bottom:10px">Sources · fiches WEKA &amp; textes officiels</div>' +
305                    '<div style="display:flex;flex-direction:column;gap:9px">' + rows + "</div>" + active + "</div>");
306            }
307            demoScroll.innerHTML = parts.join("");
308
309            var caret = st.typing ? '<span style="display:inline-block;width:1.5px;height:13px;background:#005ea8;margin-left:2px;vertical-align:-2px;animation:wkcaret 1s infinite"></span>' : "";
310            var placeholder = (!st.typing && !st.typedQ) ? '<span style="color:#9aa3b0">Posez votre question …</span>' : "";
311            demoInput.innerHTML = esc(st.typedQ) + caret + placeholder;
312        }
313
314        // clic sur une source -> ouvre / referme son extrait
315        demoScroll.addEventListener("click", function (e) {
316            var row = e.target.closest ? e.target.closest("[data-src]") : null;
317            if (!row) return;
318            var idx = parseInt(row.getAttribute("data-src"), 10);
319            st.src = st.src === idx ? -1 : idx;
320            render(); sd();
321        });
322
323        function play(i) {
324            var sc = scenarios[i % scenarios.length];
325            timers.forEach(clearTimeout); timers = [];
326            function T(fn, ms) { timers.push(setTimeout(fn, ms)); }
327
328            st = { typedQ: "", demoQ: "", demoA: "", qShown: false, thinking: false, aShown: false, chipsShown: false, srcShown: false, typing: true, src: -1, curSrcs: sc.srcs };
329            setActiveChip(i);            // la puce du hero correspondant à cette question s'active
330            render();
331            demoScroll.scrollTop = 0;
332
333            var t = 700;
334            for (var k = 1; k <= sc.q.length; k++) {
335                (function (frag) { T(function () { st.typedQ = frag; render(); }, t); })(sc.q.slice(0, k));
336                t += 36;
337            }
338            t += 450;
339            T(function () { st.typedQ = ""; st.typing = false; st.demoQ = sc.q; st.qShown = true; st.thinking = true; render(); sd(); }, t);
340            t += 1100;
341            T(function () { st.thinking = false; st.aShown = true; render(); sd(); }, t);
342            for (var m = 1; m <= sc.a.length; m += 9) {
343                (function (frag) { T(function () { st.demoA = frag; render(); sd(); }, t); })(sc.a.slice(0, m));
344                t += 22;
345            }
346            T(function () { st.demoA = sc.a; st.chipsShown = true; render(); sd(); }, t); t += 400;
347            T(function () { st.srcShown = true; render(); sd(); }, t); t += 1300;
348            T(function () { st.src = 0; render(); sd(); }, t); t += 6200;
349            T(function () { play(i + 1); }, t);
350        }
351
352        render();
353        play(0);
354    })();
355})();

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.