286 lines
12 KiB
Vue
286 lines
12 KiB
Vue
<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="flex items-center gap-2 hover:opacity-90 transition-opacity shrink-0 group relative" title="Architecture d'Écologie Politique">
|
||
<div
|
||
class="h-7 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="flex flex-col">
|
||
<span class="font-bold text-base tracking-tight leading-tight" style="color: var(--nav-text);">AEP</span>
|
||
<span class="text-xs leading-tight hidden lg:inline" style="color: var(--nav-text-muted);">Architecture d'Écologie Politique</span>
|
||
</div>
|
||
<!-- Tooltip sm (quand le sous-titre lg est caché) -->
|
||
<div class="absolute left-0 top-full mt-2 px-2 py-1 rounded text-xs whitespace-nowrap pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity lg:hidden z-50"
|
||
style="background: var(--nav-primary-solid); color: var(--nav-text-on-primary);">
|
||
Architecture d'Écologie Politique
|
||
</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="/"
|
||
class="nav-tab"
|
||
:class="{ 'nav-tab--active': route.path === '/' }"
|
||
>
|
||
Écosystème Entraide Architecture
|
||
</NuxtLink>
|
||
<NuxtLink
|
||
to="/agences"
|
||
class="nav-tab"
|
||
:class="{ 'nav-tab--active': route.path === '/agences' }"
|
||
>
|
||
Agences Inspirantes
|
||
<span class="nav-tab-badge">en construction</span>
|
||
</NuxtLink>
|
||
<NuxtLink
|
||
to="/rag"
|
||
class="nav-tab"
|
||
:class="{ 'nav-tab--active': route.path === '/rag' }"
|
||
>
|
||
RAG
|
||
<span class="nav-tab-badge">en construction</span>
|
||
</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="/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 -->
|
||
<NuxtLink
|
||
to="/contribuer"
|
||
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 : contribuer icône -->
|
||
<NuxtLink
|
||
to="/contribuer"
|
||
class="sm:hidden p-2 rounded-lg"
|
||
style="background: var(--nav-accent); color: var(--nav-text);"
|
||
title="Contribuer une fiche"
|
||
aria-label="Contribuer"
|
||
>
|
||
<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>
|
||
|
||
<!-- Hamburger mobile (lg:hidden) — toujours en dernier à droite -->
|
||
<div class="lg:hidden 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="/" 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 Architecture</NuxtLink>
|
||
<NuxtLink to="/agences" class="block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" style="color: var(--nav-text);">Agences Inspirantes</NuxtLink>
|
||
<NuxtLink to="/rag" class="block px-4 py-2.5 text-sm transition-opacity hover:opacity-70" style="color: var(--nav-text);">RAG</NuxtLink>
|
||
<div style="height: 1px; background: var(--nav-bg-alt); margin: 4px 0;"></div>
|
||
<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>
|
||
<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>
|
||
|
||
<!-- Bandeau bas — transparence IA + soutien + compteurs semaine -->
|
||
<BandeauBas />
|
||
|
||
</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>
|
||
/* ── 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>
|