Harmonisation Groupe KA : design system ka-ui, badge + footer communs, page /contact, SSO KA ID au header
- ka-ui vendoré dans frontend/src/ka/ (tokens.css, ecosystem.json, KaFooter, GroupeKaBadge) ; tokens.css importé AVANT styles.css dans main.tsx - styles.css : socle dédupliqué (palette papier/encre, grain, selection, focus viennent de ka/tokens.css) ; seule surcharge locale = accent CERISE Immo-Ka (--accent #e23744 + alias --lime/--lime-soft) ; classes métier conservées ; styles Profil (carte de membre KA-ID), Contact et pages légales ajoutés - Header : badge « Un service Groupe KA » (desktop + menu mobile), bouton « Se connecter avec KA » + lien « Créer un compte » -> groupe-ka.com/connexion ; zone compte -> /profil quand connecté - Footer local remplacé par KaFooter (siteId immo-ka, pages légales locales) - Nouvelles routes SPA : /contact (ecosystem.json : 3 courriels + rôles, avertissement agrégateur, hub, écosystème), /profil, /conditions, /confidentialite - api.ts : fetchMe/logout/User/Socials + favoris hub (fetchFavorites, toggleFavorite, favItemFromListing) — account.tsx et Profil.tsx enfin branchés - seo.py : /contact ajouté aux pages statiques SSR - index.html : title « Immo-Ka — Un service Groupe KA », theme-color encre KA - tsconfig : exclusion du code mort non référencé (src/kamaps, pages/Category.tsx — dépendait de @groupe-ka/ka-maps absent de node_modules) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
12 changed files +810 −86
modified
frontend/index.html
+3 −3
@@ -7,9 +7,9 @@ | ||
| 7 | 7 | <head> |
| 8 | 8 | <meta charset="UTF-8" /> |
| 9 | 9 | <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> |
| 10 | − <title>Immo-Ka — Propriétés à vendre au Québec</title> | |
| 11 | − <meta name="description" content="Immo-Ka agrège les propriétés à vendre affichées par les agences de courtage immobilier du Québec (RE/MAX, Sutton, Via Capitale, Proprio Direct…), toujours à jour." /> | |
| 12 | − <meta name="theme-color" content="#1a1214" /> | |
| 10 | + <title>Immo-Ka — Un service Groupe KA</title> | |
| 11 | + <meta name="description" content="Immo-Ka, un service Groupe KA, agrège les propriétés à vendre affichées par les agences de courtage immobilier du Québec (RE/MAX, Sutton, Via Capitale, Proprio Direct…), toujours à jour." /> | |
| 12 | + <meta name="theme-color" content="#141814" /> | |
| 13 | 13 | <meta name="mobile-web-app-capable" content="yes" /> |
| 14 | 14 | <meta name="apple-mobile-web-app-capable" content="yes" /> |
| 15 | 15 | <meta name="apple-mobile-web-app-title" content="Immo-Ka" /> |
modified
frontend/src/App.tsx
+98 −27
@@ -1,14 +1,21 @@ | ||
| 1 | 1 | // ----------------------------------------------------------------------------- |
| 2 | 2 | // Immo-Ka — Agrégateur de propriétés à vendre (province de Québec) |
| 3 | 3 | // Auteur : Simon-Pierre Boucher — contact@spboucher.ai |
| 4 | −// App.tsx : layout global (header + ticker en direct + footer encre) et routage | |
| 4 | +// App.tsx : layout global (header + ticker en direct + footer Groupe KA) et | |
| 5 | +// routage. Connexion via KA ID (hub groupe-ka.com) — voir account.tsx. | |
| 5 | 6 | // ----------------------------------------------------------------------------- |
| 6 | 7 | import { Ico } from "./components/Icons"; |
| 7 | 8 | import { useEffect, useState } from "react"; |
| 8 | 9 | import { NavLink, Route, Routes, useLocation } from "react-router-dom"; |
| 9 | 10 | import { fetchFacets, fetchSources, fetchStats, registerSourceNames, sourceName } from "./api"; |
| 11 | +import { kaLogin, useAccount } from "./account"; | |
| 12 | +import GroupeKaBadge from "./ka/GroupeKaBadge"; | |
| 13 | +import KaFooter from "./ka/KaFooter"; | |
| 14 | +import ContactPage from "./pages/Contact"; | |
| 10 | 15 | import Home from "./pages/Home"; |
| 16 | +import { PrivacyPage, TermsPage } from "./pages/Legal"; | |
| 11 | 17 | import ListingPage from "./pages/Listing"; |
| 18 | +import ProfilPage from "./pages/Profil"; | |
| 12 | 19 | import SourcesPage from "./pages/Sources"; |
| 13 | 20 | import StatsPage from "./pages/Stats"; |
| 14 | 21 | |
@@ -48,8 +55,75 @@ const NAV_LINKS = [ | ||
| 48 | 55 | { to: "/?view=carte", label: "Carte", icon: "map", end: false, force: true }, |
| 49 | 56 | { to: "/stats", label: "Stats", icon: "chart", end: false }, |
| 50 | 57 | { to: "/agences", label: "Agences", icon: "building", end: false }, |
| 58 | + { to: "/contact", label: "Contact", icon: "arrow", end: false }, | |
| 51 | 59 | ]; |
| 52 | 60 | |
| 61 | +/** Zone compte du header : « Se connecter avec KA » ou lien vers le profil. */ | |
| 62 | +function AccountNav() { | |
| 63 | + const { user, enabled, loaded } = useAccount(); | |
| 64 | + if (!loaded || !enabled) return null; | |
| 65 | + if (user) { | |
| 66 | + return ( | |
| 67 | + <NavLink | |
| 68 | + to="/profil" | |
| 69 | + className={({ isActive }) => `ka-acct ${isActive ? "active" : ""}`} | |
| 70 | + > | |
| 71 | + {user.picture ? ( | |
| 72 | + <img className="ka-acct-pic" src={user.picture} alt="" referrerPolicy="no-referrer" /> | |
| 73 | + ) : ( | |
| 74 | + <Ico name="people" size={16} /> | |
| 75 | + )} | |
| 76 | + {user.name ? user.name.split(" ")[0] : "Profil"} | |
| 77 | + </NavLink> | |
| 78 | + ); | |
| 79 | + } | |
| 80 | + return ( | |
| 81 | + <div className="ka-auth"> | |
| 82 | + <button className="ka-login" onClick={() => kaLogin()}> | |
| 83 | + Se connecter avec <b>KA</b> | |
| 84 | + </button> | |
| 85 | + <a | |
| 86 | + className="ka-signup" | |
| 87 | + href="https://www.groupe-ka.com/connexion" | |
| 88 | + target="_blank" | |
| 89 | + rel="noopener noreferrer" | |
| 90 | + > | |
| 91 | + Créer un compte | |
| 92 | + </a> | |
| 93 | + </div> | |
| 94 | + ); | |
| 95 | +} | |
| 96 | + | |
| 97 | +/** Même zone compte, version menu mobile. */ | |
| 98 | +function AccountMobile({ close }: { close: () => void }) { | |
| 99 | + const { user, enabled, loaded } = useAccount(); | |
| 100 | + if (!loaded || !enabled) return null; | |
| 101 | + if (user) { | |
| 102 | + return ( | |
| 103 | + <NavLink to="/profil" className="mm-link" onClick={close}> | |
| 104 | + <span className="mm-ico" aria-hidden="true"><Ico name="people" size={19} /></span> | |
| 105 | + Mon profil {user.name ? `— ${user.name.split(" ")[0]}` : ""} | |
| 106 | + <span className="mm-arrow" aria-hidden="true"><Ico name="arrow" size={15} /></span> | |
| 107 | + </NavLink> | |
| 108 | + ); | |
| 109 | + } | |
| 110 | + return ( | |
| 111 | + <div className="mm-auth"> | |
| 112 | + <button className="ka-login" onClick={() => kaLogin()}> | |
| 113 | + Se connecter avec <b>KA</b> | |
| 114 | + </button> | |
| 115 | + <a | |
| 116 | + className="ka-signup" | |
| 117 | + href="https://www.groupe-ka.com/connexion" | |
| 118 | + target="_blank" | |
| 119 | + rel="noopener noreferrer" | |
| 120 | + > | |
| 121 | + Créer un compte | |
| 122 | + </a> | |
| 123 | + </div> | |
| 124 | + ); | |
| 125 | +} | |
| 126 | + | |
| 53 | 127 | function Header() { |
| 54 | 128 | const [open, setOpen] = useState(false); |
| 55 | 129 | const location = useLocation(); |
@@ -66,8 +140,8 @@ function Header() { | ||
| 66 | 140 | <div className="container header-inner"> |
| 67 | 141 | <NavLink to="/" className="brand" aria-label="Immo-Ka — accueil"> |
| 68 | 142 | Immo<span className="ka">Ka</span> |
| 69 | − <span className="brand-tag">Toutes les agences du Québec — toujours à jour</span> | |
| 70 | 143 | </NavLink> |
| 144 | + <GroupeKaBadge /> | |
| 71 | 145 | <nav className="nav" aria-label="Navigation principale"> |
| 72 | 146 | <NavLink to="/" end className={({ isActive }) => (isActive ? "active" : "")}> |
| 73 | 147 | Propriétés |
@@ -78,7 +152,13 @@ function Header() { | ||
| 78 | 152 | <NavLink to="/agences" className={({ isActive }) => (isActive ? "active" : "")}> |
| 79 | 153 | Agences |
| 80 | 154 | </NavLink> |
| 155 | + <NavLink to="/contact" className={({ isActive }) => (isActive ? "active" : "")}> | |
| 156 | + Contact | |
| 157 | + </NavLink> | |
| 81 | 158 | </nav> |
| 159 | + <div className="header-acct"> | |
| 160 | + <AccountNav /> | |
| 161 | + </div> | |
| 82 | 162 | <button |
| 83 | 163 | className={`menu-btn ${open ? "open" : ""}`} |
| 84 | 164 | aria-expanded={open} |
@@ -104,9 +184,13 @@ function Header() { | ||
| 104 | 184 | <span className="mm-arrow" aria-hidden="true"><Ico name="arrow" size={15} /></span> |
| 105 | 185 | </NavLink> |
| 106 | 186 | ))} |
| 187 | + <AccountMobile close={() => setOpen(false)} /> | |
| 107 | 188 | <div className="mm-foot"> |
| 108 | − Agrégateur indépendant — mis à jour automatiquement, chaque fiche | |
| 109 | − renvoie à l'annonce originale de l'agence. | |
| 189 | + <GroupeKaBadge /> | |
| 190 | + <p> | |
| 191 | + Agrégateur indépendant — mis à jour automatiquement, chaque fiche | |
| 192 | + renvoie à l'annonce originale de l'agence. | |
| 193 | + </p> | |
| 110 | 194 | </div> |
| 111 | 195 | </div> |
| 112 | 196 | </header> |
@@ -116,28 +200,11 @@ function Header() { | ||
| 116 | 200 | ); |
| 117 | 201 | } |
| 118 | 202 | |
| 119 | −function Footer() { | |
| 120 | − return ( | |
| 121 | − <footer className="footer"> | |
| 122 | − <div className="container"> | |
| 123 | − <div className="fbrand"> | |
| 124 | − Immo<span className="ka">Ka</span> | |
| 125 | − </div> | |
| 126 | − <div className="frow"> | |
| 127 | − <div> | |
| 128 | − <b>Agrégateur indépendant</b> des propriétés à vendre dans la province de Québec. | |
| 129 | − Les annonces proviennent des sites publics des agences de courtage (RE/MAX, Sutton, | |
| 130 | − Via Capitale, Proprio Direct, Barnes, M Immobilier…) et sont rafraîchies | |
| 131 | − automatiquement — chaque fiche renvoie vers l'annonce originale. | |
| 132 | − </div> | |
| 133 | − </div> | |
| 134 | − <div className="fmono"> | |
| 135 | − © {new Date().getFullYear()} Simon-Pierre Boucher — contact@spboucher.ai | |
| 136 | − </div> | |
| 137 | − </div> | |
| 138 | − </footer> | |
| 139 | − ); | |
| 140 | −} | |
| 203 | +const LOCAL_LEGAL = [ | |
| 204 | + { label: "Conditions (Immo-Ka)", href: "/conditions" }, | |
| 205 | + { label: "Confidentialité (Immo-Ka)", href: "/confidentialite" }, | |
| 206 | + { label: "Contact", href: "/contact" }, | |
| 207 | +]; | |
| 141 | 208 | |
| 142 | 209 | export default function App() { |
| 143 | 210 | return ( |
@@ -149,6 +216,10 @@ export default function App() { | ||
| 149 | 216 | <Route path="/propriete/:uid" element={<ListingPage />} /> |
| 150 | 217 | <Route path="/stats" element={<StatsPage />} /> |
| 151 | 218 | <Route path="/agences" element={<SourcesPage />} /> |
| 219 | + <Route path="/contact" element={<ContactPage />} /> | |
| 220 | + <Route path="/profil" element={<ProfilPage />} /> | |
| 221 | + <Route path="/conditions" element={<TermsPage />} /> | |
| 222 | + <Route path="/confidentialite" element={<PrivacyPage />} /> | |
| 152 | 223 | <Route |
| 153 | 224 | path="*" |
| 154 | 225 | element={ |
@@ -161,7 +232,7 @@ export default function App() { | ||
| 161 | 232 | /> |
| 162 | 233 | </Routes> |
| 163 | 234 | </main> |
| 164 | − <Footer /> | |
| 235 | + <KaFooter siteId="immo-ka" localLegal={LOCAL_LEGAL} /> | |
| 165 | 236 | </> |
| 166 | 237 | ); |
| 167 | 238 | } |
modified
frontend/src/api.ts
+70 −0
@@ -219,3 +219,73 @@ export interface Quartier { | ||
| 219 | 219 | | { type: "points"; rayon_m: number; douze_mois: number; douze_mois_precedents: number } |
| 220 | 220 | | { type: "igc"; ville: string; annee: number; indice: number; indice_canada: number | null }; |
| 221 | 221 | } |
| 222 | + | |
| 223 | +// ----------------------------------------------------------------------------- | |
| 224 | +// Compte KA ID (SSO hub groupe-ka.com) + favoris « Mon univers Ka » | |
| 225 | +// ----------------------------------------------------------------------------- | |
| 226 | +export type Socials = Partial<Record< | |
| 227 | + "instagram" | "facebook" | "x" | "linkedin" | "tiktok" | "youtube", string | |
| 228 | +>>; | |
| 229 | + | |
| 230 | +export interface User { | |
| 231 | + sub: string; | |
| 232 | + email: string; | |
| 233 | + name: string; | |
| 234 | + picture?: string; | |
| 235 | + ka_id?: string; | |
| 236 | + provider?: string; // "ka-id" | "google" | |
| 237 | + created_at?: number | null; // epoch (s) | |
| 238 | + last_login?: number | null; // epoch (s) | |
| 239 | + // profil enrichi par le HUB Groupe KA (source de vérité — groupe-ka.com/compte) | |
| 240 | + bio?: string; | |
| 241 | + city?: string; | |
| 242 | + phone?: string; | |
| 243 | + website?: string; | |
| 244 | + socials?: Socials; | |
| 245 | + public?: boolean; | |
| 246 | + role_label?: string; | |
| 247 | + job_title?: string; | |
| 248 | + company?: string; | |
| 249 | + age?: number | null; | |
| 250 | + public_url?: string; | |
| 251 | + profile_source?: string; // "groupe-ka" | "local" | |
| 252 | +} | |
| 253 | + | |
| 254 | +/** Profil de session ({user: null} si non connecté ; `enabled` = SSO configuré). */ | |
| 255 | +export const fetchMe = () => get<{ user: User | null; enabled: boolean }>("/api/auth/me"); | |
| 256 | + | |
| 257 | +export const logout = () => fetch("/api/auth/logout", { method: "POST" }); | |
| 258 | + | |
| 259 | +/** Item favori tel qu'attendu par le hub (voir immoka/favorites.py). */ | |
| 260 | +export interface FavItem { | |
| 261 | + item_id: string; | |
| 262 | + title: string; | |
| 263 | + subtitle: string; | |
| 264 | + price_label: string; | |
| 265 | + image_url: string; | |
| 266 | + url: string; | |
| 267 | +} | |
| 268 | + | |
| 269 | +export function favItemFromListing(l: Listing): FavItem { | |
| 270 | + return { | |
| 271 | + item_id: l.uid, | |
| 272 | + title: l.title || l.address || l.property_type || "Propriété", | |
| 273 | + subtitle: [l.city, l.region].filter(Boolean).join(", "), | |
| 274 | + price_label: l.price_label || fmtPrice(l.price), | |
| 275 | + image_url: l.images?.[0] ?? "", | |
| 276 | + url: `/propriete/${l.uid}`, | |
| 277 | + }; | |
| 278 | +} | |
| 279 | + | |
| 280 | +export const fetchFavorites = () => | |
| 281 | + get<{ ids: string[]; items: FavItem[] }>("/api/favorites"); | |
| 282 | + | |
| 283 | +export async function toggleFavorite(on: boolean, item: FavItem) { | |
| 284 | + const res = await fetch("/api/favorites/toggle", { | |
| 285 | + method: "POST", | |
| 286 | + headers: { "Content-Type": "application/json" }, | |
| 287 | + body: JSON.stringify({ on, item }), | |
| 288 | + }); | |
| 289 | + if (!res.ok) throw new Error(`favoris ${res.status}`); | |
| 290 | + return (await res.json()) as { ok: boolean; on: boolean }; | |
| 291 | +} | |
added
frontend/src/ka/GroupeKaBadge.tsx
+21 −0
@@ -0,0 +1,21 @@ | ||
| 1 | +// Auteur : Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +// ka-ui/react/GroupeKaBadge.tsx — badge « Un service Groupe KA », cliquable | |
| 3 | +// vers le hub. À placer dans le header de chaque site (à côté du logo de la | |
| 4 | +// marque). Variante dark pour les surfaces encre. Zone de protection : le | |
| 5 | +// padding interne du badge suffit ; ne rien coller à moins de 8 px du badge. | |
| 6 | +export default function GroupeKaBadge({ dark = false }: { dark?: boolean }) { | |
| 7 | + return ( | |
| 8 | + <a | |
| 9 | + className={`gk-badge${dark ? " gk-badge--dark" : ""}`} | |
| 10 | + href="https://www.groupe-ka.com" | |
| 11 | + target="_blank" | |
| 12 | + rel="noopener noreferrer" | |
| 13 | + aria-label="Un service Groupe KA — visiter le portail de l'écosystème" | |
| 14 | + > | |
| 15 | + Un service | |
| 16 | + <b> | |
| 17 | + Groupe<span className="ka">KA</span> | |
| 18 | + </b> | |
| 19 | + </a> | |
| 20 | + ); | |
| 21 | +} | |
added
frontend/src/ka/KaFooter.tsx
+91 −0
@@ -0,0 +1,91 @@ | ||
| 1 | +// Auteur : Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +// ka-ui/react/KaFooter.tsx — footer commun Groupe KA (fond encre), à vendorer | |
| 3 | +// dans chaque app Vite/React sous frontend/src/ka/. Consomme ecosystem.json : | |
| 4 | +// toute modification des infos de contact se fait dans ka-ui/ecosystem.json | |
| 5 | +// puis se resynchronise sur les 12 sites (voir ka-ui/README.md). | |
| 6 | +import eco from "./ecosystem.json"; | |
| 7 | + | |
| 8 | +type LocalLink = { label: string; href: string }; | |
| 9 | + | |
| 10 | +export default function KaFooter({ | |
| 11 | + siteId, | |
| 12 | + localLegal = [], | |
| 13 | +}: { | |
| 14 | + /** id du site courant dans ecosystem.json (ex. "resto-ka") */ | |
| 15 | + siteId: string; | |
| 16 | + /** pages légales PROPRES au site (ex. /conditions), affichées avant celles du hub */ | |
| 17 | + localLegal?: LocalLink[]; | |
| 18 | +}) { | |
| 19 | + const year = new Date().getFullYear(); | |
| 20 | + const site = eco.sites.find((s) => s.id === siteId); | |
| 21 | + const others = eco.sites.filter((s) => s.id !== siteId); | |
| 22 | + return ( | |
| 23 | + <footer className="ka-footer" id="contact"> | |
| 24 | + <div className="container"> | |
| 25 | + <a | |
| 26 | + className="wordmark" | |
| 27 | + href={eco.hub.url} | |
| 28 | + target={siteId === "groupe-ka" ? undefined : "_blank"} | |
| 29 | + rel="noopener noreferrer" | |
| 30 | + aria-label="Groupe KA — le portail de l'écosystème" | |
| 31 | + > | |
| 32 | + Groupe <span className="ka">KA</span> | |
| 33 | + </a> | |
| 34 | + <p className="desc"> | |
| 35 | + {eco.org.tagline} Des centaines de connecteurs lisent les sites à la | |
| 36 | + source, normalisent la donnée et se resynchronisent seuls.{" "} | |
| 37 | + {site ? ( | |
| 38 | + <> | |
| 39 | + <b style={{ color: "var(--paper)" }}>{site.wordmark}</b> —{" "} | |
| 40 | + {site.tagline} — est un service Groupe KA. | |
| 41 | + </> | |
| 42 | + ) : null} | |
| 43 | + </p> | |
| 44 | + <p className="notice"> | |
| 45 | + <b>{eco.org.disclaimer}</b> | |
| 46 | + </p> | |
| 47 | + <ul className="sites"> | |
| 48 | + {others.map((s) => ( | |
| 49 | + <li key={s.id}> | |
| 50 | + <a href={`https://${s.domain}`} target="_blank" rel="noopener noreferrer"> | |
| 51 | + {s.wordmark} | |
| 52 | + </a> | |
| 53 | + </li> | |
| 54 | + ))} | |
| 55 | + {eco.extraFooterLinks.map((l) => ( | |
| 56 | + <li key={l.href}> | |
| 57 | + <a href={l.href} target="_blank" rel="noopener noreferrer"> | |
| 58 | + {l.label} | |
| 59 | + </a> | |
| 60 | + </li> | |
| 61 | + ))} | |
| 62 | + </ul> | |
| 63 | + <div className="contacts"> | |
| 64 | + {eco.contacts.map((c) => ( | |
| 65 | + <p key={c.email} style={{ margin: 0 }}> | |
| 66 | + <a href={`mailto:${c.email}`}>{c.email}</a> | |
| 67 | + <span>{c.role}</span> | |
| 68 | + </p> | |
| 69 | + ))} | |
| 70 | + </div> | |
| 71 | + <p className="legal"> | |
| 72 | + © {year} {eco.org.copyrightHolder} | |
| 73 | + {localLegal.map((l) => ( | |
| 74 | + <span key={l.href}> | |
| 75 | + {" · "} | |
| 76 | + <a href={l.href}>{l.label}</a> | |
| 77 | + </span> | |
| 78 | + ))} | |
| 79 | + {eco.legal.map((l) => ( | |
| 80 | + <span key={l.href}> | |
| 81 | + {" · "} | |
| 82 | + <a href={l.href} target="_blank" rel="noopener noreferrer"> | |
| 83 | + {l.label} | |
| 84 | + </a> | |
| 85 | + </span> | |
| 86 | + ))} | |
| 87 | + </p> | |
| 88 | + </div> | |
| 89 | + </footer> | |
| 90 | + ); | |
| 91 | +} | |
added
frontend/src/ka/ecosystem.json
+45 −0
@@ -0,0 +1,45 @@ | ||
| 1 | +{ | |
| 2 | + "org": { | |
| 3 | + "name": "Groupe KA", | |
| 4 | + "legalName": "Groupe KA — Simon-Pierre Boucher", | |
| 5 | + "tagline": "Holding québécois d'agrégateurs de produits et services entièrement automatisés.", | |
| 6 | + "disclaimer": "Groupe KA est un agrégateur de contenu : nous ne vendons rien, ne louons rien et ne sommes partie à aucune transaction.", | |
| 7 | + "copyrightHolder": "Groupe KA — Simon-Pierre Boucher" | |
| 8 | + }, | |
| 9 | + "hub": { | |
| 10 | + "url": "https://www.groupe-ka.com", | |
| 11 | + "loginPath": "/connexion", | |
| 12 | + "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." | |
| 13 | + }, | |
| 14 | + "contacts": [ | |
| 15 | + { "email": "contact@groupe-ka.com", "role": "Projets, partenariats & données" }, | |
| 16 | + { "email": "info@groupe-ka.com", "role": "Médias & questions générales" }, | |
| 17 | + { "email": "admin@groupe-ka.com", "role": "Légal, vie privée & Loi 25" } | |
| 18 | + ], | |
| 19 | + "legal": [ | |
| 20 | + { "label": "Conditions d'utilisation", "href": "https://www.groupe-ka.com/conditions" }, | |
| 21 | + { "label": "Politique de confidentialité", "href": "https://www.groupe-ka.com/confidentialite" }, | |
| 22 | + { "label": "Protection des renseignements personnels (Loi 25)", "href": "https://www.groupe-ka.com/loi-25" }, | |
| 23 | + { "label": "Transparence des robots d'indexation", "href": "https://www.groupe-ka.com/bots" } | |
| 24 | + ], | |
| 25 | + "sites": [ | |
| 26 | + { "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" }, | |
| 27 | + { "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" }, | |
| 28 | + { "id": "lou-ka", "wordmark": "Lou·Ka", "domain": "www.lou-ka.com", "accent": "#ff6a00", "accentSoft": "#fff1e6", "accentDeep": "#cc5500", "onAccent": "#ffffff", "tagline": "Tous les logements à louer" }, | |
| 29 | + { "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" }, | |
| 30 | + { "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é" }, | |
| 31 | + { "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" }, | |
| 32 | + { "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" }, | |
| 33 | + { "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" }, | |
| 34 | + { "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" }, | |
| 35 | + { "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" }, | |
| 36 | + { "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" }, | |
| 37 | + { "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" } | |
| 38 | + ], | |
| 39 | + "extraFooterLinks": [ | |
| 40 | + { "label": "ValoPlex", "href": "https://www.valoplex.com" }, | |
| 41 | + { "label": "Ka2", "href": "https://www.ka2.bot" }, | |
| 42 | + { "label": "Ka4", "href": "https://www.ka4.bot" }, | |
| 43 | + { "label": "Ka6", "href": "https://www.ka6.bot" } | |
| 44 | + ] | |
| 45 | +} | |
added
frontend/src/ka/tokens.css
+229 −0
@@ -0,0 +1,229 @@ | ||
| 1 | +/* ----------------------------------------------------------------------------- | |
| 2 | + Auteur : Simon-Pierre Boucher — contact@spboucher.ai | |
| 3 | + Fichier : ka-ui/tokens.css — SOURCE CANONIQUE (repo ka-ui sur spbgit) | |
| 4 | + Desc. : Design system commun GROUPE KA « éditorial sharp » — tokens + socle | |
| 5 | + de composants partagés par les 12 plateformes. Chaque site importe ce | |
| 6 | + fichier PUIS surcharge uniquement son accent (voir accents.css). | |
| 7 | + | |
| 8 | + · Typo : display Space Grotesk / texte Inter / micro-étiquettes JetBrains Mono | |
| 9 | + · Palette: papier #f5f3ee · encre #141814 · vert profond #1c5c41 + ACCENT du site | |
| 10 | + · Signature : bordures encre 1,5–2 px + ombres décalées dures, grain de film, | |
| 11 | + surlignés d'accent inclinés (néo-brutalisme raffiné) | |
| 12 | + · Breakpoints communs : 360 / 768 / 1024 / 1440 px (mobile-first) | |
| 13 | + · Zones tactiles ≥ 44×44 px, safe-areas iOS, typographie fluide (clamp) | |
| 14 | +----------------------------------------------------------------------------- */ | |
| 15 | + | |
| 16 | +:root { | |
| 17 | + /* ---- Palette fixe Groupe KA (identique sur les 12 sites) ---- */ | |
| 18 | + --paper: #f5f3ee; | |
| 19 | + --surface: #ffffff; | |
| 20 | + --surface-2: #faf9f5; | |
| 21 | + --ink: #141814; | |
| 22 | + --ink-2: #4d5551; | |
| 23 | + --ink-3: #8b928c; | |
| 24 | + --line: rgba(20, 24, 20, 0.14); | |
| 25 | + --line-strong: rgba(20, 24, 20, 0.85); | |
| 26 | + --green: #1c5c41; | |
| 27 | + --green-deep: #123f2e; | |
| 28 | + --amber: #e8a33d; | |
| 29 | + --amber-soft: #fdf3e2; | |
| 30 | + --danger: #b3423a; | |
| 31 | + --danger-soft: #fbe9e7; | |
| 32 | + | |
| 33 | + /* ---- ACCENT du site — SEULES variables surchargées par marque ---- */ | |
| 34 | + --accent: #d9f26b; /* défaut : lime Groupe KA */ | |
| 35 | + --accent-soft: #f0f9d2; | |
| 36 | + --accent-deep: #123f2e; | |
| 37 | + --on-accent: var(--ink); /* couleur du texte posé SUR l'accent */ | |
| 38 | + | |
| 39 | + /* alias rétro-compatibles (les satellites historiques utilisent --lime) */ | |
| 40 | + --lime: var(--accent); | |
| 41 | + --lime-soft: var(--accent-soft); | |
| 42 | + | |
| 43 | + /* ---- Géométrie sharp ---- */ | |
| 44 | + --r-card: 10px; | |
| 45 | + --r-ctl: 6px; | |
| 46 | + --r-pill: 999px; | |
| 47 | + | |
| 48 | + /* ---- Ombres décalées — la signature du groupe ---- */ | |
| 49 | + --shadow-flat: 0 1px 2px rgba(20, 24, 20, 0.05); | |
| 50 | + --shadow-off: 6px 6px 0 var(--ink); | |
| 51 | + --shadow-off-soft: 8px 8px 0 rgba(20, 24, 20, 0.08); | |
| 52 | + --shadow-off-mid: 4px 4px 0 rgba(20, 24, 20, 0.18); | |
| 53 | + | |
| 54 | + /* ---- Typographie ---- */ | |
| 55 | + --font-display: "Space Grotesk", system-ui, sans-serif; | |
| 56 | + --font-body: "Inter", system-ui, sans-serif; | |
| 57 | + --font-mono: "JetBrains Mono", ui-monospace, monospace; | |
| 58 | + | |
| 59 | + /* Échelle fluide (mobile-first, clamp) */ | |
| 60 | + --fs-h1: clamp(30px, 3.2vw + 18px, 54px); | |
| 61 | + --fs-h2: clamp(23px, 1.8vw + 14px, 34px); | |
| 62 | + --fs-h3: clamp(17px, 0.9vw + 12px, 22px); | |
| 63 | + --fs-body: 15px; | |
| 64 | + --fs-small: 13px; | |
| 65 | + | |
| 66 | + /* Espacements */ | |
| 67 | + --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; | |
| 68 | + --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px; | |
| 69 | + | |
| 70 | + /* Cible tactile minimale */ | |
| 71 | + --touch: 44px; | |
| 72 | +} | |
| 73 | + | |
| 74 | +/* ---- Socle ---- */ | |
| 75 | +* { box-sizing: border-box; } | |
| 76 | +html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; } | |
| 77 | +body { | |
| 78 | + margin: 0; | |
| 79 | + background: var(--paper); | |
| 80 | + color: var(--ink); | |
| 81 | + font-family: var(--font-body); | |
| 82 | + font-size: var(--fs-body); | |
| 83 | + line-height: 1.55; | |
| 84 | + -webkit-font-smoothing: antialiased; | |
| 85 | + padding-bottom: env(safe-area-inset-bottom); | |
| 86 | + overflow-x: clip; /* jamais de débordement horizontal */ | |
| 87 | +} | |
| 88 | +img, svg, video { max-width: 100%; height: auto; display: block; } | |
| 89 | +h1, h2, h3, h4 { font-family: var(--font-display); letter-spacing: -0.03em; margin: 0 0 0.5em; } | |
| 90 | +h1 { font-size: var(--fs-h1); line-height: 1.06; } | |
| 91 | +h2 { font-size: var(--fs-h2); line-height: 1.15; } | |
| 92 | +h3 { font-size: var(--fs-h3); line-height: 1.25; } | |
| 93 | +a { color: inherit; } | |
| 94 | +::selection { background: var(--accent); color: var(--on-accent); } | |
| 95 | +:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; } | |
| 96 | + | |
| 97 | +/* Grain de film pleine page — ⚠️ jamais de z-index sur body > * (position: | |
| 98 | + relative seulement), sinon les utilitaires z-* sont écrasés. */ | |
| 99 | +body::before { | |
| 100 | + content: ""; | |
| 101 | + position: fixed; inset: 0; pointer-events: none; opacity: 0.35; z-index: 9999; | |
| 102 | + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.06'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E"); | |
| 103 | +} | |
| 104 | +body > * { position: relative; } | |
| 105 | + | |
| 106 | +/* ---- Conteneur commun ---- */ | |
| 107 | +.container { max-width: 1152px; margin: 0 auto; padding: 0 var(--sp-4); } | |
| 108 | +@media (min-width: 768px) { .container { padding: 0 var(--sp-5); } } | |
| 109 | + | |
| 110 | +/* ---- Micro-typographie signature ---- */ | |
| 111 | +.kicker { | |
| 112 | + display: inline-flex; align-items: center; gap: 10px; | |
| 113 | + font-family: var(--font-mono); font-size: 11.5px; font-weight: 500; | |
| 114 | + text-transform: uppercase; letter-spacing: 0.12em; color: var(--green); | |
| 115 | +} | |
| 116 | +.kicker::before { content: ""; width: 22px; height: 2px; background: var(--green); } | |
| 117 | +.klabel { | |
| 118 | + font-family: var(--font-mono); font-size: 10px; font-weight: 700; | |
| 119 | + text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3); | |
| 120 | +} | |
| 121 | +.hl { | |
| 122 | + display: inline-block; background: var(--accent); color: var(--on-accent); | |
| 123 | + border-radius: 8px; padding: 0 10px 2px; transform: rotate(-1deg); | |
| 124 | +} | |
| 125 | +.outline-txt { color: transparent; -webkit-text-stroke: 2px var(--ink); } | |
| 126 | + | |
| 127 | +/* ---- Boutons (cible ≥ 44 px) ---- */ | |
| 128 | +.btn { | |
| 129 | + display: inline-flex; align-items: center; justify-content: center; gap: 8px; | |
| 130 | + min-height: var(--touch); padding: 10px 18px; | |
| 131 | + font-family: var(--font-display); font-weight: 700; font-size: 14px; | |
| 132 | + border: 1.5px solid var(--ink); border-radius: var(--r-ctl); | |
| 133 | + background: var(--surface); color: var(--ink); | |
| 134 | + cursor: pointer; text-decoration: none; | |
| 135 | + transition: transform 0.15s, box-shadow 0.15s, background 0.15s, color 0.15s; | |
| 136 | +} | |
| 137 | +.btn:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off-mid); } | |
| 138 | +.btn:active { transform: translate(0, 0); box-shadow: none; } | |
| 139 | +.btn:disabled { opacity: 0.45; pointer-events: none; } | |
| 140 | +.btn-primary { background: var(--ink); color: var(--accent); } | |
| 141 | +.btn-primary:hover { background: var(--accent-deep); } | |
| 142 | +.btn-accent { background: var(--accent); color: var(--on-accent); } | |
| 143 | +.btn-ghost { background: transparent; border-color: var(--line); } | |
| 144 | +.btn-ghost:hover { border-color: var(--ink); } | |
| 145 | + | |
| 146 | +/* ---- Cartes ---- */ | |
| 147 | +.card { | |
| 148 | + background: var(--surface); border: 1.5px solid var(--ink); | |
| 149 | + border-radius: var(--r-card); box-shadow: var(--shadow-off-soft); | |
| 150 | + overflow: hidden; | |
| 151 | +} | |
| 152 | +.card-hover { transition: transform 0.15s, box-shadow 0.15s; } | |
| 153 | +.card-hover:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off); } | |
| 154 | + | |
| 155 | +/* ---- Chips / badges ---- */ | |
| 156 | +.chip { | |
| 157 | + display: inline-flex; align-items: center; gap: 6px; | |
| 158 | + padding: 4px 11px; font-family: var(--font-mono); font-size: 11px; font-weight: 700; | |
| 159 | + text-transform: uppercase; letter-spacing: 0.06em; | |
| 160 | + border: 1.5px solid var(--ink); border-radius: var(--r-pill); | |
| 161 | + background: var(--surface); color: var(--ink); | |
| 162 | +} | |
| 163 | +.chip-accent { background: var(--accent); color: var(--on-accent); } | |
| 164 | +.chip-soft { background: var(--accent-soft); border-color: var(--line); } | |
| 165 | + | |
| 166 | +/* ---- Champs ---- */ | |
| 167 | +.input, .select, .textarea { | |
| 168 | + width: 100%; min-height: var(--touch); padding: 10px 14px; | |
| 169 | + font: inherit; color: var(--ink); background: var(--surface); | |
| 170 | + border: 1.5px solid var(--ink); border-radius: var(--r-ctl); | |
| 171 | +} | |
| 172 | +.input:focus, .select:focus, .textarea:focus { | |
| 173 | + outline: none; box-shadow: 3px 3px 0 var(--accent); border-color: var(--ink); | |
| 174 | +} | |
| 175 | +.input::placeholder { color: var(--ink-3); } | |
| 176 | + | |
| 177 | +/* ---- Badge « Un service Groupe KA » (header, cliquable → hub) ---- */ | |
| 178 | +.gk-badge { | |
| 179 | + display: inline-flex; align-items: center; gap: 7px; | |
| 180 | + min-height: 30px; padding: 3px 10px 4px; | |
| 181 | + font-family: var(--font-mono); font-size: 10px; font-weight: 700; | |
| 182 | + letter-spacing: 0.08em; text-transform: uppercase; text-decoration: none; | |
| 183 | + border: 1.5px solid var(--ink); border-radius: var(--r-pill); | |
| 184 | + background: var(--surface); color: var(--ink-2); white-space: nowrap; | |
| 185 | +} | |
| 186 | +.gk-badge b { | |
| 187 | + font-family: var(--font-display); font-size: 12px; letter-spacing: -0.02em; | |
| 188 | + text-transform: none; color: var(--ink); | |
| 189 | +} | |
| 190 | +.gk-badge b .ka { | |
| 191 | + display: inline-block; background: var(--ink); color: var(--accent); | |
| 192 | + border-radius: 5px; padding: 0 5px 1px; margin-left: 3px; transform: rotate(-2deg); | |
| 193 | +} | |
| 194 | +.gk-badge:hover .ka { transform: rotate(0); } | |
| 195 | +.gk-badge--dark { background: transparent; border-color: rgba(245,243,238,0.4); color: rgba(245,243,238,0.75); } | |
| 196 | +.gk-badge--dark b { color: var(--paper); } | |
| 197 | + | |
| 198 | +/* ---- Footer commun (fond encre — voir react/KaFooter.tsx) ---- */ | |
| 199 | +.ka-footer { margin-top: var(--sp-8); background: var(--ink); padding: 44px 0; font-size: 13px; color: rgba(245,243,238,0.75); } | |
| 200 | +.ka-footer a { text-decoration: none; } | |
| 201 | +.ka-footer .wordmark { font-family: var(--font-display); font-weight: 700; font-size: 30px; letter-spacing: -0.04em; color: var(--paper); text-decoration: none; } | |
| 202 | +.ka-footer .wordmark .ka { color: var(--accent); } | |
| 203 | +.ka-footer .desc { max-width: 640px; margin-top: var(--sp-4); } | |
| 204 | +.ka-footer .notice { max-width: 640px; margin-top: var(--sp-4); border-left: 2px solid var(--accent); padding-left: var(--sp-4); } | |
| 205 | +.ka-footer .notice b { color: var(--paper); } | |
| 206 | +.ka-footer .sites { list-style: none; display: flex; flex-wrap: wrap; gap: 10px 26px; margin: 26px 0 0; padding: 0; font-family: var(--font-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; } | |
| 207 | +.ka-footer .sites a { color: rgba(245,243,238,0.65); } | |
| 208 | +.ka-footer .sites a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; } | |
| 209 | +.ka-footer .contacts { display: grid; gap: 14px 32px; border-top: 1px solid rgba(245,243,238,0.15); margin-top: 30px; padding-top: 26px; } | |
| 210 | +@media (min-width: 768px) { .ka-footer .contacts { grid-template-columns: repeat(3, 1fr); } } | |
| 211 | +.ka-footer .contacts a { font-family: var(--font-mono); font-weight: 700; font-size: 12px; color: rgba(245,243,238,0.85); } | |
| 212 | +.ka-footer .contacts a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; } | |
| 213 | +.ka-footer .contacts span { display: block; margin-top: 3px; font-size: 11px; color: rgba(245,243,238,0.5); } | |
| 214 | +.ka-footer .legal { margin-top: 30px; font-family: var(--font-mono); font-size: 11px; color: rgba(245,243,238,0.45); } | |
| 215 | +.ka-footer .legal a { color: inherit; } | |
| 216 | +.ka-footer .legal a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; } | |
| 217 | + | |
| 218 | +/* ---- Tableaux → cartes empilées sous 768 px ---- */ | |
| 219 | +.tbl-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; } | |
| 220 | +@media (max-width: 767px) { | |
| 221 | + .tbl-stack thead { display: none; } | |
| 222 | + .tbl-stack tr { display: block; border: 1.5px solid var(--ink); border-radius: var(--r-card); margin-bottom: var(--sp-3); background: var(--surface); } | |
| 223 | + .tbl-stack td { display: flex; justify-content: space-between; gap: var(--sp-3); padding: 8px 12px; border: 0; } | |
| 224 | + .tbl-stack td::before { content: attr(data-label); font-family: var(--font-mono); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-3); } | |
| 225 | +} | |
| 226 | + | |
| 227 | +/* ---- Utilitaires responsive ---- */ | |
| 228 | +.only-mobile { display: initial; } .only-desktop { display: none; } | |
| 229 | +@media (min-width: 768px) { .only-mobile { display: none; } .only-desktop { display: initial; } } | |
modified
frontend/src/main.tsx
+7 −2
@@ -1,18 +1,23 @@ | ||
| 1 | 1 | // ----------------------------------------------------------------------------- |
| 2 | 2 | // Immo-Ka — Agrégateur de propriétés à vendre (province de Québec) |
| 3 | 3 | // Auteur : Simon-Pierre Boucher — contact@spboucher.ai |
| 4 | −// main.tsx : point d'entrée React | |
| 4 | +// main.tsx : point d'entrée React — tokens Groupe KA (ka/tokens.css) AVANT le | |
| 5 | +// CSS local, puis contexte de compte KA ID (AccountProvider). | |
| 5 | 6 | // ----------------------------------------------------------------------------- |
| 6 | 7 | import React from "react"; |
| 7 | 8 | import ReactDOM from "react-dom/client"; |
| 8 | 9 | import { BrowserRouter } from "react-router-dom"; |
| 9 | 10 | import App from "./App"; |
| 11 | +import { AccountProvider } from "./account"; | |
| 12 | +import "./ka/tokens.css"; | |
| 10 | 13 | import "./styles.css"; |
| 11 | 14 | |
| 12 | 15 | ReactDOM.createRoot(document.getElementById("root")!).render( |
| 13 | 16 | <React.StrictMode> |
| 14 | 17 | <BrowserRouter> |
| 15 | − <App /> | |
| 18 | + <AccountProvider> | |
| 19 | + <App /> | |
| 20 | + </AccountProvider> | |
| 16 | 21 | </BrowserRouter> |
| 17 | 22 | </React.StrictMode> |
| 18 | 23 | ); |
added
frontend/src/pages/Contact.tsx
+71 −0
@@ -0,0 +1,71 @@ | ||
| 1 | +// ----------------------------------------------------------------------------- | |
| 2 | +// Immo-Ka — Agrégateur de propriétés à vendre (province de Québec) | |
| 3 | +// Une application du Groupe-Ka — contact@groupe-ka.com | |
| 4 | +// pages/Contact.tsx : page contact commune Groupe KA — consomme ka/ecosystem.json | |
| 5 | +// (courriels + rôles, avertissement agrégateur, hub, liste des sites). | |
| 6 | +// ----------------------------------------------------------------------------- | |
| 7 | +import eco from "../ka/ecosystem.json"; | |
| 8 | + | |
| 9 | +export default function ContactPage() { | |
| 10 | + const sites = eco.sites.filter((s) => s.id !== "immo-ka"); | |
| 11 | + return ( | |
| 12 | + <div className="container contact"> | |
| 13 | + <span className="kicker">Contact</span> | |
| 14 | + <h1> | |
| 15 | + Écrire au <span className="hl">Groupe KA</span> | |
| 16 | + </h1> | |
| 17 | + <p className="lede"> | |
| 18 | + Immo-Ka est un service du <b>{eco.org.name}</b> — {eco.org.tagline.toLowerCase()}{" "} | |
| 19 | + Toutes les plateformes partagent les mêmes canaux de contact. | |
| 20 | + </p> | |
| 21 | + | |
| 22 | + <div className="contact-cards"> | |
| 23 | + {eco.contacts.map((c) => ( | |
| 24 | + <a key={c.email} className="contact-card" href={`mailto:${c.email}`}> | |
| 25 | + <span className="contact-role">{c.role}</span> | |
| 26 | + <span className="contact-mail">{c.email}</span> | |
| 27 | + </a> | |
| 28 | + ))} | |
| 29 | + </div> | |
| 30 | + | |
| 31 | + <p className="contact-disclaimer"> | |
| 32 | + <b>{eco.org.disclaimer}</b> Chaque fiche renvoie vers l'annonce originale | |
| 33 | + de l'agence ou de la plateforme source : pour une propriété précise, | |
| 34 | + contactez directement le courtier indiqué sur la fiche. | |
| 35 | + </p> | |
| 36 | + | |
| 37 | + <div className="contact-hub"> | |
| 38 | + <a | |
| 39 | + className="btn btn-primary" | |
| 40 | + href={eco.hub.url} | |
| 41 | + target="_blank" | |
| 42 | + rel="noopener noreferrer" | |
| 43 | + > | |
| 44 | + Visiter le portail groupe-ka.com ↗ | |
| 45 | + </a> | |
| 46 | + <a | |
| 47 | + className="btn btn-ghost" | |
| 48 | + href={`${eco.hub.url}${eco.hub.loginPath}`} | |
| 49 | + target="_blank" | |
| 50 | + rel="noopener noreferrer" | |
| 51 | + > | |
| 52 | + Créer un compte KA ID | |
| 53 | + </a> | |
| 54 | + </div> | |
| 55 | + | |
| 56 | + <section className="contact-sites"> | |
| 57 | + <h2>L'écosystème ·Ka</h2> | |
| 58 | + <ul> | |
| 59 | + {sites.map((s) => ( | |
| 60 | + <li key={s.id}> | |
| 61 | + <a href={`https://${s.domain}`} target="_blank" rel="noopener noreferrer"> | |
| 62 | + <b>{s.wordmark}</b> | |
| 63 | + <span>{s.tagline}</span> | |
| 64 | + </a> | |
| 65 | + </li> | |
| 66 | + ))} | |
| 67 | + </ul> | |
| 68 | + </section> | |
| 69 | + </div> | |
| 70 | + ); | |
| 71 | +} | |
modified
frontend/src/styles.css
+168 −53
@@ -1,57 +1,28 @@ | ||
| 1 | 1 | /* ----------------------------------------------------------------------------- |
| 2 | 2 | Immo-Ka — Agrégateur de propriétés à vendre (province de Québec) |
| 3 | 3 | Auteur : Simon-Pierre Boucher — contact@spboucher.ai |
| 4 | − styles.css : système de design « éditorial sharp » — thème clair | |
| 5 | − · Typo display Space Grotesk / texte Inter / micro-étiquettes JetBrains Mono | |
| 6 | − · Bordures encre + ombres décalées (néo-brutalisme raffiné) | |
| 7 | − · Accent rouge vif sur encre chocolat · 100 % adaptatif mobile | |
| 4 | + styles.css : classes métier Immo-Ka par-dessus le design system commun | |
| 5 | + Groupe KA (ka/tokens.css, importé AVANT ce fichier dans main.tsx). | |
| 6 | + · Le socle (palette papier/encre, typo, grain, selection, focus, socle | |
| 7 | + de composants) vient de ka/tokens.css — NE PAS le dupliquer ici. | |
| 8 | + · SEUL l'accent de marque est surchargé : CERISE Immo-Ka. | |
| 8 | 9 | ----------------------------------------------------------------------------- */ |
| 9 | 10 | :root { |
| 10 | − /* Palette ROUGE — distincte de Lou-Ka (lime/vert). Noms de tokens conservés : | |
| 11 | − `--lime` = accent vif (rouge), `--green` = rouge profond (secondaire). */ | |
| 12 | − --paper: #f7f1ef; | |
| 13 | − --surface: #ffffff; | |
| 14 | − --surface-2: #fdf6f5; | |
| 15 | − --ink: #1a1214; /* encre chocolat/near-black chaud */ | |
| 16 | − --ink-2: #5a4d50; | |
| 17 | − --ink-3: #9b8b8e; | |
| 18 | − --line: rgba(26, 18, 20, 0.14); | |
| 19 | − --line-strong: rgba(26, 18, 20, 0.85); | |
| 20 | − --green: #b3202b; /* rouge profond (accent secondaire / kicker) */ | |
| 21 | − --green-deep: #7f1620; | |
| 22 | − --lime: #e23744; /* rouge vif (accent principal) */ | |
| 23 | − --lime-soft: #fbe0e2; | |
| 24 | − --amber: #e8a33d; | |
| 25 | − --amber-soft: #fdf3e2; | |
| 26 | − --danger: #b3202b; | |
| 27 | − --r-card: 14px; | |
| 28 | − --r-ctl: 8px; | |
| 29 | − --shadow-flat: 0 1px 2px rgba(26, 18, 20, 0.06); | |
| 30 | − --shadow-off: 6px 6px 0 var(--ink); | |
| 31 | − --shadow-off-soft: 8px 8px 0 rgba(226, 55, 68, 0.14); | |
| 32 | − --font-display: "Space Grotesk", system-ui, sans-serif; | |
| 33 | − --font-body: "Inter", system-ui, sans-serif; | |
| 34 | − --font-mono: "JetBrains Mono", ui-monospace, monospace; | |
| 11 | + /* ---- Accent CERISE Immo-Ka (seule surcharge de ka/tokens.css) ---- */ | |
| 12 | + --accent: #e23744; | |
| 13 | + --accent-soft: #fbe0e2; | |
| 14 | + --accent-deep: #a8232e; | |
| 15 | + --on-accent: #ffffff; | |
| 16 | + /* alias historiques — tout le CSS métier ci-dessous utilise --lime */ | |
| 17 | + --lime: var(--accent); | |
| 18 | + --lime-soft: var(--accent-soft); | |
| 35 | 19 | } |
| 36 | 20 | |
| 37 | −* { box-sizing: border-box; } | |
| 38 | −html { scroll-behavior: smooth; scroll-padding-top: 76px; } | |
| 39 | −body { | |
| 40 | − margin: 0; background: var(--paper); color: var(--ink); | |
| 41 | − font-family: var(--font-body); font-size: 15px; line-height: 1.55; | |
| 42 | − -webkit-font-smoothing: antialiased; padding-bottom: env(safe-area-inset-bottom); | |
| 43 | −} | |
| 44 | −body::before { | |
| 45 | − content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: 0.35; | |
| 46 | − background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E"); | |
| 47 | −} | |
| 48 | −#root { position: relative; z-index: 1; } | |
| 49 | − | |
| 50 | −h1, h2, h3, h4 { font-family: var(--font-display); letter-spacing: -0.03em; margin: 0; } | |
| 51 | −a { color: inherit; text-decoration: none; } | |
| 21 | +/* Ancre sous le header collant (spécifique Immo-Ka) */ | |
| 22 | +html { scroll-padding-top: 76px; } | |
| 23 | +h1, h2, h3, h4 { margin: 0; } | |
| 24 | +a { text-decoration: none; } | |
| 52 | 25 | button { font-family: inherit; } |
| 53 | −img { display: block; } | |
| 54 | −::selection { background: var(--lime); color: #fff; } | |
| 55 | 26 | |
| 56 | 27 | .mono { font-family: var(--font-mono); } |
| 57 | 28 | .kicker { |
@@ -474,13 +445,9 @@ table.rooms tr:nth-child(even) td { background: var(--surface-2); } | ||
| 474 | 445 | .mm-backdrop { position: fixed; inset: 0; z-index: 40; background: rgba(26, 18, 20, 0.35); backdrop-filter: blur(2px); } |
| 475 | 446 | @media (max-width: 760px) { .menu-btn { display: flex; } .nav { display: none; } .mobile-menu { display: block; } } |
| 476 | 447 | |
| 477 | −/* ================= Footer ================= */ | |
| 478 | −.footer { background: var(--ink); color: rgba(247, 241, 239, 0.75); margin-top: 20px; padding: 44px 0 max(40px, env(safe-area-inset-bottom)); font-size: 13px; position: relative; z-index: 1; } | |
| 479 | −.footer .fbrand { font-family: var(--font-display); font-weight: 700; font-size: 34px; color: var(--paper); letter-spacing: -0.04em; margin-bottom: 12px; } | |
| 480 | −.footer .fbrand .ka { color: var(--lime); } | |
| 481 | −.footer b { color: var(--paper); } | |
| 482 | −.footer .frow { display: flex; flex-direction: column; gap: 5px; max-width: 760px; } | |
| 483 | −.footer .fmono { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: rgba(247, 241, 239,0.45); margin-top: 18px; } | |
| 448 | +/* ================= Footer ================= | |
| 449 | + Remplacé par le footer commun Groupe KA (ka/KaFooter.tsx — styles .ka-footer | |
| 450 | + dans ka/tokens.css). */ | |
| 484 | 451 | |
| 485 | 452 | /* ================= Mobile : feuille de filtres + FAB ================= */ |
| 486 | 453 | .sheet-head { display: none; } |
@@ -629,3 +596,151 @@ table.rooms tr:nth-child(even) td { background: var(--surface-2); } | ||
| 629 | 596 | font-size: 13px; padding: 8px; background: var(--green-deep); color: #fff; |
| 630 | 597 | text-decoration: none; } |
| 631 | 598 | .vp-link:hover { background: var(--green); } |
| 599 | + | |
| 600 | +/* ============================================================================= | |
| 601 | + Harmonisation Groupe KA — header (badge + compte KA ID), pages Profil, | |
| 602 | + Contact et légales. Le socle vient de ka/tokens.css. | |
| 603 | +============================================================================= */ | |
| 604 | + | |
| 605 | +/* ---- Header : badge « Un service Groupe KA » + zone compte ---- */ | |
| 606 | +.header-inner .gk-badge { flex: none; } | |
| 607 | +@media (max-width: 900px) { .header-inner .gk-badge { display: none; } } | |
| 608 | +.header-acct { display: flex; align-items: center; gap: 10px; flex: none; } | |
| 609 | +.ka-auth { display: flex; align-items: center; gap: 10px; } | |
| 610 | +.ka-login { | |
| 611 | + display: inline-flex; align-items: center; gap: 6px; min-height: 40px; | |
| 612 | + padding: 8px 15px; border: 1.5px solid var(--ink); border-radius: 999px; | |
| 613 | + background: var(--surface); color: var(--ink); cursor: pointer; | |
| 614 | + font-family: var(--font-display); font-weight: 600; font-size: 13.5px; | |
| 615 | + box-shadow: 3px 3px 0 rgba(20, 24, 20, 0.15); | |
| 616 | + transition: transform 0.13s ease, box-shadow 0.13s ease, background 0.15s ease; | |
| 617 | +} | |
| 618 | +.ka-login b { background: var(--ink); color: var(--accent); border-radius: 5px; padding: 0 6px 1px; transform: rotate(-2deg); transition: transform 0.15s ease; } | |
| 619 | +.ka-login:hover { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 rgba(20, 24, 20, 0.2); } | |
| 620 | +.ka-login:hover b { transform: rotate(0); } | |
| 621 | +.ka-login:active { transform: translate(2px, 2px); box-shadow: none; } | |
| 622 | +.ka-signup { font-family: var(--font-mono); font-size: 11px; color: var(--ink-2); text-decoration: underline; text-underline-offset: 3px; white-space: nowrap; } | |
| 623 | +.ka-signup:hover { color: var(--accent-deep); } | |
| 624 | +@media (max-width: 1100px) { .header-acct .ka-signup { display: none; } } | |
| 625 | +.ka-acct { | |
| 626 | + display: inline-flex; align-items: center; gap: 8px; min-height: 40px; | |
| 627 | + padding: 6px 14px; border: 1.5px solid var(--ink); border-radius: 999px; | |
| 628 | + background: var(--surface); font-family: var(--font-display); | |
| 629 | + font-weight: 600; font-size: 13.5px; color: var(--ink); | |
| 630 | + max-width: 160px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; | |
| 631 | +} | |
| 632 | +.ka-acct:hover, .ka-acct.active { background: var(--ink); color: var(--accent); } | |
| 633 | +.ka-acct-pic { width: 24px; height: 24px; border-radius: 50%; flex: none; border: 1px solid var(--line); } | |
| 634 | +@media (max-width: 760px) { | |
| 635 | + .header-acct { margin-left: auto; } | |
| 636 | + .menu-btn { margin-left: 0; } | |
| 637 | + .ka-login { min-height: 44px; } | |
| 638 | +} | |
| 639 | +@media (max-width: 380px) { .header-acct { display: none; } } /* → menu mobile */ | |
| 640 | + | |
| 641 | +/* ---- Menu mobile : compte + badge groupe ---- */ | |
| 642 | +.mm-auth { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 15px 10px; border-bottom: 1.5px dashed var(--line); } | |
| 643 | +.mm-auth .ka-login { min-height: 44px; } | |
| 644 | +.mm-foot p { margin: 10px 0 0; } | |
| 645 | + | |
| 646 | +/* ---- Générique : lede (Profil / Contact) ---- */ | |
| 647 | +.lede { color: var(--ink-2); font-size: 15.5px; max-width: 640px; } | |
| 648 | + | |
| 649 | +/* ================= Page Profil (KA ID) ================= */ | |
| 650 | +.profil { padding: 44px 0 90px; } | |
| 651 | +.profil h1 { font-size: clamp(28px, 4.6vw, 44px); margin: 10px 0 24px; letter-spacing: -0.03em; } | |
| 652 | +.profil .lede { margin-bottom: 22px; } | |
| 653 | +.profil .notice { padding: 60px 0; } | |
| 654 | + | |
| 655 | +/* Carte de membre — encre + cerise */ | |
| 656 | +.pc { | |
| 657 | + position: relative; overflow: hidden; max-width: 460px; | |
| 658 | + background: var(--ink); color: var(--paper); | |
| 659 | + border: 1.5px solid var(--ink); border-radius: var(--r-card); | |
| 660 | + box-shadow: var(--shadow-off-soft); padding: 22px 26px 0; | |
| 661 | +} | |
| 662 | +.pc-watermark { | |
| 663 | + position: absolute; right: -18px; bottom: 14px; font-family: var(--font-display); | |
| 664 | + font-weight: 700; font-size: 150px; line-height: 1; letter-spacing: -0.06em; | |
| 665 | + color: rgba(245, 243, 238, 0.05); pointer-events: none; user-select: none; | |
| 666 | +} | |
| 667 | +.pc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; } | |
| 668 | +.pc-brand { font-family: var(--font-display); font-weight: 700; font-size: 24px; letter-spacing: -0.03em; } | |
| 669 | +.pc-ka { display: inline-block; background: var(--accent); color: var(--on-accent); border-radius: 6px; padding: 0 7px 2px; margin-left: 3px; transform: rotate(-2deg); } | |
| 670 | +.pc-label { font-family: var(--font-mono); font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.16em; color: rgba(245, 243, 238, 0.5); } | |
| 671 | +.pc-id-block { margin: 26px 0 22px; display: flex; flex-direction: column; gap: 4px; } | |
| 672 | +.pc-id-label { font-family: var(--font-mono); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.2em; color: var(--accent); } | |
| 673 | +.pc-id { font-family: var(--font-mono); font-weight: 700; font-size: clamp(17px, 4.4vw, 22px); letter-spacing: 0.04em; word-break: break-all; } | |
| 674 | +.pc-foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 14px; padding-bottom: 18px; } | |
| 675 | +.pc-holder { display: flex; flex-direction: column; gap: 3px; min-width: 0; } | |
| 676 | +.pc-holder-name { font-family: var(--font-display); font-weight: 600; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| 677 | +.pc-role-badge { align-self: flex-start; font-family: var(--font-mono); font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; background: var(--accent); color: var(--on-accent); border-radius: 999px; padding: 2px 9px; } | |
| 678 | +.pc-holder-since { font-family: var(--font-mono); font-size: 10.5px; color: rgba(245, 243, 238, 0.5); letter-spacing: 0.06em; } | |
| 679 | +.pc-avatar { width: 52px; height: 52px; border-radius: 50%; border: 2px solid var(--accent); flex: none; object-fit: cover; } | |
| 680 | +.pc-strip { display: flex; align-items: flex-end; gap: 5px; margin: 0 -26px; padding: 9px 18px; background: rgba(226, 55, 68, 0.14); border-top: 1px solid rgba(226, 55, 68, 0.4); overflow: hidden; } | |
| 681 | +.pc-strip i { display: block; width: 3px; border-radius: 1px; background: var(--accent); opacity: 0.75; } | |
| 682 | +.pc-strip i:nth-child(3n) { height: 14px; opacity: 0.4; } | |
| 683 | +.pc-strip i:nth-child(3n+1) { height: 9px; } | |
| 684 | +.pc-strip i:nth-child(3n+2) { height: 17px; opacity: 0.95; } | |
| 685 | +.pc-copy { margin-top: 16px; } | |
| 686 | +.pc-copy.ok { background: var(--ink); color: var(--accent); } | |
| 687 | + | |
| 688 | +.profil-grid { | |
| 689 | + display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); | |
| 690 | + gap: 12px; margin-top: 28px; max-width: 720px; | |
| 691 | +} | |
| 692 | +.pg-item { background: var(--surface); border: 1.5px solid var(--line-strong); border-radius: var(--r-ctl); padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; min-width: 0; } | |
| 693 | +.pg-label { font-family: var(--font-mono); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; color: var(--ink-3); } | |
| 694 | +.pg-value { font-weight: 600; font-size: 14.5px; word-break: break-word; } | |
| 695 | +.pg-value.mono { font-family: var(--font-mono); color: var(--accent-deep); } | |
| 696 | +.profil-note { max-width: 640px; color: var(--ink-2); font-size: 13.5px; margin-top: 22px; } | |
| 697 | +.profil-note a { text-decoration: underline; text-underline-offset: 3px; } | |
| 698 | +.profil-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 26px; } | |
| 699 | + | |
| 700 | +/* Profil hub Groupe KA (lecture seule) */ | |
| 701 | +.hub-profile { margin-top: 34px; padding-top: 24px; border-top: 1.5px solid var(--line-strong); max-width: 720px; display: flex; flex-direction: column; gap: 14px; } | |
| 702 | +.hub-profile h3 { font-size: 18px; } | |
| 703 | +.hub-profile .hub-bio { color: var(--ink-2); font-size: 14px; margin: 0; max-width: 560px; } | |
| 704 | +.hub-profile .pub-meta { display: flex; gap: 8px; flex-wrap: wrap; } | |
| 705 | +.hub-profile .stat-chip { display: inline-flex; align-items: center; gap: 6px; } | |
| 706 | +.hub-profile a.stat-chip:hover { background: var(--accent-soft); } | |
| 707 | +.hub-hint { color: var(--ink-3); font-size: 12.5px; margin: 0; } | |
| 708 | + | |
| 709 | +/* ================= Pages légales ================= */ | |
| 710 | +.legal { padding: 48px 0 80px; max-width: 780px; } | |
| 711 | +.legal h1 { font-size: clamp(28px, 4.6vw, 44px); margin: 8px 0 6px; } | |
| 712 | +.legal .legal-meta { color: var(--ink-3); font-family: var(--font-mono); font-size: 12px; margin: 0 0 26px; } | |
| 713 | +.legal h2 { font-size: 20px; margin: 30px 0 8px; border-left: 4px solid var(--accent); padding-left: 10px; } | |
| 714 | +.legal p, .legal li { color: var(--ink-2); font-size: 14.5px; } | |
| 715 | +.legal ul { padding-left: 20px; display: flex; flex-direction: column; gap: 6px; } | |
| 716 | +.legal a { text-decoration: underline; text-underline-offset: 3px; } | |
| 717 | +.legal b { color: var(--ink); } | |
| 718 | + | |
| 719 | +/* ================= Page Contact (écosystème Groupe KA) ================= */ | |
| 720 | +.contact { padding: 48px 0 90px; } | |
| 721 | +.contact h1 { font-size: clamp(30px, 5vw, 52px); margin: 12px 0 14px; text-transform: uppercase; letter-spacing: -0.03em; } | |
| 722 | +.contact-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 14px; margin: 28px 0 6px; max-width: 900px; } | |
| 723 | +.contact-card { | |
| 724 | + display: flex; flex-direction: column; gap: 8px; min-height: var(--touch, 44px); | |
| 725 | + background: var(--surface); border: 1.5px solid var(--ink); | |
| 726 | + border-radius: var(--r-card); padding: 18px 20px; | |
| 727 | + box-shadow: var(--shadow-off-soft); | |
| 728 | + transition: transform 0.15s ease, box-shadow 0.15s ease; | |
| 729 | +} | |
| 730 | +.contact-card:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off); } | |
| 731 | +.contact-role { font-family: var(--font-mono); font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--green); } | |
| 732 | +.contact-mail { font-family: var(--font-display); font-weight: 700; font-size: clamp(14px, 2vw, 17px); word-break: break-all; border-bottom: 2px solid var(--accent); align-self: flex-start; } | |
| 733 | +.contact-disclaimer { max-width: 720px; margin: 24px 0 0; padding: 14px 16px; border-left: 3px solid var(--accent); background: var(--accent-soft); color: var(--ink-2); font-size: 13.5px; } | |
| 734 | +.contact-disclaimer b { color: var(--ink); } | |
| 735 | +.contact-hub { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 26px; } | |
| 736 | +.contact-sites { margin-top: 46px; } | |
| 737 | +.contact-sites h2 { font-size: 21px; text-transform: uppercase; margin-bottom: 14px; } | |
| 738 | +.contact-sites ul { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 10px; } | |
| 739 | +.contact-sites a { display: flex; flex-direction: column; gap: 3px; min-height: var(--touch, 44px); background: var(--surface); border: 1.5px solid var(--line-strong); border-radius: var(--r-ctl); padding: 12px 14px; transition: transform 0.14s ease, box-shadow 0.14s ease; } | |
| 740 | +.contact-sites a:hover { transform: translate(-2px, -2px); box-shadow: 4px 4px 0 var(--ink); } | |
| 741 | +.contact-sites b { font-family: var(--font-display); font-size: 15px; } | |
| 742 | +.contact-sites span { font-size: 12px; color: var(--ink-3); } | |
| 743 | +@media (max-width: 480px) { | |
| 744 | + .header-inner { gap: 10px; } | |
| 745 | + .ka-login { font-size: 12px; padding: 6px 10px; gap: 4px; min-height: 44px; } | |
| 746 | +} | |
modified
frontend/tsconfig.json
+2 −1
@@ -16,5 +16,6 @@ | ||
| 16 | 16 | "noUnusedParameters": false, |
| 17 | 17 | "noFallthroughCasesInSwitch": true |
| 18 | 18 | }, |
| 19 | − "include": ["src"] | |
| 19 | + "include": ["src"], | |
| 20 | + "exclude": ["src/kamaps", "src/pages/Category.tsx"] | |
| 20 | 21 | } |
modified
immoka/seo.py
+5 −0
@@ -610,6 +610,11 @@ _STATIC_META = { | ||
| 610 | 610 | "/confidentialite": ("Politique de confidentialité", |
| 611 | 611 | "Politique de confidentialité d'Immo-Ka (Groupe-Ka) — Loi 25.", False), |
| 612 | 612 | "/profil": ("Mon profil", "Votre compte Groupe KA sur Immo-Ka.", True), |
| 613 | + "/contact": ("Contact — Groupe KA", | |
| 614 | + "Écrire au Groupe KA : contact@groupe-ka.com (projets et données), " | |
| 615 | + "info@groupe-ka.com (médias), admin@groupe-ka.com (légal et Loi 25). " | |
| 616 | + "Immo-Ka est un service Groupe KA — https://www.groupe-ka.com.", | |
| 617 | + False), | |
| 613 | 618 | } |
| 614 | 619 | |
| 615 | 620 | |
| 616 | 621 | |