> ## Documentation Index
> Fetch the complete documentation index at: https://ia101.nicolassandoz.fr/llms.txt
> Use this file to discover all available pages before exploring further.

# Carte du cours IA 101 : visualiser les liens entre les leçons

> Explorez le parcours IA 101 sous forme de graphe interactif et identifiez les prérequis de chaque leçon.

export const courseGraphEdges = [{
  "source": "fondamentaux/quest-ce-que-lia",
  "target": "fondamentaux/histoire-et-evolution"
}, {
  "source": "fondamentaux/histoire-et-evolution",
  "target": "fondamentaux/types-dia"
}, {
  "source": "fondamentaux/types-dia",
  "target": "fondamentaux/modeles-de-langage"
}, {
  "source": "fondamentaux/modeles-de-langage",
  "target": "fondamentaux/tokens-et-embeddings"
}, {
  "source": "fondamentaux/tokens-et-embeddings",
  "target": "fondamentaux/entrainement-et-fine-tuning"
}, {
  "source": "fondamentaux/entrainement-et-fine-tuning",
  "target": "fondamentaux/limites-et-hallucinations"
}, {
  "source": "prompt-engineering/introduction-au-prompt",
  "target": "prompt-engineering/anatomie-dun-prompt"
}, {
  "source": "prompt-engineering/anatomie-dun-prompt",
  "target": "prompt-engineering/bonnes-pratiques"
}, {
  "source": "prompt-engineering/bonnes-pratiques",
  "target": "prompt-engineering/few-shot-learning"
}, {
  "source": "prompt-engineering/few-shot-learning",
  "target": "prompt-engineering/chain-of-thought"
}, {
  "source": "prompt-engineering/chain-of-thought",
  "target": "prompt-engineering/role-playing-et-personas"
}, {
  "source": "prompt-engineering/role-playing-et-personas",
  "target": "prompt-engineering/prompts-iteratifs"
}, {
  "source": "prompt-engineering/prompts-iteratifs",
  "target": "prompt-engineering/patterns-reutilisables"
}, {
  "source": "prompt-engineering/patterns-reutilisables",
  "target": "prompt-engineering/redaction-et-creation"
}, {
  "source": "prompt-engineering/redaction-et-creation",
  "target": "prompt-engineering/analyse-et-synthese"
}, {
  "source": "prompt-engineering/analyse-et-synthese",
  "target": "prompt-engineering/code-et-developpement"
}, {
  "source": "fondamentaux/modeles-de-langage",
  "target": "context-engineering/quest-ce-que-le-contexte"
}, {
  "source": "fondamentaux/tokens-et-embeddings",
  "target": "context-engineering/fenetre-de-contexte"
}, {
  "source": "context-engineering/fenetre-de-contexte",
  "target": "context-engineering/gestion-de-la-memoire"
}, {
  "source": "context-engineering/gestion-de-la-memoire",
  "target": "context-engineering/structurer-le-contexte"
}, {
  "source": "context-engineering/structurer-le-contexte",
  "target": "context-engineering/rag-et-retrieval"
}, {
  "source": "context-engineering/rag-et-retrieval",
  "target": "context-engineering/agents-et-outils"
}, {
  "source": "context-engineering/agents-et-outils",
  "target": "context-engineering/optimisation-du-contexte"
}, {
  "source": "ia-responsable/empreinte-environnementale",
  "target": "ia-responsable/ordres-de-grandeur"
}, {
  "source": "ia-responsable/ordres-de-grandeur",
  "target": "ia-responsable/sobriete-et-ecoconception"
}, {
  "source": "ia-responsable/sobriete-et-ecoconception",
  "target": "ia-responsable/impact-cognitif"
}, {
  "source": "ia-responsable/impact-cognitif",
  "target": "ia-responsable/biais-et-equite"
}, {
  "source": "ia-responsable/biais-et-equite",
  "target": "ia-responsable/travail-et-societe"
}, {
  "source": "ia-responsable/travail-et-societe",
  "target": "ia-responsable/cadre-reglementaire"
}, {
  "source": "ia-responsable/cadre-reglementaire",
  "target": "ia-responsable/demarche-ia-responsable"
}];

