Paillasse/portail/resources/views/generateur.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

106 lines
12 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', 'Générateur de TD')
@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-[#fce8e6] px-3 py-1 text-sm font-bold">Générateur de TD</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">Des exercices à l’infini, corrigés tout de suite.</h1></div>
<p class="mt-6 max-w-3xl text-xl font-medium leading-relaxed">Chaque clic tire un nouvel exercice au hasard. La correction montre le calcul, étape par étape. En mode examen : 10 questions, chronométrées, notées sur 20.</p>
</div>
</section>
<div class="mx-auto grid max-w-7xl gap-10 px-5 py-12 lg:grid-cols-12 lg:px-10">
<aside class="grid content-start gap-4 lg:col-span-3">
<p class="font-extrabold">Type d’exercice</p>
<div class="grid gap-2" role="group" aria-label="Type d’exercice">
<button type="button" data-type="reseau" aria-pressed="true" class="border border-[#e3e5e8] rounded-2xl bg-[#e6f4ea] px-4 py-3 text-left font-extrabold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-2 aria-pressed:outline-black">Sous-réseaux</button>
<button type="button" data-type="binaire" aria-pressed="false" class="border border-[#e3e5e8] rounded-2xl bg-[#fef7e0] px-4 py-3 text-left font-extrabold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-2 aria-pressed:outline-black">Décimal → binaire</button>
<button type="button" data-type="hexa" aria-pressed="false" class="border border-[#e3e5e8] rounded-2xl bg-[#eef0fd] px-4 py-3 text-left font-extrabold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-2 aria-pressed:outline-black">Décimal → hexadécimal</button>
<button type="button" data-type="c2" aria-pressed="false" class="border border-[#e3e5e8] rounded-2xl bg-[#fce8e6] px-4 py-3 text-left font-extrabold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-2 aria-pressed:outline-black">Complément à 2</button>
<button type="button" data-type="chmod" aria-pressed="false" class="border border-[#e3e5e8] rounded-2xl bg-white px-4 py-3 text-left font-extrabold aria-pressed:outline aria-pressed:outline-4 aria-pressed:outline-offset-2 aria-pressed:outline-black">Droits chmod</button>
</div>
<div class="mt-4 border border-[#e3e5e8] rounded-2xl bg-black p-4 text-white">
<p class="font-extrabold">Mode examen</p>
<p class="mt-1 text-sm font-semibold">10 questions du type choisi, chrono, note sur 20.</p>
<button type="button" id="g-exam" class="mt-3 border-2 border-white bg-[#ec3013] px-3 py-1 font-extrabold">Lancer l’examen</button>
</div>
<p class="border border-[#dadce0] rounded-lg bg-white p-3 font-mono text-sm font-semibold">Série : <span id="g-serie">0</span> · Réussis : <span id="g-ok">0</span> / <span id="g-tot">0</span></p>
</aside>
<section class="lg:col-span-9" aria-live="polite">
<div id="g-barre" hidden class="mb-5 flex flex-wrap items-center gap-4 border border-[#e3e5e8] rounded-2xl bg-[#ec3013] px-5 py-3 font-extrabold text-white"><span>MODE EXAMEN</span><span>Question <span id="g-qn">1</span> / 10</span><span class="ml-auto font-mono text-xl" id="g-chrono">00:00</span></div>
<form id="g-form" class="border border-[#e3e5e8] rounded-2xl bg-white p-7">
<p id="g-titre" class="font-mono text-sm font-semibold"></p>
<p id="g-enonce" class="mt-3 text-3xl font-extrabold leading-tight"></p>
<div id="g-champs" class="mt-6 grid gap-4 sm:grid-cols-3"></div>
<div class="mt-6 flex flex-wrap gap-4"><button type="submit" class="inline-flex items-center gap-4 border border-[#e3e5e8] rounded-2xl bg-black px-6 py-4 text-lg font-extrabold text-white transition-transform hover:bg-[#ec3013]">Corriger</button><button type="button" id="g-new" class="inline-flex items-center gap-3 border border-[#e3e5e8] rounded-2xl bg-white px-5 py-3 font-extrabold hover:bg-[#fef7e0]">Nouvel exercice</button></div>
<div id="g-corr" hidden class="mt-6 border border-[#e3e5e8] rounded-2xl p-5"><p id="g-verdict" class="text-xl font-extrabold"></p><pre id="g-etapes" class="mt-3 whitespace-pre-wrap font-mono text-sm font-semibold"></pre></div>
</form>
<div id="g-fin" hidden class="mt-8 border border-[#e3e5e8] rounded-2xl bg-[#fef7e0] p-7"><p class="text-2xl font-extrabold">Examen terminé</p><p class="mt-2 font-mono text-7xl font-semibold"><span id="g-note">0</span> / 20</p><p id="g-temps" class="mt-2 text-lg font-bold"></p></div>
</section>
</div>
<script>
(function () {
var $ = function (id) { return document.getElementById(id); };
var type = 'reseau', cur = null, serie = 0, ok = 0, tot = 0, exam = null;
var r = function (a, b) { return a + Math.floor(Math.random() * (b - a + 1)); };
var ip = function (n) { return [n >>> 24, (n >>> 16) & 255, (n >>> 8) & 255, n & 255].join('.'); };
var b8 = function (n) { return (n & 255).toString(2).padStart(8, '0'); };
var G = {
reseau: function () {
var pref = [[10, r(0, 255)], [172, r(16, 31)], [192, 168]][r(0, 2)], a = ((pref[0] << 24) | (pref[1] << 16) | (r(0, 255) << 8) | r(1, 254)) >>> 0, c = r(20, 30);
var m = c === 0 ? 0 : (0xFFFFFFFF << (32 - c)) >>> 0, net = (a & m) >>> 0, bc = (net | (~m >>> 0)) >>> 0, h = Math.pow(2, 32 - c) - 2;
return { titre: 'Sous-réseaux · IPv4', enonce: ip(a) + '/' + c, champs: [['Adresse réseau', ip(net)], ['Diffusion', ip(bc)], ['Nombre d’hôtes', String(h)]],
etapes: 'Masque /' + c + ' = ' + ip(m) + '\nAdresse ET masque = ' + ip(net) + '\nBits d’hôte à 1 → diffusion ' + ip(bc) + '\nHôtes = 2^' + (32 - c) + ' − 2 = ' + h };
},
binaire: function () { var n = r(0, 255); return { titre: 'Conversion · décimal vers binaire (8 bits)', enonce: String(n), champs: [['En binaire', b8(n), function (v) { return /^[01]{1,8}$/.test(v) && parseInt(v, 2) === n; }]], etapes: [128, 64, 32, 16, 8, 4, 2, 1].map(function (p) { return p + ' → ' + ((n & p) ? '1' : '0'); }).join(' ') + '\n' + n + ' = ' + b8(n) }; },
hexa: function () { var n = r(0, 255), hx = n.toString(16).toUpperCase().padStart(2, '0'); return { titre: 'Conversion · décimal vers hexadécimal', enonce: String(n), champs: [['En hexadécimal', hx, function (v) { return parseInt(v.replace(/^0x/i, ''), 16) === n && /^(0x)?[0-9a-f]{1,2}$/i.test(v); }]], etapes: n + ' = ' + Math.floor(n / 16) + ' × 16 + ' + (n % 16) + '\n' + Math.floor(n / 16) + ' → ' + (Math.floor(n / 16)).toString(16).toUpperCase() + ', ' + (n % 16) + ' → ' + (n % 16).toString(16).toUpperCase() + '\nRésultat : ' + hx }; },
c2: function () { var n = r(-128, -1); return { titre: 'Complément à 2 · sur 8 bits', enonce: String(n), champs: [['Sur 8 bits', b8(n)]], etapes: '|' + n + '| = ' + (-n) + ' = ' + b8(-n) + '\nOn inverse les bits : ' + b8(~(-n)) + '\nOn ajoute 1 : ' + b8(n) }; },
chmod: function () {
var d = [r(4, 7), r(0, 7), r(0, 7)], s = function (x) { return (x & 4 ? 'r' : '-') + (x & 2 ? 'w' : '-') + (x & 1 ? 'x' : '-'); }, sym = d.map(s).join(''), oct = d.join('');
return Math.random() < .5
? { titre: 'Droits Linux · octal vers symbolique', enonce: 'chmod ' + oct, champs: [['Droits (ex. rwxr-x---)', sym]], etapes: d.map(function (x, i) { return ['propriétaire', 'groupe', 'autres'][i] + ' : ' + x + ' = ' + s(x); }).join('\n') + '\nr = 4, w = 2, x = 1' }
: { titre: 'Droits Linux · symbolique vers octal', enonce: sym, champs: [['En octal (ex. 750)', oct]], etapes: d.map(function (x, i) { return s(x) + ' = ' + x; }).join('\n') + '\nr = 4, w = 2, x = 1' };
}
};
function tirer() {
cur = G[type]();
$('g-titre').textContent = cur.titre; $('g-enonce').textContent = cur.enonce; $('g-corr').hidden = true;
$('g-champs').innerHTML = cur.champs.map(function (c, i) { return '<label class="grid gap-2 font-extrabold">' + c[0] + '<input data-i="' + i + '" autocomplete="off" class="w-full border border-[#e3e5e8] rounded-2xl bg-white px-4 py-3 text-lg font-semibold text-black placeholder:text-[#444141] focus:bg-[#f8f9fa] focus:outline-none font-mono"></label>'; }).join('');
var f = $('g-champs').querySelector('input'); if (f) f.focus();
if (exam) $('g-qn').textContent = exam.n + 1;
}
$('g-form').addEventListener('submit', function (e) {
e.preventDefault();
if (!$('g-corr').hidden) { if (exam) suivante(); return; }
var bons = 0;
cur.champs.forEach(function (c, i) {
var inp = $('g-champs').querySelector('[data-i="' + i + '"]'), v = inp.value.trim().replace(/\s/g, ''), juste = c[2] ? c[2](v) : v.toLowerCase() === c[1].toLowerCase();
if (juste) bons++; inp.classList.toggle('bg-[#e6f4ea]', juste); inp.classList.toggle('bg-[#FFD9E8]', !juste);
});
var tout = bons === cur.champs.length; tot++; if (tout) { ok++; serie++; } else serie = 0;
if (exam) exam.pts += bons / cur.champs.length;
$('g-serie').textContent = serie; $('g-ok').textContent = ok; $('g-tot').textContent = tot;
var cr = $('g-corr'); cr.hidden = false; cr.className = 'mt-6 border border-[#e3e5e8] rounded-2xl p-5 ' + (tout ? 'bg-[#e6f4ea]' : 'bg-[#FFD9E8]');
$('g-verdict').textContent = (tout ? 'Juste !' : 'Pas tout à fait.') + ' Réponse : ' + cur.champs.map(function (c) { return c[1]; }).join(' · ');
$('g-etapes').textContent = cur.etapes;
if (exam) $('g-new').textContent = exam.n === 9 ? 'Voir ma note' : 'Question suivante';
});
function suivante() { exam.n++; if (exam.n >= 10) return finir(); tirer(); }
function finir() {
clearInterval(exam.t); $('g-form').hidden = true; $('g-fin').hidden = false;
$('g-note').textContent = (Math.round(exam.pts * 2 * 10) / 10).toString().replace('.', ',');
$('g-temps').textContent = 'Temps : ' + $('g-chrono').textContent; exam = null; $('g-new').textContent = 'Nouvel exercice';
}
$('g-new').addEventListener('click', function () { if (exam) { if ($('g-corr').hidden) return; suivante(); } else tirer(); });
document.querySelectorAll('[data-type]').forEach(function (b) { b.addEventListener('click', function () { if (exam) return; type = b.dataset.type; document.querySelectorAll('[data-type]').forEach(function (x) { x.setAttribute('aria-pressed', x === b); }); tirer(); }); });
$('g-exam').addEventListener('click', function () {
var d = Date.now(); exam = { n: 0, pts: 0, t: setInterval(function () { var s = Math.floor((Date.now() - d) / 1000); $('g-chrono').textContent = String(Math.floor(s / 60)).padStart(2, '0') + ':' + String(s % 60).padStart(2, '0'); }, 1000) };
$('g-barre').hidden = false; $('g-fin').hidden = true; $('g-form').hidden = false; tirer();
});
tirer();
})();
</script>
@endsection