Files
stagiaire/PROCHAINE_ETAPE_contact.md
T

26 KiB
Raw Blame History

📬 Page Contact — relecture & prochaine étape

Salut Mélissa ! 👋

Ta page contact prend forme : le tableau de l'équipe est sympa, le formulaire est en place, et le bouton « contact » qui reste enfoncé pour montrer la page courante (.instyled) est une très bonne idée d'ergonomie. Bravo ! 🎉

Ce guide a trois objectifs :

  1. Corriger 4 bugs (avec l'explication, pas juste la solution).
  2. T'apprendre à définir et limiter une <textarea> proprement.
  3. Te lancer sur ta première mission JavaScript 🚀 (dans le thème FleetZen).

🚨 Bug 1 — L'accolade } oubliée (le bug le plus important)

Tu te souviens de la leçon « chaque { a son } » ? Elle te concerne en plein ! 😄 Regarde le tout début de contact.css :

body {
    width: 100%;
    height: 100%;
    background-color: lightblue;
    margin: 0;
    padding-bottom: 60px;
                              /* ❌ il manque le } ICI pour fermer body */
#nav_barre {                  /* du coup le navigateur croit que tout ça est ENCORE dans body */
    display: flex;
    ...

Tu as ouvert body { mais tu ne l'as jamais refermé. Résultat : le navigateur pense que #nav_barre, .styled, .table… sont tous imbriqués dans body, il se perd, et une grande partie de ton CSS est ignorée. C'est pour ça que ta page ne ressemble pas à ce que tu attends.

Correction : ferme body avec un }

body {
    width: 100%;
    height: 100%;
    background-color: lightblue;
    margin: 0;
    padding-bottom: 60px;
}                             /* ✅ on ferme body ici */

#nav_barre {
    display: flex;
    ...
}

💡 À retenir : à chaque { correspond un }. Une accolade oubliée et tout le reste de la feuille de style casse. Astuce : la plupart des éditeurs colorent ou « replient » les blocs — si la coloration paraît bizarre à partir d'un endroit, c'est souvent une accolade manquante juste avant.


🐛 Bug 2 — Un lien de navigation cassé

Ta page contact.html est déjà dans le dossier pages/. Or ton lien vers services pointe vers :

<a href="pages/services.html">   <!-- ❌ cherche pages/pages/services.html → n'existe pas -->

Comme tu es déjà dans pages/, tu redemandes d'entrer dans pages/ → le chemin n'existe pas.

Correction : le fichier est juste à côté, dans le même dossier

<a href="services.html">         <!-- ✅ même dossier, on met juste le nom du fichier -->

💡 À retenir : les chemins sont relatifs au fichier courant.

  • services.html → dans le même dossier
  • ../index.htmlremonter d'un dossier (le ..), puis prendre index.html Ton lien « retour » (../index.html) est d'ailleurs correct, bravo : c'est la même logique.

🐛 Bug 3 — Le champ e-mail n'est pas un vrai champ e-mail

<input type="text" name="e-mail" id="e-mail" ...>   <!-- ❌ type="text" -->

En type="text", le navigateur accepte n'importe quoi. Pour un e-mail, il existe un type dédié qui vérifie tout seul qu'il y a bien un @ :

<input type="email" name="email" id="email" ...>    <!-- ✅ -->

💡 À retenir : le bon type d'<input> fait travailler le navigateur à ta place : email, tel, number, date… Chacun ajoute sa petite vérification automatique.

⚠️ Petit détail : essaie d'éviter le tiret dans les id/name (e-mail). Préfère email ou e_mail — c'est plus simple à réutiliser ensuite, surtout en JavaScript.


🎯 Bug 4 — L'icône SVG n'est pas centrée dans le bouton « retour »

Ton bouton « retour » mélange une image (le SVG) et du texte côte à côte :

<button class="styled"><svg ...></svg>retour</button>

Le souci : par défaut, une icône SVG se pose sur la ligne du texte (sur la « ligne de base », comme une lettre). Du coup elle paraît trop haute ou trop basse, et ton line-height: 2.5 accentue le décalage. L'icône et le mot « retour » ne sont pas alignés sur leur milieu.

La solution : transformer le bouton en boîte flex

.styled {
    display: inline-flex;     /* le bouton range ses enfants (icône + texte) */
    align-items: center;      /* ↕ les centre verticalement, sur le même milieu */
    justify-content: center;  /* ↔ les centre horizontalement */
    gap: 6px;                 /* un petit espace entre l'icône et le mot */
    line-height: normal;      /* on enlève le 2.5 qui déréglait l'alignement */
    /* ...garde le reste de tes styles (couleur, border-radius, etc.) */
}

align-items: center, c'est exactement le flex que tu connais déjà (tu l'utilises sur #buttons). Ici on l'applique au bouton lui-même, parce que c'est lui le parent qui contient l'icône et le texte.

💡 À retenir : dès qu'un bouton (ou n'importe quelle boîte) contient une icône + du texte à aligner, passe-le en display: inline-flex; align-items: center;. C'est le réflexe pro pour centrer proprement une icône avec du texte.