export const courseGraphNodes = [{
  "id": "introduction",
  "href": "/introduction",
  "title": "IA 101 : Fondamentaux de l'intelligence artificielle",
  "label": "Accueil",
  "chapter": "fondamentaux",
  "group": "Introduction",
  "position": 0,
  "prerequisites": []
}, {
  "id": "ressources/le-projet",
  "href": "/ressources/le-projet",
  "title": "Le projet IA 101 : genèse, méthode et stack technique",
  "label": "Le Projet",
  "chapter": "fondamentaux",
  "group": "Introduction",
  "position": 1,
  "prerequisites": []
}, {
  "id": "fondamentaux/quest-ce-que-lia",
  "href": "/fondamentaux/quest-ce-que-lia",
  "title": "Qu'est-ce que l'IA ? Définition, exemples et limites",
  "label": "L'IA expliquée",
  "chapter": "fondamentaux",
  "group": "Introduction",
  "position": 2,
  "prerequisites": []
}, {
  "id": "fondamentaux/histoire-et-evolution",
  "href": "/fondamentaux/histoire-et-evolution",
  "title": "Histoire et évolution de l'IA : des origines à aujourd'hui",
  "label": "Histoire",
  "chapter": "fondamentaux",
  "group": "Introduction",
  "position": 3,
  "prerequisites": ["/fondamentaux/quest-ce-que-lia"]
}, {
  "id": "fondamentaux/types-dia",
  "href": "/fondamentaux/types-dia",
  "title": "Les types d'IA : Machine Learning, Deep Learning et LLM",
  "label": "Types d'IA",
  "chapter": "fondamentaux",
  "group": "Introduction",
  "position": 4,
  "prerequisites": ["/fondamentaux/histoire-et-evolution"]
}, {
  "id": "fondamentaux/modeles-de-langage",
  "href": "/fondamentaux/modeles-de-langage",
  "title": "Les grands modèles de langage : fonctionnement des LLM",
  "label": "Modèles LLM",
  "chapter": "fondamentaux",
  "group": "Concepts Clés",
  "position": 5,
  "prerequisites": ["/fondamentaux/types-dia"]
}, {
  "id": "fondamentaux/tokens-et-embeddings",
  "href": "/fondamentaux/tokens-et-embeddings",
  "title": "Tokens et embeddings : le vocabulaire des modèles d'IA",
  "label": "Tokens & Embeddings",
  "chapter": "fondamentaux",
  "group": "Concepts Clés",
  "position": 6,
  "prerequisites": ["/fondamentaux/modeles-de-langage"]
}, {
  "id": "fondamentaux/entrainement-et-fine-tuning",
  "href": "/fondamentaux/entrainement-et-fine-tuning",
  "title": "Entraînement et fine-tuning des modèles de langage",
  "label": "Entraînement",
  "chapter": "fondamentaux",
  "group": "Concepts Clés",
  "position": 7,
  "prerequisites": ["/fondamentaux/tokens-et-embeddings"]
}, {
  "id": "fondamentaux/limites-et-hallucinations",
  "href": "/fondamentaux/limites-et-hallucinations",
  "title": "Limites des LLM : hallucinations et biais à connaître",
  "label": "Limites & Biais",
  "chapter": "fondamentaux",
  "group": "Concepts Clés",
  "position": 8,
  "prerequisites": ["/fondamentaux/entrainement-et-fine-tuning"]
}, {
  "id": "prompt-engineering/introduction-au-prompt",
  "href": "/prompt-engineering/introduction-au-prompt",
  "title": "Introduction au prompt engineering : vos premiers pas",
  "label": "Introduction",
  "chapter": "prompt-engineering",
  "group": "Les Bases",
  "position": 0,
  "prerequisites": []
}, {
  "id": "prompt-engineering/anatomie-dun-prompt",
  "href": "/prompt-engineering/anatomie-dun-prompt",
  "title": "Anatomie d'un prompt : les quatre composantes essentielles",
  "label": "Anatomie",
  "chapter": "prompt-engineering",
  "group": "Les Bases",
  "position": 1,
  "prerequisites": ["/prompt-engineering/introduction-au-prompt"]
}, {
  "id": "prompt-engineering/bonnes-pratiques",
  "href": "/prompt-engineering/bonnes-pratiques",
  "title": "Bonnes pratiques du prompt engineering en 10 règles",
  "label": "Bonnes Pratiques",
  "chapter": "prompt-engineering",
  "group": "Les Bases",
  "position": 2,
  "prerequisites": ["/prompt-engineering/anatomie-dun-prompt"]
}, {
  "id": "prompt-engineering/few-shot-learning",
  "href": "/prompt-engineering/few-shot-learning",
  "title": "Few-shot learning : guider un LLM avec des exemples",
  "label": "Few-Shot",
  "chapter": "prompt-engineering",
  "group": "Techniques Avancées",
  "position": 3,
  "prerequisites": ["/prompt-engineering/bonnes-pratiques"]
}, {
  "id": "prompt-engineering/chain-of-thought",
  "href": "/prompt-engineering/chain-of-thought",
  "title": "Chain-of-Thought : améliorer le raisonnement des LLM",
  "label": "Chain-of-Thought",
  "chapter": "prompt-engineering",
  "group": "Techniques Avancées",
  "position": 4,
  "prerequisites": ["/prompt-engineering/few-shot-learning"]
}, {
  "id": "prompt-engineering/role-playing-et-personas",
  "href": "/prompt-engineering/role-playing-et-personas",
  "title": "Role-playing et personas : donner un rôle au modèle d'IA",
  "label": "Personas",
  "chapter": "prompt-engineering",
  "group": "Techniques Avancées",
  "position": 5,
  "prerequisites": ["/prompt-engineering/chain-of-thought"]
}, {
  "id": "prompt-engineering/prompts-iteratifs",
  "href": "/prompt-engineering/prompts-iteratifs",
  "title": "Prompts itératifs : affiner vos résultats progressivement",
  "label": "Prompts Itératifs",
  "chapter": "prompt-engineering",
  "group": "Techniques Avancées",
  "position": 6,
  "prerequisites": ["/prompt-engineering/role-playing-et-personas"]
}, {
  "id": "prompt-engineering/patterns-reutilisables",
  "href": "/prompt-engineering/patterns-reutilisables",
  "title": "Patterns de prompts réutilisables : votre bibliothèque prête à l'emploi",
  "label": "Patterns Réutilisables",
  "chapter": "prompt-engineering",
  "group": "Techniques Avancées",
  "position": 7,
  "prerequisites": ["/prompt-engineering/prompts-iteratifs"]
}, {
  "id": "prompt-engineering/redaction-et-creation",
  "href": "/prompt-engineering/redaction-et-creation",
  "title": "Prompts pour la rédaction et la création de contenu",
  "label": "Rédaction",
  "chapter": "prompt-engineering",
  "group": "Cas d'Usage",
  "position": 8,
  "prerequisites": ["/prompt-engineering/patterns-reutilisables"]
}, {
  "id": "prompt-engineering/analyse-et-synthese",
  "href": "/prompt-engineering/analyse-et-synthese",
  "title": "Prompts pour analyser et synthétiser l'information",
  "label": "Analyse",
  "chapter": "prompt-engineering",
  "group": "Cas d'Usage",
  "position": 9,
  "prerequisites": ["/prompt-engineering/redaction-et-creation"]
}, {
  "id": "prompt-engineering/code-et-developpement",
  "href": "/prompt-engineering/code-et-developpement",
  "title": "Prompts pour le développement et l'assistance au code",
  "label": "Code",
  "chapter": "prompt-engineering",
  "group": "Cas d'Usage",
  "position": 10,
  "prerequisites": ["/prompt-engineering/analyse-et-synthese"]
}, {
  "id": "context-engineering/quest-ce-que-le-contexte",
  "href": "/context-engineering/quest-ce-que-le-contexte",
  "title": "Context engineering : définition, rôle et composants",
  "label": "Le Contexte",
  "chapter": "context-engineering",
  "group": "Comprendre le Contexte",
  "position": 0,
  "prerequisites": ["/fondamentaux/modeles-de-langage"]
}, {
  "id": "context-engineering/fenetre-de-contexte",
  "href": "/context-engineering/fenetre-de-contexte",
  "title": "Fenêtre de contexte des LLM : capacité et stratégies",
  "label": "Fenêtre",
  "chapter": "context-engineering",
  "group": "Comprendre le Contexte",
  "position": 1,
  "prerequisites": ["/fondamentaux/tokens-et-embeddings"]
}, {
  "id": "context-engineering/gestion-de-la-memoire",
  "href": "/context-engineering/gestion-de-la-memoire",
  "title": "Gestion de la mémoire dans les conversations avec l'IA",
  "label": "Mémoire",
  "chapter": "context-engineering",
  "group": "Comprendre le Contexte",
  "position": 2,
  "prerequisites": ["/context-engineering/fenetre-de-contexte"]
}, {
  "id": "context-engineering/structurer-le-contexte",
  "href": "/context-engineering/structurer-le-contexte",
  "title": "Structurer le contexte pour des réponses plus précises",
  "label": "Structuration",
  "chapter": "context-engineering",
  "group": "Techniques",
  "position": 3,
  "prerequisites": ["/context-engineering/gestion-de-la-memoire"]
}, {
  "id": "context-engineering/rag-et-retrieval",
  "href": "/context-engineering/rag-et-retrieval",
  "title": "RAG : connecter un LLM à vos données personnalisées",
  "label": "RAG",
  "chapter": "context-engineering",
  "group": "Techniques",
  "position": 4,
  "prerequisites": ["/context-engineering/structurer-le-contexte"]
}, {
  "id": "context-engineering/agents-et-outils",
  "href": "/context-engineering/agents-et-outils",
  "title": "Agents IA et outils : comment l'IA agit dans le monde",
  "label": "Agents",
  "chapter": "context-engineering",
  "group": "Techniques",
  "position": 5,
  "prerequisites": ["/context-engineering/rag-et-retrieval"]
}, {
  "id": "context-engineering/optimisation-du-contexte",
  "href": "/context-engineering/optimisation-du-contexte",
  "title": "Optimiser le contexte pour économiser tokens et coût",
  "label": "Optimisation",
  "chapter": "context-engineering",
  "group": "Techniques",
  "position": 6,
  "prerequisites": ["/context-engineering/agents-et-outils"]
}, {
  "id": "ia-responsable/empreinte-environnementale",
  "href": "/ia-responsable/empreinte-environnementale",
  "title": "L'empreinte environnementale de l'IA : d'où vient réellement l'impact ?",
  "label": "Empreinte environnementale",
  "chapter": "ia-responsable",
  "group": "Impact environnemental",
  "position": 0,
  "prerequisites": []
}, {
  "id": "ia-responsable/ordres-de-grandeur",
  "href": "/ia-responsable/ordres-de-grandeur",
  "title": "Ordres de grandeur : ce que consomme vraiment une requête IA",
  "label": "Ordres de grandeur",
  "chapter": "ia-responsable",
  "group": "Impact environnemental",
  "position": 1,
  "prerequisites": ["/ia-responsable/empreinte-environnementale"]
}, {
  "id": "ia-responsable/sobriete-et-ecoconception",
  "href": "/ia-responsable/sobriete-et-ecoconception",
  "title": "Sobriété et écoconception : réduire l'impact de vos usages IA",
  "label": "Sobriété & écoconception",
  "chapter": "ia-responsable",
  "group": "Impact environnemental",
  "position": 2,
  "prerequisites": ["/ia-responsable/ordres-de-grandeur"]
}, {
  "id": "ia-responsable/impact-cognitif",
  "href": "/ia-responsable/impact-cognitif",
  "title": "Impact cognitif : ce que l'IA fait à notre façon de penser",
  "label": "Impact cognitif",
  "chapter": "ia-responsable",
  "group": "Impacts humains et sociétaux",
  "position": 3,
  "prerequisites": ["/ia-responsable/sobriete-et-ecoconception"]
}, {
  "id": "ia-responsable/biais-et-equite",
  "href": "/ia-responsable/biais-et-equite",
  "title": "Biais, équité et travail invisible derrière l'IA",
  "label": "Biais & équité",
  "chapter": "ia-responsable",
  "group": "Impacts humains et sociétaux",
  "position": 4,
  "prerequisites": ["/ia-responsable/impact-cognitif"]
}, {
  "id": "ia-responsable/travail-et-societe",
  "href": "/ia-responsable/travail-et-societe",
  "title": "Travail, information, création : les effets sociétaux de l'IA",
  "label": "Travail & société",
  "chapter": "ia-responsable",
  "group": "Impacts humains et sociétaux",
  "position": 5,
  "prerequisites": ["/ia-responsable/biais-et-equite"]
}, {
  "id": "ia-responsable/cadre-reglementaire",
  "href": "/ia-responsable/cadre-reglementaire",
  "title": "Cadre réglementaire : AI Act, RGPD, CSRD et normes volontaires",
  "label": "Cadre réglementaire",
  "chapter": "ia-responsable",
  "group": "Passer à l'action",
  "position": 6,
  "prerequisites": ["/ia-responsable/travail-et-societe"]
}, {
  "id": "ia-responsable/demarche-ia-responsable",
  "href": "/ia-responsable/demarche-ia-responsable",
  "title": "Construire une démarche IA responsable dans votre organisation",
  "label": "Démarche & charte",
  "chapter": "ia-responsable",
  "group": "Passer à l'action",
  "position": 7,
  "prerequisites": ["/ia-responsable/cadre-reglementaire"]
}, {
  "id": "ia-responsable/ressources-ia-responsable",
  "href": "/ia-responsable/ressources-ia-responsable",
  "title": "Ressources sur l'IA responsable : rapports, outils et références",
  "label": "Sources & références",
  "chapter": "ia-responsable",
  "group": "Passer à l'action",
  "position": 8,
  "prerequisites": []
}, {
  "id": "ressources/glossaire",
  "href": "/ressources/glossaire",
  "title": "Glossaire IA : les termes essentiels du cours IA 101",
  "label": "Glossaire",
  "chapter": "ressources",
  "group": "Références",
  "position": 0,
  "prerequisites": []
}, {
  "id": "ressources/outils-et-plateformes",
  "href": "/ressources/outils-et-plateformes",
  "title": "Outils et plateformes IA : guide pratique de sélection",
  "label": "Outils",
  "chapter": "ressources",
  "group": "Références",
  "position": 1,
  "prerequisites": []
}, {
  "id": "ressources/aller-plus-loin",
  "href": "/ressources/aller-plus-loin",
  "title": "Approfondir l'IA : cours en ligne, lectures et communautés",
  "label": "Aller Plus Loin",
  "chapter": "ressources",
  "group": "Références",
  "position": 2,
  "prerequisites": []
}, {
  "id": "ressources/nouveautes",
  "href": "/ressources/nouveautes",
  "title": "Nouveautés : les mises à jour du cours IA 101",
  "label": "Nouveautés",
  "chapter": "ressources",
  "group": "Références",
  "position": 3,
  "prerequisites": []
}];

