Paillasse/design/_backup-ludique/resources/views/ide.blade.php
2026-10-06 23:28:18 +02:00

285 lines
43 KiB
PHP
Raw Permalink 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.

<!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>