Compare commits

...
Author SHA1 Message Date
JulesandClaude Opus 5.5 58937a04a3 feat(aep): Soutenir dans le menu déroulant, bandeau bas retiré, cartes Mistral et « souverain » retirés
Décisions Jules du 27/09 : ① À propos sans les cartes Mistral Nemo / Small
(modèles qui ne sont plus en service), Le Chat sans « souverain » (outils.vue
et fmhy-curated.json) ; ② le bandeau bas (transparence IA + don) est retiré,
le menu déroulant devient visible à toutes les largeurs et porte un bouton
Soutenir → liberapay.com/trans-former.fr/donate (onglets masqués dedans ≥1024px).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:49:26 +02:00
JulesandClaude Opus 5.5 38818f77ae feat(aep): têtes Jobs (S) et Réseaux AEP (M) + retrait des mentions Mistral
Arbitrages de Jules du 26/09 (table R §6) : 2A Jobs en S (tête ← slide 16),
1A Réseaux AEP en M (tête ← slide 17), 6 retrait de « Propulsé par Mistral »
et du badge « IA souveraine : Mistral AI », sans rien affirmer à la place.

- pages/trouver-du-taf.vue : EchelleTete S sous le titre, centrée comme l'en-tête ;
  note du chatbot réduite à « 20 questions/jour ».
- pages/agences.vue : popup Réseaux avec echelle="M".
- ChatbotPlaceholder, ChatbotSheet : phrase « serveur européen souverain
  (Mistral FR, zéro rétention) » retirée.
- a-propos : badge « IA souveraine » retiré.
Non touchés : cartes ia-card « Mistral Nemo / Small » d'À propos, « Mistral Le Chat »
d'Outils (hors consigne).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:42:19 +02:00
JulesandClaude Opus 5.5 d9f934a8b2 feat(aep): têtes d'onglet S / M / XL (slides 16-18 du deck du 23/09) + lien mobile recherche-média
- utils/echelles.ts : texte des slides 15 à 18, mot pour mot, source unique
  (la future nav S / M / XL lira ce fichier).
- EchelleTete.vue : badge d'échelle, titre, sept mots, phrase, ligne
  « Chaque section est ouverte aux contributions » vers /proposer.
- Placée en tête du texte existant, sans le remplacer : Outils (S),
  popup Écosystème Entraide (M), page Codev (M), popup média (XL).
  Réseaux AEP et Jobs attendent les arbitrages n° 1 et 2 (table R §6).
- /proposer?categorie=<id> présélectionne l'onglet.
- Menu mobile : « recherche-média » pointait sur /rag (301), pointe sur /media,
  état actif comme les autres liens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 17:58:31 +02:00
