SPB Git forge

spb/immo-ka

Public

Immo-Ka — agrégateur des propriétés à vendre au Québec (73 connecteurs, ~40 000 annonces, React+FastAPI)

112commits 1branches 0releases
125.4 MBsize
maindefault branch
13 days agolast push
Python 47.5% HTML 27.9% TypeScript 15.5% CSS 7.2% JavaScript 2%

fix+feat: carte éditoriale contrastée + correctifs « aucun point »

- gzip sur l'API (GeoJSON 20 Mo → 2,3 Mo : les points s'affichent enfin sur connexion normale)
- repli roundRect (Safari < 16) + addPills non bloquant : les points survivent à tout
- style éditorial : relief au zoom province, eau bleue franche, forêts vertes, autoroutes rouges Immo-Ka, primaires jaunes, bâtiments terracotta, étiquettes fr renforcées

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Simon-Pierre Boucher committed 1 mo ago (Aug 13, 2026) parent 953ddf5

4 changed files +157 −88

modified frontend/src/components/MapView.tsx +0 −0

Binary file not shown.

modified frontend/src/map/immokaStyle.ts +134 −85
@@ -3,182 +3,231 @@
3 3 // Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 4 // map/immokaStyle.ts : style cartographique « Groupe Ka », écrit FROM SCRATCH
5 5 // sur le schéma OpenMapTiles (tuiles vectorielles libres OpenFreeMap).
6 −// Palette maison : papier chaud, eau froide en contraste, routes crème,
7 −// autoroutes blush, étiquettes encre chocolat — la carte EST la marque.
6 +// Direction « éditoriale contrastée » : crème chaud, eau bleue franche,
7 +// forêts vertes, autoroutes rouges (la marque), primaires jaune Michelin,
8 +// relief au zoom province — une carte de magazine, pas un fond gris.
8 9 // -----------------------------------------------------------------------------
9 10 import type { StyleSpecification } from "maplibre-gl";
10 11
11 −// Palette (alignée sur styles.css)
12 −const PAPER = "#f7f1ef"; // fond — papier chaud
13 −const WATER = "#cfdde4"; // eau — gris-bleu froid (contraste voulu)
14 −const GREEN_1 = "#e7ece0"; // couvert végétal léger
15 −const GREEN_2 = "#dde5d4"; // bois / forêt
16 −const PARK = "#dfe9d8"; // parcs
17 −const RESID = "#f2e9e6"; // trame résidentielle (à peine plus foncée)
18 −const BUILD = "#e9dcd8"; // bâtiments
19 −const ROAD = "#ffffff"; // voirie — crème/blanc
20 −const ROAD_CASE = "#e3d4d1"; // liseré de voirie
21 −const MOTOR = "#f3cfd0"; // autoroutes — blush (clin d'œil rouge Immo-Ka)
22 −const MOTOR_CASE = "#dfb0b3";
23 −const RAIL = "#d8c9c8";
24 −const BOUND = "#c49399"; // frontières — vieux rose
25 −const INK = "#3f3033"; // étiquettes — encre chocolat
26 −const INK_SOFT = "#7a6a6d";
27 −const WATER_INK = "#5e7b8a";
12 +// ---- palette éditoriale -----------------------------------------------------
13 +const PAPER = "#f6efe6"; // crème chaud
14 +const WATER = "#a6c7dd"; // eau bleue franche
15 +const WATER_DEEP = "#97bcd6"; // océan / grands plans d'eau
16 +const GRASS = "#dce8c6"; // prés / cultures
17 +const WOOD = "#c4dcae"; // forêts — vert affirmé
18 +const PARK = "#cbe3b3"; // parcs
19 +const RESID = "#efe2d2"; // trame urbaine sable
20 +const INDUS = "#e9e0e2"; // industriel mauve-gris
21 +const SAND = "#f3e8c9";
22 +const BUILD = "#e2cdbd"; // bâtiments terracotta pâle
23 +const BUILD_LINE = "#d2b7a4";
24 +const MOTOR = "#e0584c"; // autoroutes — ROUGE Immo-Ka
25 +const MOTOR_CASE = "#ad342d";
26 +const PRIM = "#f6d17c"; // primaires — jaune éditorial
27 +const PRIM_CASE = "#cfa14a";
28 +const ROAD = "#ffffff";
29 +const ROAD_CASE = "#cfbcab";
30 +const MINOR_CASE = "#dccbba";
31 +const RAIL = "#a29191";
32 +const BOUND = "#a77f8b";
33 +const INK = "#2a2024"; // étiquettes principales
34 +const INK_SOFT = "#6d5d60";
35 +const WATER_INK = "#3f6f92";
36 +const GREEN_INK = "#5c7a45";
28 37
29 38 const style: StyleSpecification = {
30 39 version: 8,
31 − name: "Immo-Ka · Groupe Ka",
40 + name: "Immo-Ka · Groupe Ka (éditorial)",
32 41 glyphs: "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf",
33 42 sources: {
34 43 omt: { type: "vector", url: "https://tiles.openfreemap.org/planet" },
44 + relief: {
45 + type: "raster", maxzoom: 6, tileSize: 256,
46 + tiles: ["https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png"],
47 + },
35 48 },
36 49 layers: [
37 50 { id: "bg", type: "background", paint: { "background-color": PAPER } },
38 51
52 + // relief léger au zoom province — donne du corps à la vue d'ensemble
53 + { id: "relief", type: "raster", source: "relief", maxzoom: 7,
54 + paint: { "raster-opacity": ["interpolate", ["linear"], ["zoom"], 0, 0.55, 5, 0.45, 6.5, 0] } },
55 +
39 56 // --- occupation du sol -------------------------------------------------
40 57 { id: "landcover-grass", type: "fill", source: "omt", "source-layer": "landcover",
41 − filter: ["in", ["get", "class"], ["literal", ["grass", "farmland", "wetland"]]],
42 − paint: { "fill-color": GREEN_1, "fill-opacity": 0.7 } },
58 + filter: ["in", ["get", "class"], ["literal", ["grass", "farmland"]]],
59 + paint: { "fill-color": GRASS, "fill-opacity": 0.85 } },
60 + { id: "landcover-wetland", type: "fill", source: "omt", "source-layer": "landcover",
61 + filter: ["==", ["get", "class"], "wetland"],
62 + paint: { "fill-color": "#cfe0cf", "fill-opacity": 0.7 } },
43 63 { id: "landcover-wood", type: "fill", source: "omt", "source-layer": "landcover",
44 64 filter: ["==", ["get", "class"], "wood"],
45 − paint: { "fill-color": GREEN_2, "fill-opacity": ["interpolate", ["linear"], ["zoom"], 6, 0.5, 12, 0.8] } },
65 + paint: { "fill-color": WOOD,
66 + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 5, 0.7, 11, 0.9] } },
67 + { id: "landcover-sand", type: "fill", source: "omt", "source-layer": "landcover",
68 + filter: ["==", ["get", "class"], "sand"], paint: { "fill-color": SAND } },
46 69 { id: "landcover-ice", type: "fill", source: "omt", "source-layer": "landcover",
47 − filter: ["==", ["get", "class"], "ice"], paint: { "fill-color": "#f2f4f5" } },
70 + filter: ["==", ["get", "class"], "ice"], paint: { "fill-color": "#eef4f6" } },
48 71 { id: "landuse-residential", type: "fill", source: "omt", "source-layer": "landuse",
49 72 filter: ["in", ["get", "class"], ["literal", ["residential", "suburb", "neighbourhood"]]],
50 − paint: { "fill-color": RESID, "fill-opacity": ["interpolate", ["linear"], ["zoom"], 8, 0.35, 12, 0.75] } },
73 + paint: { "fill-color": RESID,
74 + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 8, 0.5, 12, 0.9] } },
75 + { id: "landuse-indus", type: "fill", source: "omt", "source-layer": "landuse",
76 + filter: ["in", ["get", "class"], ["literal", ["industrial", "commercial", "quarry", "railway"]]],
77 + paint: { "fill-color": INDUS, "fill-opacity": 0.7 } },
78 + { id: "landuse-green", type: "fill", source: "omt", "source-layer": "landuse",
79 + filter: ["in", ["get", "class"], ["literal", ["cemetery", "stadium", "pitch", "playground"]]],
80 + paint: { "fill-color": PARK, "fill-opacity": 0.6 } },
51 81 { id: "park", type: "fill", source: "omt", "source-layer": "park",
52 − paint: { "fill-color": PARK, "fill-opacity": 0.85 } },
82 + paint: { "fill-color": PARK, "fill-opacity": 0.75 } },
53 83
54 84 // --- eau ---------------------------------------------------------------
55 85 { id: "waterway", type: "line", source: "omt", "source-layer": "waterway",
56 86 paint: { "line-color": WATER,
57 − "line-width": ["interpolate", ["exponential", 1.4], ["zoom"], 8, 0.6, 14, 2.5, 18, 6] } },
87 + "line-width": ["interpolate", ["exponential", 1.4], ["zoom"], 8, 0.8, 14, 3, 18, 8] } },
58 88 { id: "water", type: "fill", source: "omt", "source-layer": "water",
59 − paint: { "fill-color": WATER } },
89 + paint: { "fill-color": ["case", ["==", ["get", "class"], "ocean"], WATER_DEEP, WATER] } },
60 90
61 91 // --- aéroports ----------------------------------------------------------
62 92 { id: "aeroway", type: "line", source: "omt", "source-layer": "aeroway",
63 93 minzoom: 11, filter: ["in", ["get", "class"], ["literal", ["runway", "taxiway"]]],
64 − paint: { "line-color": "#eee3e0",
65 − "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 11, 1, 16, 24] } },
94 + paint: { "line-color": "#e6d9d2",
95 + "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 11, 1, 16, 26] } },
66 96
67 − // --- tunnels (discrets, pointillés) --------------------------------------
97 + // --- tunnels --------------------------------------------------------------
68 98 { id: "tunnel", type: "line", source: "omt", "source-layer": "transportation",
69 99 filter: ["==", ["get", "brunnel"], "tunnel"],
70 100 paint: { "line-color": ROAD_CASE, "line-dasharray": [2, 2],
71 − "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 8, 0.8, 14, 3, 18, 10] } },
101 + "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 8, 0.8, 14, 3.5, 18, 12] } },
72 102
73 − // --- voirie (liseré puis remplissage) ------------------------------------
103 + // --- voirie : liserés (du bas vers le haut de la hiérarchie) ---------------
74 104 { id: "road-minor-case", type: "line", source: "omt", "source-layer": "transportation",
75 − minzoom: 12,
76 − filter: ["all", ["!=", ["get", "brunnel"], "tunnel"],
77 − ["in", ["get", "class"], ["literal", ["minor", "service", "track", "path"]]]],
78 − layout: { "line-cap": "round", "line-join": "round" },
79 − paint: { "line-color": ROAD_CASE,
80 − "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 1.4, 15, 4, 18, 14] } },
81 − { id: "road-minor", type: "line", source: "omt", "source-layer": "transportation",
82 − minzoom: 12,
105 + minzoom: 11,
83 106 filter: ["all", ["!=", ["get", "brunnel"], "tunnel"],
84 − ["in", ["get", "class"], ["literal", ["minor", "service", "track", "path"]]]],
107 + ["in", ["get", "class"], ["literal", ["minor", "service", "track"]]]],
85 108 layout: { "line-cap": "round", "line-join": "round" },
86 − paint: { "line-color": ROAD,
87 − "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.8, 15, 3, 18, 12] } },
109 + paint: { "line-color": MINOR_CASE,
110 + "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 11, 1.2, 15, 5, 18, 16] } },
88 111 { id: "road-mid-case", type: "line", source: "omt", "source-layer": "transportation",
89 112 minzoom: 9,
90 113 filter: ["all", ["!=", ["get", "brunnel"], "tunnel"],
91 114 ["in", ["get", "class"], ["literal", ["secondary", "tertiary"]]]],
92 115 layout: { "line-cap": "round", "line-join": "round" },
93 116 paint: { "line-color": ROAD_CASE,
94 − "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 9, 1.6, 14, 6, 18, 20] } },
117 + "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 9, 2, 14, 7, 18, 22] } },
118 + { id: "road-prim-case", type: "line", source: "omt", "source-layer": "transportation",
119 + minzoom: 7,
120 + filter: ["all", ["!=", ["get", "brunnel"], "tunnel"],
121 + ["in", ["get", "class"], ["literal", ["primary", "trunk"]]]],
122 + layout: { "line-cap": "round", "line-join": "round" },
123 + paint: { "line-color": PRIM_CASE,
124 + "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 7, 1.8, 14, 8, 18, 26] } },
125 + { id: "motorway-case", type: "line", source: "omt", "source-layer": "transportation",
126 + minzoom: 5, filter: ["==", ["get", "class"], "motorway"],
127 + layout: { "line-cap": "round", "line-join": "round" },
128 + paint: { "line-color": MOTOR_CASE,
129 + "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 1.4, 14, 9, 18, 28] } },
130 +
131 + // --- voirie : remplissages ---------------------------------------------------
132 + { id: "road-path", type: "line", source: "omt", "source-layer": "transportation",
133 + minzoom: 14, filter: ["==", ["get", "class"], "path"],
134 + paint: { "line-color": "#b8a394", "line-dasharray": [2.5, 2],
135 + "line-width": ["interpolate", ["linear"], ["zoom"], 14, 0.8, 18, 2.5] } },
136 + { id: "road-minor", type: "line", source: "omt", "source-layer": "transportation",
137 + minzoom: 11,
138 + filter: ["all", ["!=", ["get", "brunnel"], "tunnel"],
139 + ["in", ["get", "class"], ["literal", ["minor", "service", "track"]]]],
140 + layout: { "line-cap": "round", "line-join": "round" },
141 + paint: { "line-color": ROAD,
142 + "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 11, 0.7, 15, 3.8, 18, 13] } },
95 143 { id: "road-mid", type: "line", source: "omt", "source-layer": "transportation",
96 144 minzoom: 9,
97 145 filter: ["all", ["!=", ["get", "brunnel"], "tunnel"],
98 146 ["in", ["get", "class"], ["literal", ["secondary", "tertiary"]]]],
99 147 layout: { "line-cap": "round", "line-join": "round" },
100 148 paint: { "line-color": ROAD,
101 − "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 9, 1, 14, 4.5, 18, 17] } },
102 − { id: "road-major-case", type: "line", source: "omt", "source-layer": "transportation",
149 + "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 9, 1.3, 14, 5.5, 18, 18] } },
150 + { id: "road-prim", type: "line", source: "omt", "source-layer": "transportation",
103 151 minzoom: 7,
104 152 filter: ["all", ["!=", ["get", "brunnel"], "tunnel"],
105 153 ["in", ["get", "class"], ["literal", ["primary", "trunk"]]]],
106 154 layout: { "line-cap": "round", "line-join": "round" },
107 − paint: { "line-color": ROAD_CASE,
108 − "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 7, 1.2, 14, 7, 18, 24] } },
109 − { id: "road-major", type: "line", source: "omt", "source-layer": "transportation",
110 − minzoom: 7,
111 − filter: ["all", ["!=", ["get", "brunnel"], "tunnel"],
112 − ["in", ["get", "class"], ["literal", ["primary", "trunk"]]]],
113 − layout: { "line-cap": "round", "line-join": "round" },
114 − paint: { "line-color": ROAD,
115 − "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 7, 0.75, 14, 5, 18, 20] } },
116 − { id: "motorway-case", type: "line", source: "omt", "source-layer": "transportation",
117 − minzoom: 5, filter: ["==", ["get", "class"], "motorway"],
118 − layout: { "line-cap": "round", "line-join": "round" },
119 − paint: { "line-color": MOTOR_CASE,
120 − "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.9, 14, 8, 18, 26] } },
155 + paint: { "line-color": PRIM,
156 + "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 7, 1.1, 14, 6.5, 18, 22] } },
121 157 { id: "motorway", type: "line", source: "omt", "source-layer": "transportation",
122 158 minzoom: 5, filter: ["==", ["get", "class"], "motorway"],
123 159 layout: { "line-cap": "round", "line-join": "round" },
124 160 paint: { "line-color": MOTOR,
125 − "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.6, 14, 6, 18, 22] } },
161 + "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.9, 14, 7, 18, 24] } },
126 162 { id: "rail", type: "line", source: "omt", "source-layer": "transportation",
127 163 minzoom: 10, filter: ["==", ["get", "class"], "rail"],
128 164 paint: { "line-color": RAIL, "line-dasharray": [3, 3],
129 − "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.6, 16, 2] } },
165 + "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.7, 16, 2.2] } },
130 166
131 − // --- bâtiments ------------------------------------------------------------
167 + // --- ponts (léger liseré pour lecture) -----------------------------------------
168 + { id: "bridge-case", type: "line", source: "omt", "source-layer": "transportation",
169 + minzoom: 12, filter: ["==", ["get", "brunnel"], "bridge"],
170 + paint: { "line-color": "#b7a08e", "line-gap-width":
171 + ["interpolate", ["exponential", 1.5], ["zoom"], 12, 3, 18, 20], "line-width": 1.2 } },
172 +
173 + // --- bâtiments -------------------------------------------------------------------
132 174 { id: "building", type: "fill", source: "omt", "source-layer": "building",
133 175 minzoom: 13,
134 − paint: { "fill-color": BUILD, "fill-outline-color": "#ddc9c4",
135 − "fill-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0.45, 16, 0.9] } },
176 + paint: { "fill-color": BUILD, "fill-outline-color": BUILD_LINE,
177 + "fill-opacity": ["interpolate", ["linear"], ["zoom"], 13, 0.55, 16, 0.95] } },
136 178
137 − // --- frontières -------------------------------------------------------------
179 + // --- frontières -----------------------------------------------------------------
138 180 { id: "boundary-prov", type: "line", source: "omt", "source-layer": "boundary",
139 181 filter: ["all", ["==", ["get", "admin_level"], 4], ["!=", ["get", "maritime"], 1]],
140 − paint: { "line-color": BOUND, "line-dasharray": [4, 3], "line-width": 1 } },
182 + paint: { "line-color": BOUND, "line-dasharray": [4, 3],
183 + "line-width": ["interpolate", ["linear"], ["zoom"], 4, 0.8, 10, 1.6] } },
141 184 { id: "boundary-country", type: "line", source: "omt", "source-layer": "boundary",
142 185 filter: ["all", ["==", ["get", "admin_level"], 2], ["!=", ["get", "maritime"], 1]],
143 186 paint: { "line-color": BOUND,
144 − "line-width": ["interpolate", ["linear"], ["zoom"], 3, 0.8, 10, 2] } },
187 + "line-width": ["interpolate", ["linear"], ["zoom"], 3, 1.2, 10, 2.6] } },
145 188
146 − // --- étiquettes ---------------------------------------------------------------
189 + // --- étiquettes --------------------------------------------------------------------
190 + { id: "park-label", type: "symbol", source: "omt", "source-layer": "park",
191 + minzoom: 11, filter: ["==", ["geometry-type"], "Point"],
192 + layout: { "text-field": ["coalesce", ["get", "name:fr"], ["get", "name"]],
193 + "text-font": ["Noto Sans Italic"], "text-size": 11.5 },
194 + paint: { "text-color": GREEN_INK, "text-halo-color": PAPER, "text-halo-width": 1.4 } },
147 195 { id: "water-label", type: "symbol", source: "omt", "source-layer": "water_name",
148 196 layout: { "text-field": ["coalesce", ["get", "name:fr"], ["get", "name"]],
149 − "text-font": ["Noto Sans Italic"], "text-size": 12 },
150 − paint: { "text-color": WATER_INK, "text-halo-color": PAPER, "text-halo-width": 1 } },
197 + "text-font": ["Noto Sans Italic"],
198 + "text-size": ["interpolate", ["linear"], ["zoom"], 6, 11, 12, 14] },
199 + paint: { "text-color": WATER_INK, "text-halo-color": "rgba(246,239,230,0.75)",
200 + "text-halo-width": 1.2 } },
151 201 { id: "road-label", type: "symbol", source: "omt", "source-layer": "transportation_name",
152 − minzoom: 13,
202 + minzoom: 12.5,
153 203 layout: { "symbol-placement": "line",
154 204 "text-field": ["coalesce", ["get", "name:fr"], ["get", "name"]],
155 − "text-font": ["Noto Sans Regular"], "text-size": 11 },
156 − paint: { "text-color": INK_SOFT, "text-halo-color": PAPER, "text-halo-width": 1.5 } },
205 + "text-font": ["Noto Sans Regular"], "text-size": 11.5 },
206 + paint: { "text-color": INK_SOFT, "text-halo-color": PAPER, "text-halo-width": 1.6 } },
157 207 { id: "place-village", type: "symbol", source: "omt", "source-layer": "place",
158 208 minzoom: 9,
159 209 filter: ["in", ["get", "class"], ["literal", ["village", "suburb", "neighbourhood", "hamlet"]]],
160 210 layout: { "text-field": ["coalesce", ["get", "name:fr"], ["get", "name"]],
161 211 "text-font": ["Noto Sans Regular"],
162 − "text-size": ["interpolate", ["linear"], ["zoom"], 9, 10.5, 14, 13] },
163 − paint: { "text-color": INK_SOFT, "text-halo-color": PAPER, "text-halo-width": 1.6 } },
212 + "text-size": ["interpolate", ["linear"], ["zoom"], 9, 11, 14, 13.5] },
213 + paint: { "text-color": INK_SOFT, "text-halo-color": PAPER, "text-halo-width": 1.7 } },
164 214 { id: "place-town", type: "symbol", source: "omt", "source-layer": "place",
165 215 filter: ["==", ["get", "class"], "town"],
166 216 layout: { "text-field": ["coalesce", ["get", "name:fr"], ["get", "name"]],
167 − "text-font": ["Noto Sans Regular"],
168 − "text-size": ["interpolate", ["linear"], ["zoom"], 6, 11, 12, 15] },
169 − paint: { "text-color": INK, "text-halo-color": PAPER, "text-halo-width": 1.6 } },
217 + "text-font": ["Noto Sans Bold"],
218 + "text-size": ["interpolate", ["linear"], ["zoom"], 6, 12, 12, 16] },
219 + paint: { "text-color": INK, "text-halo-color": PAPER, "text-halo-width": 1.8 } },
170 220 { id: "place-city", type: "symbol", source: "omt", "source-layer": "place",
171 221 filter: ["==", ["get", "class"], "city"],
172 222 layout: { "text-field": ["coalesce", ["get", "name:fr"], ["get", "name"]],
173 223 "text-font": ["Noto Sans Bold"],
174 − "text-size": ["interpolate", ["linear"], ["zoom"], 4, 12, 10, 17],
175 − "text-transform": "none" },
176 − paint: { "text-color": INK, "text-halo-color": PAPER, "text-halo-width": 1.8 } },
224 + "text-size": ["interpolate", ["linear"], ["zoom"], 4, 13, 10, 19] },
225 + paint: { "text-color": INK, "text-halo-color": PAPER, "text-halo-width": 2 } },
177 226 { id: "place-state", type: "symbol", source: "omt", "source-layer": "place",
178 227 maxzoom: 7, filter: ["==", ["get", "class"], "state"],
179 228 layout: { "text-field": ["coalesce", ["get", "name:fr"], ["get", "name"]],
180 − "text-font": ["Noto Sans Regular"], "text-size": 11,
181 − "text-transform": "uppercase", "text-letter-spacing": 0.15 },
229 + "text-font": ["Noto Sans Regular"], "text-size": 11.5,
230 + "text-transform": "uppercase", "text-letter-spacing": 0.18 },
182 231 paint: { "text-color": BOUND, "text-halo-color": PAPER, "text-halo-width": 1.5 } },
183 232 ],
184 233 };
modified frontend/src/map/pills.ts +17 −2
@@ -12,6 +12,21 @@ const H = 30; // hauteur de la pastille
12 12 const W = 64; // largeur de base (étirée au besoin)
13 13 const DPR = 2; // netteté écrans Retina
14 14
15 +/** roundRect avec repli manuel (Safari < 16 ne l'a pas). */
16 +function 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 +}
29 +
15 30 function drawPill(fill: string, stroke: string, shadow = true): ImageData {
16 31 const cv = document.createElement("canvas");
17 32 cv.width = W * DPR;
@@ -25,7 +40,7 @@ function drawPill(fill: string, stroke: string, shadow = true): ImageData {
25 40 }
26 41 // corps arrondi
27 42 g.beginPath();
28 − g.roundRect(1, 1, W - 2, H - 2, R);
43 + rr(g, 1, 1, W - 2, H - 2, R);
29 44 g.fillStyle = fill;
30 45 g.fill();
31 46 g.shadowColor = "transparent";
@@ -40,7 +55,7 @@ function drawPill(fill: string, stroke: string, shadow = true): ImageData {
40 55 g.strokeStyle = stroke;
41 56 g.lineWidth = 1.4;
42 57 g.beginPath();
43 − g.roundRect(1, 1, W - 2, H - 2, R);
58 + rr(g, 1, 1, W - 2, H - 2, R);
44 59 g.stroke();
45 60 return g.getImageData(0, 0, cv.width, cv.height);
46 61 }
modified immoka/web.py +6 −1
@@ -11,6 +11,7 @@ from pathlib import Path
11 11
12 12 from fastapi import BackgroundTasks, FastAPI, HTTPException, Query
13 13 from fastapi.middleware.cors import CORSMiddleware
14 +from fastapi.middleware.gzip import GZipMiddleware
14 15 from fastapi.responses import FileResponse
15 16 from fastapi.staticfiles import StaticFiles
16 17
@@ -26,6 +27,8 @@ app = FastAPI(title="Immo-Ka API", version="0.1",
26 27 description="Agrégateur de maisons à vendre — province de Québec")
27 28 app.add_middleware(CORSMiddleware, allow_origins=["*"],
28 29 allow_methods=["*"], allow_headers=["*"])
30 +# le GeoJSON complet pèse ~20 Mo — la compression est vitale pour la carte
31 +app.add_middleware(GZipMiddleware, minimum_size=1024)
29 32
30 33 _sync_lock = threading.Lock()
31 34
@@ -189,7 +192,9 @@ def listings_geojson(
189 192 "type": "Feature",
190 193 "geometry": {"type": "Point", "coordinates": [r["lng"], r["lat"]]},
191 194 "properties": {
192 − "uid": r["uid"], "title": r["title"], "address": r["address"],
195 + "uid": r["uid"],
196 + "title": None if r["address"] else r["title"],
197 + "address": r["address"],
193 198 "price": r["price"], "price_label": r["price_label"],
194 199 "property_type": r["property_type"], "bedrooms": r["bedrooms"],
195 200 "bathrooms": r["bathrooms"], "source": r["source"],
196 201