feat(media): rename /pensees-ecologiques → /media + corpus réel + 12 écoles FRACAS Bonpote

- Page pages/pensees-ecologiques.vue → pages/media.vue (titre "ATIS Média")
- Labels onglet/menu "Pensées" → "Média" (app.vue, agences, index, filters)
- auteurs-pensees.json reconciled avec 141 docs LightRAG (était 27)
  · 28 auteurs (était 18), 64 livres, slugs corrigés (ex: bookchin-ecologie-liberte)
  · 12 écoles: 8 familles FRACAS Bonpote + 4 extensions ATIS
  · Labels alignés Bonpote: Écologies libertaires (ex eco-anarchisme),
    Écologies anti-industrielles (ex technocritique)
  · Familles Bonpote ajoutées: Capitalisme vert + Écofascismes
    (corpus_status: non_ingere — fidélité carte, critique éditoriale assumée)

V2 Phase 2.3 — corpus réel reflété, alignement Bonpote initial
This commit is contained in:
Jules Neny
2026-05-11 23:21:49 +02:00
parent 538c9a1214
commit 11732a6a4b
10 changed files with 548 additions and 174 deletions

View File

@@ -52,9 +52,10 @@
<div class="chatbot-body-inner" ref="messagesContainer">
<!-- Onboarding -->
<div v-if="messages.length === 0" class="onboarding-bubble">
<p>Explore les 120 structures de la carte par la conversation. Je peux t'aider à trouver des collectifs, agences ou réseaux selon ta situation, ta pratique ou tes inspirations du moment.</p>
<p class="example">Exemple : "Je cherche des acteurs de la rénovation de maisons individuelles en France, plutôt en milieu rural, avec des approches biosourcées ou low-tech."</p>
<p style="margin-top: 8px; font-size: 0.72rem; opacity: 0.6;">Propulsé par Mistral FR - serveur européen souverain, zéro rétention.</p>
<p>Je connais les structures d'entraide pour architectes référencées sur cette carte — appui juridique, technique, économique, formation, santé mentale, gestion d'agence</p>
<p>Décris ta situation, je te propose les fiches les plus pertinentes.</p>
<p class="example">Exemple : "Architecte salarié, litige avec mon employeur, besoin d'un appui juridique droit du travail, Île-de-France."</p>
<p style="margin-top: 8px; font-size: 0.72rem; opacity: 0.6;">Propulsé par Mistral FR serveur européen souverain, zéro rétention.</p>
</div>
<!-- Messages -->

View File

@@ -1,38 +1,16 @@
<template>
<div class="space-y-1.5">
<p class="text-xs font-bold uppercase tracking-widest" style="color: var(--nav-text-muted);">Échelle</p>
<!-- Inline sur 1 ligne même pattern que FonctionFilter -->
<div class="flex flex-wrap gap-x-4 gap-y-1.5">
<label
<div class="space-y-1">
<p class="filter-label">ÉCHELLE</p>
<div class="chips-row">
<span
v-for="option in ECHELLES"
:key="option"
class="flex items-center gap-1.5 cursor-pointer select-none transition-opacity"
>
<!-- Case carrée -->
<span
class="flex items-center justify-center shrink-0 transition-all"
style="width: 18px; height: 18px; border: 1.5px solid; border-radius: 3px;"
:style="isSelected(option)
? 'background: var(--nav-primary); border-color: var(--nav-primary); color: #ffffff;'
: 'background: var(--nav-bg-alt); border-color: rgba(26,34,56,0.25); color: transparent;'"
>
<svg v-if="isSelected(option)" width="11" height="11" viewBox="0 0 12 12" fill="none">
<polyline points="2,6 5,9 10,3" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<!-- Label -->
<span
class="text-sm leading-tight"
:style="isSelected(option) ? 'color: var(--nav-text); font-weight: 600;' : 'color: var(--nav-text);'"
>{{ option }}</span>
<!-- Input réel (masqué) -->
<input
type="checkbox"
class="sr-only"
:checked="isSelected(option)"
@change="toggle(option)"
/>
</label>
class="chip"
:style="isSelected(option)
? 'background: var(--nav-primary); color: var(--nav-text-on-primary); font-weight: 600;'
: 'background: var(--nav-bg-alt); color: var(--nav-text-muted);'"
@click="toggle(option)"
>{{ option }}</span>
</div>
</div>
</template>
@@ -61,3 +39,24 @@ function toggle(option: string) {
}
}
</script>
<style scoped>
.filter-label {
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.08em;
color: var(--nav-text-muted);
display: block;
margin-bottom: 4px;
text-transform: uppercase;
}
.chips-row { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; }
.chip {
cursor: pointer;
padding: 3px 10px;
border-radius: 9999px;
font-size: 0.75rem;
transition: all 0.15s;
user-select: none;
}
</style>

View File

