SPB Git forge

spb/auto-ka

Public
61commits 1branches 0releases
14.4 MBsize
maindefault branch
12 days agolast push
Python 61.6% TypeScript 20.9% CSS 11.4% JavaScript 5.1% HTML 1.1%

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>
Simon-Pierre Boucher committed 1 mo ago (Aug 19, 2026) parent 4a7b276

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