// Brisas do Lago — mapa técnico do empreendimento // Posições dos lotes calculadas a partir de quadras em coords mundo (1400x1000). // Style modes: 'tecnico' (planta técnica), 'esquematico' (cards limpos), 'isometrico' (3D leve). // ───────────────────────────────────────────────────────────────────────── // LAYOUT DAS QUADRAS — cada quadra é um polígono em coords mundo // Lotes são fatiados ao longo de uma "direção" dada por dois pontos âncora // ───────────────────────────────────────────────────────────────────────── // Define cada quadra como uma sequência de 4 vértices (polígono) + a direção // de fatiamento (qual aresta corresponde à frente dos lotes). // O fatiamento é feito proporcionalmente à frente de cada lote. const QLAYOUTS = { // Quadra A: arco curvado superior, lotes 1-20 (20 lotes) // Posicionada em cima formando um arco suave A: { // 20 segmentos ao longo de uma curva — vamos definir como uma sequência de // polígonos individuais ao invés de um único polígono fatiado, porque é uma curva type: 'arc', range: [1, 20], // anchor points para o arco (frente dos lotes) front: [ [100, 110], [180, 105], [260, 102], [340, 100], [420, 100], [500, 102], [580, 106], [660, 112], [740, 120], [820, 132] ], // back: fundo (mais para cima) back: [ [100, 60], [180, 55], [260, 52], [340, 50], [420, 50], [500, 52], [580, 55], [660, 60], [740, 66], [820, 76] ], // segunda metade do arco — desce pela direita front2: [ [820, 132], [880, 160], [925, 200], [955, 250], [970, 305], [970, 360], [955, 415], [925, 465], [880, 510], [820, 545], [740, 565] ], back2: [ [820, 76], [890, 100], [945, 145], [985, 205], [1010, 280], [1015, 360], [1005, 440], [975, 510], [930, 575], [865, 620], [780, 642] ] }, // Quadra B: bloco abaixo da extremidade leste de A (lotes 21-25) B: { type: 'rect', range: [21, 25], p: [[200, 200], [600, 200], [600, 290], [200, 290]], sliceAxis: 'x', // horizontal: lotes lado a lado }, // Quadra C: pequeno bloco (lotes 26-30) C: { type: 'rect', range: [26, 30], p: [[200, 320], [600, 320], [600, 410], [200, 410]], sliceAxis: 'x', }, // Quadra D: bloco vertical à direita (lotes 31-40) D: { type: 'rect', range: [31, 40], p: [[640, 200], [840, 200], [840, 720], [640, 720]], sliceAxis: 'y', }, // Quadra E: bloco horizontal meio (lotes 41-48) E: { type: 'rect', range: [41, 48], p: [[120, 450], [580, 450], [580, 540], [120, 540]], sliceAxis: 'x', }, // Quadra F: pequeno bloco (lotes 49-52) F: { type: 'rect', range: [49, 52], p: [[120, 570], [380, 570], [380, 660], [120, 660]], sliceAxis: 'x', }, // Quadra G: bloco horizontal lower (lotes 53-62) G: { type: 'rect', range: [53, 62], p: [[120, 690], [600, 690], [600, 780], [120, 780]], sliceAxis: 'x', }, // Quadra H: lote único grande perto do lago (63) H: { type: 'rect', range: [63, 63], p: [[640, 750], [840, 750], [840, 870], [640, 870]], sliceAxis: 'x', }, // Quadra I: bloco perto do lago (64-74) I: { type: 'rect', range: [64, 74], p: [[880, 200], [1080, 200], [1080, 720], [880, 720]], sliceAxis: 'y', }, // Quadra J: lakeside row (75-83) J: { type: 'rect', range: [75, 83], p: [[1140, 200], [1320, 200], [1320, 720], [1140, 720]], sliceAxis: 'y', }, // Quadra K: lakeside premium (84-91) K: { type: 'rect', range: [84, 91], p: [[120, 820], [880, 820], [880, 920], [120, 920]], sliceAxis: 'x', }, // Quadra L: lakeside end (92-95) L: { type: 'rect', range: [92, 95], p: [[920, 800], [1320, 800], [1320, 920], [920, 920]], sliceAxis: 'x', }, }; // ───────────────────────────────────────────────────────────────────────── // Gera os polígonos individuais de cada lote // ───────────────────────────────────────────────────────────────────────── function gerarPolígonosLotes(LOTES) { const result = {}; for (const [quadra, layout] of Object.entries(QLAYOUTS)) { const lotesDestaQ = LOTES.filter(l => l.quadra === quadra) .sort((a, b) => a.numero - b.numero); if (layout.type === 'arc') { // 20 lotes na Quadra A: 10 no arco superior (front+back) + 10 na descida (front2+back2) // Distribuir proporcionalmente à frente const primeiros10 = lotesDestaQ.slice(0, 10); const segundos10 = lotesDestaQ.slice(10, 20); sliceArc(primeiros10, layout.front, layout.back, result); sliceArc(segundos10, layout.front2, layout.back2, result); } else if (layout.type === 'rect') { sliceRect(lotesDestaQ, layout.p, layout.sliceAxis, result); } } return result; } function sliceArc(lotes, front, back, result) { // front e back têm N+1 pontos âncora; queremos N segmentos // se o número de pontos < lotes+1, interpolar const nLotes = lotes.length; const totalFrente = lotes.reduce((s, l) => s + l.frente, 0); // Compute cumulative fractions let cum = 0; // Build smooth bezier-like sample points const frontPts = sampleCurve(front, nLotes + 1); const backPts = sampleCurve(back, nLotes + 1); // Atribuir cada lote a um segmento (sem proporcionalidade complexa — segmentos iguais) for (let i = 0; i < nLotes; i++) { const lote = lotes[i]; const poly = [ frontPts[i], frontPts[i + 1], backPts[i + 1], backPts[i] ]; result[lote.numero] = poly; } } function sampleCurve(pts, n) { // Interpola linearmente entre os pontos âncora para n+1 amostras igualmente espaçadas if (pts.length === n) return pts; const result = []; for (let i = 0; i < n; i++) { const t = (i / (n - 1)) * (pts.length - 1); const idx = Math.floor(t); const frac = t - idx; if (idx >= pts.length - 1) { result.push(pts[pts.length - 1]); } else { const a = pts[idx], b = pts[idx + 1]; result.push([a[0] + (b[0] - a[0]) * frac, a[1] + (b[1] - a[1]) * frac]); } } return result; } function sliceRect(lotes, p, axis, result) { // p = [tl, tr, br, bl] const [tl, tr, br, bl] = p; const nLotes = lotes.length; const totalFrente = lotes.reduce((s, l) => s + l.frente, 0); let cum = 0; for (let i = 0; i < nLotes; i++) { const lote = lotes[i]; const t0 = cum / totalFrente; cum += lote.frente; const t1 = cum / totalFrente; let poly; if (axis === 'x') { // fatia vertical const x0_top = tl[0] + (tr[0] - tl[0]) * t0; const x1_top = tl[0] + (tr[0] - tl[0]) * t1; const x0_bot = bl[0] + (br[0] - bl[0]) * t0; const x1_bot = bl[0] + (br[0] - bl[0]) * t1; const y_top = tl[1] + (tr[1] - tl[1]) * ((t0 + t1) / 2); const y_bot = bl[1] + (br[1] - bl[1]) * ((t0 + t1) / 2); poly = [ [x0_top, tl[1]], [x1_top, tr[1]], [x1_bot, br[1]], [x0_bot, bl[1]] ]; } else { // fatia horizontal const y0_l = tl[1] + (bl[1] - tl[1]) * t0; const y1_l = tl[1] + (bl[1] - tl[1]) * t1; const y0_r = tr[1] + (br[1] - tr[1]) * t0; const y1_r = tr[1] + (br[1] - tr[1]) * t1; poly = [ [tl[0], y0_l], [tr[0], y0_r], [tr[0], y1_r], [tl[0], y1_l] ]; } result[lote.numero] = poly; } } // Cálculo do centroide de um polígono (para colocar números/marcadores) function centroid(poly) { let x = 0, y = 0; for (const [px, py] of poly) { x += px; y += py; } return [x / poly.length, y / poly.length]; } // ───────────────────────────────────────────────────────────────────────── // CORES POR STATUS // ───────────────────────────────────────────────────────────────────────── const STATUS_COLORS = { disponivel: { fill: '#FBF6EC', stroke: '#2D4F3C', text: '#1A2E2A' }, reservado: { fill: '#F5C99A', stroke: '#A8632F', text: '#5C2F12' }, negociacao: { fill: '#BFD4DD', stroke: '#3B6B7E', text: '#1F3D49' }, vendido: { fill: '#C7CFC8', stroke: '#6E7B72', text: '#3A4640' }, }; const STATUS_LABELS = { disponivel: 'Disponível', reservado: 'Reservado', negociacao: 'Em negociação', vendido: 'Vendido', }; // ───────────────────────────────────────────────────────────────────────── // MAPA — componente principal // ───────────────────────────────────────────────────────────────────────── function MapaLoteamento({ estilo = 'tecnico', filtroStatus = null, filtroVista = null, selecionado = null, hover = null, onSelect, onHover, className, compacto = false, }) { const LOTES = window.BL.LOTES; const poligonos = React.useMemo(() => gerarPolígonosLotes(LOTES), [LOTES]); const isIso = estilo === 'isometrico'; // Aplicar transformação isométrica const transform = isIso ? 'matrix(1, 0.18, -0.55, 0.78, 350, -40)' : ''; return ( {/* Fundo: terreno */} {/* Cinturão verde APP (norte) */} {/* Cinturão verde APP (oeste/leste laterais) */} {/* Lago (Lagoa do Cassó) — borda sul/sudeste */} {/* Píer */} {!compacto && ( Píer privativo )} {/* Ruas — desenhar como linhas com nomes */} {/* Av. Fátima Costa — entrada esquerda */} {/* Rua das Trilhas — entre A e B/C */} {/* Rua das Árvores — entre B/C/E... */} {/* Rua dos Ventos — entre E e G */} {/* Rua do Lago — entre G e K */} {/* Rua dos Pássaros — vertical entre A/D/I/J */} {/* Rua do Sol — vertical entre D e I */} {/* Rua da Lua — vertical entre I e J */} {/* Rua dos Peixes — borda leste */} {/* Rua dos Lençóis — interna pequena */} {/* Rua Cassó — paralela ao lago */} {/* Linha tracejada central das ruas */} {/* Portaria */} {!compacto && ( Portaria 24h )} {/* Estacionamento 40 vagas */} {Array.from({length: 14}).map((_, i) => ( ))} {!compacto && ( 40 vagas )} {/* Quadra poliesportiva */} {!compacto && ( Quadra poliesportiva )} {/* LOTES */} {LOTES.map(lote => { const poly = poligonos[lote.numero]; if (!poly) return null; const isSel = selecionado === lote.numero; const isHov = hover === lote.numero; const dimmed = (filtroStatus && filtroStatus !== lote.status) || (filtroVista === 'lago' && !lote.vistaLago) || (filtroVista === 'interior' && lote.vistaLago); const colors = STATUS_COLORS[lote.status]; const cent = centroid(poly); const pathD = 'M ' + poly.map(p => p.join(',')).join(' L ') + ' Z'; return ( onHover?.(lote.numero)} onMouseLeave={() => onHover?.(null)} onClick={() => onSelect?.(lote.numero)}> {lote.status === 'vendido' && ( )} {!compacto && (poly[1][0] - poly[0][0] > 12 || poly[2][1] - poly[0][1] > 20) && ( {lote.numero} )} ); })} {/* Rótulos das quadras */} {!compacto && ( A B C D E F G H I J K L )} {/* fim isometric transform */} {/* Rótulos de ruas (não-isométricos sempre) */} {!compacto && estilo !== 'isometrico' && ( AV. FÁTIMA COSTA RUA DAS TRILHAS RUA DAS ÁRVORES RUA DOS VENTOS RUA DO LAGO RUA CASSÓ RUA DOS PÁSSAROS RUA DO SOL RUA DA LUA )} {/* Bússola */} {!compacto && ( N )} ); } // expose window.MapaLoteamento = MapaLoteamento; window.STATUS_COLORS = STATUS_COLORS; window.STATUS_LABELS = STATUS_LABELS;