diff --git a/components/CartePensees.vue b/components/CartePensees.vue
index fb402ba..42ade66 100644
--- a/components/CartePensees.vue
+++ b/components/CartePensees.vue
@@ -1,10 +1,201 @@
-
\ No newline at end of file
+ })
+
+ // ---- LIENS APPARTENANCE (couche 4) ----
+ const gLinks = g.append('g').attr('class', 'links-appartenance')
+ d3LinkSel = gLinks.selectAll('line').data(links).join('line')
+ .attr('stroke', 'rgba(150,150,150,0.28)').attr('stroke-width', 1.2)
+
+ // ---- EDGE LABELS - sous-courants (couche 4b) ----
+ const subcourantLinks = links.filter((l: any) => l.isSubcourant)
+ d3EdgeLabelSel = gLinks.selectAll('text.pensees-edge-label')
+ .data(subcourantLinks)
+ .join('text')
+ .attr('class', 'pensees-edge-label')
+
+ // ---- NODES AUTEURS (couche 5) ----
+ const gAuteurs = g.append('g').attr('class', 'auteurs')
+ d3NodeSel = gAuteurs.selectAll('g').data(auteurNodes).join('g')
+ .style('cursor', (d: any) => d.ingere ? 'pointer' : 'default')
+ .call(d3.drag()
+ .on('start', (e: any, d: any) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y })
+ .on('drag', (e: any, d: any) => { d.fx = e.x; d.fy = e.y })
+ .on('end', (e: any, d: any) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null }))
+ .on('click', (e: any, d: any) => {
+ if (!d.ingere) return
+ e.stopPropagation()
+ emit('select-auteur', d.id)
+ })
+
+ // Phase 8.D : grisage conditionnel auteurs non-ingeres
+ d3NodeSel.append('circle')
+ .attr('r', (d: any) => d.r)
+ .attr('fill', (d: any) => d.ingere ? (d.color + 'cc') : '#bbbbbb')
+ .attr('stroke', (d: any) => d.ingere ? d.color : '#999999')
+ .attr('stroke-width', 1.5)
+ .attr('opacity', (d: any) => d.ingere ? 1 : 0.35)
+
+ // ---- LABELS AUTEURS (couche 6 - drop-shadow blanc) ----
+ d3NodeSel.append('text')
+ .attr('class', 'pensees-auteur-label')
+ .text((d: any) => d.nom.split(' ').pop() ?? d.nom)
+ .attr('text-anchor', 'middle')
+ .attr('dy', (d: any) => -(d.r + 4))
+ .style('pointer-events', 'none')
+ .style('opacity', (d: any) => d.ingere ? 1 : 0.3)
+ .style('fill', (d: any) => d.ingere ? '#1a1a1a' : '#777777')
+
+ d3NodeSel
+ .on('mouseenter', (e: any, d: any) => {
+ if (!tooltipRef.value) return
+ let tooltipHtml = ''
+ if (d.ingere) {
+ const rawBio = d.bio_courte || ''
+ const bio = rawBio.length > 90 ? rawBio.slice(0, 87) + '...' : rawBio
+ tooltipHtml = `${d.nom} ${d.dates}
${bio || 'Dans le RAG ATIS.'}`
+ } else {
+ tooltipHtml = `${d.nom} ${d.dates}
Présent dans Bonpote, pas encore ingéré dans le RAG ATIS.`
+ }
+ tooltipRef.value.innerHTML = tooltipHtml
+ tooltipRef.value.style.opacity = '1'
+ })
+ .on('mousemove', (e: any) => {
+ if (!tooltipRef.value || !svgEl) return
+ const rect = (svgEl as HTMLElement).getBoundingClientRect()
+ tooltipRef.value.style.left = (e.clientX - rect.left + 14) + 'px'
+ tooltipRef.value.style.top = (e.clientY - rect.top - 10) + 'px'
+ })
+ .on('mouseleave', () => { if (tooltipRef.value) tooltipRef.value.style.opacity = '0' })
+
+ simulation.on('tick', () => {
+ d3LinkSel
+ .attr('x1', (d: any) => d.source.x).attr('y1', (d: any) => d.source.y)
+ .attr('x2', (d: any) => d.target.x).attr('y2', (d: any) => d.target.y)
+
+ d3EdgeLabelSel
+ .attr('x', (d: any) => (d.source.x + d.target.x) / 2)
+ .attr('y', (d: any) => (d.source.y + d.target.y) / 2)
+ .text((d: any) => {
+ const targetId = typeof d.target === 'object' ? d.target.id : d.target
+ return targetId
+ })
+
+ d3NodeSel.attr('transform', (d: any) => `translate(${d.x},${d.y})`)
+ })
+}
+
+watch(() => props.active, (val) => {
+ if (val && import.meta.client && props.data)
+ requestAnimationFrame(() => requestAnimationFrame(() => initGraph()))
+})
+watch(() => props.data, (val) => {
+ if (val && props.active && import.meta.client)
+ requestAnimationFrame(() => requestAnimationFrame(() => initGraph()))
+})
+onMounted(async () => {
+ if (import.meta.client && props.data && props.active) {
+ await nextTick()
+ initGraph()
+ }
+})
+onUnmounted(() => { if (simulation) simulation.stop() })
+
+function triggerResize() {
+ if (simulation) {
+ simulation.alpha(0.3).restart()
+ } else if (import.meta.client && props.data && props.active) {
+ initGraph()
+ }
+}
+defineExpose({ triggerResize })
+
+
+