SPB Git

spb/groupe-ka Public

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

TypeScript 93.9% CSS 6%
26.7 KB · 829 lines tsx
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// Figures & visualisations du Groupe KA — SVG/HTML dessinés sur mesure,3// style « éditorial sharp ». Une mesure = une teinte ; identité portée par4// les étiquettes et pastilles de marque, jamais par la couleur seule.56/* ------------------------------------------------------------------ */7/* Données partagées des figures                                       */8/* ------------------------------------------------------------------ */910export const FIG_PLATFORMS: {11  name: string;12  accent: string;13  domain: string;14}[] = [15  { name: "Lou·Ka", accent: "#d9f26b", domain: "lou-ka.com" },16  { name: "Immo·Ka", accent: "#e23744", domain: "immo-ka.com" },17  { name: "Vrai-Prix", accent: "#ff5148", domain: "vrai-prix.com" },18  { name: "ValoPlex", accent: "#ff9f45", domain: "valoplex.com" },19  { name: "Auto·Ka", accent: "#ff5a2a", domain: "auto-ka.com" },20  { name: "Fabri·Ka", accent: "#C4532E", domain: "fabri-ka.com" },21  { name: "Food·Ka", accent: "#d9f26b", domain: "food-ka.com" },22];2324const ACCENT: Record<string, string> = Object.fromEntries(25  FIG_PLATFORMS.map((p) => [p.name, p.accent]),26);2728function BrandChip({ name }: { name: string }) {29  return (30    <span31      aria-hidden="true"32      className="inline-flex h-[16px] w-[16px] flex-none items-center justify-center rounded-[4px] bg-ink"33    >34      <span35        className="h-[10px] w-[10px] rounded-[2.5px]"36        style={{ background: ACCENT[name] ?? "#d9f26b" }}37      />38    </span>39  );40}4142function FigCaption({ lead, children }: { lead: string; children: React.ReactNode }) {43  return (44    <figcaption className="border-t-[1.5px] border-ink bg-surface-2 px-5 py-4 text-[13px] text-ink-2 sm:px-7">45      <span className="gk-mono font-bold text-green">{lead} — </span>46      {children}47    </figcaption>48  );49}5051/* ------------------------------------------------------------------ */52/* 1. Le sablier ·Ka : plein de sites → connecteurs → KA → 7 flèches   */53/* ------------------------------------------------------------------ */5455const FIG_SOURCES = [56  "Gestionnaires immobiliers",57  "Agences de courtage",58  "Rôles fonciers (MAMH)",59  "Registres de ventes",60  "Concessionnaires autos",61  "Marchands d'occasion",62  "Boutiques québécoises",63  "Sites de fabricants",64  "Bannières d'épicerie",65  "Circulaires & soldes",66];6768export function AggregationFigure() {69  const W = 1060;70  const H = 620;71  const srcX = 196; // bord droit des chips sources72  const kaL = 462; // bord gauche de la boîte KA73  const kaR = 598; // bord droit de la boîte KA74  const kaY = H / 2;75  const dstX = 836; // bord gauche des chips plateformes7677  const srcYs = FIG_SOURCES.map(78    (_, i) => 52 + (i * (H - 104)) / (FIG_SOURCES.length - 1),79  );80  const dstYs = FIG_PLATFORMS.map(81    (_, i) => 70 + (i * (H - 140)) / (FIG_PLATFORMS.length - 1),82  );8384  return (85    <figure className="gk-card mt-7 overflow-hidden p-0">86      <div className="overflow-x-auto">87        <svg88          viewBox={`0 0 ${W} ${H}`}89          role="img"90          aria-label="Schéma de l'agrégation KA : des centaines de sites sources sont lus par des connecteurs, la donnée converge vers KA qui la redistribue vers les sept plateformes."91          className="block min-w-[760px]"92        >93          <defs>94            <marker95              id="ka-arrow"96              viewBox="0 0 10 10"97              refX="8"98              refY="5"99              markerWidth="7"100              markerHeight="7"101              orient="auto-start-reverse"102            >103              <path d="M0,0 L10,5 L0,10 z" fill="#141814" />104            </marker>105          </defs>106107          {/* ---- flèches sources → KA (la moitié « plein de sites ») ---- */}108          {srcYs.map((y, i) => (109            <path110              key={`in-${i}`}111              d={`M ${srcX} ${y} C ${srcX + 130} ${y}, ${kaL - 120} ${kaY}, ${kaL - 8} ${kaY + (i - (FIG_SOURCES.length - 1) / 2) * 7}`}112              fill="none"113              stroke="#141814"114              strokeWidth="1.5"115              strokeOpacity="0.5"116              className="fig-flow"117              style={{ animationDelay: `${i * 0.13}s` }}118            />119          ))}120121          {/* ---- flèches KA → plateformes (le split) ---- */}122          {dstYs.map((y, i) => (123            <path124              key={`out-${i}`}125              d={`M ${kaR + 4} ${kaY + (i - (FIG_PLATFORMS.length - 1) / 2) * 9} C ${kaR + 120} ${kaY}, ${dstX - 130} ${y}, ${dstX - 12} ${y}`}126              fill="none"127              stroke="#141814"128              strokeWidth="2"129              strokeOpacity="0.75"130              markerEnd="url(#ka-arrow)"131              className="fig-flow"132              style={{ animationDelay: `${0.4 + i * 0.11}s` }}133            />134          ))}135136          {/* ---- chips sources (plein de sites) ---- */}137          {FIG_SOURCES.map((label, i) => (138            <g key={label}>139              <rect140                x="16"141                y={srcYs[i] - 15}142                width={srcX - 16}143                height="30"144                rx="6"145                fill="#ffffff"146                stroke="#141814"147                strokeWidth="1.5"148              />149              <circle cx="32" cy={srcYs[i]} r="3.5" fill="#1c5c41" />150              <text151                x="44"152                y={srcYs[i] + 3.5}153                fontFamily="var(--font-jetbrains), monospace"154                fontSize="10.5"155                fontWeight="700"156                fill="#141814"157              >158                {label}159              </text>160            </g>161          ))}162          <text163            x="16"164            y="24"165            fontFamily="var(--font-jetbrains), monospace"166            fontSize="11"167            fontWeight="700"168            letterSpacing="1.5"169            fill="#8b928c"170          >171            DES CENTAINES DE SITES172          </text>173174          {/* ---- étiquette connecteurs sur le faisceau ---- */}175          <g transform={`translate(${(srcX + kaL) / 2 - 8} ${kaY - 116}) rotate(-4)`}>176            <rect177              x="-98"178              y="-24"179              width="196"180              height="48"181              rx="12"182              fill="#d9f26b"183              stroke="#141814"184              strokeWidth="1.5"185            />186            <text187              x="0"188              y="-4"189              textAnchor="middle"190              fontFamily="var(--font-jetbrains), monospace"191              fontSize="11"192              fontWeight="700"193              letterSpacing="1"194              fill="#141814"195            >196              450+ CONNECTEURS197            </text>198            <text199              x="0"200              y="13"201              textAnchor="middle"202              fontFamily="var(--font-jetbrains), monospace"203              fontSize="8.5"204              fill="#2c3a2c"205            >206              lecture à la source · normalisation207            </text>208          </g>209210          {/* ---- la boîte KA ---- */}211          <g transform={`translate(${(kaL + kaR) / 2} ${kaY}) rotate(-2)`}>212            <rect213              x={-(kaR - kaL) / 2 + 6}214              y="-64"215              width={kaR - kaL}216              height="140"217              rx="12"218              fill="rgba(20,24,20,0.18)"219            />220            <rect221              x={-(kaR - kaL) / 2}222              y="-70"223              width={kaR - kaL}224              height="140"225              rx="12"226              fill="#141814"227            />228            <text229              x="0"230              y="6"231              textAnchor="middle"232              fontFamily="var(--font-space-grotesk), sans-serif"233              fontSize="58"234              fontWeight="700"235              letterSpacing="-2"236              fill="#d9f26b"237            >238              KA239            </text>240            <text241              x="0"242              y="34"243              textAnchor="middle"244              fontFamily="var(--font-jetbrains), monospace"245              fontSize="10.5"246              fontWeight="700"247              letterSpacing="2"248              fill="rgba(245,243,238,0.75)"249            >250              = AGRÉGATION251            </text>252            <text253              x="0"254              y="51"255              textAnchor="middle"256              fontFamily="var(--font-jetbrains), monospace"257              fontSize="8"258              fill="rgba(245,243,238,0.55)"259            >260              dédoublonner · géocoder261            </text>262          </g>263264          {/* ---- chips plateformes (le split ·Ka) ---- */}265          {FIG_PLATFORMS.map((p, i) => (266            <g key={p.name}>267              <rect268                x={dstX}269                y={dstYs[i] - 20}270                width={W - dstX - 16}271                height="40"272                rx="8"273                fill="#ffffff"274                stroke="#141814"275                strokeWidth="1.5"276              />277              <rect278                x={dstX + 12}279                y={dstYs[i] - 8}280                width="16"281                height="16"282                rx="4"283                fill="#141814"284              />285              <rect286                x={dstX + 15}287                y={dstYs[i] - 5}288                width="10"289                height="10"290                rx="2.5"291                fill={p.accent}292              />293              <text294                x={dstX + 38}295                y={dstYs[i] - 1}296                fontFamily="var(--font-space-grotesk), sans-serif"297                fontSize="14.5"298                fontWeight="700"299                fill="#141814"300              >301                {p.name}302              </text>303              <text304                x={dstX + 38}305                y={dstYs[i] + 13}306                fontFamily="var(--font-jetbrains), monospace"307                fontSize="9"308                fill="#8b928c"309              >310                {p.domain}311              </text>312            </g>313          ))}314          <text315            x={dstX}316            y="34"317            fontFamily="var(--font-jetbrains), monospace"318            fontSize="11"319            fontWeight="700"320            letterSpacing="1.5"321            fill="#8b928c"322          >323            SEPT PLATEFORMES324          </text>325        </svg>326      </div>327      <FigCaption lead="LE SABLIER ·KA">328        des centaines de sites éparpillés, lus à la source par des connecteurs329        dédiés ; tout converge vers KA, qui normalise, dédoublonne et330        redistribue vers sept plateformes. Une flèche entre, sept flèches331        sortent : c&apos;est ça, agréger.332      </FigCaption>333    </figure>334  );335}336337/* ------------------------------------------------------------------ */338/* 2. Le pipeline : de la page web à la donnée propre (vertical)       */339/* ------------------------------------------------------------------ */340341const PIPELINE_STEPS: { num: string; title: string; desc: string }[] = [342  {343    num: "01",344    title: "Site source",345    desc: "La page publique, telle que tout le monde la voit.",346  },347  {348    num: "02",349    title: "Connecteur",350    desc: "Un lecteur dédié par site — il connaît sa structure par cœur.",351  },352  {353    num: "03",354    title: "Normalisation",355    desc: "Mêmes champs, mêmes unités : un 3½ est un 3½ partout.",356  },357  {358    num: "04",359    title: "Dédoublonnage & géocodage",360    desc: "Une seule fiche par annonce réelle, posée sur la carte.",361  },362  {363    num: "05",364    title: "Publication",365    desc: "En ligne, datée, traçable jusqu'à la source originale.",366  },367];368369export function PipelineFigure() {370  const W = 480;371  const rowH = 96;372  const boxW = 356;373  const H = PIPELINE_STEPS.length * rowH + 24;374375  return (376    <figure className="gk-card overflow-hidden p-0">377      <div className="overflow-x-auto">378        <svg379          viewBox={`0 0 ${W} ${H}`}380          role="img"381          aria-label="Le pipeline de la donnée KA : site source, connecteur, normalisation, dédoublonnage et géocodage, publication — puis resynchronisation en boucle."382          className="block min-w-[420px]"383        >384          <defs>385            <marker386              id="pipe-arrow"387              viewBox="0 0 10 10"388              refX="8"389              refY="5"390              markerWidth="7"391              markerHeight="7"392              orient="auto-start-reverse"393            >394              <path d="M0,0 L10,5 L0,10 z" fill="#141814" />395            </marker>396          </defs>397398          {/* connecteurs verticaux entre stations */}399          {PIPELINE_STEPS.slice(0, -1).map((_, i) => (400            <line401              key={`v-${i}`}402              x1={24 + boxW / 2}403              y1={20 + i * rowH + 64}404              x2={24 + boxW / 2}405              y2={20 + (i + 1) * rowH - 6}406              stroke="#141814"407              strokeWidth="2"408              markerEnd="url(#pipe-arrow)"409              className="fig-flow"410              style={{ animationDelay: `${i * 0.2}s` }}411            />412          ))}413414          {/* boucle de resynchronisation : bas → haut, à droite */}415          <path416            d={`M ${24 + boxW} ${20 + (PIPELINE_STEPS.length - 1) * rowH + 32}417                C ${W - 26} ${20 + (PIPELINE_STEPS.length - 1) * rowH + 32},418                  ${W - 26} ${20 + 32},419                  ${24 + boxW + 10} ${20 + 32}`}420            fill="none"421            stroke="#1c5c41"422            strokeWidth="2"423            markerEnd="url(#pipe-arrow)"424            className="fig-flow"425            style={{ animationDelay: "0.5s" }}426          />427          <g428            transform={`translate(${W - 27} ${H / 2}) rotate(90)`}429          >430            <rect431              x="-78"432              y="-13"433              width="156"434              height="26"435              rx="13"436              fill="#d9f26b"437              stroke="#141814"438              strokeWidth="1.5"439            />440            <text441              x="0"442              y="4"443              textAnchor="middle"444              fontFamily="var(--font-jetbrains), monospace"445              fontSize="9.5"446              fontWeight="700"447              letterSpacing="1"448              fill="#141814"449            >450              RESYNC EN CONTINU451            </text>452          </g>453454          {/* stations */}455          {PIPELINE_STEPS.map((s, i) => (456            <g key={s.num}>457              <rect458                x="24"459                y={20 + i * rowH}460                width={boxW}461                height="64"462                rx="10"463                fill={i === PIPELINE_STEPS.length - 1 ? "#141814" : "#ffffff"}464                stroke="#141814"465                strokeWidth="1.5"466              />467              <text468                x="42"469                y={20 + i * rowH + 27}470                fontFamily="var(--font-jetbrains), monospace"471                fontSize="11"472                fontWeight="700"473                fill={i === PIPELINE_STEPS.length - 1 ? "#d9f26b" : "#1c5c41"}474              >475                {s.num}476              </text>477              <text478                x="70"479                y={20 + i * rowH + 27}480                fontFamily="var(--font-space-grotesk), sans-serif"481                fontSize="14.5"482                fontWeight="700"483                fill={i === PIPELINE_STEPS.length - 1 ? "#f5f3ee" : "#141814"}484              >485                {s.title}486              </text>487              <text488                x="42"489                y={20 + i * rowH + 48}490                fontFamily="var(--font-inter), sans-serif"491                fontSize="11"492                fill={493                  i === PIPELINE_STEPS.length - 1494                    ? "rgba(245,243,238,0.7)"495                    : "#4d5551"496                }497              >498                {s.desc}499              </text>500            </g>501          ))}502        </svg>503      </div>504      <FigCaption lead="LE PIPELINE">505        cinq stations, aucune main humaine. La boucle verte à droite est le506        secret : tout le pipeline se rejoue en continu, donc une annonce507        retirée à la source disparaît d&apos;elle-même.508      </FigCaption>509    </figure>510  );511}512513/* ------------------------------------------------------------------ */514/* 3. Le cycle ·Ka : la boucle jour et nuit                            */515/* ------------------------------------------------------------------ */516517const CYCLE_STEPS = [518  { num: "01", label: "Lire les sources", angle: -90 },519  { num: "02", label: "Comparer à hier", angle: 0 },520  { num: "03", label: "Mettre à jour", angle: 90 },521  { num: "04", label: "Publier & tracer", angle: 180 },522];523524export function CycleFigure() {525  const S = 480;526  const cx = S / 2;527  const cy = S / 2 + 6;528  const R = 150;529530  return (531    <figure className="gk-card overflow-hidden p-0">532      <div className="flex justify-center">533        <svg534          viewBox={`0 0 ${S} ${S}`}535          role="img"536          aria-label="Le cycle KA : lire les sources, comparer à hier, mettre à jour, publier et tracer — en boucle, jour et nuit."537          className="block w-full max-w-[480px]"538        >539          <defs>540            <marker541              id="cyc-arrow"542              viewBox="0 0 10 10"543              refX="7"544              refY="5"545              markerWidth="6.5"546              markerHeight="6.5"547              orient="auto"548            >549              <path d="M0,0 L10,5 L0,10 z" fill="#141814" />550            </marker>551          </defs>552553          {/* anneau pointillé qui tourne (le flux) */}554          <circle555            cx={cx}556            cy={cy}557            r={R}558            fill="none"559            stroke="#141814"560            strokeWidth="2"561            strokeOpacity="0.65"562            className="fig-flow"563          />564          {/* flèches de sens, aux diagonales */}565          {[-45, 45, 135, 225].map((a) => {566            const rad = (a * Math.PI) / 180;567            const x = cx + R * Math.cos(rad);568            const y = cy + R * Math.sin(rad);569            // tangente (sens horaire)570            const tx = -Math.sin(rad);571            const ty = Math.cos(rad);572            return (573              <line574                key={a}575                x1={x - tx}576                y1={y - ty}577                x2={x + tx * 2}578                y2={y + ty * 2}579                stroke="#141814"580                strokeWidth="2"581                markerEnd="url(#cyc-arrow)"582              />583            );584          })}585586          {/* KA au centre */}587          <g transform={`translate(${cx} ${cy}) rotate(-2)`}>588            <rect x="-48" y="-42" width="96" height="88" rx="10" fill="rgba(20,24,20,0.18)" transform="translate(5 5)" />589            <rect x="-48" y="-42" width="96" height="88" rx="10" fill="#141814" />590            <text591              x="0"592              y="8"593              textAnchor="middle"594              fontFamily="var(--font-space-grotesk), sans-serif"595              fontSize="40"596              fontWeight="700"597              letterSpacing="-1.5"598              fill="#d9f26b"599            >600              KA601            </text>602            <text603              x="0"604              y="30"605              textAnchor="middle"606              fontFamily="var(--font-jetbrains), monospace"607              fontSize="8"608              letterSpacing="1.5"609              fill="rgba(245,243,238,0.65)"610            >611              JOUR ET NUIT612            </text>613          </g>614615          {/* étapes sur l'anneau */}616          {CYCLE_STEPS.map((s) => {617            const rad = (s.angle * Math.PI) / 180;618            const x = cx + R * Math.cos(rad);619            const y = cy + R * Math.sin(rad);620            const w = 150;621            return (622              <g key={s.num} transform={`translate(${x} ${y})`}>623                <rect624                  x={-w / 2}625                  y="-19"626                  width={w}627                  height="38"628                  rx="8"629                  fill="#ffffff"630                  stroke="#141814"631                  strokeWidth="1.5"632                />633                <text634                  x={-w / 2 + 12}635                  y="4.5"636                  fontFamily="var(--font-jetbrains), monospace"637                  fontSize="10"638                  fontWeight="700"639                  fill="#1c5c41"640                >641                  {s.num}642                </text>643                <text644                  x={-w / 2 + 34}645                  y="4.5"646                  fontFamily="var(--font-space-grotesk), sans-serif"647                  fontSize="12.5"648                  fontWeight="700"649                  fill="#141814"650                >651                  {s.label}652                </text>653              </g>654            );655          })}656        </svg>657      </div>658      <FigCaption lead="LE CYCLE ·KA">659        quatre gestes, en boucle, sans intervention humaine. C&apos;est la660        définition maison de l&apos;agrégation : pas un instantané, un661        battement.662      </FigCaption>663    </figure>664  );665}666667/* ------------------------------------------------------------------ */668/* 4. Barres : ce qui est en ligne en ce moment (une mesure, une       */669/*    teinte ; identité par étiquette + pastille de marque)            */670/* ------------------------------------------------------------------ */671672const INVENTORY: { name: string; value: number; unit: string }[] = [673  { name: "Fabri·Ka", value: 281000, unit: "produits québécois" },674  { name: "Immo·Ka", value: 54000, unit: "propriétés à vendre" },675  { name: "Food·Ka", value: 23700, unit: "prix d'épicerie" },676  { name: "Auto·Ka", value: 15900, unit: "véhicules d'occasion" },677  { name: "Lou·Ka", value: 7000, unit: "logements à louer" },678];679680const fmt = (n: number) => n.toLocaleString("fr-CA").replace(/ /g, " ");681682export function InventoryBars() {683  const max = Math.max(...INVENTORY.map((d) => d.value));684  return (685    <figure className="gk-card overflow-hidden p-0">686      <div className="p-5 sm:p-7">687        <p className="klabel">Inventaire vivant · aujourd&apos;hui</p>688        <h3 className="gk-display mt-2 text-[17px] font-bold uppercase">689          Ce qui est en ligne en ce moment690        </h3>691        <div className="mt-6 space-y-5">692          {INVENTORY.map((d) => (693            <div key={d.name} title={`${d.name} — ${fmt(d.value)}+ ${d.unit}`}>694              <div className="flex items-baseline justify-between gap-3">695                <span className="flex items-center gap-2 text-[13px] font-bold">696                  <BrandChip name={d.name} />697                  <span className="gk-display">{d.name}</span>698                  <span className="gk-mono text-[10px] font-medium text-ink-3">699                    {d.unit}700                  </span>701                </span>702                <span className="gk-mono text-[12.5px] font-bold text-ink">703                  {fmt(d.value)}+704                </span>705              </div>706              <div className="mt-[6px] h-[14px] w-full rounded-r-[4px] bg-[rgba(20,24,20,0.06)]">707                <div708                  className="bar-grow h-full rounded-r-[4px] bg-green"709                  style={{ width: `${Math.max((d.value / max) * 100, 1.5)}%` }}710                />711              </div>712            </div>713          ))}714        </div>715      </div>716      <FigCaption lead="L'INVENTAIRE">717        chaque barre est vivante : elle grossit quand les connecteurs trouvent718        du neuf, elle maigrit quand une annonce disparaît à la source. Compté719        aujourd&apos;hui, pas promis.720      </FigCaption>721    </figure>722  );723}724725/* ------------------------------------------------------------------ */726/* 5. Matrice de connecteurs : un point = un connecteur                */727/* ------------------------------------------------------------------ */728729const CONNECTORS: { name: string; count: number; kind: string }[] = [730  { name: "Auto·Ka", count: 124, kind: "concessionnaires" },731  { name: "Lou·Ka", count: 108, kind: "gestionnaires" },732  { name: "Immo·Ka", count: 65, kind: "agences" },733  { name: "Food·Ka", count: 20, kind: "bannières" },734];735736export function ConnectorMatrix() {737  return (738    <figure className="gk-card overflow-hidden p-0">739      <div className="p-5 sm:p-7">740        <p className="klabel">La flotte · un point = un connecteur</p>741        <h3 className="gk-display mt-2 text-[17px] font-bold uppercase">742          317 connecteurs, dessinés un par un743        </h3>744        <div className="mt-6 space-y-6">745          {CONNECTORS.map((c) => (746            <div key={c.name} title={`${c.name} — ${c.count} connecteurs (${c.kind})`}>747              <div className="flex items-baseline justify-between gap-3">748                <span className="flex items-center gap-2 text-[13px] font-bold">749                  <BrandChip name={c.name} />750                  <span className="gk-display">{c.name}</span>751                  <span className="gk-mono text-[10px] font-medium text-ink-3">752                    {c.kind}753                  </span>754                </span>755                <span className="gk-mono text-[12.5px] font-bold">756                  {c.count}757                </span>758              </div>759              <div className="mt-[8px] flex max-w-[460px] flex-wrap gap-[3px]">760                {Array.from({ length: c.count }, (_, i) => (761                  <span762                    key={i}763                    className="h-[7px] w-[7px] rounded-[2px] bg-green"764                  />765                ))}766              </div>767            </div>768          ))}769        </div>770      </div>771      <FigCaption lead="LA FLOTTE">772        chaque point est un petit programme qui connaît un site par cœur et le773        relit sans arrêt. S&apos;y ajoutent les flux des 1 204 boutiques de774        Fabri·Ka et les données publiques qui alimentent Vrai-Prix et ValoPlex.775      </FigCaption>776    </figure>777  );778}779780/* ------------------------------------------------------------------ */781/* 6. Tuiles de couverture : les deux moteurs d'évaluation             */782/* ------------------------------------------------------------------ */783784const COVERAGE: {785  name: string;786  value: string;787  label: string;788  sub: string;789}[] = [790  {791    name: "Vrai-Prix",792    value: "3 747 008",793    label: "propriétés couvertes",794    sub: "toutes les unités du rôle foncier du Québec, estimées",795  },796  {797    name: "ValoPlex",798    value: "393 867",799    label: "plex évalués",800    sub: "1 733 744 portes — l'unité d'analyse du plex",801  },802];803804export function CoverageTiles() {805  return (806    <div className="flex h-full flex-col gap-5">807      {COVERAGE.map((c) => (808        <article key={c.name} className="gk-card gk-card-hover flex-1 p-5 sm:p-7">809          <p className="klabel flex items-center gap-2">810            <BrandChip name={c.name} />811            {c.name} · couverture812          </p>813          <p className="gk-display mt-3 text-[clamp(34px,4.5vw,48px)] leading-none font-bold tracking-[-0.03em]">814            {c.value}815          </p>816          <p className="gk-display mt-1 text-[14px] font-bold uppercase text-green">817            {c.label}818          </p>819          <p className="mt-2 text-[12.5px] text-ink-2">{c.sub}</p>820        </article>821      ))}822      <p className="gk-mono text-[11px] leading-relaxed text-ink-3">823        Couvrir n&apos;est pas agréger au rabais : chaque estimation garde ses824        comparables, ses ajustements en dollars et sa fourchette.825      </p>826    </div>827  );828}829