cours 16-10-26
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,366 @@
|
||||
/* =====================================================================
|
||||
script.js — le "moteur" de la page
|
||||
---------------------------------------------------------------------
|
||||
Ce fichier fait fonctionner les éditeurs de code interactifs.
|
||||
Il est volontairement TRÈS commenté : c'est lui-même un exemple
|
||||
de vrai code JavaScript. Une fois que tu auras lu les chapitres,
|
||||
reviens le lire : tu devrais tout comprendre !
|
||||
|
||||
Ce qu'il fait :
|
||||
1. Trouver tous les éditeurs (.bac) de la page
|
||||
2. Quand on clique « Exécuter », lancer le code écrit dedans
|
||||
3. Intercepter les console.log pour les afficher DANS la page
|
||||
4. Traduire les erreurs en français pour aider à comprendre
|
||||
5. Surligner dans le menu le chapitre en cours de lecture
|
||||
6. Gérer le bouton clair / sombre
|
||||
===================================================================== */
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
"use strict" active le MODE STRICT : JavaScript devient plus sévère
|
||||
et signale des erreurs qu'il ignorerait sinon (ex : utiliser une
|
||||
variable jamais déclarée). C'est une bonne habitude.
|
||||
--------------------------------------------------------------------- */
|
||||
"use strict";
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
1. OUTIL : transformer n'importe quelle valeur en texte lisible
|
||||
=====================================================================
|
||||
console.log("bonjour") est facile à afficher. Mais comment afficher
|
||||
un tableau [1, 2, 3] ou un objet { nom: "Ada" } ? Cette fonction
|
||||
regarde le TYPE de la valeur et fabrique un texte adapté.
|
||||
|
||||
Elle renvoie un élément HTML <span> coloré selon le type.
|
||||
--------------------------------------------------------------------- */
|
||||
function formaterValeur(valeur, profondeur = 0) {
|
||||
// typeof renvoie le type de la valeur sous forme de texte :
|
||||
// "string", "number", "boolean", "undefined", "object", "function"…
|
||||
const type = typeof valeur;
|
||||
|
||||
// Un "texte" (string) : au premier niveau on l'affiche tel quel,
|
||||
// à l'intérieur d'un tableau/objet on l'entoure de guillemets.
|
||||
if (type === "string") {
|
||||
return profondeur === 0 ? valeur : `"${valeur}"`;
|
||||
}
|
||||
|
||||
if (type === "number" || type === "boolean" || type === "bigint") {
|
||||
return String(valeur); // String(42) → "42"
|
||||
}
|
||||
|
||||
if (valeur === undefined) return "undefined";
|
||||
if (valeur === null) return "null";
|
||||
|
||||
if (type === "function") {
|
||||
// valeur.name = le nom de la fonction (vide si anonyme)
|
||||
return `ƒ ${valeur.name || "(anonyme)"}()`;
|
||||
}
|
||||
|
||||
// On évite de descendre trop profond (objets imbriqués à l'infini)
|
||||
if (profondeur > 3) return "…";
|
||||
|
||||
// Un tableau : Array.isArray est LA façon fiable de le reconnaître,
|
||||
// car typeof [] renvoie "object" (une bizarrerie historique).
|
||||
if (Array.isArray(valeur)) {
|
||||
// .map transforme chaque élément, .join colle le tout avec ", "
|
||||
const morceaux = valeur.map(v => formaterValeur(v, profondeur + 1));
|
||||
return `[${morceaux.join(", ")}]`;
|
||||
}
|
||||
|
||||
// Un élément HTML (ex : document.querySelector("h1"))
|
||||
if (valeur instanceof Element) {
|
||||
const classes = valeur.className ? "." + String(valeur.className).split(" ").join(".") : "";
|
||||
return `<${valeur.tagName.toLowerCase()}${valeur.id ? "#" + valeur.id : ""}${classes}>`;
|
||||
}
|
||||
|
||||
// Une Promise (chapitre asynchrone)
|
||||
if (valeur instanceof Promise) return "Promise { … }";
|
||||
|
||||
// Une erreur
|
||||
if (valeur instanceof Error) return `${valeur.name}: ${valeur.message}`;
|
||||
|
||||
// Un objet "classique" : { cle: valeur, ... }
|
||||
if (type === "object") {
|
||||
// Le nom du "constructeur" : utile pour les objets créés avec une classe
|
||||
const nomClasse = valeur.constructor && valeur.constructor.name !== "Object"
|
||||
? valeur.constructor.name + " "
|
||||
: "";
|
||||
// Object.entries({a:1, b:2}) → [["a",1], ["b",2]]
|
||||
const paires = Object.entries(valeur).map(
|
||||
([cle, v]) => `${cle}: ${formaterValeur(v, profondeur + 1)}`
|
||||
);
|
||||
return `${nomClasse}{ ${paires.join(", ")} }`;
|
||||
}
|
||||
|
||||
return String(valeur);
|
||||
}
|
||||
|
||||
/* Renvoie le nom d'une classe CSS selon le type, pour colorer la sortie */
|
||||
function classeCouleur(valeur) {
|
||||
if (typeof valeur === "number") return "t-nombre";
|
||||
if (typeof valeur === "string") return "t-texte";
|
||||
if (typeof valeur === "boolean") return "t-booleen";
|
||||
if (valeur === null || valeur === undefined) return "t-vide";
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
2. OUTIL : traduire les erreurs JavaScript en français
|
||||
=====================================================================
|
||||
Quand le code plante, JavaScript lance une "erreur" avec un nom
|
||||
(ReferenceError, TypeError…) et un message en anglais.
|
||||
On ajoute une petite explication pour débutant.
|
||||
--------------------------------------------------------------------- */
|
||||
const EXPLICATIONS_ERREURS = {
|
||||
// Un objet utilisé comme un "dictionnaire" : nom de l'erreur → explication
|
||||
ReferenceError: "Tu utilises un nom (variable ou fonction) qui n'existe pas. Faute de frappe ? Variable déclarée plus bas ou dans un autre bloc { } ?",
|
||||
TypeError: "Tu fais une opération impossible sur ce type de valeur (ex : appeler quelque chose qui n'est pas une fonction, lire une propriété de undefined, modifier une const).",
|
||||
SyntaxError: "Le code est mal écrit : il manque sans doute une parenthèse, une accolade, un guillemet ou une virgule.",
|
||||
RangeError: "Une valeur est hors des limites autorisées (souvent : une fonction qui s'appelle elle-même à l'infini).",
|
||||
};
|
||||
|
||||
function expliquerErreur(erreur) {
|
||||
// L'opérateur ?? ("coalescence des nuls") donne la valeur de droite
|
||||
// si celle de gauche est null ou undefined.
|
||||
return EXPLICATIONS_ERREURS[erreur.name] ?? "";
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
3. UNE "FAUSSE" CONSOLE pour chaque éditeur
|
||||
=====================================================================
|
||||
Le vrai console.log écrit dans les outils de développement (F12).
|
||||
On fabrique un objet qui a les MÊMES méthodes (log, warn, error…)
|
||||
mais qui écrit dans la zone .sortie de l'éditeur.
|
||||
|
||||
C'est une "fabrique" : une fonction qui renvoie un objet.
|
||||
--------------------------------------------------------------------- */
|
||||
function creerConsole(zoneSortie) {
|
||||
|
||||
// Fonction interne qui ajoute UNE ligne dans la zone de sortie.
|
||||
// "...valeurs" (paramètre du reste) récupère TOUS les arguments
|
||||
// dans un tableau : console.log("a", 1, true) → ["a", 1, true]
|
||||
function ecrire(niveau, ...valeurs) {
|
||||
const ligne = document.createElement("div"); // crée un <div> vide
|
||||
ligne.className = "ligne " + niveau;
|
||||
|
||||
// Pour chaque valeur, on crée un <span> coloré, séparés par un espace
|
||||
valeurs.forEach((valeur, index) => {
|
||||
if (index > 0) ligne.append(" ");
|
||||
const span = document.createElement("span");
|
||||
span.className = classeCouleur(valeur);
|
||||
// textContent (et pas innerHTML) : le texte est affiché tel quel,
|
||||
// une balise <b> s'affichera littéralement au lieu d'être interprétée.
|
||||
span.textContent = formaterValeur(valeur);
|
||||
ligne.append(span);
|
||||
});
|
||||
|
||||
zoneSortie.append(ligne); // ajoute dans la page
|
||||
zoneSortie.scrollTop = zoneSortie.scrollHeight; // défile tout en bas
|
||||
|
||||
// On envoie AUSSI dans la vraie console du navigateur (F12)
|
||||
console[niveau === "err" ? "error" : niveau === "warn" ? "warn" : "log"](...valeurs);
|
||||
}
|
||||
|
||||
// L'objet renvoyé : mêmes noms de méthodes que la vraie console
|
||||
return {
|
||||
log: (...v) => ecrire("", ...v),
|
||||
info: (...v) => ecrire("info", ...v),
|
||||
warn: (...v) => ecrire("warn", "⚠", ...v),
|
||||
error: (...v) => ecrire("err", "✖", ...v),
|
||||
clear: () => { zoneSortie.innerHTML = ""; },
|
||||
|
||||
// console.table : affiche un tableau d'objets sous forme de lignes
|
||||
table: (donnees) => {
|
||||
if (!Array.isArray(donnees)) return ecrire("", donnees);
|
||||
donnees.forEach((element, i) => ecrire("", `[${i}]`, element));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
4. EXÉCUTER LE CODE d'un éditeur
|
||||
=====================================================================
|
||||
Comment exécuter du texte comme si c'était du code ?
|
||||
On crée une fonction à partir d'une chaîne de caractères.
|
||||
|
||||
AsyncFunction est le "constructeur" des fonctions async : il permet
|
||||
d'utiliser "await" directement dans les exemples (chapitre 12).
|
||||
On passe à cette fonction nos propres "console" et "scene", qui
|
||||
REMPLACENT les vrais à l'intérieur du code exécuté.
|
||||
|
||||
⚠ Exécuter du texte comme du code est dangereux avec du texte venant
|
||||
d'inconnus. Ici, c'est TOI qui écris le code, donc pas de souci.
|
||||
--------------------------------------------------------------------- */
|
||||
const AsyncFunction = (async function () {}).constructor;
|
||||
|
||||
async function executer(bac) {
|
||||
// querySelector cherche le PREMIER élément correspondant au sélecteur CSS,
|
||||
// mais seulement À L'INTÉRIEUR de "bac".
|
||||
const editeur = bac.querySelector("textarea");
|
||||
const sortie = bac.querySelector(".sortie");
|
||||
const scene = bac.querySelector(".scene"); // peut être null s'il n'y en a pas
|
||||
|
||||
sortie.innerHTML = ""; // on efface l'ancienne sortie
|
||||
|
||||
// Si l'éditeur a une scène, on la remet dans son état de départ.
|
||||
// (dataset.initial = l'attribut data-initial, rempli au chargement)
|
||||
if (scene) scene.innerHTML = scene.dataset.initial;
|
||||
|
||||
const maConsole = creerConsole(sortie);
|
||||
|
||||
// try / catch : on "essaie" un bloc de code ; si une erreur survient,
|
||||
// au lieu de tout planter, on "attrape" l'erreur dans catch.
|
||||
try {
|
||||
const fonction = new AsyncFunction("console", "scene", editeur.value);
|
||||
await fonction(maConsole, scene);
|
||||
} catch (erreur) {
|
||||
maConsole.error(`${erreur.name} : ${erreur.message}`);
|
||||
const aide = expliquerErreur(erreur);
|
||||
if (aide) maConsole.info("💡 " + aide);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
5. BRANCHER chaque éditeur : boutons, raccourcis clavier
|
||||
=====================================================================
|
||||
document.querySelectorAll renvoie TOUS les éléments correspondants.
|
||||
On boucle dessus avec forEach.
|
||||
--------------------------------------------------------------------- */
|
||||
function preparerEditeurs() {
|
||||
document.querySelectorAll(".bac").forEach((bac) => {
|
||||
const editeur = bac.querySelector("textarea");
|
||||
const scene = bac.querySelector(".scene");
|
||||
|
||||
// On mémorise le code d'origine pour le bouton « Réinitialiser ».
|
||||
// .trim() enlève les espaces / retours à la ligne au début et à la fin.
|
||||
editeur.value = editeur.value.trim();
|
||||
const codeOriginal = editeur.value;
|
||||
|
||||
// On mémorise aussi le contenu de départ de la scène
|
||||
if (scene) scene.dataset.initial = scene.innerHTML;
|
||||
|
||||
// L'éditeur s'adapte à la hauteur de son code
|
||||
ajusterHauteur(editeur);
|
||||
editeur.addEventListener("input", () => ajusterHauteur(editeur));
|
||||
|
||||
// Désactive le correcteur orthographique (inutile pour du code)
|
||||
editeur.spellcheck = false;
|
||||
|
||||
// --- Bouton « Exécuter » ---
|
||||
bac.querySelector(".executer").addEventListener("click", () => executer(bac));
|
||||
|
||||
// --- Bouton « Réinitialiser » ---
|
||||
bac.querySelector(".reinit").addEventListener("click", () => {
|
||||
editeur.value = codeOriginal;
|
||||
ajusterHauteur(editeur);
|
||||
bac.querySelector(".sortie").innerHTML = "";
|
||||
if (scene) scene.innerHTML = scene.dataset.initial;
|
||||
});
|
||||
|
||||
// --- Raccourcis clavier dans l'éditeur ---
|
||||
// L'événement "keydown" se déclenche quand on enfonce une touche.
|
||||
// L'objet "evenement" décrit la touche (evenement.key) et les
|
||||
// touches spéciales tenues (ctrlKey, metaKey = Cmd sur Mac…).
|
||||
editeur.addEventListener("keydown", (evenement) => {
|
||||
|
||||
// Ctrl+Entrée (ou Cmd+Entrée) → exécuter
|
||||
if (evenement.key === "Enter" && (evenement.ctrlKey || evenement.metaKey)) {
|
||||
evenement.preventDefault(); // empêche le retour à la ligne
|
||||
executer(bac);
|
||||
}
|
||||
|
||||
// Tab → insérer 2 espaces au lieu de quitter le champ
|
||||
// (Échap puis Tab permet quand même de sortir au clavier)
|
||||
if (evenement.key === "Tab" && !evenement.shiftKey && !editeur.dataset.sortir) {
|
||||
evenement.preventDefault();
|
||||
const debut = editeur.selectionStart; // position du curseur
|
||||
const fin = editeur.selectionEnd;
|
||||
editeur.value = editeur.value.slice(0, debut) + " " + editeur.value.slice(fin);
|
||||
editeur.selectionStart = editeur.selectionEnd = debut + 2;
|
||||
}
|
||||
// Échap : le prochain Tab sortira de l'éditeur (accessibilité clavier)
|
||||
editeur.dataset.sortir = evenement.key === "Escape" ? "1" : "";
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* Fait grandir le <textarea> pour qu'il montre tout son code */
|
||||
function ajusterHauteur(editeur) {
|
||||
editeur.style.height = "auto"; // on remet à zéro…
|
||||
editeur.style.height = editeur.scrollHeight + 4 + "px"; // …puis on mesure
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
6. MENU : surligner le chapitre en cours
|
||||
=====================================================================
|
||||
IntersectionObserver "observe" des éléments et nous prévient quand
|
||||
ils entrent ou sortent de l'écran. Bien plus efficace que de
|
||||
recalculer à chaque mouvement de molette.
|
||||
--------------------------------------------------------------------- */
|
||||
function suivreLecture() {
|
||||
const liens = document.querySelectorAll(".sommaire a");
|
||||
|
||||
const observateur = new IntersectionObserver((entrees) => {
|
||||
entrees.forEach((entree) => {
|
||||
if (!entree.isIntersecting) return; // pas visible → on ignore
|
||||
const id = entree.target.id;
|
||||
liens.forEach((lien) => {
|
||||
// classList.toggle(nom, condition) : ajoute la classe si la
|
||||
// condition est vraie, l'enlève sinon.
|
||||
lien.classList.toggle("actif", lien.getAttribute("href") === "#" + id);
|
||||
});
|
||||
});
|
||||
}, {
|
||||
// On considère qu'une section est "lue" quand elle passe
|
||||
// dans la bande située entre 20 % et 40 % du haut de l'écran.
|
||||
rootMargin: "-20% 0px -60% 0px",
|
||||
});
|
||||
|
||||
document.querySelectorAll("section.chapitre").forEach((s) => observateur.observe(s));
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
7. THÈME clair / sombre
|
||||
=====================================================================
|
||||
localStorage permet de garder une petite info dans le navigateur,
|
||||
même après fermeture de la page. On l'entoure de try/catch car il
|
||||
peut être bloqué (navigation privée, réglages de confidentialité…).
|
||||
--------------------------------------------------------------------- */
|
||||
function gererTheme() {
|
||||
const racine = document.documentElement; // la balise <html>
|
||||
const bouton = document.querySelector("#bouton-theme");
|
||||
|
||||
// Relire le choix enregistré, s'il existe
|
||||
try {
|
||||
const sauvegarde = localStorage.getItem("theme");
|
||||
if (sauvegarde) racine.dataset.theme = sauvegarde;
|
||||
} catch (e) { /* pas grave : on garde le thème du système */ }
|
||||
|
||||
bouton.addEventListener("click", () => {
|
||||
// Quel thème est affiché en ce moment ?
|
||||
const sombreSysteme = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const actuel = racine.dataset.theme || (sombreSysteme ? "dark" : "light");
|
||||
const nouveau = actuel === "dark" ? "light" : "dark";
|
||||
|
||||
racine.dataset.theme = nouveau; // → <html data-theme="dark">, lu par le CSS
|
||||
try { localStorage.setItem("theme", nouveau); } catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
8. DÉMARRAGE
|
||||
=====================================================================
|
||||
La balise <script> est chargée avec l'attribut "defer" dans le HTML :
|
||||
le script s'exécute seulement quand tout le HTML a été lu.
|
||||
On peut donc chercher les éléments de la page sans risque.
|
||||
--------------------------------------------------------------------- */
|
||||
preparerEditeurs();
|
||||
suivreLecture();
|
||||
gererTheme();
|
||||
@@ -0,0 +1,461 @@
|
||||
/* =====================================================================
|
||||
style.css — la "peau" de la page
|
||||
---------------------------------------------------------------------
|
||||
Le CSS (Cascading Style Sheets) décrit l'APPARENCE des éléments HTML.
|
||||
Une règle CSS se lit ainsi :
|
||||
|
||||
sélecteur { ← QUI on cible (balise, .classe, #id…)
|
||||
propriété: valeur; ← CE QU'ON change
|
||||
}
|
||||
|
||||
"Cascading" (en cascade) : si plusieurs règles ciblent le même
|
||||
élément, la plus SPÉCIFIQUE gagne (un #id bat une .classe qui bat
|
||||
une balise), et à égalité, la DERNIÈRE écrite gagne.
|
||||
===================================================================== */
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
1. VARIABLES CSS (on dit aussi "custom properties")
|
||||
---------------------------------------------------------------------
|
||||
:root = l'élément racine du document (<html>).
|
||||
On y déclare des variables qui commencent par deux tirets "--".
|
||||
On les réutilise ensuite partout avec var(--nom).
|
||||
Avantage : changer une couleur à UN endroit la change PARTOUT.
|
||||
--------------------------------------------------------------------- */
|
||||
:root {
|
||||
--fond: #f7f5f0; /* couleur de fond de la page */
|
||||
--surface: #ffffff; /* fond des "cartes" et blocs */
|
||||
--texte: #1d1b18; /* couleur du texte principal */
|
||||
--texte-doux: #6b665e; /* texte secondaire, plus discret */
|
||||
--bordure: #e2ddd3; /* traits de séparation */
|
||||
--accent: #d4a017; /* jaune "JavaScript" */
|
||||
--accent-fort: #8a6500; /* version plus foncée pour le texte */
|
||||
--code-fond: #1e1e24; /* fond des éditeurs de code */
|
||||
--code-texte: #e8e6e3; /* texte dans les éditeurs */
|
||||
--ok: #2f9e5b; /* vert : succès */
|
||||
--erreur: #d64545; /* rouge : erreur */
|
||||
--info: #3b7dd8; /* bleu : information */
|
||||
|
||||
--rayon: 10px; /* arrondi des coins */
|
||||
--police: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
--police-code: "JetBrains Mono", "Fira Code", Consolas, monospace;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
MODE SOMBRE AUTOMATIQUE
|
||||
---------------------------------------------------------------------
|
||||
@media permet d'appliquer des règles SOUS CONDITION.
|
||||
Ici : "si le système de l'utilisateur est réglé en thème sombre".
|
||||
On ne fait que redéfinir les variables : tout le reste suit tout seul.
|
||||
:not([data-theme="light"]) → sauf si l'utilisateur a forcé le clair
|
||||
avec le bouton (voir script.js).
|
||||
--------------------------------------------------------------------- */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--fond: #141416;
|
||||
--surface: #1c1c20;
|
||||
--texte: #ecebe8;
|
||||
--texte-doux: #9d988f;
|
||||
--bordure: #2e2e34;
|
||||
--accent-fort: #f0c040;
|
||||
--code-fond: #0f0f12;
|
||||
}
|
||||
}
|
||||
/* Même chose quand l'utilisateur force le thème sombre avec le bouton */
|
||||
:root[data-theme="dark"] {
|
||||
--fond: #141416;
|
||||
--surface: #1c1c20;
|
||||
--texte: #ecebe8;
|
||||
--texte-doux: #9d988f;
|
||||
--bordure: #2e2e34;
|
||||
--accent-fort: #f0c040;
|
||||
--code-fond: #0f0f12;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
2. RÉINITIALISATION DE BASE
|
||||
---------------------------------------------------------------------
|
||||
"*" = sélecteur universel : TOUS les éléments.
|
||||
box-sizing: border-box → la largeur d'un élément INCLUT ses marges
|
||||
intérieures (padding) et sa bordure. Beaucoup plus intuitif.
|
||||
--------------------------------------------------------------------- */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth; /* défilement doux quand on clique un lien #ancre */
|
||||
scroll-padding-top: 20px; /* laisse un peu d'air au-dessus du titre ciblé */
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0; /* le navigateur met 8px par défaut : on enlève */
|
||||
background: var(--fond);
|
||||
color: var(--texte);
|
||||
font-family: var(--police);
|
||||
font-size: 17px;
|
||||
line-height: 1.65; /* interligne confortable pour la lecture */
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
3. MISE EN PAGE : grille à deux colonnes (menu + contenu)
|
||||
---------------------------------------------------------------------
|
||||
display: grid transforme un conteneur en GRILLE.
|
||||
grid-template-columns définit la largeur des colonnes :
|
||||
- 260px : le menu a une largeur fixe
|
||||
- 1fr : le contenu prend "1 fraction" de l'espace restant
|
||||
--------------------------------------------------------------------- */
|
||||
.mise-en-page {
|
||||
display: grid;
|
||||
grid-template-columns: 260px 1fr;
|
||||
min-height: 100vh; /* vh = % de la hauteur de la fenêtre */
|
||||
}
|
||||
|
||||
/* Le menu latéral */
|
||||
.sommaire {
|
||||
position: sticky; /* "colle" en haut quand on défile */
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
overflow-y: auto; /* barre de défilement si le menu est long */
|
||||
padding: 24px 18px;
|
||||
border-right: 1px solid var(--bordure);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.sommaire .logo {
|
||||
display: flex; /* flexbox : aligne les enfants en ligne */
|
||||
align-items: center; /* centrés verticalement */
|
||||
gap: 10px; /* espace entre eux */
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem; /* rem = relatif à la taille de police racine */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sommaire .logo span {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
font-family: var(--police-code);
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.sommaire ol {
|
||||
list-style: none; /* enlève les numéros automatiques */
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
counter-reset: chap -1; /* compteur CSS maison, démarre à -1 → premier = 0 */
|
||||
}
|
||||
|
||||
.sommaire li {
|
||||
counter-increment: chap; /* +1 à chaque <li> */
|
||||
}
|
||||
|
||||
.sommaire a {
|
||||
display: block;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
color: var(--texte-doux);
|
||||
text-decoration: none; /* enlève le soulignement des liens */
|
||||
font-size: 0.93rem;
|
||||
transition: background .15s, color .15s; /* animation douce au survol */
|
||||
}
|
||||
|
||||
/* ::before = pseudo-élément : du contenu "virtuel" inséré AVANT le texte */
|
||||
.sommaire a::before {
|
||||
content: counter(chap, decimal-leading-zero) " ";
|
||||
font-family: var(--police-code);
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
/* :hover = quand la souris passe dessus */
|
||||
.sommaire a:hover {
|
||||
background: var(--fond);
|
||||
color: var(--texte);
|
||||
}
|
||||
|
||||
/* .actif est ajoutée par JavaScript sur le chapitre en cours de lecture */
|
||||
.sommaire a.actif {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#bouton-theme {
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
4. LE CONTENU
|
||||
--------------------------------------------------------------------- */
|
||||
main {
|
||||
max-width: 860px; /* lignes pas trop longues = lecture facile */
|
||||
padding: 40px 48px 120px;
|
||||
min-width: 0; /* évite qu'un long bloc de code élargisse la grille */
|
||||
}
|
||||
|
||||
header.intro h1 {
|
||||
font-size: 2.6rem;
|
||||
line-height: 1.15;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
header.intro h1 mark {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
padding: 0 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
header.intro p.chapeau {
|
||||
font-size: 1.2rem;
|
||||
color: var(--texte-doux);
|
||||
}
|
||||
|
||||
section.chapitre {
|
||||
padding-top: 40px;
|
||||
margin-top: 40px;
|
||||
border-top: 1px solid var(--bordure);
|
||||
}
|
||||
|
||||
section.chapitre h2 {
|
||||
font-size: 1.8rem;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
section.chapitre h2 .num {
|
||||
font-family: var(--police-code);
|
||||
color: var(--accent-fort);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 32px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Le code "en ligne" dans le texte, ex : <code>let</code> */
|
||||
p code, li code, td code, summary code {
|
||||
font-family: var(--police-code);
|
||||
font-size: .88em;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--bordure);
|
||||
padding: 1px 6px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Encadrés pédagogiques */
|
||||
.encadre {
|
||||
border-left: 4px solid var(--info);
|
||||
background: var(--surface);
|
||||
padding: 12px 18px;
|
||||
border-radius: 0 var(--rayon) var(--rayon) 0;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.encadre.astuce { border-color: var(--ok); }
|
||||
.encadre.attention { border-color: var(--erreur); }
|
||||
.encadre strong:first-child { display: block; margin-bottom: 4px; }
|
||||
|
||||
/* Tableaux */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse; /* fusionne les bordures des cellules */
|
||||
margin: 16px 0;
|
||||
font-size: .95rem;
|
||||
}
|
||||
th, td {
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--bordure);
|
||||
}
|
||||
th { color: var(--texte-doux); font-weight: 600; }
|
||||
|
||||
/* Images de schéma faites en texte */
|
||||
pre.schema {
|
||||
font-family: var(--police-code);
|
||||
font-size: .85rem;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--bordure);
|
||||
border-radius: var(--rayon);
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
5. LE "BAC À SABLE" : éditeur de code + sortie
|
||||
---------------------------------------------------------------------
|
||||
Chaque .bac contient :
|
||||
- une barre (titre + boutons)
|
||||
- un <textarea> où l'on écrit le code
|
||||
- une zone .sortie où s'affichent les console.log
|
||||
--------------------------------------------------------------------- */
|
||||
.bac {
|
||||
margin: 20px 0;
|
||||
border-radius: var(--rayon);
|
||||
overflow: hidden; /* coupe ce qui dépasse des coins arrondis */
|
||||
border: 1px solid var(--bordure);
|
||||
background: var(--code-fond);
|
||||
}
|
||||
|
||||
.bac .barre {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between; /* titre à gauche, boutons à droite */
|
||||
gap: 8px;
|
||||
flex-wrap: wrap; /* les boutons passent à la ligne si besoin */
|
||||
padding: 8px 12px;
|
||||
background: rgba(255,255,255,.04);
|
||||
color: #9d988f;
|
||||
font-size: .82rem;
|
||||
font-family: var(--police-code);
|
||||
}
|
||||
|
||||
.bac .boutons { display: flex; gap: 6px; }
|
||||
|
||||
.bac textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
padding: 14px 16px;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: vertical; /* on peut agrandir verticalement */
|
||||
background: transparent;
|
||||
color: var(--code-texte);
|
||||
font-family: var(--police-code);
|
||||
font-size: 14.5px;
|
||||
line-height: 1.6;
|
||||
tab-size: 2; /* une tabulation = 2 espaces */
|
||||
white-space: pre; /* garde les espaces et retours à la ligne */
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.bac .sortie {
|
||||
border-top: 1px solid rgba(255,255,255,.08);
|
||||
padding: 10px 16px;
|
||||
font-family: var(--police-code);
|
||||
font-size: 13.5px;
|
||||
color: #cfcac2;
|
||||
min-height: 38px;
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
background: rgba(0,0,0,.25);
|
||||
}
|
||||
|
||||
/* :empty = élément sans contenu → on affiche un texte d'aide via ::before */
|
||||
.bac .sortie:empty::before {
|
||||
content: "▸ La sortie s'affichera ici. Clique sur « Exécuter » (ou Ctrl+Entrée).";
|
||||
color: #6b665e;
|
||||
}
|
||||
|
||||
/* Chaque ligne de sortie */
|
||||
.ligne { white-space: pre-wrap; padding: 2px 0; border-bottom: 1px dashed rgba(255,255,255,.05); }
|
||||
.ligne.err { color: #ff8a8a; }
|
||||
.ligne.warn { color: #f0c040; }
|
||||
.ligne.info { color: #8ab4f8; }
|
||||
.ligne .t-nombre { color: #b5cea8; }
|
||||
.ligne .t-texte { color: #ce9178; }
|
||||
.ligne .t-booleen { color: #569cd6; }
|
||||
.ligne .t-vide { color: #808080; font-style: italic; }
|
||||
|
||||
/* Zone visuelle pour les démos DOM */
|
||||
.scene {
|
||||
background: var(--surface);
|
||||
border-top: 1px solid rgba(255,255,255,.08);
|
||||
padding: 16px;
|
||||
min-height: 60px;
|
||||
color: var(--texte);
|
||||
font-family: var(--police);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
6. BOUTONS
|
||||
--------------------------------------------------------------------- */
|
||||
button {
|
||||
font: inherit; /* hérite de la police du parent */
|
||||
cursor: pointer; /* curseur "main" */
|
||||
border: 1px solid var(--bordure);
|
||||
background: var(--surface);
|
||||
color: var(--texte);
|
||||
padding: 6px 14px;
|
||||
border-radius: 7px;
|
||||
font-size: .9rem;
|
||||
}
|
||||
button:hover { border-color: var(--accent); }
|
||||
|
||||
/* Boutons dans la barre des éditeurs */
|
||||
.bac button {
|
||||
font-family: var(--police-code);
|
||||
font-size: .8rem;
|
||||
padding: 4px 10px;
|
||||
background: transparent;
|
||||
color: #cfcac2;
|
||||
border-color: rgba(255,255,255,.15);
|
||||
}
|
||||
.bac button.executer {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
border-color: var(--accent);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* :focus-visible = focus au CLAVIER : important pour l'accessibilité */
|
||||
button:focus-visible, textarea:focus-visible, a:focus-visible, input:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
7. EXERCICES (balise <details> : un bloc qu'on ouvre / ferme)
|
||||
--------------------------------------------------------------------- */
|
||||
.exercice {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--bordure);
|
||||
border-radius: var(--rayon);
|
||||
padding: 16px 20px;
|
||||
margin: 24px 0;
|
||||
}
|
||||
.exercice > strong { color: var(--accent-fort); }
|
||||
|
||||
details {
|
||||
margin-top: 10px;
|
||||
}
|
||||
details summary {
|
||||
cursor: pointer;
|
||||
color: var(--texte-doux);
|
||||
font-size: .92rem;
|
||||
}
|
||||
details[open] summary { margin-bottom: 8px; }
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
8. RESPONSIVE : adaptation aux petits écrans (téléphone)
|
||||
---------------------------------------------------------------------
|
||||
max-width: 860px → "si l'écran fait 860px de large OU MOINS".
|
||||
On passe alors en une seule colonne et le menu devient un bandeau.
|
||||
--------------------------------------------------------------------- */
|
||||
@media (max-width: 860px) {
|
||||
.mise-en-page { grid-template-columns: 1fr; }
|
||||
|
||||
.sommaire {
|
||||
position: static;
|
||||
height: auto;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--bordure);
|
||||
}
|
||||
.sommaire ol {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
.sommaire a { font-size: .8rem; padding: 4px 8px; }
|
||||
|
||||
main { padding: 24px 16px 80px; }
|
||||
header.intro h1 { font-size: 2rem; }
|
||||
}
|
||||
Reference in New Issue
Block a user