SPB Git

Repository Public

Commit

Mobile : socle mobile tokens.css injecté, champs ≥16 px jusqu au breakpoint tabbar, logo tactile, ka-agent corrigé

- frontend/src/ka/tokens.css : socle mobile obligatoire (échelle z-index --z-*,
  --vh100 dvh, .ka-bottombar, .ka-scroll-lock, .ka-menu/.ka-overlay, filet
  anti-zoom iOS 16 px, zone tactile étendue du gk-badge) — inliné au build.
- frontend/src/index.template.html : champs de recherche/filtres (.search-input
  input, select.f) ≥16 px étendus de ≤760 px à ≤940 px (breakpoint tabbar) ;
  zone tactile ≥44 px du logo .brand (::after inset -8px, rendu inchangé).
- frontend/src/ka-agent.js : safe-areas, textarea 16 px, plein écran ≤640 px +
  verrou kaa-lock — recopié dans dist/ par build_frontend.py.
- Vérifié : les boutons d en-tête masqués ≤940 px restent tous joignables —
  Statistiques et Explorer l annuaire via la tabbar basse, Contact via la nav
  du footer local ; le bouton compte KA ID reste visible dans le header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed · Aug 19, 2026 parent 5d23feb
5 changed files +321 −14 Browse files
modified frontend/dist/index.html +179 −0
@@ -74,7 +74,24 @@
74 74
75 /* Cible tactile minimale */ 75 /* Cible tactile minimale */
76 --touch: 44px; 76 --touch: 44px;
77+
78+ /* ---- Échelle z-index COMMUNE (obligatoire — aucune valeur arbitraire) ----
79+ grain de film body::before = 9999 (pointer-events:none, toujours au-dessus,
80+ purement visuel). Tout composant interactif se place sous 9999 : */
81+ --z-content: 1; /* contenu positionné ordinaire */
82+ --z-sticky: 300; /* éléments sticky de contenu (sous-nav…) */
83+ --z-header: 500; /* header du site */
84+ --z-bottombar: 600; /* tab bar / barre d'action basse */
85+ --z-dropdown: 700; /* menus déroulants (au-dessus des barres) */
86+ --z-overlay: 800; /* voile sous modale/panneau */
87+ --z-modal: 900; /* modales, panneaux de filtres, galeries */
88+ --z-toast: 950; /* notifications */
89+
90+ /* ---- Hauteur de viewport DYNAMIQUE (barre du navigateur mobile qui se
91+ replie au scroll) : toujours var(--vh100), JAMAIS 100vh en dur. ---- */
92+ --vh100: 100vh;
77 } 93 }
94+@supports (height: 100dvh) { :root { --vh100: 100dvh; } }
78 95
79 /* ---- Socle ---- */ 96 /* ---- Socle ---- */
80 * { box-sizing: border-box; } 97 * { box-sizing: border-box; }
@@ -232,6 +249,76 @@ body > * { position: relative; }
232 /* ---- Utilitaires responsive ---- */ 249 /* ---- Utilitaires responsive ---- */
233 .only-mobile { display: initial; } .only-desktop { display: none; } 250 .only-mobile { display: initial; } .only-desktop { display: none; }
234 @media (min-width: 768px) { .only-mobile { display: none; } .only-desktop { display: initial; } } 251 @media (min-width: 768px) { .only-mobile { display: none; } .only-desktop { display: initial; } }
252+
253+/* =============================================================================
254+ SOCLE MOBILE OBLIGATOIRE (2026-08-19) — règles communes aux 13 sites.
255+ RÈGLES pour tout composant futur :
256+ · TAP, jamais :hover, pour ouvrir un menu (le survol n'existe pas au doigt) ;
257+ le :hover ne sert qu'aux effets décoratifs, sous @media (hover: hover).
258+ · var(--vh100) (dvh) et JAMAIS 100vh pour tout élément calé sur le viewport.
259+ · position:fixed exige qu'AUCUN ancêtre n'ait transform/filter/perspective/
260+ will-change/backdrop-filter — sinon monter l'élément à la racine (body).
261+ · Barre basse fixe = .ka-bottombar + classe has-bottombar sur <body>.
262+ · Menu/panneau ouvert = .ka-scroll-lock sur <html> (scroll arrière-plan gelé).
263+ · z-index : uniquement l'échelle --z-* ci-dessus.
264+ · Zones tactiles ≥ var(--touch) (44 px), champs ≥ 16 px (zoom iOS).
265+ ========================================================================== */
266+
267+/* Anti-zoom iOS : au doigt, aucun champ sous 16 px (le focus d'un champ <16 px
268+ déclenche un zoom automatique de page sur Safari iOS). !important assumé :
269+ c'est un filet d'accessibilité, un champ plus petit casse le zoom quoi
270+ qu'il arrive — ne s'applique qu'aux écrans tactiles. */
271+@media (hover: none) and (pointer: coarse) {
272+ input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
273+ select, textarea { font-size: max(16px, 1em) !important; }
274+}
275+
276+/* Zone tactile du badge Groupe KA étendue à ≥44 px au doigt, sans changer
277+ son rendu (débord de la zone cliquable via pseudo-élément). */
278+@media (pointer: coarse) {
279+ .gk-badge { position: relative; }
280+ .gk-badge::after { content: ""; position: absolute; inset: -8px; }
281+}
282+
283+/* Hauteurs viewport dynamiques */
284+.h-viewport { height: 100vh; height: 100dvh; }
285+.min-h-viewport { min-height: 100vh; min-height: 100dvh; }
286+
287+/* Barre fixe basse fiable : safe-area iPhone incluse, immunisée contre les
288+ transforms accidentels, et compensation d'espace via body.has-bottombar. */
289+.ka-bottombar {
290+ position: fixed; left: 0; right: 0; bottom: 0;
291+ z-index: var(--z-bottombar);
292+ padding-bottom: env(safe-area-inset-bottom, 0px);
293+ background: var(--surface);
294+ border-top: 1.5px solid var(--ink);
295+ transform: none !important; filter: none !important;
296+}
297+body.has-bottombar { padding-bottom: calc(var(--bottombar-h, 64px) + env(safe-area-inset-bottom, 0px)); }
298+
299+/* Verrou de scroll d'arrière-plan (menu mobile, modale, panneau de filtres
300+ ouverts) : ajouter/retirer .ka-scroll-lock sur <html>. */
301+html.ka-scroll-lock, html.ka-scroll-lock body { overflow: hidden !important; overscroll-behavior: none; }
302+
303+/* Menu déroulant de référence : au-dessus de tout contenu (cartes Mapbox
304+ incluses), défilement interne avec élan, jamais plus haut que l'écran. */
305+.ka-menu {
306+ position: absolute; z-index: var(--z-dropdown);
307+ min-width: 180px;
308+ max-height: min(60vh, 420px); max-height: min(60dvh, 420px);
309+ overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
310+ background: var(--surface); border: 1.5px solid var(--ink);
311+ border-radius: var(--r-card); box-shadow: var(--shadow-off-mid);
312+}
313+.ka-menu a, .ka-menu button, .ka-menu label, .ka-menu [role="menuitem"], .ka-menu [role="option"] {
314+ display: flex; align-items: center; min-height: var(--touch);
315+ padding: 10px 14px; width: 100%; text-decoration: none;
316+}
317+/* Voile plein écran sous une modale / un panneau */
318+.ka-overlay {
319+ position: fixed; inset: 0; z-index: var(--z-overlay);
320+ background: rgba(20, 24, 20, 0.45);
321+}
235 (ka/tokens.css — design system commun Groupe KA, 322 (ka/tokens.css — design system commun Groupe KA,
236 importé EN PREMIER) et {"org":{"name":"Groupe KA","legalName":"Groupe KA — Simon-Pierre Boucher","tagline":"Holding québécois d'agrégateurs de produits et services entièrement automatisés.","disclaimer":"Groupe KA est un agrégateur de contenu : nous ne vendons rien, ne louons rien et ne sommes partie à aucune transaction.","copyrightHolder":"Groupe KA — Simon-Pierre Boucher"},"hub":{"url":"https://www.groupe-ka.com","loginPath":"/connexion","signupNote":"La création de compte KA ID se fait sur le hub groupe-ka.com ; chaque site délègue sa connexion via /api/auth/ka/login."},"contacts":[{"email":"contact@groupe-ka.com","role":"Projets, partenariats & données"},{"email":"info@groupe-ka.com","role":"Médias & questions générales"},{"email":"admin@groupe-ka.com","role":"Légal, vie privée & Loi 25"}],"legal":[{"label":"Conditions d'utilisation","href":"https://www.groupe-ka.com/conditions"},{"label":"Politique de confidentialité","href":"https://www.groupe-ka.com/confidentialite"},{"label":"Protection des renseignements personnels (Loi 25)","href":"https://www.groupe-ka.com/loi-25"},{"label":"Transparence des robots d'indexation","href":"https://www.groupe-ka.com/bots"}],"sites":[{"id":"groupe-ka","wordmark":"Groupe KA","domain":"www.groupe-ka.com","accent":"#d9f26b","accentSoft":"#f0f9d2","accentDeep":"#123f2e","onAccent":"#141814","tagline":"Le portail de l'écosystème ·Ka"},{"id":"trouve-ka","wordmark":"Trouve·Ka","domain":"www.trouve-ka.com","accent":"#1c7ed6","accentSoft":"#e7f2fd","accentDeep":"#14508f","onAccent":"#ffffff","tagline":"Le moteur de recherche du web québécois"},{"id":"lou-ka","wordmark":"Lou·Ka","domain":"www.lou-ka.com","accent":"#ff6a00","accentSoft":"#fff1e6","accentDeep":"#cc5500","onAccent":"#ffffff","tagline":"Tous les logements à louer"},{"id":"immo-ka","wordmark":"Immo·Ka","domain":"www.immo-ka.com","accent":"#e23744","accentSoft":"#fbe0e2","accentDeep":"#a8232e","onAccent":"#ffffff","tagline":"Toutes les propriétés à vendre"},{"id":"vrai-prix","wordmark":"Vrai-Prix","domain":"www.vrai-prix.com","accent":"#ff5148","accentSoft":"#ffe3e0","accentDeep":"#9e2a25","onAccent":"#ffffff","tagline":"La valeur réelle de chaque propriété"},{"id":"auto-ka","wordmark":"Auto·Ka","domain":"www.auto-ka.com","accent":"#ff5a2a","accentSoft":"#ffe8de","accentDeep":"#cc3f16","onAccent":"#ffffff","tagline":"Les voitures usagées du Québec"},{"id":"fabri-ka","wordmark":"Fabri·Ka","domain":"www.fabri-ka.com","accent":"#c4532e","accentSoft":"#f7e3da","accentDeep":"#a94525","onAccent":"#ffffff","tagline":"Les produits fabriqués au Québec"},{"id":"food-ka","wordmark":"Food·Ka","domain":"www.food-ka.com","accent":"#1f9d55","accentSoft":"#e2f5ea","accentDeep":"#157a40","onAccent":"#ffffff","tagline":"Les prix d'épicerie, suivis à la source"},{"id":"resto-ka","wordmark":"Resto·Ka","domain":"www.resto-ka.com","accent":"#f08c00","accentSoft":"#fdeed7","accentDeep":"#b96a00","onAccent":"#141814","tagline":"Chaque resto, chaque plat, chaque prix"},{"id":"sorti-ka","wordmark":"Sorti·Ka","domain":"www.sorti-ka.com","accent":"#d6336c","accentSoft":"#fbe0eb","accentDeep":"#a12551","onAccent":"#ffffff","tagline":"Toutes les sorties, dans les 17 régions"},{"id":"crea-ka","wordmark":"Créa·Ka","domain":"www.crea-ka.com","accent":"#7048e8","accentSoft":"#ece5fc","accentDeep":"#5433b8","onAccent":"#ffffff","tagline":"Les créateurs d'ici, tous leurs liens"},{"id":"api-ka","wordmark":"API·Ka","domain":"www.api-ka.com","accent":"#3b5bdb","accentSoft":"#e4eafb","accentDeep":"#2b44a8","onAccent":"#ffffff","tagline":"La donnée de l'écosystème, par API"},{"id":"job-ka","wordmark":"Job·Ka","domain":"www.job-ka.com","accent":"#0c8599","accentSoft":"#def0f4","accentDeep":"#095c6b","onAccent":"#ffffff","tagline":"Tous les emplois des employeurs québécois"}],"extraFooterLinks":[{"label":"ValoPlex","href":"https://www.valoplex.com"},{"label":"Ka2","href":"https://www.ka2.bot"},{"label":"Ka4","href":"https://www.ka4.bot"},{"label":"Ka6","href":"https://www.ka6.bot"}]} (ka/ecosystem.json — contacts, 323 importé EN PREMIER) et {"org":{"name":"Groupe KA","legalName":"Groupe KA — Simon-Pierre Boucher","tagline":"Holding québécois d'agrégateurs de produits et services entièrement automatisés.","disclaimer":"Groupe KA est un agrégateur de contenu : nous ne vendons rien, ne louons rien et ne sommes partie à aucune transaction.","copyrightHolder":"Groupe KA — Simon-Pierre Boucher"},"hub":{"url":"https://www.groupe-ka.com","loginPath":"/connexion","signupNote":"La création de compte KA ID se fait sur le hub groupe-ka.com ; chaque site délègue sa connexion via /api/auth/ka/login."},"contacts":[{"email":"contact@groupe-ka.com","role":"Projets, partenariats & données"},{"email":"info@groupe-ka.com","role":"Médias & questions générales"},{"email":"admin@groupe-ka.com","role":"Légal, vie privée & Loi 25"}],"legal":[{"label":"Conditions d'utilisation","href":"https://www.groupe-ka.com/conditions"},{"label":"Politique de confidentialité","href":"https://www.groupe-ka.com/confidentialite"},{"label":"Protection des renseignements personnels (Loi 25)","href":"https://www.groupe-ka.com/loi-25"},{"label":"Transparence des robots d'indexation","href":"https://www.groupe-ka.com/bots"}],"sites":[{"id":"groupe-ka","wordmark":"Groupe KA","domain":"www.groupe-ka.com","accent":"#d9f26b","accentSoft":"#f0f9d2","accentDeep":"#123f2e","onAccent":"#141814","tagline":"Le portail de l'écosystème ·Ka"},{"id":"trouve-ka","wordmark":"Trouve·Ka","domain":"www.trouve-ka.com","accent":"#1c7ed6","accentSoft":"#e7f2fd","accentDeep":"#14508f","onAccent":"#ffffff","tagline":"Le moteur de recherche du web québécois"},{"id":"lou-ka","wordmark":"Lou·Ka","domain":"www.lou-ka.com","accent":"#ff6a00","accentSoft":"#fff1e6","accentDeep":"#cc5500","onAccent":"#ffffff","tagline":"Tous les logements à louer"},{"id":"immo-ka","wordmark":"Immo·Ka","domain":"www.immo-ka.com","accent":"#e23744","accentSoft":"#fbe0e2","accentDeep":"#a8232e","onAccent":"#ffffff","tagline":"Toutes les propriétés à vendre"},{"id":"vrai-prix","wordmark":"Vrai-Prix","domain":"www.vrai-prix.com","accent":"#ff5148","accentSoft":"#ffe3e0","accentDeep":"#9e2a25","onAccent":"#ffffff","tagline":"La valeur réelle de chaque propriété"},{"id":"auto-ka","wordmark":"Auto·Ka","domain":"www.auto-ka.com","accent":"#ff5a2a","accentSoft":"#ffe8de","accentDeep":"#cc3f16","onAccent":"#ffffff","tagline":"Les voitures usagées du Québec"},{"id":"fabri-ka","wordmark":"Fabri·Ka","domain":"www.fabri-ka.com","accent":"#c4532e","accentSoft":"#f7e3da","accentDeep":"#a94525","onAccent":"#ffffff","tagline":"Les produits fabriqués au Québec"},{"id":"food-ka","wordmark":"Food·Ka","domain":"www.food-ka.com","accent":"#1f9d55","accentSoft":"#e2f5ea","accentDeep":"#157a40","onAccent":"#ffffff","tagline":"Les prix d'épicerie, suivis à la source"},{"id":"resto-ka","wordmark":"Resto·Ka","domain":"www.resto-ka.com","accent":"#f08c00","accentSoft":"#fdeed7","accentDeep":"#b96a00","onAccent":"#141814","tagline":"Chaque resto, chaque plat, chaque prix"},{"id":"sorti-ka","wordmark":"Sorti·Ka","domain":"www.sorti-ka.com","accent":"#d6336c","accentSoft":"#fbe0eb","accentDeep":"#a12551","onAccent":"#ffffff","tagline":"Toutes les sorties, dans les 17 régions"},{"id":"crea-ka","wordmark":"Créa·Ka","domain":"www.crea-ka.com","accent":"#7048e8","accentSoft":"#ece5fc","accentDeep":"#5433b8","onAccent":"#ffffff","tagline":"Les créateurs d'ici, tous leurs liens"},{"id":"api-ka","wordmark":"API·Ka","domain":"www.api-ka.com","accent":"#3b5bdb","accentSoft":"#e4eafb","accentDeep":"#2b44a8","onAccent":"#ffffff","tagline":"La donnée de l'écosystème, par API"},{"id":"job-ka","wordmark":"Job·Ka","domain":"www.job-ka.com","accent":"#0c8599","accentSoft":"#def0f4","accentDeep":"#095c6b","onAccent":"#ffffff","tagline":"Tous les emplois des employeurs québécois"}],"extraFooterLinks":[{"label":"ValoPlex","href":"https://www.valoplex.com"},{"label":"Ka2","href":"https://www.ka2.bot"},{"label":"Ka4","href":"https://www.ka4.bot"},{"label":"Ka6","href":"https://www.ka6.bot"}]} (ka/ecosystem.json — contacts,
237 sites, mentions légales du groupe : footer commun + page /contact). 324 sites, mentions légales du groupe : footer commun + page /contact).
@@ -337,7 +424,24 @@ body > * { position: relative; }
337 424
338 /* Cible tactile minimale */ 425 /* Cible tactile minimale */
339 --touch: 44px; 426 --touch: 44px;
427+
428+ /* ---- Échelle z-index COMMUNE (obligatoire — aucune valeur arbitraire) ----
429+ grain de film body::before = 9999 (pointer-events:none, toujours au-dessus,
430+ purement visuel). Tout composant interactif se place sous 9999 : */
431+ --z-content: 1; /* contenu positionné ordinaire */
432+ --z-sticky: 300; /* éléments sticky de contenu (sous-nav…) */
433+ --z-header: 500; /* header du site */
434+ --z-bottombar: 600; /* tab bar / barre d'action basse */
435+ --z-dropdown: 700; /* menus déroulants (au-dessus des barres) */
436+ --z-overlay: 800; /* voile sous modale/panneau */
437+ --z-modal: 900; /* modales, panneaux de filtres, galeries */
438+ --z-toast: 950; /* notifications */
439+
440+ /* ---- Hauteur de viewport DYNAMIQUE (barre du navigateur mobile qui se
441+ replie au scroll) : toujours var(--vh100), JAMAIS 100vh en dur. ---- */
442+ --vh100: 100vh;
340 } 443 }
444+@supports (height: 100dvh) { :root { --vh100: 100dvh; } }
341 445
342 /* ---- Socle ---- */ 446 /* ---- Socle ---- */
343 * { box-sizing: border-box; } 447 * { box-sizing: border-box; }
@@ -496,6 +600,76 @@ body > * { position: relative; }
496 .only-mobile { display: initial; } .only-desktop { display: none; } 600 .only-mobile { display: initial; } .only-desktop { display: none; }
497 @media (min-width: 768px) { .only-mobile { display: none; } .only-desktop { display: initial; } } 601 @media (min-width: 768px) { .only-mobile { display: none; } .only-desktop { display: initial; } }
498 602
603+/* =============================================================================
604+ SOCLE MOBILE OBLIGATOIRE (2026-08-19) — règles communes aux 13 sites.
605+ RÈGLES pour tout composant futur :
606+ · TAP, jamais :hover, pour ouvrir un menu (le survol n'existe pas au doigt) ;
607+ le :hover ne sert qu'aux effets décoratifs, sous @media (hover: hover).
608+ · var(--vh100) (dvh) et JAMAIS 100vh pour tout élément calé sur le viewport.
609+ · position:fixed exige qu'AUCUN ancêtre n'ait transform/filter/perspective/
610+ will-change/backdrop-filter — sinon monter l'élément à la racine (body).
611+ · Barre basse fixe = .ka-bottombar + classe has-bottombar sur <body>.
612+ · Menu/panneau ouvert = .ka-scroll-lock sur <html> (scroll arrière-plan gelé).
613+ · z-index : uniquement l'échelle --z-* ci-dessus.
614+ · Zones tactiles ≥ var(--touch) (44 px), champs ≥ 16 px (zoom iOS).
615+ ========================================================================== */
616+
617+/* Anti-zoom iOS : au doigt, aucun champ sous 16 px (le focus d'un champ <16 px
618+ déclenche un zoom automatique de page sur Safari iOS). !important assumé :
619+ c'est un filet d'accessibilité, un champ plus petit casse le zoom quoi
620+ qu'il arrive — ne s'applique qu'aux écrans tactiles. */
621+@media (hover: none) and (pointer: coarse) {
622+ input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
623+ select, textarea { font-size: max(16px, 1em) !important; }
624+}
625+
626+/* Zone tactile du badge Groupe KA étendue à ≥44 px au doigt, sans changer
627+ son rendu (débord de la zone cliquable via pseudo-élément). */
628+@media (pointer: coarse) {
629+ .gk-badge { position: relative; }
630+ .gk-badge::after { content: ""; position: absolute; inset: -8px; }
631+}
632+
633+/* Hauteurs viewport dynamiques */
634+.h-viewport { height: 100vh; height: 100dvh; }
635+.min-h-viewport { min-height: 100vh; min-height: 100dvh; }
636+
637+/* Barre fixe basse fiable : safe-area iPhone incluse, immunisée contre les
638+ transforms accidentels, et compensation d'espace via body.has-bottombar. */
639+.ka-bottombar {
640+ position: fixed; left: 0; right: 0; bottom: 0;
641+ z-index: var(--z-bottombar);
642+ padding-bottom: env(safe-area-inset-bottom, 0px);
643+ background: var(--surface);
644+ border-top: 1.5px solid var(--ink);
645+ transform: none !important; filter: none !important;
646+}
647+body.has-bottombar { padding-bottom: calc(var(--bottombar-h, 64px) + env(safe-area-inset-bottom, 0px)); }
648+
649+/* Verrou de scroll d'arrière-plan (menu mobile, modale, panneau de filtres
650+ ouverts) : ajouter/retirer .ka-scroll-lock sur <html>. */
651+html.ka-scroll-lock, html.ka-scroll-lock body { overflow: hidden !important; overscroll-behavior: none; }
652+
653+/* Menu déroulant de référence : au-dessus de tout contenu (cartes Mapbox
654+ incluses), défilement interne avec élan, jamais plus haut que l'écran. */
655+.ka-menu {
656+ position: absolute; z-index: var(--z-dropdown);
657+ min-width: 180px;
658+ max-height: min(60vh, 420px); max-height: min(60dvh, 420px);
659+ overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
660+ background: var(--surface); border: 1.5px solid var(--ink);
661+ border-radius: var(--r-card); box-shadow: var(--shadow-off-mid);
662+}
663+.ka-menu a, .ka-menu button, .ka-menu label, .ka-menu [role="menuitem"], .ka-menu [role="option"] {
664+ display: flex; align-items: center; min-height: var(--touch);
665+ padding: 10px 14px; width: 100%; text-decoration: none;
666+}
667+/* Voile plein écran sous une modale / un panneau */
668+.ka-overlay {
669+ position: fixed; inset: 0; z-index: var(--z-overlay);
670+ background: rgba(20, 24, 20, 0.45);
671+}
672+
499 </style> 673 </style>
500 <style> 674 <style>
501 /* Surcharges Créa-Ka par-dessus ka/tokens.css. Convention du groupe : les 675 /* Surcharges Créa-Ka par-dessus ka/tokens.css. Convention du groupe : les
@@ -636,6 +810,11 @@ select.f.active{background-color:var(--accent-soft);color:var(--ink);border-colo
636 .filters::-webkit-scrollbar{display:none} 810 .filters::-webkit-scrollbar{display:none}
637 select.f{flex:none;font-size:16px;padding:8px 30px 8px 13px} 811 select.f{flex:none;font-size:16px;padding:8px 30px 8px 13px}
638 } 812 }
813+/* Anti-zoom iOS jusqu'au breakpoint tabbar (940 px) : champs >= 16 px
814+ (filet global : tokens.css force aussi 16 px au doigt) */
815+@media(max-width:940px){.search-input input,select.f{font-size:16px}}
816+/* Cible tactile >= 44 px du logo (112×26 rendus) : zone étendue invisible */
817+@media(pointer:coarse){.brand{position:relative}.brand::after{content:"";position:absolute;inset:-8px}}
639 818
640 /* ===== grille créateurs ===== */ 819 /* ===== grille créateurs ===== */
641 .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:18px;padding:24px 0 10px} 820 .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:18px;padding:24px 0 10px}
modified frontend/dist/ka-agent.js +25 −7
@@ -17,18 +17,19 @@
17 17
18 /* ---------- styles ---------- */ 18 /* ---------- styles ---------- */
19 var css = ` 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 border-radius:50%;border:2px solid var(--ink,#141814);background:var(--accent,#d9f26b); 21 border-radius:50%;border:2px solid var(--ink,#141814);background:var(--accent,#d9f26b);
22 color:var(--on-accent,#141814);font:700 17px/1 "Space Grotesk",system-ui,sans-serif; 22 color:var(--on-accent,#141814);font:700 17px/1 "Space Grotesk",system-ui,sans-serif;
23 cursor:pointer;box-shadow:4px 4px 0 rgba(20,24,20,.85);display:flex;align-items:center; 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 .kaa-btn:hover{transform:translate(-2px,-2px)} 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)); 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(100dvh - 110px));display:none;flex-direction:column;background:var(--paper,#f5f3ee); 27+ height:min(580px,calc(100vh - 110px));height:min(580px,calc(100dvh - 110px));display:none;flex-direction:column;background:var(--paper,#f5f3ee);
28 border:2px solid var(--ink,#141814);border-radius:14px;box-shadow:8px 8px 0 rgba(20,24,20,.85); 28 border:2px solid var(--ink,#141814);border-radius:14px;box-shadow:8px 8px 0 rgba(20,24,20,.85);
29 overflow:hidden;font-family:Inter,system-ui,sans-serif} 29 overflow:hidden;font-family:Inter,system-ui,sans-serif}
30 .kaa-panel.kaa-open{display:flex} 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 .kaa-head{display:flex;align-items:center;gap:9px;padding:11px 14px;background:var(--ink,#141814); 33 .kaa-head{display:flex;align-items:center;gap:9px;padding:11px 14px;background:var(--ink,#141814);
33 color:var(--paper,#f5f3ee);flex:none} 34 color:var(--paper,#f5f3ee);flex:none}
34 .kaa-head b{font:700 15px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.02em} 35 .kaa-head b{font:700 15px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.02em}
@@ -53,7 +54,7 @@
53 border-radius:999px;background:var(--surface-2,#faf9f5)} 54 border-radius:999px;background:var(--surface-2,#faf9f5)}
54 .kaa-in{display:flex;gap:8px;padding:11px;border-top:2px solid var(--ink,#141814);background:#fff;flex:none; 55 .kaa-in{display:flex;gap:8px;padding:11px;border-top:2px solid var(--ink,#141814);background:#fff;flex:none;
55 padding-bottom:calc(11px + env(safe-area-inset-bottom,0))} 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 border:1.5px solid var(--ink,#141814);border-radius:9px;background:var(--paper,#f5f3ee);outline:none} 58 border:1.5px solid var(--ink,#141814);border-radius:9px;background:var(--paper,#f5f3ee);outline:none}
58 .kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px; 59 .kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px;
59 background:var(--ink,#141814);color:var(--accent,#d9f26b);font:700 16px "Space Grotesk",system-ui;cursor:pointer} 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 /* ---------- ouverture / plein écran ---------- */ 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 btn.addEventListener("click", function () { 157 btn.addEventListener("click", function () {
146 panel.classList.toggle("kaa-open"); 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 panel.querySelector(".kaa-x2").addEventListener("click", function () { 165 panel.querySelector(".kaa-x2").addEventListener("click", function () {
150 panel.classList.remove("kaa-open", "kaa-full"); 166 panel.classList.remove("kaa-open", "kaa-full");
167+ syncLock();
151 }); 168 });
152 panel.querySelector(".kaa-x1").addEventListener("click", function () { 169 panel.querySelector(".kaa-x1").addEventListener("click", function () {
153 panel.classList.toggle("kaa-full"); 170 panel.classList.toggle("kaa-full");
171+ syncLock();
154 scroll(); 172 scroll();
155 }); 173 });
156 174
modified frontend/src/index.template.html +5 −0
@@ -178,6 +178,11 @@ select.f.active{background-color:var(--accent-soft);color:var(--ink);border-colo
178 .filters::-webkit-scrollbar{display:none} 178 .filters::-webkit-scrollbar{display:none}
179 select.f{flex:none;font-size:16px;padding:8px 30px 8px 13px} 179 select.f{flex:none;font-size:16px;padding:8px 30px 8px 13px}
180 } 180 }
181+/* Anti-zoom iOS jusqu'au breakpoint tabbar (940 px) : champs >= 16 px
182+ (filet global : tokens.css force aussi 16 px au doigt) */
183+@media(max-width:940px){.search-input input,select.f{font-size:16px}}
184+/* Cible tactile >= 44 px du logo (112×26 rendus) : zone étendue invisible */
185+@media(pointer:coarse){.brand{position:relative}.brand::after{content:"";position:absolute;inset:-8px}}
181 186
182 /* ===== grille créateurs ===== */ 187 /* ===== grille créateurs ===== */
183 .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:18px;padding:24px 0 10px} 188 .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:18px;padding:24px 0 10px}
modified frontend/src/ka-agent.js +25 −7
@@ -17,18 +17,19 @@
17 17
18 /* ---------- styles ---------- */ 18 /* ---------- styles ---------- */
19 var css = ` 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 border-radius:50%;border:2px solid var(--ink,#141814);background:var(--accent,#d9f26b); 21 border-radius:50%;border:2px solid var(--ink,#141814);background:var(--accent,#d9f26b);
22 color:var(--on-accent,#141814);font:700 17px/1 "Space Grotesk",system-ui,sans-serif; 22 color:var(--on-accent,#141814);font:700 17px/1 "Space Grotesk",system-ui,sans-serif;
23 cursor:pointer;box-shadow:4px 4px 0 rgba(20,24,20,.85);display:flex;align-items:center; 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 .kaa-btn:hover{transform:translate(-2px,-2px)} 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)); 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(100dvh - 110px));display:none;flex-direction:column;background:var(--paper,#f5f3ee); 27+ height:min(580px,calc(100vh - 110px));height:min(580px,calc(100dvh - 110px));display:none;flex-direction:column;background:var(--paper,#f5f3ee);
28 border:2px solid var(--ink,#141814);border-radius:14px;box-shadow:8px 8px 0 rgba(20,24,20,.85); 28 border:2px solid var(--ink,#141814);border-radius:14px;box-shadow:8px 8px 0 rgba(20,24,20,.85);
29 overflow:hidden;font-family:Inter,system-ui,sans-serif} 29 overflow:hidden;font-family:Inter,system-ui,sans-serif}
30 .kaa-panel.kaa-open{display:flex} 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 .kaa-head{display:flex;align-items:center;gap:9px;padding:11px 14px;background:var(--ink,#141814); 33 .kaa-head{display:flex;align-items:center;gap:9px;padding:11px 14px;background:var(--ink,#141814);
33 color:var(--paper,#f5f3ee);flex:none} 34 color:var(--paper,#f5f3ee);flex:none}
34 .kaa-head b{font:700 15px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.02em} 35 .kaa-head b{font:700 15px "Space Grotesk",system-ui,sans-serif;letter-spacing:-.02em}
@@ -53,7 +54,7 @@
53 border-radius:999px;background:var(--surface-2,#faf9f5)} 54 border-radius:999px;background:var(--surface-2,#faf9f5)}
54 .kaa-in{display:flex;gap:8px;padding:11px;border-top:2px solid var(--ink,#141814);background:#fff;flex:none; 55 .kaa-in{display:flex;gap:8px;padding:11px;border-top:2px solid var(--ink,#141814);background:#fff;flex:none;
55 padding-bottom:calc(11px + env(safe-area-inset-bottom,0))} 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 border:1.5px solid var(--ink,#141814);border-radius:9px;background:var(--paper,#f5f3ee);outline:none} 58 border:1.5px solid var(--ink,#141814);border-radius:9px;background:var(--paper,#f5f3ee);outline:none}
58 .kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px; 59 .kaa-in button{min-width:52px;min-height:44px;border:1.5px solid var(--ink,#141814);border-radius:9px;
59 background:var(--ink,#141814);color:var(--accent,#d9f26b);font:700 16px "Space Grotesk",system-ui;cursor:pointer} 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 /* ---------- ouverture / plein écran ---------- */ 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 btn.addEventListener("click", function () { 157 btn.addEventListener("click", function () {
146 panel.classList.toggle("kaa-open"); 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 panel.querySelector(".kaa-x2").addEventListener("click", function () { 165 panel.querySelector(".kaa-x2").addEventListener("click", function () {
150 panel.classList.remove("kaa-open", "kaa-full"); 166 panel.classList.remove("kaa-open", "kaa-full");
167+ syncLock();
151 }); 168 });
152 panel.querySelector(".kaa-x1").addEventListener("click", function () { 169 panel.querySelector(".kaa-x1").addEventListener("click", function () {
153 panel.classList.toggle("kaa-full"); 170 panel.classList.toggle("kaa-full");
171+ syncLock();
154 scroll(); 172 scroll();
155 }); 173 });
156 174
modified frontend/src/ka/tokens.css +87 −0
@@ -69,7 +69,24 @@
69 69
70 /* Cible tactile minimale */ 70 /* Cible tactile minimale */
71 --touch: 44px; 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 /* ---- Socle ---- */ 91 /* ---- Socle ---- */
75 * { box-sizing: border-box; } 92 * { box-sizing: border-box; }
@@ -227,3 +244,73 @@ body > * { position: relative; }
227 /* ---- Utilitaires responsive ---- */ 244 /* ---- Utilitaires responsive ---- */
228 .only-mobile { display: initial; } .only-desktop { display: none; } 245 .only-mobile { display: initial; } .only-desktop { display: none; }
229 @media (min-width: 768px) { .only-mobile { display: none; } .only-desktop { display: initial; } } 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+}
230 317
Menu

Theme