🔎 Comme tous tes boutons partagent la classe .styled, cette correction les rend tous propres d'un coup — même ceux sans icône (le texte y reste bien centré).


📐 La leçon du jour : définir et limiter une <textarea>

Ta zone de message est « brute » :

<textarea name="contacter" id="contacter"></textarea>

Par défaut elle est petite, et l'utilisateur peut écrire sans aucune limite. On va la cadrer.

1️⃣ Lui donner une taille de départ : rows et cols

<textarea id="contacter" name="contacter"
          rows="6" cols="40"></textarea>
  • rows = nombre de lignes visibles (la hauteur).
  • cols = largeur en caractères.

🔎 Ce ne sont pas des pixels : c'est « combien de lignes/caractères je vois » au départ.

2️⃣ Limiter le nombre de caractères : maxlength

<textarea id="contacter" name="contacter"
          rows="6" cols="40"
          maxlength="500"></textarea>

maxlength="500" = impossible de taper plus de 500 caractères. Le navigateur bloque tout seul, sans JavaScript. 👍

3️⃣ Cadrer le redimensionnement : resize en CSS

La petite poignée en bas à droite permet à l'utilisateur d'étirer la zone et de casser ta mise en page. On la contrôle en CSS :

#contacter {
    resize: vertical;   /* étirement en hauteur seulement (recommandé) */
    /* resize: none;    pour l'interdire complètement */
    width: 100%;
    min-height: 120px;
}

💡 À retenir :

  • rows / colstaille de départ.
  • maxlengthlimite le nombre de caractères (le navigateur bloque tout seul).
  • resize (CSS) → contrôle la poignée d'étirement (vertical, horizontal, none, both).

4️⃣ 👉 Votre question : laisser l'utilisateur redimensionner, mais entre un min et un max

Tu as écrit pour l'instant :

textarea {
    resize: initial;   /* "initial" = la valeur par défaut = "both" : étirable dans TOUS les sens, sans limite */
    width: 527px;
    height: 80px;
}

resize: initial remet la valeur par défaut, c'est-à-dire both : l'utilisateur peut tout étirer, dans toutes les directions et sans aucune borne. Il peut donc rendre la zone énorme et casser ta mise en page. Vous m'avez justement demandé comment autoriser l'agrandissement sans qu'il parte trop grand (ni trop petit). La recette tient en 2 idées qui se combinent :

  1. resize dit dans quelle direction on peut étirer.
  2. min-* / max-* disent jusqu'où on peut aller.
#contacter {
    resize: vertical;     /* on autorise l'étirement, mais en HAUTEUR seulement */

    min-height: 80px;     /* impossible de la réduire en dessous de 80px */
    max-height: 300px;    /* impossible de l'agrandir au-delà de 300px */

    width: 100%;          /* largeur pilotée en CSS, pas par l'utilisateur */
}

Avec ça : l'utilisateur attrape la poignée, tire vers le bas pour avoir plus de place… mais la zone refuse de dépasser 300px et de descendre sous 80px. Ta mise en page reste protégée. 🎉

Et si on veut autoriser les deux sens (vertical et horizontal) ?

Même principe, mais avec 4 bornes : 2 pour la hauteur, 2 pour la largeur. On passe resize à both (c'est d'ailleurs ce que faisait ton resize: initial, mais cette fois borné) :

#contacter {
    resize: both;         /* la poignée étire en hauteur ET en largeur */

    min-height: 80px;     /* bornes verticales */
    max-height: 300px;

    min-width: 200px;     /* bornes horizontales */
    max-width: 100%;      /* ne dépasse jamais la largeur du parent → pas de scroll */
}

Chaque direction a sa paire min/max :

  • hauteurmin-height / max-height
  • largeurmin-width / max-width