@@ -1,35 +1,33 @@
<template>
<div class="space-y-1.5">
<p class="text-xs font-bold uppercase tracking-widest" style="color: var(--nav-text-muted);">Fonction</p>
<div class="space-y-1">
<div class="space-y-1">
<!-- Label + toggle collapse -->
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px;">
<p class="filter-label" style="margin-bottom: 0;">
FONCTION
<span v-if="modelValue.length" style="font-weight: 400; text-transform: none; letter-spacing: 0; font-size: 0.65rem; margin-left: 4px;">({{ modelValue.length }} active{{ modelValue.length > 1 ? 's' : '' }})</span>
</p>
<button
@click="toggleCollapse"
style="font-size: 0.7rem; color: var(--nav-text-muted); background: none; border: none; cursor: pointer; text-decoration: underline; padding: 0; white-space: nowrap;"
>{{ isOpen ? 'Replier' : 'Fonctions (' + FONCTIONS.length + ')' }}</button>
</div>
<!-- Chips (visible si ouvert ou si des fonctions sont actives) -->
<div v-if="isOpen" class="chips-row">
<span
v-for="fn in FONCTIONS"
:key="fn"
class="chip"
:style="modelValue.includes(fn)
? 'background: var(--nav-primary); color: var(--nav-text-on-primary); font-weight: 600;'
: 'background: var(--nav-bg-alt); color: var(--nav-text-muted);'"
@click="toggle(fn)"
:aria-pressed="modelValue.includes(fn)"
class="flex items-center gap-2.5 w-full rounded px-1 py-0.5 transition-all text-left hover:opacity-80"
:style="modelValue.includes(fn) ? 'background: rgba(26,34,56,0.06);' : ''"
>
<!-- Case : affiche le rang de priorité si actif, sinon le nombre d'orgs -->
<span
class="flex items-center justify-center shrink-0 text-xs font-bold transition-all"
style="width: 24px; height: 24px; border: 1.5px solid; border-radius: 4px;"
:style="modelValue.includes(fn)
? 'background: var(--nav-primary); border-color: var(--nav-primary); color: var(--nav-text-on-primary);'
: 'background: var(--nav-bg-alt); border-color: var(--nav-bg-alt); color: var(--nav-text-muted);'"
>
{{ modelValue.includes(fn) ? (modelValue.indexOf(fn) + 1) : (counts[fn] ?? 0) }}
</span>
<!-- Label -->
<span
class="text-sm leading-tight"
:style="modelValue.includes(fn) ? 'color: var(--nav-text); font-weight: 600;' : 'color: var(--nav-text);'"
>{{ fn }}</span>
</button>
>{{ fn }}</span>
</div>
<!-- Effacer (visible même replié si filtres actifs) -->
<p v-if="modelValue.length" class="text-xs pt-0.5" style="color: var(--nav-text-muted);">
{{ modelValue.length }} actif{{ modelValue.length > 1 ? 's' : '' }}
<button @click="emit('update:modelValue', [])" class="ml-2 underline hover:opacity-70">Effacer</button>
<button @click="emit('update:modelValue', [])" class="underline hover:opacity-70">Effacer</button>
</p>
</div>
</template>
@@ -57,6 +55,25 @@ const emit = defineEmits<{
'update:modelValue': [value: string[]]
}>()
// Replié par défaut, ouvre automatiquement quand des filtres sont actifs
const manuallyOpen = ref(false)
const isOpen = computed(() => {
return manuallyOpen.value || props.modelValue.length > 0
})
function toggleCollapse() {
// Si des filtres actifs forcent l'ouverture, on doit gérer le cas « forcer fermer »
if (isOpen.value) {
manuallyOpen.value = false
// Si des fonctions sont actives, le computed va les réouvrir — on les efface
// Non : on laisse le choix à l'utilisateur. On toggle juste manuallyOpen.
// Quand replié avec filtres actifs, l'indicateur "(N actives)" reste visible.
} else {
manuallyOpen.value = true
}
}
function toggle(fn: string) {
if (props.modelValue.includes(fn)) {
emit('update:modelValue', props.modelValue.filter(f => f !== fn))
@@ -65,3 +82,23 @@ function toggle(fn: string) {
}
}
</script>
<style scoped>
.filter-label {
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.08em;
color: var(--nav-text-muted);
display: block;
text-transform: uppercase;
}
.chips-row { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; }
.chip {
cursor: pointer;
padding: 3px 10px;
border-radius: 9999px;
font-size: 0.75rem;
transition: all 0.15s;
user-select: none;
}
</style>

View File

@@ -877,6 +877,7 @@ onUnmounted(() => {
/* Labels des structures dans le graphe (D3 injecte les <text>, donc style global) */
.graph-view .graph-struct-label {
fill: var(--nav-text);
opacity: 0.7;
paint-order: stroke;
stroke: var(--nav-bg);
stroke-width: 3px;