16 changed files with 228 additions and 572 deletions
+10 -13
View File
@@ -163,8 +163,8 @@
</svg>
</NuxtLink>
<!-- Hamburger mobile (lg:hidden) — toujours en dernier à droite -->
<div class="lg:hidden relative">
<!-- Menu déroulant — toutes largeurs (les onglets n'y apparaissent qu'en dessous de 1024px) ; porte « Soutenir » -->
<div class="relative">
<button
@click="hamburgerOpen = !hamburgerOpen"
class="p-2 rounded-lg transition-all hover:opacity-80"
@@ -185,16 +185,16 @@
style="background: var(--nav-surface); border: 1px solid var(--nav-bg-alt); z-index: 9999;"
@click="hamburgerOpen = false"
>
<NuxtLink to="/outils" class="block px-4 py-2.5 text-sm font-medium transition-opacity hover:opacity-70" :style="route.path === '/outils' ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">Outils</NuxtLink>
<NuxtLink to="/" class="block px-4 py-2.5 text-sm font-medium transition-opacity hover:opacity-70" :style="route.path === '/' ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">Écosystème Entraide</NuxtLink>
<NuxtLink to="/agences" class="block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" :style="route.path === '/agences' ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">Réseaux AEP</NuxtLink>
<NuxtLink to="/trouver-du-taf" class="block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" :style="route.path === '/trouver-du-taf' ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">Jobs</NuxtLink>
<NuxtLink to="/rag" class="block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" style="color: var(--nav-text);">recherche-média</NuxtLink>
<NuxtLink to="/codev" class="block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" :style="route.path.startsWith('/codev') ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">Codev</NuxtLink>
<div style="height: 1px; background: var(--nav-bg-alt); margin: 4px 0;"></div>
<NuxtLink to="/outils" class="lg:hidden block px-4 py-2.5 text-sm font-medium transition-opacity hover:opacity-70" :style="route.path === '/outils' ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">Outils</NuxtLink>
<NuxtLink to="/" class="lg:hidden block px-4 py-2.5 text-sm font-medium transition-opacity hover:opacity-70" :style="route.path === '/' ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">Écosystème Entraide</NuxtLink>
<NuxtLink to="/agences" class="lg:hidden block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" :style="route.path === '/agences' ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">Réseaux AEP</NuxtLink>
<NuxtLink to="/trouver-du-taf" class="lg:hidden block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" :style="route.path === '/trouver-du-taf' ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">Jobs</NuxtLink>
<NuxtLink to="/media" class="lg:hidden block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" :style="route.path.startsWith('/media') ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">recherche-média</NuxtLink>
<NuxtLink to="/codev" class="lg:hidden block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" :style="route.path.startsWith('/codev') ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text);'">Codev</NuxtLink>
<div class="lg:hidden" style="height: 1px; background: var(--nav-bg-alt); margin: 4px 0;"></div>
<NuxtLink to="/manifeste" class="block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" :style="route.path === '/manifeste' ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text-muted);'">Manifeste</NuxtLink>
<NuxtLink to="/a-propos" class="block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" style="color: var(--nav-text-muted);">À propos</NuxtLink>
<a href="https://liberapay.com/trans-former.fr/donate" target="_blank" rel="noopener noreferrer" class="block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" style="color: var(--nav-text-muted);">Soutenir →</a>
<a href="https://liberapay.com/trans-former.fr/donate" target="_blank" rel="noopener noreferrer" class="block mx-3 my-2 px-4 py-2 rounded-lg text-sm font-semibold text-center transition-opacity hover:opacity-80" style="background: var(--nav-accent); color: var(--nav-text);">Soutenir</a>
<NuxtLink to="/signaler" class="block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" style="color: var(--nav-text-muted);">Signaler</NuxtLink>
</div>
</div>
@@ -206,9 +206,6 @@
<NuxtPage />
</div>
<!-- Bandeau bas — transparence IA + soutien + compteurs semaine -->
<BandeauBas />
</div>
</template>
-488
View File
@@ -1,488 +0,0 @@
<template>
<!-- ═══════════════════════════════════════ BANDEAU BAS ═══════════════════ -->
<!-- DESKTOP uniquement (≥1024px) — mobile a le FAB séparé -->
<footer
v-if="!isMobile"
ref="bandeauEl"
class="bandeau-bas shrink-0"
:class="{ 'bandeau-collapsed': isCollapsed }"
aria-label="Informations projet AEP"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
>
<!-- Contenu plein -->
<div class="bandeau-inner" :class="{ 'bandeau-inner--hidden': isCollapsed }">
<!-- ── GAUCHE : Transparence IA ──────────────────────────────────────── -->
<div class="bandeau-col">
<p class="bandeau-label">Transparence IA</p>
<template v-if="stats">
<p class="bandeau-value">
Coût IA ce mois : <strong>{{ stats.cout_mois_eur.toFixed(2) }} €</strong>
&nbsp;·&nbsp;
Tokens : <strong>{{ stats.tokens_mois.toLocaleString('fr-FR') }}</strong>
</p>
<!-- Jauge -->
<div class="jauge-track" aria-label="Budget IA consommé" role="progressbar" :aria-valuenow="jaugePct" aria-valuemin="0" aria-valuemax="100">
<div class="jauge-fill" :style="{ width: jaugePct + '%' }" />
</div>
<p class="bandeau-sub">
{{ stats.requetes_mois }} requête{{ stats.requetes_mois !== 1 ? 's' : '' }} ce mois
</p>
</template>
<template v-else-if="loading">
<p class="bandeau-sub">Chargement…</p>
</template>
<template v-else>
<p class="bandeau-sub">Données indisponibles</p>
</template>
</div>
<!-- ── MILIEU : CTA Soutien ──────────────────────────────────────────── -->
<div class="bandeau-col bandeau-col--center">
<div class="soutenir-wrap">
<button
class="btn-soutenir"
type="button"
@click="modalOpen = true"
@mouseenter="tooltipVisible = true"
@mouseleave="tooltipVisible = false"
@focus="tooltipVisible = true"
@blur="tooltipVisible = false"
aria-label="Soutenir le projet AEP sur Liberapay"
>
Soutenir le projet
</button>
<!-- Tooltip au hover -->
<div v-if="tooltipVisible" class="soutenir-tooltip" role="tooltip">
1 € = 30 fiches mises en ligne
</div>
</div>
</div>
<!-- ── DROITE : Compteurs semaine ────────────────────────────────────── -->
<div class="bandeau-col bandeau-col--right">
<p class="bandeau-label">Cette semaine</p>
<template v-if="stats">
<p class="bandeau-value">
{{ stats.fiches_semaine }} fiche{{ stats.fiches_semaine !== 1 ? 's' : '' }} ajoutée{{ stats.fiches_semaine !== 1 ? 's' : '' }}
</p>
<p class="bandeau-sub">
{{ stats.requetes_chatbot_semaine }} requête{{ stats.requetes_chatbot_semaine !== 1 ? 's' : '' }} chatbot
</p>
</template>
<template v-else>
<p class="bandeau-sub">—</p>
</template>
</div>
</div>
<!-- Barre fine (état collapsed) -->
<div class="bandeau-thin" :class="{ 'bandeau-thin--visible': isCollapsed }">
<span class="bandeau-thin-label">AEP · Transparence IA</span>
</div>
<!-- ── MODAL Liberapay ───────────────────────────────────────────────── -->
<Teleport to="body">
<Transition name="backdrop">
<div
v-if="modalOpen"
class="modal-backdrop"
@click="modalOpen = false"
aria-hidden="true"
/>
</Transition>
<Transition name="modal">
<div
v-if="modalOpen"
class="modal-box"
role="dialog"
aria-modal="true"
aria-label="Soutenir AEP sur Liberapay"
>
<button
class="modal-close"
type="button"
@click="modalOpen = false"
aria-label="Fermer"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
<h2 class="modal-title">Soutenir AEP</h2>
<p class="modal-desc">
AEP est un outil libre, sans publicité, financé par les dons.
1 € finance environ 30 fiches mises en ligne.
</p>
<div class="modal-widget">
<iframe
src="https://liberapay.com/trans-former.fr/widgets/button.html"
width="95"
height="22"
style="border: 0;"
title="Faire un don sur Liberapay"
/>
</div>
<a
href="https://liberapay.com/trans-former.fr/donate"
target="_blank"
rel="noopener noreferrer"
class="modal-link"
>
Faire un don sur Liberapay →
</a>
</div>
</Transition>
</Teleport>
</footer>
<!-- Mobile (< 1024px) : pas de FAB — Soutenir est dans le menu hamburger -->
</template>
<script setup lang="ts">
interface Stats {
cout_mois_eur: number
budget_mois: number
tokens_mois: number
co2_kg: number
requetes_mois: number
fiches_semaine: number
requetes_chatbot_semaine: number
}
const stats = ref<Stats | null>(null)
const loading = ref(true)
const modalOpen = ref(false)
const tooltipVisible = ref(false)
// Desktop — replié par défaut, déploie au hover, replie immédiatement à la sortie
const bandeauEl = ref<HTMLElement | null>(null)
const isCollapsed = ref(true) // replié par défaut
const REFRESH_MS = 5 * 60 * 1000 // 5 minutes
// Détection mobile côté client
const isMobile = ref(false)
onMounted(() => {
isMobile.value = window.innerWidth < 1024
const handleResize = () => {
isMobile.value = window.innerWidth < 1024
}
window.addEventListener('resize', handleResize)
onUnmounted(() => window.removeEventListener('resize', handleResize))
fetchStats()
const interval = setInterval(fetchStats, REFRESH_MS)
onUnmounted(() => clearInterval(interval))
})
function onMouseEnter() {
isCollapsed.value = false
}
function onMouseLeave() {
// Repli immédiat — pas de timer
isCollapsed.value = true
}
async function fetchStats() {
try {
const res = await $fetch<Stats>('/api/stats')
stats.value = res
} catch {
stats.value = null
} finally {
loading.value = false
}
}
const jaugePct = computed(() => {
if (!stats.value) return 0
return Math.min(100, Math.round((stats.value.cout_mois_eur / stats.value.budget_mois) * 100))
})
</script>
<style scoped>
/* ── Bandeau bas ─────────────────────────────────────────────────────────── */
.bandeau-bas {
background: rgba(26, 34, 56, 0.7); /* opacité 70% */
color: var(--nav-text-on-primary);
font-family: var(--nav-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
border-top: 1px solid rgba(255, 255, 255, 0.08);
transition: min-height 0.25s ease;
position: relative;
overflow: hidden;
}
.bandeau-collapsed {
min-height: 32px !important;
cursor: pointer;
}
.bandeau-inner {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
padding: 10px 20px;
max-width: 1400px;
margin: 0 auto;
min-height: 64px;
transition: opacity 0.2s ease, max-height 0.3s ease;
max-height: 200px;
opacity: 1;
}
.bandeau-inner--hidden {
opacity: 0;
max-height: 0;
overflow: hidden;
padding-top: 0;
padding-bottom: 0;
min-height: 0;
}
/* Barre fine (collapsed) */
.bandeau-thin {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.bandeau-thin--visible {
opacity: 1;
}
.bandeau-thin-label {
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
opacity: 0.7;
}
/* ── Colonnes ────────────────────────────────────────────────────────────── */
.bandeau-col {
flex: 1;
display: flex;
flex-direction: column;
gap: 3px;
min-width: 0;
}
.bandeau-col--center {
align-items: center;
justify-content: center;
flex: 0 0 auto;
padding-top: 8px; /* décaler légèrement vers le bas pour mieux centrer dans la hauteur */
}
.bandeau-col--right {
align-items: flex-end;
}
/* ── Typo ────────────────────────────────────────────────────────────────── */
.bandeau-label {
font-size: 0.65rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
opacity: 0.65;
margin: 0;
line-height: 1.3;
}
.bandeau-value {
font-size: 0.775rem;
font-weight: 500;
color: var(--nav-text-on-primary);
margin: 0;
line-height: 1.4;
}
.bandeau-value strong {
font-weight: 700;
}
.bandeau-sub {
font-size: 0.68rem;
opacity: 0.7;
margin: 0;
line-height: 1.4;
}
/* ── Jauge budget ────────────────────────────────────────────────────────── */
.jauge-track {
width: 100%;
max-width: 180px;
height: 4px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.18);
overflow: hidden;
margin: 3px 0;
}
.jauge-fill {
height: 100%;
border-radius: 2px;
background: var(--nav-accent);
transition: width 0.6s ease;
}
/* ── Bouton soutenir + tooltip ───────────────────────────────────────────── */
.soutenir-wrap {
position: relative;
display: inline-flex;
flex-direction: column;
align-items: center;
}
.btn-soutenir {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 7px 16px;
border-radius: 8px;
border: none;
background: var(--nav-accent);
color: var(--nav-text);
font-size: 0.8rem;
font-weight: 700;
font-family: inherit;
cursor: pointer;
white-space: nowrap;
transition: opacity 0.15s, transform 0.1s;
flex-shrink: 0;
}
.btn-soutenir:hover { opacity: 0.88; transform: translateY(-1px); }
.btn-soutenir:active { opacity: 1; transform: translateY(0); }
.soutenir-tooltip {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: var(--nav-primary-solid, #1a2238);
color: var(--nav-text-on-primary);
font-size: 0.72rem;
font-weight: 600;
padding: 5px 10px;
border-radius: 6px;
white-space: nowrap;
pointer-events: none;
z-index: 10;
}
.soutenir-tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 5px solid transparent;
border-top-color: var(--nav-primary-solid, #1a2238);
}
/* ── Modal ───────────────────────────────────────────────────────────────── */
.modal-backdrop {
position: fixed;
inset: 0;
z-index: 2000;
background: rgba(26, 34, 56, 0.55);
}
.modal-box {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2001;
background: var(--nav-surface, #ffffff);
border-radius: 16px;
padding: 28px 24px 24px;
width: min(380px, 90vw);
box-shadow: 0 8px 40px rgba(26, 34, 56, 0.22);
display: flex;
flex-direction: column;
gap: 12px;
}
.modal-close {
position: absolute;
top: 14px;
right: 14px;
width: 30px;
height: 30px;
border-radius: 8px;
border: none;
background: var(--nav-bg-alt, #eee9df);
color: var(--nav-text-muted, rgba(26,34,56,0.55));
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s;
}
.modal-close:hover { background: var(--nav-bg, #f8f6f1); }
.modal-title {
font-size: 1.05rem;
font-weight: 700;
color: var(--nav-text, #1a2238);
margin: 0;
}
.modal-desc {
font-size: 0.85rem;
color: var(--nav-text-muted, rgba(26,34,56,0.55));
line-height: 1.6;
margin: 0;
}
.modal-widget {
display: flex;
justify-content: center;
padding: 12px 0;
}
.modal-link {
display: block;
text-align: center;
font-size: 0.8rem;
color: var(--nav-primary-solid, #1a2238);
text-decoration: underline;
transition: opacity 0.15s;
}
.modal-link:hover { opacity: 0.7; }
/* ── Transitions ─────────────────────────────────────────────────────────── */
.backdrop-enter-active, .backdrop-leave-active { transition: opacity 0.2s ease; }
.backdrop-enter-from, .backdrop-leave-to { opacity: 0; }
.modal-enter-active, .modal-leave-active { transition: opacity 0.2s ease, transform 0.2s ease; }
.modal-enter-from, .modal-leave-to { opacity: 0; transform: translate(-50%, -48%); }
.sheet-enter-active, .sheet-leave-active { transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1); }
.sheet-enter-from, .sheet-leave-to { transform: translateY(100%); }
@media (prefers-reduced-motion: reduce) {
.btn-soutenir { transition: none; }
.jauge-fill { transition: none; }
.modal-enter-active, .modal-leave-active { transition: none; }
.backdrop-enter-active, .backdrop-leave-active { transition: none; }
.sheet-enter-active, .sheet-leave-active { transition: none; }
.bandeau-bas { transition: none; }
.bandeau-inner { transition: none; }
}
</style>
-1
View File
@@ -54,7 +54,6 @@
<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>
</div>
<!-- Messages -->
-1
View File
@@ -69,7 +69,6 @@
<div ref="messagesContainer" class="flex-1 overflow-y-auto px-4 py-4 flex flex-col gap-3">
<!-- Message onboarding (avant la première question) -->
<div v-if="messages.length === 0" class="onboarding-bubble">
<p>Ce chatbot fonctionne sur un serveur européen souverain (Mistral FR, zéro rétention), conçu sobre en énergie.</p>
<p>Pour m'aider à te répondre efficacement, formule ta requête ainsi :</p>
<ul>
<li>• Besoin : [ce que tu cherches]</li>
+121
View File
@@ -0,0 +1,121 @@
<template>
<section class="echelle-tete" :class="{ 'echelle-tete--compact': compact }" :aria-label="`Échelle ${e.id} : ${e.titre}`">
<p class="echelle-tete__kicker">
<span class="echelle-tete__badge" aria-hidden="true">{{ e.id }}</span>
<span class="echelle-tete__titre">{{ e.titre }}<span v-if="e.sousTitre" class="echelle-tete__sous-titre">{{ ' ' + e.sousTitre }}</span></span>
</p>
<ul class="echelle-tete__mots">
<li v-for="m in e.mots" :key="m">{{ m }}</li>
</ul>
<p class="echelle-tete__phrase">{{ e.phrase }}</p>
<slot />
<p v-if="contribuer" class="echelle-tete__contribuer">
<span>{{ ECHELLES_CADRE_COURT }}</span>
<NuxtLink :to="`/proposer?categorie=${e.categorieProposer}`" @click="$emit('navigate')">Proposer une ressource →</NuxtLink>
</p>
</section>
</template>
<script setup lang="ts">
import { ECHELLES, type EchelleId } from '~/utils/echelles'
const props = withDefaults(defineProps<{
echelle: EchelleId
/** Variante resserrée, pour une popup ou un en-tête centré. */
compact?: boolean
/** Affiche la ligne « on pose l'infrastructure, le collectif nourrit ». */
contribuer?: boolean
}>(), { compact: false, contribuer: true })
defineEmits<{ navigate: [] }>()
const e = computed(() => ECHELLES[props.echelle])
const ECHELLES_CADRE_COURT = 'Chaque section est ouverte aux contributions.'
</script>
<style scoped>
.echelle-tete {
display: flex;
flex-direction: column;
gap: var(--space-sm);
margin: 0 0 var(--space-md);
}
.echelle-tete__kicker {
display: flex;
align-items: baseline;
gap: var(--space-sm);
margin: 0;
}
.echelle-tete__badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2rem;
padding: 0.1rem 0.5rem;
border-radius: var(--radius-sm);
background: var(--accent);
color: var(--on-accent);
font-weight: var(--fw-bold);
font-size: var(--fs-sm);
letter-spacing: 0.04em;
line-height: 1.4;
}
.echelle-tete__titre {
font-size: var(--fs-lg);
font-weight: var(--fw-bold);
color: var(--text);
line-height: var(--lh-tight);
}
.echelle-tete__sous-titre {
font-weight: var(--fw-regular);
color: var(--text-muted);
}
.echelle-tete__mots {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 0.15rem 0;
font-size: var(--fs-sm);
color: var(--text);
line-height: var(--lh-base);
}
.echelle-tete__mots li:not(:last-child)::after {
content: '·';
margin: 0 0.5rem;
color: var(--accent);
font-weight: var(--fw-bold);
}
.echelle-tete__phrase {
margin: 0;
font-size: var(--fs-sm);
font-style: italic;
color: var(--text-muted);
line-height: var(--lh-base);
}
.echelle-tete__contribuer {
margin: 0;
display: flex;
flex-wrap: wrap;
gap: 0.25rem 0.75rem;
font-size: var(--fs-xs);
color: var(--text-muted);
}
.echelle-tete__contribuer a {
color: var(--link);
text-decoration: underline;
text-underline-offset: 2px;
}
.echelle-tete__contribuer a:hover { color: var(--link-hover); }
.echelle-tete--compact { text-align: left; }
.echelle-tete--compact .echelle-tete__titre { font-size: var(--fs-base); }
</style>
+1
View File
@@ -275,6 +275,7 @@
</button>
</div>
<div class="flex-1 overflow-y-auto px-5 py-4" style="color:var(--nav-text);font-size:0.875rem;line-height:1.6;">
<EchelleTete echelle="XL" compact @navigate="ragInfoOpen = false" />
<p class="mb-3">Une bibliothèque parlante des pensées écologiques, organisée pour aider à créer une pensée complexe et nuancée, à soutenir l'écriture de nouveaux récits, des alternatives concrètes et des projets collectifs.</p>
<p class="mb-4" style="color:var(--nav-text-muted);font-size:0.8rem;">Projet open source, ouvert à toutes et à tous - <a href="https://bonpote.com/la-carte-des-pensees-ecologiques/" target="_blank" rel="noopener" style="text-decoration:underline;">article + carte FRACAS Bonpote V2</a>.</p>
<div class="flex flex-col gap-3">
+5
View File
@@ -32,6 +32,7 @@
<div class="mission-body">
<h2 id="mission-title" class="mission-title">{{ title }}</h2>
<EchelleTete v-if="echelle" :echelle="echelle" compact @navigate="close" />
<slot>
<p class="mission-text">
L'architecture est l'une des professions les plus complexes qui soit ; elle croise droit, technique, esthétique, économie, social, écologie — tout à la fois, tout simultanément, souvent sans filet. Paradoxalement, c'est aussi l'une des moins structurées sur le plan de l'entraide&nbsp;: peu de transmission horizontale, beaucoup d'isolement, une culture du chacun-pour-soi héritée d'une formation qui prépare à la compétition plus qu'à la coopération. On sort de l'école seul·e. On s'installe seul·e. On réinvente ce que d'autres ont déjà traversé.
@@ -52,11 +53,15 @@
</template>
<script setup lang="ts">
import type { EchelleId } from '~/utils/echelles'
const props = withDefaults(defineProps<{
modelValue: boolean
title?: string
ctaLabel?: string
storageKey?: string
/** Tête d'échelle du deck (S / M / XL) affichée sous le titre. Absente sur /agences tant que l'arbitrage n° 1 n'est pas rendu. */
echelle?: EchelleId
}>(), {
title: "L'écosystème d'entraide architecte",
ctaLabel: 'Explorer la carte',
-65
View File
@@ -27,14 +27,6 @@
Ce site tourne sur un VPS - un serveur privé loué en Europe - construit entièrement avec des logiciels open source, quasi gratuits. Pas de services Google, pas d'Amazon, pas de Microsoft dans la chaîne. C'est un choix délibéré : contrôler son infrastructure, c'est contrôler ses données.
</p>
<div class="badges-row">
<div class="badge">
<span class="badge-icon" aria-hidden="true">
<!-- Mistral logo placeholder -->
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 8v4l3 3"/></svg>
</span>
<span class="badge-label">IA souveraine</span>
<span class="badge-detail">Mistral AI, Paris - conforme RGPD ; pas de collecte ni de profiling ; aucun transfert hors UE</span>
</div>
<div class="badge">
<span class="badge-icon" aria-hidden="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
@@ -103,22 +95,8 @@
L'IA est un outil. La question, c'est au service de quoi. Oui, elle est énergivore - et il faut l'assumer. Mais si c'est au service de l'émancipation, du collectif, de transformer nos infrastructures sociétales, alors on dit oui - en sachant que c'est une étape transitoire. L'infrastructure de l'IA elle-même peut évoluer : devenir plus locale, plus sobre, plus souveraine. C'est dans cette direction qu'on regarde.
</p>
<div class="ia-grid">
<div class="ia-card">
<div class="ia-card-label">Enrichissement des fiches</div>
<div class="ia-card-model">Mistral Nemo</div>
<div class="ia-card-cost">~0,00003 €/fiche</div>
</div>
<div class="ia-card">
<div class="ia-card-label">Chatbot de recherche</div>
<div class="ia-card-model">Mistral Small</div>
<div class="ia-card-cost">~0,003 €/requête</div>
</div>
</div>
<ul class="ia-details">
<li>10 € / mois investis au départ par le collectif - vos dons constituent la réserve pour faire vivre le projet et financer ses recherches</li>
<li>Coût visible en direct dans le bandeau bas de la carte</li>
</ul>
</section>
@@ -398,49 +376,6 @@ function copyApiUrl() {
/* ── IA grid ─────────────────────────────────────────────────────────────────── */
.ia-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-bottom: 1rem;
}
@media (max-width: 480px) {
.ia-grid {
grid-template-columns: 1fr;
}
}
.ia-card {
background: var(--nav-surface);
border: 1px solid rgba(26, 34, 56, 0.1);
border-radius: 10px;
padding: 1rem 1.125rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.ia-card-label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--nav-text-muted);
font-weight: 600;
}
.ia-card-model {
font-size: 1rem;
font-weight: 700;
color: var(--nav-text);
}
.ia-card-cost {
font-size: 0.825rem;
color: var(--nav-text-muted);
font-variant-numeric: tabular-nums;
}
.ia-details {
list-style: none;
padding: 0;
+1
View File
@@ -484,6 +484,7 @@
title="Réseaux AEP — l'architecture qui s'engage"
ctaLabel="Explorer les 120 réseaux"
storageKey="aep_reseaux_seen"
echelle="M"
>
<p class="mission-text">
Cette carte rassemble <strong>120 réseaux, collectifs et agences</strong> qui pratiquent une architecture engagée — écologique, politique, biorégionale. Ce ne sont pas seulement des agences «&nbsp;vertes&nbsp;»&nbsp;: ce sont celles et ceux qui assument des positions, refusent des projets, expérimentent des modèles de gouvernance, mettent leurs ressources et leurs savoirs en commun.
+8
View File
@@ -5,6 +5,7 @@
<div class="lock-header">
<h1>Co-développement</h1>
<p class="lock-subtitle">Entraide entre pairs</p>
<EchelleTete echelle="M" compact :contribuer="false" class="lock-echelle" />
<p class="lock-intro">Cet espace est un cercle. Pour entrer, il y a un mot.</p>
</div>
@@ -104,6 +105,13 @@ async function submit() {
margin: 0 0 1rem;
}
.lock-echelle {
text-align: left;
padding: var(--space-md);
border-radius: var(--radius-md);
background: var(--bg-alt);
}
.lock-intro {
font-size: 0.9rem;
color: var(--nav-text-muted, #6b7280);
+1
View File
@@ -391,6 +391,7 @@
<MissionPopup
:modelValue="missionOpen"
@update:modelValue="missionOpen = $event"
echelle="M"
/>
</div>
+2 -1
View File
@@ -9,6 +9,7 @@
</div>
<div>
<h1 class="outils-header__title">Outils</h1>
<EchelleTete echelle="S" :contribuer="false" />
<p class="outils-header__intro">
En tant qu'architecte, on jongle avec une multitude d'outils — simulation, dessin,
calcul, recherche, partage. Les mutualiser, se conseiller dessus, savoir lequel
@@ -326,7 +327,7 @@ const fmhyGuide = [
{ nom: 'DeepSeek', url: 'https://chat.deepseek.com/', desc: 'modèle de pointe, usage illimité gratuit' },
{ nom: 'Qwen Studio', url: 'https://chat.qwen.ai/', desc: 'chat IA (Qwen) illimité gratuit' },
{ nom: 'Google AI Studio', url: 'https://aistudio.google.com/', desc: 'Gemini en accès généreux gratuit' },
{ nom: 'Mistral Le Chat', url: 'https://chat.mistral.ai/', desc: 'LLM français, souverain, gratuit' },
{ nom: 'Mistral Le Chat', url: 'https://chat.mistral.ai/', desc: 'LLM français, gratuit' },
{ nom: 'LM Studio', url: 'https://lmstudio.ai/', desc: 'faire tourner des modèles en local, sans cloud' },
{ nom: 'Jan', url: 'https://jan.ai/', desc: 'application desktop libre pour IA locale' },
{ nom: 'Open WebUI', url: 'https://openwebui.com/', desc: 'interface web auto-hébergeable pour modèles locaux' }
+4 -1
View File
@@ -52,7 +52,10 @@ const TABS = [
{ id: 'references', label: 'Références (bibliothèque)' },
] as const
const activeTab = ref<string>('ecosysteme')
// ?categorie=outils (lien « Proposer une ressource » des têtes d'échelle) présélectionne l'onglet
const route = useRoute()
const categorie = String(route.query.categorie ?? '')
const activeTab = ref<string>(TABS.some(t => t.id === categorie) ? categorie : 'ecosysteme')
const success = ref(false)
function onSuccess() {
+6 -1
View File
@@ -5,6 +5,7 @@
<div class="taff-header">
<div class="taff-header-inner">
<h1 class="taff-title">Trouver du taf en archi</h1>
<EchelleTete echelle="S" :contribuer="false" class="taff-echelle-tete" />
<p class="taff-subtitle">
Annuaire critique des plateformes de mise en relation et d'appels d'offres,
pour les <strong>architectes indépendants</strong> — 70&nbsp;% de la profession et sa part la plus précaire économiquement.
@@ -296,7 +297,7 @@
</svg>
</button>
</div>
<p class="taff-chat-footer-note">Propulsé par Mistral · 20 questions/jour</p>
<p class="taff-chat-footer-note">20 questions/jour</p>
</div>
</Transition>
</Teleport>
@@ -649,6 +650,10 @@ const parsedDescription = computed(() => {
.taff-header { padding: 2.5rem 1.5rem 1.5rem; border-bottom: 1px solid var(--nav-bg-alt); text-align: center; }
.taff-header-inner { max-width: 680px; margin: 0 auto; }
/* En-tête Jobs centré : la tête d'échelle suit. */
.taff-echelle-tete { text-align: center; margin-bottom: var(--space-md); }
.taff-echelle-tete :deep(.echelle-tete__kicker),
.taff-echelle-tete :deep(.echelle-tete__mots) { justify-content: center; }
.taff-title { font-size: 1.875rem; font-weight: 800; color: var(--nav-text); margin-bottom: 0.5rem; letter-spacing: -0.02em; }
.taff-subtitle { font-size: 0.9375rem; color: var(--nav-text-muted); line-height: 1.6; margin-bottom: 0.625rem; }
.taff-cible { font-size: 0.875rem; color: var(--nav-text-muted); line-height: 1.55; margin-bottom: 1rem; font-style: italic; }
+1 -1
View File
@@ -7,7 +7,7 @@
"children": [
{ "name": "ChatGPT (OpenAI)", "url": "https://chat.openai.com/", "desc": "LLM généraliste, référence." },
{ "name": "Claude (Anthropic)", "url": "https://claude.ai/", "desc": "Excellent pour rédaction longue et analyse de documents." },
{ "name": "Mistral Le Chat", "url": "https://chat.mistral.ai/", "desc": "LLM français, souverain, gratuit." },
{ "name": "Mistral Le Chat", "url": "https://chat.mistral.ai/", "desc": "LLM français, gratuit." },
{ "name": "Perplexity", "url": "https://www.perplexity.ai/", "desc": "Moteur de recherche IA avec sources citées." },
{ "name": "Hugging Face", "url": "https://huggingface.co/", "desc": "Hub de modèles open source. Indispensable." },
{ "name": "LM Studio", "url": "https://lmstudio.ai/", "desc": "Faire tourner des LLM localement, sans cloud." }
+68
View File
@@ -0,0 +1,68 @@
/**
* Les trois échelles du deck du 23/09/2026 (conférence AEP), slides 15 à 18.
* Texte repris mot pour mot : c'est la tête de chaque onglet (directive Jules du 25/09, point 2).
* Source unique : la future nav S / M / XL (arbitrage n° 1, table R) lira aussi ce fichier.
*/
export type EchelleId = 'S' | 'M' | 'XL'
export interface Echelle {
id: EchelleId
titre: string
sousTitre?: string
mots: string[]
phrase: string
/** Catégorie de /proposer présélectionnée par le lien de contribution. */
categorieProposer: 'ecosysteme' | 'reseau' | 'jobs' | 'outils' | 'references'
}
export const ECHELLES: Record<EchelleId, Echelle> = {
S: {
id: 'S',
titre: 'Partir de soi',
sousTitre: "pour s'émanciper",
mots: [
'quête de sens',
'où habiter, avec qui',
'gagner sa vie',
'santé mentale',
'mener ses projets',
'naviguer avec soi',
'les compétences psychosociales',
],
phrase: 'Des ressources, des méthodes, des pairs : matériel et mutualisé.',
categorieProposer: 'outils',
},
M: {
id: 'M',
titre: 'La communauté',
mots: [
'entraide',
'mutualisation',
'appartenance',
'être vu, soutenu',
'traverser les creux',
'accomplissement',
'le codev',
],
phrase: "C'est le groupe qui remet un individu debout. L'affinité fait tenir, la confrontation fait avancer.",
categorieProposer: 'ecosysteme',
},
XL: {
id: 'XL',
titre: 'Contribuer',
mots: [
'contribution',
'affiliation, filiation',
'identité',
'sens',
'de nouveaux récits',
'un nouveau contrat social',
'la médecine du corps social',
],
phrase: 'La bibliothèque, les récits, la filiation entre projets.',
categorieProposer: 'references',
},
}
/** Slide 15, commune aux trois échelles. */
export const ECHELLES_CADRE = "Trois échelles, imbriquées. Pas un escalier : ça circule dans les deux sens. Entrez par celle qui presse."