L'utilisateur peut alors étirer dans tous les sens, mais jamais au-delà de ces 4 limites.

⚠️ Le piège classique en horizontal : mets max-width: 100% (et pas un grand 527px fixe), sinon l'utilisateur peut élargir la zone plus que l'écran et faire apparaître une barre de défilement horizontale.

Récap des valeurs de resize :

resize Ce que l'utilisateur peut étirer
none rien (verrouillé)
vertical la hauteur seulement (le plus confortable pour un message)
horizontal la largeur seulement
both les deux — c'est ce que donne initial par défaut

🎯 Petit conseil de ciblage : vise plutôt #contacter (l'id de ta textarea) que textarea tout court. Le sélecteur textarea toucherait toutes les zones de texte du site, même celles que Guillaume ajoutera sur sa page.

💡 À retenir : resize = la direction autorisée. min-height/max-height (et min-width/max-width) = les bornes. Les deux ensemble = un redimensionnement libre mais encadré.


🧭 Petit plus — centrer un bloc sans le « pousser » avec margin-left

J'ai vu que tu centres ta table et ton nouveau .form avec margin-left: 600px / 620px. Ça « marche » sur ton écran… mais sur un écran plus petit, le bloc est poussé hors de la fenêtre → barre de défilement horizontale. Pour vraiment centrer un bloc de largeur fixe :

.form {
    width: 35%;
    margin-left: auto;    /* "auto" à gauche ET à droite = */
    margin-right: auto;   /* le navigateur répartit l'espace → bloc centré */
    /* on enlève le margin-left: 620px */
}

💡 À retenir : margin: auto à gauche et à droite centre un bloc qui a une largeur. C'est l'outil correct ; margin-left: 620px ne fait que le décaler et casse l'affichage sur petit écran.


🚀 Ta première mission JavaScript : le compteur de caractères

Jusqu'ici tu as fait du HTML (la structure) et du CSS (l'apparence). Le JavaScript, c'est la 3ᵉ couche : le comportement, ce qui réagit en direct. 🧠

On va le faire en mode tuto : tu construis petit bout par petit bout, et tu testes à chaque étape. Ne copie pas tout d'un coup — avance palier par palier, c'est comme ça qu'on comprend. 🙂

Objectif final : maxlength empêche de dépasser 500 caractères… mais l'utilisateur ne voit pas combien il lui en reste. On va afficher un compteur en direct : « Il vous reste 437 caractères ». C'est exactement ce que font Twitter/X, les SMS, etc.


🪜 Étape 0 — Découvrir la console (ton meilleur ami)

Ouvre ta page dans le navigateur, puis appuie sur F12 → onglet Console. C'est là que le JS « parle ».

Crée un fichier contact.js avec une seule ligne :

console.log("Mon fichier JS est bien chargé !");

Et branche-le en bas du <body>, juste avant </body> :

<script src="contact.js"></script>

🔎 Pourquoi à la fin du body ? Parce que le JS s'exécute de haut en bas. S'il se lance avant que le HTML existe, il ne trouve rien à manipuler. À la fin, tout le HTML est déjà là. 👍

Teste maintenant : recharge la page. Tu dois voir ton message dans la Console. Si oui, ton JS est branché. Sinon, vérifie le chemin du src.


🪜 Étape 1 — Ajouter l'affichage du compteur

Sous ta textarea, ajoute la ligne qui affichera le décompte :

<textarea id="contacter" name="contacter" rows="6" maxlength="500"></textarea>
<p>Il vous reste <span id="restant">500</span> caractères.</p>

Teste maintenant : tu dois lire « Il vous reste 500 caractères ». Pour l'instant ce 500 est figé, on va le rendre vivant.

🔎 Le <span id="restant"> est juste une petite « boîte » dans laquelle le JS viendra écrire le nombre. Son id est l'étiquette par laquelle on va l'attraper.


🪜 Étape 2 — Attraper les éléments depuis le JS

Dans contact.js, remplace ton console.log de test par :

const zoneMessage = document.getElementById("contacter"); // ta textarea
const compteur = document.getElementById("restant");      // le <span>

console.log(zoneMessage); // pour vérifier qu'on l'a bien trouvée

Teste maintenant : dans la Console tu dois voir s'afficher ta <textarea>. Si tu vois null, l'id ne correspond pas → vérifie l'orthographe (contacter).

