Paillasse/design/_backup-ludique/resources/views/ide.blade.php

286 lines
43 KiB
PHP
Raw Normal View History

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>IDE en ligne · Paillasse</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@500;700;800;900&family=IBM+Plex+Mono:wght@400;600&display=swap" rel="stylesheet">
@vite(['resources/css/app.css', 'resources/js/app.js'])
<style>
.etoile { clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%); }
.zigzag { background: linear-gradient(135deg, transparent 50%, #000 50% 62%, transparent 62%) 0 0 / 16px 16px, linear-gradient(45deg, transparent 50%, #000 50% 62%, transparent 62%) 0 0 / 16px 16px; }
@keyframes flotte { 0%,100% { transform: translateY(0) rotate(var(--r, 0deg)); } 50% { transform: translateY(-10px) rotate(var(--r, 0deg)); } }
.flotte { animation: flotte 6s ease-in-out infinite; }
@keyframes clignote { 50% { opacity: 0; } }
.curseur { animation: clignote 1s steps(1) infinite; }
@media (prefers-reduced-motion: reduce) { .flotte, .curseur { animation: none; } }
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css">
<script defer src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js"></script>
<script defer src="{{ asset('js/paillasse-terminal.js') }}"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/lib/codemirror.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/lib/codemirror.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/python/python.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/sql/sql.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/xml/xml.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/javascript/javascript.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/css/css.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/htmlmixed/htmlmixed.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/shell/shell.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/addon/edit/matchbrackets.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/addon/edit/closebrackets.min.js"></script>
<style>
.CodeMirror { height: 100%; font-family: "IBM Plex Mono", monospace; font-size: 15px; background: #13233A; color: #E8EDF5; }
.CodeMirror-gutters { background: #0D192B; border-right: 4px solid #000; }
.CodeMirror-linenumber { color: #6B7F99; }
.CodeMirror-cursor { border-left: 2px solid #FFD23F; }
.CodeMirror-selected, .CodeMirror-focused .CodeMirror-selected { background: #2B4A73 !important; }
.CodeMirror-matchingbracket { color: #FFD23F !important; text-decoration: underline; }
.cm-s-default .cm-keyword { color: #FF6FB5; font-weight: 600; }
.cm-s-default .cm-string { color: #7CF2C0; }
.cm-s-default .cm-number { color: #FFD23F; }
.cm-s-default .cm-comment { color: #8DA2BF; font-style: italic; }
.cm-s-default .cm-def, .cm-s-default .cm-variable-2 { color: #A9C6FF; }
.cm-s-default .cm-builtin { color: #7CE3F2; }
.cm-s-default .cm-variable, .cm-s-default .cm-property { color: #E8EDF5; }
.cm-s-default .cm-operator { color: #FFD23F; }
.cm-s-default .cm-tag { color: #FF6FB5; } .cm-s-default .cm-attribute { color: #FFD23F; } .cm-s-default .cm-bracket { color: #9FB0C8; }
</style>
</head>
<body class="bg-white font-display text-black antialiased">
<header class="border-b-4 border-black">
<nav class="relative mx-auto flex max-w-[1600px] flex-wrap items-center justify-between gap-x-6 gap-y-4 px-5 py-5 lg:px-8 2xl:flex-nowrap" aria-label="Navigation principale">
<a href="{{ route('accueil') }}" class="flex items-center gap-3 text-2xl font-black tracking-tight">
<span class="relative inline-flex h-10 w-12 items-end" aria-hidden="true"><span class="absolute left-2 top-0 h-6 w-4 -rotate-6 border-2 border-black bg-[#7CF2C0]"></span><span class="absolute right-1 top-1 h-5 w-5 rounded-full border-2 border-black bg-[#FF6FB5]"></span><span class="relative h-3 w-full border-2 border-black bg-[#FFD23F] shadow-[3px_3px_0px_#000000]"></span></span>
Paillasse
</a>
<ul class="order-last flex w-full flex-wrap items-center gap-x-6 gap-y-2 whitespace-nowrap text-base font-bold md:flex-nowrap md:gap-x-8 2xl:order-none 2xl:w-auto 2xl:flex-1 2xl:justify-center">
<li><a href="{{ route('fonctionnalites') }}" @if(request()->routeIs('fonctionnalites')) aria-current="page" @endif class="border-2 border-transparent px-1 underline-offset-4 hover:underline aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F] aria-[current=page]:no-underline">Fonctionnalités</a></li>
<li>
<details class="group">
<summary class="flex cursor-pointer list-none items-center gap-1 border-2 border-transparent px-1 hover:underline group-open:border-black group-open:bg-[#FFD23F] [&::-webkit-details-marker]:hidden">Ressources <span aria-hidden="true" class="transition-transform group-open:rotate-180">▾</span></summary>
<div class="absolute inset-x-5 top-full z-50 mx-auto max-h-[80vh] max-w-[880px] overflow-y-auto border-4 border-black bg-white shadow-[8px_8px_0px_#000000] lg:inset-x-8">
<div class="grid gap-x-4 gap-y-6 p-5 sm:grid-cols-2 lg:grid-cols-4">
<div><p class="flex items-center gap-2 border-b-2 border-black pb-2 font-mono text-xs font-semibold uppercase tracking-wider"><span class="h-3 w-3 border-2 border-black" style="background:#FFD23F"></span>Espaces</p><ul class="mt-2 grid gap-0.5"><li><a href="{{ route('cockpit') }}" @if(request()->routeIs('cockpit')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Cockpit prof</a></li><li><a href="{{ route('espace.eleve') }}" @if(request()->routeIs('espace.eleve')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Espace élève</a></li><li><a href="{{ route('vie.scolaire') }}" @if(request()->routeIs('vie.scolaire')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Vie scolaire</a></li><li><a href="{{ route('academie') }}" @if(request()->routeIs('academie')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Académie</a></li></ul></div>
<div><p class="flex items-center gap-2 border-b-2 border-black pb-2 font-mono text-xs font-semibold uppercase tracking-wider"><span class="h-3 w-3 border-2 border-black" style="background:#7CF2C0"></span>Apprendre</p><ul class="mt-2 grid gap-0.5"><li><a href="{{ route('cours.index') }}" @if(request()->routeIs('cours.index')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Cours gratuits</a></li><li><a href="{{ route('labs.index') }}" @if(request()->routeIs('labs.index')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Labs sur VM</a></li><li><a href="{{ route('labs.web') }}" @if(request()->routeIs('labs.web')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Lab web</a></li><li><a href="{{ route('ide') }}" @if(request()->routeIs('ide')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">IDE en ligne</a></li><li><a href="{{ route('maison') }}" @if(request()->routeIs('maison')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Ressources à la maison</a></li></ul></div>
<div><p class="flex items-center gap-2 border-b-2 border-black pb-2 font-mono text-xs font-semibold uppercase tracking-wider"><span class="h-3 w-3 border-2 border-black" style="background:#C4B5FD"></span>Préparer</p><ul class="mt-2 grid gap-0.5"><li><a href="{{ route('bibliotheque') }}" @if(request()->routeIs('bibliotheque')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Bibliothèque du prof</a></li><li><a href="{{ route('banque') }}" @if(request()->routeIs('banque')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Banque de sujets</a></li><li><a href="{{ route('generateur') }}" @if(request()->routeIs('generateur')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Générateur de TD</a></li><li><a href="{{ route('td.imprimable') }}" @if(request()->routeIs('td.imprimable')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">TD à imprimer</a></li><li><a href="{{ route('quiz.projection') }}" @if(request()->routeIs('quiz.projection')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Quiz flash</a></li></ul></div>
<div><p class="flex items-center gap-2 border-b-2 border-black pb-2 font-mono text-xs font-semibold uppercase tracking-wider"><span class="h-3 w-3 border-2 border-black" style="background:#FF6FB5"></span>Évaluer et suivre</p><ul class="mt-2 grid gap-0.5"><li><a href="{{ route('sujet.creer') }}" @if(request()->routeIs('sujet.creer')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Créer un sujet</a></li><li><a href="{{ route('devoirs') }}" @if(request()->routeIs('devoirs')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Devoirs maison</a></li><li><a href="{{ route('correction') }}" @if(request()->routeIs('correction')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Correction assistée</a></li><li><a href="{{ route('fiche.eleve') }}" @if(request()->routeIs('fiche.eleve')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Fiche élève</a></li><li><a href="{{ route('messagerie') }}" @if(request()->routeIs('messagerie')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Messagerie</a></li><li><a href="{{ route('export.ent') }}" @if(request()->routeIs('export.ent')) aria-current="page" @endif class="block border-2 border-transparent px-2 py-1.5 text-[15px] font-bold hover:border-black aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F]">Export Pronote / ENT</a></li></ul></div>
</div>
<div class="flex flex-wrap items-center justify-between gap-3 border-t-4 border-black bg-[#FFFBEA] px-5 py-3"><p class="text-sm font-semibold">Vous débutez ? Tout est expliqué en vidéo.</p><a href="{{ route('aide') }}" class="border-2 border-black bg-white px-3 py-1 text-sm font-extrabold hover:bg-black hover:text-white">Aide et tutoriels →</a></div>
</div>
</details>
</li>
<li><a href="{{ route('etablissements') }}" @if(request()->routeIs('etablissements')) aria-current="page" @endif class="border-2 border-transparent px-1 underline-offset-4 hover:underline aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F] aria-[current=page]:no-underline">Établissements</a></li>
<li><a href="{{ route('tarifs') }}" @if(request()->routeIs('tarifs')) aria-current="page" @endif class="border-2 border-transparent px-1 underline-offset-4 hover:underline aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F] aria-[current=page]:no-underline">Tarifs</a></li>
<li><a href="{{ route('pourquoi') }}" @if(request()->routeIs('pourquoi')) aria-current="page" @endif class="border-2 border-transparent px-1 underline-offset-4 hover:underline aria-[current=page]:border-black aria-[current=page]:bg-[#FFD23F] aria-[current=page]:no-underline">Pourquoi ce projet ?</a></li>
</ul>
<div class="mr-4 flex shrink-0 items-center gap-8">
<a href="{{ route('login') }}" class="inline-flex h-12 min-w-[12rem] items-center border-2 border-black bg-white px-6 font-bold shadow-[4px_4px_0px_#000000] transition-transform hover:-translate-x-0.5 hover:-translate-y-0.5 hover:bg-[#FFD23F] hover:shadow-[6px_6px_0px_#000000] active:translate-x-1 active:translate-y-1 active:shadow-none">Se connecter</a>
<a href="{{ route('register') }}" class="relative inline-flex h-12 min-w-[12rem] items-center border-2 border-black bg-[#ec3013] px-6 font-bold text-white shadow-[4px_4px_0px_#000000] transition-transform hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-[6px_6px_0px_#000000] active:translate-x-1 active:translate-y-1 active:shadow-none"><span>Créer une classe</span><span class="pointer-events-none absolute -left-6 -top-4 -rotate-12" aria-hidden="true"><svg viewBox="0 0 44 30" class="h-7 w-10"><rect x="2" y="2" width="40" height="26" fill="#C4B5FD" stroke="#000" stroke-width="3"/><rect x="2" y="8" width="40" height="5" fill="#000"/><rect x="7" y="17" width="9" height="6" fill="#FFD23F" stroke="#000" stroke-width="2"/><path d="M4 28 L40 2" stroke="#ec3013" stroke-width="7" stroke-linecap="round"/><path d="M4 28 L40 2" stroke="#000" stroke-width="2" stroke-linecap="round"/></svg></span><span class="pointer-events-none absolute -bottom-4 -right-5 grid h-9 w-9 rotate-12 place-items-center rounded-full border-2 border-black bg-[#FFD23F] font-mono text-[11px] font-semibold leading-none text-black shadow-[2px_2px_0px_#000000] outline outline-1 outline-offset-[-5px] outline-black" aria-hidden="true">0€</span></a>
</div>
</nav>
</header>
<main>
<section class="border-b-4 border-black bg-white">
<div class="mx-auto flex max-w-[1500px] flex-wrap items-end gap-6 px-5 pb-8 pt-10 lg:px-8">
<div class="flex-1">
<p class="inline-block -rotate-2 border-2 border-black bg-[#FFD23F] px-3 py-1 text-sm font-extrabold uppercase tracking-wider shadow-[4px_4px_0px_#000000]">IDE en ligne</p>
<h1 class="mt-5 text-5xl font-black leading-[0.95] tracking-tighter sm:text-6xl">Coder, exécuter, vérifier. Dans le navigateur.</h1>
<p class="mt-4 max-w-3xl text-lg font-semibold">Python, SQL, HTML/CSS/JavaScript et Linux. Le code s’exécute vraiment, les tests disent tout de suite si c’est juste. Ton code est gardé sur cet appareil.</p>
</div>
</div>
</section>
<div class="mx-auto grid max-w-[1500px] gap-6 px-5 py-8 lg:grid-cols-12 lg:px-8">
<nav class="lg:col-span-2" aria-label="Exercices">
<div class="grid gap-5 lg:sticky lg:top-4">
<div>
<p class="border-2 border-black bg-[#FFD23F] px-2 py-1 font-mono text-sm font-semibold">Python</p>
<ul class="mt-2 grid gap-1"><li><button type="button" data-ex="py-pair" class="w-full border-2 border-black bg-white px-2 py-2 text-left text-sm font-bold hover:bg-[#FFFBEA] aria-[current=true]:bg-black aria-[current=true]:text-white"><span data-etat class="mr-1 font-mono"></span>Nombre pair ou impair</button></li><li><button type="button" data-ex="py-somme" class="w-full border-2 border-black bg-white px-2 py-2 text-left text-sm font-bold hover:bg-[#FFFBEA] aria-[current=true]:bg-black aria-[current=true]:text-white"><span data-etat class="mr-1 font-mono"></span>Somme d’une liste</button></li><li><button type="button" data-ex="py-voyelles" class="w-full border-2 border-black bg-white px-2 py-2 text-left text-sm font-bold hover:bg-[#FFFBEA] aria-[current=true]:bg-black aria-[current=true]:text-white"><span data-etat class="mr-1 font-mono"></span>Compter les voyelles</button></li></ul>
</div>
<div>
<p class="border-2 border-black bg-[#C4B5FD] px-2 py-1 font-mono text-sm font-semibold">SQL</p>
<ul class="mt-2 grid gap-1"><li><button type="button" data-ex="sql-select" class="w-full border-2 border-black bg-white px-2 py-2 text-left text-sm font-bold hover:bg-[#FFFBEA] aria-[current=true]:bg-black aria-[current=true]:text-white"><span data-etat class="mr-1 font-mono"></span>Filtrer et trier</button></li><li><button type="button" data-ex="sql-group" class="w-full border-2 border-black bg-white px-2 py-2 text-left text-sm font-bold hover:bg-[#FFFBEA] aria-[current=true]:bg-black aria-[current=true]:text-white"><span data-etat class="mr-1 font-mono"></span>Moyenne par classe</button></li></ul>
</div>
<div>
<p class="border-2 border-black bg-[#FF6FB5] px-2 py-1 font-mono text-sm font-semibold">Web</p>
<ul class="mt-2 grid gap-1"><li><button type="button" data-ex="web-bouton" class="w-full border-2 border-black bg-white px-2 py-2 text-left text-sm font-bold hover:bg-[#FFFBEA] aria-[current=true]:bg-black aria-[current=true]:text-white"><span data-etat class="mr-1 font-mono"></span>Un bouton qui répond</button></li></ul>
</div>
<div>
<p class="border-2 border-black bg-[#7CF2C0] px-2 py-1 font-mono text-sm font-semibold">Linux</p>
<ul class="mt-2 grid gap-1"><li><button type="button" data-ex="bash-tp" class="w-full border-2 border-black bg-white px-2 py-2 text-left text-sm font-bold hover:bg-[#FFFBEA] aria-[current=true]:bg-black aria-[current=true]:text-white"><span data-etat class="mr-1 font-mono"></span>Préparer son dossier de TP</button></li></ul>
</div>
</div>
</nav>
<section class="flex min-w-0 flex-col border-4 border-black bg-[#13233A] shadow-[8px_8px_0px_#000000] lg:col-span-7" aria-label="Éditeur">
<div class="flex flex-wrap items-center gap-3 border-b-4 border-black bg-black px-3 py-2 text-white">
<span class="flex gap-1.5"><span class="h-3 w-3 bg-[#ec3013]"></span><span class="h-3 w-3 bg-[#FFD23F]"></span><span class="h-3 w-3 bg-[#7CF2C0]"></span></span>
<span id="fichier" class="font-mono text-sm font-semibold">main.py</span>
<span class="ml-auto flex flex-wrap gap-2">
<button type="button" id="b-reset" class="border-2 border-white px-3 py-1 text-sm font-extrabold hover:bg-white hover:text-black">Réinitialiser</button>
<button type="button" id="b-run" class="border-2 border-white bg-[#7CF2C0] px-3 py-1 text-sm font-extrabold text-black">▶ Exécuter</button>
<button type="button" id="b-test" class="border-2 border-white bg-[#FFD23F] px-3 py-1 text-sm font-extrabold text-black">✓ Vérifier</button>
</span>
</div>
<div id="editeur" class="h-[440px]"></div>
<div class="border-t-4 border-black bg-[#0D192B]">
<div class="flex gap-0 border-b-2 border-black text-sm font-extrabold" role="tablist">
<button type="button" role="tab" data-onglet="sortie" aria-selected="true" class="border-r-2 border-black px-4 py-2 text-white aria-selected:bg-[#FFD23F] aria-selected:text-black">Sortie</button>
<button type="button" role="tab" data-onglet="apercu" aria-selected="false" class="border-r-2 border-black px-4 py-2 text-white aria-selected:bg-[#FFD23F] aria-selected:text-black">Aperçu web</button>
<button type="button" role="tab" data-onglet="terminal" aria-selected="false" class="border-r-2 border-black px-4 py-2 text-white aria-selected:bg-[#FFD23F] aria-selected:text-black">Terminal</button>
</div>
<pre id="p-sortie" data-panneau="sortie" class="h-[220px] overflow-auto whitespace-pre-wrap p-4 font-mono text-sm text-[#E8EDF5]">Clique sur ▶ Exécuter pour lancer ton code.</pre>
<div data-panneau="apercu" hidden class="h-[220px] bg-white"><iframe id="p-apercu" title="Aperçu de la page" class="h-full w-full" sandbox="allow-scripts allow-same-origin"></iframe></div>
<div data-panneau="terminal" hidden class="h-[220px] p-2"><div id="p-terminal" class="h-full"></div></div>
</div>
</section>
<aside class="grid content-start gap-6 lg:col-span-3">
<section class="border-4 border-black bg-white p-5 shadow-[6px_6px_0px_#000000]" aria-labelledby="c-titre">
<p id="c-cursus" class="font-mono text-xs font-semibold"></p>
<h2 id="c-titre" class="mt-1 text-2xl font-black leading-tight"></h2>
<p id="c-consigne" class="mt-3 font-medium leading-relaxed [&_code]:border-2 [&_code]:border-black [&_code]:bg-[#FFD23F] [&_code]:px-1 [&_code]:font-mono [&_code]:text-sm"></p>
</section>
<section class="border-4 border-black bg-[#7CF2C0] p-5 shadow-[6px_6px_0px_#000000]" aria-labelledby="t-tests">
<h2 id="t-tests" class="text-xl font-black">Tests</h2>
<ul id="tests" class="mt-3 grid gap-2 font-mono text-sm font-semibold"><li>Clique sur ✓ Vérifier.</li></ul>
<p id="score" hidden class="mt-4 border-4 border-black bg-white px-3 py-2 text-lg font-black"></p>
</section>
<section class="border-4 border-black bg-[#C4B5FD] p-5 shadow-[6px_6px_0px_#000000]"><h2 class="text-xl font-black">Pour aller plus loin</h2><p class="mt-1 text-sm font-semibold">Plateformes externes pour s’entraîner encore.</p><div class="mt-3"><ul class="grid gap-2"><li><a href="https://www.hackerrank.com/domains/python" target="_blank" rel="noopener" class="flex items-center justify-between gap-3 border-2 border-black bg-white px-3 py-2 font-bold hover:bg-[#FFD23F]"><span>HackerRank · Python</span><span class="font-mono" aria-hidden="true">↗</span></a></li><li><a href="https://www.hackerrank.com/domains/sql" target="_blank" rel="noopener" class="flex items-center justify-between gap-3 border-2 border-black bg-white px-3 py-2 font-bold hover:bg-[#FFD23F]"><span>HackerRank · SQL</span><span class="font-mono" aria-hidden="true">↗</span></a></li><li><a href="https://www.hackerrank.com/domains/shell" target="_blank" rel="noopener" class="flex items-center justify-between gap-3 border-2 border-black bg-white px-3 py-2 font-bold hover:bg-[#FFD23F]"><span>HackerRank · Linux Shell</span><span class="font-mono" aria-hidden="true">↗</span></a></li><li><a href="https://www.datacamp.com/category/python" target="_blank" rel="noopener" class="flex items-center justify-between gap-3 border-2 border-black bg-white px-3 py-2 font-bold hover:bg-[#FFD23F]"><span>DataCamp · Python</span><span class="font-mono" aria-hidden="true">↗</span></a></li><li><a href="https://www.datacamp.com/category/sql" target="_blank" rel="noopener" class="flex items-center justify-between gap-3 border-2 border-black bg-white px-3 py-2 font-bold hover:bg-[#FFD23F]"><span>DataCamp · SQL</span><span class="font-mono" aria-hidden="true">↗</span></a></li></ul></div></section>
<p class="border-2 border-black bg-[#FFD23F] p-3 text-sm font-bold">Python tourne dans ton navigateur (Pyodide), SQL aussi (SQLite). Rien à installer.</p>
</aside>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
var EX = [{"id":"py-pair","lang":"python","groupe":"Python","titre":"Nombre pair ou impair","cursus":"NSI · Première","consigne":"Complète la fonction <code>est_pair(n)<\/code> : elle renvoie <code>True<\/code> si <code>n<\/code> est pair, <code>False<\/code> sinon. Astuce : l’opérateur <code>%<\/code> donne le reste de la division.","code":"def est_pair(n):\n # Renvoie True si n est pair, False sinon\n pass\n\n\nprint(est_pair(4))\nprint(est_pair(7))\n","tests":["est_pair(4) == True","est_pair(7) == False","est_pair(0) == True","est_pair(-2) == True"]},{"id":"py-somme","lang":"python","groupe":"Python","titre":"Somme d’une liste","cursus":"NSI · Première","consigne":"Écris <code>somme(liste)<\/code> qui renvoie la somme des éléments, <strong>sans utiliser<\/strong> la fonction <code>sum()<\/code>. Utilise une boucle <code>for<\/code>.","code":"def somme(liste):\n total = 0\n # Parcours la liste et ajoute chaque élément à total\n return total\n\n\nprint(somme([3, 5, 2]))\n","tests":["somme([3, 5, 2]) == 10","somme([]) == 0","somme([-1, 1]) == 0","somme([100]) == 100"]},{"id":"py-voyelles","lang":"python","groupe":"Python","titre":"Compter les voyelles","cursus":"BTS CIEL · Informatique","consigne":"Écris <code>nb_voyelles(mot)<\/code> qui compte les voyelles (a, e, i, o, u, y) d’un mot en minuscules.","code":"def nb_voyelles(mot):\n voyelles = \"aeiouy\"\n # À toi de jouer\n return 0\n\n\nprint(nb_voyelles(\"reseau\"))\n","tests":["nb_voyelles(\"reseau\") == 4","nb_voyelles(\"bcd\") == 0","nb_voyelles(\"python\") == 2"]},{"id":"sql-select","lang":"sql","groupe":"SQL","titre":"Filtrer et trier","cursus":"NSI · Terminale","consigne":"Table <code>eleves(nom, classe, moyenne)<\/code>. Affiche le <strong>nom<\/strong> et la <strong>moyenne<\/strong> des élèves de <code>SIO1<\/code> qui ont au moins 12, du meilleur au moins bon.","code":"SELECT nom, moyenne\nFROM eleves\n-- Complète la requête\n;\n","solution":"SELECT nom, moyenne FROM eleves WHERE classe = 'SIO1' AND moyenne >= 12 ORDER BY moyenne DESC;"},{"id":"sql-group","lang":"sql","groupe":"SQL","titre":"Moyenne par classe","cursus":"BTS SIO · Bloc 1","consigne":"Affiche chaque <strong>classe<\/strong> et la <strong>moyenne<\/strong> de ses élèves arrondie à 1 décimale, triées par classe. Utilise <code>GROUP BY<\/code> et <code>ROUND(AVG(...), 1)<\/code>.","code":"SELECT classe\nFROM eleves\n;\n","solution":"SELECT classe, ROUND(AVG(moyenne), 1) FROM eleves GROUP BY classe ORDER BY classe;"},{"id":"web-bouton","lang":"web","groupe":"Web","titre":"Un bouton qui répond","cursus":"NSI · Première","consigne":"Quand on clique sur le bouton, le paragraphe <code>#message<\/code> doit afficher <strong>Bonjour !<\/strong>. Ajoute un écouteur <code>click<\/code> en JavaScript.","code":"<!doctype html>\n<html lang=\"fr\">\n<body style=\"font-family: sans-serif; padding: 20px\">\n <button id=\"bouton\">Clique-moi<\/button>\n <p id=\"message\"><\/p>\n\n <script>\n const bouton = document.getElementById(\"bouton\");\n // Ajoute un écouteur de clic ici\n <\/script>\n<\/body>\n<\/html>\n"},{"id":"bash-tp","lang":"bash","groupe":"Linux","titre":"Préparer son dossier de TP","cursus":"BTS SIO · Bloc 2","consigne":"Dans le terminal : crée un dossier <code>tp<\/code>, entre dedans, puis crée un fichier <code>script.sh<\/code> qui contient <code>echo Bonjour<\/code>.","code":"# Cet exercice se fait dans le terminal, onglet ci-dessous.\n# Commandes utiles : mkdir, cd, echo \"...\" > fichier, cat\n"}];
var SETUP = "CREATE TABLE eleves (id INTEGER PRIMARY KEY, nom TEXT, classe TEXT, moyenne REAL);" +
"INSERT INTO eleves (nom, classe, moyenne) VALUES ('Léa', 'SIO1', 15.5), ('Yanis', 'SIO1', 11), ('Nora', 'SIO1', 12), ('Tom', 'SIO2', 14), ('Inès', 'SIO2', 9.5), ('Sami', 'SIO1', 13.25), ('Chloé', 'SIO2', 17);";
var MODES = { python: 'python', sql: 'text/x-sql', web: 'htmlmixed', bash: 'shell' };
var FICH = { python: 'main.py', sql: 'requete.sql', web: 'index.html', bash: 'notes.sh' };
var $ = function (id) { return document.getElementById(id); };
var cur = null, py = null, SQL = null, shell = null, termOk = false;
var ed = CodeMirror($('editeur'), { lineNumbers: true, indentUnit: 4, matchBrackets: true, autoCloseBrackets: true, viewportMargin: Infinity });
ed.on('change', function () { if (cur) localStorage.setItem('paillasse-ide-' + cur.id, ed.getValue()); });
function onglet(n) {
document.querySelectorAll('[data-onglet]').forEach(function (b) { b.setAttribute('aria-selected', b.dataset.onglet === n); });
document.querySelectorAll('[data-panneau]').forEach(function (p) { p.hidden = p.dataset.panneau !== n; });
if (n === 'terminal' && !termOk) {
termOk = true;
PaillasseTerminal.mount($('p-terminal'), { user: 'eleve', host: 'ide', fontSize: 13, focus: true, onCommand: function (l, sh) { shell = sh; }, welcome: 'Terminal Paillasse · tape help' });
}
}
document.querySelectorAll('[data-onglet]').forEach(function (b) { b.addEventListener('click', function () { onglet(b.dataset.onglet); }); });
function sortie(t, err) { var p = $('p-sortie'); p.textContent = t; p.style.color = err ? '#FF9A8A' : '#E8EDF5'; }
function charger(id) {
cur = EX.filter(function (e) { return e.id === id; })[0];
document.querySelectorAll('[data-ex]').forEach(function (b) { b.setAttribute('aria-current', b.dataset.ex === id); });
$('c-titre').textContent = cur.titre; $('c-cursus').textContent = cur.groupe + ' · ' + cur.cursus; $('c-consigne').innerHTML = cur.consigne;
$('fichier').textContent = FICH[cur.lang];
ed.setOption('mode', MODES[cur.lang]);
ed.setValue(localStorage.getItem('paillasse-ide-' + id) || cur.code);
$('tests').innerHTML = '<li>Clique sur ✓ Vérifier.</li>'; $('score').hidden = true;
sortie(cur.lang === 'bash' ? 'Ouvre l’onglet Terminal pour cet exercice.' : 'Clique sur ▶ Exécuter pour lancer ton code.');
onglet(cur.lang === 'web' ? 'apercu' : cur.lang === 'bash' ? 'terminal' : 'sortie');
if (cur.lang === 'web') $('p-apercu').srcdoc = ed.getValue();
}
function chargerScript(url) { return new Promise(function (ok, ko) { var s = document.createElement('script'); s.src = url; s.onload = ok; s.onerror = ko; document.head.appendChild(s); }); }
function python() {
if (py) return Promise.resolve(py);
sortie('Chargement de Python… (quelques secondes, la première fois seulement)');
return chargerScript('https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js').then(function () { return loadPyodide(); }).then(function (p) { py = p; return p; });
}
function sqlite() {
if (SQL) return Promise.resolve(SQL);
return chargerScript('https://cdn.jsdelivr.net/npm/sql.js@1.10.3/dist/sql-wasm.js').then(function () { return initSqlJs({ locateFile: function (f) { return 'https://cdn.jsdelivr.net/npm/sql.js@1.10.3/dist/' + f; } }); }).then(function (s) { SQL = s; return s; });
}
function base() { var db = new SQL.Database(); db.run(SETUP); return db; }
function tableau(res) {
if (!res.length) return '(aucune ligne)';
var r = res[res.length - 1], w = r.columns.map(function (c, i) { return Math.max(String(c).length, Math.max.apply(null, r.values.map(function (v) { return String(v[i]).length; }))); });
var ligne = function (a) { return a.map(function (x, i) { return String(x).padEnd(w[i]); }).join(' │ '); };
return ligne(r.columns) + '\n' + w.map(function (n) { return '─'.repeat(n); }).join('─┼─') + '\n' + r.values.map(ligne).join('\n') + '\n\n' + r.values.length + ' ligne(s)';
}
function executer() {
var code = ed.getValue();
if (cur.lang === 'python') {
return python().then(function (p) {
var out = '';
p.setStdout({ batched: function (s) { out += s + '\n'; } }); p.setStderr({ batched: function (s) { out += s + '\n'; } });
try { p.runPython(code); sortie(out || '(aucune sortie)'); } catch (e) { sortie(out + String(e.message).split('\n').slice(-3).join('\n'), true); }
});
}
if (cur.lang === 'sql') return sqlite().then(function () { try { sortie(tableau(base().exec(code))); } catch (e) { sortie('Erreur SQL : ' + e.message, true); } });
if (cur.lang === 'web') { $('p-apercu').srcdoc = code; onglet('apercu'); return Promise.resolve(); }
onglet('terminal'); return Promise.resolve();
}
function afficher(res) {
var ok = res.filter(function (r) { return r[1]; }).length;
$('tests').innerHTML = res.map(function (r) { return '<li class="flex gap-2 border-2 border-black ' + (r[1] ? 'bg-white' : 'bg-[#FFD9E8]') + ' px-2 py-1"><span>' + (r[1] ? '✓' : '✗') + '</span><span>' + r[0] + '</span></li>'; }).join('');
var s = $('score'); s.hidden = false; s.textContent = ok === res.length ? 'Bravo, tout est juste ! ' + ok + ' / ' + res.length : ok + ' / ' + res.length + ' tests réussis';
var b = document.querySelector('[data-ex="' + cur.id + '"] [data-etat]'); if (b) b.textContent = ok === res.length ? '✓' : '';
}
function verifier() {
var code = ed.getValue();
if (cur.lang === 'python') return python().then(function (p) {
try { p.runPython(code); } catch (e) { return afficher([['Le code s’exécute sans erreur', false]]); }
afficher(cur.tests.map(function (t) { var ok = false; try { ok = p.runPython(t) === true; } catch (e) {} return [t, ok]; }));
});
if (cur.lang === 'sql') return sqlite().then(function () {
var attendu = JSON.stringify(base().exec(cur.solution)[0].values), obtenu = null;
try { var r = base().exec(code); obtenu = r.length ? JSON.stringify(r[r.length - 1].values) : '[]'; } catch (e) { return afficher([['Requête valide', false]]); }
afficher([['Requête valide', true], ['Bonnes lignes, dans le bon ordre', obtenu === attendu]]);
});
if (cur.lang === 'web') {
var f = $('p-apercu'); f.srcdoc = code; onglet('apercu');
return new Promise(function (ok) { f.onload = function () {
var d = f.contentDocument, b = d && d.getElementById('bouton'), m = d && d.getElementById('message');
var r = [['Le bouton #bouton existe', !!b], ['Le paragraphe #message existe', !!m]];
if (b && m) { b.click(); r.push(['Le clic affiche « Bonjour ! »', /Bonjour/.test(m.textContent)]); }
afficher(r); ok();
}; });
}
onglet('terminal');
var h = shell ? shell.home : '/home/eleve', f2 = shell && shell.files.get(h + '/tp/script.sh');
afficher([['Le dossier ~/tp existe', !!(shell && shell.dirs.has(h + '/tp'))], ['Le fichier ~/tp/script.sh existe', f2 !== undefined && f2 !== null], ['Il contient echo Bonjour', !!f2 && /echo\s+"?Bonjour/.test(f2)]]);
return Promise.resolve();
}
document.querySelectorAll('[data-ex]').forEach(function (b) { b.addEventListener('click', function () { charger(b.dataset.ex); }); });
$('b-run').addEventListener('click', function () { executer().catch(function (e) { sortie('Impossible de charger l’environnement : ' + e, true); }); });
$('b-test').addEventListener('click', function () { verifier().catch(function (e) { sortie('Impossible de charger l’environnement : ' + e, true); }); });
$('b-reset').addEventListener('click', function () { localStorage.removeItem('paillasse-ide-' + cur.id); ed.setValue(cur.code); });
ed.setOption('extraKeys', { 'Ctrl-Enter': function () { $('b-run').click(); }, 'Cmd-Enter': function () { $('b-run').click(); } });
var q = new URLSearchParams(location.search).get('ex');
charger(q && EX.some(function (e) { return e.id === q; }) ? q : EX[0].id);
});
</script>
</main>
<footer class="border-t-4 border-black bg-white">
<div class="mx-auto grid max-w-7xl gap-10 px-5 py-14 sm:grid-cols-2 lg:grid-cols-4 lg:px-10">
<div>
<p class="text-2xl font-black tracking-tight">Paillasse</p>
<p class="mt-3 font-medium">Cours, labs, IDE et examens en ligne, du lycée au master.</p>
</div>
<nav aria-label="Produit"><p class="font-mono text-sm font-semibold uppercase">Produit</p><ul class="mt-4 space-y-2 font-bold"><li><a href="{{ route('cockpit') }}" class="hover:underline">Cockpit prof</a></li><li><a href="{{ route('espace.eleve') }}" class="hover:underline">Espace élève</a></li><li><a href="{{ route('aide') }}" class="hover:underline">Aide et tutoriels</a></li><li><a href="{{ route('labs.index') }}" class="hover:underline">Labs</a></li><li><a href="{{ route('fonctionnalites') }}" class="hover:underline">Fonctionnalités</a></li><li><a href="{{ route('cours.index') }}" class="hover:underline">Cours gratuits</a></li><li><a href="{{ route('tarifs') }}" class="hover:underline">Tarifs</a></li><li><a href="{{ route('pourquoi') }}" class="hover:underline">Pourquoi ce projet ?</a></li></ul></nav>
<nav aria-label="Établissements"><p class="font-mono text-sm font-semibold uppercase">Établissements</p><ul class="mt-4 space-y-2 font-bold"><li><a href="{{ route('etablissements') }}" class="hover:underline">Équiper une classe</a></li><li><a href="{{ route('login') }}" class="hover:underline">Se connecter</a></li></ul></nav>
<div class="self-start justify-self-start border-2 border-black bg-[#7CF2C0] px-4 py-3 font-mono text-sm font-semibold shadow-[4px_4px_0px_#000000]">● Tous les services fonctionnent</div>
</div>
<div class="border-t-4 border-black bg-white">
<ul class="mx-auto flex max-w-7xl flex-wrap items-center justify-center gap-8 px-5 py-8 lg:px-10" aria-label="Engagements">
<li class="grid h-40 w-40 shrink-0 -rotate-3 place-items-center rounded-full border-4 border-black bg-white text-center shadow-[4px_4px_0px_#000000] outline outline-2 outline-offset-[-10px] outline-black"><span class="flex max-w-[6.5rem] flex-col items-center gap-1.5"><span class="text-[10px] font-extrabold uppercase tracking-wide leading-none">Hébergé en</span><span class="flex h-7 w-11 border-2 border-black" aria-hidden="true"><span class="flex-1 bg-[#0055A4]"></span><span class="flex-1 bg-white"></span><span class="flex-1 bg-[#EF4135]"></span></span><span class="text-[10px] font-bold leading-tight">France</span></span></li>
<li class="grid h-40 w-40 shrink-0 rotate-3 place-items-center rounded-full border-4 border-black bg-[#FFD23F] text-center shadow-[4px_4px_0px_#000000] outline outline-2 outline-offset-[-10px] outline-black"><span class="flex max-w-[6.5rem] flex-col items-center gap-1.5"><span class="text-[10px] font-extrabold uppercase tracking-wide leading-none">Données</span><span class="font-mono text-3xl font-semibold leading-none">RGPD</span><span class="text-[10px] font-bold leading-tight">minimisées</span></span></li>
<li class="grid h-40 w-40 shrink-0 -rotate-6 place-items-center rounded-full border-4 border-black bg-[#7CF2C0] text-center shadow-[4px_4px_0px_#000000] outline outline-2 outline-offset-[-10px] outline-black"><span class="flex max-w-[6.5rem] flex-col items-center gap-1.5"><span class="text-[10px] font-extrabold uppercase tracking-wide leading-none">Référentiels</span><span class="font-mono text-3xl font-semibold leading-none">20</span><span class="text-[10px] font-bold leading-tight">cursus couverts</span></span></li>
<li class="grid h-40 w-40 shrink-0 rotate-6 place-items-center rounded-full border-4 border-black bg-[#FF6FB5] text-center shadow-[4px_4px_0px_#000000] outline outline-2 outline-offset-[-10px] outline-black"><span class="flex max-w-[6.5rem] flex-col items-center gap-1.5"><span class="text-[10px] font-extrabold uppercase tracking-wide leading-none">Essai</span><span class="font-mono text-3xl font-semibold leading-none">0 €</span><span class="text-[10px] font-bold leading-tight">sans CB</span></span></li>
</ul>
</div>
<div class="border-t-4 border-black bg-[#FFD23F]">
<div class="mx-auto flex max-w-7xl flex-wrap items-center gap-x-6 gap-y-2 px-5 py-4 lg:px-10"><p class="flex-1 font-extrabold">Fait avec <span class="text-[#ec3013]" aria-label="amour">♥</span> par Adam Belkacemi</p><nav aria-label="Informations légales"><ul class="flex flex-wrap gap-x-4 gap-y-1 text-sm font-bold"><li><a href="{{ route('legal') }}#mentions" class="underline underline-offset-4">Mentions légales</a></li><li><a href="{{ route('legal') }}#cgu" class="underline underline-offset-4">CGU</a></li><li><a href="{{ route('legal') }}#confidentialite" class="underline underline-offset-4">Confidentialité et RGPD</a></li><li><a href="{{ route('legal') }}#cookies" class="underline underline-offset-4">Cookies</a></li><li><a href="{{ route('legal') }}#sous-traitance" class="underline underline-offset-4">Sous-traitance</a></li></ul></nav></div>
</div>
</footer>
</body>
</html>