feat: PC3 mindmap Carte O (D3 force-directed) + scrape AEP/Articles + tabs centre HAUT

- scripts/build-carte-o.js : scan recursif AEP/Articles/, parse YAML + legacy header, extract wikilinks, infer 5 famille
- src/components/vue/CarteO.vue : D3 v7 force-directed avec drag, zoom + pan, click handler, tooltips, ResizeObserver
- src/components/vue/CarteOModal.vue : modal recap intention avec Teleport, Esc + backdrop close, transitions
- src/components/vue/CarteOWrapper.vue : fetch /data/carte-o.json, etat selectionne, fallback mobile (msg + miniature SVG)
- src/components/astro/ColCentre.astro : tabs Carte O / Chatbot, panneaux ARIA
- package.json : prebuild + predev hooks, build:carte-o script
- public/data/carte-o.json : 84 nodes / 94 edges sur 21 themes, distribution familles equilibree

Drill-down V1 = zoom + pan seul (V2 recursif backlog).
Pattern adapte de nav-carte/components/codev/CodevGraph.vue (sans coupling Nuxt).
Build Astro 6.3.1 OK, bundle CarteOWrapper 69KB.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jules Neny
2026-05-09 00:59:23 +02:00
parent aeaec6fc06
commit 32bdc9a2e5
8 changed files with 3423 additions and 7 deletions

View File

@@ -1,11 +1,88 @@
---
// Placeholder Centre : HAUT mindmap AEP (PC3) ; BAS iframe carte AEP (PC4)
// Centre - HAUT : tabs (Carte O mindmap | Chatbot RAG placeholder PC7).
// BAS : iframe carte AEP (PC4).
import CarteOWrapper from '../vue/CarteOWrapper.vue';
import ChatbotPlaceholder from '../vue/ChatbotPlaceholder.vue';
---
<div class="h-full grid grid-rows-2 gap-2 p-2">
<section class="border border-dashed border-neutral-300 rounded flex items-center justify-center">
<p class="text-sm text-neutral-400">Mindmap AEP — PC3</p>
<!-- HAUT 50% : tabs Carte O / Chatbot -->
<section class="border border-neutral-200 rounded flex flex-col overflow-hidden bg-white">
<nav role="tablist" aria-label="Vues centrales" class="flex border-b border-neutral-200 px-1 pt-1">
<button
type="button"
role="tab"
id="tab-mindmap"
aria-controls="panel-mindmap"
aria-selected="true"
data-tab="mindmap"
class="tab-btn px-3 py-2 text-sm border-b-2 border-neutral-900 font-medium text-neutral-900"
>
Carte O
</button>
<button
type="button"
role="tab"
id="tab-chatbot"
aria-controls="panel-chatbot"
aria-selected="false"
data-tab="chatbot"
class="tab-btn px-3 py-2 text-sm border-b-2 border-transparent text-neutral-500 hover:text-neutral-900"
>
Chatbot
</button>
</nav>
<div class="flex-1 overflow-hidden relative">
<div
id="panel-mindmap"
role="tabpanel"
aria-labelledby="tab-mindmap"
data-tab-panel="mindmap"
class="absolute inset-0"
>
<CarteOWrapper client:visible />
</div>
<div
id="panel-chatbot"
role="tabpanel"
aria-labelledby="tab-chatbot"
data-tab-panel="chatbot"
class="absolute inset-0 hidden"
>
<ChatbotPlaceholder client:visible />
</div>
</div>
</section>
<!-- BAS 50% : iframe carte AEP (PC4) -->
<section class="border border-dashed border-neutral-300 rounded flex items-center justify-center">
<p class="text-sm text-neutral-400">Iframe carte AEP — PC4</p>
</section>
</div>
<script>
// Tabs toggle.
const tabs = document.querySelectorAll<HTMLButtonElement>('[data-tab]');
const panels = document.querySelectorAll<HTMLElement>('[data-tab-panel]');
tabs.forEach((tab) => {
tab.addEventListener('click', () => {
const target = tab.dataset.tab;
if (!target) return;
tabs.forEach((t) => {
const active = t.dataset.tab === target;
t.setAttribute('aria-selected', active ? 'true' : 'false');
t.classList.toggle('border-neutral-900', active);
t.classList.toggle('border-transparent', !active);
t.classList.toggle('font-medium', active);
t.classList.toggle('text-neutral-900', active);
t.classList.toggle('text-neutral-500', !active);
});
panels.forEach((p) => {
p.classList.toggle('hidden', p.dataset.tabPanel !== target);
});
});
});
</script>