🧩 Idée clé n°1 — SÉLECTIONNER. document.getElementById("contacter") veut dire : « va me chercher l'élément dont l'id est contacter ». On le range dans une variable pour s'en servir après.


🪜 Étape 3 — Réagir quand on tape

Ajoute en dessous :

zoneMessage.addEventListener("input", function () {
    console.log("Tu viens de taper quelque chose !");
});

Teste maintenant : clique dans la zone et tape quelques lettres. À chaque touche, un message apparaît dans la Console. 🎉

🧩 Idée clé n°2 — RÉAGIR. addEventListener("input", … ) se lit : « écoute les saisies dans cette zone, et à chaque frappe, exécute cette fonction ». ("input" = « le contenu a changé », parfait pour une textarea.)


🪜 Étape 4 — Calculer et afficher ce qu'il reste

Maintenant on remplace le console.log par le vrai calcul. Voici le fichier complet :

// 1. On attrape la textarea et la "boîte" où afficher le compteur
const zoneMessage = document.getElementById("contacter");
const compteur = document.getElementById("restant");
const MAX = 500;

// 2. À chaque frappe, on recalcule ce qu'il reste
zoneMessage.addEventListener("input", function () {
    const dejaTape = zoneMessage.value.length; // .value = le texte tapé, .length = sa longueur
    compteur.textContent = MAX - dejaTape;     // on écrit le résultat dans le <span>
});

🧩 Idée clé n°3 — MODIFIER. compteur.textContent = … réécrit le texte affiché en direct, sans recharger la page. C'est ça, la magie du JS.

🔎 zoneMessage.value, c'est le contenu tapé par l'utilisateur ; .length en donne le nombre de caractères. 500 ce nombre = ce qu'il reste.

Teste maintenant : tape du texte → le nombre doit descendre à chaque lettre (499, 498, …) et s'arrêter à 0 quand tu atteins la limite. Si oui : bravo, tu viens d'écrire ta première vraie logique en JavaScript ! 🎉

🎯 Défis bonus (dans l'ordre de difficulté) :

  1. Passe le compteur en rouge quand il reste moins de 50 caractères :
    compteur.style.color = (MAX - dejaTape) < 50 ? "red" : "black";
    
  2. Affiche le texte « caractères » au singulier quand il reste 1 ou 0.
  3. Désactive le bouton « Envoyer » tant que le message est vide.

🎓 Niveau 2 — Les conditions (et de nouveaux exos JS)

Bravo Mélissa 👏 — tu as fait marcher le compteur et le bonus couleur (le compteur qui passe en rouge). Tu es prête pour la suite : les conditions, c'est-à-dire apprendre au programme à choisir quoi faire selon la situation.

🩹 Petite correction au passage (vu dans ton HTML) : ton champ e-mail est type="e_mail". Ce type n'existe pas → le navigateur le traite comme du texte normal. Le bon, c'est type="email" (sans underscore). Avec, le navigateur vérifie tout seul qu'il y a bien un @. 😉


🧠 La leçon : if / else — faire choisir le programme

Une condition, c'est une question qui n'a que deux réponses : vrai ou faux. En JavaScript on écrit :

if (/* une question */) {
    // ce qu'on fait SI la réponse est "vrai"
} else {
    // ce qu'on fait SINON
}

Pour poser la question, on utilise des opérateurs de comparaison :

Opérateur Question posée Exemple vrai
=== est égal à ? restant === 1
!== est différent de ? restant !== 0
> < plus grand / plus petit ? restant < 50
>= <= plus grand/petit ou égal ? restant >= 1

⚠️ Le piège n°1 des débutants : = et === ne veulent PAS dire la même chose !

  • = (un seul) → ranger une valeur : restant = 5 (« mets 5 dans restant »).
  • === (trois) → comparer : restant === 5 (« est-ce que restant vaut 5 ? »). Dans un if (...), c'est toujours ===.

✍️ Exo : afficher « caractère » au singulier

Aujourd'hui ton HTML affiche toujours « caractères » au pluriel, même quand il n'en reste qu'un. On va corriger ça avec une condition.

Étape 1 — Donner une étiquette au mot dans le HTML

Pour pouvoir changer le mot tout seul, on l'enferme dans son propre <span> :

<p class="small">Il vous reste <span id="restant">500</span> <span id="mot">caractères</span>.</p>

Étape 2 — Choisir le bon mot en JavaScript

Dans contact.js, attrape ce nouveau <span> (en haut, avec les autres) :

const motCaractere = document.getElementById("mot");

Puis, à l'intérieur de ton addEventListener("input", …), ajoute la condition :

zoneMessage.addEventListener("input", function () {
    const dejaTape = zoneMessage.value.length;
    const restant = MAX - dejaTape;        // on range le calcul dans une variable, plus lisible

    compteur.textContent = restant;
    compteur.style.color = restant < 50 ? "red" : "black";

    // 👇 la nouvelle condition
    if (restant === 1) {
        motCaractere.textContent = "caractère";   // singulier
    } else {
        motCaractere.textContent = "caractères";  // pluriel
    }
});

Teste maintenant : tape jusqu'à ce qu'il reste exactement 1 → tu dois lire « Il vous reste 1 caractère ». À 2 ou plus → « caractères ». 🎉

🔎 Tu connaissais déjà une condition sans le savoir ! Ton bonus couleur :

compteur.style.color = restant < 50 ? "red" : "black";

C'est exactement un if/else… en version courte (on l'appelle le ternaire). La version longue équivalente serait :

