SPB Git

spb/ora-ka Public

Ora-Ka — cinq agrégateurs Ka, une barre de recherche hybride (exact + sémantique)

Python 80% TypeScript 12.9% CSS 6.8%
3.4 KB · 90 lines typescript
Raw Blame History
1// -----------------------------------------------------------------------------2// Immo-Ka — Agrégateur de propriétés à vendre (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// map/pills.ts : pastilles de prix pour la carte — images « étirables »5//   (stretchable images MapLibre) générées au canvas : la pastille s'adapte6//   à la largeur du texte (icon-text-fit) sans aucun sprite externe.7// -----------------------------------------------------------------------------8import type { Map as MLMap, StyleImageMetadata } from "maplibre-gl";910const R = 13;          // rayon des coins (px logiques)11const H = 30;          // hauteur de la pastille12const W = 64;          // largeur de base (étirée au besoin)13const DPR = 2;         // netteté écrans Retina1415/** roundRect avec repli manuel (Safari < 16 ne l'a pas). */16function rr(g: CanvasRenderingContext2D, x: number, y: number,17            w: number, h: number, r: number): void {18  if (typeof (g as any).roundRect === "function") {19    (g as any).roundRect(x, y, w, h, r);20    return;21  }22  g.moveTo(x + r, y);23  g.arcTo(x + w, y, x + w, y + h, r);24  g.arcTo(x + w, y + h, x, y + h, r);25  g.arcTo(x, y + h, x, y, r);26  g.arcTo(x, y, x + w, y, r);27  g.closePath();28}2930function drawPill(fill: string, stroke: string, shadow = true): ImageData {31  const cv = document.createElement("canvas");32  cv.width = W * DPR;33  cv.height = (H + 6) * DPR;   // marge pour l'ombre + la pointe34  const g = cv.getContext("2d")!;35  g.scale(DPR, DPR);36  if (shadow) {37    g.shadowColor = "rgba(26,18,20,0.28)";38    g.shadowBlur = 5;39    g.shadowOffsetY = 2;40  }41  // corps arrondi42  g.beginPath();43  rr(g, 1, 1, W - 2, H - 2, R);44  g.fillStyle = fill;45  g.fill();46  g.shadowColor = "transparent";47  // petite pointe sous la pastille (ancrage visuel sur la propriété)48  g.beginPath();49  g.moveTo(W / 2 - 5, H - 1.5);50  g.lineTo(W / 2, H + 4);51  g.lineTo(W / 2 + 5, H - 1.5);52  g.closePath();53  g.fillStyle = fill;54  g.fill();55  g.strokeStyle = stroke;56  g.lineWidth = 1.4;57  g.beginPath();58  rr(g, 1, 1, W - 2, H - 2, R);59  g.stroke();60  return g.getImageData(0, 0, cv.width, cv.height);61}6263/** Enregistre les variantes de pastille dans la carte. */64export function addPills(map: MLMap): void {65  const meta: Partial<StyleImageMetadata> = {66    pixelRatio: DPR,67    // zones étirables entre les coins arrondis — coordonnées PHYSIQUES (× DPR)68    // et non dégénérées (x1 < x2), sinon MapLibre rejette l'image en silence69    stretchX: [[(R + 1) * DPR, (W - R - 1) * DPR]],70    stretchY: [[(R + 1) * DPR, (H - R - 1) * DPR]],71    // zone où le texte peut vivre72    content: [8 * DPR, 5 * DPR, (W - 8) * DPR, (H - 5) * DPR],73  };74  if (!map.hasImage("pill"))75    map.addImage("pill", drawPill("#ffffff", "rgba(26,18,20,0.35)"), meta);76  if (!map.hasImage("pill-deal"))77    map.addImage("pill-deal", drawPill("#e23744", "#b3202b"), meta);78  if (!map.hasImage("pill-active"))79    map.addImage("pill-active", drawPill("#1a1214", "#1a1214"), meta);80}8182/** « 425 k$ », « 1,25 M$ » — format compact des pastilles. */83export function shortPrice(p: number | null | undefined): string {84  if (p == null) return "•";85  if (p >= 1_000_000)86    return `${(p / 1_000_000).toLocaleString("fr-CA", { maximumFractionDigits: 2 })} M$`;87  if (p >= 10_000) return `${Math.round(p / 1000)} k$`;88  return `${p.toLocaleString("fr-CA", { maximumFractionDigits: 0 })} $`;89}90