Paillasse/portail/resources/views/bibliotheque.blade.php
Adam Belkacemi e4a6aaebb1 ajoute les pages légales, la purge RGPD et sert polices et scripts depuis le portail
Pages /legal et /confidentialite réécrites d'après le système réel, purge
des classes archivées depuis 90 jours (prouvée par Pest), archivage côté
prof. Polices, xterm.js et CodeMirror ne sont plus chargés depuis Google
ni jsDelivr. Pages d'aperçu signalées, sans vrais établissements ni faux
chiffres. Journaux Laravel gardés 14 jours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:07:25 +02:00

122 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('layouts.vitrine')
@section('titre', 'Bibliothèque')
@section('contenu')
@include('partials.apercu')
<section class="border-b border-[#e3e5e8] bg-white">
<div class="mx-auto max-w-7xl px-5 pb-12 pt-12 lg:px-10">
<p class="inline-block border border-[#dadce0] rounded-lg bg-[#eef0fd] px-3 py-1 text-sm font-bold">Bibliothèque du prof</p>
<div class="min-w-0 flex-1"><p data-public="P" class="mb-2 inline-flex items-center gap-2 rounded-full border border-[#b8c1cf] bg-white px-3 py-1 text-sm font-bold text-[#1d3fa8]">Pour les profs</p>
<h1 class="mt-6 max-w-5xl text-6xl font-extrabold leading-[1.05] tracking-tight sm:text-7xl">Tout le référentiel, fiche par fiche.</h1></div>
<p class="mt-6 max-w-3xl text-xl font-medium leading-relaxed">Du SNT au master, en passant par l’école d’ingénieurs. Chaque fiche regroupe cours, quiz, TD, labs et examens. Un même chapitre sert plusieurs formations : OSPF compte pour la NSI, le BTS SIO, le BTS CIEL, le BUT R&amp;T et les titres pro.</p>
<dl id="stats" class="mt-10 grid max-w-4xl grid-cols-2 border border-[#e3e5e8] rounded-2xl sm:grid-cols-5"></dl>
<div class="mt-6 max-w-4xl border border-[#e3e5e8] rounded-2xl bg-[#e6f4ea] p-4 text-sm font-bold"><p>Construit à partir des programmes officiels. Vérifié le 29 septembre 2026 :</p><ul class="mt-2 flex flex-wrap gap-x-5 gap-y-1 underline underline-offset-4"><li><a href="https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000049926463" target="_blank" rel="noopener">BTS SIO · arrêté du 8 juillet 2024 (épreuves E5, E6, E7)</a></li><li><a href="https://www.francecompetences.fr/recherche/rncp/37391/" target="_blank" rel="noopener">BTS CIEL · RNCP37391, arrêté du 25 janvier 2023</a></li><li><a href="https://www.onisep.fr/ressources/univers-formation/formations/post-bac/but-reseaux-et-telecommunications-parcours-cybersecurite" target="_blank" rel="noopener">BUT R&amp;T · 5 parcours (Onisep)</a></li></ul></div>
</div>
</section>
<div class="sticky top-0 z-20 border-b border-[#e3e5e8] bg-white">
<div class="mx-auto flex max-w-7xl flex-wrap items-center gap-3 px-5 py-3 lg:px-10">
<div class="flex border border-[#e3e5e8] rounded-2xl font-extrabold" role="tablist">
<button type="button" data-vue="themes" aria-selected="true" class="px-4 py-2 aria-selected:bg-black aria-selected:text-white">Par thème</button>
<button type="button" data-vue="formations" aria-selected="false" class="border-l border-[#e3e5e8] px-4 py-2 aria-selected:bg-black aria-selected:text-white">Par formation</button>
<button type="button" data-vue="examens" aria-selected="false" class="border-l border-[#e3e5e8] px-4 py-2 aria-selected:bg-black aria-selected:text-white">Examens</button>
</div>
<input id="b-recherche" class="min-w-[200px] flex-1 border border-[#e3e5e8] rounded-2xl px-3 py-2 font-semibold focus:bg-[#f8f9fa] focus:outline-none" placeholder="Rechercher : OSPF, chmod, RSA…" aria-label="Rechercher">
<select id="b-theme" class="border border-[#e3e5e8] rounded-2xl px-3 py-2 font-bold" aria-label="Thème"><option value="">Tous les thèmes</option></select>
<label class="flex items-center gap-2 font-bold"><input id="b-vm" type="checkbox" class="h-5 w-5 accent-black"> Masquer hors VM</label>
<p class="ml-auto border border-[#e3e5e8] rounded-2xl bg-[#e6f4ea] px-3 py-2 font-extrabold">Ma séquence : <span id="b-seq">0</span></p>
</div>
<div class="mx-auto flex max-w-7xl flex-wrap gap-2 px-5 pb-3 lg:px-10" role="group" aria-label="Activités">
<button type="button" data-tag="C" aria-pressed="false" class="border border-[#dadce0] rounded-lg bg-white px-2 py-0.5 font-mono text-xs font-semibold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-1 aria-pressed:outline-black">C · Cours</button>
<button type="button" data-tag="Q" aria-pressed="false" class="border border-[#dadce0] rounded-lg bg-[#fef7e0] px-2 py-0.5 font-mono text-xs font-semibold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-1 aria-pressed:outline-black">Q · Quiz</button>
<button type="button" data-tag="TD" aria-pressed="false" class="border border-[#dadce0] rounded-lg bg-[#eef0fd] px-2 py-0.5 font-mono text-xs font-semibold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-1 aria-pressed:outline-black">TD · TD</button>
<button type="button" data-tag="LAB" aria-pressed="false" class="border border-[#dadce0] rounded-lg bg-[#e6f4ea] px-2 py-0.5 font-mono text-xs font-semibold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-1 aria-pressed:outline-black">LAB · TP sur VM</button>
<button type="button" data-tag="LAB+" aria-pressed="false" class="border border-[#dadce0] rounded-lg bg-[#e6f4ea] px-2 py-0.5 font-mono text-xs font-semibold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-1 aria-pressed:outline-black">LAB+ · Lab multi-VM</button>
<button type="button" data-tag="IDE" aria-pressed="false" class="border border-[#dadce0] rounded-lg bg-[#13233A] text-white px-2 py-0.5 font-mono text-xs font-semibold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-1 aria-pressed:outline-black">IDE · IDE</button>
<button type="button" data-tag="GEN" aria-pressed="false" class="border border-[#dadce0] rounded-lg bg-[#fce8e6] px-2 py-0.5 font-mono text-xs font-semibold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-1 aria-pressed:outline-black">GEN · Générateur</button>
<button type="button" data-tag="EXAM" aria-pressed="false" class="border border-[#dadce0] rounded-lg bg-[#ec3013] text-white px-2 py-0.5 font-mono text-xs font-semibold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-1 aria-pressed:outline-black">EXAM · Examen</button>
<button type="button" data-tag="PROJ" aria-pressed="false" class="border border-[#dadce0] rounded-lg bg-white px-2 py-0.5 font-mono text-xs font-semibold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-1 aria-pressed:outline-black">PROJ · Projet</button>
<button type="button" data-tag="CTF" aria-pressed="false" class="border border-[#dadce0] rounded-lg bg-[#0b57d0] text-white px-2 py-0.5 font-mono text-xs font-semibold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-1 aria-pressed:outline-black">CTF · CTF</button>
</div>
</div>
<div id="b-contenu" class="mx-auto max-w-7xl px-5 py-12 lg:px-10" data-url-fiche="{{ route('cours.fiche') }}" data-url-cours="{{ route('cours.show') }}" data-url-ide="{{ route('ide') }}" data-url-lab="{{ route('labs.essai') }}" data-url-gen="{{ route('generateur') }}" data-url-exam="{{ route('examen') }}" data-url-banque="{{ route('banque') }}"></div>
<script src="{{ asset('js/referentiel.js') }}"></script>
<script src="{{ asset('js/contenus-1.js') }}"></script>
<script src="{{ asset('js/contenus-2.js') }}"></script>
<script src="{{ asset('js/contenus-3.js') }}"></script>
<script src="{{ asset('js/contenus-exemples.js') }}"></script>
<script src="{{ asset('js/contenus-reseau-1.js') }}"></script>
<script src="{{ asset('js/contenus-reseau-2.js') }}"></script>
<script src="{{ asset('js/contenus-devops-1.js') }}"></script>
<script src="{{ asset('js/contenus-devops-2.js') }}"></script>
<script src="{{ asset('js/contenus-infra-3.js') }}"></script>
<script src="{{ asset('js/contenus-data-4.js') }}"></script>
<script src="{{ asset('js/contenus-sys-5.js') }}"></script>
<script src="{{ asset('js/contenus-lot-6.js') }}"></script>
<script src="{{ asset('js/contenus-lot-7.js') }}"></script>
<script src="{{ asset('js/contenus-lot-8.js') }}"></script>
<script src="{{ asset('js/contenus-lot-9.js') }}"></script>
<script src="{{ asset('js/contenus-lot-10.js') }}"></script>
<script src="{{ asset('js/contenus-lot-11.js') }}"></script>
<script src="{{ asset('js/contenus-lot-12.js') }}"></script>
<script src="{{ asset('js/contenus-alias.js') }}"></script>
<script>
(function () {
var R = window.REFERENTIEL, box = document.getElementById('b-contenu'), vue = 'themes', seq = 0, actifs = [];
var BG = {"C":"bg-white","Q":"bg-[#fef7e0]","TD":"bg-[#eef0fd]","LAB":"bg-[#e6f4ea]","LAB+":"bg-[#e6f4ea]","IDE":"bg-[#13233A] text-white","GEN":"bg-[#fce8e6]","EXAM":"bg-[#ec3013] text-white","PROJ":"bg-white","CTF":"bg-[#0b57d0] text-white"}; BG.HV = 'bg-white border-dashed';
var U = box.dataset;
var KW = { ipv6: /ipv6/i, projet: /projet|sa[ée]|scrum|agile|itil|pilpro|erp|management|gestion|lean|comptabilit|cejm|épreuve|examen|grand oral|sre|observabilit/i, maths: /math|signal|transmission|modulation|suite|matrice|probab|fourier|complexe|ensemble|trigonom|intégral|différenti|numérisation|décibel/i, fichiers: /fichier|nfs|ftp|ntp|temps|glpi|inventaire|bonding|agrégation|tssr|ais|maintenir|infrastructure/i, archi: /architecture|processeur|mémoire|matériel|composant|machine|système d’exploitation|os\b/i, iot: /iot|node-red|modbus|coap|chaîne d’information|chaîne d’énergie|objet|capteur|embarqué|électronique|microcontr|arduino|esp32|mqtt|domotique/i, voip: /voip|téléphon|sip|voix|toip|visioconf/i, pandas: /pandas|donnée|data|statistique|tableau|csv|visualis/i, ml: /éthique|biais|ai act|kafka|spark|flux temps réel|distribué|apprentissage|machine learning|\bia\b|intelligence artificielle|modèle|knn|voisins|prédict/i, neurones: /neurone|deep|ocr|vocal|image|vision|langage/i, k8s: /kubernetes|orchestr|cloud|cluster/i, supervision: /supervision|monitoring|zabbix|prometheus|snmp|journalisation|logs centralis/i, annuaire: /ldap|active directory|annuaire|gpo|samba|identit|authentification|kerberos/i, git: /\bgit\b|version|collabor/i, cicd: /ci\/cd|pipeline|intégration continue|déploiement/i, docker: /docker|conteneur|virtualisation|devops|devcloud|cloud/i, compose: /compose/i, ansible: /ansible|iac|automatis|ci\/cd|pipeline/i, binaire: /binaire|hexa|représentation|entiers|codage/i, terminal: /terminal|linux|shell|commande|debian|système|fichiers|cp, mv|cat, less|nano|vim|processus|systemd|journalctl|apt|paquets|tar|archiv|compression|cron|planification|performance|noyau|démarrage|grub|sauvegarde|rsync|lvm/i, droits: /droits|permission|utilisateur|groupe|chmod|compte/i, tubes: /bash|script|grep|journaux|log|automatis|expressions régulières|regex/i, osi: /osi|tcp\/ip|couche|encapsulation|réseau|protocole|internet/i, sql: /sql|données|base|requête|mcd|merise|relationnel|group by|agrégats|ddl|dml|create|insert|vues|procédures|triggers|transactions|acid|index|explain|normalisation|réplication/i, python: /python|programm|algorithm|développ|code|fonction|listes|tuples|dictionnaires|chaînes|récursiv|tests|pytest|tris|dichotom|complexité|piles|graphes|pandas|json|csv|types construits/i, web: /web|photographie|pixel|rvb|exif|gps|nmea|cartographie|histoire|anglais|html|css|http|site|cms|apache|php|javascript|nginx|reverse proxy|proxy|api rest|laravel|mvc|oauth|authentification|framework/i, crypto: /crypto|sécurité|cyber|chiffr|mot de passe|ssh|tls|certificat|rgpd|gpg|vpn|wireguard|ipsec|fail2ban|force brute|nmap|scan|vulnérab|cve|ids|suricata|anssi|menaces|hameçonnage|rançongiciel|iso 27001|ebios|pssi|nis2|ctf|dmz|hygiène/i, diagnostic: /diagnostic|dépannage|incident|ping|supervision|maintenance|arp|tcp et udp|ss\b|tcpdump|pcap|trames|analyse de trames/i, ipv4: /ipv4|adressage|masque|cidr|sous-réseau|vlsm|plan d/i, vlan: /vlan|802\.1q|commutation|switch|ethernet|trunk/i, dhcp: /dhcp|bail|adresse automatique/i, dns: /dns|domaine|nom de|messagerie|postfix|dkim|dmarc/i, nat: /nat|pat|passerelle|pare-feu|nftables|firewall|dmz/i, routage: /routage|routeur|route|ospf|rip|interconnex|bgp|mpls|vrrp|keepalived/i };
var PRO = (window.CONTENUS_PRO || []).filter(function (c) { return c.intro; });
var statut = function (t) { if (PRO.some(function (c) { return c.match.test(t); })) return ['Complet', 'bg-[#e6f4ea]']; var p = [t].concat(t.split(/\s*[:,;·]\s*|\s+et\s+/)); if (PRO.some(function (c) { return p.some(function (x) { return x.length > 1 && (c.match.test(x) || (KW[c.id] && KW[c.id].test(x))); }); })) return ['Lié', 'bg-[#fef7e0]']; return ['Bientôt', 'bg-white']; };
var badgeS = function (t) { var s = statut(t); return '<span class="border border-[#dadce0] rounded-lg ' + s[1] + ' px-1.5 text-[11px] font-extrabold">' + s[0] + '</span>'; };
var esc = function (s) { return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;'); };
var badges = function (tags) { return tags.split(' ').map(function (t) { return '<span class="border border-[#dadce0] rounded-lg ' + (BG[t] || 'bg-white') + ' px-1.5 font-mono text-[11px] font-semibold">' + (t === 'HV' ? 'hors VM' : t) + '</span>'; }).join(''); };
var lienT = function (f) { var t = f.tags.split(' '); if (t.indexOf('IDE') > -1 || t.indexOf('GEN') > -1 || t.indexOf('LAB') > -1 || t.indexOf('EXAM') > -1) return lien(f.tags); return U.urlFiche + '?chapitre=' + encodeURIComponent(f.t); };
var lien = function (tags) { var t = tags.split(' '); return t.indexOf('IDE') > -1 ? U.urlIde : t.indexOf('GEN') > -1 ? U.urlGen : (t.indexOf('LAB') > -1 || t.indexOf('LAB+') > -1) ? U.urlLab : t.indexOf('EXAM') > -1 ? U.urlExam : U.urlCours; };
var tous = []; R.themes.forEach(function (th) { th.items.forEach(function (it, i) { tous.push({ code: th.code + '-' + (i + 1), theme: th.code, t: it[0], tags: it[1] }); }); });
var nb = function (tag) { return tous.filter(function (f) { return f.tags.split(' ').indexOf(tag) > -1; }).length; };
document.getElementById('stats').innerHTML = [['Fiches', tous.length, 'bg-white'], ['Quiz', nb('Q'), 'bg-[#fef7e0]'], ['Labs', nb('LAB') + nb('LAB+'), 'bg-[#e6f4ea]'], ['Formations', R.formations.length, 'bg-[#eef0fd]'], ['Examens', R.examens.length + nb('EXAM'), 'bg-[#fce8e6]']].map(function (s, i) { return '<div class="' + (i ? 'border-l-4 ' : '') + 'border-black ' + s[2] + ' p-4"><dt class="font-mono text-sm font-semibold">' + s[0] + '</dt><dd class="text-4xl font-extrabold">' + s[1] + '</dd></div>'; }).join('');
var sel = document.getElementById('b-theme'); R.themes.forEach(function (th) { var o = document.createElement('option'); o.value = th.code; o.textContent = th.code + ' · ' + th.nom; sel.appendChild(o); });
function garde(t, tags) {
var q = document.getElementById('b-recherche').value.toLowerCase(), tg = tags.split(' ');
if (q && t.toLowerCase().indexOf(q) === -1) return false;
if (document.getElementById('b-vm').checked && tg.indexOf('HV') > -1) return false;
return actifs.every(function (a) { return tg.indexOf(a) > -1; });
}
function carte(code, t, tags, sous) {
return '<li class="flex flex-col gap-3 border border-[#e3e5e8] rounded-2xl bg-white p-4"><div class="flex items-start justify-between gap-2"><span class="border border-[#dadce0] rounded-lg bg-black px-1.5 font-mono text-xs font-semibold text-white">' + code + '</span>' + (sous ? '<span class="text-right text-xs font-bold">' + sous + '</span>' : '') + '</div><p class="flex-1 font-extrabold leading-snug">' + esc(t) + '</p><div class="flex flex-wrap gap-1">' + badgeS(t) + badges(tags) + '</div><div class="flex flex-wrap gap-2 border-t border-[#e3e5e8] pt-3"><a href="' + lien(tags) + '" class="border border-[#dadce0] rounded-lg bg-[#fef7e0] px-2 py-0.5 text-sm font-extrabold hover:bg-[#0b57d0] hover:text-white">Ouvrir</a><button type="button" data-ajout class="border border-[#dadce0] rounded-lg bg-white px-2 py-0.5 text-sm font-extrabold hover:bg-[#e6f4ea]">+ Séquence</button></div></li>';
}
function rendre() {
var h = '', th = sel.value;
if (vue === 'themes') {
R.themes.filter(function (x) { return !th || x.code === th; }).forEach(function (x) {
var its = x.items.map(function (it, i) { return [x.code + '-' + (i + 1), it[0], it[1]]; }).filter(function (it) { return garde(it[1], it[2]); });
if (!its.length) return;
h += '<section class="mb-14"><h2 class="flex flex-wrap items-baseline gap-3 text-3xl font-extrabold tracking-tight"><span class="border border-[#e3e5e8] rounded-2xl bg-[#eef0fd] px-2 font-mono text-2xl">' + x.code + '</span>' + esc(x.nom) + '<span class="font-mono text-base font-semibold">' + its.length + ' fiche(s)</span></h2><ul class="mt-6 grid gap-5 sm:grid-cols-2 lg:grid-cols-4">' + its.map(function (it) { return carte(it[0], it[1], it[2]); }).join('') + '</ul></section>';
});
} else if (vue === 'formations') {
var gr = {}; R.formations.forEach(function (f) { (gr[f.groupe] = gr[f.groupe] || []).push(f); });
Object.keys(gr).forEach(function (g) {
var bloc = gr[g].map(function (f) {
var ch = f.chapitres.filter(function (c) { return garde(c[0], c[1]); });
if (!ch.length) return '';
return '<details class="border border-[#e3e5e8] rounded-2xl bg-white" open><summary class="cursor-pointer border-b border-[#e3e5e8] bg-[#e6f4ea] px-5 py-3 text-xl font-extrabold">' + esc(f.nom) + ' <span class="font-mono text-sm font-semibold">· ' + ch.length + ' chapitre(s)</span></summary><ul class="divide-y divide-[#e3e5e8]">' + ch.map(function (c, i) { return '<li class="flex flex-wrap items-center gap-3 px-5 py-3"><span class="font-mono text-sm font-semibold">' + (i + 1) + '</span><a href="' + (/IDE|GEN|LAB|EXAM/.test(c[1]) ? lien(c[1]) : U.urlFiche + '?chapitre=' + encodeURIComponent(c[0])) + '" class="min-w-0 flex-1 font-bold hover:underline">' + esc(c[0]) + '</a><span class="flex flex-wrap gap-1">' + badgeS(c[0]) + badges(c[1]) + '</span><button type="button" data-ajout class="border border-[#dadce0] rounded-lg px-2 text-xs font-extrabold hover:bg-[#e6f4ea]">+ Séquence</button></li>'; }).join('') + '</ul></details>';
}).join('');
if (bloc) h += '<section class="mb-12"><h2 class="text-3xl font-extrabold tracking-tight">' + esc(g) + '</h2><div class="mt-5 grid gap-6">' + bloc + '</div></section>';
});
} else {
var ex = R.examens.filter(function (e) { return garde(e[0] + ' ' + e[1], 'EXAM'); });
var ext = tous.filter(function (f) { return f.tags.split(' ').indexOf('EXAM') > -1 && garde(f.t, f.tags); });
h += '<section class="mb-12"><h2 class="text-3xl font-extrabold tracking-tight">Épreuves et examens blancs</h2><ul class="mt-6 grid gap-5 sm:grid-cols-2 lg:grid-cols-3">' + ex.map(function (e) { return '<li class="border border-[#e3e5e8] rounded-2xl bg-white p-5"><p class="font-mono text-sm font-semibold">⏱ ' + e[2] + '</p><p class="mt-2 text-xl font-extrabold">' + esc(e[0]) + '</p><p class="mt-1 font-semibold">' + esc(e[1]) + '</p><div class="mt-4 flex gap-2"><a href="' + U.urlExam + '" class="border border-[#dadce0] rounded-lg bg-[#ec3013] px-2 py-0.5 text-sm font-extrabold text-white">Aperçu</a><a href="' + U.urlBanque + '" class="border border-[#dadce0] rounded-lg bg-white px-2 py-0.5 text-sm font-extrabold hover:bg-[#fef7e0]">Personnaliser</a></div></li>'; }).join('') + '</ul></section>';
h += '<section><h2 class="text-3xl font-extrabold tracking-tight">Fiches avec mode examen</h2><ul class="mt-6 grid gap-5 sm:grid-cols-2 lg:grid-cols-4">' + ext.map(function (f) { return carte(f.code, f.t, f.tags); }).join('') + '</ul></section>';
}
box.innerHTML = h || '<p class="border border-[#e3e5e8] rounded-2xl bg-white p-6 text-xl font-extrabold">Aucune fiche ne correspond à ces filtres.</p>';
}
box.addEventListener('click', function (e) { var b = e.target.closest('[data-ajout]'); if (!b || b.disabled) return; seq++; document.getElementById('b-seq').textContent = seq; b.textContent = 'Ajoutée ✓'; b.disabled = true; b.classList.add('bg-[#e6f4ea]'); });
document.querySelectorAll('[data-vue]').forEach(function (b) { b.addEventListener('click', function () { vue = b.dataset.vue; document.querySelectorAll('[data-vue]').forEach(function (x) { x.setAttribute('aria-selected', x === b); }); rendre(); }); });
document.querySelectorAll('[data-tag]').forEach(function (b) { b.addEventListener('click', function () { var on = b.getAttribute('aria-pressed') !== 'true'; b.setAttribute('aria-pressed', on); actifs = Array.prototype.filter.call(document.querySelectorAll('[data-tag]'), function (x) { return x.getAttribute('aria-pressed') === 'true'; }).map(function (x) { return x.dataset.tag; }); rendre(); }); });
['b-recherche', 'b-theme', 'b-vm'].forEach(function (id) { document.getElementById(id).addEventListener('input', rendre); document.getElementById(id).addEventListener('change', rendre); });
rendre();
})();
</script>
@endsection