if (restant < 50) {
    compteur.style.color = "red";
} else {
    compteur.style.color = "black";
}

Les deux font la même chose. Le ? : est juste un raccourci pratique quand il n'y a qu'une ligne à choisir. 👍


🧪 De nouveaux exos pour découvrir plus de JavaScript

Chaque exo t'apprend une nouvelle notion. Fais-les dans l'ordre, et teste à chaque fois.

🧪 Exo 1 — Empêcher l'envoi d'un message vide

🆕 Nouvelle notion : réagir à l'envoi du formulaire ("submit") et bloquer l'action par défaut.

Aujourd'hui, si on clique « Envoyer » avec un message vide, le formulaire part quand même. On va l'en empêcher.

const formulaire = document.querySelector("form"); // on attrape le <form>

formulaire.addEventListener("submit", function (evenement) {
    if (zoneMessage.value.trim() === "") {     // .trim() enlève les espaces autour
        evenement.preventDefault();            // ⛔ on ANNULE l'envoi
        alert("Merci d'écrire un message avant d'envoyer !");
    }
});

Teste : clique « Envoyer » sans rien écrire → une alerte apparaît et la page ne se recharge pas. Écris un message → l'envoi repart normalement.

🔎 evenement est une info que le navigateur passe à ta fonction (« voici ce qui s'est passé »). evenement.preventDefault() veut dire « n'effectue PAS l'action habituelle » (ici : ne soumets pas le formulaire). .trim() sert à considérer « 3 espaces » comme un message vide.

🧪 Exo 2 — Un bouton « Effacer le message »

🆕 Nouvelle notion : ranger du code dans une fonction pour le réutiliser (ne pas se répéter).

Ajoute un bouton dans le HTML, à côté de « Envoyer » :

<button type="button" id="effacer">Effacer</button>

Le souci : quand on efface, il faut aussi remettre le compteur à 500. On a donc besoin de réafficher le compteur à deux endroits (à la frappe ET à l'effacement). La bonne pratique : écrire ce calcul une seule fois dans une fonction, et l'appeler aux deux endroits.

const boutonEffacer = document.getElementById("effacer");

// une fonction qui remet l'affichage à jour à partir du texte actuel
function rafraichirCompteur() {
    const restant = MAX - zoneMessage.value.length;
    compteur.textContent = restant;
    compteur.style.color = restant < 50 ? "red" : "black";
    motCaractere.textContent = restant === 1 ? "caractère" : "caractères";
}

// à la frappe : on rafraîchit
zoneMessage.addEventListener("input", rafraichirCompteur);

// au clic sur Effacer : on vide la zone PUIS on rafraîchit
boutonEffacer.addEventListener("click", function () {
    zoneMessage.value = "";   // on vide la textarea
    rafraichirCompteur();     // et on remet le compteur à 500
});

Teste : écris du texte, clique « Effacer » → la zone se vide et le compteur repasse à 500.

🔎 Tu viens de factoriser : au lieu de recopier les mêmes lignes deux fois, tu les as mises dans rafraichirCompteur() et tu l'appelles. Si un jour tu changes l'affichage, tu ne le modifies qu'à un seul endroit. C'est exactement la même idée que le composant réutilisable plus bas. ♻️

🧪 Exo 3 — « FleetZen est-il joignable maintenant ? »

🆕 Nouvelle notion : lire l'heure actuelle (new Date()) et combiner deux conditions avec && (ET).

Une page contact, ça parle d'horaires. Affichons un petit badge « Ouvert » ou « 🔴 Fermé » selon l'heure (ouvert de 9h à 18h).

D'abord un endroit pour l'afficher, dans le HTML :

<p id="statut_horaires"></p>

Puis en JavaScript :

const badge = document.getElementById("statut_horaires");
const heure = new Date().getHours();   // l'heure actuelle, de 0 à 23

if (heure >= 9 && heure < 18) {        // && = "ET" : les DEUX doivent être vraies
    badge.textContent = "✅ Nous sommes ouverts (9h18h)";
    badge.style.color = "green";
} else {
    badge.textContent = "🔴 Fermé — écrivez-nous, on répond dès l'ouverture !";
    badge.style.color = "red";
}

Teste : recharge la page. Selon l'heure qu'il est, tu vois « Ouvert » ou « Fermé ». (Pour tester l'autre cas sans attendre, change temporairement 9 et 18.)

🔎 new Date().getHours() donne l'heure de l'ordinateur (ex. 14). && se lit « ET » : heure >= 9 && heure < 18 n'est vrai que si l'heure est à la fois ≥ 9 et < 18. Son cousin est || qui se lit « OU ».


🗺️ Où tu en es maintenant : tu sais sélectionner, réagir, modifier, et maintenant faire choisir ton programme avec des conditions, factoriser dans des fonctions, et lire des infos comme l'heure. C'est déjà le cœur du JavaScript du quotidien. La suite (boucles for, tableaux) viendra naturellement — demande-moi quand tu veux ! 🚀


♻️ Bonus — La notion de composant réutilisable (la navbar)

Regarde : la barre du haut (#nav_barre) est recopiée à l'identique dans index.html, services.html et contact.html. Le jour où on change un lien, il faut le modifier dans 3 fichiers → source d'erreurs (c'est d'ailleurs comme ça que le Bug 2 se glisse 😉).

C'est le problème que résolvent les composants : on écrit la navbar une seule fois, et on la réutilise partout.

Première approche très simple en JavaScript : une fonction qui génère la navbar.

function creerNavbar() {
    return `
        <div id="nav_barre" class="shadow">
            <h1>FleetZen</h1>
            <div id="buttons">
                <button class="instyled">contact</button>
                <a href="services.html"><button class="styled">services</button></a>
                <a href="../index.html"><button class="styled">retour</button></a>
            </div>
        </div>`;
}

// On l'injecte tout en haut du body :
document.body.insertAdjacentHTML("afterbegin", creerNavbar());

Teste : enlève la navbar écrite « en dur » dans le HTML, garde ce JS, recharge → la navbar réapparaît, générée par la fonction. Maintenant le jour où tu changes un lien, tu le changes à un seul endroit pour toutes les pages (fini les liens cassés du Bug 2 😉).

💡 À retenir : un composant = un bout d'interface écrit une fois et réutilisé partout. C'est l'idée centrale derrière React, Vue, etc. — mais ça commence par une simple fonction comme celle-ci.


Ta checklist

  • Fermer body avec un } dans le CSS (Bug 1 — le plus urgent)
  • Réparer le lien vers services.html (Bug 2)
  • Passer le champ e-mail en type="email" (Bug 3 — tu as mis e_mail, presque ! 😉)
  • Centrer l'icône SVG du bouton « retour » avec inline-flex (Bug 4)
  • Ajouter le redimensionnement borné (resize: vertical + min/max-height)
  • Centrer table et .form avec margin: auto (au lieu de margin-left: 600px)
  • Ajouter rows, cols, maxlength à la textarea
  • Faire marcher le compteur de caractères en JavaScript
  • Bonus couleur : compteur en rouge sous 50 caractères
  • Ajouter un <footer>
  • (Bonus) Transformer la navbar en composant réutilisable
  • Penser à fermer la balise </html> en fin de fichier

🎓 Niveau 2 (nouveau)

  • Afficher « caractère » au singulier quand il en reste 1 (leçon if/else)
  • Exo 1 — empêcher l'envoi d'un message vide (submit + preventDefault)
  • Exo 2 — bouton « Effacer » + factoriser dans une fonction
  • Exo 3 — badge « Ouvert / Fermé » selon l'heure (new Date() + &&)
  • Commits petits et clairs, puis git push

Pose-moi toutes tes questions. Tu gères ! 📬