Mobile : hamburger standard KA, verrou de scroll du menu compte, anti-zoom iOS, cibles tactiles
- App.tsx : menu compte — verrou du scroll d arriere-plan, fermeture Escape et tap exterieur au niveau document (le backdrop fixed est confine au header par son backdrop-filter), aria-haspopup=menu sur le bouton. - App.tsx : navigation mobile structuree — hamburger standard KA (modele food-ka) : bouton 44x44 aria-expanded, panneau .mobile-menu sous le header (0 -> 480 px), backdrop rendu en FRERE du header (backdrop-filter = containing block), verrou scroll, fermeture route/backdrop/Escape, items >= 44 px, padding-bottom safe-area ; remplace la bande scroll-x sous 760 px. - styles.css : CSS du hamburger (seuil 760 px, z-index --z-overlay/--z-modal, .header.menu-open remonte au-dessus du backdrop) ; menu compte bascule sur les variables --z-* ; extension tactile ::after du logo (102x32) ; regles anti-zoom iOS generalisees >= 16 px (selects #f-sort et cie, inputs). - ka/tokens.css : section SOCLE MOBILE OBLIGATOIRE (echelle --z-*, --vh100, verrou ka-scroll-lock, filet anti-zoom iOS). - public/ka-agent.js : agent KA corrige (safe-area, textarea 16 px, plein ecran auto <=640 px + verrou scroll). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 changed files +289 −28
modified
frontend/public/ka-agent.js
+25 −7
@@ -17,18 +17,19 @@ | ||
| 17 | 17 | |
| 18 | 18 | /* ---------- styles ---------- */ |
| 19 | 19 | var css = ` |
| 20 | − .kaa-btn{position:fixed;right:18px;bottom:18px;z-index:2147483000;width:58px;height:58px; | |
| 20 | + .kaa-btn{position:fixed;right:18px;bottom:calc(18px + env(safe-area-inset-bottom,0px));z-index:2147483000;width:58px;height:58px; | |
| 21 | 21 | border-radius:50%;border:2px solid var(--ink,#141814);background:var(--accent,#d9f26b); |
| 22 | 22 | color:var(--on-accent,#141814);font:700 17px/1 "Space Grotesk",system-ui,sans-serif; |
| 23 | 23 | cursor:pointer;box-shadow:4px 4px 0 rgba(20,24,20,.85);display:flex;align-items:center; |
| 24 | − justify-content:center;transition:transform .15s;padding-bottom:env(safe-area-inset-bottom,0)} | |
| 24 | + justify-content:center;transition:transform .15s} | |
| 25 | 25 | .kaa-btn:hover{transform:translate(-2px,-2px)} |
| 26 | − .kaa-panel{position:fixed;right:16px;bottom:86px;z-index:2147483001;width:min(392px,calc(100vw - 24px)); | |
| 27 | − height:min(580px,calc(100dvh - 110px));display:none;flex-direction:column;background:var(--paper,#f5f3ee); | |
| 26 | + .kaa-panel{position:fixed;right:16px;bottom:calc(86px + env(safe-area-inset-bottom,0px));z-index:2147483001;width:min(392px,calc(100vw - 24px)); | |
| 27 | + height:min(580px,calc(100vh - 110px));height:min(580px,calc(100dvh - 110px));display:none;flex-direction:column;background:var(--paper,#f5f3ee); | |
| 28 | 28 | border:2px solid var(--ink,#141814);border-radius:14px;box-shadow:8px 8px 0 rgba(20,24,20,.85); |
| 29 | 29 | overflow:hidden;font-family:Inter,system-ui,sans-serif} |
| 30 | 30 | .kaa-panel.kaa-open{display:flex} |
| 31 | − .kaa-panel.kaa-full{right:0;bottom:0;width:100vw;height:100dvh;border-radius:0;border:0;box-shadow:none} | |
| 31 | + .kaa-panel.kaa-full{right:0;bottom:0;width:100vw;height:100vh;height:100dvh;border-radius:0;border:0;box-shadow:none} | |
| 32 | + html.kaa-lock,html.kaa-lock body{overflow:hidden!important;overscroll-behavior:none} | |
| 32 | 33 | .kaa-head{display:flex;align-items:center;gap:9px;padding:11px 14px;background:var(--ink,#141814); |
| 33 | 34 | color:var(--paper,#f5f3ee);flex:none} |
| 34 | 35 | .kaa-head b{font:700 15px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.02em} |
@@ -53,7 +54,7 @@ | ||
| 53 | 54 | border-radius:999px;background:var(--surface-2,#faf9f5)} |
| 54 | 55 | .kaa-in{display:flex;gap:8px;padding:11px;border-top:2px solid var(--ink,#141814);background:#fff;flex:none; |
| 55 | 56 | padding-bottom:calc(11px + env(safe-area-inset-bottom,0))} |
| 56 | − .kaa-in textarea{flex:1;resize:none;min-height:44px;max-height:110px;padding:11px 12px;font:13.5px/1.4 Inter,system-ui,sans-serif; | |
| 57 | + .kaa-in textarea{flex:1;resize:none;min-height:44px;max-height:110px;padding:10px 12px;font:16px/1.4 Inter,system-ui,sans-serif; | |
| 57 | 58 | border:1.5px solid var(--ink,#141814);border-radius:9px;background:var(--paper,#f5f3ee);outline:none} |
| 58 | 59 | .kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px; |
| 59 | 60 | background:var(--ink,#141814);color:var(--accent,#d9f26b);font:700 16px "Space Grotesk",system-ui;cursor:pointer} |
@@ -142,15 +143,32 @@ | ||
| 142 | 143 | } |
| 143 | 144 | |
| 144 | 145 | /* ---------- ouverture / plein écran ---------- */ |
| 146 | + /* Sur téléphone : panneau plein écran d'office + scroll d'arrière-plan | |
| 147 | + verrouillé tant qu'il est ouvert (html.kaa-lock). Pas de focus clavier | |
| 148 | + forcé au tap (le clavier ne s'ouvre que si l'utilisateur touche le champ). */ | |
| 149 | + var mobileMq = window.matchMedia("(max-width: 640px)"); | |
| 150 | + var fineInput = window.matchMedia("(hover: hover) and (pointer: fine)"); | |
| 151 | + function syncLock() { | |
| 152 | + var open = panel.classList.contains("kaa-open"); | |
| 153 | + if (open && mobileMq.matches) panel.classList.add("kaa-full"); | |
| 154 | + document.documentElement.classList.toggle( | |
| 155 | + "kaa-lock", open && panel.classList.contains("kaa-full")); | |
| 156 | + } | |
| 145 | 157 | btn.addEventListener("click", function () { |
| 146 | 158 | panel.classList.toggle("kaa-open"); |
| 147 | − if (panel.classList.contains("kaa-open")) { render(); ta.focus(); } | |
| 159 | + if (panel.classList.contains("kaa-open")) { | |
| 160 | + render(); | |
| 161 | + if (fineInput.matches) ta.focus(); | |
| 162 | + } | |
| 163 | + syncLock(); | |
| 148 | 164 | }); |
| 149 | 165 | panel.querySelector(".kaa-x2").addEventListener("click", function () { |
| 150 | 166 | panel.classList.remove("kaa-open", "kaa-full"); |
| 167 | + syncLock(); | |
| 151 | 168 | }); |
| 152 | 169 | panel.querySelector(".kaa-x1").addEventListener("click", function () { |
| 153 | 170 | panel.classList.toggle("kaa-full"); |
| 171 | + syncLock(); | |
| 154 | 172 | scroll(); |
| 155 | 173 | }); |
| 156 | 174 | |
modified
frontend/src/App.tsx
+95 −19
@@ -62,6 +62,26 @@ function AccountMenu() { | ||
| 62 | 62 | const loc = useLocation(); |
| 63 | 63 | const nav = useNavigate(); |
| 64 | 64 | |
| 65 | + // menu ouvert = verrou du scroll d'arrière-plan + fermeture par Escape et | |
| 66 | + // par tap extérieur au niveau document (le backdrop fixed est confiné au | |
| 67 | + // header par son backdrop-filter — containing block — il ne couvre pas | |
| 68 | + // toute la page) | |
| 69 | + useEffect(() => { | |
| 70 | + if (!menuOpen) return; | |
| 71 | + document.body.style.overflow = "hidden"; | |
| 72 | + const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setMenuOpen(false); }; | |
| 73 | + const onDown = (e: PointerEvent) => { | |
| 74 | + if (!(e.target as Element | null)?.closest(".account")) setMenuOpen(false); | |
| 75 | + }; | |
| 76 | + window.addEventListener("keydown", onKey); | |
| 77 | + document.addEventListener("pointerdown", onDown); | |
| 78 | + return () => { | |
| 79 | + document.body.style.overflow = ""; | |
| 80 | + window.removeEventListener("keydown", onKey); | |
| 81 | + document.removeEventListener("pointerdown", onDown); | |
| 82 | + }; | |
| 83 | + }, [menuOpen]); | |
| 84 | + | |
| 65 | 85 | if (!enabled) return null; |
| 66 | 86 | if (!me) { |
| 67 | 87 | const next = encodeURIComponent(loc.pathname + loc.search); |
@@ -78,6 +98,7 @@ function AccountMenu() { | ||
| 78 | 98 | className="account-btn" |
| 79 | 99 | onClick={() => setMenuOpen(!menuOpen)} |
| 80 | 100 | aria-expanded={menuOpen} |
| 101 | + aria-haspopup="menu" | |
| 81 | 102 | aria-label={`Compte : ${me.name || me.email}`} |
| 82 | 103 | > |
| 83 | 104 | {me.picture |
@@ -132,10 +153,39 @@ function AccountMenu() { | ||
| 132 | 153 | ); |
| 133 | 154 | } |
| 134 | 155 | |
| 156 | +/* Liens de navigation — partagés entre la nav desktop et le menu mobile */ | |
| 157 | +const NAV_LINKS = [ | |
| 158 | + { to: "/", label: "Autos", icon: "🚗", end: true }, | |
| 159 | + { to: "/motos", label: "Motos", icon: "🏍️", end: false }, | |
| 160 | + { to: "/scooters", label: "Scooters", icon: "🛵", end: false }, | |
| 161 | + { to: "/stats", label: "Stats", icon: "📊", end: false }, | |
| 162 | + { to: "/sources", label: "Sources", icon: "🗂", end: false }, | |
| 163 | + { to: "/contact", label: "Contact", icon: "✉️", end: false }, | |
| 164 | +]; | |
| 165 | + | |
| 135 | 166 | function Header() { |
| 167 | + const [open, setOpen] = useState(false); | |
| 168 | + const location = useLocation(); | |
| 169 | + | |
| 170 | + // fermer le menu à chaque navigation + verrouiller le défilement en dessous | |
| 171 | + useEffect(() => { setOpen(false); }, [location]); | |
| 172 | + useEffect(() => { | |
| 173 | + if (!open) return; | |
| 174 | + document.body.style.overflow = "hidden"; | |
| 175 | + const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setOpen(false); }; | |
| 176 | + window.addEventListener("keydown", onKey); | |
| 177 | + return () => { | |
| 178 | + document.body.style.overflow = ""; | |
| 179 | + window.removeEventListener("keydown", onKey); | |
| 180 | + }; | |
| 181 | + }, [open]); | |
| 182 | + | |
| 136 | 183 | return ( |
| 137 | 184 | <> |
| 138 | − <header className="header"> | |
| 185 | + {/* .menu-open remonte le header au-dessus du backdrop (posé HORS du | |
| 186 | + header : son backdrop-filter créerait un containing block pour | |
| 187 | + position:fixed) */} | |
| 188 | + <header className={`header ${open ? "menu-open" : ""}`}> | |
| 139 | 189 | <div className="container header-inner"> |
| 140 | 190 | <NavLink to="/" className="brand" aria-label="Auto-Ka — accueil"> |
| 141 | 191 | Auto<span className="ka">Ka</span> |
@@ -143,28 +193,54 @@ function Header() { | ||
| 143 | 193 | </NavLink> |
| 144 | 194 | <GroupeKaBadge /> |
| 145 | 195 | <nav className="nav" aria-label="Navigation principale"> |
| 146 | − <NavLink to="/" end className={({ isActive }) => (isActive ? "active" : "")}> | |
| 147 | − Autos | |
| 148 | − </NavLink> | |
| 149 | − <NavLink to="/motos" className={({ isActive }) => (isActive ? "active" : "")}> | |
| 150 | − Motos | |
| 151 | − </NavLink> | |
| 152 | − <NavLink to="/scooters" className={({ isActive }) => (isActive ? "active" : "")}> | |
| 153 | − Scooters | |
| 154 | − </NavLink> | |
| 155 | − <NavLink to="/stats" className={({ isActive }) => (isActive ? "active" : "")}> | |
| 156 | − Stats | |
| 157 | − </NavLink> | |
| 158 | − <NavLink to="/sources" className={({ isActive }) => (isActive ? "active" : "")}> | |
| 159 | − Sources | |
| 160 | − </NavLink> | |
| 161 | − <NavLink to="/contact" className={({ isActive }) => (isActive ? "active" : "")}> | |
| 162 | − Contact | |
| 163 | − </NavLink> | |
| 196 | + {NAV_LINKS.map((l) => ( | |
| 197 | + <NavLink | |
| 198 | + key={l.to} | |
| 199 | + to={l.to} | |
| 200 | + end={l.end} | |
| 201 | + className={({ isActive }) => (isActive ? "active" : "")} | |
| 202 | + > | |
| 203 | + {l.label} | |
| 204 | + </NavLink> | |
| 205 | + ))} | |
| 164 | 206 | </nav> |
| 165 | 207 | <AccountMenu /> |
| 208 | + <button | |
| 209 | + className={`menu-btn ${open ? "open" : ""}`} | |
| 210 | + aria-expanded={open} | |
| 211 | + aria-label={open ? "Fermer le menu" : "Ouvrir le menu"} | |
| 212 | + onClick={() => setOpen(!open)} | |
| 213 | + > | |
| 214 | + <span /><span /><span /> | |
| 215 | + </button> | |
| 216 | + </div> | |
| 217 | + | |
| 218 | + {/* menu déroulant mobile */} | |
| 219 | + <div className={`mobile-menu ${open ? "open" : ""}`} role="navigation" aria-label="Menu mobile"> | |
| 220 | + {NAV_LINKS.map((l, i) => ( | |
| 221 | + <NavLink | |
| 222 | + key={l.to} | |
| 223 | + to={l.to} | |
| 224 | + end={l.end} | |
| 225 | + style={{ transitionDelay: open ? `${60 + i * 45}ms` : "0ms" }} | |
| 226 | + className={({ isActive }) => `mm-link ${isActive ? "active" : ""}`} | |
| 227 | + onClick={() => setOpen(false)} | |
| 228 | + > | |
| 229 | + <span className="mm-ico" aria-hidden="true">{l.icon}</span> | |
| 230 | + {l.label} | |
| 231 | + <span className="mm-arrow" aria-hidden="true">→</span> | |
| 232 | + </NavLink> | |
| 233 | + ))} | |
| 234 | + <div className="mm-badge"> | |
| 235 | + <GroupeKaBadge /> | |
| 236 | + </div> | |
| 237 | + <div className="mm-foot"> | |
| 238 | + Agrégateur indépendant — mis à jour automatiquement, chaque fiche | |
| 239 | + renvoie à l'annonce originale du concessionnaire. | |
| 240 | + </div> | |
| 166 | 241 | </div> |
| 167 | 242 | </header> |
| 243 | + {open && <div className="mm-backdrop" onClick={() => setOpen(false)} aria-hidden="true" />} | |
| 168 | 244 | <Ticker /> |
| 169 | 245 | {/* variante mobile du badge (le badge du header est masqué < 1024 px) */} |
| 170 | 246 | <div className="gk-mobile"> |
modified
frontend/src/ka/tokens.css
+87 −0
@@ -69,7 +69,24 @@ | ||
| 69 | 69 | |
| 70 | 70 | /* Cible tactile minimale */ |
| 71 | 71 | --touch: 44px; |
| 72 | + | |
| 73 | + /* ---- Échelle z-index COMMUNE (obligatoire — aucune valeur arbitraire) ---- | |
| 74 | + grain de film body::before = 9999 (pointer-events:none, toujours au-dessus, | |
| 75 | + purement visuel). Tout composant interactif se place sous 9999 : */ | |
| 76 | + --z-content: 1; /* contenu positionné ordinaire */ | |
| 77 | + --z-sticky: 300; /* éléments sticky de contenu (sous-nav…) */ | |
| 78 | + --z-header: 500; /* header du site */ | |
| 79 | + --z-bottombar: 600; /* tab bar / barre d'action basse */ | |
| 80 | + --z-dropdown: 700; /* menus déroulants (au-dessus des barres) */ | |
| 81 | + --z-overlay: 800; /* voile sous modale/panneau */ | |
| 82 | + --z-modal: 900; /* modales, panneaux de filtres, galeries */ | |
| 83 | + --z-toast: 950; /* notifications */ | |
| 84 | + | |
| 85 | + /* ---- Hauteur de viewport DYNAMIQUE (barre du navigateur mobile qui se | |
| 86 | + replie au scroll) : toujours var(--vh100), JAMAIS 100vh en dur. ---- */ | |
| 87 | + --vh100: 100vh; | |
| 72 | 88 | } |
| 89 | +@supports (height: 100dvh) { :root { --vh100: 100dvh; } } | |
| 73 | 90 | |
| 74 | 91 | /* ---- Socle ---- */ |
| 75 | 92 | * { box-sizing: border-box; } |
@@ -227,3 +244,73 @@ body > * { position: relative; } | ||
| 227 | 244 | /* ---- Utilitaires responsive ---- */ |
| 228 | 245 | .only-mobile { display: initial; } .only-desktop { display: none; } |
| 229 | 246 | @media (min-width: 768px) { .only-mobile { display: none; } .only-desktop { display: initial; } } |
| 247 | + | |
| 248 | +/* ============================================================================= | |
| 249 | + SOCLE MOBILE OBLIGATOIRE (2026-08-19) — règles communes aux 13 sites. | |
| 250 | + RÈGLES pour tout composant futur : | |
| 251 | + · TAP, jamais :hover, pour ouvrir un menu (le survol n'existe pas au doigt) ; | |
| 252 | + le :hover ne sert qu'aux effets décoratifs, sous @media (hover: hover). | |
| 253 | + · var(--vh100) (dvh) et JAMAIS 100vh pour tout élément calé sur le viewport. | |
| 254 | + · position:fixed exige qu'AUCUN ancêtre n'ait transform/filter/perspective/ | |
| 255 | + will-change/backdrop-filter — sinon monter l'élément à la racine (body). | |
| 256 | + · Barre basse fixe = .ka-bottombar + classe has-bottombar sur <body>. | |
| 257 | + · Menu/panneau ouvert = .ka-scroll-lock sur <html> (scroll arrière-plan gelé). | |
| 258 | + · z-index : uniquement l'échelle --z-* ci-dessus. | |
| 259 | + · Zones tactiles ≥ var(--touch) (44 px), champs ≥ 16 px (zoom iOS). | |
| 260 | + ========================================================================== */ | |
| 261 | + | |
| 262 | +/* Anti-zoom iOS : au doigt, aucun champ sous 16 px (le focus d'un champ <16 px | |
| 263 | + déclenche un zoom automatique de page sur Safari iOS). !important assumé : | |
| 264 | + c'est un filet d'accessibilité, un champ plus petit casse le zoom quoi | |
| 265 | + qu'il arrive — ne s'applique qu'aux écrans tactiles. */ | |
| 266 | +@media (hover: none) and (pointer: coarse) { | |
| 267 | + input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), | |
| 268 | + select, textarea { font-size: max(16px, 1em) !important; } | |
| 269 | +} | |
| 270 | + | |
| 271 | +/* Zone tactile du badge Groupe KA étendue à ≥44 px au doigt, sans changer | |
| 272 | + son rendu (débord de la zone cliquable via pseudo-élément). */ | |
| 273 | +@media (pointer: coarse) { | |
| 274 | + .gk-badge { position: relative; } | |
| 275 | + .gk-badge::after { content: ""; position: absolute; inset: -8px; } | |
| 276 | +} | |
| 277 | + | |
| 278 | +/* Hauteurs viewport dynamiques */ | |
| 279 | +.h-viewport { height: 100vh; height: 100dvh; } | |
| 280 | +.min-h-viewport { min-height: 100vh; min-height: 100dvh; } | |
| 281 | + | |
| 282 | +/* Barre fixe basse fiable : safe-area iPhone incluse, immunisée contre les | |
| 283 | + transforms accidentels, et compensation d'espace via body.has-bottombar. */ | |
| 284 | +.ka-bottombar { | |
| 285 | + position: fixed; left: 0; right: 0; bottom: 0; | |
| 286 | + z-index: var(--z-bottombar); | |
| 287 | + padding-bottom: env(safe-area-inset-bottom, 0px); | |
| 288 | + background: var(--surface); | |
| 289 | + border-top: 1.5px solid var(--ink); | |
| 290 | + transform: none !important; filter: none !important; | |
| 291 | +} | |
| 292 | +body.has-bottombar { padding-bottom: calc(var(--bottombar-h, 64px) + env(safe-area-inset-bottom, 0px)); } | |
| 293 | + | |
| 294 | +/* Verrou de scroll d'arrière-plan (menu mobile, modale, panneau de filtres | |
| 295 | + ouverts) : ajouter/retirer .ka-scroll-lock sur <html>. */ | |
| 296 | +html.ka-scroll-lock, html.ka-scroll-lock body { overflow: hidden !important; overscroll-behavior: none; } | |
| 297 | + | |
| 298 | +/* Menu déroulant de référence : au-dessus de tout contenu (cartes Mapbox | |
| 299 | + incluses), défilement interne avec élan, jamais plus haut que l'écran. */ | |
| 300 | +.ka-menu { | |
| 301 | + position: absolute; z-index: var(--z-dropdown); | |
| 302 | + min-width: 180px; | |
| 303 | + max-height: min(60vh, 420px); max-height: min(60dvh, 420px); | |
| 304 | + overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; | |
| 305 | + background: var(--surface); border: 1.5px solid var(--ink); | |
| 306 | + border-radius: var(--r-card); box-shadow: var(--shadow-off-mid); | |
| 307 | +} | |
| 308 | +.ka-menu a, .ka-menu button, .ka-menu label, .ka-menu [role="menuitem"], .ka-menu [role="option"] { | |
| 309 | + display: flex; align-items: center; min-height: var(--touch); | |
| 310 | + padding: 10px 14px; width: 100%; text-decoration: none; | |
| 311 | +} | |
| 312 | +/* Voile plein écran sous une modale / un panneau */ | |
| 313 | +.ka-overlay { | |
| 314 | + position: fixed; inset: 0; z-index: var(--z-overlay); | |
| 315 | + background: rgba(20, 24, 20, 0.45); | |
| 316 | +} | |
modified
frontend/src/styles.css
+82 −2
@@ -43,9 +43,12 @@ button { font-family: inherit; } | ||
| 43 | 43 | } |
| 44 | 44 | .header-inner { display: flex; align-items: center; gap: 20px; height: 64px; } |
| 45 | 45 | .brand { |
| 46 | + position: relative; | |
| 46 | 47 | font-family: var(--font-display); font-weight: 700; font-size: 26px; |
| 47 | 48 | letter-spacing: -0.04em; display: flex; align-items: center; line-height: 1; |
| 48 | 49 | } |
| 50 | +/* Cible tactile ≥44 px pour le logo (102×32 au naturel) */ | |
| 51 | +.brand::after { content: ""; position: absolute; inset: -8px; } | |
| 49 | 52 | .brand .ka { |
| 50 | 53 | background: var(--ink); color: var(--accent); padding: 2px 7px 4px; |
| 51 | 54 | border-radius: 6px; margin-left: 3px; transform: rotate(-2deg); |
@@ -471,9 +474,9 @@ button { font-family: inherit; } | ||
| 471 | 474 | background: var(--accent); color: var(--on-accent); width: 100%; height: 100%; |
| 472 | 475 | display: flex; align-items: center; justify-content: center; |
| 473 | 476 | } |
| 474 | −.account-backdrop { position: fixed; inset: 0; z-index: 60; } | |
| 477 | +.account-backdrop { position: fixed; inset: 0; z-index: var(--z-overlay, 800); } | |
| 475 | 478 | .account-menu { |
| 476 | − position: absolute; right: 0; top: calc(100% + 10px); z-index: 61; | |
| 479 | + position: absolute; right: 0; top: calc(100% + 10px); z-index: var(--z-modal, 900); | |
| 477 | 480 | min-width: 250px; background: var(--surface); |
| 478 | 481 | border: 2px solid var(--ink); border-radius: var(--r-card); |
| 479 | 482 | box-shadow: 6px 6px 0 rgba(23, 24, 28, 0.16); overflow: hidden; |
@@ -632,3 +635,80 @@ a.hub-chip:hover { border-color: var(--ink); color: var(--ink); } | ||
| 632 | 635 | .seo-links a.spec-chip:hover { border-color: var(--accent); } |
| 633 | 636 | .src-list { columns: 3; column-gap: 28px; padding-left: 18px; font-size: 13.5px; line-height: 1.9; } |
| 634 | 637 | @media (max-width: 800px) { .src-list { columns: 1; } } |
| 638 | + | |
| 639 | +/* ========================================================================== | |
| 640 | + Menu déroulant mobile (hamburger standard KA) + socle mobile — 2026-08-19 | |
| 641 | + La nav en bande scroll-x est remplacée sous 760 px par le hamburger. | |
| 642 | + ========================================================================== */ | |
| 643 | +.menu-btn { | |
| 644 | + display: none; position: relative; z-index: 60; | |
| 645 | + width: 44px; height: 44px; margin-left: auto; flex: none; | |
| 646 | + border: 1.5px solid var(--ink); border-radius: var(--r-ctl); | |
| 647 | + background: var(--surface); cursor: pointer; padding: 0; | |
| 648 | + flex-direction: column; align-items: center; justify-content: center; gap: 5px; | |
| 649 | + box-shadow: 3px 3px 0 rgba(23, 24, 28, 0.15); | |
| 650 | + transition: box-shadow 0.15s ease, transform 0.15s ease; | |
| 651 | +} | |
| 652 | +.menu-btn:active { transform: translate(2px, 2px); box-shadow: 1px 1px 0 rgba(23, 24, 28, 0.15); } | |
| 653 | +.menu-btn span { | |
| 654 | + display: block; width: 18px; height: 2px; background: var(--ink); | |
| 655 | + border-radius: 2px; transition: transform 0.25s ease, opacity 0.2s ease; | |
| 656 | +} | |
| 657 | +.menu-btn.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); } | |
| 658 | +.menu-btn.open span:nth-child(2) { opacity: 0; } | |
| 659 | +.menu-btn.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); } | |
| 660 | + | |
| 661 | +.mobile-menu { | |
| 662 | + display: none; position: absolute; left: 0; right: 0; top: 100%; | |
| 663 | + background: var(--paper); border-bottom: 2px solid var(--ink); | |
| 664 | + box-shadow: 0 18px 30px rgba(23, 24, 28, 0.18); | |
| 665 | + padding: 8px 16px calc(16px + env(safe-area-inset-bottom)); | |
| 666 | + max-height: 0; overflow: hidden; opacity: 0; | |
| 667 | + transition: max-height 0.3s ease, opacity 0.22s ease; | |
| 668 | +} | |
| 669 | +.mobile-menu.open { max-height: 480px; opacity: 1; overflow-y: auto; } | |
| 670 | +.mm-link { | |
| 671 | + display: flex; align-items: center; gap: 12px; | |
| 672 | + padding: 15px 10px; min-height: 52px; | |
| 673 | + font-family: var(--font-display); font-weight: 700; font-size: 19px; | |
| 674 | + letter-spacing: -0.02em; border-bottom: 1.5px dashed var(--line); | |
| 675 | + opacity: 0; transform: translateY(-8px); | |
| 676 | + transition: opacity 0.25s ease, transform 0.25s ease; | |
| 677 | +} | |
| 678 | +.mobile-menu.open .mm-link { opacity: 1; transform: translateY(0); } | |
| 679 | +.mm-link.active { color: var(--accent-deep); } | |
| 680 | +.mm-link.active .mm-arrow { opacity: 1; } | |
| 681 | +.mm-ico { width: 26px; text-align: center; font-size: 17px; } | |
| 682 | +.mm-arrow { margin-left: auto; opacity: 0.25; transition: opacity 0.15s ease; } | |
| 683 | +.mm-link:active { background: var(--accent-soft); border-radius: var(--r-ctl); } | |
| 684 | +.mm-badge { display: flex; padding: 12px 10px 0; } | |
| 685 | +.mm-foot { | |
| 686 | + padding: 12px 10px 4px; font-family: var(--font-mono); | |
| 687 | + font-size: 10.5px; color: var(--ink-3); letter-spacing: 0.04em; | |
| 688 | +} | |
| 689 | +/* Backdrop rendu en FRÈRE du header (le backdrop-filter du header en ferait | |
| 690 | + le containing block d'un position:fixed) ; .menu-open remonte le header | |
| 691 | + au-dessus du backdrop pour que le panneau reste visible. */ | |
| 692 | +.mm-backdrop { | |
| 693 | + position: fixed; inset: 0; z-index: var(--z-overlay, 800); | |
| 694 | + background: rgba(23, 24, 28, 0.35); backdrop-filter: blur(2px); | |
| 695 | +} | |
| 696 | +.header.menu-open { z-index: var(--z-modal, 900); } | |
| 697 | +@media (max-width: 760px) { | |
| 698 | + .menu-btn { display: flex; } | |
| 699 | + .nav { display: none; } | |
| 700 | + .mobile-menu { display: block; } | |
| 701 | +} | |
| 702 | + | |
| 703 | +/* Anti-zoom iOS généralisé : ≥16 px sur tous les champs et selects en | |
| 704 | + contexte tactile (couvre #f-sort et les 9 selects de la page d'accueil ; | |
| 705 | + renfort explicite du filet de ka/tokens.css). */ | |
| 706 | +@media (hover: none) and (pointer: coarse) { | |
| 707 | + select, | |
| 708 | + input[type="text"], | |
| 709 | + input[type="search"], | |
| 710 | + input[type="number"], | |
| 711 | + .f-field select, .f-field input, .sort-box select { | |
| 712 | + font-size: max(16px, 1em) !important; | |
| 713 | + } | |
| 714 | +} | |
| 635 | 715 | |