SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%

Retour au design system commun Groupe KA « éditorial sharp » (accent orange conservé)

Lou-Ka abandonne l'identité marine/bleu du 2026-08-15 et revient au socle
partagé ka-ui (papier #f5f3ee, encre #141814, Space Grotesk/Inter/JetBrains
Mono, bordures encre 1,5 px + ombres décalées), en gardant son accent orange
(#ff6a00), son logo « porte entrouverte » et sa baseline.

- ka-ui vendoré : frontend/src/ka/ (tokens.css, ecosystem.json, KaFooter,
  GroupeKaBadge) ; tokens.css importé AVANT styles.css dans main.tsx
- styles.css : :root réécrit (accent orange + alias --lime, anciens jetons
  marine/bleu remappés sur les jetons communs), géométrie sharp 10/6 px,
  états sélectionnés encre+accent, couche d'harmonisation finale ; le thème
  Ka Maps passe à l'accent orange sur le chrome de la carte
- Header : badge « Un service Groupe KA » (desktop + menu mobile ≥44 px)
- Footer : KaFooter commun (siteId lou-ka, pages légales locales) précédé
  d'une rangée de navigation locale + « Gérer mes témoins »
- Nouvelle page /contact (courriels + rôles d'ecosystem.json, avertissement
  agrégateur, liste des sites, lien hub) ; route ajoutée au fallback SPA
- SEO : title « Lou-Ka — Un service Groupe KA » (index.html) ; titre SSR de
  l'accueil harmonisé + lien Groupe KA dans le corps SEO (seo.py)
- « Créer mon compte » pointe vers https://www.groupe-ka.com/connexion
- Polices Space Grotesk + JetBrains Mono ajoutées (Google Fonts)
- ka-maps redéployé sur le nœud (~/apps/ka-maps) pour le build

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 1 mo ago (Aug 18, 2026) parent c556557

14 changed files +731 −219

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>Lou-Ka — Logements à louer au Québec</title>
10 + <title>Lou-Ka — Un service Groupe KA</title>
11 11 <meta name="description" content="Lou-Ka agrège les appartements et logements à louer publiés par les gestionnaires immobiliers partout au Québec — Montréal, Québec, Lévis, Gatineau et plus — toujours à jour." />
12 − <meta name="theme-color" content="#ffffff" />
12 + <meta name="theme-color" content="#f5f3ee" />
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-status-bar-style" content="default" />
@@ -17,7 +17,7 @@
17 17 <link rel="manifest" href="/manifest.webmanifest" />
18 18 <link rel="preconnect" href="https://fonts.googleapis.com" />
19 19 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
20 − <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
20 + <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet" />
21 21 <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='14' fill='%230B1330'/%3E%3Crect x='14.6' y='5.9' width='34.8' height='52.2' rx='11' fill='none' stroke='%23FF6A00' stroke-width='5' stroke-linejoin='round'/%3E%3Cpath d='M19.8 12.9 L41.3 16.1 L39 45.6 L19.8 48.8 Z' fill='%23ffffff' stroke='%23ffffff' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='M22 48.2 L39.5 45.8 L45.5 55.9 L20 55.9 Z' fill='%23FF6A00'/%3E%3C/svg%3E" />
22 22 </head>
23 23 <body>
modified frontend/package-lock.json +0 −1
@@ -32,7 +32,6 @@
32 32 },
33 33 "devDependencies": {
34 34 "@types/react": "^19.0.0",
35 − "react": "^19.0.0",
36 35 "typescript": "^5.8.0",
37 36 "vitest": "^3.0.0"
38 37 },
modified frontend/src/App.tsx +30 −32
@@ -16,6 +16,9 @@ import {
16 16 IcoMap, IcoSearch, IcoUser,
17 17 } from "./components/Icons";
18 18 import { LogoIcon } from "./components/Logo";
19 +import GroupeKaBadge from "./ka/GroupeKaBadge";
20 +import KaFooter from "./ka/KaFooter";
21 +import ContactPage from "./pages/Contact";
19 22 import Home from "./pages/Home";
20 23 import ListingPage from "./pages/Listing";
21 24 import BotPage from "./pages/Bot";
@@ -73,6 +76,7 @@ const NAV_LINKS = [
73 76 { to: "/?view=carte", label: "Carte", icon: <IcoMap size={17} />, end: false, force: true },
74 77 { to: "/stats", label: "Stats", icon: <IcoChart size={17} />, end: false },
75 78 { to: "/sources", label: "Sources", icon: <IcoFolder size={17} />, end: false },
79 + { to: "/contact", label: "Contact", icon: <IcoUser size={17} />, end: false },
76 80 { to: "/conditions", label: "Conditions", icon: <IcoDoc size={17} />, end: false },
77 81 { to: "/confidentialite", label: "Confidentialité", icon: <IcoLock size={17} />, end: false },
78 82 ];
@@ -204,6 +208,7 @@ function Header() {
204 208 Lou-<span className="ka">Ka</span>
205 209 <span className="brand-tag">La porte d'entrée vers votre prochain chez-vous.</span>
206 210 </NavLink>
211 + <GroupeKaBadge />
207 212 <nav className="nav" aria-label="Navigation principale">
208 213 <NavLink to="/" end className={({ isActive }) => (isActive ? "active" : "")}>
209 214 Logements
@@ -243,6 +248,9 @@ function Header() {
243 248 <span className="mm-arrow" aria-hidden="true">→</span>
244 249 </NavLink>
245 250 ))}
251 + <div className="mm-badge">
252 + <GroupeKaBadge />
253 + </div>
246 254 <div className="mm-foot">
247 255 Agrégateur indépendant — mis à jour automatiquement, chaque fiche
248 256 renvoie à l'annonce originale.
@@ -255,39 +263,21 @@ function Header() {
255 263 );
256 264 }
257 265
266 +/** Pied de page : rangée de navigation locale + footer commun Groupe KA. */
258 267 function Footer() {
259 268 return (
260 − <footer className="footer">
261 − <div className="container">
262 − <div className="fbrand">
263 − <LogoIcon size={32} dark />
264 − Lou-<span className="ka">Ka</span>
265 − </div>
266 − <div className="fbaseline">
267 − La porte d'entrée vers votre prochain <span className="hl">chez-vous.</span>
268 − </div>
269 − <div className="frow">
270 − <div>
271 − <b>Agrégateur indépendant</b> de logements à louer dans la province de Québec.
272 − Les annonces proviennent des sites publics des gestionnaires immobiliers et sont
273 − rafraîchies automatiquement — chaque fiche renvoie vers l'annonce originale.
274 − </div>
275 − <div>
276 − Lou-Ka est une plateforme du{" "}
277 − <a href="https://www.groupe-ka.com" target="_blank" rel="noopener noreferrer">
278 − <b>Groupe KA</b>
279 − </a>{" "}
280 − — contact :{" "}
281 − <a href="mailto:contact@groupe-ka.com">contact@groupe-ka.com</a>
282 − </div>
283 − </div>
284 − <div className="fmono">
285 − © {new Date().getFullYear()} Groupe KA — Lou-Ka
286 − {" · "}
287 − <NavLink to="/conditions">Conditions d'utilisation</NavLink>
288 − {" · "}
289 − <NavLink to="/confidentialite">Confidentialité</NavLink>
290 − {" · "}
269 + <>
270 + <div className="prefooter">
271 + <div className="container">
272 + <span className="baseline">
273 + <b>Lou-Ka</b> — La porte d'entrée vers votre prochain chez-vous.
274 + Agrégateur indépendant : chaque fiche renvoie vers l'annonce originale.
275 + </span>
276 + <NavLink to="/">Logements</NavLink>
277 + <NavLink to="/villes">Villes</NavLink>
278 + <NavLink to="/stats">Stats</NavLink>
279 + <NavLink to="/sources">Sources</NavLink>
280 + <NavLink to="/contact">Contact</NavLink>
291 281 <button
292 282 className="flink"
293 283 onClick={() => window.dispatchEvent(new Event("louka:openConsent"))}
@@ -296,7 +286,14 @@ function Footer() {
296 286 </button>
297 287 </div>
298 288 </div>
299 − </footer>
289 + <KaFooter
290 + siteId="lou-ka"
291 + localLegal={[
292 + { label: "Conditions d'utilisation (Lou-Ka)", href: "/conditions" },
293 + { label: "Confidentialité (Lou-Ka)", href: "/confidentialite" },
294 + ]}
295 + />
296 + </>
300 297 );
301 298 }
302 299
@@ -335,6 +332,7 @@ export default function App() {
335 332 <Route path="/ville/:ville/:type" element={<VillePage />} />
336 333 <Route path="/stats" element={<StatsPage />} />
337 334 <Route path="/sources" element={<SourcesPage />} />
335 + <Route path="/contact" element={<ContactPage />} />
338 336 <Route path="/confidentialite" element={<PrivacyPage />} />
339 337 <Route path="/conditions" element={<TermsPage />} />
340 338 <Route path="/profil" element={<ProfilePage />} />
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 +1 −0
@@ -7,6 +7,7 @@ import React from "react";
7 7 import ReactDOM from "react-dom/client";
8 8 import { BrowserRouter } from "react-router-dom";
9 9 import App from "./App";
10 +import "./ka/tokens.css";
10 11 import "./styles.css";
11 12
12 13 ReactDOM.createRoot(document.getElementById("root")!).render(
added frontend/src/pages/Contact.tsx +64 −0
@@ -0,0 +1,64 @@
1 +// -----------------------------------------------------------------------------
2 +// Lou-Ka — Agrégateur de logements à louer (province de Québec)
3 +// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +// pages/Contact.tsx : page Contact commune Groupe KA (données ecosystem.json)
5 +// -----------------------------------------------------------------------------
6 +import { useEffect } from "react";
7 +import eco from "../ka/ecosystem.json";
8 +
9 +export default function ContactPage() {
10 + useEffect(() => {
11 + document.title = "Contact | Lou-Ka — Un service Groupe KA";
12 + window.scrollTo(0, 0);
13 + }, []);
14 +
15 + const others = eco.sites.filter((s) => s.id !== "lou-ka");
16 +
17 + return (
18 + <div className="container contact-page">
19 + <span className="kicker">Contact</span>
20 + <h1>
21 + Écrire au <span className="hl">Groupe KA</span>
22 + </h1>
23 + <p className="lede">
24 + Lou-Ka — {eco.sites.find((s) => s.id === "lou-ka")?.tagline?.toLowerCase()} —
25 + est un service du {eco.org.name}. {eco.org.tagline} Toutes les demandes
26 + passent par les adresses ci-dessous.
27 + </p>
28 +
29 + <div className="contact-grid">
30 + {eco.contacts.map((c) => (
31 + <div className="card contact-card" key={c.email}>
32 + <span className="klabel">{c.role}</span>
33 + <a className="mail" href={`mailto:${c.email}`}>{c.email}</a>
34 + </div>
35 + ))}
36 + </div>
37 +
38 + <p className="contact-note">
39 + <b>{eco.org.disclaimer}</b> Les annonces affichées sur Lou-Ka proviennent
40 + des sites publics des gestionnaires immobiliers ; chaque fiche renvoie
41 + vers l'annonce originale. Pour toute question sur un logement, contactez
42 + directement le gestionnaire indiqué sur la fiche.
43 + </p>
44 +
45 + <div className="contact-hub">
46 + <span className="klabel">L'écosystème ·Ka</span>
47 + <ul className="contact-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 + </ul>
56 + <p style={{ marginTop: 18 }}>
57 + <a className="btn btn-primary" href={eco.hub.url} target="_blank" rel="noopener noreferrer">
58 + Visiter le portail groupe-ka.com →
59 + </a>
60 + </p>
61 + </div>
62 + </div>
63 + );
64 +}
modified frontend/src/pages/PublicProfile.tsx +1 −1
@@ -159,7 +159,7 @@ export default function PublicProfilePage() {
159 159
160 160 <div className="profil-actions">
161 161 <Link className="btn btn-primary" to="/">Explorer les logements</Link>
162 − <a className="btn btn-ghost" href="/api/auth/ka/login">Créer mon compte</a>
162 + <a className="btn btn-ghost" href="https://www.groupe-ka.com/connexion" target="_blank" rel="noopener noreferrer">Créer mon compte</a>
163 163 </div>
164 164 </div>
165 165 );
modified frontend/src/styles.css +240 −178
@@ -1,95 +1,43 @@
1 1 /* -----------------------------------------------------------------------------
2 2 Lou-Ka — Agrégateur de logements à louer (province de Québec)
3 3 Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 − styles.css : identité visuelle officielle Lou-Ka
5 − · Palette : marine #0B1330 · bleu vif #2258FF · orange #FF6A00
6 − gris pâle #F2F2F2 · blanc #FFFFFF
7 − · Typo : Inter (fallback SF Pro / system-ui)
8 − · Style : minimaliste chaleureux — coins très arrondis, pill-shaped,
9 − ombres douces diffuses, touches « liquid glass »
4 + styles.css : styles LOCAUX de Lou-Ka, posés SUR le design system commun
5 + Groupe KA « éditorial sharp » (frontend/src/ka/tokens.css, importé AVANT
6 + ce fichier dans main.tsx).
7 + · Palette commune : papier #f5f3ee · encre #141814 · vert profond #1c5c41
8 + · ACCENT Lou-Ka : orange #FF6A00 (seule surcharge de tokens.css)
9 + · Typo : display Space Grotesk / texte Inter / micro-étiquettes JetBrains Mono
10 + · Signature : bordures encre 1,5 px + ombres décalées dures, rayons 10/6 px
10 11 · Baseline : « La porte d'entrée vers votre prochain chez-vous. »
11 12 · 100 % adaptatif mobile (PWA installable, safe-areas iOS)
12 13 ----------------------------------------------------------------------------- */
13 14 :root {
14 − /* palette officielle */
15 − --navy: #0b1330;
16 − --blue: #2258ff;
17 − --orange: #ff6a00;
18 − --grey: #f2f2f2;
15 + /* ---- ACCENT Lou-Ka (surcharge officielle des jetons ka-ui) ---- */
16 + --accent: #ff6a00;
17 + --accent-soft: #fff1e6;
18 + --accent-deep: #cc5500;
19 + --on-accent: #ffffff;
20 + --lime: var(--accent);
21 + --lime-soft: var(--accent-soft);
22 +
23 + /* ---- Alias hérités de l'ancienne identité marine/bleu — remappés sur
24 + les jetons communs (le fond redevient papier via tokens.css) ---- */
25 + --navy: var(--ink);
26 + --navy-soft: var(--surface-2);
27 + --blue: var(--green);
28 + --blue-soft: #e8f0ea;
29 + --orange: var(--accent);
30 + --orange-soft: var(--accent-soft);
31 + --orange-deep: var(--accent-deep);
32 + --grey: var(--surface-2);
19 33 --white: #ffffff;
20 −
21 − /* dérivés (nuances des couleurs officielles) */
22 − --orange-deep: #e55f00;
23 − --orange-soft: #fff1e6;
24 − --blue-soft: #e9efff;
25 − --navy-soft: #eef0f6;
26 −
27 − /* jetons sémantiques */
28 − --paper: var(--white);
29 − --surface: var(--white);
30 − --surface-2: var(--grey);
31 − --ink: var(--navy);
32 − --ink-2: #3e4560;
33 − --ink-3: #8a90a5;
34 − --line: rgba(11, 19, 48, 0.1);
35 − --line-strong: rgba(11, 19, 48, 0.16);
36 − --danger: #d64545;
37 −
38 − /* alias hérités (compatibilité) */
39 − --green: var(--blue);
40 − --green-deep: var(--navy);
41 − --lime: var(--orange);
42 − --lime-soft: var(--orange-soft);
43 − --amber: var(--orange);
44 − --amber-soft: var(--orange-soft);
45 −
46 − /* géométrie : coins très arrondis, contrôles pill */
47 − --r-card: 18px;
48 − --r-ctl: 12px;
49 − --r-pill: 999px;
50 −
51 − /* ombres douces et diffuses, jamais dures */
52 − --shadow-flat: 0 1px 3px rgba(11, 19, 48, 0.06);
53 − --shadow-off: 0 16px 40px rgba(11, 19, 48, 0.14);
54 − --shadow-off-soft: 0 8px 24px rgba(11, 19, 48, 0.08);
55 −
56 − --font-display: "Inter", "SF Pro Display", system-ui, sans-serif;
57 − --font-body: "Inter", "SF Pro Text", system-ui, sans-serif;
58 − --font-mono: "Inter", "SF Pro Text", system-ui, sans-serif;
59 −}
60 −
61 −* { box-sizing: border-box; }
62 −html { scroll-behavior: smooth; }
63 −body {
64 − margin: 0;
65 − background: var(--paper);
66 − color: var(--ink);
67 − font-family: var(--font-body);
68 − font-size: 15px;
69 − line-height: 1.55;
70 − -webkit-font-smoothing: antialiased;
71 − padding-bottom: env(safe-area-inset-bottom);
72 34 }
73 −#root { position: relative; z-index: 1; }
74 35
75 −h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.02em; margin: 0; }
76 −a { color: inherit; text-decoration: none; }
36 +#root { position: relative; z-index: 1; }
37 +a { text-decoration: none; }
77 38 button { font-family: inherit; }
78 −img { display: block; }
79 −::selection { background: var(--orange); color: var(--white); }
80 39
81 40 .mono { font-family: var(--font-mono); }
82 −/* mot-clé accentué des titres — toujours orange (« chez-vous. ») */
83 −h1 .hl, h2 .hl { color: var(--orange); }
84 −.kicker {
85 − font-family: var(--font-body); font-size: 12px; font-weight: 600;
86 − text-transform: uppercase; letter-spacing: 0.12em; color: var(--blue);
87 − display: inline-flex; align-items: center; gap: 8px;
88 −}
89 −.kicker::before { content: ""; width: 22px; height: 3px; border-radius: 2px; background: var(--orange); }
90 −
91 −.container { max-width: 1240px; margin: 0 auto; padding: 0 24px; }
92 −@media (max-width: 640px) { .container { padding: 0 16px; } }
93 41
94 42 /* ================= Header ================= */
95 43 .header {
@@ -143,11 +91,10 @@ h1 .hl, h2 .hl { color: var(--orange); }
143 91 letter-spacing: -0.03em; max-width: 900px; margin-top: 14px; color: var(--navy);
144 92 }
145 93 .hero h1 .outline { color: var(--navy); }
146 −.hero h1 .hl { color: var(--orange); }
147 94 .hero p.lede { color: var(--ink-2); font-size: 17px; max-width: 640px; margin: 18px 0 0; }
148 95 .stat-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 26px; }
149 96 .stat-chip {
150 − background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-pill);
97 + background: var(--surface); border: 1.5px solid var(--ink); border-radius: var(--r-pill);
151 98 padding: 8px 16px; font-size: 12.5px; font-weight: 500;
152 99 color: var(--ink-2); display: flex; gap: 8px; align-items: center;
153 100 box-shadow: var(--shadow-flat);
@@ -181,7 +128,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
181 128
182 129 /* ================= Filter bar ================= */
183 130 .filterbar {
184 − background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card);
131 + background: var(--surface); border: 1.5px solid var(--ink); border-radius: var(--r-card);
185 132 box-shadow: var(--shadow-off-soft); padding: 14px; margin: 30px 0 6px;
186 133 display: flex; flex-direction: column; gap: 0; min-width: 0;
187 134 }
@@ -197,7 +144,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
197 144 }
198 145 .f-search:focus-within {
199 146 background: var(--white); border-color: var(--blue);
200 − box-shadow: 0 0 0 3px rgba(34, 88, 255, 0.15);
147 + box-shadow: 0 0 0 3px rgba(28, 92, 65, 0.15);
201 148 }
202 149 .f-search input {
203 150 border: none; background: none; outline: none; flex: 1; min-width: 0;
@@ -210,11 +157,11 @@ h1 .hl, h2 .hl { color: var(--orange); }
210 157 }
211 158 .f-ctl {
212 159 flex: 0 1 auto; min-width: 0; display: flex; flex-direction: column; justify-content: center;
213 − gap: 2px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface);
160 + gap: 2px; border: 1.5px solid var(--ink); border-radius: 14px; background: var(--surface);
214 161 padding: 7px 14px 6px; min-height: 52px; cursor: pointer;
215 162 transition: box-shadow 0.15s ease, border-color 0.15s ease;
216 163 }
217 −.f-ctl:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(34, 88, 255, 0.15); }
164 +.f-ctl:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(28, 92, 65, 0.15); }
218 165 .f-ctl > span {
219 166 font-size: 9.5px; font-weight: 700;
220 167 text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3);
@@ -224,14 +171,14 @@ h1 .hl, h2 .hl { color: var(--orange); }
224 171 font-weight: 700; font-size: 14.5px; color: var(--navy); cursor: pointer;
225 172 appearance: none; -webkit-appearance: none; padding-right: 16px; min-width: 0; max-width: 170px;
226 173 text-overflow: ellipsis;
227 − background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='5'%3E%3Cpath d='M0 0l4.5 5L9 0z' fill='%230B1330'/%3E%3C/svg%3E");
174 + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='5'%3E%3Cpath d='M0 0l4.5 5L9 0z' fill='%23141814'/%3E%3C/svg%3E");
228 175 background-repeat: no-repeat; background-position: right center;
229 176 }
230 177 .range-pair { display: flex; align-items: center; gap: 4px; }
231 178 .range-pair select { max-width: 86px; }
232 179 .range-sep { color: var(--ink-3); font-size: 11px; }
233 180 .f-more {
234 − flex: none; align-self: stretch; border: 1px solid var(--line-strong); border-radius: var(--r-pill);
181 + flex: none; align-self: stretch; border: 1.5px solid var(--ink); border-radius: var(--r-pill);
235 182 background: var(--surface); color: var(--navy); padding: 0 20px; cursor: pointer;
236 183 font-family: var(--font-display); font-weight: 600; font-size: 14px; min-height: 52px;
237 184 transition: all 0.13s ease;
@@ -254,19 +201,19 @@ h1 .hl, h2 .hl { color: var(--orange); }
254 201 .f-group-end { justify-content: flex-end; }
255 202 .f-group .btn:disabled { opacity: 0.4; cursor: default; }
256 203 .f-native {
257 − border: 1px solid var(--line); background: var(--grey); border-radius: var(--r-ctl);
204 + border: 1.5px solid var(--ink); background: var(--grey); border-radius: var(--r-ctl);
258 205 padding: 10px 30px 10px 14px; font-size: 14px; color: var(--ink); outline: none;
259 206 font-family: inherit; min-height: 42px; width: 100%; min-width: 0;
260 207 appearance: none; -webkit-appearance: none;
261 − background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%230B1330'/%3E%3C/svg%3E");
208 + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23141814'/%3E%3C/svg%3E");
262 209 background-repeat: no-repeat; background-position: right 12px center;
263 210 }
264 −.f-native:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(34, 88, 255, 0.15); background: var(--white); }
211 +.f-native:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(28, 92, 65, 0.15); background: var(--white); }
265 212
266 213 /* segments (pilules soudées) */
267 214 .seg { display: inline-flex; flex-wrap: wrap; row-gap: 6px; }
268 215 .seg button {
269 − border: 1px solid var(--line-strong); background: var(--surface); color: var(--ink-2);
216 + border: 1.5px solid var(--ink); background: var(--surface); color: var(--ink-2);
270 217 padding: 8px 14px; font-family: var(--font-display); font-weight: 600; font-size: 13px;
271 218 cursor: pointer; margin-left: -1px; white-space: nowrap; min-height: 38px;
272 219 transition: all 0.12s ease;
@@ -280,7 +227,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
280 227 .pills { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 2px; }
281 228 .pill {
282 229 display: inline-flex; align-items: center; gap: 7px;
283 − border: 1px solid rgba(34, 88, 255, 0.3); background: var(--blue-soft); color: var(--blue);
230 + border: 1px solid rgba(28, 92, 65, 0.3); background: var(--blue-soft); color: var(--blue);
284 231 border-radius: var(--r-pill); padding: 6px 14px; font-size: 12.5px; font-weight: 600;
285 232 cursor: pointer; transition: all 0.13s ease;
286 233 }
@@ -299,45 +246,32 @@ h1 .hl, h2 .hl { color: var(--orange); }
299 246 text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3); padding-left: 2px;
300 247 }
301 248 .field input, .field select {
302 − border: 1px solid var(--line); background: var(--grey); border-radius: var(--r-ctl);
249 + border: 1.5px solid var(--ink); background: var(--grey); border-radius: var(--r-ctl);
303 250 padding: 11px 14px; font-size: 15px; color: var(--ink); outline: none; font-family: inherit;
304 251 transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; min-height: 44px;
305 252 appearance: none; -webkit-appearance: none;
306 253 }
307 254 .field select {
308 − background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%230B1330'/%3E%3C/svg%3E");
255 + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23141814'/%3E%3C/svg%3E");
309 256 background-repeat: no-repeat; background-position: right 12px center; padding-right: 30px;
310 257 }
311 258 .field input:focus, .field select:focus {
312 259 border-color: var(--blue); background: var(--white);
313 − box-shadow: 0 0 0 3px rgba(34, 88, 255, 0.15);
260 + box-shadow: 0 0 0 3px rgba(28, 92, 65, 0.15);
314 261 }
315 −.btn {
316 − border: 1px solid transparent; border-radius: var(--r-pill); padding: 11px 22px;
317 − font-weight: 600; font-size: 14px; cursor: pointer; min-height: 44px;
318 − transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.15s ease;
319 − font-family: var(--font-display); letter-spacing: 0.01em;
320 −}
321 −.btn:active { transform: translateY(1px); }
322 −.btn-primary {
323 − background: var(--orange); color: var(--white);
324 − box-shadow: 0 6px 18px rgba(255, 106, 0, 0.32);
325 −}
326 −.btn-primary:hover { background: var(--orange-deep); }
327 −.btn-ghost { background: transparent; color: var(--navy); border-color: var(--line-strong); }
328 −.btn-ghost:hover { background: var(--navy-soft); border-color: var(--navy); }
262 +/* .btn / .btn-primary / .btn-ghost : fournis par tokens.css (ka-ui). */
329 263
330 264 /* ================= Chips ================= */
331 265 .chips { display: flex; gap: 8px; margin: 16px 0 4px; overflow-x: auto; padding-bottom: 6px; scrollbar-width: none; }
332 266 .chips::-webkit-scrollbar { display: none; }
333 267 .chip {
334 − border: 1px solid var(--line-strong); background: var(--surface); color: var(--navy);
335 − border-radius: var(--r-pill); padding: 8px 18px; font-size: 13.5px; font-weight: 600; cursor: pointer;
268 + border: 1.5px solid var(--ink); background: var(--surface); color: var(--ink);
269 + border-radius: var(--r-pill); padding: 8px 18px; font-size: 13px; font-weight: 700; cursor: pointer;
336 270 font-family: var(--font-display); white-space: nowrap; min-height: 40px;
337 − transition: all 0.13s ease;
271 + transition: transform 0.13s, box-shadow 0.13s, background 0.13s, color 0.13s;
338 272 }
339 −.chip:hover { background: var(--blue-soft); border-color: var(--blue); color: var(--blue); transform: translateY(-1px); }
340 −.chip.on { background: var(--blue); border-color: var(--blue); color: var(--white); box-shadow: 0 6px 16px rgba(34, 88, 255, 0.28); }
273 +.chip:hover { transform: translate(-1px, -1px); box-shadow: var(--shadow-off-mid); }
274 +.chip.on { background: var(--ink); border-color: var(--ink); color: var(--accent); box-shadow: 3px 3px 0 var(--accent); }
341 275
342 276 /* ================= Results ================= */
343 277 .results-head { display: flex; align-items: baseline; gap: 14px; margin: 28px 0 18px; }
@@ -350,12 +284,12 @@ h1 .hl, h2 .hl { color: var(--orange); }
350 284 @media (max-width: 640px) { .grid { grid-template-columns: 1fr; gap: 16px; padding-bottom: 48px; } }
351 285
352 286 .card {
353 − background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card);
287 + background: var(--surface); border: 1.5px solid var(--ink); border-radius: var(--r-card);
354 288 overflow: hidden; display: flex; flex-direction: column; box-shadow: var(--shadow-flat);
355 289 transition: transform 0.16s ease, box-shadow 0.16s ease;
356 290 }
357 291 .card:hover { transform: translateY(-4px); box-shadow: var(--shadow-off); }
358 −.card:focus-visible { outline: 3px solid rgba(34, 88, 255, 0.4); outline-offset: 2px; }
292 +.card:focus-visible { outline: 3px solid rgba(28, 92, 65, 0.4); outline-offset: 2px; }
359 293 .card-img { position: relative; aspect-ratio: 16/10.5; background: var(--grey); overflow: hidden; }
360 294 .card-img img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.4s ease; }
361 295 .card:hover .card-img img { transform: scale(1.05); }
@@ -385,7 +319,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
385 319
386 320 /* ================= Skeletons ================= */
387 321 @keyframes shimmer { 0% { background-position: -400px 0; } 100% { background-position: 400px 0; } }
388 −.skel { border-radius: var(--r-card); border: 1px solid var(--line); overflow: hidden; background: var(--surface); }
322 +.skel { border-radius: var(--r-card); border: 1.5px solid var(--ink); overflow: hidden; background: var(--surface); }
389 323 .skel .sk-img, .skel .sk-line {
390 324 background: linear-gradient(90deg, #ececef 25%, #f7f7f9 50%, #ececef 75%);
391 325 background-size: 800px 100%; animation: shimmer 1.4s infinite linear;
@@ -415,7 +349,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
415 349
416 350 .gallery { display: flex; flex-direction: column; gap: 10px; }
417 351 .gallery-main {
418 − border-radius: var(--r-card); overflow: hidden; border: 1px solid var(--line);
352 + border-radius: var(--r-card); overflow: hidden; border: 1.5px solid var(--ink);
419 353 aspect-ratio: 16/10; background: var(--grey); cursor: zoom-in; box-shadow: var(--shadow-off-soft);
420 354 }
421 355 .gallery-main img { width: 100%; height: 100%; object-fit: cover; }
@@ -429,7 +363,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
429 363 .thumbs img { width: 100%; height: 100%; object-fit: cover; }
430 364
431 365 .panel {
432 − background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card);
366 + background: var(--surface); border: 1.5px solid var(--ink); border-radius: var(--r-card);
433 367 box-shadow: var(--shadow-off-soft); padding: 26px; position: sticky; top: 120px;
434 368 }
435 369 @media (max-width: 900px) { .panel { position: static; } }
@@ -442,11 +376,12 @@ h1 .hl, h2 .hl { color: var(--orange); }
442 376 .kv .cell { background: var(--grey); border-radius: var(--r-ctl); padding: 10px 14px; }
443 377 .kv .cell .k { font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3); font-weight: 700; }
444 378 .kv .cell .v { font-weight: 700; font-size: 14.5px; margin-top: 2px; font-family: var(--font-display); color: var(--navy); }
445 −.klabel { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3); font-weight: 700; margin-bottom: 8px; }
379 +/* .klabel : fourni par tokens.css (ka-ui). */
380 +.panel .klabel, .f-bloc .klabel { display: block; margin-bottom: 8px; }
446 381 .amenity-row { display: flex; flex-wrap: wrap; gap: 7px; margin: 0 0 20px; }
447 382 .amenity {
448 383 background: var(--blue-soft); color: var(--blue); font-size: 12px; font-weight: 600;
449 − border: 1px solid rgba(34, 88, 255, 0.25); border-radius: var(--r-pill); padding: 5px 13px;
384 + border: 1px solid rgba(28, 92, 65, 0.25); border-radius: var(--r-pill); padding: 5px 13px;
450 385 }
451 386 .cta {
452 387 display: block; text-align: center; background: var(--orange); color: var(--white);
@@ -460,7 +395,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
460 395
461 396 /* ================= Lightbox ================= */
462 397 .lightbox {
463 − position: fixed; inset: 0; background: rgba(11, 19, 48, 0.94); z-index: 100;
398 + position: fixed; inset: 0; background: rgba(20, 24, 20, 0.94); z-index: 100;
464 399 display: flex; align-items: center; justify-content: center; cursor: zoom-out;
465 400 padding: max(16px, env(safe-area-inset-top)) 16px;
466 401 }
@@ -470,7 +405,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
470 405 .sources { padding: 44px 0 90px; }
471 406 .sources h1 { font-size: clamp(28px, 4vw, 40px); margin: 10px 0 6px; color: var(--navy); }
472 407 .sources .sub { color: var(--ink-2); margin-bottom: 30px; max-width: 700px; }
473 −.src-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r-card); box-shadow: var(--shadow-off-soft); background: var(--surface); }
408 +.src-wrap { overflow-x: auto; border: 1.5px solid var(--ink); border-radius: var(--r-card); box-shadow: var(--shadow-off-soft); background: var(--surface); }
474 409 .src-table { width: 100%; border-collapse: separate; border-spacing: 0; min-width: 720px; }
475 410 .src-table th {
476 411 text-align: left; font-size: 10.5px; text-transform: uppercase;
@@ -480,7 +415,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
480 415 .src-table td { padding: 13px 18px; border-bottom: 1px solid var(--line); font-size: 14px; vertical-align: top; }
481 416 .src-table tr:last-child td { border-bottom: none; }
482 417 .src-table tr:hover td { background: var(--navy-soft); }
483 −.src-table a { color: var(--blue); font-weight: 600; border-bottom: 1.5px solid rgba(34, 88, 255, 0.35); }
418 +.src-table a { color: var(--blue); font-weight: 600; border-bottom: 1.5px solid rgba(28, 92, 65, 0.35); }
484 419 .pill { display: inline-block; border-radius: var(--r-pill); padding: 3px 10px; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
485 420 .pill.ok { background: var(--blue-soft); color: var(--blue); }
486 421 .pill.todo { background: var(--orange-soft); color: var(--orange-deep); }
@@ -493,7 +428,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
493 428
494 429 .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; margin-bottom: 30px; }
495 430 .tile {
496 − background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card);
431 + background: var(--surface); border: 1.5px solid var(--ink); border-radius: var(--r-card);
497 432 padding: 18px 20px; box-shadow: var(--shadow-flat);
498 433 }
499 434 .tile-v { font-family: var(--font-display); font-weight: 700; font-size: clamp(24px, 3vw, 34px); letter-spacing: -0.03em; line-height: 1.05; color: var(--navy); }
@@ -503,7 +438,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
503 438 .hero-tile .tile-k { color: rgba(255, 255, 255, 0.6); }
504 439
505 440 .viz-card {
506 − background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card);
441 + background: var(--surface); border: 1.5px solid var(--ink); border-radius: var(--r-card);
507 442 box-shadow: var(--shadow-off-soft); padding: 26px 28px 20px; margin-bottom: 22px;
508 443 }
509 444 .viz-card h2 { font-size: 19px; letter-spacing: -0.01em; color: var(--navy); }
@@ -529,7 +464,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
529 464 gap: 12px; align-items: center; min-height: 26px; cursor: default;
530 465 }
531 466 .hbar-label { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
532 −.hbar-label a { border-bottom: 1.5px solid rgba(34, 88, 255, 0.35); }
467 +.hbar-label a { border-bottom: 1.5px solid rgba(28, 92, 65, 0.35); }
533 468 .hbar-label a:hover { color: var(--blue); }
534 469 .hbar-track { background: var(--grey); border-radius: var(--r-pill); height: 18px; overflow: hidden; }
535 470 .hbar-fill {
@@ -551,7 +486,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
551 486 position: fixed; z-index: 120; pointer-events: none; max-width: 180px;
552 487 background: var(--navy); color: var(--white); border-radius: 12px;
553 488 padding: 9px 12px; font-size: 12px; line-height: 1.5;
554 − box-shadow: 0 8px 24px rgba(11, 19, 48, 0.35);
489 + box-shadow: 0 8px 24px rgba(20, 24, 20, 0.35);
555 490 }
556 491 .viz-tip-title { font-family: var(--font-display); font-weight: 700; color: var(--orange); margin-bottom: 2px; }
557 492
@@ -601,7 +536,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
601 536 margin: 0; border-radius: 24px 24px 0 0; border: none;
602 537 max-height: 82dvh; overflow-y: auto; -webkit-overflow-scrolling: touch;
603 538 padding: 16px 18px calc(18px + env(safe-area-inset-bottom));
604 − box-shadow: 0 -16px 48px rgba(11, 19, 48, 0.28);
539 + box-shadow: 0 -16px 48px rgba(20, 24, 20, 0.28);
605 540 animation: sheet-up 0.22s ease;
606 541 }
607 542 @keyframes sheet-up { from { transform: translateY(30%); opacity: 0.4; } to { transform: none; opacity: 1; } }
@@ -613,13 +548,13 @@ h1 .hl, h2 .hl { color: var(--orange); }
613 548 border-bottom: 1px solid var(--line); margin-bottom: 2px; z-index: 1;
614 549 }
615 550 .sheet-close {
616 − border: 1px solid var(--line-strong); background: var(--surface); border-radius: 50%;
551 + border: 1.5px solid var(--ink); background: var(--surface); border-radius: 50%;
617 552 width: 36px; height: 36px; font-size: 15px; cursor: pointer; line-height: 1; color: var(--navy);
618 553 }
619 554 .filterbar.open .sheet-apply { display: block; width: 100%; }
620 555 .sheet-backdrop {
621 556 display: block; position: fixed; inset: 0; z-index: 90;
622 − background: rgba(11, 19, 48, 0.45); backdrop-filter: blur(2px);
557 + background: rgba(20, 24, 20, 0.45); backdrop-filter: blur(2px);
623 558 }
624 559
625 560 /* Bouton flottant */
@@ -688,7 +623,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
688 623 /* --- Carte interactive (mission 4) --------------------------------------- */
689 624 .results-tools { display: flex; align-items: center; gap: 14px; }
690 625 .view-toggle {
691 − display: inline-flex; border: 1px solid var(--line-strong);
626 + display: inline-flex; border: 1.5px solid var(--ink);
692 627 border-radius: var(--r-pill); overflow: hidden; background: var(--surface);
693 628 box-shadow: var(--shadow-flat);
694 629 }
@@ -709,7 +644,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
709 644 }
710 645 .map-list .card { margin: 0; flex: 0 0 auto; }
711 646 .mapview {
712 − width: 100%; height: 100%; border: 1px solid var(--line);
647 + width: 100%; height: 100%; border: 1.5px solid var(--ink);
713 648 border-radius: var(--r-card); box-shadow: var(--shadow-off-soft);
714 649 overflow: hidden; background: var(--grey);
715 650 }
@@ -728,12 +663,12 @@ h1 .hl, h2 .hl { color: var(--orange); }
728 663 /* popup MapLibre au style Lou-Ka */
729 664 .maplibregl-popup-content {
730 665 padding: 0; border-radius: var(--r-card); overflow: hidden;
731 − border: 1px solid var(--line); box-shadow: var(--shadow-off);
666 + border: 1.5px solid var(--ink); box-shadow: var(--shadow-off);
732 667 font-family: var(--font-body);
733 668 }
734 669 .maplibregl-popup-close-button {
735 670 font-size: 18px; padding: 2px 8px; color: var(--white); z-index: 2;
736 − text-shadow: 0 0 4px rgba(11, 19, 48, 0.8);
671 + text-shadow: 0 0 4px rgba(20, 24, 20, 0.8);
737 672 }
738 673 .mv-pop img, .mv-noimg { width: 100%; height: 130px; object-fit: cover; }
739 674 .mv-noimg { display: flex; align-items: center; justify-content: center;
@@ -772,7 +707,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
772 707 .menu-btn {
773 708 display: none; position: relative; z-index: 60;
774 709 width: 44px; height: 44px; margin-left: auto;
775 − border: 1px solid var(--line-strong); border-radius: var(--r-ctl);
710 + border: 1.5px solid var(--ink); border-radius: var(--r-ctl);
776 711 background: var(--surface); cursor: pointer; padding: 0;
777 712 flex-direction: column; align-items: center; justify-content: center; gap: 5px;
778 713 box-shadow: var(--shadow-flat);
@@ -790,7 +725,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
790 725 .mobile-menu {
791 726 display: none; position: absolute; left: 0; right: 0; top: 100%;
792 727 background: var(--white); border-bottom: 1px solid var(--line);
793 − box-shadow: 0 18px 30px rgba(11, 19, 48, 0.14);
728 + box-shadow: 0 18px 30px rgba(20, 24, 20, 0.14);
794 729 padding: 8px 16px calc(16px + env(safe-area-inset-bottom));
795 730 max-height: 0; overflow: hidden; opacity: 0;
796 731 transition: max-height 0.3s ease, opacity 0.22s ease;
@@ -817,7 +752,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
817 752 }
818 753 .mm-backdrop {
819 754 position: fixed; inset: 0; z-index: 40;
820 − background: rgba(11, 19, 48, 0.35); backdrop-filter: blur(2px);
755 + background: rgba(20, 24, 20, 0.35); backdrop-filter: blur(2px);
821 756 }
822 757 @media (max-width: 760px) {
823 758 .menu-btn { display: flex; }
@@ -834,7 +769,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
834 769 @keyframes cookie-in { from { transform: translateY(24px); opacity: 0; } }
835 770 .cookie-inner {
836 771 max-width: 720px; margin: 0 auto; background: var(--surface);
837 − border: 1px solid var(--line); border-radius: var(--r-card);
772 + border: 1.5px solid var(--ink); border-radius: var(--r-card);
838 773 box-shadow: var(--shadow-off); padding: 16px 18px;
839 774 }
840 775 .cookie-text { font-size: 13.5px; color: var(--ink-2); line-height: 1.5; }
@@ -873,7 +808,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
873 808 }
874 809 @media (max-width: 640px) { .q-grid { grid-template-columns: repeat(2, 1fr); } }
875 810 .q-cell {
876 − background: var(--surface); border: 1px solid var(--line);
811 + background: var(--surface); border: 1.5px solid var(--ink);
877 812 border-radius: var(--r-card); padding: 12px 14px; box-shadow: var(--shadow-flat);
878 813 }
879 814 .q-val { font-family: var(--font-display); font-weight: 700; font-size: 19px; letter-spacing: -0.02em; color: var(--navy); }
@@ -893,10 +828,10 @@ h1 .hl, h2 .hl { color: var(--orange); }
893 828 .q-badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
894 829 .q-badge {
895 830 display: inline-flex; align-items: center; gap: 6px;
896 − border: 1px solid var(--line-strong); border-radius: var(--r-pill);
831 + border: 1.5px solid var(--ink); border-radius: var(--r-pill);
897 832 padding: 7px 14px; font-size: 12.5px; background: var(--surface);
898 833 }
899 −.q-badge.cool { background: var(--blue-soft); border-color: rgba(34, 88, 255, 0.3); color: var(--blue); }
834 +.q-badge.cool { background: var(--blue-soft); border-color: rgba(28, 92, 65, 0.3); color: var(--blue); }
900 835 .q-badge.hot { background: var(--orange-soft); border-color: rgba(255, 106, 0, 0.35); color: var(--orange-deep); }
901 836 .quartier .fine { margin-top: 10px; }
902 837
@@ -917,7 +852,7 @@ h1 .hl, h2 .hl { color: var(--orange); }
917 852
918 853 /* galerie à balayage natif */
919 854 .carousel { position: relative; border-radius: var(--r-card); overflow: hidden;
920 − border: 1px solid var(--line); background: var(--grey); box-shadow: var(--shadow-off-soft); }
855 + border: 1.5px solid var(--ink); background: var(--grey); box-shadow: var(--shadow-off-soft); }
921 856 .carousel-track {
922 857 display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
923 858 -webkit-overflow-scrolling: touch; scrollbar-width: none; aspect-ratio: 16/11;
@@ -931,14 +866,14 @@ h1 .hl, h2 .hl { color: var(--orange); }
931 866 aspect-ratio: 16/11; font-size: 48px; }
932 867 .carousel-count {
933 868 position: absolute; right: 12px; bottom: 12px; z-index: 2;
934 − background: rgba(11, 19, 48, 0.78); color: var(--white);
869 + background: rgba(20, 24, 20, 0.78); color: var(--white);
935 870 font-size: 12px; font-weight: 600;
936 871 padding: 4px 11px; border-radius: var(--r-pill);
937 872 backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
938 873 }
939 874 .carousel-nav {
940 875 position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
941 − width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--line);
876 + width: 44px; height: 44px; border-radius: 50%; border: 1.5px solid var(--ink);
942 877 background: rgba(255, 255, 255, 0.92); color: var(--navy); font-size: 22px; cursor: pointer;
943 878 display: flex; align-items: center; justify-content: center; line-height: 1;
944 879 box-shadow: var(--shadow-flat);
@@ -951,9 +886,9 @@ h1 .hl, h2 .hl { color: var(--orange); }
951 886 .deal-badge {
952 887 display: inline-flex; align-items: center; gap: 6px; margin: 8px 0 4px;
953 888 border-radius: var(--r-pill); padding: 7px 14px; font-size: 13px; font-weight: 600;
954 − border: 1px solid var(--line-strong);
889 + border: 1.5px solid var(--ink);
955 890 }
956 −.deal-good { background: var(--blue-soft); border-color: rgba(34, 88, 255, 0.3); color: var(--blue); }
891 +.deal-good { background: var(--blue-soft); border-color: rgba(28, 92, 65, 0.3); color: var(--blue); }
957 892 .deal-ok { background: var(--surface); color: var(--ink-2); }
958 893 .deal-high { background: var(--orange-soft); border-color: rgba(255, 106, 0, 0.35); color: var(--orange-deep); }
959 894 .f-hero h1 { font-size: clamp(20px, 4.5vw, 26px); margin-top: 8px; }
@@ -987,7 +922,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
987 922
988 923 /* description restructurée */
989 924 .enbref {
990 − background: var(--blue-soft); border: 1px solid rgba(34, 88, 255, 0.25);
925 + background: var(--blue-soft); border: 1px solid rgba(28, 92, 65, 0.25);
991 926 border-radius: var(--r-card); padding: 12px 16px; color: var(--navy);
992 927 font-size: 15px; margin: 0 0 14px;
993 928 }
@@ -1002,15 +937,15 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1002 937 .texte-original p { color: var(--ink-3); font-size: 13px; white-space: pre-line; }
1003 938
1004 939 /* inclusions : confirmées vs mentionnées */
1005 −.amenity.confirmed { background: var(--blue-soft); border-color: rgba(34, 88, 255, 0.25); color: var(--blue); font-weight: 600; }
940 +.amenity.confirmed { background: var(--blue-soft); border-color: rgba(28, 92, 65, 0.25); color: var(--blue); font-weight: 600; }
1006 941 .amenity.unconfirmed { background: transparent; border-style: dashed; border-color: var(--line-strong); color: var(--ink-3); }
1007 942
1008 943 /* détails pratiques : historique de prix */
1009 944 .prix-histo {
1010 945 margin-top: 12px; padding: 10px 14px; border-radius: var(--r-card);
1011 − border: 1px solid var(--line-strong); font-size: 13.5px; background: var(--surface);
946 + border: 1.5px solid var(--ink); font-size: 13.5px; background: var(--surface);
1012 947 }
1013 −.prix-histo.down { background: var(--blue-soft); border-color: rgba(34, 88, 255, 0.3); color: var(--blue); }
948 +.prix-histo.down { background: var(--blue-soft); border-color: rgba(28, 92, 65, 0.3); color: var(--blue); }
1014 949
1015 950 /* quartier compacté sur mobile */
1016 951 @media (max-width: 640px) {
@@ -1022,7 +957,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1022 957 .q-badge-sub { display: block; font-size: 10.5px; color: var(--ink-3); font-weight: 400; margin-left: 4px; }
1023 958
1024 959 /* POI groupés repliables */
1025 −.poi-groupe { border: 1px solid var(--line); border-radius: var(--r-card);
960 +.poi-groupe { border: 1.5px solid var(--ink); border-radius: var(--r-card);
1026 961 margin-bottom: 8px; background: var(--surface); overflow: hidden; }
1027 962 .poi-groupe summary {
1028 963 display: flex; justify-content: space-between; align-items: center; gap: 10px;
@@ -1060,7 +995,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1060 995 }
1061 996 .pass-card {
1062 997 width: 100%; max-width: 520px; background: var(--surface);
1063 − border: 1px solid var(--line); border-radius: var(--r-card);
998 + border: 1.5px solid var(--ink); border-radius: var(--r-card);
1064 999 box-shadow: var(--shadow-off); padding: 30px 28px; text-align: center;
1065 1000 }
1066 1001 .pass-brands {
@@ -1084,7 +1019,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1084 1019 .pass-gestionnaire {
1085 1020 font-family: var(--font-display); font-weight: 700; font-size: 19px;
1086 1021 display: inline-flex; align-items: center; gap: 8px;
1087 − background: var(--blue-soft); border: 1px solid rgba(34, 88, 255, 0.25);
1022 + background: var(--blue-soft); border: 1px solid rgba(28, 92, 65, 0.25);
1088 1023 border-radius: var(--r-pill); padding: 6px 16px; color: var(--blue);
1089 1024 }
1090 1025 .pass-card h1 { font-size: 20px; letter-spacing: -0.02em; }
@@ -1117,7 +1052,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1117 1052 .onglets::-webkit-scrollbar { display: none; }
1118 1053 .onglet {
1119 1054 flex: 0 0 auto; display: inline-flex; align-items: center; gap: 7px;
1120 − border: 1px solid var(--line-strong);
1055 + border: 1.5px solid var(--ink);
1121 1056 border-radius: var(--r-pill);
1122 1057 background: var(--surface); color: var(--ink-2); cursor: pointer;
1123 1058 padding: 10px 18px; font-weight: 600; font-size: 13.5px; min-height: 44px;
@@ -1159,7 +1094,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1159 1094 .pager-btn {
1160 1095 min-width: 38px; height: 38px; padding: 0 10px;
1161 1096 display: inline-flex; align-items: center; justify-content: center;
1162 − background: var(--surface); border: 1px solid var(--line-strong);
1097 + background: var(--surface); border: 1.5px solid var(--ink);
1163 1098 border-radius: var(--r-pill); cursor: pointer;
1164 1099 font-size: 13px; font-weight: 600;
1165 1100 color: var(--navy); transition: all 0.12s ease;
@@ -1203,7 +1138,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1203 1138 background: var(--navy); border: none;
1204 1139 border-radius: var(--r-pill); font-weight: 600; font-size: 13.5px; color: var(--white);
1205 1140 transition: all 0.12s ease; white-space: nowrap;
1206 − box-shadow: 0 4px 14px rgba(11, 19, 48, 0.22);
1141 + box-shadow: 0 4px 14px rgba(20, 24, 20, 0.22);
1207 1142 }
1208 1143 .login-btn:hover { background: var(--blue); }
1209 1144 .account { position: relative; margin-left: 10px; }
@@ -1217,7 +1152,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1217 1152 .account-backdrop { position: fixed; inset: 0; z-index: 90; }
1218 1153 .account-menu {
1219 1154 position: absolute; right: 0; top: calc(100% + 10px); z-index: 91;
1220 − min-width: 220px; background: var(--surface); border: 1px solid var(--line);
1155 + min-width: 220px; background: var(--surface); border: 1.5px solid var(--ink);
1221 1156 border-radius: var(--r-card); box-shadow: var(--shadow-off); overflow: hidden;
1222 1157 }
1223 1158 .account-id { display: flex; flex-direction: column; gap: 2px; padding: 12px 14px; border-bottom: 1px solid var(--line); }
@@ -1233,7 +1168,6 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1233 1168 /* ================= Profil — carte de membre Groupe KA ================= */
1234 1169 .profil { padding-top: 40px; padding-bottom: 60px; }
1235 1170 .profil h1 { font-size: clamp(30px, 5vw, 44px); margin: 10px 0 26px; color: var(--navy); }
1236 −.profil h1 .hl { color: var(--orange); }
1237 1171 .profil .lede { max-width: 520px; color: var(--ink-2); margin-bottom: 22px; }
1238 1172
1239 1173 .pc {
@@ -1285,7 +1219,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1285 1219 gap: 12px; margin-top: 30px; max-width: 760px;
1286 1220 }
1287 1221 .pg-item {
1288 − background: var(--surface); border: 1px solid var(--line);
1222 + background: var(--surface); border: 1.5px solid var(--ink);
1289 1223 border-radius: var(--r-card); padding: 14px 16px;
1290 1224 display: flex; flex-direction: column; gap: 4px; box-shadow: var(--shadow-flat);
1291 1225 }
@@ -1313,20 +1247,19 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1313 1247 .public-link {
1314 1248 display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
1315 1249 margin-top: 14px; padding: 12px 14px; background: var(--blue-soft);
1316 − border: 1px solid rgba(34, 88, 255, 0.25); border-radius: var(--r-card);
1250 + border: 1px solid rgba(28, 92, 65, 0.25); border-radius: var(--r-card);
1317 1251 }
1318 1252 .public-link a { font-size: 13px; color: var(--blue); text-decoration: underline; text-underline-offset: 3px; word-break: break-all; }
1319 1253
1320 1254 /* ================= Bienvenue — choix du rôle ================= */
1321 1255 .bienvenue { padding-top: 44px; padding-bottom: 70px; }
1322 1256 .bienvenue h1 { font-size: clamp(30px, 5vw, 46px); margin: 10px 0 14px; color: var(--navy); }
1323 −.bienvenue h1 .hl { color: var(--orange); }
1324 1257 .bienvenue .lede { max-width: 480px; color: var(--ink-2); margin-bottom: 30px; }
1325 1258 .role-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 18px; max-width: 780px; }
1326 1259 .role-card {
1327 1260 display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
1328 1261 text-align: left; padding: 24px 24px 20px; cursor: pointer;
1329 − background: var(--surface); border: 1px solid var(--line);
1262 + background: var(--surface); border: 1.5px solid var(--ink);
1330 1263 border-radius: var(--r-card); box-shadow: var(--shadow-flat);
1331 1264 font-family: var(--font-body); transition: all 0.15s ease;
1332 1265 }
@@ -1361,7 +1294,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1361 1294 .claim-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; max-width: 640px; }
1362 1295 .claim-row select {
1363 1296 flex: 1; min-width: 260px; padding: 11px 14px; font-size: 14px;
1364 − border: 1px solid var(--line-strong); border-radius: var(--r-ctl);
1297 + border: 1.5px solid var(--ink); border-radius: var(--r-ctl);
1365 1298 background: var(--surface); font-family: var(--font-body);
1366 1299 }
1367 1300 .claim-error { color: var(--danger); font-size: 13.5px; font-weight: 600; margin-top: 10px; }
@@ -1373,21 +1306,21 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1373 1306 .org-form label span { font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3); }
1374 1307 .org-form input, .org-form textarea {
1375 1308 padding: 10px 14px; font-size: 14px; font-family: var(--font-body);
1376 − border: 1px solid var(--line-strong); border-radius: var(--r-ctl); background: var(--surface);
1309 + border: 1.5px solid var(--ink); border-radius: var(--r-ctl); background: var(--surface);
1377 1310 }
1378 1311 .org-form textarea { resize: vertical; }
1379 −.org-form input:focus, .org-form textarea:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(34, 88, 255, 0.15); }
1312 +.org-form input:focus, .org-form textarea:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(28, 92, 65, 0.15); }
1380 1313 .org-form-row { display: flex; gap: 12px; flex-wrap: wrap; }
1381 1314 .org-form .btn { align-self: flex-start; }
1382 1315
1383 1316 /* ================= Page publique /g/{source} ================= */
1384 1317 .orgpub-head { display: flex; gap: 20px; align-items: flex-start; margin-top: 8px; }
1385 −.orgpub-logo { width: 84px; height: 84px; object-fit: contain; background: var(--surface); border: 1px solid var(--line); border-radius: 18px; padding: 8px; flex: none; box-shadow: var(--shadow-flat); }
1318 +.orgpub-logo { width: 84px; height: 84px; object-fit: contain; background: var(--surface); border: 1.5px solid var(--ink); border-radius: 18px; padding: 8px; flex: none; box-shadow: var(--shadow-flat); }
1386 1319 .orgpub-id h1 { font-size: clamp(26px, 4.4vw, 40px); margin: 6px 0 8px; color: var(--navy); }
1387 1320 .orgpub-tagline { font-size: 16px; color: var(--ink-2); margin: 0 0 12px; max-width: 640px; }
1388 1321 .orgpub-meta { display: flex; gap: 8px; flex-wrap: wrap; }
1389 1322 .orgpub-meta a.stat-chip:hover { background: var(--blue-soft); }
1390 −.orgpub-verified { background: var(--blue-soft); border-color: rgba(34, 88, 255, 0.3); color: var(--blue); font-weight: 600; }
1323 +.orgpub-verified { background: var(--blue-soft); border-color: rgba(28, 92, 65, 0.3); color: var(--blue); font-weight: 600; }
1391 1324 .orgpub-desc { max-width: 720px; color: var(--ink-2); margin: 18px 0 6px; white-space: pre-line; }
1392 1325 .orgpub .results-head h2 { display: inline-flex; align-items: center; gap: 8px; }
1393 1326 @media (max-width: 560px) { .orgpub-head { flex-direction: column; } }
@@ -1429,7 +1362,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1429 1362 .pub-social {
1430 1363 display: flex; align-items: center; justify-content: center;
1431 1364 width: 44px; height: 44px; background: var(--surface);
1432 − border: 1px solid var(--line-strong); border-radius: 14px; color: var(--navy);
1365 + border: 1.5px solid var(--ink); border-radius: 14px; color: var(--navy);
1433 1366 transition: all 0.12s ease;
1434 1367 }
1435 1368 .pub-social:hover { background: var(--navy); border-color: var(--navy); color: var(--white); transform: translateY(-2px); box-shadow: var(--shadow-off-soft); }
@@ -1439,13 +1372,13 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1439 1372 Jetons du design system appliqués au chrome de la carte + sync liste↔carte.
1440 1373 ----------------------------------------------------------------------------- */
1441 1374 .mapview .ka-map {
1442 − --ka-accent: var(--blue);
1443 − --ka-on-accent: var(--white);
1375 + --ka-accent: var(--accent);
1376 + --ka-on-accent: var(--on-accent);
1444 1377 --ka-surface: var(--surface);
1445 1378 --ka-ink: var(--navy);
1446 1379 --ka-line: var(--line-strong);
1447 1380 --ka-radius: var(--r-ctl);
1448 − --ka-shadow: 0 8px 24px rgba(11, 19, 48, 0.14);
1381 + --ka-shadow: 0 8px 24px rgba(20, 24, 20, 0.14);
1449 1382 --ka-font: var(--font-body);
1450 1383 }
1451 1384 .mapview { position: relative; overflow: hidden; }
@@ -1453,18 +1386,18 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1453 1386
1454 1387 /* mini-carte 3D de la fiche (Emplacement) — bâtiment de l'annonce en orange */
1455 1388 .lmap3d {
1456 − position: relative; height: 340px; border: 1px solid var(--line);
1389 + position: relative; height: 340px; border: 1.5px solid var(--ink);
1457 1390 border-radius: var(--r-card); box-shadow: var(--shadow-off-soft);
1458 1391 overflow: hidden; background: var(--grey);
1459 1392 }
1460 1393 .lmap3d .ka-map {
1461 − --ka-accent: var(--blue);
1462 − --ka-on-accent: var(--white);
1394 + --ka-accent: var(--accent);
1395 + --ka-on-accent: var(--on-accent);
1463 1396 --ka-surface: var(--surface);
1464 1397 --ka-ink: var(--navy);
1465 1398 --ka-line: var(--line-strong);
1466 1399 --ka-radius: var(--r-ctl);
1467 − --ka-shadow: 0 8px 24px rgba(11, 19, 48, 0.14);
1400 + --ka-shadow: 0 8px 24px rgba(20, 24, 20, 0.14);
1468 1401 --ka-font: var(--font-body);
1469 1402 position: absolute; inset: 0;
1470 1403 }
@@ -1475,14 +1408,14 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1475 1408 .lmap3d-legende {
1476 1409 position: absolute; left: 10px; bottom: 10px; z-index: 5;
1477 1410 display: inline-flex; align-items: center; gap: 6px;
1478 − padding: 4px 10px; border: 1px solid var(--line);
1411 + padding: 4px 10px; border: 1.5px solid var(--ink);
1479 1412 border-radius: var(--r-pill); background: var(--surface);
1480 1413 font-size: 10.5px; font-weight: 500; color: var(--ink-2);
1481 1414 pointer-events: none;
1482 1415 }
1483 1416 .lmap3d-legende i {
1484 1417 width: 10px; height: 10px; border-radius: 3px;
1485 − background: var(--orange); border: 1px solid rgba(11, 19, 48, 0.25);
1418 + background: var(--orange); border: 1px solid rgba(20, 24, 20, 0.25);
1486 1419 }
1487 1420 @media (max-width: 780px) { .lmap3d { height: 280px; } }
1488 1421 .ka-preview .mv-pop img, .ka-preview .mv-noimg { height: 120px; }
@@ -1492,3 +1425,132 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1492 1425 /* carte de liste synchronisée avec la carte (survol / sélection) */
1493 1426 .map-card { border-radius: var(--r-card); }
1494 1427 .map-card-sel { outline: 2px solid var(--blue); outline-offset: 2px; }
1428 +
1429 +/* =============================================================================
1430 + HARMONISATION « ÉDITORIAL SHARP » (Groupe KA) — couche finale posée après
1431 + les styles historiques : géométrie 10/6 px, bordures encre 1,5 px, ombres
1432 + décalées dures, accent orange Lou-Ka. La carte Ka Maps (Mapbox) conserve
1433 + son propre thème marine/orange à l'intérieur du canevas.
1434 +============================================================================= */
1435 +
1436 +/* — contrôles : plus de pilules (les chips/badges gardent --r-pill) — */
1437 +.nav a, .login-btn, .cta, .mv-pop-cta, .f-search, .f-more, .pager-btn,
1438 +.onglet, .view-toggle, .fab, .f-ctl, .pass-gestionnaire, .hbar-track,
1439 +.q-bar-track, .pass-progress { border-radius: var(--r-ctl); }
1440 +.seg button:first-child { border-radius: var(--r-ctl) 0 0 var(--r-ctl); }
1441 +.seg button:last-child { border-radius: 0 var(--r-ctl) var(--r-ctl) 0; }
1442 +
1443 +/* — header : papier translucide + trait d'encre — */
1444 +.header {
1445 + background: rgba(245, 243, 238, 0.92);
1446 + border-bottom: 1.5px solid var(--ink);
1447 +}
1448 +.nav a { font-family: var(--font-display); }
1449 +.nav a:hover { color: var(--accent-deep); background: var(--accent-soft); }
1450 +.nav a.active { background: var(--ink); color: var(--accent); }
1451 +.brand .ka {
1452 + background: var(--accent); color: var(--on-accent);
1453 + border-radius: 6px; padding: 0 6px 2px; margin-left: 2px; transform: rotate(-2deg);
1454 +}
1455 +.header .gk-badge { margin-left: 4px; flex: none; }
1456 +@media (max-width: 1080px) { .header .gk-badge { display: none; } }
1457 +.mm-badge {
1458 + display: flex; align-items: center; min-height: var(--touch);
1459 + padding: 8px 10px 2px;
1460 +}
1461 +
1462 +/* — ticker : encre + mono — */
1463 +.ticker { font-family: var(--font-mono); border-bottom: 1.5px solid var(--ink); }
1464 +
1465 +/* — états sélectionnés : encre + accent (fini le bleu) — */
1466 +.seg button.on, .onglet.on, .view-toggle button.on, .f-more.on {
1467 + background: var(--ink); border-color: var(--ink); color: var(--accent);
1468 +}
1469 +.pager-btn.on { background: var(--ink); border-color: var(--ink); color: var(--accent); }
1470 +
1471 +/* — CTA / FAB : aplat accent, bordure encre, ombre décalée — */
1472 +.cta, .mv-pop-cta, .fab {
1473 + background: var(--accent); color: var(--on-accent);
1474 + border: 1.5px solid var(--ink); box-shadow: 4px 4px 0 var(--ink);
1475 + font-family: var(--font-display); font-weight: 700;
1476 +}
1477 +.cta:hover, .mv-pop-cta:hover, .fab:hover { background: var(--accent-deep); }
1478 +.cta:active, .fab:active { box-shadow: none; }
1479 +.fab { box-shadow: 4px 4px 0 var(--ink), 0 10px 28px rgba(20, 24, 20, 0.25); }
1480 +.login-btn {
1481 + background: var(--ink); color: var(--accent);
1482 + border: 1.5px solid var(--ink); box-shadow: none;
1483 + font-family: var(--font-display); font-weight: 700;
1484 +}
1485 +.login-btn:hover { background: var(--accent-deep); color: var(--on-accent); }
1486 +
1487 +/* — cartes : levée nette diagonale (signature du groupe) — */
1488 +.card:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off); }
1489 +.role-card:hover:not(:disabled) { transform: translate(-2px, -2px); box-shadow: var(--shadow-off); border-color: var(--ink); }
1490 +.badge { border: 1.5px solid var(--ink); border-radius: var(--r-ctl); font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.05em; font-size: 10px; }
1491 +.badge.type { background: var(--accent); color: var(--on-accent); }
1492 +.source-tag { font-family: var(--font-mono); }
1493 +
1494 +/* — barre d'onglets mobile : papier + encre, actif = accent orange — */
1495 +@media (max-width: 760px) {
1496 + .tabbar { background: rgba(245, 243, 238, 0.95); border-top: 1.5px solid var(--ink); }
1497 + .tabbar a { font-family: var(--font-display); }
1498 + .tabbar a.active { color: var(--accent); }
1499 +}
1500 +.mobile-menu { background: var(--paper); border-bottom: 1.5px solid var(--ink); }
1501 +
1502 +/* — carte de membre KA : encre plutôt que marine — */
1503 +.pc { background: linear-gradient(135deg, #1d231d 0%, var(--ink) 55%, #0b0e0b 100%); }
1504 +
1505 +/* — micro-étiquettes en mono (système) — */
1506 +.f-ctl > span, .f-group > label, .field label, .tile-k, .pg-label,
1507 +.pc-label, .pc-id-label, .crumbs, .viz-sub { font-family: var(--font-mono); }
1508 +
1509 +/* — page Contact (design system) — */
1510 +.contact-page { padding: 44px 0 90px; }
1511 +.contact-page h1 { margin: 10px 0 8px; }
1512 +.contact-page .lede { color: var(--ink-2); max-width: 640px; margin: 0 0 26px; }
1513 +.contact-grid { display: grid; gap: 16px; grid-template-columns: 1fr; margin: 26px 0; }
1514 +@media (min-width: 768px) { .contact-grid { grid-template-columns: repeat(3, 1fr); } }
1515 +.contact-card { padding: 18px 20px; }
1516 +.contact-card .klabel { display: block; margin-bottom: 6px; }
1517 +.contact-card a.mail {
1518 + font-family: var(--font-mono); font-weight: 700; font-size: 13.5px;
1519 + color: var(--accent-deep); word-break: break-all;
1520 + text-decoration: underline; text-underline-offset: 3px;
1521 +}
1522 +.contact-card p { margin: 6px 0 0; color: var(--ink-2); font-size: 13.5px; }
1523 +.contact-note {
1524 + border-left: 3px solid var(--accent); background: var(--surface);
1525 + border-top: 1.5px solid var(--ink); border-right: 1.5px solid var(--ink);
1526 + border-bottom: 1.5px solid var(--ink); border-radius: 0 var(--r-card) var(--r-card) 0;
1527 + padding: 14px 18px; max-width: 720px; color: var(--ink-2); font-size: 14px;
1528 +}
1529 +.contact-note b { color: var(--ink); }
1530 +.contact-sites { list-style: none; margin: 14px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
1531 +.contact-sites a {
1532 + display: inline-flex; align-items: center; min-height: 34px;
1533 + padding: 4px 12px; border: 1.5px solid var(--ink); border-radius: var(--r-pill);
1534 + background: var(--surface); font-family: var(--font-mono); font-size: 11px;
1535 + font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
1536 + transition: transform 0.12s, box-shadow 0.12s;
1537 +}
1538 +.contact-sites a:hover { transform: translate(-1px, -1px); box-shadow: var(--shadow-off-mid); }
1539 +.contact-hub { margin-top: 26px; }
1540 +
1541 +/* — rangée de navigation locale au-dessus du footer commun — */
1542 +.prefooter { border-top: 1.5px solid var(--ink); background: var(--surface-2); padding: 18px 0; }
1543 +.prefooter .container {
1544 + display: flex; flex-wrap: wrap; align-items: center; gap: 6px 20px;
1545 + font-family: var(--font-mono); font-size: 11.5px; font-weight: 700;
1546 + text-transform: uppercase; letter-spacing: 0.08em;
1547 +}
1548 +.prefooter a, .prefooter .flink {
1549 + color: var(--ink-2); text-decoration: none; text-transform: uppercase;
1550 + letter-spacing: 0.08em; font-family: var(--font-mono); font-weight: 700; font-size: 11.5px;
1551 + min-height: var(--touch); display: inline-flex; align-items: center;
1552 +}
1553 +.prefooter a:hover, .prefooter .flink:hover { color: var(--accent-deep); text-decoration: underline; text-underline-offset: 4px; }
1554 +.prefooter .baseline { width: 100%; font-family: var(--font-body); font-weight: 400; font-size: 13px; color: var(--ink-2); text-transform: none; letter-spacing: 0; }
1555 +.ka-footer { margin-top: 0; }
1556 +@media (max-width: 760px) { .ka-footer { padding-bottom: calc(72px + env(safe-area-inset-bottom)); } }
modified frontend/tsconfig.tsbuildinfo +1 −1
@@ -1 +1 @@
1 −{"root":["./src/app.tsx","./src/account.tsx","./src/api.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/cookieconsent.tsx","./src/components/icons.tsx","./src/components/listingcard.tsx","./src/components/listingmap3d.tsx","./src/components/logo.tsx","./src/components/mapview.tsx","./src/components/pager.tsx","./src/components/quartierblock.tsx","./src/kamaps/adapter.ts","./src/kamaps/config.ts","./src/kamaps/theme.ts","./src/pages/bienvenue.tsx","./src/pages/bot.tsx","./src/pages/favoris.tsx","./src/pages/gestion.tsx","./src/pages/gestionpublic.tsx","./src/pages/home.tsx","./src/pages/listing.tsx","./src/pages/passerelle.tsx","./src/pages/privacy.tsx","./src/pages/profile.tsx","./src/pages/publicprofile.tsx","./src/pages/sources.tsx","./src/pages/stats.tsx","./src/pages/terms.tsx","./src/pages/ville.tsx"],"version":"5.9.3"}
\ No newline at end of file
1 +{"root":["./src/app.tsx","./src/logo.tsx","./src/account.tsx","./src/api.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/cookieconsent.tsx","./src/components/icons.tsx","./src/components/listingcard.tsx","./src/components/listingmap3d.tsx","./src/components/logo.tsx","./src/components/mapview.tsx","./src/components/pager.tsx","./src/components/quartierblock.tsx","./src/ka/groupekabadge.tsx","./src/ka/kafooter.tsx","./src/kamaps/adapter.ts","./src/kamaps/config.ts","./src/kamaps/theme.ts","./src/pages/bienvenue.tsx","./src/pages/bot.tsx","./src/pages/contact.tsx","./src/pages/favoris.tsx","./src/pages/gestion.tsx","./src/pages/gestionpublic.tsx","./src/pages/home.tsx","./src/pages/listing.tsx","./src/pages/passerelle.tsx","./src/pages/privacy.tsx","./src/pages/profile.tsx","./src/pages/publicprofile.tsx","./src/pages/sources.tsx","./src/pages/stats.tsx","./src/pages/terms.tsx","./src/pages/ville.tsx"],"version":"5.9.3"}
\ No newline at end of file
modified louka/seo.py +4 −2
@@ -120,7 +120,9 @@ def _render(*, title: str, description: str, path: str, jsonld: list[dict] | Non
120 120 page = page.replace("</head>", " " + "\n ".join(extras) + "\n</head>", 1)
121 121 if body:
122 122 seo_div = ('<div style="max-width:960px;margin:0 auto;padding:24px;'
123 − 'font-family:system-ui,sans-serif;color:#0B1330">' + body + "</div>")
123 + 'font-family:system-ui,sans-serif;color:#141814">' + body
124 + + '<p>Lou-Ka — Un service <a href="https://www.groupe-ka.com">Groupe KA</a></p>'
125 + + "</div>")
124 126 page = page.replace('<div id="root">', '<div id="root">' + seo_div, 1)
125 127 return HTMLResponse(page, status_code=status,
126 128 headers={"Cache-Control": "no-cache"})
@@ -359,7 +361,7 @@ def home_ssr():
359 361 WHERE active=1 AND {PRICE_OK} ORDER BY first_seen DESC LIMIT 20""").fetchall()
360 362 con.close()
361 363
362 − title = f"Lou-Ka — {total:,} logements à louer au Québec".replace(",", " ")
364 + title = f"Lou-Ka — Un service Groupe KA · {total:,} logements à louer au Québec".replace(",", " ")
363 365 description = (f"{total:,} appartements et logements à louer partout au Québec, "
364 366 f"agrégés depuis {nsources} gestionnaires immobiliers et toujours à jour : "
365 367 "Montréal, Québec, Lévis, Gatineau et plus. Photos, prix, disponibilité "
modified louka/web.py +1 −1
@@ -403,7 +403,7 @@ if FRONTEND_DIST.exists():
403 403 # tout autre chemin inconnu renvoie index.html avec un statut 404 : le
404 404 # routeur React affiche sa page « introuvable », les moteurs de recherche
405 405 # voient un vrai 404 (fin des soft-404).
406 − _CLIENT_ROUTES = {"profil", "favoris", "gestion", "bienvenue", "bot"}
406 + _CLIENT_ROUTES = {"profil", "favoris", "gestion", "bienvenue", "bot", "contact"}
407 407 _CLIENT_PREFIXES = ("u/", "passerelle/")
408 408
409 409 @app.get("/{full_path:path}")
410 410