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:
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user