export const courseGraphChapters = [{
  "id": "fondamentaux",
  "label": "Fondamentaux",
  "color": "#3f7898",
  "order": 0
}, {
  "id": "prompt-engineering",
  "label": "Prompt Engineering",
  "color": "#5f91aa",
  "order": 1
}, {
  "id": "context-engineering",
  "label": "Context Engineering",
  "color": "#7eb2d4",
  "order": 2
}, {
  "id": "ia-responsable",
  "label": "IA Responsable",
  "color": "#7e9f6d",
  "order": 3
}, {
  "id": "ressources",
  "label": "Ressources",
  "color": "#87929c",
  "order": 4
}];

export const CourseGraph = ({chapters = [], nodes = [], edges = []}) => {
  const [activeChapter, setActiveChapter] = useState("all");
  const [focusedNode, setFocusedNode] = useState(null);
  const wrapGraphLabel = (label, maxCharacters = 22) => {
    const words = label.split(" ");
    const lines = [];
    for (const word of words) {
      const currentLine = lines.at(-1);
      if (!currentLine || `${currentLine} ${word}`.length > maxCharacters) {
        lines.push(word);
      } else {
        lines[lines.length - 1] = `${currentLine} ${word}`;
      }
    }
    if (lines.length <= 2) return lines;
    const secondLine = lines.slice(1).join(" ");
    return [lines[0], `${secondLine.slice(0, maxCharacters - 1).trimEnd()}…`];
  };
  const nodeWidth = 224;
  const nodeHeight = 54;
  const rowGap = 18;
  const chartWidth = 1420;
  const chartPadding = 34;
  const columnGap = chapters.length > 1 ? (chartWidth - chartPadding * 2 - nodeWidth * chapters.length) / (chapters.length - 1) : 0;
  const maxRows = Math.max(1, ...chapters.map(chapter => nodes.filter(node => node.chapter === chapter.id).length));
  const chartHeight = 112 + maxRows * (nodeHeight + rowGap);
  const chapterById = useMemo(() => new Map(chapters.map(chapter => [chapter.id, chapter])), [chapters]);
  const nodeById = useMemo(() => new Map(nodes.map(node => [node.id, node])), [nodes]);
  const positions = useMemo(() => {
    const nextPositions = new Map();
    for (const node of nodes) {
      const chapterIndex = chapters.findIndex(chapter => chapter.id === node.chapter);
      nextPositions.set(node.id, {
        x: chartPadding + chapterIndex * (nodeWidth + columnGap),
        y: 94 + node.position * (nodeHeight + rowGap)
      });
    }
    return nextPositions;
  }, [chapters, nodes, columnGap]);
  const relatedNodeIds = useMemo(() => {
    if (!focusedNode) return new Set();
    const related = new Set([focusedNode]);
    for (const edge of edges) {
      if (edge.source === focusedNode) related.add(edge.target);
      if (edge.target === focusedNode) related.add(edge.source);
    }
    return related;
  }, [edges, focusedNode]);
  const visibleChapters = activeChapter === "all" ? chapters : chapters.filter(chapter => chapter.id === activeChapter);
  const nodeOpacity = node => {
    if (focusedNode) return relatedNodeIds.has(node.id) ? 1 : 0.16;
    if (activeChapter !== "all" && node.chapter !== activeChapter) return 0.12;
    return 1;
  };
  const edgeOpacity = edge => {
    if (focusedNode) {
      return edge.source === focusedNode || edge.target === focusedNode ? 0.95 : 0.05;
    }
    if (activeChapter !== "all") {
      const source = nodeById.get(edge.source);
      const target = nodeById.get(edge.target);
      return source?.chapter === activeChapter || target?.chapter === activeChapter ? 0.72 : 0.04;
    }
    return 0.34;
  };
  const edgePath = edge => {
    const source = positions.get(edge.source);
    const target = positions.get(edge.target);
    if (!source || !target) return "";
    const sourceNode = nodeById.get(edge.source);
    const targetNode = nodeById.get(edge.target);
    if (sourceNode?.chapter === targetNode?.chapter) {
      const x = source.x + nodeWidth / 2;
      const startY = source.y + nodeHeight;
      const endY = target.y;
      return `M ${x} ${startY} C ${x} ${startY + 18}, ${x} ${endY - 18}, ${x} ${endY}`;
    }
    const startX = source.x + nodeWidth;
    const startY = source.y + nodeHeight / 2;
    const endX = target.x;
    const endY = target.y + nodeHeight / 2;
    const middleX = (startX + endX) / 2;
    return `M ${startX} ${startY} C ${middleX} ${startY}, ${middleX} ${endY}, ${endX} ${endY}`;
  };
  if (!chapters.length || !nodes.length) {
    return <div className="not-prose rounded-xl border border-slate-200 bg-slate-50 p-6 text-sm text-slate-700 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-300">
        La carte du cours ne contient encore aucune page.
      </div>;
  }
  return <div className="not-prose my-8">
      <div className="mb-5 flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
        <div>
          <p className="m-0 text-sm font-semibold text-slate-950 dark:text-white">
            {nodes.length} pages · {edges.length} relations de prérequis
          </p>
          <p className="mt-1 mb-0 max-w-2xl text-sm leading-6 text-slate-600 dark:text-slate-400">
            Sélectionnez un chapitre, puis survolez une leçon pour isoler ses connexions.
          </p>
        </div>

        <div className="flex flex-wrap gap-2" aria-label="Filtrer la carte par chapitre">
          <button type="button" aria-pressed={activeChapter === "all"} onClick={() => setActiveChapter("all")} style={activeChapter === "all" ? {
    backgroundColor: "#7eb2d4",
    borderColor: "#7eb2d4",
    color: "#001e32"
  } : undefined} className={activeChapter === "all" ? "rounded-full border-2 border-sky-600 px-3 py-2 text-xs font-semibold text-sky-700 transition-transform active:scale-95 dark:border-sky-400 dark:text-sky-300" : "rounded-full border border-slate-200 bg-white px-3 py-2 text-xs font-semibold text-slate-600 transition-colors hover:border-slate-400 hover:text-slate-950 active:scale-95 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-300 dark:hover:border-slate-600 dark:hover:text-white"}>
            Tout le cours
          </button>

          {chapters.map(chapter => <button key={chapter.id} type="button" aria-pressed={activeChapter === chapter.id} onClick={() => setActiveChapter(chapter.id)} style={activeChapter === chapter.id ? {
    backgroundColor: "#7eb2d4",
    borderColor: "#7eb2d4",
    color: "#001e32"
  } : undefined} className={activeChapter === chapter.id ? "inline-flex items-center gap-2 rounded-full border-2 border-sky-600 px-3 py-2 text-xs font-semibold text-sky-700 transition-transform active:scale-95 dark:border-sky-400 dark:text-sky-300" : "inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white px-3 py-2 text-xs font-semibold text-slate-600 transition-colors hover:border-slate-400 hover:text-slate-950 active:scale-95 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-300 dark:hover:border-slate-600 dark:hover:text-white"}>
              <svg width="8" height="8" viewBox="0 0 8 8" aria-hidden="true">
                <circle cx="4" cy="4" r="4" fill={chapter.color} />
              </svg>
              {chapter.label}
            </button>)}
        </div>
      </div>

      <p className="sr-only" aria-live="polite">
        {activeChapter === "all" ? "Tous les chapitres sont mis en évidence." : `Le chapitre ${chapterById.get(activeChapter)?.label} est mis en évidence.`}
      </p>

      <div className="hidden overflow-x-auto rounded-2xl border border-slate-200 bg-slate-50 shadow-sm md:block dark:border-slate-800 dark:bg-slate-950">
        <svg role="navigation" aria-labelledby="course-graph-title course-graph-description" viewBox={`0 0 ${chartWidth} ${chartHeight}`} width="100%" className="h-auto min-w-[1280px]">
          <title id="course-graph-title">Carte des leçons du cours IA 101</title>
          <desc id="course-graph-description">
            Chaque colonne représente un chapitre. Les flèches relient un prérequis à la leçon qui en dépend.
          </desc>

          <defs>
            <marker id="course-graph-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
              <path d="M 0 0 L 10 5 L 0 10 z" fill="#94a3b8" />
            </marker>
          </defs>

          {chapters.map((chapter, chapterIndex) => {
    const x = chartPadding + chapterIndex * (nodeWidth + columnGap);
    const chapterNodes = nodes.filter(node => node.chapter === chapter.id);
    const opacity = activeChapter === "all" || activeChapter === chapter.id ? 1 : 0.2;
    return <g key={chapter.id} opacity={opacity} className="transition-opacity duration-300">
                <rect x={x} y="34" width="34" height="4" rx="2" fill={chapter.color} />
                <text x={x} y="59" className="fill-slate-950 text-sm font-semibold dark:fill-white">
                  {chapter.label}
                </text>
                <text x={x} y="78" className="fill-slate-500 text-xs dark:fill-slate-400">
                  {chapterNodes.length} {chapterNodes.length > 1 ? "pages" : "page"}
                </text>
              </g>;
  })}

          <g aria-hidden="true">
            {edges.map(edge => <path key={`${edge.source}-${edge.target}`} d={edgePath(edge)} fill="none" stroke="#94a3b8" strokeWidth={focusedNode ? 2.2 : 1.5} markerEnd="url(#course-graph-arrow)" opacity={edgeOpacity(edge)} className="transition-opacity duration-300" />)}
          </g>

          {nodes.map(node => {
    const position = positions.get(node.id);
    const chapter = chapterById.get(node.chapter);
    const labelLines = wrapGraphLabel(node.label);
    return <a key={node.id} href={node.href} role="link" tabIndex="0" aria-label={`Ouvrir la leçon : ${node.title}`} onMouseEnter={() => setFocusedNode(node.id)} onMouseLeave={() => setFocusedNode(null)} onFocus={() => setFocusedNode(node.id)} onBlur={() => setFocusedNode(null)} opacity={nodeOpacity(node)} className="group transition-opacity duration-300 focus:outline-none">
                <title>{node.title}</title>
                <rect x={position.x} y={position.y} width={nodeWidth} height={nodeHeight} rx="12" className="fill-white stroke-slate-300 transition-colors group-hover:stroke-slate-500 group-focus:stroke-sky-600 group-focus:stroke-2 dark:fill-slate-900 dark:stroke-slate-700 dark:group-hover:stroke-slate-400 dark:group-focus:stroke-sky-400" />
                <rect x={position.x} y={position.y} width="5" height={nodeHeight} rx="2.5" fill={chapter.color} />
                <circle cx={position.x + 25} cy={position.y + nodeHeight / 2} r="12" fill={chapter.color} opacity="0.16" />
                <text x={position.x + 25} y={position.y + nodeHeight / 2 + 4} textAnchor="middle" fill={chapter.color} className="text-xs font-semibold">
                  {node.position + 1}
                </text>
                <text x={position.x + 46} y={position.y + (labelLines.length === 1 ? 32 : 23)} className="fill-slate-900 text-xs font-medium dark:fill-slate-100">
                  {labelLines.map((line, index) => <tspan key={`${node.id}-${index}`} x={position.x + 46} dy={index === 0 ? 0 : 17}>
                      {line}
                    </tspan>)}
                </text>
              </a>;
  })}
        </svg>
      </div>

      <div className="space-y-8 md:hidden">
        {visibleChapters.map(chapter => {
    const chapterNodes = nodes.filter(node => node.chapter === chapter.id);
    return <section key={chapter.id} aria-labelledby={`mobile-${chapter.id}`}>
              <div className="mb-3 flex items-center gap-3">
                <svg width="28" height="4" viewBox="0 0 28 4" aria-hidden="true">
                  <rect width="28" height="4" rx="2" fill={chapter.color} />
                </svg>
                <h2 id={`mobile-${chapter.id}`} className="m-0 text-base font-semibold text-slate-950 dark:text-white">
                  {chapter.label}
                </h2>
              </div>

              <ol className="m-0 divide-y divide-slate-200 overflow-hidden rounded-xl border border-slate-200 bg-white p-0 dark:divide-slate-800 dark:border-slate-800 dark:bg-slate-950">
                {chapterNodes.map(node => {
      const prerequisiteTitles = node.prerequisites.map(href => nodes.find(candidate => candidate.href === href)?.label).filter(Boolean);
      return <li key={node.id} className="m-0 list-none p-0">
                      <a href={node.href} className="flex items-start gap-3 px-4 py-4 no-underline transition-colors hover:bg-slate-50 active:bg-slate-100 dark:hover:bg-slate-900 dark:active:bg-slate-800">
                        <span className="mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold" style={{
        color: chapter.color,
        backgroundColor: `${chapter.color}20`
      }}>
                          {node.position + 1}
                        </span>
                        <span>
                          <span className="block text-sm font-semibold text-slate-950 dark:text-white">
                            {node.label}
                          </span>
                          {prerequisiteTitles.length > 0 && <span className="mt-1 block text-xs leading-5 text-slate-500 dark:text-slate-400">
                              Après : {prerequisiteTitles.join(", ")}
                            </span>}
                        </span>
                      </a>
                    </li>;
    })}
              </ol>
            </section>;
  })}
      </div>

      <div className="mt-4 flex flex-wrap gap-x-6 gap-y-2 text-xs text-slate-500 dark:text-slate-400">
        <span className="inline-flex items-center gap-2">
          <svg width="30" height="8" viewBox="0 0 30 8" aria-hidden="true">
            <path d="M 0 4 H 25" stroke="#94a3b8" strokeWidth="1.5" />
            <path d="M 24 1 L 30 4 L 24 7 z" fill="#94a3b8" />
          </svg>
          Prérequis vers leçon suivante
        </span>
        <span>Un clic ouvre directement la leçon.</span>
      </div>
    </div>;
};

Cette carte présente l'organisation complète du cours. Chaque colonne correspond à un chapitre. Une flèche indique qu'une leçon est recommandée avant une autre.

<CourseGraph chapters={courseGraphChapters} nodes={courseGraphNodes} edges={courseGraphEdges} />

## Comment utiliser cette carte

* **Filtrez par chapitre** pour faire ressortir une partie du cours.
* **Survolez une leçon** pour isoler ses prérequis et les leçons qui en dépendent.
* **Cliquez sur un nœud** pour ouvrir directement la leçon.

<Tip>
  Vous pouvez suivre les colonnes dans l'ordre pour un parcours guidé, ou utiliser les connexions pour construire votre propre chemin d'apprentissage.
</Tip>
