TypeScript 90.1%
JavaScript 3.2%
Python 3.2%
CSS 1.8%
HTML 1.8%
1// UQO Éval — laboratoire pédagogique d'évaluation immobilière2/**3 * Navigation d'en-tête. Le menu mobile utilise le motif checkbox + peer-checked :4 * il s'ouvre et se ferme en PUR CSS — fonctionne même sans JavaScript (cache5 * périmé, hydratation cassée, JS bloqué). Le JS n'est qu'une amélioration6 * (fermeture au changement de page, verrou de défilement).7 */8"use client";9import { useEffect, useRef } from "react";10import Link from "next/link";11import { usePathname } from "next/navigation";12import { useLang } from "./LangContext";1314export default function HeaderNav() {15 const { lang, setLang } = useLang();16 const path = usePathname();17 const fr = lang === "fr";18 const box = useRef<HTMLInputElement>(null);1920 // améliorations JS (facultatives) : fermer à la navigation, verrouiller le scroll21 useEffect(() => {22 if (box.current) box.current.checked = false;23 document.body.style.overflow = "";24 }, [path]);25 const onToggle = () => {26 document.body.style.overflow = box.current?.checked ? "hidden" : "";27 };2829 const items = [30 { href: "/", label: fr ? "Analyser" : "Analyze" },31 { href: "/a-vendre", label: fr ? "À vendre" : "For sale" },32 { href: "/hedonique", label: fr ? "Hédonique" : "Hedonic" },33 { href: "/carte", label: fr ? "Carte" : "Map" },34 { href: "/assistant", label: fr ? "Assistant IA" : "AI assistant" },35 { href: "/parc", label: fr ? "Parc immobilier" : "Portfolio" },36 { href: "/stats", label: fr ? "Statistiques" : "Statistics" },37 { href: "/cout", label: fr ? "Coût" : "Cost" },38 { href: "/methodologie", label: fr ? "Méthodologie" : "Methodology" },39 ];40 const isActive = (href: string) =>41 href === "/" ? path === "/" : path.startsWith(href);4243 return (44 <>45 {/* nav texte — desktop : soulignement rouge = page courante */}46 <nav className="hidden items-center gap-5 lg:flex">47 {items.map((it) => (48 <Link49 key={it.href}50 href={it.href}51 className={`vp-mono inline-flex min-h-[38px] items-center border-b-2 pt-[2px] text-[11px] font-medium uppercase tracking-[0.1em] transition-colors ${52 isActive(it.href)53 ? "border-accent text-ink"54 : "border-transparent text-ink-2 hover:text-ink"55 }`}56 >57 {it.label}58 </Link>59 ))}60 </nav>6162 {/* ---- menu mobile : checkbox cachée + peer-checked, zéro JS requis ---- */}63 <div className="lg:hidden">64 <input65 ref={box}66 type="checkbox"67 id="vp-menu"68 className="peer sr-only"69 onChange={onToggle}70 aria-label="Menu"71 />72 {/* hamburger (label = bouton) */}73 <label74 htmlFor="vp-menu"75 aria-hidden="true"76 className="flex h-11 w-11 cursor-pointer flex-col items-center justify-center gap-[5px]"77 >78 <span className="h-px w-[20px] bg-ink" />79 <span className="h-px w-[20px] bg-ink" />80 <span className="h-px w-[12px] self-center ml-[8px] bg-accent" />81 </label>8283 {/* panneau plein écran encre — sibling du checkbox, affiché via peer-checked */}84 <div85 className="vp-dark fixed inset-0 z-[var(--z-modal,900)] hidden flex-col overflow-y-auto peer-checked:flex"86 role="dialog"87 aria-modal="true"88 aria-label="Menu"89 >90 {/* barre du haut : marque + fermer */}91 <div className="flex h-14 shrink-0 items-center justify-between border-b border-[rgba(245,243,238,0.18)] px-4">92 <Link93 href="/"94 className="flex items-end gap-3"95 aria-label="UQO Éval — accueil"96 >97 {/* eslint-disable-next-line @next/next/no-img-element */}98 <img src="/uqo/uqo-blanc.svg" alt="UQO" className="h-[24px] w-auto" />99 <span className="vp-display text-[19px] font-bold leading-none tracking-[-0.04em] text-[var(--accent-bright)]">100 Éval101 </span>102 </Link>103 <label104 htmlFor="vp-menu"105 aria-label={fr ? "Fermer le menu" : "Close menu"}106 className="vp-mono flex h-11 w-11 cursor-pointer items-center justify-center text-[15px] text-[var(--paper-on-ink)]"107 >108 ✕109 </label>110 </div>111112 {/* liens géants */}113 <nav className="flex flex-1 flex-col justify-center px-6 py-8">114 {items.map((it, i) => (115 <Link116 key={it.href}117 href={it.href}118 className="group flex items-baseline gap-4 border-b border-[rgba(245,243,238,0.12)] py-[18px] last:border-0"119 >120 <span className="vp-mono text-[11px] font-medium text-[var(--accent-bright)]">121 0{i + 1}122 </span>123 <span124 className={`vp-display text-[28px] font-bold uppercase leading-none tracking-[-0.03em] ${125 isActive(it.href)126 ? "text-[var(--accent-bright)]"127 : "text-[var(--paper-on-ink)] group-active:text-[var(--accent-bright)]"128 }`}129 >130 {it.label}131 </span>132 {isActive(it.href) && (133 <span134 aria-hidden="true"135 className="mb-[2px] inline-block h-[6px] w-[6px] self-center bg-[var(--accent-bright)]"136 />137 )}138 </Link>139 ))}140 </nav>141142 {/* pied du menu */}143 <div className="shrink-0 border-t border-[rgba(245,243,238,0.18)] px-6 pb-[max(20px,env(safe-area-inset-bottom))] pt-4">144 <p className="vp-mono mb-4 text-[10px] uppercase tracking-[0.1em] text-[rgba(245,243,238,0.55)]">145 {fr146 ? "Laboratoire pédagogique d'évaluation immobilière — UQO"147 : "Real-estate valuation teaching laboratory — UQO"}148 </p>149 <button150 onClick={() => setLang(fr ? "en" : "fr")}151 className="vp-mono mb-4 border-b border-[var(--paper-on-ink)] pb-1 text-[12px] font-medium uppercase tracking-[0.1em] text-[var(--paper-on-ink)]"152 >153 {fr ? "English version" : "Version française"}154 </button>155 <div className="vp-mono flex flex-wrap gap-x-5 gap-y-1.5 text-[10.5px] uppercase tracking-[0.06em] text-[rgba(245,243,238,0.55)]">156 <Link href="/conditions">{fr ? "Conditions" : "Terms"}</Link>157 <Link href="/confidentialite">158 {fr ? "Confidentialité" : "Privacy"}159 </Link>160 <Link href="/contact">Contact</Link>161 </div>162 </div>163 </div>164 </div>165 </>166 );167}168