SPB Git forge

spb/groupe-ka

Public

Groupe KA — site du holding + KA ID (compte unique & SSO des 7 plateformes). Next.js 16, SQLite, Google & Apple login.

81commits 1branches 0releases
89.2 MBsize
maindefault branch
22 days agolast push
TypeScript 70.4% HTML 18.4% JavaScript 4% Python 3.8% CSS 3.4%

archive 2026-09-04 : retrait de Trouve·Ka (section phare, /recherche, barre de recherche), Resto·Ka, Créa·Ka, House·Ka et Rent·Ka du site (écosystème, métriques live, SSO, alertes, pages légales, rapport quotidien)

Simon-Pierre Boucher committed 22 days ago (Sep 5, 2026) parent d5ebd56

21 changed files +23 −975

modified scripts/rapport-quotidien.mjs +2 −4
@@ -28,9 +28,7 @@ const SERVICES = [
28 28 ["autoka", "Auto·Ka", "voitures usagées"],
29 29 ["fabrika", "Fabri·Ka", "produits québécois"],
30 30 ["foodka", "Food·Ka", "prix d'épicerie"],
31 − ["restoka", "Resto·Ka", "restos, menus et prix"],
32 31 ["sortika", "Sorti·Ka", "sorties et événements"],
33 − ["creaka", "Créa·Ka", "créateurs d'ici"],
34 32 ["jobka", "Job·Ka", "offres d'emploi"],
35 33 ];
36 34
@@ -176,9 +174,9 @@ Règles absolues :
176 174 "vigilance": ["points à surveiller (collectes échouées, baisses inhabituelles, connecteurs dégradés) — liste vide si tout va bien"],
177 175 "mot_de_la_fin": "une phrase de clôture avec du panache"
178 176 }
179 −- "faits_saillants" : une entrée par plateforme qui a quelque chose à dire (6 à 10 entrées, Trouve·Ka compris s'il a du nouveau).
177 +- "faits_saillants" : une entrée par plateforme qui a quelque chose à dire (6 à 10 entrées compris s'il a du nouveau).
180 178 - "analyse" : 2 ou 3 sections.
181 −- Utilise les vrais noms : Lou·Ka, Immo·Ka, Auto·Ka, Fabri·Ka, Food·Ka, Resto·Ka, Sorti·Ka, Créa·Ka, Job·Ka, API·Ka, Trouve·Ka.`;
179 +- Utilise les vrais noms : Lou·Ka, Immo·Ka, Auto·Ka, Fabri·Ka, Food·Ka, Sorti·Ka, Job·Ka, API·Ka.`;
182 180
183 181 async function rediger(snapshot, dateRapport) {
184 182 const key = process.env.ANTHROPIC_API_KEY;
deleted src/app/SearchBar.tsx +0 −84
@@ -1,84 +0,0 @@
1 −// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
2 −// Barre de recherche « tout l'écosystème KA » — simple formulaire GET vers
3 −// /recherche (rendu serveur, moteur Trouve·Ka). Deux tailles : compacte pour
4 −// le header desktop (défaut), grande pour la page /recherche (hero).
5 −"use client";
6 −
7 −export default function SearchBar({
8 − size = "header",
9 − defaultQuery = "",
10 − site,
11 −}: {
12 − size?: "header" | "hero";
13 − defaultQuery?: string;
14 − site?: string;
15 −}) {
16 − if (size === "hero") {
17 − return (
18 − <form
19 − action="/recherche"
20 − role="search"
21 − className="flex w-full max-w-xl items-center gap-2"
22 − >
23 − <input
24 − type="search"
25 − name="q"
26 − defaultValue={defaultQuery}
27 − className="input flex-1"
28 − placeholder="Rechercher dans l'écosystème KA…"
29 − aria-label="Recherche"
30 − autoComplete="off"
31 − />
32 − {site ? <input type="hidden" name="site" value={site} /> : null}
33 − <button type="submit" className="btn btn-primary shrink-0">
34 − Rechercher
35 − </button>
36 − </form>
37 − );
38 − }
39 − // Header : la rangée de nav est déjà très chargée (12 items + compte +
40 − // CTA). En dessous de ~1900 px, la barre complète pousserait le bouton
41 − // compte et « Écrivez-nous » hors écran (mesuré Playwright 2026-08-23) :
42 − // on affiche alors une icône-lien vers /recherche, et la barre complète
43 − // seulement sur les très grands écrans. Affichage conditionnel en CSS pur
44 − // (aucun réordonnancement entre breakpoints).
45 − // NB : .btn est redéfini HORS layer dans globals.css (il bat les utilities
46 − // Tailwind) → la visibilité responsive se pose sur des conteneurs neutres,
47 − // jamais sur l'élément .btn lui-même.
48 − return (
49 − <div className="flex items-center">
50 − <span className="min-[1950px]:hidden">
51 − <a
52 − href="/recherche"
53 − className="btn"
54 − aria-label="Recherche dans l'écosystème KA"
55 − title="Rechercher dans l'écosystème KA"
56 − >
57 − ⌕
58 − </a>
59 − </span>
60 − <form
61 − action="/recherche"
62 − role="search"
63 − className="hidden items-center gap-1.5 min-[1950px]:flex"
64 − >
65 − <input
66 − type="search"
67 − name="q"
68 − className="input w-40 px-3 text-[13.5px] min-[2100px]:w-56"
69 − placeholder="Rechercher dans l'écosystème KA…"
70 − aria-label="Recherche"
71 − autoComplete="off"
72 − />
73 − <button
74 − type="submit"
75 − className="btn shrink-0"
76 − aria-label="Lancer la recherche"
77 − title="Rechercher dans l'écosystème KA"
78 − >
79 − ⌕
80 − </button>
81 − </form>
82 − </div>
83 − );
84 −}
modified src/app/agents/page.tsx +0 −3
@@ -36,7 +36,6 @@ const GUARDIANS: Guardian[] = [
36 36 perimetre: [
37 37 { app: "Lou·Ka", url: "https://www.lou-ka.com" },
38 38 { app: "Immo·Ka", url: "https://www.immo-ka.com" },
39 − { app: "Resto·Ka", url: "https://www.resto-ka.com" },
40 39 ],
41 40 },
42 41 {
@@ -64,8 +63,6 @@ const GUARDIANS: Guardian[] = [
64 63 perimetre: [
65 64 { app: "Fabri·Ka", url: "https://www.fabri-ka.com" },
66 65 { app: "Job·Ka", url: "https://www.job-ka.com" },
67 − { app: "Créa·Ka", url: "https://www.crea-ka.com" },
68 − { app: "Rent·Ka", url: "https://www.rent-ka.com" },
69 66 ],
70 67 extra: "+ agent par défaut de toute nouvelle plateforme",
71 68 },
modified src/app/avis/page.tsx +1 −1
@@ -29,7 +29,7 @@ export default function Avis() {
29 29 </h1>
30 30 <p className="rise mt-5 max-w-2xl text-[15px] text-ink-2 [animation-delay:0.16s]">
31 31 <strong className="text-ink">
32 − Groupe KA et ses plateformes (Trouve·Ka, Lou·Ka, Immo·Ka,
32 + Groupe KA et ses plateformes (Lou·Ka, Immo·Ka,
33 33 Vrai-Prix, ValoPlex, Auto·Ka, Fabri·Ka, Food·Ka, ka6) sont des
34 34 services d&apos;agrégation et de recherche.
35 35 </strong>{" "}
modified src/app/compte/page.tsx +0 −7
@@ -35,18 +35,13 @@ export const dynamic = "force-dynamic";
35 35 const PLATFORM_URLS: Record<string, string> = {
36 36 "lou-ka": "https://www.lou-ka.com",
37 37 "immo-ka": "https://www.immo-ka.com",
38 − "house-ka": "https://www.house-ka.com",
39 − "rent-ka": "https://www.rent-ka.com",
40 38 "vrai-prix": "https://www.vrai-prix.com",
41 39 valoplex: "https://www.valoplex.com",
42 40 "auto-ka": "https://www.auto-ka.com",
43 41 "fabri-ka": "https://www.fabri-ka.com",
44 42 "food-ka": "https://www.food-ka.com",
45 − "resto-ka": "https://www.resto-ka.com",
46 43 "sorti-ka": "https://www.sorti-ka.com",
47 − "crea-ka": "https://www.crea-ka.com",
48 44 "job-ka": "https://www.job-ka.com",
49 − "trouve-ka": "https://www.trouve-ka.com",
50 45 "api-ka": "https://www.api-ka.com",
51 46 "ora-ka": "https://www.ora-ka.com",
52 47 "toit-ka": "https://www.toit-ka.com",
@@ -68,8 +63,6 @@ function fmtDate(iso: string | null): string {
68 63 const APP_ACCENTS: Record<string, string> = {
69 64 "lou-ka": "#d9f26b",
70 65 "immo-ka": "#e23744",
71 − "house-ka": "#0f6b4f",
72 − "rent-ka": "#2456e6",
73 66 "vrai-prix": "#ff5148",
74 67 valoplex: "#ff9f45",
75 68 "auto-ka": "#ff5a2a",
modified src/app/connexion/page.tsx +0 −3
@@ -19,12 +19,9 @@ const PLATFORMS: { name: string; field: string }[] = [
19 19 { name: "Vrai-Prix", field: "Évaluation" },
20 20 { name: "Auto·Ka", field: "Véhicules" },
21 21 { name: "Food·Ka", field: "Épicerie" },
22 − { name: "Resto·Ka", field: "Restaurants" },
23 22 { name: "Sorti·Ka", field: "Sorties" },
24 − { name: "Créa·Ka", field: "Créateurs" },
25 23 { name: "Job·Ka", field: "Emplois" },
26 24 { name: "Fabri·Ka", field: "Artisans" },
27 − { name: "Trouve·Ka", field: "Annonces" },
28 25 { name: "API·Ka", field: "Données" },
29 26 ];
30 27
modified src/app/figures.tsx +1 −10
@@ -14,16 +14,12 @@ export const FIG_PLATFORMS: {
14 14 }[] = [
15 15 { name: "Lou·Ka", accent: "#ff6a00", domain: "lou-ka.com" },
16 16 { name: "Immo·Ka", accent: "#e23744", domain: "immo-ka.com" },
17 − { name: "House·Ka", accent: "#0f6b4f", domain: "house-ka.com" },
18 − { name: "Rent·Ka", accent: "#2456e6", domain: "rent-ka.com" },
19 17 { name: "Vrai-Prix", accent: "#ff5148", domain: "vrai-prix.com" },
20 18 { name: "ValoPlex", accent: "#ff9f45", domain: "valoplex.com" },
21 19 { name: "Auto·Ka", accent: "#ff5a2a", domain: "auto-ka.com" },
22 20 { name: "Fabri·Ka", accent: "#C4532E", domain: "fabri-ka.com" },
23 21 { name: "Food·Ka", accent: "#1f9d55", domain: "food-ka.com" },
24 − { name: "Resto·Ka", accent: "#f08c00", domain: "resto-ka.com" },
25 22 { name: "Sorti·Ka", accent: "#d6336c", domain: "sorti-ka.com" },
26 − { name: "Créa·Ka", accent: "#7048e8", domain: "crea-ka.com" },
27 23 { name: "Job·Ka", accent: "#0c8599", domain: "job-ka.com" },
28 24 ];
29 25
@@ -71,7 +67,6 @@ const FIG_SOURCES = [
71 67 "Circulaires & soldes",
72 68 "Menus de restaurants",
73 69 "Billetteries d'événements",
74 − "Profils de créateurs",
75 70 ];
76 71
77 72 export function AggregationFigure() {
@@ -682,13 +677,11 @@ export function CycleFigure() {
682 677
683 678 const INVENTORY: { name: string; value: number; unit: string }[] = [
684 679 { name: "Fabri·Ka", value: 281000, unit: "produits québécois" },
685 − { name: "Resto·Ka", value: 61000, unit: "plats & prix réels" },
686 680 { name: "Immo·Ka", value: 54000, unit: "propriétés à vendre" },
687 681 { name: "Food·Ka", value: 23700, unit: "prix d'épicerie" },
688 682 { name: "Auto·Ka", value: 15900, unit: "véhicules d'occasion" },
689 683 { name: "Sorti·Ka", value: 11100, unit: "événements & sorties" },
690 684 { name: "Lou·Ka", value: 7000, unit: "logements à louer" },
691 − { name: "Créa·Ka", value: 2200, unit: "créateurs d'ici" },
692 685 ];
693 686
694 687 const fmt = (n: number) => n.toLocaleString("fr-CA").replace(/ /g, " ");
@@ -770,7 +763,6 @@ const CONNECTORS: { name: string; count: number; kind: string }[] = [
770 763 { name: "Lou·Ka", count: 108, kind: "gestionnaires" },
771 764 { name: "Immo·Ka", count: 65, kind: "agences" },
772 765 { name: "Food·Ka", count: 20, kind: "bannières" },
773 − { name: "Resto·Ka", count: 19, kind: "chaînes de restos" },
774 766 { name: "Sorti·Ka", count: 4, kind: "billetteries & données ouvertes" },
775 767 ];
776 768
@@ -827,8 +819,7 @@ export function ConnectorMatrix({
827 819 <FigCaption lead="LA FLOTTE">
828 820 chaque point est un petit programme qui connaît un site par cœur et le
829 821 relit sans arrêt. S&apos;y ajoutent les flux des 1 204 boutiques de
830 − Fabri·Ka, les données publiques qui alimentent Vrai-Prix et ValoPlex,
831 − et l&apos;annuaire multi-plateformes de Créa·Ka.
822 + Fabri·Ka, les données publiques qui alimentent Vrai-Prix et ValoPlex.
832 823 </FigCaption>
833 824 </figure>
834 825 );
modified src/app/layout.tsx +3 −22
@@ -2,7 +2,6 @@
2 2 import type { Metadata, Viewport } from "next";
3 3 import { Inter, JetBrains_Mono, Space_Grotesk } from "next/font/google";
4 4 import UserButton from "./UserButton";
5 −import SearchBar from "./SearchBar";
6 5 import { getLiveMetrics, fmtDec, fmtInt, fmtPlus, type LiveMetrics } from "@/lib/live";
7 6 import "./globals.css";
8 7
@@ -26,11 +25,11 @@ export const metadata: Metadata = {
26 25 canonical: "./",
27 26 },
28 27 description:
29 − "Groupe KA est un holding québécois d’agrégateurs de produits et services entièrement automatisés : Trouve·Ka (le moteur de recherche de l’écosystème), Lou·Ka (location), Rent·Ka (location — Canada hors Québec), Immo·Ka (propriétés à vendre), House·Ka (maisons à vendre — Canada hors Québec), Vrai-Prix (valeur résidentielle), ValoPlex (plex), Auto·Ka (voitures usagées), Fabri·Ka (produits québécois), Food·Ka (épicerie), Resto·Ka (restaurants, menus et prix), Sorti·Ka (sorties et événements) Créa·Ka (créateurs d’ici) et Job·Ka (emplois). Zéro saisie manuelle.",
28 + "Groupe KA est un holding québécois d’agrégateurs de produits et services entièrement automatisés : Lou·Ka (location), Immo·Ka (propriétés à vendre), Vrai-Prix (valeur résidentielle), ValoPlex (plex), Auto·Ka (voitures usagées), Fabri·Ka (produits québécois), Food·Ka (épicerie), Sorti·Ka (sorties et événements) Job·Ka (emplois). Zéro saisie manuelle.",
30 29 openGraph: {
31 30 title: "Groupe KA — holding d’agrégateurs automatisés, sans boîte noire",
32 31 description:
33 − "Ka, c’est l’agrégation : des centaines de connecteurs, treize plateformes — Lou·Ka, Rent·Ka, Immo·Ka, House·Ka, Vrai-Prix, ValoPlex, Auto·Ka, Fabri·Ka, Food·Ka, Resto·Ka, Sorti·Ka, Créa·Ka et Job·Ka.",
32 + "Ka, c’est l’agrégation : des centaines de connecteurs, neuf plateformes — Lou·Ka, Immo·Ka, Vrai-Prix, ValoPlex, Auto·Ka, Fabri·Ka, Food·Ka, Sorti·Ka et Job·Ka.",
34 33 url: "./",
35 34 siteName: "Groupe KA",
36 35 locale: "fr_CA",
@@ -60,22 +59,17 @@ export const viewport: Viewport = {
60 59 // alignées sur la liste réellement affichée dans le pied de page.
61 60 const SITE_URL = "https://www.groupe-ka.com/";
62 61 const PLATEFORMES: [string, string][] = [
63 − ["Trouve·Ka", "https://www.trouve-ka.com"],
64 62 ["Lou·Ka", "https://www.lou-ka.com"],
65 63 ["Immo·Ka", "https://www.immo-ka.com"],
66 − ["House·Ka", "https://www.house-ka.com"],
67 64 ["Vrai-Prix", "https://www.vrai-prix.com"],
68 65 ["ValoPlex", "https://www.valoplex.com"],
69 66 ["Auto·Ka", "https://www.auto-ka.com"],
70 67 ["Fabri·Ka", "https://www.fabri-ka.com"],
71 68 ["Food·Ka", "https://www.food-ka.com"],
72 − ["Resto·Ka", "https://www.resto-ka.com"],
73 69 ["Sorti·Ka", "https://www.sorti-ka.com"],
74 − ["Créa·Ka", "https://www.crea-ka.com"],
75 70 ["API·Ka", "https://www.api-ka.com"],
76 71 ["Job·Ka", "https://www.job-ka.com"],
77 72 ["Ka·Stats", "https://www.ka-stats.com"],
78 − ["Rent·Ka", "https://www.rent-ka.com"],
79 73 ];
80 74 const JSON_LD = {
81 75 "@context": "https://schema.org",
@@ -121,7 +115,6 @@ const JSON_LD = {
121 115 };
122 116
123 117 const NAV_ITEMS = [
124 − { href: "/#trouve-ka", label: "Moteur" },
125 118 { href: "/#flux", label: "L’agrégation" },
126 119 { href: "/#chiffres", label: "Chiffres" },
127 120 { href: "/#produits", label: "Plateformes" },
@@ -146,8 +139,6 @@ const MOBILE_NAV_ITEMS = [
146 139 // Ticker construit sur les métriques LIVE des plateformes (src/lib/live.ts).
147 140 const tickerItems = (m: LiveMetrics): string[] => [
148 141 "·Ka = agréger — mille sites, un seul endroit",
149 − `Trouve·Ka cherche tout l'écosystème — ${fmtPlus(m.trouveka.pages)} pages des sites ·Ka indexées`,
150 − `Trouve·Ka indexe ${fmtInt(m.trouveka.indexedLastHour)} pages à l'heure — le robot ne dort pas`,
151 142 "Dix agrégateurs · une obsession : la transparence",
152 143 `${fmtInt(m.totals.connectors)}+ connecteurs lisent les sources jour et nuit`,
153 144 `${fmtPlus(m.louka.total)} logements à louer agrégés en continu — loyer moyen ${fmtInt(m.louka.avgPrice)} $`,
@@ -156,9 +147,7 @@ const tickerItems = (m: LiveMetrics): string[] => [
156 147 `${fmtPlus(m.autoka.total)} voitures usagées chez ${m.autoka.sources} concessionnaires — prix moyen ${fmtInt(m.autoka.avgPrice)} $`,
157 148 `${fmtPlus(m.fabrika.products)} produits québécois de ${fmtInt(m.fabrika.stores)} boutiques`,
158 149 `${fmtPlus(m.foodka.total)} prix d'épicerie suivis chez ${m.foodka.sources} bannières`,
159 − `${fmtPlus(m.restoka.items)} plats et prix réels chez ${fmtPlus(m.restoka.restaurants)} restos — resto-ka.com`,
160 150 `${fmtPlus(m.sortika.active)} sorties et événements dans les 17 régions — sorti-ka.com`,
161 − `${fmtPlus(m.creaka.creators)} créateurs québécois, tous leurs liens regroupés — crea-ka.com`,
162 151 `${fmtPlus(m.jobka.total)} offres d'emploi chez ${m.jobka.employers} employeurs — salaire moyen ${fmtInt(m.jobka.avgSalaryYear)} $/an`,
163 152 `${fmtInt(m.vraiprix.units)} propriétés évaluées au Québec`,
164 153 // ValoPlex n'expose pas de /api/stats (vérifié 2026-08-18) : chiffres du
@@ -221,9 +210,6 @@ export default async function RootLayout({
221 210 aria-hidden="true"
222 211 className="mx-3 h-[22px] w-px self-center bg-[rgba(20,24,20,0.3)]"
223 212 />
224 − <li className="hidden xl:flex">
225 − <SearchBar />
226 − </li>
227 213 <li className="ml-2">
228 214 <UserButton />
229 215 </li>
@@ -319,7 +305,7 @@ export default async function RootLayout({
319 305 entièrement automatisés. Des centaines de connecteurs lisent les
320 306 sites à la source, normalisent la donnée et se resynchronisent
321 307 seuls — logements, propriétés, autos, produits d&apos;ici,
322 − épicerie, restos, sorties, créateurs. Rien d&apos;inventé, tout
308 + épicerie, sorties. Rien d&apos;inventé, tout
323 309 est traçable.{" "}
324 310 <span className="text-lime">
325 311 ·Ka, c&apos;est le suffixe qui veut dire agréger.
@@ -339,21 +325,16 @@ export default async function RootLayout({
339 325 </p>
340 326 <ul className="gk-mono mt-7 flex flex-wrap gap-x-7 gap-y-3 text-[11px] font-bold tracking-[0.1em] uppercase">
341 327 {[
342 − ["Trouve·Ka", "https://www.trouve-ka.com"],
343 328 ["Lou·Ka", "https://www.lou-ka.com"],
344 329 ["Immo·Ka", "https://www.immo-ka.com"],
345 − ["House·Ka", "https://www.house-ka.com"],
346 330 ["Vrai-Prix", "https://www.vrai-prix.com"],
347 331 ["ValoPlex", "https://www.valoplex.com"],
348 332 ["Auto·Ka", "https://www.auto-ka.com"],
349 333 ["Fabri·Ka", "https://www.fabri-ka.com"],
350 334 ["Food·Ka", "https://www.food-ka.com"],
351 − ["Resto·Ka", "https://www.resto-ka.com"],
352 335 ["Sorti·Ka", "https://www.sorti-ka.com"],
353 − ["Créa·Ka", "https://www.crea-ka.com"],
354 336 ["API·Ka", "https://www.api-ka.com"],
355 337 ["Job·Ka", "https://www.job-ka.com"],
356 − ["Rent·Ka", "https://www.rent-ka.com"],
357 338 ["Ka·Stats", "https://www.ka-stats.com"],
358 339 ["Ka2", "https://www.ka2.bot"],
359 340 ["Ka4", "https://www.ka4.bot"],
modified src/app/loi-25/page.tsx +0 −14
@@ -11,13 +11,6 @@ const MAILTO =
11 11 "gk-mono font-bold underline underline-offset-4 hover:text-ink";
12 12
13 13 const APPS_ROWS: [string, string, string, string, string][] = [
14 − [
15 − "Trouve·Ka",
16 − "www.trouve-ka.com",
17 − "Moteur de recherche — écosystème Groupe KA",
18 − "Pages publiques des sites du Groupe KA, indexées par un robot dédié à partir de leurs sitemaps",
19 − "Renseignements rendus publics par les sites indexés; extraits et liens vers la page originale seulement",
20 − ],
21 14 [
22 15 "Lou·Ka",
23 16 "www.lou-ka.com",
@@ -25,13 +18,6 @@ const APPS_ROWS: [string, string, string, string, string][] = [
25 18 "Annonces de logements provenant de 108 sources publiques, synchronisées à chaque heure",
26 19 "Coordonnées de propriétaires ou de locateurs rendues publiques dans les annonces sources",
27 20 ],
28 − [
29 − "Rent·Ka",
30 − "www.rent-ka.com",
31 − "Location résidentielle — Canada hors Québec",
32 − "Annonces de logements provenant de gestionnaires immobiliers et de portails canadiens publics, synchronisées à chaque heure",
33 − "Coordonnées de gestionnaires immobiliers rendues publiques dans les annonces sources",
34 − ],
35 21 [
36 22 "Immo·Ka",
37 23 "www.immo-ka.com",
modified src/app/marque/page.tsx +1 −21
@@ -72,21 +72,11 @@ const INK = "#141814";
72 72 // ---------------------------------------------------------------------------
73 73
74 74 const COLOR_STORIES: Record<string, { name: string; story: string }> = {
75 − "rent-ka": {
76 − name: "Cobalt continental",
77 − story:
78 − "Un bleu franc et travailleur, celui des portes qui s'ouvrent d'un océan à l'autre. Le cobalt tranche avec l'orange de Lou·Ka comme l'anglais tranche avec le français : même métier, autre horizon. Posé sur le papier crème du système KA, il signe le sérieux du bail — net, direct, sans détour.",
79 − },
80 75 "groupe-ka": {
81 76 name: "Lime KA",
82 77 story:
83 78 "Le vert-jaune électrique du groupe : une teinte végétale poussée à la saturation maximale, à mi-chemin entre la chartreuse et le lime. C'est la couleur mère de l'écosystème — celle du surligneur posé sur l'encre, des boutons du hub et du wordmark inversé. Très claire, elle exige toujours du texte encre par-dessus.",
84 79 },
85 − "trouve-ka": {
86 − name: "Bleu moteur",
87 − story:
88 − "Un bleu azur franc, ni marine ni cyan : la couleur historique de la recherche sur le web. Elle évoque le lien hypertexte, la confiance et la neutralité — exactement le rôle de Trouve·Ka, le moteur qui indexe tout l'écosystème sans favoriser personne.",
89 − },
90 80 "lou-ka": {
91 81 name: "Orange signal",
92 82 story:
@@ -117,25 +107,15 @@ const COLOR_STORIES: Record<string, { name: string; story: string }> = {
117 107 story:
118 108 "Un vert franc de fraîcheur, plus vif que le vert profond institutionnel du groupe : celui des étals de fruits et légumes et des étiquettes « spécial ». Il marque les aubaines et les baisses de prix.",
119 109 },
120 − "resto-ka": {
121 − name: "Safran cuisine",
122 − story:
123 − "Un jaune-orangé safrané, chaud comme une salle de resto le soir. Assez clair pour exiger du texte encre par-dessus (seul accent avec le lime du groupe dans ce cas), il porte les fourchettes de prix et les badges de menus.",
124 − },
125 110 "sorti-ka": {
126 111 name: "Framboise scène",
127 112 story:
128 113 "Un rose framboise assumé, saturé et nocturne : néons de salle de spectacle, tampon d'entrée sur la main. La couleur de l'événementiel du groupe, posée sur les dates et les badges « gratuit ».",
129 114 },
130 − "crea-ka": {
131 − name: "Violet créateur",
132 − story:
133 − "Un violet électrique à mi-chemin de l'indigo et du pourpre — la couleur des plateformes de création et du streaming. Il relie les fiches des créateurs et leurs compteurs d'abonnés.",
134 − },
135 115 "api-ka": {
136 116 name: "Indigo machine",
137 117 story:
138 − "Un bleu-violet d'interface développeur : plus profond que le bleu de Trouve·Ka, plus sage que le violet de Créa·Ka. La couleur du code, des clés d'API et des réponses JSON de la plateforme centrale.",
118 + "Un bleu-violet d'interface développeur : La couleur du code, des clés d'API et des réponses JSON de la plateforme centrale.",
139 119 },
140 120 "job-ka": {
141 121 name: "Sarcelle carrière",
modified src/app/mon-ka/page.tsx +0 −5
@@ -28,18 +28,13 @@ export const dynamic = "force-dynamic";
28 28 const APP_ACCENTS: Record<string, string> = {
29 29 "lou-ka": "#d9f26b",
30 30 "immo-ka": "#e23744",
31 − "house-ka": "#0f6b4f",
32 − "rent-ka": "#2456e6",
33 31 "vrai-prix": "#ff5148",
34 32 valoplex: "#ff9f45",
35 33 "auto-ka": "#ff5a2a",
36 34 "fabri-ka": "#C4532E",
37 35 "food-ka": "#d9f26b",
38 − "resto-ka": "#e23744",
39 36 "sorti-ka": "#ff9f45",
40 − "crea-ka": "#d9f26b",
41 37 "job-ka": "#ff5a2a",
42 − "trouve-ka": "#d9f26b",
43 38 };
44 39
45 40 const EVENT_LABELS: Record<string, string> = {
modified src/app/page.tsx +8 −283
@@ -79,26 +79,6 @@ const PRODUCTS: Product[] = [
79 79 "Lien direct vers l'inscription originale de l'agence",
80 80 ],
81 81 },
82 − {
83 − num: "03",
84 − markLeft: "House",
85 − markBoxed: "Ka",
86 − accent: "#0f6b4f",
87 − inkbox: "#14201a",
88 − field: "Immobilier — Canada",
89 − domain: "www.house-ka.com",
90 − url: "https://www.house-ka.com",
91 − tagline: "Every home for sale in Canada. One place.",
92 − description:
93 − "Le jumeau anglophone d'Immo·Ka pour le reste du Canada — toutes les provinces sauf le Québec. Réseaux nationaux (Century 21) et sites de courtage branchés au flux CREA DDF, agrégés à la source, normalisés et géolocalisés ; chaque fiche renvoie à l'inscription originale du courtier.",
94 − chips: ["260 000+ fiches", "10 provinces & territoires", "30 connecteurs"],
95 − features: [
96 − "Recherche unifiée en anglais : ville, type, prix, chambres",
97 − "Vue carte pan-canadienne (Ka Maps), grappes pondérées par prix",
98 − "Taux hypothécaires réels des banques + calculateur canadien",
99 − "Commerces de proximité adaptés à chaque province",
100 − ],
101 − },
102 82 {
103 83 num: "04",
104 84 markLeft: "Vrai",
@@ -199,27 +179,6 @@ const PRODUCTS: Product[] = [
199 179 "Catégories d'épicerie complètes, du frais au garde-manger",
200 180 ],
201 181 },
202 − {
203 − num: "09",
204 − markLeft: "Resto",
205 − markBoxed: "Ka",
206 − accent: "#ffc53d",
207 − inkbox: "#1a1410",
208 − field: "Restaurants",
209 − domain: "www.resto-ka.com",
210 − url: "https://www.resto-ka.com",
211 − tagline: "Tous les restaurants du Québec. Menus et prix réels.",
212 − description:
213 − "L'agrégateur des menus de restaurants : 19 chaînes intégrées — Sushi Shop, Valentine, Normandin, Ashton, Scores, Allô mon Coco et d'autres — lues via leurs systèmes de commande. Chaque plat, chaque option, chaque prix réel ; l'historique est conservé plat par plat, alors quand le trio monte d'un dollar, ça se voit.",
214 − chips: ["540+ restaurants", "61 000+ plats & prix", "14 régions"],
215 − features: [
216 − "Recherche par plat, groupée par chaîne — qui fait quoi, à quel prix",
217 − "Prix réels lus dans les systèmes de commande, jamais des PDF périmés",
218 − "Historique des prix par plat et alertes de dérive",
219 − "98 % des plats en photo, options et suppléments détaillés",
220 − ],
221 − isNew: true,
222 − },
223 182 {
224 183 num: "10",
225 184 markLeft: "Sorti",
@@ -241,27 +200,6 @@ const PRODUCTS: Product[] = [
241 200 ],
242 201 isNew: true,
243 202 },
244 − {
245 − num: "11",
246 − markLeft: "Créa",
247 − markBoxed: "Ka",
248 − accent: "#7da2ff",
249 − inkbox: "#10162b",
250 − field: "Créateurs",
251 − domain: "www.crea-ka.com",
252 − url: "https://www.crea-ka.com",
253 − tagline: "Tous les créateurs québécois. Tous leurs liens.",
254 − description:
255 − "L'annuaire public des créateurs de contenu d'ici : YouTube, Instagram, TikTok, Twitch, balados et plus, regroupés créateur par créateur. La résolution d'identité relie les comptes d'une même personne — jamais par le nom seul, les homonymes existent — et l'annuaire se retire sur simple demande, mineurs exclus d'office.",
256 − chips: ["2 200+ créateurs", "2 700+ comptes reliés", "10 plateformes"],
257 − features: [
258 − "Recherche par niche, région, langue et taille d'audience (nano → méga)",
259 − "Tous les comptes d'un créateur regroupés sur une seule page",
260 − "Retrait immédiat et persistant sur demande — l'éthique avant l'index",
261 − "Données publiques seulement, mineurs exclus de l'annuaire",
262 − ],
263 − isNew: true,
264 − },
265 203 {
266 204 num: "12",
267 205 markLeft: "Job",
@@ -283,27 +221,6 @@ const PRODUCTS: Product[] = [
283 221 ],
284 222 isNew: true,
285 223 },
286 − {
287 − num: "13",
288 − markLeft: "Rent",
289 − markBoxed: "Ka",
290 − accent: "#2456e6",
291 − inkbox: "#0b1330",
292 − field: "Locations — Canada hors Québec",
293 − domain: "www.rent-ka.com",
294 − url: "https://www.rent-ka.com",
295 − tagline: "Tous les logements à louer du reste du Canada.",
296 − description:
297 − "Le pendant canadien de Lou·Ka, en anglais : agrégation continue des logements à louer de l'Ontario, de l'Alberta, de la Colombie-Britannique, des Prairies et de l'Atlantique — gestionnaires immobiliers, grands propriétaires et portails, avec juste valeur estimée, KA Scores et lien direct vers l'annonce originale.",
298 − chips: ["8 000+ annonces", "8 provinces", "600+ connecteurs", "EN"],
299 − features: [
300 − "Ontario, Alberta, C.-B., Prairies et Atlantique — synchronisation horaire",
301 − "Juste valeur estimée et historique de prix sur chaque fiche",
302 − "KA Scores (marche, transport, calme, services) calculés par immeuble",
303 − "Chaque annonce renvoie à la source originale du gestionnaire",
304 − ],
305 − isNew: true,
306 − },
307 224 ];
308 225
309 226 type Bot = {
@@ -327,8 +244,8 @@ const BOTS: Bot[] = [
327 244 url: "https://www.ka2.bot",
328 245 tagline: "Gardien immobilier & local.",
329 246 description:
330 − "ka·2 veille sur les connecteurs de Lou·Ka, Immo·Ka et Resto·Ka. Quand la supervision en déclare un cassé ou endormi, il dépêche une session Claude Code dans le dépôt de la plateforme, sur son nœud : diagnostic, correctif minimal, test en volume réel, commit signé — et rollback git automatique si ça empire.",
331 − chips: ["Lou·Ka · Immo·Ka · Resto·Ka", "Réparation autonome", "Flux public en direct"],
247 + "ka·2 veille sur les connecteurs de Lou·Ka et Immo·Ka. Quand la supervision en déclare un cassé ou endormi, il dépêche une session Claude Code dans le dépôt de la plateforme, sur son nœud : diagnostic, correctif minimal, test en volume réel, commit signé — et rollback git automatique si ça empire.",
248 + chips: ["Lou·Ka · Immo·Ka", "Réparation autonome", "Flux public en direct"],
332 249 features: [
333 250 "Détection en continu via la supervision api-ka (scan toutes les 5 minutes)",
334 251 "Répare dans le code réel de la plateforme, jamais à côté",
@@ -361,8 +278,8 @@ const BOTS: Bot[] = [
361 278 url: "https://www.ka6.bot",
362 279 tagline: "Gardien flagship — les gros volumes.",
363 280 description:
364 − "ka·6 tient le plus gros territoire : Fabri·Ka et ses ~1 700 connecteurs, Job·Ka, Créa·Ka — et toute nouvelle plateforme par défaut. Dès sa première garde, il a réparé des connecteurs que le web avait cassés : encodage corrompu, migrations de plateforme, API disparues.",
365 − chips: ["Fabri·Ka · Job·Ka · Créa·Ka", "~2 000 connecteurs sous garde", "Efforts commandés"],
281 + "ka·6 tient le plus gros territoire : Fabri·Ka et ses ~1 700 connecteurs, Job·Ka, Vrai-Prix — et toute nouvelle plateforme par défaut. Dès sa première garde, il a réparé des connecteurs que le web avait cassés : encodage corrompu, migrations de plateforme, API disparues.",
282 + chips: ["Fabri·Ka · Job·Ka · Vrai-Prix", "~2 000 connecteurs sous garde", "Efforts commandés"],
366 283 features: [
367 284 "File d'incidents priorisée : les connecteurs cassés passent avant les endormis",
368 285 "On peut lui commander un effort : nouveau connecteur ou enrichissement",
@@ -391,36 +308,18 @@ const MANIFESTO = [
391 308 ];
392 309
393 310 const socleRows = (m: LiveMetrics): [string, string, string, string][] => [
394 − [
395 − "Trouve·Ka",
396 − "Recherche — tout l'écosystème KA",
397 − `Crawler dédié + index plein texte — ${fmtPlus(m.trouveka.pages)} pages`,
398 − "www.trouve-ka.com",
399 − ],
400 311 [
401 312 "Lou·Ka",
402 313 "Location résidentielle",
403 314 `${m.louka.sources} connecteurs sources + synchronisation horaire`,
404 315 "www.lou-ka.com",
405 316 ],
406 − [
407 − "Rent·Ka",
408 − "Location résidentielle — Canada hors Québec",
409 − `${m.rentka.sources} connecteurs — 8 provinces, MàJ horaire`,
410 − "www.rent-ka.com",
411 − ],
412 317 [
413 318 "Immo·Ka",
414 319 "Propriétés à vendre",
415 320 `${m.immoka.sources} connecteurs d'agences + détection des ventes`,
416 321 "www.immo-ka.com",
417 322 ],
418 − [
419 − "House·Ka",
420 − "Propriétés à vendre — Canada hors Québec",
421 − `${m.houseka.sources} connecteurs + réseau Century 21 national`,
422 − "www.house-ka.com",
423 − ],
424 323 [
425 324 "Vrai-Prix",
426 325 "Résidentiel — tout le Québec",
@@ -451,12 +350,6 @@ const socleRows = (m: LiveMetrics): [string, string, string, string][] => [
451 350 `${m.foodka.sources} bannières — prix, soldes et prix unitaires`,
452 351 "www.food-ka.com",
453 352 ],
454 − [
455 − "Resto·Ka",
456 − "Restaurants — menus & prix",
457 − `${m.restoka.chains} chaînes intégrées — ${fmtPlus(m.restoka.items)} plats suivis`,
458 − "www.resto-ka.com",
459 − ],
460 353 [
461 354 "Sorti·Ka",
462 355 "Sorties & événements",
@@ -469,12 +362,6 @@ const socleRows = (m: LiveMetrics): [string, string, string, string][] => [
469 362 `${m.jobka.employers} employeurs suivis sur 9 plateformes ATS`,
470 363 "www.job-ka.com",
471 364 ],
472 − [
473 − "Créa·Ka",
474 − "Créateurs de contenu",
475 − "Annuaire multi-plateformes + résolution d'identité",
476 − "www.crea-ka.com",
477 − ],
478 365 ];
479 366
480 367 /* ------------------------------------------------------------------ */
@@ -609,9 +496,8 @@ function KaDefinition() {
609 496 <span className="gk-mono font-bold text-green">2.</span>
610 497 <span>
611 498 Marque de fabrique du groupe : Lou·Ka loge, Immo·Ka vend, Auto·Ka
612 − roule, Fabri·Ka fabrique, Food·Ka nourrit, Resto·Ka régale,
613 − Sorti·Ka divertit, Créa·Ka rassemble, Job·Ka embauche — et
614 − House·Ka et Rent·Ka portent le geste au reste du Canada.
499 + roule, Fabri·Ka fabrique, Food·Ka nourrit,
500 + Sorti·Ka divertit, Job·Ka embauche.
615 501 </span>
616 502 </li>
617 503 </ol>
@@ -631,8 +517,7 @@ function VitalsBand({ m }: { m: LiveMetrics }) {
631 517 const vitals: { v: string; l: string }[] = [
632 518 { v: fmtPlus(m.totals.items), l: "fiches en ligne, comptées à l'instant" },
633 519 { v: `${fmtInt(m.totals.connectors)}+`, l: "connecteurs sources actifs" },
634 − { v: "13", l: "plateformes + 1 moteur de recherche" },
635 − { v: fmtPlus(m.trouveka.pages), l: "pages indexées par Trouve·Ka" },
520 + { v: "8", l: "plateformes" },
636 521 { v: fmtInt(m.vraiprix.units), l: "propriétés évaluées au Québec" },
637 522 { v: "22 M+", l: "observations immobilières" },
638 523 ];
@@ -674,24 +559,12 @@ function liveChips(m: LiveMetrics): Record<string, string[]> {
674 559 `Loyer moyen ${fmtInt(m.louka.avgPrice)} $`,
675 560 "MàJ horaire",
676 561 ],
677 − "www.rent-ka.com": [
678 − `${fmtPlus(m.rentka.total)} annonces`,
679 − `${m.rentka.sources} connecteurs`,
680 − `Loyer moyen ${fmtInt(m.rentka.avgPrice)} $`,
681 − "8 provinces",
682 − ],
683 562 "www.immo-ka.com": [
684 563 `${fmtPlus(m.immoka.total)} propriétés`,
685 564 `${m.immoka.sources} connecteurs`,
686 565 `${fmtInt(m.immoka.cities)} villes`,
687 566 `Prix moyen ${fmtInt(m.immoka.avgPrice)} $`,
688 567 ],
689 − "www.house-ka.com": [
690 − `${fmtPlus(m.houseka.total)} fiches`,
691 − `${m.houseka.sources} connecteurs`,
692 − `${fmtInt(m.houseka.cities)} villes`,
693 − `Prix moyen ${fmtInt(m.houseka.avgPrice)} $`,
694 − ],
695 568 "www.auto-ka.com": [
696 569 `${fmtPlus(m.autoka.total)} véhicules`,
697 570 `${m.autoka.sources} concessionnaires`,
@@ -708,22 +581,11 @@ function liveChips(m: LiveMetrics): Record<string, string[]> {
708 581 `${m.foodka.sources} bannières`,
709 582 `${fmtPlus(m.foodka.onSale)} soldes actifs`,
710 583 ],
711 − "www.resto-ka.com": [
712 − `${fmtPlus(m.restoka.restaurants)} restaurants`,
713 − `${fmtPlus(m.restoka.items)} plats & prix`,
714 − `${m.restoka.regions} régions`,
715 − ],
716 584 "www.sorti-ka.com": [
717 585 `${fmtPlus(m.sortika.active)} événements`,
718 586 `${fmtPlus(m.sortika.free)} sorties gratuites`,
719 587 `${fmtInt(m.sortika.cities)} villes`,
720 588 ],
721 − "www.crea-ka.com": [
722 − `${fmtPlus(m.creaka.creators)} créateurs`,
723 − `${fmtPlus(m.creaka.accounts)} comptes reliés`,
724 − `${m.creaka.platforms} plateformes`,
725 − `${m.creaka.niches} niches`,
726 − ],
727 589 "www.job-ka.com": [
728 590 `${fmtPlus(m.jobka.total)} offres`,
729 591 `${m.jobka.employers} employeurs`,
@@ -801,13 +663,10 @@ function ProductRow({ p }: { p: Product }) {
801 663 function SyncHealth({ m }: { m: LiveMetrics }) {
802 664 const rows = [
803 665 { name: "Lou·Ka", syncs: m.louka.syncs },
804 − { name: "Rent·Ka", syncs: m.rentka.syncs },
805 666 { name: "Immo·Ka", syncs: m.immoka.syncs },
806 − { name: "House·Ka", syncs: m.houseka.syncs },
807 667 { name: "Auto·Ka", syncs: m.autoka.syncs },
808 668 { name: "Fabri·Ka", syncs: m.fabrika.syncs },
809 669 { name: "Food·Ka", syncs: m.foodka.syncs },
810 − { name: "Resto·Ka", syncs: m.restoka.syncs },
811 670 { name: "Job·Ka", syncs: m.jobka.syncs },
812 671 ].filter((r): r is { name: string; syncs: NonNullable<LiveMetrics["louka"]["syncs"]> } =>
813 672 r.syncs !== null,
@@ -861,13 +720,6 @@ function SyncHealth({ m }: { m: LiveMetrics }) {
861 720 /* ------------------------------------------------------------------ */
862 721
863 722 const MARQUEE_BRANDS = [
864 − {
865 − left: "Trouve",
866 − boxed: "Ka",
867 − accent: "#b9cfee",
868 − inkbox: "#141814",
869 − url: "https://www.trouve-ka.com",
870 − },
871 723 ...PRODUCTS.map((p) => ({
872 724 left: p.markLeft,
873 725 boxed: p.markBoxed,
@@ -967,124 +819,6 @@ export default async function Home() {
967 819 {/* ================= Signes vitaux (bande pleine largeur) ================= */}
968 820 <VitalsBand m={m} />
969 821
970 − {/* ================= Trouve·Ka — le produit phare ================= */}
971 − <Section
972 − id="trouve-ka"
973 − index="01"
974 − kicker="Le produit phare · moteur de recherche"
975 − title={
976 − <>
977 − Trouve·Ka. <span className="hl">Cherche tout le groupe.</span>
978 − </>
979 − }
980 − lead={
981 − <>
982 − Les agrégateurs ·Ka couvrent chacun un marché. Trouve·Ka les
983 − rassemble : c&apos;est le moteur de recherche du groupe, dont
984 − l&apos;index est bâti exclusivement sur les sites de
985 − l&apos;écosystème — son robot les parcourt jour et nuit, guidé par
986 − leurs sitemaps, et n&apos;indexe qu&apos;eux.
987 − </>
988 − }
989 − >
990 − <div className="mt-10 grid items-start gap-12 lg:grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)] lg:gap-14">
991 − <div className="min-w-0">
992 − <div className="flex flex-wrap items-baseline justify-between gap-3">
993 − <span className="gk-mono text-[12px] font-bold text-green">
994 − MOTEUR
995 − </span>
996 − <span className="klabel">www.trouve-ka.com</span>
997 − </div>
998 − <h3 className="gk-display mt-3 inline-flex items-baseline gap-[5px] text-[34px] font-bold tracking-[-0.04em] sm:text-[42px]">
999 − Trouve
1000 − <span
1001 − className="inline-block -rotate-2 rounded-md px-[8px] pb-[3px]"
1002 − style={{ background: "#141814", color: "#b9cfee" }}
1003 − >
1004 − Ka
1005 − </span>
1006 − </h3>
1007 − <p className="gk-display mt-2 text-[17px] font-bold uppercase tracking-[-0.01em]">
1008 − Tout l&apos;écosystème KA, dans une seule barre de recherche.
1009 − </p>
1010 − {/* Barre de recherche factice — l'objet lui-même */}
1011 − <div
1012 − aria-hidden="true"
1013 − className="mt-6 flex items-center gap-3 rounded-full border-2 border-ink bg-white px-5 py-3 shadow-[4px_4px_0_rgba(20,24,20,0.8)]"
1014 − >
1015 − <span className="gk-mono text-[15px] font-bold">⌕</span>
1016 − <span className="gk-mono flex-1 truncate text-[12.5px] text-ink-3">
1017 − 4½ à Limoilou · plex à vendre · emploi infirmier · resto Gaspésie…
1018 − </span>
1019 − <span
1020 − className="gk-display rounded-full bg-ink px-4 py-[5px] text-[12.5px] font-bold"
1021 − style={{ color: "#b9cfee" }}
1022 − >
1023 − Chercher
1024 − </span>
1025 − </div>
1026 − <DataLine
1027 − className="mt-5"
1028 − items={[
1029 − `${fmtPlus(m.trouveka.pages)} pages indexées`,
1030 − `${fmtInt(m.trouveka.domains)} sites du groupe`,
1031 − `${fmtInt(m.trouveka.indexedLastHour)} pages/heure`,
1032 − "100 % Groupe KA",
1033 − ]}
1034 − />
1035 − <DiamondList
1036 − className="rule-hair mt-5 pt-5"
1037 − items={[
1038 − "Résultats 100 % Groupe KA — logements, propriétés, emplois, restos, événements, produits, véhicules, créateurs",
1039 − `État du moteur public : ${fmtInt(m.trouveka.frontierPending)} pages en file de crawl, ${fmtDec(m.trouveka.embeddingCoverage * 100)} % de l'index couvert par la recherche sémantique`,
1040 − "Une fiche publiée sur un site ·Ka devient cherchable en quelques secondes",
1041 − "Robot dédié, documenté, qui respecte robots.txt",
1042 − ]}
1043 − />
1044 − <div className="mt-7 flex flex-wrap items-center gap-x-7 gap-y-4">
1045 − <a
1046 − href="https://www.trouve-ka.com"
1047 − target="_blank"
1048 − rel="noopener noreferrer"
1049 − className="btn btn-primary"
1050 − >
1051 − Chercher sur trouve-ka.com ↗
1052 − </a>
1053 − <a href="/recherche" className="cta-link">
1054 − Chercher sans quitter groupe-ka.com
1055 − </a>
1056 − </div>
1057 − </div>
1058 − <div className="band-ink rounded-[18px] p-6 sm:p-8">
1059 − <p className="kicker !text-lime">
1060 − Pourquoi un moteur de recherche ?
1061 − </p>
1062 − <p className="gk-display mt-5 text-[clamp(24px,3.2vw,34px)] leading-[1.05] font-bold tracking-[-0.03em] uppercase">
1063 − Les grands moteurs
1064 − <br />
1065 − cherchent le monde.
1066 − <br />
1067 − <span className="text-lime">Trouve·Ka cherche le Groupe KA.</span>
1068 − </p>
1069 − <p className="mt-4 max-w-md text-[13.5px] leading-relaxed text-[rgba(245,243,238,0.75)]">
1070 − Un 4½ sur Lou·Ka, une érablière sur Fabri·Ka, un festival sur
1071 − Sorti·Ka : des millions de fiches réparties sur les sites du
1072 − groupe — et une seule barre pour toutes les trouver. C&apos;est
1073 − l&apos;agrégation poussée à sa conclusion logique — un index
1074 − maison pour tout l&apos;écosystème.
1075 − </p>
1076 − <DiamondList
1077 − dark
1078 − className="mt-5 border-t border-[rgba(245,243,238,0.15)] pt-5"
1079 − items={[
1080 − "Crawler maison + index de recherche dédié",
1081 − "Nourri par les sitemaps des sites de l'écosystème",
1082 − "Zéro publicité, zéro profilage — comme tout le groupe",
1083 − ]}
1084 − />
1085 − </div>
1086 − </div>
1087 − </Section>
1088 822
1089 823 {/* ================= La figure d'agrégation ================= */}
1090 824 <Section
@@ -1147,27 +881,20 @@ export default async function Home() {
1147 881 <InventoryBars
1148 882 data={[
1149 883 { name: "Fabri·Ka", value: m.fabrika.products, unit: "produits québécois" },
1150 − { name: "House·Ka", value: m.houseka.total, unit: "propriétés — Canada" },
1151 − { name: "Resto·Ka", value: m.restoka.items, unit: "plats & prix réels" },
1152 884 { name: "Immo·Ka", value: m.immoka.total, unit: "propriétés à vendre" },
1153 − { name: "Rent·Ka", value: m.rentka.total, unit: "logements — Canada" },
1154 885 { name: "Food·Ka", value: m.foodka.total, unit: "prix d'épicerie" },
1155 886 { name: "Auto·Ka", value: m.autoka.total, unit: "véhicules d'occasion" },
1156 887 { name: "Sorti·Ka", value: m.sortika.active, unit: "événements & sorties" },
1157 888 { name: "Lou·Ka", value: m.louka.total, unit: "logements à louer" },
1158 − { name: "Créa·Ka", value: m.creaka.creators, unit: "créateurs d'ici" },
1159 889 { name: "Job·Ka", value: m.jobka.total, unit: "offres d'emploi" },
1160 890 ]}
1161 891 />
1162 892 <ConnectorMatrix
1163 893 data={[
1164 894 { name: "Lou·Ka", count: m.louka.sources, kind: "gestionnaires & portails" },
1165 − { name: "Rent·Ka", count: m.rentka.sources, kind: "gestionnaires — Canada" },
1166 895 { name: "Auto·Ka", count: m.autoka.sources, kind: "concessionnaires" },
1167 896 { name: "Food·Ka", count: m.foodka.sources, kind: "bannières" },
1168 897 { name: "Immo·Ka", count: m.immoka.sources, kind: "agences" },
1169 − { name: "House·Ka", count: m.houseka.sources, kind: "réseaux & courtiers — Canada" },
1170 − { name: "Resto·Ka", count: m.restoka.chains, kind: "chaînes de restos" },
1171 898 { name: "Job·Ka", count: m.jobka.employers, kind: "employeurs (pages carrières)" },
1172 899 { name: "Sorti·Ka", count: SORTIKA_CONNECTORS, kind: "billetteries & données ouvertes" },
1173 900 ]}
@@ -1255,9 +982,7 @@ export default async function Home() {
1255 982 <>
1256 983 Chaque agrégateur attaque un angle mort du quotidien : se loger,
1257 984 acheter, évaluer, rouler, consommer local, faire l&apos;épicerie,
1258 − manger au resto, sortir, suivre les créateurs d&apos;ici — au
1259 − Québec d&apos;abord, et jusqu&apos;au reste du Canada avec House·Ka
1260 − et Rent·Ka. Aucune saisie manuelle — les plateformes se
985 + sortir — au Québec. Aucune saisie manuelle — les plateformes se
1261 986 maintiennent elles-mêmes, annonce par annonce, prix par prix.
1262 987 </>
1263 988 }
deleted src/app/recherche/page.tsx +0 −235
@@ -1,235 +0,0 @@
1 −// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
2 −// /recherche — la recherche « tout l'écosystème KA », propulsée par le
3 −// moteur Trouve·Ka (pivot 2026-08 : Trouve·Ka devient le moteur de recherche
4 −// du Groupe KA et n'indexe plus que les 14 sites de l'écosystème).
5 −// Rendu 100 % serveur : formulaire GET, pagination et filtres par site en
6 −// liens. Échec réseau = repli poli, jamais de page cassée.
7 −import type { Metadata } from "next";
8 −import SearchBar from "../SearchBar";
9 −import { decodeEntities, sanitizeSnippet, searchTrouveKa } from "@/lib/search";
10 −import { fmtInt } from "@/lib/live";
11 −
12 −export const metadata: Metadata = {
13 − title: { absolute: "Recherche — Groupe KA" },
14 − description:
15 − "Recherchez dans tout l'écosystème KA — logements, propriétés, autos, produits d'ici, épicerie, restos, sorties, créateurs et emplois — avec le moteur Trouve·Ka.",
16 −};
17 −
18 −// Les 14 sites de l'écosystème (filtre par domaine du moteur).
19 −const SITES: [string, string][] = [
20 − ["www.lou-ka.com", "Lou·Ka"],
21 − ["www.immo-ka.com", "Immo·Ka"],
22 − ["www.house-ka.com", "House·Ka"],
23 − ["www.rent-ka.com", "Rent·Ka"],
24 − ["www.vrai-prix.com", "Vrai-Prix"],
25 − ["www.toit-ka.com", "Toit·Ka"],
26 − ["www.auto-ka.com", "Auto·Ka"],
27 − ["www.fabri-ka.com", "Fabri·Ka"],
28 − ["www.food-ka.com", "Food·Ka"],
29 − ["www.resto-ka.com", "Resto·Ka"],
30 − ["www.sorti-ka.com", "Sorti·Ka"],
31 − ["www.crea-ka.com", "Créa·Ka"],
32 − ["www.job-ka.com", "Job·Ka"],
33 − ["www.valoplex.com", "Valoplex"],
34 − ["www.groupe-ka.com", "Groupe KA"],
35 − ["www.trouve-ka.com", "Trouve·Ka"],
36 −];
37 −
38 −const PoweredBy = () => (
39 − <p className="gk-mono mt-4 text-[11.5px] text-ink-3">
40 − Propulsé par{" "}
41 − <a
42 − href="https://www.trouve-ka.com"
43 − target="_blank"
44 − rel="noopener noreferrer"
45 − className="font-bold text-green underline-offset-4 hover:underline"
46 − >
47 − Trouve·Ka
48 − </a>{" "}
49 − — le moteur de recherche du Groupe KA.
50 − </p>
51 −);
52 −
53 −export default async function RecherchePage({
54 − searchParams,
55 −}: {
56 − searchParams: Promise<{ q?: string; page?: string; site?: string }>;
57 −}) {
58 − const sp = await searchParams;
59 − const q = (sp.q ?? "").trim();
60 − const page = Math.max(1, Number.parseInt(sp.page ?? "1", 10) || 1);
61 − const site = SITES.some(([d]) => d === sp.site) ? sp.site : undefined;
62 −
63 − // Lien interne en conservant q / site / page voulus.
64 − const href = (over: { page?: number; site?: string | null }) => {
65 − const u = new URLSearchParams({ q });
66 − const s = over.site === undefined ? site : (over.site ?? undefined);
67 − if (s) u.set("site", s);
68 − if (over.page && over.page > 1) u.set("page", String(over.page));
69 − return `/recherche?${u.toString()}`;
70 − };
71 −
72 − /* ---- État d'accueil (pas de requête) ---- */
73 − if (!q) {
74 − return (
75 − <main className="mx-auto max-w-6xl px-4 py-14 sm:px-6">
76 − <p className="kicker">Recherche · tout l’écosystème</p>
77 − <h1 className="gk-display mt-3 max-w-3xl text-[clamp(28px,4.5vw,44px)] leading-[1.02] font-bold tracking-[-0.035em] uppercase">
78 − Rechercher dans tout <span className="hl">l’écosystème KA</span>
79 − </h1>
80 − <p className="mt-4 max-w-2xl text-[14.5px] text-ink-2">
81 − Un seul champ pour chercher sur les 14 sites du Groupe KA :
82 − logements, propriétés, autos, produits d’ici, épicerie, restos,
83 − sorties, créateurs et emplois.
84 − </p>
85 − <div className="mt-8">
86 − <SearchBar size="hero" />
87 − </div>
88 − <PoweredBy />
89 − </main>
90 − );
91 − }
92 −
93 − /* ---- Résultats ---- */
94 − const outcome = await searchTrouveKa(q, { page, site });
95 −
96 − return (
97 − <main className="mx-auto max-w-6xl px-4 py-12 sm:px-6">
98 − <p className="kicker">Recherche · tout l’écosystème</p>
99 − <h1 className="gk-display mt-3 text-[clamp(24px,4vw,36px)] leading-[1.05] font-bold tracking-[-0.035em] uppercase">
100 − Résultats pour «&nbsp;{q}&nbsp;»
101 − </h1>
102 −
103 − <div className="mt-6">
104 − <SearchBar size="hero" defaultQuery={q} site={site} />
105 − </div>
106 −
107 − {/* Filtres par site */}
108 − <div className="mt-6 flex flex-wrap gap-2" aria-label="Filtrer par site">
109 − <a
110 − href={href({ site: null })}
111 − className={!site ? "chip chip-accent" : "chip"}
112 − aria-current={!site ? "true" : undefined}
113 − >
114 − Tous
115 − </a>
116 − {SITES.map(([domain, label]) => (
117 − <a
118 − key={domain}
119 − href={href({ site: domain })}
120 − className={site === domain ? "chip chip-accent" : "chip"}
121 − aria-current={site === domain ? "true" : undefined}
122 − >
123 − {label}
124 − </a>
125 − ))}
126 − </div>
127 −
128 − {!outcome.ok ? (
129 − /* Repli : moteur injoignable — la page reste servie. */
130 − <div className="card mt-8 max-w-2xl p-6">
131 − <p className="gk-display text-[18px] font-bold">
132 − Le moteur de recherche est momentanément indisponible.
133 − </p>
134 − <p className="mt-2 text-[14px] text-ink-2">
135 − Réessayez dans un instant, ou cherchez directement sur{" "}
136 − <a
137 − href={`https://www.trouve-ka.com/?q=${encodeURIComponent(q)}`}
138 − target="_blank"
139 − rel="noopener noreferrer"
140 − className="font-bold text-green underline-offset-4 hover:underline"
141 − >
142 − trouve-ka.com
143 − </a>
144 − .
145 − </p>
146 − </div>
147 − ) : (
148 − <>
149 − <p className="gk-mono mt-6 text-[12px] text-ink-2">
150 − {fmtInt(outcome.data.total)}{" "}
151 − {outcome.data.total > 1 ? "résultats" : "résultat"} (
152 − {fmtInt(outcome.data.took_ms)}&nbsp;ms)
153 − </p>
154 −
155 − {outcome.data.results.length === 0 ? (
156 − <div className="card mt-6 max-w-2xl p-6">
157 − <p className="gk-display text-[18px] font-bold">
158 − Aucun résultat{site ? " sur ce site" : ""} pour «&nbsp;{q}&nbsp;».
159 − </p>
160 − <p className="mt-2 text-[14px] text-ink-2">
161 − Essayez d’autres mots-clés{site ? (
162 − <>
163 − {" "}
164 − ou{" "}
165 − <a
166 − href={href({ site: null, page: 1 })}
167 − className="font-bold text-green underline-offset-4 hover:underline"
168 − >
169 − cherchez sur tous les sites
170 − </a>
171 − </>
172 − ) : null}
173 − .
174 − </p>
175 − </div>
176 − ) : (
177 − <ol className="mt-6 flex max-w-3xl flex-col gap-4">
178 − {outcome.data.results.map((r) => (
179 − <li key={r.url} className="card p-5">
180 − <p className="gk-mono text-[11px] break-all text-ink-3">
181 − {r.display_url}
182 − </p>
183 − <h2 className="mt-1">
184 − <a
185 − href={r.url}
186 − target="_blank"
187 − rel="noopener noreferrer"
188 − className="gk-display text-[18px] leading-snug font-bold underline-offset-4 hover:underline"
189 − >
190 − {r.title ? decodeEntities(r.title) : r.url}
191 − </a>
192 − </h2>
193 − {r.snippet ? (
194 − <p
195 − className="mt-2 text-[13.5px] leading-relaxed text-ink-2 [&_em]:rounded-[3px] [&_em]:bg-[var(--lime)] [&_em]:px-[3px] [&_em]:font-semibold [&_em]:not-italic [&_em]:text-ink"
196 − dangerouslySetInnerHTML={{
197 − __html: sanitizeSnippet(r.snippet),
198 − }}
199 − />
200 − ) : null}
201 − <p className="mt-3">
202 − <span className="chip chip-soft">{r.domain}</span>
203 − </p>
204 − </li>
205 − ))}
206 − </ol>
207 − )}
208 −
209 − {/* Pagination */}
210 − <nav
211 − className="mt-8 flex max-w-3xl items-center justify-between gap-4"
212 − aria-label="Pagination des résultats"
213 − >
214 − {page > 1 ? (
215 − <a href={href({ page: page - 1 })} className="btn">
216 − ← Précédent
217 − </a>
218 − ) : (
219 − <span aria-hidden="true" />
220 − )}
221 − {page * outcome.data.limit < outcome.data.total ? (
222 − <a href={href({ page: page + 1 })} className="btn">
223 − Suivant →
224 − </a>
225 − ) : (
226 − <span aria-hidden="true" />
227 − )}
228 − </nav>
229 − </>
230 − )}
231 −
232 − <PoweredBy />
233 − </main>
234 − );
235 −}
modified src/app/retrait/page.tsx +1 −1
@@ -109,7 +109,7 @@ export default function Retrait() {
109 109 <p className="rise mt-5 max-w-2xl text-[15px] text-ink-2 [animation-delay:0.16s]">
110 110 La présente politique explique comment demander le retrait
111 111 d&apos;une annonce ou d&apos;une page indexée sur nos plateformes
112 − (Trouve·Ka, Lou·Ka, Immo·Ka, Auto·Ka, Fabri·Ka, Food·Ka, ka6), ainsi
112 + (Lou·Ka, Immo·Ka, Auto·Ka, Fabri·Ka, Food·Ka, ka6), ainsi
113 113 que la révision d&apos;une
114 114 estimation ou l&apos;arrêt de la couverture d&apos;une propriété sur
115 115 Vrai-Prix et ValoPlex.
modified src/ka/ecosystem.json +1 −51
@@ -54,16 +54,6 @@
54 54 "onAccent": "#141814",
55 55 "tagline": "Le portail de l'écosystème ·Ka"
56 56 },
57 − {
58 − "id": "trouve-ka",
59 − "wordmark": "Trouve·Ka",
60 − "domain": "www.trouve-ka.com",
61 − "accent": "#1c7ed6",
62 − "accentSoft": "#e7f2fd",
63 − "accentDeep": "#14508f",
64 − "onAccent": "#ffffff",
65 − "tagline": "Le moteur de recherche du Groupe KA"
66 − },
67 57 {
68 58 "id": "lou-ka",
69 59 "wordmark": "Lou·Ka",
@@ -84,26 +74,6 @@
84 74 "onAccent": "#ffffff",
85 75 "tagline": "Toutes les propriétés à vendre"
86 76 },
87 − {
88 − "id": "house-ka",
89 − "wordmark": "House·Ka",
90 − "domain": "www.house-ka.com",
91 − "accent": "#0f6b4f",
92 − "accentSoft": "#dcefe6",
93 − "accentDeep": "#0a4a37",
94 − "onAccent": "#ffffff",
95 − "tagline": "Maisons à vendre — Canada hors Québec (EN)"
96 − },
97 − {
98 − "id": "rent-ka",
99 − "wordmark": "Rent·Ka",
100 − "domain": "www.rent-ka.com",
101 − "accent": "#2456e6",
102 − "accentSoft": "#e9efff",
103 − "accentDeep": "#1738a8",
104 − "onAccent": "#ffffff",
105 − "tagline": "Locations résidentielles — Canada hors Québec (EN)"
106 − },
107 77 {
108 78 "id": "vrai-prix",
109 79 "wordmark": "Vrai-Prix",
@@ -144,16 +114,6 @@
144 114 "onAccent": "#ffffff",
145 115 "tagline": "Les prix d'épicerie, suivis à la source"
146 116 },
147 − {
148 − "id": "resto-ka",
149 − "wordmark": "Resto·Ka",
150 − "domain": "www.resto-ka.com",
151 − "accent": "#f08c00",
152 − "accentSoft": "#fdeed7",
153 − "accentDeep": "#b96a00",
154 − "onAccent": "#141814",
155 − "tagline": "Chaque resto, chaque plat, chaque prix"
156 − },
157 117 {
158 118 "id": "sorti-ka",
159 119 "wordmark": "Sorti·Ka",
@@ -164,16 +124,6 @@
164 124 "onAccent": "#ffffff",
165 125 "tagline": "Toutes les sorties, dans les 17 régions"
166 126 },
167 − {
168 − "id": "crea-ka",
169 − "wordmark": "Créa·Ka",
170 − "domain": "www.crea-ka.com",
171 − "accent": "#7048e8",
172 − "accentSoft": "#ece5fc",
173 − "accentDeep": "#5433b8",
174 − "onAccent": "#ffffff",
175 − "tagline": "Les créateurs d'ici, tous leurs liens"
176 − },
177 127 {
178 128 "id": "api-ka",
179 129 "wordmark": "API·Ka",
@@ -223,4 +173,4 @@
223 173 "href": "https://www.ka6.bot"
224 174 }
225 175 ]
226 −}
\ No newline at end of file
176 +}
modified src/lib/alerts.ts +3 −8
@@ -15,22 +15,17 @@ type Adapter = { base: string; path: string; small: Record<string, string>; tota
15 15 const ADAPTERS: Record<string, Adapter> = {
16 16 "lou-ka": { base: "https://www.lou-ka.com", path: "/api/listings", small: { limit: "1" }, total: "total" },
17 17 "immo-ka": { base: "https://www.immo-ka.com", path: "/api/listings", small: { limit: "1" }, total: "total" },
18 − "house-ka": { base: "https://www.house-ka.com", path: "/api/listings", small: { limit: "1" }, total: "total" },
19 18 "auto-ka": { base: "https://www.auto-ka.com", path: "/api/vehicles", small: { limit: "1" }, total: "total" },
20 19 "food-ka": { base: "https://www.food-ka.com", path: "/api/products", small: { limit: "1" }, total: "total" },
21 20 "fabri-ka": { base: "https://www.fabri-ka.com", path: "/api/products", small: { per_page: "1" }, total: "total" },
22 − "resto-ka": { base: "https://www.resto-ka.com", path: "/api/restaurants", small: { limit: "1" }, total: "total" },
23 21 "sorti-ka": { base: "https://www.sorti-ka.com", path: "/api/events", small: { limit: "1" }, total: "total" },
24 22 "job-ka": { base: "https://www.job-ka.com", path: "/api/jobs", small: { limit: "1" }, total: "total" },
25 − "rent-ka": { base: "https://www.rent-ka.com", path: "/api/listings", small: { limit: "1" }, total: "total" },
26 − "crea-ka": { base: "https://www.crea-ka.com", path: "/api/creators", small: { limit: "1" }, total: "total" },
27 23 };
28 24
29 25 const APP_LABELS: Record<string, string> = {
30 − "lou-ka": "Lou·Ka", "immo-ka": "Immo·Ka", "house-ka": "House·Ka", "auto-ka": "Auto·Ka",
31 − "food-ka": "Food·Ka", "fabri-ka": "Fabri·Ka", "resto-ka": "Resto·Ka",
32 − "sorti-ka": "Sorti·Ka", "job-ka": "Job·Ka", "crea-ka": "Créa·Ka",
33 − "rent-ka": "Rent·Ka",
26 + "lou-ka": "Lou·Ka", "immo-ka": "Immo·Ka", "auto-ka": "Auto·Ka",
27 + "food-ka": "Food·Ka", "fabri-ka": "Fabri·Ka",
28 + "sorti-ka": "Sorti·Ka", "job-ka": "Job·Ka",
34 29 };
35 30
36 31 type AlertRow = {
modified src/lib/comms/categories.ts +0 −3
@@ -66,7 +66,6 @@ export const CORE_FIELDS = new Set([
66 66
67 67 export const SITES_KA = [
68 68 "Groupe KA (portail)",
69 − "Trouve·Ka",
70 69 "Lou·Ka",
71 70 "Immo·Ka",
72 71 "Vrai-Prix",
@@ -74,9 +73,7 @@ export const SITES_KA = [
74 73 "Auto·Ka",
75 74 "Fabri·Ka",
76 75 "Food·Ka",
77 − "Resto·Ka",
78 76 "Sorti·Ka",
79 − "Créa·Ka",
80 77 "API·Ka",
81 78 "Job·Ka",
82 79 "Ka·Stats",
modified src/lib/email.ts +1 −1
@@ -123,7 +123,7 @@ function layout(opts: {
123 123 <td style="padding:20px 2px 0;font-family:${F_MONO};font-size:9.5px;font-weight:700;line-height:1.9;letter-spacing:0.1em;text-transform:uppercase;color:${INK_3};">
124 124 Un seul compte · tout l'écosystème<br>
125 125 <a href="https://www.groupe-ka.com" style="color:${GREEN};text-decoration:none;">www.groupe-ka.com</a>
126 − &nbsp;—&nbsp;Lou·Ka&nbsp;· Immo·Ka&nbsp;· Vrai-Prix&nbsp;· Auto·Ka&nbsp;· Food·Ka&nbsp;· Resto·Ka&nbsp;· Sorti·Ka&nbsp;· Créa·Ka&nbsp;· Job·Ka&nbsp;…
126 + &nbsp;—&nbsp;Lou·Ka&nbsp;· Immo·Ka&nbsp;· Vrai-Prix&nbsp;· Auto·Ka&nbsp;· Food·Ka&nbsp;· Sorti·Ka&nbsp;· Job·Ka&nbsp;…
127 127 </td>
128 128 </tr>
129 129
modified src/lib/live.ts +1 −92
@@ -103,19 +103,6 @@ export type LiveMetrics = {
103 103 };
104 104 syncs: SyncHealth;
105 105 };
106 − houseka: {
107 − total: number;
108 − sources: number;
109 − cities: number;
110 − avgPrice: number;
111 − syncs: SyncHealth;
112 − };
113 − rentka: {
114 − total: number;
115 − sources: number;
116 − avgPrice: number;
117 − syncs: SyncHealth;
118 − };
119 106 autoka: {
120 107 total: number;
121 108 sources: number;
@@ -139,13 +126,6 @@ export type LiveMetrics = {
139 126 avgPrice: number;
140 127 syncs: SyncHealth;
141 128 };
142 − restoka: {
143 − restaurants: number;
144 − items: number;
145 − chains: number;
146 − regions: number;
147 − syncs: SyncHealth;
148 − };
149 129 sortika: {
150 130 active: number;
151 131 free: number;
@@ -153,22 +133,6 @@ export type LiveMetrics = {
153 133 cities: number;
154 134 regions: number;
155 135 };
156 − creaka: {
157 − creators: number;
158 − accounts: number;
159 − platforms: number;
160 − niches: number;
161 − tiers: { nano: number; micro: number; macro: number; mega: number };
162 − };
163 − trouveka: {
164 − pages: number;
165 − domains: number;
166 − running: boolean;
167 − indexedLastHour: number;
168 − errorsLastHour: number;
169 − frontierPending: number;
170 − embeddingCoverage: number;
171 − };
172 136 vraiprix: {
173 137 units: number;
174 138 transactions: number;
@@ -214,21 +178,16 @@ const topCities = (o: Json | null): { city: string; n: number }[] => {
214 178 };
215 179
216 180 export async function getLiveMetrics(): Promise<LiveMetrics> {
217 − const [lou, immo, auto, fab, food, resto, sorti, crea, trouve, vp, job, house, rent] =
181 + const [lou, immo, auto, fab, food, sorti, vp, job] =
218 182 await Promise.all([
219 183 fetchJson("https://www.lou-ka.com/api/stats"),
220 184 fetchJson("https://www.immo-ka.com/api/stats"),
221 185 fetchJson("https://www.auto-ka.com/api/stats"),
222 186 fetchJson("https://www.fabri-ka.com/api/stats"),
223 187 fetchJson("https://www.food-ka.com/api/stats"),
224 − fetchJson("https://www.resto-ka.com/api/stats"),
225 188 fetchJson("https://www.sorti-ka.com/api/stats"),
226 − fetchJson("https://www.crea-ka.com/api/stats"),
227 − fetchJson("https://www.trouve-ka.com/api/status"),
228 189 fetchJson("https://www.vrai-prix.com/api/stats"),
229 190 fetchJson("https://www.job-ka.com/api/stats"),
230 − fetchJson("https://www.house-ka.com/api/stats"),
231 − fetchJson("https://www.rent-ka.com/api/stats"),
232 191 ]);
233 192
234 193 const m: LiveMetrics = {
@@ -257,19 +216,6 @@ export async function getLiveMetrics(): Promise<LiveMetrics> {
257 216 },
258 217 syncs: syncSummary(immo),
259 218 },
260 − houseka: {
261 − total: num(house, "total", 260279),
262 − sources: num(house, "sources", 30),
263 − cities: num(house, "cities", 5921),
264 − avgPrice: num(house, "avg_price", 934000),
265 − syncs: syncSummary(house),
266 − },
267 − rentka: {
268 − total: num(rent, "total", 8955),
269 − sources: num(rent, "sources", 78),
270 − avgPrice: num(rent, "avg_price", 1974),
271 − syncs: syncSummary(rent),
272 − },
273 219 autoka: {
274 220 total: num(auto, "total", 17098),
275 221 sources: num(auto, "sources", 125),
@@ -293,13 +239,6 @@ export async function getLiveMetrics(): Promise<LiveMetrics> {
293 239 avgPrice: num(food, "avg_price", 10.48),
294 240 syncs: syncSummary(food),
295 241 },
296 − restoka: {
297 − restaurants: num(resto, "restaurants", 14091),
298 − items: num(resto, "items", 79159),
299 − chains: num(resto, "chains", 24),
300 − regions: num(resto, "regions", 17),
301 − syncs: syncSummary(resto),
302 − },
303 242 sortika: {
304 243 active: num(sorti, "total_active", 15229),
305 244 free: num(sorti, "free_upcoming", 3159),
@@ -307,29 +246,6 @@ export async function getLiveMetrics(): Promise<LiveMetrics> {
307 246 cities: num(sorti, "cities", 428),
308 247 regions: keyCount(sorti, "by_region", 17),
309 248 },
310 − creaka: {
311 − creators: num(crea, "creators", 5103),
312 − accounts: num(crea, "accounts", 5664),
313 − platforms: keyCount(crea, "by_platform", 10),
314 − niches: keyCount(crea, "by_niche", 20),
315 − tiers: {
316 − nano: num(crea, "by_tier.nano", 4671),
317 − micro: num(crea, "by_tier.micro", 284),
318 − macro: num(crea, "by_tier.macro", 112),
319 − mega: num(crea, "by_tier.mega", 36),
320 − },
321 − },
322 − // Pivot 2026-08-23 : Trouve·Ka indexe les 14 sites du Groupe KA
323 − // (replis = instantané du jour du pivot, index en croissance continue).
324 − trouveka: {
325 − pages: num(trouve, "pages_indexed", 7200),
326 − domains: num(trouve, "domains_count", 14),
327 − running: (trouve?.crawler_state ?? "running") === "running",
328 − indexedLastHour: num(trouve, "indexed_last_hour", 6600),
329 − errorsLastHour: num(trouve, "errors_last_hour", 26),
330 − frontierPending: num(trouve, "frontier_pending", 4584180),
331 − embeddingCoverage: num(trouve, "embedding_coverage", 1.0),
332 − },
333 249 vraiprix: {
334 250 units: num(vp, "units_total", 3747008),
335 251 transactions: num(vp, "transactions", 745119),
@@ -354,25 +270,18 @@ export async function getLiveMetrics(): Promise<LiveMetrics> {
354 270
355 271 m.totals.connectors =
356 272 m.louka.sources +
357 − m.rentka.sources +
358 273 m.immoka.sources +
359 − m.houseka.sources +
360 274 m.autoka.sources +
361 275 m.foodka.sources +
362 − m.restoka.chains +
363 276 m.jobka.employers +
364 277 SORTIKA_CONNECTORS;
365 278 m.totals.items =
366 279 m.louka.total +
367 − m.rentka.total +
368 280 m.immoka.total +
369 − m.houseka.total +
370 281 m.autoka.total +
371 282 m.fabrika.products +
372 283 m.foodka.total +
373 − m.restoka.items +
374 284 m.sortika.active +
375 − m.creaka.creators +
376 285 m.jobka.total;
377 286
378 287 return m;
deleted src/lib/search.ts +0 −102
@@ -1,102 +0,0 @@
1 −// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
2 −// Client du moteur Trouve·Ka — la recherche de tout l'écosystème Groupe KA.
3 −// Contrat public : GET /api/search?q=…&page=…&limit=…&site=<domaine> →
4 −// { query, total, took_ms, page, limit, related[], results[] }.
5 −// Jamais de throw : tout échec (réseau, HTTP, JSON invalide) retourne
6 −// { ok: false } et la page affiche un repli poli — la recherche ne doit
7 −// JAMAIS faire planter le site mère.
8 −// ⚠️ Le snippet contient du HTML de surlignage (<em>) : il DOIT passer par
9 −// sanitizeSnippet() avant tout dangerouslySetInnerHTML.
10 −
11 −export type SearchResult = {
12 − title: string;
13 − url: string;
14 − display_url: string;
15 − snippet: string;
16 − domain: string;
17 − language: string;
18 − quebec_score: number;
19 − badges: string[];
20 − published_at: string | null;
21 − image: string | null;
22 −};
23 −
24 −export type SearchData = {
25 − query: string;
26 − total: number;
27 − took_ms: number;
28 − page: number;
29 − limit: number;
30 − related: string[];
31 − results: SearchResult[];
32 −};
33 −
34 −export type SearchOutcome = { ok: true; data: SearchData } | { ok: false };
35 −
36 −// URL de base surchargée en environnement (tests, bascule d'infra) :
37 −// TROUVEKA_SEARCH_URL=https://…/api/search
38 −const BASE_URL =
39 − process.env.TROUVEKA_SEARCH_URL || "https://www.trouve-ka.com/api/search";
40 −
41 −export async function searchTrouveKa(
42 − q: string,
43 − opts?: { page?: number; site?: string },
44 −): Promise<SearchOutcome> {
45 − try {
46 − const url = new URL(BASE_URL);
47 − url.searchParams.set("q", q);
48 − url.searchParams.set("page", String(opts?.page ?? 1));
49 − url.searchParams.set("limit", "10");
50 − // Filtre par domaine (en cours d'ajout côté moteur — inoffensif s'il
51 − // est ignoré : on filtre alors « tous les sites »).
52 − if (opts?.site) url.searchParams.set("site", opts.site);
53 − const res = await fetch(url.toString(), {
54 − cache: "no-store",
55 − signal: AbortSignal.timeout(6000),
56 − headers: { accept: "application/json" },
57 − });
58 − if (!res.ok) return { ok: false };
59 − const data = (await res.json()) as SearchData;
60 − if (!data || !Array.isArray(data.results)) return { ok: false };
61 − return { ok: true, data };
62 − } catch {
63 − return { ok: false };
64 − }
65 −}
66 −
67 −/**
68 − * Décode les entités HTML déjà présentes dans les textes du moteur
69 − * (&#x27;, &amp;, &lt;…) vers du texte brut. Sans ce décodage, le
70 − * ré-échappement de sanitizeSnippet doublerait l'encodage et afficherait
71 − * « &#x27; » littéralement (observé en prod 2026-08-23).
72 − */
73 −export function decodeEntities(s: string): string {
74 − const cp = (n: number) =>
75 − Number.isFinite(n) && n >= 0 && n <= 0x10ffff ? String.fromCodePoint(n) : "";
76 − return s
77 − .replace(/&#x([0-9a-f]+);/gi, (_, h: string) => cp(parseInt(h, 16)))
78 − .replace(/&#(\d+);/g, (_, d: string) => cp(parseInt(d, 10)))
79 − .replaceAll("&lt;", "<")
80 − .replaceAll("&gt;", ">")
81 − .replaceAll("&quot;", '"')
82 − .replaceAll("&apos;", "'")
83 − .replaceAll("&nbsp;", " ")
84 − .replaceAll("&amp;", "&");
85 −}
86 −
87 −/**
88 − * Assainit un snippet du moteur : décode les entités existantes, échappe
89 − * TOUT le HTML (&, <, >, guillemets) puis ne ré-autorise que les balises de
90 − * surlignage <em>/</em> ajoutées par Trouve·Ka. Résultat sûr pour
91 − * dangerouslySetInnerHTML.
92 − */
93 −export function sanitizeSnippet(html: string): string {
94 − return decodeEntities(html)
95 − .replaceAll("&", "&amp;")
96 − .replaceAll("<", "&lt;")
97 − .replaceAll(">", "&gt;")
98 − .replaceAll('"', "&quot;")
99 − .replaceAll("'", "&#39;")
100 − .replaceAll("&lt;em&gt;", "<em>")
101 − .replaceAll("&lt;/em&gt;", "</em>");
102 −}
modified src/lib/sso.ts +0 −25
@@ -17,21 +17,6 @@ export const SSO_CLIENTS: Record<
17 17 redirectPrefixes: ["https://www.immo-ka.com/"],
18 18 secretEnv: "KA_SSO_SECRET_IMMO_KA",
19 19 },
20 − "house-ka": {
21 − label: "House·Ka",
22 − redirectPrefixes: ["https://www.house-ka.com/"],
23 − secretEnv: "KA_SSO_SECRET_HOUSE_KA",
24 − },
25 − "rent-ka": {
26 − label: "Rent·Ka",
27 − redirectPrefixes: ["https://www.rent-ka.com/", "http://localhost:8125/"],
28 − secretEnv: "KA_SSO_SECRET_RENT_KA",
29 − },
30 − "crea-ka": {
31 − label: "Créa·Ka",
32 − redirectPrefixes: ["https://www.crea-ka.com/", "http://localhost:8160/"],
33 − secretEnv: "KA_SSO_SECRET_CREA_KA",
34 − },
35 20 "vrai-prix": {
36 21 label: "Vrai-Prix",
37 22 redirectPrefixes: ["https://www.vrai-prix.com/"],
@@ -72,11 +57,6 @@ export const SSO_CLIENTS: Record<
72 57 redirectPrefixes: ["https://www.sorti-ka.com/", "http://localhost:8120/"],
73 58 secretEnv: "KA_SSO_SECRET_SORTI_KA",
74 59 },
75 − "resto-ka": {
76 − label: "Resto·Ka",
77 − redirectPrefixes: ["https://www.resto-ka.com/", "http://localhost:8115/"],
78 − secretEnv: "KA_SSO_SECRET_RESTO_KA",
79 − },
80 60 "ka-android": {
81 61 label: "KA (app Android)",
82 62 redirectPrefixes: ["ka-android://auth"],
@@ -97,11 +77,6 @@ export const SSO_CLIENTS: Record<
97 77 redirectPrefixes: ["https://www.api-ka.com/", "http://localhost:8000/"],
98 78 secretEnv: "KA_SSO_SECRET_API_KA",
99 79 },
100 − "trouve-ka": {
101 − label: "Trouve·Ka",
102 − redirectPrefixes: ["https://www.trouve-ka.com/", "http://localhost:3000/"],
103 − secretEnv: "KA_SSO_SECRET_TROUVE_KA",
104 − },
105 80 };
106 81
107 82 /**
108 83