Files
nav-carte/app.vue
T
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

324 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="flex flex-col h-screen overflow-hidden" :class="{ dark: isDark }" style="background: var(--nav-bg);">
<!-- ═══════════════════════════════════════ TOP NAV GLOBAL -->
<header
class="flex items-center justify-between px-4 py-2.5 shrink-0 relative z-[9999] shadow-sm gap-3"
style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);"
>
<!-- Logo -->
<a href="/" class="logo-link flex items-center gap-2 hover:opacity-90 transition-opacity shrink-0" title="Architecture d'Écologie Politique">
<div
class="h-8 px-2 rounded-lg flex items-center justify-center shrink-0"
style="background: var(--nav-primary-solid);"
>
<span class="font-bold text-xs tracking-tight" style="color: var(--nav-text-on-primary);">AEP</span>
</div>
<div class="logo-text flex flex-col leading-tight">
<span class="logo-line-1 font-bold tracking-tight" style="color: var(--nav-text);">Architecture</span>
<span class="logo-line-2 font-bold tracking-tight" style="color: var(--nav-text);">d'Écologie Politique</span>
</div>
</a>
<!-- ── Onglets desktop (≥1024px) — remplace la barre de recherche ── -->
<nav class="hidden lg:flex flex-1 justify-center items-end gap-0 mx-6" aria-label="Navigation projets">
<NuxtLink
to="/outils"
class="nav-tab"
:class="{ 'nav-tab--active': route.path === '/outils' }"
>
Outils
</NuxtLink>
<NuxtLink
to="/"
class="nav-tab"
:class="{ 'nav-tab--active': route.path === '/' }"
>
Écosystème Entraide
</NuxtLink>
<NuxtLink
to="/agences"
class="nav-tab"
:class="{ 'nav-tab--active': route.path === '/agences' }"
>
Réseaux AEP
</NuxtLink>
<NuxtLink
to="/trouver-du-taf"
class="nav-tab"
:class="{ 'nav-tab--active': route.path === '/trouver-du-taf' }"
>
Jobs
</NuxtLink>
<NuxtLink
to="/codev"
class="nav-tab"
:class="{ 'nav-tab--active': route.path.startsWith('/codev') }"
>
Codev
</NuxtLink>
<NuxtLink
to="/media"
class="nav-tab"
:class="{ 'nav-tab--active': route.path.startsWith('/media') }"
>
recherche-média
</NuxtLink>
</nav>
<!-- ── Barre recherche mobile (640px–1024px) — masquée < 640px car accessible dans la sheet -->
<div class="hidden sm:flex lg:hidden flex-1 mx-2">
<label class="flex items-center gap-2 w-full px-3 py-1.5 rounded-xl border" style="background: var(--nav-bg); border-color: var(--nav-bg-alt);">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="color: var(--nav-text-muted); flex-shrink: 0;">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<input
v-model="headerSearch"
type="search"
placeholder="Rechercher…"
class="flex-1 bg-transparent border-0 outline-none text-sm"
style="color: var(--nav-text); font-family: var(--nav-font);"
autocomplete="off"
@input="onHeaderSearch"
@keydown.enter="onHeaderSearch"
/>
<button
v-if="headerSearch"
type="button"
@click.stop="clearHeaderSearch"
class="flex-shrink-0"
style="color: var(--nav-text-muted);"
aria-label="Effacer la recherche"
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</label>
</div>
<!-- Actions droite -->
<div class="flex items-center gap-2 shrink-0">
<NuxtLink
to="/manifeste"
class="px-3 py-1.5 rounded-lg text-sm font-medium transition-all hover:opacity-80 hidden md:inline-flex items-center gap-1"
:style="route.path === '/manifeste' ? 'color: var(--nav-primary-solid); font-weight: 700;' : 'color: var(--nav-text-muted);'"
>
Manifeste
</NuxtLink>
<NuxtLink
to="/a-propos"
class="px-3 py-1.5 rounded-lg text-sm font-medium transition-all hover:opacity-80 hidden md:inline-flex items-center gap-1"
style="color: var(--nav-text-muted);"
>
À propos
</NuxtLink>
<NuxtLink
to="/signaler"
class="px-3 py-1.5 rounded-lg text-sm font-medium transition-all hover:opacity-80 hidden lg:inline-flex items-center gap-1"
style="color: var(--nav-text-muted);"
>
Signaler
</NuxtLink>
<!-- Proposer une ressource (5 onglets) -->
<NuxtLink
to="/proposer"
class="px-3 py-1.5 rounded-lg text-sm font-semibold transition-all hover:opacity-80 hidden sm:inline-flex items-center gap-1"
style="background: var(--nav-accent); color: var(--nav-text);"
>
+ Proposer
</NuxtLink>
<!-- Toggle dark mode -->
<button
@click="toggleDark"
class="p-2 rounded-lg transition-all hover:opacity-80"
style="background: var(--nav-bg-alt); color: var(--nav-text-muted);"
:title="isDark ? 'Passer en mode clair' : 'Passer en mode sombre'"
:aria-label="isDark ? 'Mode clair' : 'Mode sombre'"
>
<svg v-if="!isDark" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
<svg v-else width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
</button>
<!-- Mobile : proposer icône -->
<NuxtLink
to="/proposer"
class="sm:hidden p-2 rounded-lg"
style="background: var(--nav-accent); color: var(--nav-text);"
title="Proposer une ressource"
aria-label="Proposer"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
</NuxtLink>
<!-- 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"
style="background: var(--nav-bg-alt); color: var(--nav-text-muted);"
:aria-label="hamburgerOpen ? 'Fermer le menu' : 'Menu'"
:aria-expanded="hamburgerOpen"
>
<svg v-if="!hamburgerOpen" 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="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
</svg>
<svg v-else 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>
<div
v-if="hamburgerOpen"
class="absolute right-0 top-full mt-1 rounded-lg shadow-lg min-w-[210px] py-1"
style="background: var(--nav-surface); border: 1px solid var(--nav-bg-alt); z-index: 9999;"
@click="hamburgerOpen = false"
>
<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 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>
</div>
</header>
<!-- Contenu page (flex-1 pour remplir l'espace) -->
<div class="flex-1" :class="route.path === '/' ? 'overflow-hidden' : 'overflow-y-auto'">
<NuxtPage />
</div>
</div>
</template>
<script setup lang="ts">
const router = useRouter()
const route = useRoute()
const hamburgerOpen = ref(false)
watch(() => route.path, () => { hamburgerOpen.value = false })
// ── Dark mode ─────────────────────────────────────────────────────────────
const isDark = ref(false)
onMounted(() => {
const stored = localStorage.getItem('aep_theme')
if (stored === 'dark') {
isDark.value = true
document.documentElement.classList.add('dark')
}
})
function toggleDark() {
isDark.value = !isDark.value
if (isDark.value) {
document.documentElement.classList.add('dark')
localStorage.setItem('aep_theme', 'dark')
} else {
document.documentElement.classList.remove('dark')
localStorage.setItem('aep_theme', 'light')
}
}
// ── Barre de recherche header mobile ─────────────────────────────────────
const headerSearch = ref((route.query.q as string) ?? '')
// Sync depuis URL quand la route change
watch(() => route.query.q, (v) => {
headerSearch.value = (v as string) ?? ''
})
function onHeaderSearch() {
const q = headerSearch.value.trim()
if (route.path === '/') {
router.replace({ query: q ? { ...route.query, q } : { ...route.query, q: undefined } })
} else if (q) {
router.push({ path: '/', query: { q } })
}
}
function clearHeaderSearch() {
headerSearch.value = ''
if (route.path === '/') {
const q = { ...route.query }
delete q.q
router.replace({ query: Object.keys(q).length ? q : undefined })
}
}
// ── Fiche aléatoire ───────────────────────────────────────────────────────
function goRandom() {
router.push({ path: '/', query: { random: '1' } })
}
</script>
<style>
/* ── Logo header (texte 2 lignes) ─────────────────────────────────────── */
.logo-text {
line-height: 1.05;
}
.logo-line-1, .logo-line-2 {
font-size: 0.7rem;
letter-spacing: -0.01em;
}
@media (min-width: 640px) {
.logo-line-1, .logo-line-2 { font-size: 0.78rem; }
}
@media (min-width: 1024px) {
.logo-line-1, .logo-line-2 { font-size: 0.85rem; }
}
/* ── Onglets header desktop ───────────────────────────────────────────── */
.nav-tab {
position: relative;
display: inline-flex;
flex-direction: column;
align-items: center;
padding: 6px 16px 8px;
font-size: 0.8rem;
font-weight: 500;
color: var(--nav-text-muted);
text-decoration: none;
transition: color 0.15s;
border-bottom: 2px solid transparent;
gap: 2px;
}
.nav-tab:hover {
color: var(--nav-text);
}
.nav-tab--active {
color: var(--nav-text);
border-bottom-color: var(--nav-primary-solid);
font-weight: 600;
}
.nav-tab-badge {
font-size: 0.6rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--nav-text-muted);
opacity: 0.65;
}
</style>