- NavEchelles : trois groupes (accroche pastille + titre, <ul> étiqueté, aria-current) ≥ 1280 ; groupe courant seul 768-1279 ; lu depuis utils/echelles.ts - NavTiroir remplace le menu déroulant : tiroir à droite, voile, Échap, focus piégé et rendu, cibles 44 px ; bouton « Menu » masqué ≥ 1280 - FilEchelle < 768, dans le flux avant le conteneur de page - Soutenir et À propos dans la barre ≥ 1280 ; Signaler quitte la barre - barre de recherche d'en-tête retirée (/ garde sa recherche dans MobileSheet à 768 et 1000, vérifié au DOM) - html lang="fr" (Lighthouse html-has-lang) B10 M2-M4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBu7CSLy7PJgL4HJaV5oHP
264 lines
9.1 KiB
Vue
264 lines
9.1 KiB
Vue
<template>
|
|
<div class="flex flex-col h-screen overflow-hidden" :class="{ dark: isDark }" style="background: var(--nav-bg);">
|
|
|
|
<!-- ═══════════════════════════════════════ TOP NAV GLOBAL (B10 : échelles S / M / XL) -->
|
|
<header
|
|
class="app-header flex items-center px-4 shrink-0 relative z-[9999] shadow-sm gap-3"
|
|
style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);"
|
|
>
|
|
<!-- Logo : texte complet sauf 768-1023 et 1280-1439 (place des onglets) -->
|
|
<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>
|
|
|
|
<!-- Échelles : trois groupes ≥ 1280, groupe courant 768-1279, rien < 768 (fil d'échelle) -->
|
|
<NavEchelles />
|
|
<div class="flex-1 md:hidden" aria-hidden="true" />
|
|
|
|
<!-- Actions droite -->
|
|
<div class="flex items-center gap-2 shrink-0">
|
|
<NuxtLink
|
|
to="/manifeste"
|
|
class="header-lien hidden md:inline-flex"
|
|
:aria-current="route.path === '/manifeste' ? 'page' : undefined"
|
|
>
|
|
Manifeste
|
|
</NuxtLink>
|
|
<NuxtLink
|
|
to="/a-propos"
|
|
class="header-lien header-xl"
|
|
:aria-current="route.path === '/a-propos' ? 'page' : undefined"
|
|
>
|
|
À propos
|
|
</NuxtLink>
|
|
<a
|
|
:href="SOUTENIR_URL"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="header-soutenir header-xl"
|
|
>
|
|
Soutenir
|
|
</a>
|
|
<!-- Proposer une ressource -->
|
|
<NuxtLink
|
|
to="/proposer"
|
|
class="header-proposer hidden md:inline-flex"
|
|
>
|
|
+ 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="md:hidden p-2 rounded-lg"
|
|
style="background: var(--accent); color: var(--on-accent);"
|
|
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" aria-hidden="true">
|
|
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
|
</svg>
|
|
</NuxtLink>
|
|
|
|
<!-- Menu (< 1280) : ouvre le tiroir ; le mot est écrit à partir de 768 -->
|
|
<button
|
|
type="button"
|
|
class="header-menu"
|
|
aria-label="Ouvrir le menu"
|
|
aria-haspopup="dialog"
|
|
:aria-expanded="menuOpen"
|
|
@click="menuOpen = true"
|
|
>
|
|
<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="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>
|
|
<span class="header-menu__mot" aria-hidden="true">Menu</span>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Fil d'échelle (< 768) : dans le flux de la colonne, hors du conteneur de page -->
|
|
<FilEchelle />
|
|
|
|
<!-- Contenu page (flex-1 pour remplir l'espace) -->
|
|
<div class="flex-1 min-h-0" :class="route.path === '/' ? 'overflow-hidden' : 'overflow-y-auto'">
|
|
<NuxtPage />
|
|
</div>
|
|
|
|
<NavTiroir :open="menuOpen" @close="menuOpen = false" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { SOUTENIR_URL } from '~/utils/liens'
|
|
|
|
const route = useRoute()
|
|
|
|
const menuOpen = ref(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')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
/* ── En-tête ──────────────────────────────────────────────────────────── */
|
|
.app-header {
|
|
min-height: 52px;
|
|
}
|
|
|
|
/* ── 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; }
|
|
}
|
|
/* Le groupe courant (768-1023) et les trois groupes (1280-1439) prennent la place du texte */
|
|
@media (min-width: 768px) and (max-width: 1023px), (min-width: 1280px) and (max-width: 1439px) {
|
|
.logo-text { display: none; }
|
|
}
|
|
|
|
/* ── Liens et boutons de droite ───────────────────────────────────────── */
|
|
.header-lien {
|
|
align-items: center;
|
|
padding: 6px 8px;
|
|
border-radius: var(--radius-md);
|
|
font-size: var(--fs-sm);
|
|
font-weight: var(--fw-medium);
|
|
white-space: nowrap;
|
|
text-decoration: none;
|
|
color: color-mix(in srgb, var(--text) 78%, transparent);
|
|
}
|
|
.header-lien:hover,
|
|
.header-lien[aria-current="page"] {
|
|
color: var(--text);
|
|
}
|
|
.header-lien[aria-current="page"] {
|
|
font-weight: var(--fw-bold);
|
|
}
|
|
|
|
.header-soutenir {
|
|
align-items: center;
|
|
padding: 5px 10px;
|
|
border: 1.5px solid var(--accent);
|
|
border-radius: var(--radius-md);
|
|
font-size: var(--fs-sm);
|
|
font-weight: var(--fw-semibold);
|
|
white-space: nowrap;
|
|
text-decoration: none;
|
|
color: var(--text);
|
|
}
|
|
|
|
.header-proposer {
|
|
align-items: center;
|
|
padding: 6px 11px;
|
|
border-radius: var(--radius-md);
|
|
background: var(--accent);
|
|
color: var(--on-accent);
|
|
font-size: var(--fs-sm);
|
|
font-weight: var(--fw-semibold);
|
|
white-space: nowrap;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.header-soutenir:hover,
|
|
.header-proposer:hover {
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.header-lien:focus-visible,
|
|
.header-soutenir:focus-visible,
|
|
.header-proposer:focus-visible,
|
|
.header-menu:focus-visible {
|
|
outline: 2px solid var(--focus-ring);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* À propos et Soutenir : barre complète seulement (≥ 1280), sinon dans le tiroir */
|
|
.header-xl { display: none; }
|
|
@media (min-width: 1280px) {
|
|
.header-xl { display: inline-flex; }
|
|
}
|
|
|
|
.header-menu {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 8px;
|
|
border: 1px solid transparent;
|
|
border-radius: var(--radius-md);
|
|
background: var(--bg-alt);
|
|
color: var(--text);
|
|
font-size: var(--fs-sm);
|
|
font-weight: var(--fw-semibold);
|
|
cursor: pointer;
|
|
}
|
|
.header-menu__mot { display: none; }
|
|
@media (min-width: 768px) {
|
|
.header-menu {
|
|
padding: 5px 10px;
|
|
border-color: var(--border);
|
|
background: transparent;
|
|
}
|
|
.header-menu__mot { display: inline; }
|
|
}
|
|
@media (min-width: 1280px) {
|
|
.header-menu { display: none; }
|
|
}
|
|
</style>
|