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)
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'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'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'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'ici, |
| 322 | − épicerie, restos, sorties, créateurs. Rien d'inventé, tout | |
| 308 | + épicerie, sorties. Rien d'inventé, tout | |
| 323 | 309 | est traçable.{" "} |
| 324 | 310 | <span className="text-lime"> |
| 325 | 311 | ·Ka, c'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'est le moteur de recherche du groupe, dont | |
| 984 | − l'index est bâti exclusivement sur les sites de | |
| 985 | − l'écosystème — son robot les parcourt jour et nuit, guidé par | |
| 986 | − leurs sitemaps, et n'indexe qu'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'é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'est | |
| 1073 | − l'agrégation poussée à sa conclusion logique — un index | |
| 1074 | − maison pour tout l'é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'épicerie, |
| 1258 | − manger au resto, sortir, suivre les créateurs d'ici — au | |
| 1259 | − Québec d'abord, et jusqu'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 « {q} » | |
| 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)} 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 « {q} ». | |
| 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'une annonce ou d'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'une |
| 114 | 114 | estimation ou l'arrêt de la couverture d'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 | − — Lou·Ka · Immo·Ka · Vrai-Prix · Auto·Ka · Food·Ka · Resto·Ka · Sorti·Ka · Créa·Ka · Job·Ka … | |
| 126 | + — Lou·Ka · Immo·Ka · Vrai-Prix · Auto·Ka · Food·Ka · Sorti·Ka · Job·Ka … | |
| 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 | − * (', &, <…) vers du texte brut. Sans ce décodage, le | |
| 70 | − * ré-échappement de sanitizeSnippet doublerait l'encodage et afficherait | |
| 71 | − * « ' » 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("<", "<") | |
| 80 | − .replaceAll(">", ">") | |
| 81 | − .replaceAll(""", '"') | |
| 82 | − .replaceAll("'", "'") | |
| 83 | − .replaceAll(" ", " ") | |
| 84 | − .replaceAll("&", "&"); | |
| 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("&", "&") | |
| 96 | − .replaceAll("<", "<") | |
| 97 | − .replaceAll(">", ">") | |
| 98 | − .replaceAll('"', """) | |
| 99 | − .replaceAll("'", "'") | |
| 100 | − .replaceAll("<em>", "<em>") | |
| 101 | − .replaceAll("</em>", "</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 | |