Barre de navigation mobile commune Groupe KA (KA Tabbar v1)
L ancienne tabbar 3 onglets bord-à-bord devient la pilule flottante du standard ka-ui/nav : Découvrir · Recherche · Villes · Favoris, accent orange, safe-area iOS. Recherche = focus du champ de l accueil (id f-q) ; le fab Filtres remonte au-dessus de la pilule. Stats/Sources restent au menu. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 changed files +345 −39
modified
frontend/src/App.tsx
+25 −15
@@ -6,7 +6,7 @@ | |||||
| 6 | // ============================================================================== | 6 | // ============================================================================== | ||
| 7 | import { useEffect, useState } from "react"; | 7 | import { useEffect, useState } from "react"; | ||
| 8 | import { createPortal } from "react-dom"; | 8 | import { createPortal } from "react-dom"; | ||
| 9 | −import { NavLink, Route, Routes, useLocation } from "react-router-dom"; | 9 | +import { NavLink, Route, Routes, useLocation, useNavigate } from "react-router-dom"; | ||
| 10 | import { AccountProvider, useAccount } from "./account"; | 10 | import { AccountProvider, useAccount } from "./account"; | ||
| 11 | import { | 11 | import { | ||
| 12 | fetchAuthConfig, fetchFacets, fetchSources, fetchStats, logout, | 12 | fetchAuthConfig, fetchFacets, fetchSources, fetchStats, logout, | ||
@@ -19,6 +19,7 @@ import { FavProvider } from "./favorites"; | |||||
| 19 | import { LogoIcon } from "./components/Logo"; | 19 | import { LogoIcon } from "./components/Logo"; | ||
| 20 | import GroupeKaBadge from "./ka/GroupeKaBadge"; | 20 | import GroupeKaBadge from "./ka/GroupeKaBadge"; | ||
| 21 | import KaFooter from "./ka/KaFooter"; | 21 | import KaFooter from "./ka/KaFooter"; | ||
| 22 | +import KaTabbar from "./ka/KaTabbar"; | ||||
| 22 | import ContactPage from "./pages/Contact"; | 23 | import ContactPage from "./pages/Contact"; | ||
| 23 | import FavorisPage from "./pages/Favoris"; | 24 | import FavorisPage from "./pages/Favoris"; | ||
| 24 | import Home from "./pages/Home"; | 25 | import Home from "./pages/Home"; | ||
@@ -242,23 +243,32 @@ function Footer() { | |||||
| 242 | ); | 243 | ); | ||
| 243 | } | 244 | } | ||
| 244 | 245 | ||||
| 245 | −/** Barre de navigation inférieure (mobile) — icône active en orange. */ | 246 | +/** Barre de navigation mobile commune Groupe KA (ka-ui/nav — KA Tabbar v1) : | ||
| 247 | + Découvrir · Recherche · Villes · Favoris, accent orange signature. */ | ||||
| 246 | function MobileTabBar() { | 248 | function MobileTabBar() { | ||
| 247 | const location = useLocation(); | 249 | const location = useLocation(); | ||
| 248 | − const tabs = [ | 250 | + const navigate = useNavigate(); | ||
| 249 | − { to: "/", label: "Rechercher", icon: <IcoSearch size={20} />, on: location.pathname === "/" }, | 251 | + const p = location.pathname; | ||
| 250 | − { to: "/stats", label: "Stats", icon: <IcoChart size={20} />, on: location.pathname.startsWith("/stats") }, | 252 | + const focusSearch = () => { | ||
| 251 | − { to: "/sources", label: "Sources", icon: <IcoFolder size={20} />, on: location.pathname.startsWith("/sources") }, | 253 | + const go = !document.getElementById("f-q"); | ||
| 252 | − ]; | 254 | + if (go) navigate("/"); | ||
| 255 | + window.setTimeout(() => { | ||||
| 256 | + const el = document.getElementById("f-q") as HTMLInputElement | null; | ||||
| 257 | + if (el) { | ||||
| 258 | + el.scrollIntoView({ block: "center", behavior: "smooth" }); | ||||
| 259 | + el.focus({ preventScroll: true }); | ||||
| 260 | + } | ||||
| 261 | + }, go ? 140 : 0); | ||||
| 262 | + }; | ||||
| 253 | return ( | 263 | return ( | ||
| 254 | − <nav className="tabbar tabbar-3" aria-label="Navigation mobile"> | 264 | + <KaTabbar | ||
| 255 | − {tabs.map((t) => ( | 265 | + tabs={[ | ||
| 256 | − <NavLink key={t.label} to={t.to} className={() => (t.on ? "active" : "")}> | 266 | + { label: "Découvrir", icon: "compass", to: "/", active: p === "/" || p.startsWith("/resto") }, | ||
| 257 | − {t.icon} | 267 | + { label: "Recherche", icon: "search", onPress: focusSearch }, | ||
| 258 | − {t.label} | 268 | + { label: "Villes", icon: "pin", to: "/villes", active: p.startsWith("/ville") }, | ||
| 259 | − </NavLink> | 269 | + { label: "Favoris", icon: "heart", to: "/favoris", active: p.startsWith("/favoris") }, | ||
| 260 | − ))} | 270 | + ]} | ||
| 261 | − </nav> | 271 | + /> | ||
| 262 | ); | 272 | ); | ||
| 263 | } | 273 | } | ||
| 264 | 274 | ||||
added
frontend/src/ka/KaTabbar.tsx
+197 −0
@@ -0,0 +1,197 @@ | |||||
| 1 | +// KA Tabbar v1 — canonique ka-ui.git/nav/KaTabbar.tsx | ||||
| 2 | +// Barre de navigation mobile commune Groupe KA (voir TABBAR-STANDARD.md). | ||||
| 3 | +// Copier dans src/ka/ avec ka-tabbar.css. Apps react-router uniquement ; | ||||
| 4 | +// pour Next.js (vrai-prix), adapter Link/usePathname — voir le standard. | ||||
| 5 | +import type { CSSProperties, ReactElement } from "react"; | ||||
| 6 | +import { Link } from "react-router-dom"; | ||||
| 7 | +import "./ka-tabbar.css"; | ||||
| 8 | + | ||||
| 9 | +export type KaTabIcon = | ||||
| 10 | + | "compass" | "search" | "heart" | "user" | "map" | "pin" | "tag" | ||||
| 11 | + | "home" | "grid" | "store" | "basket" | "target" | "trendup" | ||||
| 12 | + | "receipt" | "book" | "chart" | "calendar"; | ||||
| 13 | + | ||||
| 14 | +const ICONS: Record<KaTabIcon, ReactElement> = { | ||||
| 15 | + compass: ( | ||||
| 16 | + <> | ||||
| 17 | + <circle cx="12" cy="12" r="9" /> | ||||
| 18 | + <path d="m15.5 8.5-2.2 5-4.8 2 2.2-5 4.8-2z" /> | ||||
| 19 | + </> | ||||
| 20 | + ), | ||||
| 21 | + search: ( | ||||
| 22 | + <> | ||||
| 23 | + <circle cx="11" cy="11" r="7" /> | ||||
| 24 | + <path d="m20.5 20.5-4.2-4.2" /> | ||||
| 25 | + </> | ||||
| 26 | + ), | ||||
| 27 | + heart: ( | ||||
| 28 | + <path d="M12 20.5S4.5 15.8 2.6 11.4A5.3 5.3 0 0 1 12 6.6a5.3 5.3 0 0 1 9.4 4.8C19.5 15.8 12 20.5 12 20.5z" /> | ||||
| 29 | + ), | ||||
| 30 | + user: ( | ||||
| 31 | + <> | ||||
| 32 | + <circle cx="12" cy="8.2" r="3.7" /> | ||||
| 33 | + <path d="M4.5 20.2a7.5 7.5 0 0 1 15 0" /> | ||||
| 34 | + </> | ||||
| 35 | + ), | ||||
| 36 | + map: ( | ||||
| 37 | + <> | ||||
| 38 | + <path d="M9 4 3.5 6v14L9 18l6 2 5.5-2V4L15 6 9 4z" /> | ||||
| 39 | + <path d="M9 4v14M15 6v14" /> | ||||
| 40 | + </> | ||||
| 41 | + ), | ||||
| 42 | + pin: ( | ||||
| 43 | + <> | ||||
| 44 | + <path d="M12 21s-7-5.6-7-11a7 7 0 0 1 14 0c0 5.4-7 11-7 11z" /> | ||||
| 45 | + <circle cx="12" cy="10" r="2.6" /> | ||||
| 46 | + </> | ||||
| 47 | + ), | ||||
| 48 | + tag: ( | ||||
| 49 | + <> | ||||
| 50 | + <path d="M20.6 13.4 12 22 2 12V2h10l8.6 8.6a2 2 0 0 1 0 2.8z" /> | ||||
| 51 | + <circle cx="7" cy="7" r="1.6" /> | ||||
| 52 | + </> | ||||
| 53 | + ), | ||||
| 54 | + home: ( | ||||
| 55 | + <> | ||||
| 56 | + <path d="m3.5 10.5 8.5-7 8.5 7" /> | ||||
| 57 | + <path d="M5.5 9v11h13V9" /> | ||||
| 58 | + </> | ||||
| 59 | + ), | ||||
| 60 | + grid: ( | ||||
| 61 | + <> | ||||
| 62 | + <rect x="4" y="4" width="7" height="7" rx="1.5" /> | ||||
| 63 | + <rect x="13" y="4" width="7" height="7" rx="1.5" /> | ||||
| 64 | + <rect x="4" y="13" width="7" height="7" rx="1.5" /> | ||||
| 65 | + <rect x="13" y="13" width="7" height="7" rx="1.5" /> | ||||
| 66 | + </> | ||||
| 67 | + ), | ||||
| 68 | + store: ( | ||||
| 69 | + <> | ||||
| 70 | + <path d="M4.5 9.5 6 4.5h12l1.5 5" /> | ||||
| 71 | + <path d="M5 9.5h14V20H5V9.5z" /> | ||||
| 72 | + <path d="M10 20v-5h4v5" /> | ||||
| 73 | + </> | ||||
| 74 | + ), | ||||
| 75 | + basket: ( | ||||
| 76 | + <> | ||||
| 77 | + <path d="M4 9h16l-1.6 10a2 2 0 0 1-2 1.7H7.6a2 2 0 0 1-2-1.7L4 9z" /> | ||||
| 78 | + <path d="M8.5 9 12 3l3.5 6" /> | ||||
| 79 | + </> | ||||
| 80 | + ), | ||||
| 81 | + target: ( | ||||
| 82 | + <> | ||||
| 83 | + <circle cx="12" cy="12" r="8.5" /> | ||||
| 84 | + <circle cx="12" cy="12" r="4.5" /> | ||||
| 85 | + <circle cx="12" cy="12" r="0.8" /> | ||||
| 86 | + </> | ||||
| 87 | + ), | ||||
| 88 | + trendup: ( | ||||
| 89 | + <> | ||||
| 90 | + <path d="m3.5 16.5 5.5-5.5 3.5 3.5 7.5-7.5" /> | ||||
| 91 | + <path d="M14.5 7H20v5.5" /> | ||||
| 92 | + </> | ||||
| 93 | + ), | ||||
| 94 | + receipt: ( | ||||
| 95 | + <> | ||||
| 96 | + <path d="M6 3.5h12V21l-2-1.4-2 1.4-2-1.4-2 1.4-2-1.4L6 21V3.5z" /> | ||||
| 97 | + <path d="M9 8h6M9 11.5h6M9 15h4" /> | ||||
| 98 | + </> | ||||
| 99 | + ), | ||||
| 100 | + book: ( | ||||
| 101 | + <> | ||||
| 102 | + <path d="M12 6.5C10.5 5 8.5 4.5 5.5 4.5H3.5V19h2c3 0 5 .5 6.5 2 1.5-1.5 3.5-2 6.5-2h2V4.5h-2c-3 0-5 .5-6.5 2z" /> | ||||
| 103 | + <path d="M12 6.5V21" /> | ||||
| 104 | + </> | ||||
| 105 | + ), | ||||
| 106 | + chart: ( | ||||
| 107 | + <> | ||||
| 108 | + <path d="M4 20h16" /> | ||||
| 109 | + <path d="M7.5 20v-6M12 20V9M16.5 20v-4" /> | ||||
| 110 | + </> | ||||
| 111 | + ), | ||||
| 112 | + calendar: ( | ||||
| 113 | + <> | ||||
| 114 | + <rect x="3.5" y="5" width="17" height="15.5" rx="2" /> | ||||
| 115 | + <path d="M8 3v4M16 3v4M3.5 10h17" /> | ||||
| 116 | + </> | ||||
| 117 | + ), | ||||
| 118 | +}; | ||||
| 119 | + | ||||
| 120 | +export function KaTabIco({ name, size = 21 }: { name: KaTabIcon; size?: number }) { | ||||
| 121 | + return ( | ||||
| 122 | + <svg | ||||
| 123 | + width={size} | ||||
| 124 | + height={size} | ||||
| 125 | + viewBox="0 0 24 24" | ||||
| 126 | + fill="none" | ||||
| 127 | + stroke="currentColor" | ||||
| 128 | + strokeWidth={1.7} | ||||
| 129 | + strokeLinecap="round" | ||||
| 130 | + strokeLinejoin="round" | ||||
| 131 | + aria-hidden="true" | ||||
| 132 | + > | ||||
| 133 | + {ICONS[name]} | ||||
| 134 | + </svg> | ||||
| 135 | + ); | ||||
| 136 | +} | ||||
| 137 | + | ||||
| 138 | +export type KaTab = { | ||||
| 139 | + label: string; | ||||
| 140 | + icon: KaTabIcon; | ||||
| 141 | + /** route interne (react-router Link) */ | ||||
| 142 | + to?: string; | ||||
| 143 | + /** lien externe (ex. hub univers KA) */ | ||||
| 144 | + href?: string; | ||||
| 145 | + /** action locale (ex. ouvrir la recherche) — rend un <button> */ | ||||
| 146 | + onPress?: () => void; | ||||
| 147 | + active?: boolean; | ||||
| 148 | +}; | ||||
| 149 | + | ||||
| 150 | +export default function KaTabbar({ | ||||
| 151 | + tabs, | ||||
| 152 | + theme = "light", | ||||
| 153 | + accent, | ||||
| 154 | + className, | ||||
| 155 | +}: { | ||||
| 156 | + tabs: KaTab[]; | ||||
| 157 | + theme?: "light" | "dark"; | ||||
| 158 | + /** couleur signature de la marque (défaut : var(--accent) du site) */ | ||||
| 159 | + accent?: string; | ||||
| 160 | + className?: string; | ||||
| 161 | +}) { | ||||
| 162 | + const cls = | ||||
| 163 | + "ka-tabbar" + | ||||
| 164 | + (theme === "dark" ? " ka-tabbar--dark" : "") + | ||||
| 165 | + (className ? " " + className : ""); | ||||
| 166 | + const style = accent ? ({ "--ktb-accent": accent } as CSSProperties) : undefined; | ||||
| 167 | + return ( | ||||
| 168 | + <nav className={cls} style={style} aria-label="Navigation mobile"> | ||||
| 169 | + {tabs.map((t) => { | ||||
| 170 | + const inner = ( | ||||
| 171 | + <> | ||||
| 172 | + <KaTabIco name={t.icon} /> | ||||
| 173 | + {t.label} | ||||
| 174 | + </> | ||||
| 175 | + ); | ||||
| 176 | + const c = t.active ? "active" : ""; | ||||
| 177 | + if (t.to) | ||||
| 178 | + return ( | ||||
| 179 | + <Link key={t.label} to={t.to} className={c} aria-current={t.active ? "page" : undefined}> | ||||
| 180 | + {inner} | ||||
| 181 | + </Link> | ||||
| 182 | + ); | ||||
| 183 | + if (t.href) | ||||
| 184 | + return ( | ||||
| 185 | + <a key={t.label} href={t.href} className={c} target="_blank" rel="noreferrer"> | ||||
| 186 | + {inner} | ||||
| 187 | + </a> | ||||
| 188 | + ); | ||||
| 189 | + return ( | ||||
| 190 | + <button key={t.label} type="button" className={c} onClick={t.onPress}> | ||||
| 191 | + {inner} | ||||
| 192 | + </button> | ||||
| 193 | + ); | ||||
| 194 | + })} | ||||
| 195 | + </nav> | ||||
| 196 | + ); | ||||
| 197 | +} | ||||
added
frontend/src/ka/ka-tabbar.css
+118 −0
@@ -0,0 +1,118 @@ | |||||
| 1 | +/* ============================================================ | ||||
| 2 | + KA Tabbar v1 — barre de navigation mobile commune Groupe KA | ||||
| 3 | + Canonique : ka-ui.git/nav/ka-tabbar.css — copiée telle quelle | ||||
| 4 | + dans chaque app (src/ka/ka-tabbar.css) et importée par le | ||||
| 5 | + composant KaTabbar. Voir TABBAR-STANDARD.md. | ||||
| 6 | + | ||||
| 7 | + Pilule flottante détachée du bas d'écran (langage Immo-Ka), | ||||
| 8 | + 4-5 onglets, safe-area iOS, thème clair (défaut) ou sombre. | ||||
| 9 | + Config par app : | ||||
| 10 | + --ktb-accent couleur signature (trait actif + icône) | ||||
| 11 | + --ktb-active-label couleur du label actif (défaut : encre) | ||||
| 12 | + .ka-tabbar--dark variante sombre (fond encre/brand) | ||||
| 13 | + --ktb-bg fond (surcharge libre, ex. vert profond) | ||||
| 14 | + ============================================================ */ | ||||
| 15 | + | ||||
| 16 | +.ka-tabbar { display: none; } | ||||
| 17 | + | ||||
| 18 | +@media (max-width: 760px) { | ||||
| 19 | + .ka-tabbar { | ||||
| 20 | + /* thème clair (défaut) — papier off-white légèrement translucide */ | ||||
| 21 | + --ktb-bg: rgba(250, 249, 245, 0.94); | ||||
| 22 | + --ktb-ink: var(--ink, #16191a); | ||||
| 23 | + --ktb-dim: var(--ink-3, #70756d); | ||||
| 24 | + --ktb-accent: var(--accent, #d6336c); | ||||
| 25 | + --ktb-line: var(--hairline-strong, rgba(20, 24, 20, 0.16)); | ||||
| 26 | + --ktb-shadow: 0 10px 30px rgba(20, 24, 20, 0.16), 0 2px 6px rgba(20, 24, 20, 0.07); | ||||
| 27 | + | ||||
| 28 | + position: fixed; | ||||
| 29 | + left: max(12px, env(safe-area-inset-left)); | ||||
| 30 | + right: max(12px, env(safe-area-inset-right)); | ||||
| 31 | + bottom: calc(10px + env(safe-area-inset-bottom)); | ||||
| 32 | + z-index: var(--z-bottombar, 600); | ||||
| 33 | + display: grid; | ||||
| 34 | + grid-auto-flow: column; | ||||
| 35 | + grid-auto-columns: 1fr; | ||||
| 36 | + max-width: 440px; | ||||
| 37 | + margin-inline: auto; | ||||
| 38 | + padding: 5px; | ||||
| 39 | + background: var(--ktb-bg); | ||||
| 40 | + -webkit-backdrop-filter: blur(14px) saturate(1.1); | ||||
| 41 | + backdrop-filter: blur(14px) saturate(1.1); | ||||
| 42 | + border: 1px solid var(--ktb-line); | ||||
| 43 | + border-radius: 18px; | ||||
| 44 | + box-shadow: var(--ktb-shadow); | ||||
| 45 | + } | ||||
| 46 | + | ||||
| 47 | + .ka-tabbar--dark { | ||||
| 48 | + --ktb-bg: rgba(18, 22, 19, 0.93); | ||||
| 49 | + --ktb-ink: #f5f3ee; | ||||
| 50 | + --ktb-dim: rgba(245, 243, 238, 0.62); | ||||
| 51 | + --ktb-line: rgba(245, 243, 238, 0.16); | ||||
| 52 | + --ktb-shadow: 0 10px 30px rgba(0, 0, 0, 0.35); | ||||
| 53 | + } | ||||
| 54 | + | ||||
| 55 | + .ka-tabbar a, | ||||
| 56 | + .ka-tabbar button { | ||||
| 57 | + appearance: none; | ||||
| 58 | + background: none; | ||||
| 59 | + border: 0; | ||||
| 60 | + cursor: pointer; | ||||
| 61 | + display: flex; | ||||
| 62 | + flex-direction: column; | ||||
| 63 | + align-items: center; | ||||
| 64 | + justify-content: center; | ||||
| 65 | + gap: 3px; | ||||
| 66 | + min-height: 50px; | ||||
| 67 | + padding: 6px 2px 5px; | ||||
| 68 | + border-radius: 13px; | ||||
| 69 | + font-family: var(--font-display, inherit); | ||||
| 70 | + font-size: 10px; | ||||
| 71 | + font-weight: 650; | ||||
| 72 | + letter-spacing: 0.01em; | ||||
| 73 | + line-height: 1; | ||||
| 74 | + color: var(--ktb-dim); | ||||
| 75 | + text-decoration: none; | ||||
| 76 | + -webkit-tap-highlight-color: transparent; | ||||
| 77 | + transition: color 0.15s ease; | ||||
| 78 | + } | ||||
| 79 | + .ka-tabbar svg { display: block; transition: transform 0.15s ease; } | ||||
| 80 | + .ka-tabbar a:active svg, | ||||
| 81 | + .ka-tabbar button:active svg { transform: translateY(1px) scale(0.9); } | ||||
| 82 | + | ||||
| 83 | + /* état actif : icône couleur signature, label encre, trait accent */ | ||||
| 84 | + .ka-tabbar .active { color: var(--ktb-active-label, var(--ktb-ink)); } | ||||
| 85 | + .ka-tabbar .active svg { color: var(--ktb-accent); animation: ktb-pop 0.25s ease; } | ||||
| 86 | + .ka-tabbar .active::after { | ||||
| 87 | + content: ""; | ||||
| 88 | + width: 16px; | ||||
| 89 | + height: 2.5px; | ||||
| 90 | + border-radius: 2px; | ||||
| 91 | + background: var(--ktb-accent); | ||||
| 92 | + animation: ktb-under 0.22s ease; | ||||
| 93 | + } | ||||
| 94 | + | ||||
| 95 | + /* le contenu respire sous la pilule (footer compris) */ | ||||
| 96 | + body:has(.ka-tabbar) { padding-bottom: calc(84px + env(safe-area-inset-bottom)); } | ||||
| 97 | + | ||||
| 98 | + /* un CTA sticky de fiche prend le bas d'écran : la pilule s'efface */ | ||||
| 99 | + body:has(.cta-sticky) .ka-tabbar { display: none; } | ||||
| 100 | + body:has(.cta-sticky) { padding-bottom: 0; } | ||||
| 101 | + | ||||
| 102 | + /* widget KA Agent : dégagé de la pilule (style injecté après le bundle | ||||
| 103 | + → spécificité doublée pour gagner) */ | ||||
| 104 | + body:has(.ka-tabbar) .kaa-btn.kaa-btn { bottom: calc(88px + env(safe-area-inset-bottom, 0px)); right: 14px; } | ||||
| 105 | + body:has(.ka-tabbar) .kaa-panel.kaa-panel:not(.kaa-full) { bottom: calc(154px + env(safe-area-inset-bottom, 0px)); } | ||||
| 106 | +} | ||||
| 107 | + | ||||
| 108 | +@keyframes ktb-under { | ||||
| 109 | + from { transform: scaleX(0); opacity: 0; } | ||||
| 110 | +} | ||||
| 111 | +@keyframes ktb-pop { | ||||
| 112 | + 40% { transform: translateY(-2px); } | ||||
| 113 | +} | ||||
| 114 | + | ||||
| 115 | +@media (prefers-reduced-motion: reduce) { | ||||
| 116 | + .ka-tabbar svg { transition: none; } | ||||
| 117 | + .ka-tabbar .active svg, .ka-tabbar .active::after { animation: none; } | ||||
| 118 | +} | ||||
modified
frontend/src/pages/Home.tsx
+1 −0
@@ -180,6 +180,7 @@ export default function Home() { | |||||
| 180 | <label className="f-search"> | 180 | <label className="f-search"> | ||
| 181 | <IcoSearch size={16} /> | 181 | <IcoSearch size={16} /> | ||
| 182 | <input | 182 | <input | ||
| 183 | + id="f-q" | ||||
| 183 | type="search" | 184 | type="search" | ||
| 184 | placeholder={mode === "plats" | 185 | placeholder={mode === "plats" | ||
| 185 | ? "Poutine, tartare, pad thaï, sushi…" | 186 | ? "Poutine, tartare, pad thaï, sushi…" | ||
modified
frontend/src/styles.css
+4 −24
@@ -718,7 +718,7 @@ img { display: block; } | |||||
| 718 | .fab { | 718 | .fab { | ||
| 719 | display: flex; align-items: center; gap: 6px; | 719 | display: flex; align-items: center; gap: 6px; | ||
| 720 | position: fixed; left: 50%; transform: translateX(-50%); | 720 | position: fixed; left: 50%; transform: translateX(-50%); | ||
| 721 | − bottom: calc(74px + env(safe-area-inset-bottom)); z-index: 80; | 721 | + bottom: calc(88px + env(safe-area-inset-bottom)); z-index: 80; | ||
| 722 | background: var(--ink); color: var(--accent); border: 1.5px solid var(--ink); | 722 | background: var(--ink); color: var(--accent); border: 1.5px solid var(--ink); | ||
| 723 | border-radius: var(--r-ctl); padding: 13px 24px; font-family: var(--font-display); | 723 | border-radius: var(--r-ctl); padding: 13px 24px; font-family: var(--font-display); | ||
| 724 | font-weight: 700; font-size: 15px; cursor: pointer; | 724 | font-weight: 700; font-size: 15px; cursor: pointer; | ||
@@ -737,29 +737,9 @@ img { display: block; } | |||||
| 737 | .mi-thumb { width: 56px; height: 56px; } | 737 | .mi-thumb { width: 56px; height: 56px; } | ||
| 738 | } | 738 | } | ||
| 739 | 739 | ||||
| 740 | −/* ================= Barre de navigation inférieure (mobile) ================= */ | 740 | +/* ============ Barre de navigation inférieure (mobile) ============ | ||
| 741 | −.tabbar { display: none; } | 741 | + KA Tabbar commune (src/ka/ka-tabbar.css) — pilule flottante, | ||
| 742 | −@media (max-width: 760px) { | 742 | + accent orange du site via var(--accent). ============ */ | ||
| 743 | − .tabbar { | ||||
| 744 | − display: grid; | ||||
| 745 | − position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-bottombar, 600); | ||||
| 746 | − background: rgba(245, 243, 238, 0.94); | ||||
| 747 | − backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); | ||||
| 748 | − border-top: 1.5px solid var(--ink); | ||||
| 749 | − padding: 6px 4px calc(6px + env(safe-area-inset-bottom)); | ||||
| 750 | − } | ||||
| 751 | − .tabbar-3 { grid-template-columns: repeat(3, 1fr); } | ||||
| 752 | − .tabbar a { | ||||
| 753 | − display: flex; flex-direction: column; align-items: center; gap: 3px; | ||||
| 754 | − padding: 5px 2px; min-height: 48px; justify-content: center; | ||||
| 755 | − font-family: var(--font-mono); font-size: 10px; font-weight: 600; color: var(--ink-3); | ||||
| 756 | − border-radius: var(--r-ctl); transition: color 0.12s ease; | ||||
| 757 | − } | ||||
| 758 | − .tabbar a.active { color: var(--accent-deep); } | ||||
| 759 | − .tabbar a svg { display: block; } | ||||
| 760 | − body { padding-bottom: calc(64px + env(safe-area-inset-bottom)); } | ||||
| 761 | − .ka-footer { padding-bottom: calc(72px + env(safe-area-inset-bottom)); } | ||||
| 762 | −} | ||||
| 763 | 743 | ||||
| 764 | /* Anti-zoom iOS : les champs doivent faire >= 16px | 744 | /* Anti-zoom iOS : les champs doivent faire >= 16px | ||
| 765 | (filet global : tokens.css force aussi 16 px au doigt) */ | 745 | (filet global : tokens.css force aussi 16 px au doigt) */ | ||
| 766 | 746 | ||||