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%

Accueil v2 « HQ » : recomposition éditoriale majeure — héros géant, bandes d'encre pleine largeur, index des plateformes en filets, figures redessinées, nav épurée

Upgrade visuel sans toucher à la logique ni aux données live :
- primitives éditoriales (bleed/band-ink, rule-ink, sec-head, stat-huge, cta-link, nav-link, idx-row, ibar) — moins de boîtes, plus de typo et d'espace
- héros pleine largeur (clamp 44-118px) + bande « signes vitaux » sombre à 6 chiffres géants
- plateformes : cartes remplacées par un index éditorial (ProductRow sur filets)
- « salle des machines » et « boucle de garde » : bandes sombres data-storytelling
- figures SVG affinées (captions sur filet, barres hairline, chips de marque)
- header : soulignements animés + burger 2 barres ; menu mobile plein écran numéroté
- table socle en variante ouverte (src-wrap--open), CTA finaux en liens-flèches
Simon-Pierre Boucher committed 1 mo ago (Aug 25, 2026) parent 63a3513

5 changed files +825 −494

modified src/app/UserButton.tsx +1 −1
@@ -31,7 +31,7 @@ export default function UserButton({
31 31 <a
32 32 href={connected ? "/compte" : "/connexion"}
33 33 aria-label={connected ? "Mon compte KA ID" : "Se connecter"}
34 − className="flex h-11 w-11 items-center justify-center rounded-md border-[1.5px] border-ink bg-surface shadow-[3px_3px_0_rgba(20,24,20,0.25)] transition-colors hover:bg-lime"
34 + className="flex h-11 w-11 items-center justify-center rounded-full border-[1.5px] border-ink bg-surface transition-colors hover:bg-lime"
35 35 >
36 36 {connected && me!.avatarUrl ? (
37 37 // eslint-disable-next-line @next/next/no-img-element
modified src/app/figures.tsx +92 −65
@@ -45,7 +45,7 @@ function BrandChip({ name }: { name: string }) {
45 45
46 46 function FigCaption({ lead, children }: { lead: string; children: React.ReactNode }) {
47 47 return (
48 − <figcaption className="border-t-[1.5px] border-ink bg-surface-2 px-5 py-4 text-[13px] text-ink-2 sm:px-7">
48 + <figcaption className="rule-hair mt-5 max-w-2xl pt-3 text-[13px] leading-relaxed text-ink-2">
49 49 <span className="gk-mono font-bold text-green">{lead} — </span>
50 50 {children}
51 51 </figcaption>
@@ -89,7 +89,7 @@ export function AggregationFigure() {
89 89 );
90 90
91 91 return (
92 − <figure className="gk-card mt-7 overflow-hidden p-0">
92 + <figure className="mt-8 border-y-2 border-ink py-2">
93 93 <div className="overflow-x-auto">
94 94 <svg
95 95 viewBox={`0 0 ${W} ${H}`}
@@ -380,7 +380,8 @@ export function PipelineFigure() {
380 380 const H = PIPELINE_STEPS.length * rowH + 24;
381 381
382 382 return (
383 − <figure className="gk-card overflow-hidden p-0">
383 + <figure className="rule-ink pt-4">
384 + <p className="klabel">Fig. 02 · Le pipeline</p>
384 385 <div className="overflow-x-auto">
385 386 <svg
386 387 viewBox={`0 0 ${W} ${H}`}
@@ -535,7 +536,8 @@ export function CycleFigure() {
535 536 const R = 150;
536 537
537 538 return (
538 − <figure className="gk-card overflow-hidden p-0">
539 + <figure className="rule-ink pt-4">
540 + <p className="klabel">Fig. 03 · Le cycle</p>
539 541 <div className="flex justify-center">
540 542 <svg
541 543 viewBox={`0 0 ${S} ${S}`}
@@ -696,37 +698,57 @@ export function InventoryBars({
696 698 }) {
697 699 const rows = [...(data ?? INVENTORY)].sort((a, b) => b.value - a.value);
698 700 const max = Math.max(...rows.map((d) => d.value));
701 + const total = rows.reduce((s, d) => s + d.value, 0);
699 702 return (
700 − <figure className="gk-card overflow-hidden p-0">
701 − <div className="p-5 sm:p-7">
703 + <figure className="rule-ink pt-4">
704 + <div className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
702 705 <p className="klabel">Inventaire vivant · aujourd&apos;hui</p>
703 − <h3 className="gk-display mt-2 text-[17px] font-bold uppercase">
704 − Ce qui est en ligne en ce moment
705 − </h3>
706 − <div className="mt-6 space-y-5">
707 − {rows.map((d) => (
708 − <div key={d.name} title={`${d.name} — ${fmt(d.value)}+ ${d.unit}`}>
709 − <div className="flex items-baseline justify-between gap-3">
710 − <span className="flex items-center gap-2 text-[13px] font-bold">
706 + <p className="gk-mono tnum text-[11px] font-bold text-green">
707 + Σ {fmt(total)}+ fiches en ligne
708 + </p>
709 + </div>
710 + <h3 className="gk-display mt-2 text-[19px] font-bold tracking-[-0.02em] uppercase">
711 + Ce qui est en ligne en ce moment
712 + </h3>
713 + <div className="mt-6">
714 + {rows.map((d, i) => (
715 + <div
716 + key={d.name}
717 + title={`${d.name} — ${fmt(d.value)}+ ${d.unit}`}
718 + className={`py-[13px] ${i > 0 ? "rule-hair" : ""}`}
719 + >
720 + <div className="flex items-baseline justify-between gap-3">
721 + <span className="flex items-baseline gap-2.5 text-[13.5px] font-bold">
722 + <span className="gk-mono tnum text-[10.5px] font-medium text-ink-3">
723 + {String(i + 1).padStart(2, "0")}
724 + </span>
725 + <span className="flex items-center gap-2">
711 726 <BrandChip name={d.name} />
712 727 <span className="gk-display">{d.name}</span>
713 − <span className="gk-mono text-[10px] font-medium text-ink-3">
714 − {d.unit}
715 − </span>
716 728 </span>
717 − <span className="gk-mono text-[12.5px] font-bold text-ink">
718 − {fmt(d.value)}+
729 + <span className="gk-mono hidden text-[10px] font-medium text-ink-3 min-[420px]:inline">
730 + {d.unit}
719 731 </span>
720 − </div>
721 − <div className="mt-[6px] h-[14px] w-full rounded-r-[4px] bg-[rgba(20,24,20,0.06)]">
722 − <div
723 − className="bar-grow h-full rounded-r-[4px] bg-green"
724 − style={{ width: `${Math.max((d.value / max) * 100, 1.5)}%` }}
725 − />
726 − </div>
732 + </span>
733 + <span className="gk-mono tnum text-[13px] font-bold text-ink">
734 + {fmt(d.value)}
735 + <span className="text-green">+</span>
736 + </span>
727 737 </div>
728 − ))}
729 − </div>
738 + <div className="ibar-track mt-[7px]">
739 + <div
740 + className={`ibar-fill ${i === 0 ? "ibar-fill--lead" : ""}`}
741 + style={{
742 + width: `${Math.max((d.value / max) * 100, 1.2)}%`,
743 + animationDelay: `${i * 0.06}s`,
744 + }}
745 + />
746 + </div>
747 + <p className="gk-mono tnum mt-[5px] text-[9.5px] text-ink-3 min-[420px]:hidden">
748 + {d.unit}
749 + </p>
750 + </div>
751 + ))}
730 752 </div>
731 753 <FigCaption lead="L'INVENTAIRE">
732 754 chaque barre est vivante : elle grossit quand les connecteurs trouvent
@@ -760,38 +782,45 @@ export function ConnectorMatrix({
760 782 const rows = [...(data ?? CONNECTORS)].sort((a, b) => b.count - a.count);
761 783 const sum = total ?? rows.reduce((s, c) => s + c.count, 0);
762 784 return (
763 − <figure className="gk-card overflow-hidden p-0">
764 − <div className="p-5 sm:p-7">
785 + <figure className="rule-ink pt-4">
786 + <div className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
765 787 <p className="klabel">La flotte · un point = un connecteur</p>
766 − <h3 className="gk-display mt-2 text-[17px] font-bold uppercase">
767 − {fmt(sum)} connecteurs, dessinés un par un
768 − </h3>
769 − <div className="mt-6 space-y-6">
770 − {rows.map((c) => (
771 − <div key={c.name} title={`${c.name} — ${c.count} connecteurs (${c.kind})`}>
772 − <div className="flex items-baseline justify-between gap-3">
773 − <span className="flex items-center gap-2 text-[13px] font-bold">
774 − <BrandChip name={c.name} />
775 − <span className="gk-display">{c.name}</span>
776 − <span className="gk-mono text-[10px] font-medium text-ink-3">
777 − {c.kind}
778 − </span>
779 − </span>
780 − <span className="gk-mono text-[12.5px] font-bold">
781 − {c.count}
788 + <p className="gk-mono tnum text-[11px] font-bold text-green">
789 + Σ {fmt(sum)}+ connecteurs
790 + </p>
791 + </div>
792 + <h3 className="gk-display mt-2 text-[19px] font-bold tracking-[-0.02em] uppercase">
793 + La flotte, dessinée point par point
794 + </h3>
795 + <div className="mt-6">
796 + {rows.map((c, i) => (
797 + <div
798 + key={c.name}
799 + title={`${c.name} — ${c.count} connecteurs (${c.kind})`}
800 + className={`py-[13px] ${i > 0 ? "rule-hair" : ""}`}
801 + >
802 + <div className="flex items-baseline justify-between gap-3">
803 + <span className="flex items-center gap-2 text-[13.5px] font-bold">
804 + <BrandChip name={c.name} />
805 + <span className="gk-display">{c.name}</span>
806 + <span className="gk-mono text-[10px] font-medium text-ink-3">
807 + {c.kind}
782 808 </span>
783 − </div>
784 − <div className="mt-[8px] flex max-w-[460px] flex-wrap gap-[3px]">
785 − {Array.from({ length: c.count }, (_, i) => (
786 − <span
787 − key={i}
788 − className="h-[7px] w-[7px] rounded-[2px] bg-green"
789 − />
790 − ))}
791 − </div>
809 + </span>
810 + <span className="gk-mono tnum text-[13px] font-bold">
811 + {c.count}
812 + </span>
792 813 </div>
793 − ))}
794 − </div>
814 + <div className="mt-[8px] flex max-w-[460px] flex-wrap gap-[3px]">
815 + {Array.from({ length: c.count }, (_, j) => (
816 + <span
817 + key={j}
818 + className="h-[7px] w-[7px] rounded-[2px] bg-green"
819 + />
820 + ))}
821 + </div>
822 + </div>
823 + ))}
795 824 </div>
796 825 <FigCaption lead="LA FLOTTE">
797 826 chaque point est un petit programme qui connaît un site par cœur et le
@@ -834,23 +863,21 @@ export function CoverageTiles({ vraiPrixUnits }: { vraiPrixUnits?: number }) {
834 863 : c,
835 864 );
836 865 return (
837 − <div className="flex h-full flex-col gap-5">
866 + <div className="flex h-full flex-col gap-7">
838 867 {tiles.map((c) => (
839 − <article key={c.name} className="gk-card gk-card-hover flex-1 p-5 sm:p-7">
868 + <article key={c.name} className="rule-ink flex-1 pt-4">
840 869 <p className="klabel flex items-center gap-2">
841 870 <BrandChip name={c.name} />
842 871 {c.name} · couverture
843 872 </p>
844 − <p className="gk-display mt-3 text-[clamp(34px,4.5vw,48px)] leading-none font-bold tracking-[-0.03em]">
845 − {c.value}
846 − </p>
847 − <p className="gk-display mt-1 text-[14px] font-bold uppercase text-green">
873 + <p className="stat-huge tnum mt-3">{c.value}</p>
874 + <p className="gk-display mt-2 text-[14px] font-bold uppercase text-green">
848 875 {c.label}
849 876 </p>
850 − <p className="mt-2 text-[12.5px] text-ink-2">{c.sub}</p>
877 + <p className="mt-2 max-w-md text-[13px] text-ink-2">{c.sub}</p>
851 878 </article>
852 879 ))}
853 − <p className="gk-mono text-[11px] leading-relaxed text-ink-3">
880 + <p className="gk-mono rule-hair pt-3 text-[11px] leading-relaxed text-ink-3">
854 881 Couvrir n&apos;est pas agréger au rabais : chaque estimation garde ses
855 882 comparables, ses ajustements en dollars et sa fourchette.
856 883 </p>
modified src/app/globals.css +225 −5
@@ -35,8 +35,8 @@
35 35 --r-card: 10px;
36 36 --r-ctl: 6px;
37 37 --shadow-flat: 0 1px 2px rgba(20, 24, 20, 0.05);
38 − --shadow-off: 6px 6px 0 var(--ink);
39 − --shadow-off-soft: 8px 8px 0 rgba(20, 24, 20, 0.08);
38 + --shadow-off: 5px 5px 0 var(--ink);
39 + --shadow-off-soft: 6px 6px 0 rgba(20, 24, 20, 0.06);
40 40 }
41 41
42 42 @theme inline {
@@ -170,6 +170,200 @@ h4 {
170 170 transform: rotate(-1deg);
171 171 }
172 172
173 +/* ==================================================================
174 + V2 — PRIMITIVES ÉDITORIALES « HQ »
175 + Compositions ouvertes : filets d'encre, bandes pleine largeur,
176 + chiffres géants, liens-flèches. Moins de boîtes, plus de rythme.
177 + ================================================================== */
178 +
179 +/* Bande pleine largeur (sort du conteneur max-w) — body est en
180 + overflow-x: clip, le débord de la barre de défilement est absorbé. */
181 +.bleed {
182 + margin-left: calc(50% - 50vw);
183 + margin-right: calc(50% - 50vw);
184 +}
185 +.band-ink {
186 + background: var(--ink);
187 + color: var(--paper);
188 +}
189 +
190 +/* Filets éditoriaux */
191 +.rule-ink {
192 + border-top: 2px solid var(--ink);
193 +}
194 +.rule-hair {
195 + border-top: 1px solid var(--line);
196 +}
197 +
198 +/* En-tête de section : filet fort + kicker à gauche, index à droite */
199 +.sec-head {
200 + border-top: 2px solid var(--ink);
201 + padding-top: 14px;
202 + display: flex;
203 + align-items: baseline;
204 + justify-content: space-between;
205 + gap: 16px;
206 +}
207 +.sec-index {
208 + font-family: var(--font-mono);
209 + font-size: 11px;
210 + font-weight: 700;
211 + letter-spacing: 0.14em;
212 + color: var(--ink-3);
213 + white-space: nowrap;
214 +}
215 +
216 +/* Chiffres géants — narration par la donnée */
217 +.stat-huge {
218 + font-family: var(--font-display);
219 + font-weight: 700;
220 + letter-spacing: -0.045em;
221 + line-height: 0.95;
222 + font-variant-numeric: tabular-nums;
223 + font-size: clamp(38px, 6.5vw, 72px);
224 +}
225 +.stat-huge sup {
226 + font-size: 0.45em;
227 + letter-spacing: -0.02em;
228 + color: var(--green);
229 +}
230 +
231 +/* Lien-flèche : le CTA éditorial (remplace les gros rectangles) */
232 +.cta-link {
233 + display: inline-flex;
234 + align-items: center;
235 + gap: 10px;
236 + min-height: 44px;
237 + font-family: var(--font-display);
238 + font-size: 14.5px;
239 + font-weight: 700;
240 + letter-spacing: -0.01em;
241 + color: var(--ink);
242 + text-decoration: none;
243 + border-bottom: 2px solid var(--ink);
244 + padding: 2px 2px 4px;
245 + transition: border-color 0.15s ease, color 0.15s ease;
246 +}
247 +.cta-link::after {
248 + content: "→";
249 + font-family: var(--font-mono);
250 + font-weight: 700;
251 + transition: transform 0.18s ease;
252 +}
253 +.cta-link:hover {
254 + color: var(--green);
255 + border-color: var(--green);
256 +}
257 +.cta-link:hover::after {
258 + transform: translateX(6px);
259 +}
260 +.cta-link--ext::after {
261 + content: "↗";
262 +}
263 +.cta-link--ext:hover::after {
264 + transform: translate(4px, -3px);
265 +}
266 +.cta-link--paper {
267 + color: var(--paper);
268 + border-color: var(--lime);
269 +}
270 +.cta-link--paper:hover {
271 + color: var(--lime);
272 + border-color: var(--lime);
273 +}
274 +
275 +/* Nav header épurée : soulignement animé, pas de pilules */
276 +.nav-link {
277 + position: relative;
278 + display: inline-block;
279 + padding: 8px 9px;
280 + font-family: var(--font-display);
281 + font-size: 13.5px;
282 + font-weight: 700;
283 + letter-spacing: -0.01em;
284 + white-space: nowrap;
285 + color: var(--ink-2);
286 + text-decoration: none;
287 + transition: color 0.15s ease;
288 +}
289 +.nav-link::after {
290 + content: "";
291 + position: absolute;
292 + left: 9px;
293 + right: 9px;
294 + bottom: 3px;
295 + height: 2px;
296 + background: var(--ink);
297 + transform: scaleX(0);
298 + transform-origin: left center;
299 + transition: transform 0.18s ease;
300 +}
301 +.nav-link:hover {
302 + color: var(--ink);
303 +}
304 +.nav-link:hover::after {
305 + transform: scaleX(1);
306 +}
307 +
308 +/* Rangée d'index (plateformes) : composition ouverte, filet + survol */
309 +.idx-row {
310 + border-top: 1px solid rgba(20, 24, 20, 0.28);
311 + transition: background 0.18s ease;
312 +}
313 +.idx-row:last-child {
314 + border-bottom: 1px solid rgba(20, 24, 20, 0.28);
315 +}
316 +@media (hover: hover) {
317 + .idx-row:hover {
318 + background: rgba(255, 255, 255, 0.72);
319 + }
320 +}
321 +
322 +/* Barres d'inventaire premium : piste hairline, valeur tabulaire */
323 +.ibar-track {
324 + height: 10px;
325 + width: 100%;
326 + background: rgba(20, 24, 20, 0.07);
327 +}
328 +.ibar-fill {
329 + height: 100%;
330 + background: var(--ink);
331 + transform-origin: left center;
332 + animation: growx 0.9s cubic-bezier(0.2, 0.8, 0.2, 1) both;
333 +}
334 +.ibar-fill--lead {
335 + background: var(--green);
336 +}
337 +
338 +/* Données mono tabulaires (alignement Bloomberg) */
339 +.tnum {
340 + font-variant-numeric: tabular-nums;
341 +}
342 +
343 +/* Burger minimal (deux barres, zone 44 px) */
344 +.burger {
345 + display: flex;
346 + flex-direction: column;
347 + justify-content: center;
348 + gap: 7px;
349 + width: 44px;
350 + height: 44px;
351 + padding: 0 8px;
352 + cursor: pointer;
353 +}
354 +.burger i {
355 + display: block;
356 + height: 2px;
357 + background: var(--ink);
358 + transition: transform 0.18s ease;
359 +}
360 +.burger:hover i:first-child {
361 + transform: translateY(-1px);
362 +}
363 +.burger:hover i:last-child {
364 + transform: translateY(1px);
365 +}
366 +
173 367 /* ---------- Cartes ---------- */
174 368
175 369 .gk-card {
@@ -196,16 +390,18 @@ h4 {
196 390 justify-content: center;
197 391 gap: 8px;
198 392 min-height: 44px;
199 − padding: 10px 20px;
393 + padding: 10px 22px;
200 394 border: 1.5px solid var(--ink);
201 − border-radius: var(--r-ctl);
395 + border-radius: 999px;
202 396 font-family: var(--font-display);
203 397 font-size: 14px;
204 398 font-weight: 700;
399 + letter-spacing: -0.01em;
205 400 cursor: pointer;
206 401 text-decoration: none;
207 402 transition:
208 403 background 0.15s ease,
404 + color 0.15s ease,
209 405 transform 0.1s ease,
210 406 box-shadow 0.1s ease;
211 407 }
@@ -216,7 +412,7 @@ h4 {
216 412 .btn-primary {
217 413 background: var(--ink);
218 414 color: var(--lime);
219 − box-shadow: 4px 4px 0 rgba(20, 24, 20, 0.25);
415 + box-shadow: 3px 3px 0 rgba(20, 24, 20, 0.22);
220 416 }
221 417 .btn-primary:hover {
222 418 background: var(--green-deep);
@@ -816,6 +1012,30 @@ h4 {
816 1012 background: var(--surface-2);
817 1013 }
818 1014
1015 +/* Variante ouverte (accueil v2) : le tableau devient éditorial —
1016 + filets seulement, aucune boîte. Les pages loi-25/retrait gardent
1017 + la version encadrée d'origine. */
1018 +.src-wrap--open {
1019 + border: none;
1020 + border-radius: 0;
1021 + box-shadow: none;
1022 + background: transparent;
1023 + border-top: 2px solid var(--ink);
1024 +}
1025 +.src-wrap--open .src-table th {
1026 + background: transparent;
1027 + border-bottom: 2px solid var(--ink);
1028 + padding-left: 4px;
1029 + padding-right: 14px;
1030 +}
1031 +.src-wrap--open .src-table td {
1032 + padding-left: 4px;
1033 + padding-right: 14px;
1034 +}
1035 +.src-wrap--open .src-table tbody tr:hover {
1036 + background: rgba(255, 255, 255, 0.72);
1037 +}
1038 +
819 1039 /* ---------- Figure d'agrégation ---------- */
820 1040
821 1041 .fig-flow {
modified src/app/layout.tsx +17 −20
@@ -195,36 +195,35 @@ export default async function RootLayout({
195 195 type="application/ld+json"
196 196 dangerouslySetInnerHTML={{ __html: JSON.stringify(JSON_LD) }}
197 197 />
198 − <header className="sticky top-0 z-[var(--z-header)] border-b-2 border-ink bg-paper">
198 + <header className="sticky top-0 z-[var(--z-header)] border-b border-[rgba(20,24,20,0.35)] bg-paper/95 backdrop-blur-sm">
199 199 <div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4 sm:px-6">
200 200 <a href="#" className="group" aria-label="Groupe KA — accueil">
201 201 <Wordmark />
202 202 </a>
203 203 <nav aria-label="Navigation principale" className="hidden xl:block">
204 − {/* gap-0.5 + px-2.5 (au lieu de gap-1 + px-3) : la rangée était
205 − déjà plus large que le conteneur max-w-6xl sous ~1660 px —
206 − resserrée pour faire place à la recherche sans rien perdre. */}
207 − <ul className="flex items-center gap-0.5">
204 + <ul className="flex items-center">
208 205 {NAV_ITEMS.map((item) => (
209 206 <li key={item.href}>
210 − <a
211 − href={item.href}
212 − className="gk-display inline-block rounded-full border-[1.5px] border-transparent px-2.5 py-[7px] text-[13.5px] font-bold whitespace-nowrap text-ink-2 transition-colors hover:border-ink hover:text-ink"
213 − >
207 + <a href={item.href} className="nav-link">
214 208 {item.label}
215 209 </a>
216 210 </li>
217 211 ))}
218 − <li className="ml-1 hidden xl:flex">
212 + {/* Cluster d'actions : séparé de la nav par un filet vertical */}
213 + <li
214 + aria-hidden="true"
215 + className="mx-3 h-[22px] w-px self-center bg-[rgba(20,24,20,0.3)]"
216 + />
217 + <li className="hidden xl:flex">
219 218 <SearchBar />
220 219 </li>
221 − <li className="ml-1">
220 + <li className="ml-2">
222 221 <UserButton />
223 222 </li>
224 223 <li>
225 224 <a
226 225 href="mailto:contact@groupe-ka.com"
227 − className="gk-display ml-1 inline-block rounded-full border-[1.5px] border-ink bg-ink px-4 py-[7px] text-[13.5px] font-bold text-lime transition-colors hover:bg-green-deep"
226 + className="gk-display ml-2 inline-block rounded-full bg-ink px-4 py-[8px] text-[13.5px] font-bold text-lime transition-colors hover:bg-green-deep"
228 227 >
229 228 Écrivez-nous
230 229 </a>
@@ -232,7 +231,7 @@ export default async function RootLayout({
232 231 </ul>
233 232 </nav>
234 233 {/* Mobile : icône compte directement dans le header + menu 100 % CSS */}
235 − <div className="flex items-center gap-2 xl:hidden">
234 + <div className="flex items-center gap-1 xl:hidden">
236 235 <UserButton variant="icon" />
237 236 <input
238 237 type="checkbox"
@@ -240,11 +239,9 @@ export default async function RootLayout({
240 239 className="peer sr-only"
241 240 aria-label="Ouvrir le menu"
242 241 />
243 − <label
244 − htmlFor="gk-menu"
245 − className="gk-mono flex h-11 w-11 cursor-pointer items-center justify-center rounded-md border-[1.5px] border-ink text-[18px] shadow-[3px_3px_0_rgba(20,24,20,0.25)] select-none"
246 − >
247 − ☰
242 + <label htmlFor="gk-menu" className="burger select-none" aria-hidden="true">
243 + <i />
244 + <i />
248 245 </label>
249 246 </div>
250 247 </div>
@@ -255,11 +252,11 @@ export default async function RootLayout({
255 252 Piloté en CSS pur : body:has(#gk-menu:checked) .gk-mobile-overlay
256 253 (globals.css) — les <label htmlFor="gk-menu"> marchent à distance. */}
257 254 <div className="gk-mobile-overlay flex-col bg-paper">
258 − <div className="flex h-16 items-center justify-between border-b-2 border-ink px-4">
255 + <div className="flex h-16 items-center justify-between border-b border-[rgba(20,24,20,0.35)] px-4">
259 256 <Wordmark />
260 257 <label
261 258 htmlFor="gk-menu"
262 − className="flex h-11 w-11 cursor-pointer items-center justify-center rounded-md border-[1.5px] border-ink text-[18px] shadow-[3px_3px_0_rgba(20,24,20,0.25)] select-none"
259 + className="gk-display flex h-11 w-11 cursor-pointer items-center justify-center text-[22px] font-bold select-none"
263 260 >
264 261 ✕
265 262 </label>
modified src/app/page.tsx +490 −403
@@ -1,4 +1,6 @@
1 1 // Auteur : Simon-Pierre Boucher — contact@spboucher.ai
2 +// Accueil v2 « HQ » — composition éditoriale ouverte : filets d'encre,
3 +// bandes pleine largeur, chiffres géants, index des plateformes.
2 4 import type { ReactNode } from "react";
3 5 import {
4 6 AggregationFigure,
@@ -422,37 +424,121 @@ const socleRows = (m: LiveMetrics): [string, string, string, string][] => [
422 424 ],
423 425 ];
424 426
427 +/* ------------------------------------------------------------------ */
428 +/* Composants éditoriaux v2 */
429 +/* ------------------------------------------------------------------ */
430 +
425 431 function Section({
426 432 id,
433 + index,
427 434 kicker,
428 435 title,
436 + lead,
429 437 children,
430 438 }: {
431 439 id?: string;
440 + index: string;
432 441 kicker: string;
433 442 title: ReactNode;
443 + lead?: ReactNode;
434 444 children: ReactNode;
435 445 }) {
436 446 return (
437 − <section id={id} className="mt-16 scroll-mt-24 sm:mt-20">
438 − <p className="kicker">{kicker}</p>
439 − <h2 className="gk-display mt-2 text-[22px] font-bold tracking-[-0.03em] uppercase sm:text-[26px]">
447 + <section id={id} className="mt-20 scroll-mt-24 sm:mt-28">
448 + <div className="sec-head">
449 + <p className="kicker">{kicker}</p>
450 + <span className="sec-index">§ {index}</span>
451 + </div>
452 + <h2 className="gk-display mt-6 max-w-3xl text-[clamp(27px,4.6vw,50px)] leading-[1.02] font-bold tracking-[-0.035em] uppercase">
440 453 {title}
441 454 </h2>
455 + {lead ? (
456 + <p className="mt-4 max-w-xl text-[15px] leading-relaxed text-ink-2">
457 + {lead}
458 + </p>
459 + ) : null}
442 460 {children}
443 461 </section>
444 462 );
445 463 }
446 464
465 +/** Ligne de données mono — remplace les rangées de pilules. */
466 +function DataLine({
467 + items,
468 + dark = false,
469 + className = "",
470 +}: {
471 + items: string[];
472 + dark?: boolean;
473 + className?: string;
474 +}) {
475 + return (
476 + <p
477 + className={`gk-mono tnum text-[11.5px] leading-[2] font-bold ${
478 + dark ? "text-[rgba(245,243,238,0.75)]" : "text-ink-2"
479 + } ${className}`}
480 + >
481 + {items.map((it, i) => (
482 + <span key={it} className="whitespace-nowrap">
483 + {i > 0 && (
484 + <span
485 + aria-hidden="true"
486 + className={`mx-2.5 ${dark ? "text-lime" : "text-green"}`}
487 + >
488 + ◆
489 + </span>
490 + )}
491 + {it}
492 + </span>
493 + ))}
494 + </p>
495 + );
496 +}
497 +
498 +/** Liste à puces losange — le marqueur signature du groupe. */
499 +function DiamondList({
500 + items,
501 + accent = "#d9f26b",
502 + dark = false,
503 + className = "",
504 +}: {
505 + items: string[];
506 + accent?: string;
507 + dark?: boolean;
508 + className?: string;
509 +}) {
510 + return (
511 + <ul className={`space-y-2.5 ${className}`}>
512 + {items.map((f) => (
513 + <li
514 + key={f}
515 + className={`flex gap-3 text-[13.5px] leading-relaxed ${
516 + dark ? "text-[rgba(245,243,238,0.75)]" : "text-ink-2"
517 + }`}
518 + >
519 + <span
520 + aria-hidden="true"
521 + className={`mt-[7px] h-[5px] w-[5px] flex-none rotate-45 ${
522 + dark ? "" : "border border-ink"
523 + }`}
524 + style={{ background: accent }}
525 + />
526 + {f}
527 + </li>
528 + ))}
529 + </ul>
530 + );
531 +}
532 +
447 533 /* ------------------------------------------------------------------ */
448 534 /* Entrée de dictionnaire — l'allusion fondatrice : ·Ka = agréger. */
449 535 /* ------------------------------------------------------------------ */
450 536
451 537 function KaDefinition() {
452 538 return (
453 − <aside className="gk-card gk-card-hover rise p-6 sm:p-7 [animation-delay:0.3s]">
539 + <aside className="rise border-l-2 border-ink pl-5 [animation-delay:0.3s] sm:pl-7">
454 540 <p className="klabel">Le nom, expliqué</p>
455 − <p className="gk-display mt-3 text-[30px] font-bold tracking-[-0.03em]">
541 + <p className="gk-display mt-3 text-[32px] font-bold tracking-[-0.03em]">
456 542 ·Ka{" "}
457 543 <span className="gk-mono align-middle text-[13px] font-medium text-ink-3">
458 544 /ka/ suffixe
@@ -475,112 +561,53 @@ function KaDefinition() {
475 561 </span>
476 562 </li>
477 563 </ol>
478 − <p className="mt-5 border-t-[1.5px] border-dashed border-[rgba(20,24,20,0.25)] pt-4 text-[13px] italic text-ink-2">
479 − « Tout ce qui finit en <span className="gk-display font-bold not-italic">·Ka</span>{" "}
480 − agrège. »
564 + <p className="rule-hair mt-5 pt-4 text-[13px] italic text-ink-2">
565 + « Tout ce qui finit en{" "}
566 + <span className="gk-display font-bold not-italic">·Ka</span> agrège. »
481 567 </p>
482 568 </aside>
483 569 );
484 570 }
485 571
486 −function ProductCard({
487 − p,
488 − index,
489 − wide = false,
490 −}: {
491 − p: Product;
492 − index: number;
493 − wide?: boolean;
494 −}) {
572 +/* ------------------------------------------------------------------ */
573 +/* Bande de signes vitaux — les chiffres live, pleine largeur */
574 +/* ------------------------------------------------------------------ */
575 +
576 +function VitalsBand({ m }: { m: LiveMetrics }) {
577 + const vitals: { v: string; l: string }[] = [
578 + { v: fmtPlus(m.totals.items), l: "fiches en ligne, comptées à l'instant" },
579 + { v: `${fmtInt(m.totals.connectors)}+`, l: "connecteurs sources actifs" },
580 + { v: "10", l: "agrégateurs + 1 moteur de recherche" },
581 + { v: fmtPlus(m.trouveka.pages), l: "pages indexées par Trouve·Ka" },
582 + { v: fmtInt(m.vraiprix.units), l: "propriétés évaluées au Québec" },
583 + { v: "22 M+", l: "observations immobilières" },
584 + ];
495 585 return (
496 − <article
497 − className={`gk-card gk-card-hover rise relative overflow-hidden p-5 pt-6 sm:p-7 sm:pt-8 ${
498 − wide ? "sm:col-span-2 lg:col-span-3" : ""
499 − }`}
500 − style={{ animationDelay: `${0.1 + index * 0.1}s` }}
501 − >
502 − {/* bande d'accent de la marque, en tête de carte */}
503 − <span
504 − aria-hidden="true"
505 − className="absolute inset-x-0 top-0 h-[6px]"
506 − style={{ background: p.accent }}
507 − />
508 − <div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-2">
509 − <span className="flex flex-wrap items-center gap-2">
510 − <span
511 − className="gk-mono inline-block -rotate-2 rounded-[5px] px-[7px] py-[2px] text-[11px] font-bold"
512 − style={{ background: p.inkbox, color: p.accent }}
513 − >
514 − {p.num}
515 − </span>
516 − <span className="klabel">{p.field}</span>
517 − {p.isNew && (
518 − <span className="gk-mono inline-block rotate-1 rounded-[5px] border border-ink bg-lime px-[7px] py-[2px] text-[10px] font-bold tracking-[0.08em] uppercase">
519 − Nouveau
520 − </span>
521 − )}
522 − </span>
523 − <span className="klabel">{p.domain}</span>
524 − </div>
525 − <div
526 − className={
527 − wide
528 − ? "lg:grid lg:grid-cols-[minmax(0,1.15fr)_minmax(0,1fr)] lg:gap-x-10"
529 − : ""
530 − }
531 − >
532 − <div>
533 − <h3 className="gk-display mt-4 inline-flex items-baseline gap-[5px] text-[32px] font-bold tracking-[-0.04em] sm:text-[38px]">
534 − {p.markLeft}
535 − <span
536 − className="inline-block -rotate-2 rounded-md px-[8px] pb-[3px]"
537 − style={{ background: p.inkbox, color: p.accent }}
538 − >
539 − {p.markBoxed}
540 − </span>
541 − </h3>
542 − <p className="gk-display mt-3 text-[17px] font-bold uppercase tracking-[-0.01em]">
543 − {p.tagline}
586 + <section aria-label="Signes vitaux de l'écosystème" className="bleed band-ink mt-12 sm:mt-16">
587 + <div className="mx-auto max-w-6xl px-4 py-9 sm:px-6 sm:py-12">
588 + <div className="flex flex-wrap items-center justify-between gap-x-6 gap-y-1 border-b border-[rgba(245,243,238,0.18)] pb-4">
589 + <p className="gk-mono flex items-center gap-2.5 text-[10.5px] font-bold tracking-[0.14em] uppercase text-lime">
590 + <span className="pulse-dot !bg-lime" aria-hidden="true" />
591 + Signes vitaux · mesurés sur les plateformes
544 592 </p>
545 − <p className="mt-3 text-[13.5px] leading-relaxed text-ink-2">
546 − {p.description}
593 + <p className="gk-mono text-[10px] tracking-[0.1em] uppercase text-[rgba(245,243,238,0.45)]">
594 + Lu en direct · cache 10 min
547 595 </p>
548 − <div className="mt-5 flex flex-wrap gap-2">
549 − {p.chips.map((chip) => (
550 − <span key={chip} className="stat-chip !text-[11px]">
551 − {chip}
552 − </span>
553 − ))}
554 − </div>
555 596 </div>
556 − <div>
557 − <ul
558 − className={`mt-5 space-y-2 border-t-[1.5px] border-dashed border-[rgba(20,24,20,0.25)] pt-5 ${
559 − wide ? "lg:mt-4 lg:border-t-0 lg:pt-0" : ""
560 − }`}
561 − >
562 − {p.features.map((f) => (
563 − <li key={f} className="flex gap-3 text-[13.5px] text-ink-2">
564 − <span
565 − aria-hidden="true"
566 − className="mt-[7px] h-[5px] w-[5px] flex-none rotate-45 border border-ink"
567 − style={{ background: p.accent }}
568 − />
569 − {f}
570 − </li>
571 − ))}
572 − </ul>
573 − <a
574 − href={p.url}
575 − target="_blank"
576 − rel="noopener noreferrer"
577 − className="btn btn-primary mt-6 w-full sm:w-auto"
578 − >
579 − Visiter {p.domain} ↗
580 − </a>
597 + <div className="mt-8 grid grid-cols-2 gap-x-6 gap-y-8 sm:grid-cols-3 lg:grid-cols-6">
598 + {vitals.map((s) => (
599 + <div key={s.l}>
600 + <p className="gk-display tnum text-[clamp(26px,3.4vw,40px)] leading-none font-bold tracking-[-0.03em] text-paper">
601 + {s.v}
602 + </p>
603 + <p className="gk-mono mt-2.5 text-[10px] leading-snug tracking-[0.06em] uppercase text-[rgba(245,243,238,0.55)]">
604 + {s.l}
605 + </p>
606 + </div>
607 + ))}
581 608 </div>
582 609 </div>
583 − </article>
610 + </section>
584 611 );
585 612 }
586 613
@@ -646,10 +673,66 @@ function liveChips(m: LiveMetrics): Record<string, string[]> {
646 673 };
647 674 }
648 675
676 +/* ------------------------------------------------------------------ */
677 +/* Index des plateformes — une rangée éditoriale par agrégateur */
678 +/* ------------------------------------------------------------------ */
679 +
680 +function ProductRow({ p }: { p: Product }) {
681 + return (
682 + <article className="idx-row py-8 sm:py-10">
683 + <div className="grid gap-x-10 gap-y-5 lg:grid-cols-[96px_minmax(0,1.3fr)_minmax(0,1fr)]">
684 + {/* Méta : numéro, marché, domaine */}
685 + <div className="flex flex-wrap items-baseline gap-x-4 gap-y-1 lg:block">
686 + <span className="gk-mono tnum text-[15px] font-bold text-green">
687 + {p.num}
688 + </span>
689 + <span className="klabel lg:mt-3 lg:block">{p.field}</span>
690 + {p.isNew && (
691 + <span className="gk-mono inline-block rotate-1 rounded-[5px] bg-lime px-[7px] py-[2px] text-[9.5px] font-bold tracking-[0.08em] uppercase lg:mt-3">
692 + Nouveau
693 + </span>
694 + )}
695 + </div>
696 + {/* Corps : wordmark, tagline, description, données live */}
697 + <div className="min-w-0">
698 + <h3 className="gk-display inline-flex flex-wrap items-baseline gap-[5px] text-[30px] font-bold tracking-[-0.04em] sm:text-[36px]">
699 + {p.markLeft}
700 + <span
701 + className="inline-block -rotate-2 rounded-md px-[8px] pb-[3px]"
702 + style={{ background: p.inkbox, color: p.accent }}
703 + >
704 + {p.markBoxed}
705 + </span>
706 + </h3>
707 + <p className="gk-display mt-2 text-[16px] font-bold uppercase tracking-[-0.01em]">
708 + {p.tagline}
709 + </p>
710 + <p className="mt-3 max-w-xl text-[13.5px] leading-relaxed text-ink-2">
711 + {p.description}
712 + </p>
713 + <DataLine items={p.chips} className="mt-4" />
714 + </div>
715 + {/* Détails : features + lien */}
716 + <div className="lg:border-l lg:border-[rgba(20,24,20,0.18)] lg:pl-8">
717 + <DiamondList items={p.features} accent={p.accent} />
718 + <a
719 + href={p.url}
720 + target="_blank"
721 + rel="noopener noreferrer"
722 + className="cta-link cta-link--ext mt-5"
723 + >
724 + Visiter {p.domain.replace("www.", "")}
725 + </a>
726 + </div>
727 + </div>
728 + </article>
729 + );
730 +}
731 +
649 732 /** Santé des dernières synchros, plateforme par plateforme (recent_syncs).
650 733 * Une plateforme qui n'expose pas (ou plus) son journal disparaît de la
651 − * liste — jamais de ligne inventée. */
652 −function SyncHealthCard({ m }: { m: LiveMetrics }) {
734 + * liste — jamais de ligne inventée. Version sombre, salle des machines. */
735 +function SyncHealth({ m }: { m: LiveMetrics }) {
653 736 const rows = [
654 737 { name: "Lou·Ka", syncs: m.louka.syncs },
655 738 { name: "Immo·Ka", syncs: m.immoka.syncs },
@@ -663,25 +746,33 @@ function SyncHealthCard({ m }: { m: LiveMetrics }) {
663 746 );
664 747 if (rows.length === 0) return null;
665 748 return (
666 − <div className="gk-card p-5 sm:p-7">
667 − <p className="klabel">Santé des syncs · dernières passes des connecteurs</p>
668 − <h3 className="gk-display mt-2 text-[17px] font-bold uppercase">
749 + <div>
750 + <p className="gk-mono text-[10.5px] font-bold tracking-[0.14em] uppercase text-lime">
751 + Santé des syncs · dernières passes
752 + </p>
753 + <h3 className="gk-display mt-3 text-[17px] font-bold uppercase text-paper">
669 754 La machine, prise sur le fait
670 755 </h3>
671 − <div className="mt-5 space-y-3">
672 − {rows.map(({ name, syncs: s }) => (
756 + <div className="mt-5">
757 + {rows.map(({ name, syncs: s }, i) => (
673 758 <div
674 759 key={name}
675 − className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 border-b border-dashed border-[rgba(20,24,20,0.18)] pb-2"
760 + className={`flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 py-[9px] ${
761 + i > 0 ? "border-t border-[rgba(245,243,238,0.14)]" : ""
762 + }`}
676 763 >
677 − <span className="gk-display text-[13.5px] font-bold whitespace-nowrap">
764 + <span className="gk-display text-[13.5px] font-bold whitespace-nowrap text-paper">
678 765 {name}
679 766 </span>
680 − <span className="gk-mono text-[11.5px] whitespace-nowrap">
681 − <span className={s.ok === s.runs ? "font-bold text-green" : "font-bold"}>
767 + <span className="gk-mono tnum text-[11.5px] whitespace-nowrap">
768 + <span
769 + className={
770 + s.ok === s.runs ? "font-bold text-lime" : "font-bold text-paper"
771 + }
772 + >
682 773 {s.ok}/{s.runs} OK
683 774 </span>
684 − <span className="text-ink-3">
775 + <span className="text-[rgba(245,243,238,0.5)]">
685 776 {" "}
686 777 · +{fmtInt(s.added)} · ~{fmtInt(s.updated)} · −{fmtInt(s.removed)}
687 778 </span>
@@ -689,10 +780,9 @@ function SyncHealthCard({ m }: { m: LiveMetrics }) {
689 780 </div>
690 781 ))}
691 782 </div>
692 − <p className="gk-mono mt-4 text-[10.5px] leading-relaxed text-ink-3">
783 + <p className="gk-mono mt-4 text-[10px] leading-relaxed text-[rgba(245,243,238,0.45)]">
693 784 Journal réel des dernières synchronisations publié par chaque
694 − plateforme : passes réussies, annonces ajoutées (+), mises à jour (~)
695 − et retirées (−). Lu en direct, cache 10 min.
785 + plateforme : passes réussies, ajouts (+), mises à jour (~), retraits (−).
696 786 </p>
697 787 </div>
698 788 );
@@ -702,79 +792,79 @@ export default async function Home() {
702 792 const m = await getLiveMetrics();
703 793 const chips = liveChips(m);
704 794 return (
705 − <main className="mx-auto max-w-6xl px-4 pb-16 sm:px-6">
706 − {/* ---------- Héros ---------- */}
707 − <section className="grid items-start gap-8 pt-12 sm:pt-14 lg:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]">
708 − <div className="min-w-0">
709 − <p className="kicker rise">
710 − Holding d’agrégateurs automatisés · Québec
795 + <main className="mx-auto max-w-6xl px-4 pb-20 sm:px-6">
796 + {/* ================= Héros ================= */}
797 + <section className="pt-9 sm:pt-14">
798 + <div className="rise rule-ink flex flex-wrap items-baseline justify-between gap-x-6 gap-y-1 pt-3">
799 + <p className="gk-mono text-[10.5px] font-bold tracking-[0.16em] uppercase text-ink-2">
800 + Groupe KA — Holding d&apos;agrégateurs automatisés
711 801 </p>
712 − <h1 className="gk-display rise mt-4 max-w-4xl text-[clamp(34px,6.4vw,68px)] leading-[0.98] font-bold tracking-[-0.035em] uppercase [animation-delay:0.08s]">
713 − Mille sites.{" "}
714 − <span className="outline-txt">Un seul</span>{" "}
715 − <span className="hl">·Ka.</span>
716 − </h1>
717 − <p className="rise mt-5 max-w-xl text-[16.5px] text-ink-2 [animation-delay:0.16s]">
718 − <strong className="text-ink">Ka, c&apos;est l&apos;agrégation.</strong>{" "}
719 − Groupe KA est un holding d&apos;agrégateurs entièrement
720 − automatisés : des centaines de connecteurs lisent les sites à la
721 − source, normalisent la donnée et se resynchronisent seuls, jour et
722 − nuit. Se loger, acheter, évaluer, rouler, consommer local, faire
723 − l&apos;épicerie, manger au resto, sortir, suivre les créateurs
724 − d&apos;ici, chercher — dix agrégateurs et un moteur de recherche,
725 − zéro saisie manuelle, zéro boîte noire.
802 + <p className="gk-mono text-[10.5px] tracking-[0.16em] uppercase text-ink-3">
803 + Québec · 100 % automatisé · zéro boîte noire
726 804 </p>
727 − <div className="rise mt-7 flex gap-3 overflow-x-auto pb-2 [animation-delay:0.24s]">
728 − <span className="stat-chip">
729 − <span className="pulse-dot" aria-hidden="true" />
730 − 10 agrégateurs en ligne
731 − </span>
732 − <span className="stat-chip">1 moteur de recherche québécois</span>
733 − <span className="stat-chip">{fmtInt(m.totals.connectors)}+ connecteurs sources</span>
734 − <span className="stat-chip">22 M+ d&apos;observations immobilières</span>
735 − <span className="stat-chip">
736 − {fmtPlus(m.totals.items)} annonces, produits &amp; événements
737 − </span>
738 − <span className="stat-chip">100 % données à la source</span>
739 − </div>
740 − <div className="rise mt-6 flex flex-wrap gap-3 [animation-delay:0.3s]">
741 − <a href="#flux" className="btn btn-primary">
742 − Voir comment KA agrège ↓
743 − </a>
744 − <a href="#produits" className="btn btn-ghost">
745 − Les dix plateformes
746 − </a>
805 + </div>
806 + <h1 className="gk-display rise mt-8 text-[clamp(44px,10.5vw,118px)] leading-[0.92] font-bold tracking-[-0.045em] uppercase [animation-delay:0.08s]">
807 + Mille sites.
808 + <br />
809 + <span className="outline-txt">Un seul</span>{" "}
810 + <span className="hl">·Ka.</span>
811 + </h1>
812 + <div className="mt-10 grid items-start gap-10 lg:grid-cols-[minmax(0,1.35fr)_minmax(0,1fr)] lg:gap-16">
813 + <div className="min-w-0">
814 + <p className="rise max-w-xl text-[16.5px] leading-relaxed text-ink-2 [animation-delay:0.16s]">
815 + <strong className="text-ink">Ka, c&apos;est l&apos;agrégation.</strong>{" "}
816 + Des centaines de connecteurs lisent les sites à la source,
817 + normalisent la donnée et se resynchronisent seuls, jour et nuit.
818 + Se loger, acheter, évaluer, rouler, consommer local, faire
819 + l&apos;épicerie, manger au resto, sortir, suivre les créateurs
820 + d&apos;ici, chercher — dix agrégateurs et un moteur de recherche,
821 + zéro saisie manuelle.
822 + </p>
823 + <div className="rise mt-8 flex flex-wrap items-center gap-x-7 gap-y-4 [animation-delay:0.24s]">
824 + <a href="#flux" className="btn btn-primary">
825 + Voir comment KA agrège ↓
826 + </a>
827 + <a href="#produits" className="cta-link">
828 + Les onze plateformes
829 + </a>
830 + </div>
747 831 </div>
832 + <KaDefinition />
748 833 </div>
749 − <KaDefinition />
750 834 </section>
751 835
752 − {/* ---------- Trouve·Ka — le produit phare ---------- */}
836 + {/* ================= Signes vitaux (bande pleine largeur) ================= */}
837 + <VitalsBand m={m} />
838 +
839 + {/* ================= Trouve·Ka — le produit phare ================= */}
753 840 <Section
754 841 id="trouve-ka"
842 + index="01"
755 843 kicker="Le produit phare · moteur de recherche"
756 844 title={
757 845 <>
758 846 Trouve·Ka. <span className="hl">Cherche tout le groupe.</span>
759 847 </>
760 848 }
849 + lead={
850 + <>
851 + Les agrégateurs ·Ka couvrent chacun un marché. Trouve·Ka les
852 + rassemble : c&apos;est le moteur de recherche du groupe, dont
853 + l&apos;index est bâti exclusivement sur les sites de
854 + l&apos;écosystème — son robot les parcourt jour et nuit, guidé par
855 + leurs sitemaps, et n&apos;indexe qu&apos;eux.
856 + </>
857 + }
761 858 >
762 − <p className="mt-3 max-w-xl text-[14.5px] text-ink-2">
763 − Les agrégateurs ·Ka couvrent chacun un marché. Trouve·Ka les
764 − rassemble : c&apos;est le moteur de recherche du groupe, dont
765 − l&apos;index est bâti exclusivement sur les sites de
766 − l&apos;écosystème — son robot les parcourt jour et nuit, guidé par
767 − leurs sitemaps, et n&apos;indexe qu&apos;eux.
768 − </p>
769 − <div className="mt-7 grid items-stretch gap-6 lg:grid-cols-[minmax(0,1.15fr)_minmax(0,1fr)]">
770 − <article className="gk-card gk-card-hover p-5 sm:p-7">
859 + <div className="mt-10 grid items-start gap-12 lg:grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)] lg:gap-14">
860 + <div className="min-w-0">
771 861 <div className="flex flex-wrap items-baseline justify-between gap-3">
772 862 <span className="gk-mono text-[12px] font-bold text-green">
773 863 MOTEUR
774 864 </span>
775 865 <span className="klabel">www.trouve-ka.com</span>
776 866 </div>
777 − <h3 className="gk-display mt-4 inline-flex items-baseline gap-[5px] text-[32px] font-bold tracking-[-0.04em] sm:text-[38px]">
867 + <h3 className="gk-display mt-3 inline-flex items-baseline gap-[5px] text-[34px] font-bold tracking-[-0.04em] sm:text-[42px]">
778 868 Trouve
779 869 <span
780 870 className="inline-block -rotate-2 rounded-md px-[8px] pb-[3px]"
@@ -783,13 +873,13 @@ export default async function Home() {
783 873 Ka
784 874 </span>
785 875 </h3>
786 − <p className="gk-display mt-3 text-[17px] font-bold uppercase tracking-[-0.01em]">
876 + <p className="gk-display mt-2 text-[17px] font-bold uppercase tracking-[-0.01em]">
787 877 Tout l&apos;écosystème KA, dans une seule barre de recherche.
788 878 </p>
789 879 {/* Barre de recherche factice — l'objet lui-même */}
790 880 <div
791 881 aria-hidden="true"
792 − className="mt-5 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.85)]"
882 + 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)]"
793 883 >
794 884 <span className="gk-mono text-[15px] font-bold">⌕</span>
795 885 <span className="gk-mono flex-1 truncate text-[12.5px] text-ink-3">
@@ -802,137 +892,127 @@ export default async function Home() {
802 892 Chercher
803 893 </span>
804 894 </div>
805 − <div className="mt-5 flex flex-wrap gap-2">
806 − <span className="stat-chip !text-[11px]">
807 − {fmtPlus(m.trouveka.pages)} pages indexées
808 − </span>
809 − <span className="stat-chip !text-[11px]">
810 − {fmtInt(m.trouveka.domains)} sites du groupe
811 − </span>
812 − <span className="stat-chip !text-[11px]">
813 − {fmtInt(m.trouveka.indexedLastHour)} pages indexées / heure
814 − </span>
815 − <span className="stat-chip !text-[11px]">100 % Groupe KA</span>
816 − </div>
817 − <ul className="mt-5 space-y-2 border-t-[1.5px] border-dashed border-[rgba(20,24,20,0.25)] pt-5">
818 − {[
895 + <DataLine
896 + className="mt-5"
897 + items={[
898 + `${fmtPlus(m.trouveka.pages)} pages indexées`,
899 + `${fmtInt(m.trouveka.domains)} sites du groupe`,
900 + `${fmtInt(m.trouveka.indexedLastHour)} pages/heure`,
901 + "100 % Groupe KA",
902 + ]}
903 + />
904 + <DiamondList
905 + className="rule-hair mt-5 pt-5"
906 + items={[
819 907 "Résultats 100 % Groupe KA — logements, propriétés, emplois, restos, événements, produits, véhicules, créateurs",
820 908 `É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`,
821 909 "Une fiche publiée sur un site ·Ka devient cherchable en quelques secondes",
822 910 "Robot dédié, documenté, qui respecte robots.txt",
823 − ].map((f) => (
824 − <li key={f} className="flex gap-3 text-[13.5px] text-ink-2">
825 − <span
826 − aria-hidden="true"
827 − className="mt-[7px] h-[5px] w-[5px] flex-none rotate-45 border border-ink bg-lime"
828 − />
829 − {f}
830 − </li>
831 − ))}
832 − </ul>
833 − <div className="mt-6 flex flex-wrap gap-3">
911 + ]}
912 + />
913 + <div className="mt-7 flex flex-wrap items-center gap-x-7 gap-y-4">
834 914 <a
835 915 href="https://www.trouve-ka.com"
836 916 target="_blank"
837 917 rel="noopener noreferrer"
838 − className="btn btn-primary w-full sm:w-auto"
918 + className="btn btn-primary"
839 919 >
840 − Chercher sur www.trouve-ka.com ↗
920 + Chercher sur trouve-ka.com ↗
841 921 </a>
842 − <a href="/recherche" className="btn w-full sm:w-auto">
922 + <a href="/recherche" className="cta-link">
843 923 Chercher sans quitter groupe-ka.com
844 924 </a>
845 925 </div>
846 − </article>
847 − <div className="gk-card !bg-ink p-6 text-paper sm:p-7">
926 + </div>
927 + <div className="band-ink rounded-[18px] p-6 sm:p-8">
848 928 <p className="kicker !text-lime">
849 929 Pourquoi un moteur de recherche ?
850 930 </p>
851 − <p className="gk-display mt-4 text-[clamp(24px,3.2vw,34px)] leading-[1.05] font-bold tracking-[-0.03em] uppercase">
931 + <p className="gk-display mt-5 text-[clamp(24px,3.2vw,34px)] leading-[1.05] font-bold tracking-[-0.03em] uppercase">
852 932 Les grands moteurs
853 933 <br />
854 934 cherchent le monde.
855 935 <br />
856 936 <span className="text-lime">Trouve·Ka cherche le Groupe KA.</span>
857 937 </p>
858 − <p className="mt-4 max-w-md text-[13.5px] text-[rgba(245,243,238,0.75)]">
938 + <p className="mt-4 max-w-md text-[13.5px] leading-relaxed text-[rgba(245,243,238,0.75)]">
859 939 Un 4½ sur Lou·Ka, une érablière sur Fabri·Ka, un festival sur
860 940 Sorti·Ka : des millions de fiches réparties sur les sites du
861 941 groupe — et une seule barre pour toutes les trouver. C&apos;est
862 942 l&apos;agrégation poussée à sa conclusion logique — un index
863 943 maison pour tout l&apos;écosystème.
864 944 </p>
865 − <ul className="mt-5 space-y-2 border-t border-[rgba(245,243,238,0.15)] pt-5 text-[13px] text-[rgba(245,243,238,0.75)]">
866 − {[
945 + <DiamondList
946 + dark
947 + className="mt-5 border-t border-[rgba(245,243,238,0.15)] pt-5"
948 + items={[
867 949 "Crawler maison + index de recherche dédié",
868 950 "Nourri par les sitemaps des sites de l'écosystème",
869 951 "Zéro publicité, zéro profilage — comme tout le groupe",
870 − ].map((f) => (
871 − <li key={f} className="flex gap-3">
872 − <span
873 − aria-hidden="true"
874 − className="mt-[6px] h-[5px] w-[5px] flex-none rotate-45 bg-lime"
875 − />
876 − {f}
877 − </li>
878 − ))}
879 − </ul>
952 + ]}
953 + />
880 954 </div>
881 955 </div>
882 956 </Section>
883 957
884 − {/* ---------- La figure d'agrégation ---------- */}
958 + {/* ================= La figure d'agrégation ================= */}
885 959 <Section
886 960 id="flux"
961 + index="02"
887 962 kicker="Le geste fondateur · agréger"
888 963 title={
889 964 <>
890 965 Plein de sites. <span className="hl">Une seule flèche.</span>
891 966 </>
892 967 }
968 + lead={
969 + <>
970 + Chaque plateforme du groupe fonctionne sur le même schéma : un
971 + faisceau de connecteurs part vers des centaines de sites sources,
972 + ramène la donnée brute, et KA la transforme en un seul endroit
973 + propre. Voici la machine, à découvert.
974 + </>
975 + }
893 976 >
894 − <p className="mt-3 max-w-xl text-[14.5px] text-ink-2">
895 − Chaque plateforme du groupe fonctionne sur le même schéma : un
896 − faisceau de connecteurs part vers des centaines de sites sources,
897 − ramène la donnée brute, et KA la transforme en un seul endroit
898 − propre. Voici la machine, à découvert.
899 − </p>
900 − <div className="mt-5 flex flex-wrap items-center gap-3">
977 + <div className="mt-6 flex flex-wrap items-center gap-x-6 gap-y-3">
901 978 <a
902 979 href="/documents/groupe-ka-agregation.pdf"
903 980 target="_blank"
904 981 rel="noopener noreferrer"
905 − className="btn btn-ghost"
982 + className="cta-link"
906 983 >
907 − ⤓ Télécharger « L&apos;agrégation, expliquée » — PDF · 12 pages
984 + ⤓ « L&apos;agrégation, expliquée » — PDF · 12 pages
908 985 </a>
909 986 <span className="gk-mono text-[11px] text-ink-3">
910 987 Tous les schémas du groupe, en un document
911 988 </span>
912 989 </div>
913 990 <AggregationFigure />
914 − <div className="mt-6 grid items-start gap-6 lg:grid-cols-2">
991 + <div className="mt-12 grid items-start gap-12 lg:grid-cols-2 lg:gap-14">
915 992 <PipelineFigure />
916 993 <CycleFigure />
917 994 </div>
918 995 </Section>
919 996
920 − {/* ---------- Les chiffres ---------- */}
997 + {/* ================= Les chiffres ================= */}
921 998 <Section
922 999 id="chiffres"
1000 + index="03"
923 1001 kicker="La preuve par les nombres · rien de caché"
924 1002 title={
925 1003 <>
926 1004 Ce que ·Ka <span className="hl">agrège, compté</span>
927 1005 </>
928 1006 }
1007 + lead={
1008 + <>
1009 + Un agrégateur se juge à ce qu&apos;il agrège. Voici l&apos;état des
1010 + lieux, mesuré sur les plateformes elles-mêmes — inventaire vivant,
1011 + flotte de connecteurs, couverture des moteurs d&apos;évaluation.
1012 + </>
1013 + }
929 1014 >
930 − <p className="mt-3 max-w-xl text-[14.5px] text-ink-2">
931 − Un agrégateur se juge à ce qu&apos;il agrège. Voici l&apos;état des
932 − lieux, mesuré sur les plateformes elles-mêmes — inventaire vivant,
933 − flotte de connecteurs, couverture des moteurs d&apos;évaluation.
934 − </p>
935 − <div className="mt-7 grid items-stretch gap-6 lg:grid-cols-2">
1015 + <div className="mt-12 grid items-start gap-12 lg:grid-cols-2 lg:gap-14">
936 1016 <InventoryBars
937 1017 data={[
938 1018 { name: "Fabri·Ka", value: m.fabrika.products, unit: "produits québécois" },
@@ -959,112 +1039,120 @@ export default async function Home() {
959 1039 total={m.totals.connectors}
960 1040 />
961 1041 </div>
962 − <div className="mt-6 grid items-start gap-6 lg:grid-cols-2">
963 − <div className="gk-card !bg-ink p-6 text-paper sm:p-7">
964 − <p className="kicker !text-lime">L&apos;équation ·Ka</p>
965 − <p className="gk-display mt-4 text-[clamp(26px,3.5vw,38px)] leading-[1.05] font-bold tracking-[-0.03em] uppercase">
966 − 1 000+ sites lus
967 − <br />
968 − <span className="text-lime">
969 − → {fmtInt(m.totals.connectors)}+ connecteurs
970 − </span>
971 − <br />→ 10 plateformes
972 − </p>
973 − <p className="mt-4 max-w-md text-[13.5px] text-[rgba(245,243,238,0.75)]">
974 − La donnée entre éparpillée, elle ressort rangée. Entre les deux,
975 − aucune main humaine — c&apos;est le suffixe ·Ka qui travaille.
976 − </p>
977 − </div>
1042 +
1043 + <div className="mt-14">
978 1044 <CoverageTiles vraiPrixUnits={m.vraiprix.units} />
979 1045 </div>
980 − <div className="mt-6 grid items-stretch gap-6 lg:grid-cols-2">
981 − <SyncHealthCard m={m} />
982 − {/* Croisement inter-plateformes calculé par Immo·Ka : prix demandé
983 − vs évaluation municipale (Vrai-Prix), toutes bannières. */}
984 − <div className="gk-card !bg-ink p-6 text-paper sm:p-7">
985 − <p className="kicker !text-lime">
986 − Immo·Ka × Vrai-Prix · le croisement
987 − </p>
988 − <p className="gk-display mt-4 text-[clamp(26px,3.5vw,38px)] leading-[1.05] font-bold tracking-[-0.03em] uppercase">
989 − Prix demandé :
990 − <br />
991 − <span className="text-lime">
992 − +{fmtDec(m.immoka.vraiprix.medianDeltaPct)} %
993 − </span>{" "}
994 − au-dessus
995 − <br />
996 − de l&apos;évaluation
997 − </p>
998 − <p className="mt-4 max-w-md text-[13.5px] text-[rgba(245,243,238,0.75)]">
999 − Sur {fmtInt(m.immoka.vraiprix.n)} annonces croisées avec
1000 − l&apos;évaluation municipale (Vrai-Prix), l&apos;écart médian
1001 − entre le prix demandé et l&apos;évaluation est de +
1002 − {fmtDec(m.immoka.vraiprix.medianDeltaPct)} %. C&apos;est ce que
1003 − deux agrégateurs qui se parlent peuvent mesurer — personne
1004 − d&apos;autre ne le publie.
1005 − </p>
1006 − <ul className="mt-5 space-y-2 border-t border-[rgba(245,243,238,0.15)] pt-5 text-[13px] text-[rgba(245,243,238,0.75)]">
1007 − {[
1008 − `${fmtDec(m.immoka.vraiprix.pctSur10)} % des annonces à plus de +10 % de l'évaluation`,
1009 − `${fmtDec(m.immoka.vraiprix.pctJuste)} % proches de l'évaluation`,
1010 − `${fmtDec(m.immoka.vraiprix.pctSous5)} % nettement sous l'évaluation`,
1011 − ].map((f) => (
1012 − <li key={f} className="flex gap-3">
1013 − <span
1014 − aria-hidden="true"
1015 − className="mt-[6px] h-[5px] w-[5px] flex-none rotate-45 bg-lime"
1016 − />
1017 − {f}
1018 − </li>
1019 − ))}
1020 − </ul>
1046 +
1047 + {/* ---- La salle des machines : bande sombre pleine largeur ---- */}
1048 + <div className="bleed band-ink mt-16">
1049 + <div className="mx-auto max-w-6xl px-4 py-12 sm:px-6 sm:py-16">
1050 + <div className="flex flex-wrap items-baseline justify-between gap-x-6 gap-y-1 border-b border-[rgba(245,243,238,0.18)] pb-4">
1051 + <p className="gk-mono text-[10.5px] font-bold tracking-[0.14em] uppercase text-lime">
1052 + La salle des machines · mesures croisées
1053 + </p>
1054 + <p className="gk-mono text-[10px] tracking-[0.1em] uppercase text-[rgba(245,243,238,0.45)]">
1055 + Personne d&apos;autre ne publie ça
1056 + </p>
1057 + </div>
1058 + <div className="mt-10 grid gap-12 lg:grid-cols-3 lg:gap-10">
1059 + <div>
1060 + <p className="gk-mono text-[10.5px] font-bold tracking-[0.14em] uppercase text-lime">
1061 + L&apos;équation ·Ka
1062 + </p>
1063 + <p className="gk-display mt-4 text-[clamp(26px,3.2vw,36px)] leading-[1.08] font-bold tracking-[-0.03em] uppercase text-paper">
1064 + 1 000+ sites lus
1065 + <br />
1066 + <span className="text-lime">
1067 + → {fmtInt(m.totals.connectors)}+ connecteurs
1068 + </span>
1069 + <br />→ 10 plateformes
1070 + </p>
1071 + <p className="mt-4 max-w-md text-[13.5px] leading-relaxed text-[rgba(245,243,238,0.7)]">
1072 + La donnée entre éparpillée, elle ressort rangée. Entre les
1073 + deux, aucune main humaine — c&apos;est le suffixe ·Ka qui
1074 + travaille.
1075 + </p>
1076 + </div>
1077 + <div>
1078 + <p className="gk-mono text-[10.5px] font-bold tracking-[0.14em] uppercase text-lime">
1079 + Immo·Ka × Vrai-Prix · le croisement
1080 + </p>
1081 + <p className="gk-display tnum mt-4 text-[clamp(40px,5vw,58px)] leading-none font-bold tracking-[-0.04em] text-lime">
1082 + +{fmtDec(m.immoka.vraiprix.medianDeltaPct)} %
1083 + </p>
1084 + <p className="gk-display mt-2 text-[14px] font-bold uppercase text-paper">
1085 + Prix demandé vs évaluation municipale
1086 + </p>
1087 + <p className="mt-3 max-w-md text-[13px] leading-relaxed text-[rgba(245,243,238,0.7)]">
1088 + Sur {fmtInt(m.immoka.vraiprix.n)} annonces croisées avec
1089 + l&apos;évaluation municipale (Vrai-Prix), l&apos;écart médian
1090 + entre le prix demandé et l&apos;évaluation. C&apos;est ce que
1091 + deux agrégateurs qui se parlent peuvent mesurer.
1092 + </p>
1093 + <DiamondList
1094 + dark
1095 + className="mt-4 border-t border-[rgba(245,243,238,0.15)] pt-4"
1096 + items={[
1097 + `${fmtDec(m.immoka.vraiprix.pctSur10)} % des annonces à plus de +10 % de l'évaluation`,
1098 + `${fmtDec(m.immoka.vraiprix.pctJuste)} % proches de l'évaluation`,
1099 + `${fmtDec(m.immoka.vraiprix.pctSous5)} % nettement sous l'évaluation`,
1100 + ]}
1101 + />
1102 + </div>
1103 + <SyncHealth m={m} />
1104 + </div>
1021 1105 </div>
1022 1106 </div>
1023 1107 </Section>
1024 1108
1025 − {/* ---------- Produits ---------- */}
1109 + {/* ================= L'index des plateformes ================= */}
1026 1110 <Section
1027 1111 id="produits"
1028 − kicker="Dix agrégateurs · un écosystème"
1112 + index="04"
1113 + kicker="Onze plateformes · un écosystème"
1029 1114 title={
1030 1115 <>
1031 1116 Ce que nous <span className="hl">agrégeons</span>
1032 1117 </>
1033 1118 }
1119 + lead={
1120 + <>
1121 + Chaque agrégateur attaque un angle mort du quotidien québécois : se
1122 + loger, acheter, évaluer, rouler, consommer local, faire
1123 + l&apos;épicerie, manger au resto, sortir, suivre les créateurs
1124 + d&apos;ici. Aucune saisie manuelle — les plateformes se
1125 + maintiennent elles-mêmes, annonce par annonce, prix par prix.
1126 + </>
1127 + }
1034 1128 >
1035 − <p className="mt-3 max-w-xl text-[14.5px] text-ink-2">
1036 − Chaque agrégateur attaque un angle mort du quotidien québécois : se
1037 − loger, acheter, évaluer, rouler, consommer local, faire
1038 − l&apos;épicerie, manger au resto, sortir, suivre les créateurs
1039 − d&apos;ici. Aucune saisie manuelle — les plateformes se maintiennent
1040 − elles-mêmes, annonce par annonce, prix par prix.
1041 − </p>
1042 − <div className="mt-7 grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
1043 − {PRODUCTS.map((p, i) => (
1044 − <ProductCard
1129 + <div className="mt-10">
1130 + {PRODUCTS.map((p) => (
1131 + <ProductRow
1045 1132 key={p.num}
1046 1133 p={{ ...p, chips: chips[p.domain] ?? p.chips }}
1047 − index={i}
1048 − wide={i === PRODUCTS.length - 1}
1049 1134 />
1050 1135 ))}
1051 1136 </div>
1052 1137 </Section>
1053 1138
1054 − {/* ---------- Socle commun ---------- */}
1139 + {/* ================= Socle commun ================= */}
1055 1140 <Section
1056 1141 id="socle"
1142 + index="05"
1057 1143 kicker="Une seule fondation"
1058 1144 title="Le socle commun"
1145 + lead={
1146 + <>
1147 + Les onze plateformes partagent la même colonne vertébrale : données
1148 + publiques et annonces lues directement à la source, connecteurs
1149 + dédiés synchronisés en continu, moteurs statistiques validés, et
1150 + une identité visuelle unique — bordures encre, lime électrique,
1151 + calcul à découvert.
1152 + </>
1153 + }
1059 1154 >
1060 − <p className="mt-3 max-w-xl text-[14.5px] text-ink-2">
1061 − Les onze plateformes partagent la même colonne vertébrale : données
1062 − publiques et annonces lues directement à la source, connecteurs
1063 − dédiés synchronisés en continu, moteurs statistiques validés, et une
1064 − identité visuelle unique — bordures encre, lime électrique, calcul à
1065 − découvert.
1066 − </p>
1067 − <div className="src-wrap mt-7">
1155 + <div className="src-wrap src-wrap--open mt-9">
1068 1156 <table className="src-table">
1069 1157 <thead>
1070 1158 <tr>
@@ -1099,34 +1187,44 @@ export default async function Home() {
1099 1187 </div>
1100 1188 </Section>
1101 1189
1102 − {/* ---------- ka2 · ka4 · ka6 — les agents gardiens ---------- */}
1190 + {/* ================= ka2 · ka4 · ka6 — les agents gardiens ================= */}
1103 1191 <Section
1104 1192 id="bots"
1193 + index="06"
1105 1194 kicker="Les gardiens du groupe · maintenance autonome"
1106 1195 title={
1107 1196 <>
1108 − Quand un connecteur casse, <span className="hl">un agent le répare</span>
1197 + Quand un connecteur casse,{" "}
1198 + <span className="hl">un agent le répare</span>
1199 + </>
1200 + }
1201 + lead={
1202 + <>
1203 + Des centaines de connecteurs lisent le web à la source — et le web
1204 + bouge : sites qui migrent, API qui disparaissent, encodages qui
1205 + changent. Depuis août 2026, ka·2, ka·4 et ka·6 sont les agents
1206 + gardiens autonomes du groupe : ils détectent la panne, ouvrent le
1207 + code, la réparent, prouvent que ça marche — et reculent si ça
1208 + empire.{" "}
1209 + <a
1210 + href="/agents"
1211 + className="font-semibold underline underline-offset-4 hover:text-green"
1212 + >
1213 + Le fonctionnement complet, ultra-détaillé →
1214 + </a>
1109 1215 </>
1110 1216 }
1111 1217 >
1112 − <p className="mt-3 max-w-xl text-[14.5px] text-ink-2">
1113 − Des centaines de connecteurs lisent le web à la source — et le web
1114 − bouge : sites qui migrent, API qui disparaissent, encodages qui
1115 − changent. Depuis août 2026, ka·2, ka·4 et ka·6 sont les agents
1116 − gardiens autonomes du groupe : ils détectent la panne, ouvrent le
1117 − code, la réparent, prouvent que ça marche — et reculent si ça
1118 − empire. <a href="/agents" className="font-semibold underline underline-offset-4 hover:text-green">Le fonctionnement complet, ultra-détaillé →</a>
1119 − </p>
1120 − <div className="mt-7 grid gap-5 lg:grid-cols-3">
1218 + <div className="mt-10 grid gap-x-10 gap-y-12 lg:grid-cols-3">
1121 1219 {BOTS.map((b) => (
1122 − <article key={b.domain} className="gk-card gk-card-hover p-5 sm:p-7">
1220 + <article key={b.domain} className="rule-ink pt-5">
1123 1221 <div className="flex flex-wrap items-baseline justify-between gap-3">
1124 1222 <span className="gk-mono text-[12px] font-bold text-green">
1125 1223 GARDIEN
1126 1224 </span>
1127 1225 <span className="klabel">{b.domain}</span>
1128 1226 </div>
1129 − <h3 className="gk-display mt-4 inline-flex items-baseline gap-[5px] text-[32px] font-bold tracking-[-0.04em] sm:text-[38px]">
1227 + <h3 className="gk-display mt-4 inline-flex items-baseline gap-[5px] text-[32px] font-bold tracking-[-0.04em] sm:text-[36px]">
1130 1228 Ka
1131 1229 <span
1132 1230 className="inline-block -rotate-2 rounded-md px-[8px] pb-[3px]"
@@ -1135,132 +1233,121 @@ export default async function Home() {
1135 1233 {b.digit}
1136 1234 </span>
1137 1235 </h3>
1138 − <p className="gk-display mt-3 text-[17px] font-bold uppercase tracking-[-0.01em]">
1236 + <p className="gk-display mt-2 text-[16px] font-bold uppercase tracking-[-0.01em]">
1139 1237 {b.tagline}
1140 1238 </p>
1141 1239 <p className="mt-3 text-[13.5px] leading-relaxed text-ink-2">
1142 1240 {b.description}
1143 1241 </p>
1144 − <div className="mt-5 flex flex-wrap gap-2">
1145 − {b.chips.map((chip) => (
1146 − <span key={chip} className="stat-chip !text-[11px]">
1147 − {chip}
1148 − </span>
1149 − ))}
1150 − </div>
1151 − <ul className="mt-5 space-y-2 border-t-[1.5px] border-dashed border-[rgba(20,24,20,0.25)] pt-5">
1152 − {b.features.map((f) => (
1153 − <li key={f} className="flex gap-3 text-[13.5px] text-ink-2">
1154 − <span
1155 − aria-hidden="true"
1156 − className="mt-[7px] h-[5px] w-[5px] flex-none rotate-45 border border-ink bg-lime"
1157 − />
1158 − {f}
1159 − </li>
1160 − ))}
1161 − </ul>
1242 + <DataLine items={b.chips} className="mt-4" />
1243 + <DiamondList
1244 + items={b.features}
1245 + accent={b.accent}
1246 + className="rule-hair mt-5 pt-5"
1247 + />
1162 1248 <a
1163 1249 href={b.url}
1164 1250 target="_blank"
1165 1251 rel="noopener noreferrer"
1166 − className="btn btn-primary mt-6 w-full sm:w-auto"
1252 + className="cta-link cta-link--ext mt-5"
1167 1253 >
1168 − Visiter {b.domain} ↗
1254 + Visiter {b.domain.replace("www.", "")}
1169 1255 </a>
1170 1256 </article>
1171 1257 ))}
1172 1258 </div>
1173 − <div className="gk-card mt-6 !bg-ink p-6 text-paper sm:p-7">
1174 − <p className="kicker !text-lime">
1175 − La boucle de garde, en quatre temps
1176 − </p>
1177 − <div className="mt-4 grid items-start gap-6 lg:grid-cols-2">
1178 − <p className="gk-display text-[clamp(24px,3.2vw,34px)] leading-[1.05] font-bold tracking-[-0.03em] uppercase">
1179 − api-ka détecte
1180 − <br />
1181 − <span className="text-lime">→ le gardien répare</span>
1182 − <br />→ ·Ka continue
1259 +
1260 + {/* ---- La boucle de garde : bande sombre pleine largeur ---- */}
1261 + <div className="bleed band-ink mt-16">
1262 + <div className="mx-auto max-w-6xl px-4 py-12 sm:px-6 sm:py-16">
1263 + <p className="gk-mono text-[10.5px] font-bold tracking-[0.14em] uppercase text-lime">
1264 + La boucle de garde, en quatre temps
1183 1265 </p>
1184 − <div>
1185 − <p className="max-w-md text-[13.5px] text-[rgba(245,243,238,0.75)]">
1186 − La supervision du groupe classe plus de 2 600 connecteurs en
1187 − continu. Un connecteur cassé devient un incident ; un incident
1188 − devient une mission Claude Code sur le nœud même de la
1189 − plateforme ; une réparation n&apos;est confirmée que par le
1190 − retour mesuré des données — et huit heures de surveillance.
1191 − Sinon : retour git au commit d&apos;avant, automatiquement.
1266 + <div className="mt-6 grid items-start gap-10 lg:grid-cols-2">
1267 + <p className="gk-display text-[clamp(26px,3.6vw,40px)] leading-[1.06] font-bold tracking-[-0.03em] uppercase text-paper">
1268 + api-ka détecte
1269 + <br />
1270 + <span className="text-lime">→ le gardien répare</span>
1271 + <br />→ ·Ka continue
1192 1272 </p>
1193 − <ul className="mt-5 space-y-2 border-t border-[rgba(245,243,238,0.15)] pt-5 text-[13px] text-[rgba(245,243,238,0.75)]">
1194 − {[
1195 − "Test réel obligatoire — un correctif sans données mesurées compte comme un échec",
1196 − "Rollback automatique si la plateforme se dégrade ou ne guérit pas",
1197 − "Jamais de push autonome — l'humain garde le dernier mot sur le code",
1198 − "Zéro boîte noire — flux d'actions, commits et coûts publics sur ka2.bot, ka4.bot et ka6.bot",
1199 − ].map((f) => (
1200 − <li key={f} className="flex gap-3">
1201 − <span
1202 − aria-hidden="true"
1203 − className="mt-[6px] h-[5px] w-[5px] flex-none rotate-45 bg-lime"
1204 − />
1205 − {f}
1206 − </li>
1207 − ))}
1208 − </ul>
1209 − <a href="/agents" className="btn btn-lime mt-6 !border-lime">
1210 − Tout le fonctionnement en détail →
1211 − </a>
1273 + <div>
1274 + <p className="max-w-md text-[13.5px] leading-relaxed text-[rgba(245,243,238,0.75)]">
1275 + La supervision du groupe classe plus de 2 600 connecteurs en
1276 + continu. Un connecteur cassé devient un incident ; un
1277 + incident devient une mission Claude Code sur le nœud même de
1278 + la plateforme ; une réparation n&apos;est confirmée que par
1279 + le retour mesuré des données — et huit heures de
1280 + surveillance. Sinon : retour git au commit d&apos;avant,
1281 + automatiquement.
1282 + </p>
1283 + <DiamondList
1284 + dark
1285 + className="mt-5 border-t border-[rgba(245,243,238,0.15)] pt-5"
1286 + items={[
1287 + "Test réel obligatoire — un correctif sans données mesurées compte comme un échec",
1288 + "Rollback automatique si la plateforme se dégrade ou ne guérit pas",
1289 + "Jamais de push autonome — l'humain garde le dernier mot sur le code",
1290 + "Zéro boîte noire — flux d'actions, commits et coûts publics sur ka2.bot, ka4.bot et ka6.bot",
1291 + ]}
1292 + />
1293 + <a href="/agents" className="cta-link cta-link--paper mt-6">
1294 + Tout le fonctionnement en détail
1295 + </a>
1296 + </div>
1212 1297 </div>
1213 1298 </div>
1214 1299 </div>
1215 1300 </Section>
1216 1301
1217 − {/* ---------- Manifeste ---------- */}
1218 − <Section id="mission" kicker="Notre manifeste" title="Trois principes, aucune exception">
1219 − <div className="mt-7 grid gap-4 sm:grid-cols-3">
1220 − {MANIFESTO.map((m) => (
1221 − <article key={m.num} className="gk-card gk-card-hover p-5 sm:p-6">
1222 − <span className="gk-mono text-[12px] font-bold text-green">
1223 − {m.num}
1302 + {/* ================= Manifeste ================= */}
1303 + <Section
1304 + id="mission"
1305 + index="07"
1306 + kicker="Notre manifeste"
1307 + title="Trois principes, aucune exception"
1308 + >
1309 + <div className="mt-10 grid gap-x-10 gap-y-10 sm:grid-cols-3">
1310 + {MANIFESTO.map((item) => (
1311 + <article key={item.num} className="rule-ink pt-5">
1312 + <span className="gk-mono tnum text-[15px] font-bold text-green">
1313 + {item.num}
1224 1314 </span>
1225 − <h3 className="gk-display mt-3 text-[17px] font-bold uppercase">
1226 − {m.title}
1315 + <h3 className="gk-display mt-3 text-[18px] font-bold uppercase">
1316 + {item.title}
1227 1317 </h3>
1228 1318 <p className="mt-3 text-[13.5px] leading-relaxed text-ink-2">
1229 − {m.body}
1319 + {item.body}
1230 1320 </p>
1231 1321 </article>
1232 1322 ))}
1233 1323 </div>
1234 1324 </Section>
1235 1325
1236 − {/* ---------- Appel à l'action ---------- */}
1237 − <section className="mt-16 sm:mt-20">
1238 − <div className="gk-card overflow-hidden !bg-ink p-7 text-paper sm:p-10">
1326 + {/* ================= Appel à l'action ================= */}
1327 + <section className="bleed band-ink mt-20 sm:mt-28">
1328 + <div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 sm:py-24">
1239 1329 <p className="kicker !text-lime">Un projet · une question</p>
1240 − <h2 className="gk-display mt-3 max-w-2xl text-[clamp(24px,4vw,40px)] leading-[1.02] font-bold tracking-[-0.03em] uppercase">
1330 + <h2 className="gk-display mt-5 max-w-3xl text-[clamp(32px,6.4vw,76px)] leading-[0.98] font-bold tracking-[-0.04em] uppercase text-paper">
1241 1331 Quelque chose à <span className="text-lime">agréger</span> ?{" "}
1242 1332 Parlons-en.
1243 1333 </h2>
1244 − <p className="mt-4 max-w-xl text-[14.5px] text-[rgba(245,243,238,0.75)]">
1334 + <p className="mt-5 max-w-xl text-[14.5px] leading-relaxed text-[rgba(245,243,238,0.75)]">
1245 1335 Partenariats, données, médias ou simple curiosité méthodologique —
1246 1336 de vraies personnes au bout. C&apos;est notre spécialité : tout
1247 1337 ramener à un seul endroit.
1248 1338 </p>
1249 − <div className="mt-7 flex flex-wrap gap-3">
1339 + <div className="mt-9 flex flex-wrap items-center gap-x-8 gap-y-4">
1250 1340 <a
1251 1341 href="mailto:contact@groupe-ka.com"
1252 1342 className="btn btn-lime !border-lime"
1253 1343 >
1254 1344 contact@groupe-ka.com
1255 1345 </a>
1256 − <a
1257 − href="mailto:info@groupe-ka.com"
1258 − className="btn !border-lime !bg-transparent !text-lime hover:!bg-[rgba(217,242,107,0.12)]"
1259 − >
1346 + <a href="mailto:info@groupe-ka.com" className="cta-link cta-link--paper">
1260 1347 info@groupe-ka.com
1261 1348 </a>
1262 1349 </div>
1263 − <p className="gk-mono mt-5 text-[11px] text-[rgba(245,243,238,0.55)]">
1350 + <p className="gk-mono mt-7 text-[11px] text-[rgba(245,243,238,0.5)]">
1264 1351 contact@ — projets &amp; partenariats · info@ — médias &amp;
1265 1352 questions générales · admin@ — légal &amp; vie privée
1266 1353 </p>
1267 1354