// ============================================================
// LOTAÇÃO ATUAL · v2 — "onde está cada lote e quantas cabeças tem ali".
// Camadas: SEÇÃO (cor base própria, respiro entre seções) › MÓDULO (2 tons claros
// alternados, borda grossa no tom escuro da seção) › PIQUETE (linha fina).
// Piquete com lote = preenchimento sólido (A: verde único · B: cor saturada da seção).
// Números: selo escuro por módulo (zoom fazenda) → selo por piquete (zoom seção+).
// ============================================================
const LT_BG = '#e6eae0'
const LT_LOTE_UNICO = '#2b6b32'
const LT_SELO = '#1f2a1c'
const LT_HATCH = '#d9a800'
const LT_SECOES = [{ nome: 'Norte', h: 175 }, { nome: 'Rio', h: 240 }, { nome: 'Sede', h: 55 }, { nome: 'Baixada', h: 330 }]
const ltTone = (h) => ({ h, l1: `oklch(0.95 0.025 ${h})`, l2: `oklch(0.865 0.06 ${h})`, dark: `oklch(0.43 0.12 ${h})`, sat: `oklch(0.56 0.17 ${h})` })
const LT_CINZA = 'oklch(0.83 0.004 130)'
// Esquema tetrádico (quadrado no círculo cromático): 2 pares complementares, 90° entre os demais.
// Ocre ↔ azul · verde ↔ vinho. A fronteira mais longa entre seções recebe um par complementar.
const LT_HUES = [65, 155, 245, 335]      // ocre · verde · azul · vinho
const LT_TERROSO = [75, 145, 240, 25]   // areia · oliva · ardósia · argila
function ltEstiloTons(estilo, secs) {
  return secs.map((s, i) => {
    if (estilo === 'terroso') { const h = LT_TERROSO[s.rank ?? i]; return { h, l1: `oklch(0.95 0.014 ${h})`, l2: `oklch(0.905 0.032 ${h})`, dark: `oklch(0.42 0.05 ${h})`, sat: `oklch(0.5 0.09 ${h})` } }
    if (estilo === 'neutro') return { h: 130, l1: 'oklch(0.965 0.006 130)', l2: 'oklch(0.925 0.01 130)', dark: 'oklch(0.45 0.02 130)', sat: LT_LOTE_UNICO }
    if (estilo === 'tecnico') return { h: 130, l1: '#f6f7f3', l2: '#f6f7f3', dark: 'oklch(0.36 0.02 130)', sat: LT_LOTE_UNICO }
    const h = s.tone.h
    return { ...s.tone, sat: `oklch(0.5 0.08 ${h})`, shades: [[0.86, 0.05], [0.77, 0.07], [0.68, 0.085], [0.59, 0.095]].map(([l, c]) => `oklch(${l} ${c} ${h})`) }
  })
}
const LT_CATS = ['Vaca Parida', 'Novilhas Cheias', 'Desmama 8 - 12 meses', 'Novilhas 13 - 18 meses', 'Bois de Engorda', 'Vacas Solteiras']
const LT_PIQ = ['ABCDEFGH'.split(''), Array.from({ length: 24 }, (_, i) => String(i + 1))].flat()
const ltFmt = (n) => n.toLocaleString('pt-BR')
const LT_FAIXAS = ['até 50', '51 - 150', 'acima de 150']
const ltFaixa = (q) => (q <= 50 ? 0 : q <= 150 ? 1 : 2)
const ltFaixaTom = (h, f) => [`oklch(0.80 0.085 ${h})`, `oklch(0.64 0.125 ${h})`, `oklch(0.47 0.135 ${h})`][f]
const ltModTom = (h, t) => (t ? `oklch(0.47 0.13 ${h})` : `oklch(0.62 0.14 ${h})`)
const ltEntrada = (dias) => { const d = new Date(2026, 5, 18 - dias); const z = (n) => String(n).padStart(2, '0'); return `${z(d.getDate())}/${z(d.getMonth() + 1)}/${d.getFullYear()}` }

function ltGeom(polys) {
  const G = 60, pts = {}, bb = {}, buckets = new Map()
  polys.forEach((p) => {
    const nums = (p.d.match(/-?\d+(?:\.\d+)?/g) || []).map(Number), arr = []
    for (let i = 0; i + 1 < nums.length; i += 2) arr.push([nums[i], nums[i + 1]])
    if (arr.length < 3) return
    pts[p.id] = arr
    let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9
    arr.forEach(([x, y]) => { if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y })
    bb[p.id] = { x0, y0, x1, y1 }
    for (let gx = Math.floor(x0 / G); gx <= Math.floor(x1 / G); gx++) for (let gy = Math.floor(y0 / G); gy <= Math.floor(y1 / G); gy++) {
      const k = gx + ':' + gy, a = buckets.get(k); if (a) a.push(p.id); else buckets.set(k, [p.id])
    }
  })
  const dentro = (arr, x, y) => { let c = false
    for (let i = 0, j = arr.length - 1; i < arr.length; j = i++) { const xi = arr[i][0], yi = arr[i][1], xj = arr[j][0], yj = arr[j][1]
      if (((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) c = !c } return c }
  const perto = (x, y) => { const gx = Math.floor(x / G), gy = Math.floor(y / G), out = []
    for (let i = -1; i <= 1; i++) for (let j = -1; j <= 1; j++) { const a = buckets.get((gx + i) + ':' + (gy + j)); if (a) out.push(...a) } return out }
  const celulaEm = (x, y) => {
    const cand = perto(x, y)
    for (const id of cand) { const b = bb[id]
      if (!b || x < b.x0 - 0.2 || x > b.x1 + 0.2 || y < b.y0 - 0.2 || y > b.y1 + 0.2) continue
      if (dentro(pts[id], x, y)) return id }
    let best = null, bd = 20
    for (const id of cand) for (const q of pts[id]) { const d = (q[0] - x) ** 2 + (q[1] - y) ** 2; if (d < bd) { bd = d; best = id } }
    return best
  }
  // percorre os lados de cada polígono e devolve o vizinho do outro lado
  const lados = (fn) => polys.forEach((p) => { const arr = pts[p.id]; if (!arr) return
    for (let i = 0; i < arr.length; i++) { const a = arr[i], b = arr[(i + 1) % arr.length]
      const dx = b[0] - a[0], dy = b[1] - a[1], len = Math.hypot(dx, dy); if (len < 0.6) continue
      const mx = (a[0] + b[0]) / 2, my = (a[1] + b[1]) / 2, nx = -dy / len, ny = dx / len
      const sgn = ((p.cx - mx) * nx + (p.cy - my) * ny) > 0 ? -1 : 1
      fn(p, a, b, celulaEm(mx + nx * 2.4 * sgn, my + ny * 2.4 * sgn)) } })
  const contornos = (grupoDe) => { const byG = {}
    lados((p, a, b, viz) => { const g = grupoDe(p.id); if (g == null) return
      if (viz == null || grupoDe(viz) !== g) (byG[g] || (byG[g] = [])).push('M' + a[0] + ',' + a[1] + 'L' + b[0] + ',' + b[1]) })
    return Object.entries(byG).map(([g, segs]) => ({ g, d: segs.join('') })) }
  return { celulaEm, contornos, lados }
}

// ---- Dados de exemplo (determinísticos) ---------------------------------
let LT_CACHE = null
function ltData() {
  if (LT_CACHE) return LT_CACHE
  const GM = window.MATEIRA
  const polys = GM.piquetes.filter((p) => p.ha > 0.4)
  const byId = {}; polys.forEach((p) => { byId[p.id] = p })
  const d2 = (a, b) => (a.cx - b.cx) ** 2 + (a.cy - b.cy) ** 2
  const cX = polys.reduce((a, p) => a + p.cx, 0) / polys.length, cY = polys.reduce((a, p) => a + p.cy, 0) / polys.length
  const farm = [...polys].sort((a, b) => d2(a, { cx: cX, cy: cY }) - d2(b, { cx: cX, cy: cY })).slice(0, Math.round(polys.length * 0.8))
  const kmeans = (pts, k) => {
    let C = [pts.reduce((a, p) => (p.cy < a.cy ? p : a), pts[0])]
    while (C.length < k) { let best = null, bd = -1; pts.forEach((p) => { const d = Math.min(...C.map((c) => d2(p, c))); if (d > bd) { bd = d; best = p } }); C.push(best) }
    C = C.map((c) => ({ cx: c.cx, cy: c.cy })); let lab = []
    for (let it = 0; it < 12; it++) {
      lab = pts.map((p) => { let bi = 0, bd = Infinity; C.forEach((c, i) => { const d = d2(p, c); if (d < bd) { bd = d; bi = i } }); return bi })
      C = C.map((c, i) => { const m = pts.filter((_, j) => lab[j] === i); return m.length ? { cx: m.reduce((a, p) => a + p.cx, 0) / m.length, cy: m.reduce((a, p) => a + p.cy, 0) / m.length } : c })
    }
    return Array.from({ length: k }, (_, i) => ({ c: C[i], pts: pts.filter((_, j) => lab[j] === i) })).filter((g) => g.pts.length)
  }
  let seed = 11; const rnd = () => { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646 }
  const box = (ids) => { let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9
    ids.forEach((id) => { const n = (byId[id].d.match(/-?\d+(?:\.\d+)?/g) || []).map(Number)
      for (let i = 0; i + 1 < n.length; i += 2) { x0 = Math.min(x0, n[i]); x1 = Math.max(x1, n[i]); y0 = Math.min(y0, n[i + 1]); y1 = Math.max(y1, n[i + 1]) } })
    return { x0, y0, x1, y1 } }

  const cellInfo = {}, mods = [], secs = []
  kmeans(farm, LT_SECOES.length).sort((a, b) => a.c.cy - b.c.cy).forEach((g, si) => {
    const S = LT_SECOES[si]
    secs.push({ ...S, idx: si, tone: ltTone(S.h), cells: g.pts.map((p) => p.id), box: box(g.pts.map((p) => p.id)) })
    kmeans(g.pts, Math.max(3, Math.round(g.pts.length / 10))).sort((a, b) => Math.round(a.c.cy / 40) - Math.round(b.c.cy / 40) || a.c.cx - b.c.cx).forEach((mg) => {
      const ord = [...mg.pts].sort((a, b) => Math.round(a.cy / 22) - Math.round(b.cy / 22) || a.cx - b.cx)
      const m = { idx: mods.length, code: 'M' + (mods.length + 1), sec: si, cells: ord.map((p) => p.id), piqs: {}, lotes: [], indisp: false, tone: 0 }
      ord.forEach((p, i) => { const nome = LT_PIQ[i] || String(i + 1); m.piqs[nome] = p.id; cellInfo[p.id] = { mod: m.idx, piq: nome, sec: si } })
      const ax = ord.reduce((a, p) => a + p.cx, 0) / ord.length, ay = ord.reduce((a, p) => a + p.cy, 0) / ord.length
      m.anchor = ord.reduce((best, p) => (d2(p, { cx: ax, cy: ay }) < d2(best, { cx: ax, cy: ay }) ? p : best), ord[0])
      m.box = box(m.cells)
      m.corner = ord.reduce((best, p) => ((p.cx - m.box.x0) + (p.cy - m.box.y0) < (best.cx - m.box.x0) + (best.cy - m.box.y0) ? p : best), ord[0])
      mods.push(m)
    })
  })
  // lotação: ~25% sem lote, 2 indisponíveis, alguns com 2 lotes em piquetes diferentes
  const INDISP = new Set([5, mods.length - 4])
  let nLote = 0
  mods.forEach((m) => {
    if (INDISP.has(m.idx)) { m.indisp = true; m.retorno = m.idx % 2 ? '15/08/2026' : '02/07/2026'; rnd(); return }
    const r = rnd()
    const n = r < 0.22 ? 0 : r < 0.42 ? 2 : 1
    const nomes = Object.keys(m.piqs), usados = new Set()
    for (let i = 0; i < n; i++) {
      let p; do { p = nomes[Math.floor(rnd() * nomes.length)] } while (usados.has(p) && usados.size < nomes.length); usados.add(p)
      m.lotes.push({ piquete: p, codigo: 'L-' + String(201 + nLote++), categoria: LT_CATS[Math.floor(rnd() * LT_CATS.length)], qtd: rnd() < 0.24 ? 40 + Math.floor(rnd() * 11) : 51 + Math.floor(rnd() * 200), dias: 1 + ((m.idx * 7 + i * 5) % 21) })
    }
  })
  const geom = ltGeom(farm)
  // vizinhança entre módulos → alterna os 2 tons claros dentro da seção
  const viz = {}
  geom.lados((p, a, b, o) => { const A = cellInfo[p.id], B = o != null && cellInfo[o]; if (A && B && A.mod !== B.mod && A.sec === B.sec) (viz[A.mod] || (viz[A.mod] = new Set())).add(B.mod) })
  const cor = {}
  mods.forEach((m) => { if (cor[m.idx] != null) return; cor[m.idx] = 0; const q = [m.idx]
    while (q.length) { const x = q.shift(); (viz[x] || []).forEach((y) => { if (cor[y] == null) { cor[y] = 1 - cor[x]; q.push(y) } }) } })
  mods.forEach((m) => { m.tone = cor[m.idx] || 0 })
  mods.forEach((m) => {
    const usados = new Set([...(viz[m.idx] || [])].map((o) => mods[o].shade).filter((x) => x != null))
    m.shade = [0, 1, 2, 3].map((t) => (m.idx + t) % 4).find((c) => !usados.has(c)) ?? m.idx % 4
  })
  // seções vizinhas recebem os matizes mais distantes entre si (melhor permutação)
  const secViz = new Map()   // par de seções → extensão da divisa (nº de lados)
  geom.lados((p, a, b, o) => { const A = cellInfo[p.id], B = o != null && cellInfo[o]; if (A && B && A.sec !== B.sec) { const k = Math.min(A.sec, B.sec) + ':' + Math.max(A.sec, B.sec); secViz.set(k, (secViz.get(k) || 0) + Math.hypot(b[0] - a[0], b[1] - a[1])) } })
  const dh = (x, y) => { const d = Math.abs(x - y) % 360; return Math.min(d, 360 - d) }
  const perms = (arr) => arr.length <= 1 ? [arr] : arr.flatMap((x, i) => perms([...arr.slice(0, i), ...arr.slice(i + 1)]).map((p) => [x, ...p]))
  let melhor = null, nota = -1
  perms(secs.map((_, i) => i)).forEach((p) => {
    // 1º: maior distância mínima entre vizinhas · 2º: divisas longas com cores complementares
    const ds = [...secViz].map(([k, w]) => { const [a, b] = k.split(':').map(Number); return { d: Math.min(dh(LT_HUES[p[a]], LT_HUES[p[b]]), dh(LT_TERROSO[p[a]], LT_TERROSO[p[b]]) * 1.2), w } })
    const v = ds.length ? Math.min(...ds.map((x) => x.d)) * 1e6 + ds.reduce((s2, x) => s2 + x.d * x.w, 0) : 0
    if (v > nota) { nota = v; melhor = p }
  })
  secs.forEach((s, i) => { s.rank = melhor[i]; s.h = LT_HUES[melhor[i]]; s.tone = ltTone(s.h) })
  const modOut = geom.contornos((id) => (cellInfo[id] ? cellInfo[id].mod : null))
  const secOut = geom.contornos((id) => (cellInfo[id] ? cellInfo[id].sec : null))
  const allBox = box(farm.map((p) => p.id))
  LT_CACHE = { W: GM.W, H: GM.H, polys, byId, rivers: GM.rivers || [], farm, cellInfo, mods, secs, modOut, secOut, allBox }
  return LT_CACHE
}

// ---- Legenda ------------------------------------------------------------
const LtLeg = ({ sw, children }) => (
  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-grayish-dark)', whiteSpace: 'nowrap' }}>{sw}{children}</span>
)
const ltHatchCss = `repeating-linear-gradient(45deg, ${LT_HATCH} 0 2px, #fff6cf 2px 5px)`
const LtSelo = ({ children, size = 12 }) => (
  <span style={{ display: 'inline-flex', alignItems: 'center', height: 18, padding: '0 6px', borderRadius: 99, background: LT_SELO, color: '#fff', fontSize: size, fontWeight: 800, lineHeight: 1 }}>{children}</span>
)

const LtMapBtn = ({ icon, node, label, onClick, bg }) => {
  const { Icon } = window.FazendaGuanabaraDesignSystem_b62f7e
  return (
    <button onClick={onClick} aria-label={label} style={{ width: 44, height: 44, display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid rgba(0,0,0,0.12)', background: bg || 'rgba(255,255,255,0.96)', borderRadius: 'var(--border-radius-default)', boxShadow: '0 2px 8px rgba(0,0,0,0.18)', cursor: 'pointer', padding: 0 }}>
      {node || <Icon name={icon} size={20} color="var(--color-grayish-dark)" />}
    </button>
  )
}

function LotacaoMapa({ variante = 'A', zoomInicial = 'fazenda', secaoFoco = 'Rio', uid = 'lt', estilo = 'atual', marcacao = 'solido', indispCor = 'amarela', gps = 'ok', onEnableGps, plataforma = 'android', leading, onSecoes, onTab, onExit, fazModal, fazenda = 'Mateira', onPiquete }) {
  const { useState, useRef, useLayoutEffect, useMemo, useEffect } = React
  const DS = window.FazendaGuanabaraDesignSystem_b62f7e
  const D = ltData()
  const wrapRef = useRef(null), drag = useRef(null), moved = useRef(false)
  const [size, setSize] = useState({ w: 366, h: 470 })
  const [vb, setVb] = useState(null)
  const [sel, setSel] = useState(null)
  // lista de lotes (faixa da variação E): seção › módulo › piquete
  const lotesLista = useMemo(() => D.mods.flatMap((mm) => mm.lotes.map((l) => ({ ...l, mod: mm.idx, code: mm.code, sec: mm.sec, cell: mm.piqs[l.piquete] })))
    .sort((a, b) => a.sec - b.sec || a.mod - b.mod), [])
  const [cardSel, setCardSel] = useState(() => Math.max(0, lotesLista.findIndex((l) => D.secs[l.sec].nome === secaoFoco)))
  const stripRef = useRef(null)
  const ehE = variante === 'E'
  const T = useMemo(() => ltEstiloTons(estilo, D.secs), [estilo])
  const plano = estilo !== 'atual'
  const loteSec = (si) => (variante === 'D' ? T[si].sat : LT_LOTE_UNICO)
  const marcaCor = (si) => (marcacao !== 'solido' ? LT_SELO : loteSec(si))

  const fitBox = (b, w, h, pad) => {
    const bw = b.x1 - b.x0 + pad * 2, bh = b.y1 - b.y0 + pad * 2, asp = w / h
    const vw = bw / bh > asp ? bw : bh * asp, vh = vw / asp
    return { x: (b.x0 + b.x1) / 2 - vw / 2, y: (b.y0 + b.y1) / 2 - vh / 2, w: vw, h: vh }
  }
  const secBox = () => (D.secs.find((s) => s.nome === secaoFoco) || D.secs[0]).box
  useLayoutEffect(() => {
    const el = wrapRef.current; setSize({ w: el.offsetWidth, h: el.offsetHeight })
  }, [])
  // centraliza o piquete do lote acima da faixa de cartões e com zoom de seção
  const STRIP_H = 96
  const centrar = (cellId, w0 = size.w, h0 = size.h) => {
    const c = D.byId[cellId], vw = (D.allBox.x1 - D.allBox.x0 + 28) / 2.4, kk = vw / w0, vh = h0 * kk
    setVb({ x: c.cx - vw / 2, y: c.cy - ((h0 - STRIP_H) / 2) * kk, w: vw, h: vh })
  }
  const escolherCard = (i) => {
    setCardSel(i); centrar(lotesLista[i].cell)
    const el = stripRef.current, card = el && el.children[i]
    if (card) el.scrollTo({ left: card.offsetLeft - (el.clientWidth - card.offsetWidth) / 2, behavior: 'smooth' })
  }
  // tweak de zoom: fazenda | seção (na E, seção = centraliza no cartão selecionado)
  useEffect(() => {
    const el = wrapRef.current; if (!el) return
    const w = el.offsetWidth, h = el.offsetHeight
    if (ehE && zoomInicial === 'secao') centrar(lotesLista[cardSel].cell, w, h)
    else setVb(zoomInicial === 'secao' ? fitBox(secBox(), w, h, 8) : fitBox(D.allBox, w, h, ehE ? 40 : 14))
    if (ehE) setTimeout(() => { const s = stripRef.current, card = s && s.children[cardSel]; if (card) s.scrollLeft = card.offsetLeft - (s.clientWidth - card.offsetWidth) / 2 }, 0)
  }, [zoomInicial, variante])
  const fit = () => { setCentrado(false); setVb(fitBox(D.allBox, size.w, size.h, 14)) }

  // GPS de demonstração: um piquete com lote na seção em foco
  const meuPonto = useMemo(() => {
    const m = D.mods.find((x) => D.secs[x.sec].nome === 'Rio' && x.lotes.length) || D.mods.find((x) => x.lotes.length)
    return m ? D.byId[m.piqs[m.lotes[0].piquete]] : null
  }, [])
  // Retorno do GPS: ok | buscando | vazio (fora do raio) | negada | indisponivel (sem sinal, última posição)
  const [gpsVivo, setGpsVivo] = useState(gps)
  const [aviso, setAviso] = useState(null)
  const [centrado, setCentrado] = useState(false)   // botão localizar: centralizado (preenchido) · disponível (contorno) · sem localização (riscado)
  useEffect(() => { setGpsVivo(gps); setAviso(null); setCentrado(false) }, [gps])
  const foraPonto = useMemo(() => ({ cx: D.allBox.x1 + 70, cy: D.allBox.y1 + 40 }), [])
  const semLoc = gpsVivo === 'negada' || gpsVivo === 'aparelho' || gpsVivo === 'indisponivel'
  const pontoGps = gpsVivo === 'vazio' ? foraPonto : (gpsVivo === 'negada' || gpsVivo === 'aparelho') ? null : meuPonto
  const focusPonto = () => {
    if (gpsVivo === 'negada' || gpsVivo === 'aparelho') { setAviso(gpsVivo); return }
    if (gpsVivo === 'buscando') return
    if (gpsVivo === 'indisponivel') { setGpsVivo('buscando'); setTimeout(() => setGpsVivo('indisponivel'), 2400); return }
    const p = pontoGps; if (!p) return
    if (gpsVivo === 'vazio') { setVb(fitBox({ x0: Math.min(D.allBox.x0, p.cx - 20), x1: p.cx + 30, y0: Math.min(D.allBox.y0, p.cy - 20), y1: p.cy + 30 }, size.w, size.h, 10)); return }
    const raio = gpsVivo === 'aproximada' ? 90 : 40
    setVb(fitBox({ x0: p.cx - raio, x1: p.cx + raio, y0: p.cy - raio, y1: p.cy + raio }, size.w, size.h, 0)); setCentrado(true)
  }
  const gpsChip = (() => {
    if (gpsVivo === 'aparelho') return { txt: 'Localização do aparelho desligada', acao: plataforma === 'android' ? 'Ativar' : null, fn: onEnableGps || (() => {}), forte: aviso === 'aparelho' }
    if (gpsVivo === 'aproximada') return { txt: 'Localização aproximada', acao: 'Ativar', fn: onEnableGps || (() => {}) }
    if (aviso === 'negada' || gpsVivo === 'negada') return { txt: 'Localização desligada', acao: 'Ativar', fn: onEnableGps || (() => {}), forte: aviso === 'negada' }
    if (gpsVivo === 'buscando') return { txt: 'Obtendo sua localização…', spin: true }
    if (gpsVivo === 'indisponivel') return { txt: 'Sem sinal de GPS · última posição há 12 min', acao: 'Tentar de novo', fn: focusPonto }
    if (gpsVivo === 'vazio') return { txt: 'Você está fora da fazenda · 1,2 km', acao: 'Ver', fn: focusPonto }
    return null
  })()

  const zoom = (f) => setVb((v) => { setCentrado(false); if (!v) return v; const w = Math.min(1400, Math.max(60, v.w * f)), h = w * (v.h / v.w); return { x: v.x + (v.w - w) / 2, y: v.y + (v.h - h) / 2, w, h } })
  const onDown = (e) => { moved.current = false; const t = e.touches ? e.touches : [e]
    drag.current = t.length === 2 ? { pinch: true, d0: Math.hypot(t[0].clientX - t[1].clientX, t[0].clientY - t[1].clientY), vb } : { x: t[0].clientX, y: t[0].clientY, vb } }
  const onMove = (e) => { const d = drag.current; if (!d || !vb) return; if (centrado) setCentrado(false); const t = e.touches ? e.touches : [e]
    if (d.pinch && t.length === 2) { const f = d.d0 / Math.hypot(t[0].clientX - t[1].clientX, t[0].clientY - t[1].clientY)
      const w = Math.min(1400, Math.max(60, d.vb.w * f)), h = w * (d.vb.h / d.vb.w); moved.current = true
      return setVb({ x: d.vb.x + (d.vb.w - w) / 2, y: d.vb.y + (d.vb.h - h) / 2, w, h }) }
    const dx = (t[0].clientX - d.x) / size.w * d.vb.w, dy = (t[0].clientY - d.y) / size.h * d.vb.h
    if (Math.abs(dx) + Math.abs(dy) > 1) moved.current = true
    setVb({ ...d.vb, x: d.vb.x - dx, y: d.vb.y - dy }) }
  const onUp = () => { drag.current = null }

  const k = vb ? vb.w / size.w : 1
  const zf = vb ? (D.allBox.x1 - D.allBox.x0 + 28) / vb.w : 1
  // Zoom fazenda: sem números (só cor). Zoom médio+ (≥1 toque no "+"): selo por piquete;
  // se os do módulo não cabem, vale o selo do módulo.
  const detalhe = zf >= 1.3
  // A, D, E: números só no zoom seção · B: soma por módulo no zoom fazenda · C: marcadores
  const mostraNum = variante === 'C' ? false : variante === 'B' ? true : detalhe

  // ---- selos: nunca sobrepostos; se os de piquete não cabem, vale o do módulo ----
  const { selos, codigos } = useMemo(() => {
    if (!vb || !mostraNum) return { selos: [], codigos: [] }
    const placed = [], selos = [], codigos = []
    const hit = (a, b) => a.x0 < b.x1 + 2 * k && a.x1 > b.x0 - 2 * k && a.y0 < b.y1 + 2 * k && a.y1 > b.y0 - 2 * k
    const mk = (cx, cy, txt) => { const w = (txt.length * 7.6 + 13) * k, h = 19 * k; return { cx, cy, txt, w, h, x0: cx - w / 2, x1: cx + w / 2, y0: cy - h / 2, y1: cy + h / 2 } }
    const livre = (b) => !placed.some((q) => hit(b, q))
    const tot = (m) => m.lotes.reduce((a, l) => a + l.qtd, 0)
    D.mods.filter((m) => m.lotes.length).sort((a, b) => tot(b) - tot(a)).forEach((m) => {
      if (detalhe) {
        const bs = m.lotes.map((l) => { const c = D.byId[m.piqs[l.piquete]]; return mk(c.cx, c.cy, ltFmt(l.qtd)) })
        if (bs.every((b, i) => livre(b) && !bs.some((o, j) => j < i && hit(b, o)))) { bs.forEach((b) => { placed.push(b); selos.push(b) }); return }
      }
      const b0 = mk(m.anchor.cx, m.anchor.cy, ltFmt(tot(m)))
      const dx = b0.w + 4 * k, dy = 22 * k
      const offs = [[0, 0], [0, -1], [0, 1], [-1, 0], [1, 0], [-1, -1], [1, -1], [-1, 1], [1, 1], [0, -2], [0, 2], [-2, 0], [2, 0], [0, -3], [0, 3]]
      for (const [ox, oy] of offs) { const b = mk(b0.cx + ox * dx, b0.cy + oy * dy, b0.txt); if (livre(b)) { placed.push(b); selos.push(b); return } }
    })
    return { selos, codigos }
  }, [vb, size.w, detalhe, mostraNum, variante])

  const gap = 2.6 // px de respiro de cada lado da divisa entre seções

  // ---- bottom sheet ----
  const ficha = sel && (() => {
    const m = D.mods[sel.mod], sec = D.secs[m.sec], lote = m.lotes.find((l) => l.piquete === sel.piq)
    // mesmo vocabulário de selos da ficha original (app + web)
    const pill = (txt, bg, fg) => <span style={{ flexShrink: 0, whiteSpace: 'nowrap', fontSize: 11, fontWeight: 'var(--font-weight-bold)', textTransform: 'uppercase', letterSpacing: 'var(--letter-space-xs)', padding: '4px 10px', borderRadius: 99, background: bg, color: fg }}>{txt}</span>
    const selo = lote ? pill('Com Lote', 'var(--color-primary)', '#fff')
      : m.indisp ? (indispCor === 'cinza' ? pill('Indisponível', 'var(--color-neutral-light-500)', 'var(--color-grayish-dark)') : pill('Indisponível', 'var(--color-alert-light-100)', 'var(--color-alert-dark)'))
      : pill('Sem Lote', 'var(--color-neutral-light-500)', 'var(--color-grayish-dark)')
    return (
      <div style={{ position: 'absolute', inset: 0, zIndex: 60, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
        <div onClick={() => setSel(null)} style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.28)' }}></div>
        <div style={{ position: 'relative', background: '#fff', borderRadius: '20px 20px 0 0', padding: '14px 18px 20px', boxShadow: '0 -8px 26px rgba(0,0,0,0.22)', animation: 'fgSheet .22s ease-out' }}>
          <div style={{ width: 40, height: 4, borderRadius: 99, background: 'var(--color-neutral-200)', margin: '0 auto 12px' }}></div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 3, marginBottom: 14 }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
              <span style={{ minWidth: 0, fontSize: 20, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-grayish-dark)', lineHeight: 1.2, textWrap: 'pretty' }}>Módulo {m.code} - Piquete {sel.piq}</span>
              {selo}
            </div>
            <span style={{ fontSize: 13, color: 'var(--color-grayish)' }}>Seção {sec.nome}</span>
          </div>
          {lote ? (
            <div style={{ display: 'flex', flexDirection: 'column' }}>
              <div style={{ display: 'flex', alignItems: 'baseline', gap: 5 }}>
                <span style={{ fontSize: 32, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-primary)', lineHeight: 1 }}>{ltFmt(lote.qtd)}</span>
                <span style={{ fontSize: 14, fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-grayish)' }}>cabeças de {lote.categoria}</span>
              </div>
              <div style={{ fontSize: 14, color: 'var(--color-grayish)', marginTop: 6 }}>Entrada em {ltEntrada(lote.dias)} · há {lote.dias} {lote.dias === 1 ? 'dia' : 'dias'}</div>
            </div>
          ) : (
            m.indisp ? (
              <div style={{ display: 'flex', flexDirection: 'column' }}>
                <span style={{ fontSize: 14, color: 'var(--color-grayish)' }}>Previsão de retorno</span>
                <span style={{ fontSize: 20, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-grayish-dark)', marginTop: 4 }}>{m.retorno}</span>
              </div>
            ) : (() => {
              // dias desde a última saída de lote (exemplo determinístico por piquete)
              const piqIdx = Object.keys(m.piqs).indexOf(sel.piq)
              const desc = 3 + ((m.idx * 11 + piqIdx * 7) % 58)
              return (
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 5 }}>
                  <span style={{ fontSize: 32, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-grayish-dark)', lineHeight: 1 }}>{desc}</span>
                  <span style={{ fontSize: 14, fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-grayish)' }}>dias de descanso</span>
                </div>
              )
            })()
          )}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 16 }}>
            <window.MButton onClick={() => verHistorico(m, sel.piq)}>Ver histórico</window.MButton>
          </div>
        </div>
      </div>
    )
  })()

  // Histórico: abre a ficha do piquete no cadastro da fazenda (módulo de exemplo → módulo real)
  const verHistorico = (m, piq) => {
    if (!onPiquete || !window.PAST) return
    const reais = window.PAST.secoesDe(fazenda).flatMap((s) => s.modulos)
    if (!reais.length) return
    const real = reais[m.idx % reais.length]
    setSel(null)
    onPiquete(real, real.piquetes.includes(piq) ? piq : real.piquetes[0])
  }

  const swLote = (cor) => {
    const base = T[0].l1, bd = '1px solid ' + T[0].dark
    if (marcacao === 'pontos') return { background: `radial-gradient(circle, ${cor} 1.3px, transparent 1.7px) 0 0 / 4px 4px, ${base}`, border: bd }
    if (marcacao === 'hachura') return { background: `repeating-linear-gradient(45deg, ${cor} 0 1.6px, ${base} 1.6px 4px)`, border: bd }
    if (marcacao === 'contorno') return { background: base, boxShadow: 'inset 0 0 0 2.5px ' + cor }
    if (marcacao === 'ponto') return { background: `radial-gradient(circle, ${cor} 3px, transparent 3.6px), ${base}`, border: bd }
    return { background: cor }
  }

  const header = (
    <div style={{ position: 'relative', zIndex: 30, padding: '10px 14px', background: '#fff', borderBottom: 'var(--border-neutral-light)', display: 'flex', alignItems: 'center', gap: 10 }}>
      <div style={{ flex: 1, minWidth: 0, display: 'flex', flexWrap: 'wrap', columnGap: 12, rowGap: 7 }}>
        {estilo === 'neutro' || estilo === 'tecnico'
          ? <LtLeg sw={<span style={{ width: 16, height: 0, borderTop: '2px ' + (estilo === 'tecnico' ? 'dashed ' : 'solid ') + T[0].dark }}></span>}>Seção</LtLeg>
          : <LtLeg sw={<span style={{ display: 'inline-flex', gap: 2 }}>{T.map((t, i) => <span key={i} style={{ width: 9, height: 13, borderRadius: 2, background: t.shades ? t.shades[2] : t.l2, border: t.shades ? 'none' : '1.5px solid ' + t.dark }}></span>)}</span>}>Seção</LtLeg>}
        {variante !== 'C' && <LtLeg sw={<span style={{ display: 'inline-flex', gap: 2 }}>{(variante === 'D' && marcacao === 'solido' ? T.map((_, i) => i) : [0]).map((i) => <span key={i} style={{ width: variante === 'D' && marcacao === 'solido' ? 10 : 14, height: 14, borderRadius: 3, flexShrink: 0, ...swLote(marcaCor(i)) }}></span>)}</span>}>Com lote</LtLeg>}
        {variante === 'C' && [6, 9, 12].map((rr, i) => <LtLeg key={i} sw={<span style={{ width: 2 * rr, height: 2 * rr, borderRadius: '50%', background: LT_SELO, flexShrink: 0 }}></span>}>{LT_FAIXAS[i]}</LtLeg>)}
        <LtLeg sw={<span style={{ width: 14, height: 14, borderRadius: 3, ...(indispCor === 'cinza' ? { background: LT_CINZA, border: '1px solid oklch(0.7 0.004 130)' } : { background: ltHatchCss, border: '1px solid ' + LT_HATCH }) }}></span>}>Indisponível</LtLeg>
      </div>
      <DS.Button type="outline" onClick={onSecoes || (() => {})} style={{ minHeight: 44, padding: '0 14px', flexShrink: 0, whiteSpace: 'nowrap' }}>Por seção</DS.Button>
    </div>
  )

  const tap = (id) => (e) => { e.stopPropagation(); if (moved.current) return; const c = D.cellInfo[id]; setSel({ mod: c.mod, piq: c.piq, cell: id }) }
  const hatchId = uid + '-hatch'

  return (
    <window.MPhone title="Lotação atual" leadingNode={leading ||
      <span style={{ display: 'inline-flex', alignItems: 'center', margin: '-8px 0', padding: '8px 12px 8px 0', minHeight: 44 }}>
        <span style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-between', width: 19, height: 14 }}>{[0, 1, 2].map((i) => <span key={i} style={{ height: 2.5, borderRadius: 2, background: '#fff' }}></span>)}</span>
      </span>} actionIcon="exit-outline" onAction={onExit || (() => {})}
      footer={<window.PastTabBar active="consultar" onTab={onTab || (() => {})} />} modal={fazModal || ficha}>
      <div style={{ margin: '-20px -20px 0', flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
        {header}
        <div ref={wrapRef} style={{ position: 'relative', flex: 1, minHeight: 0, background: LT_BG, backgroundImage: plano ? 'none' : 'radial-gradient(circle, rgba(120,134,110,0.22) 1px, transparent 1.2px)', backgroundSize: '22px 22px', overflow: 'hidden' }}>
          {vb && (
            <svg viewBox={`${vb.x} ${vb.y} ${vb.w} ${vb.h}`} width="100%" height="100%" style={{ display: 'block', touchAction: 'none', cursor: 'grab' }}
              onMouseDown={onDown} onMouseMove={onMove} onMouseUp={onUp} onMouseLeave={onUp} onTouchStart={onDown} onTouchMove={onMove} onTouchEnd={onUp}
              onWheel={(e) => { e.preventDefault(); zoom(e.deltaY > 0 ? 1.12 : 0.89) }}>
              <defs>
                <pattern id={hatchId} width={6 * k} height={6 * k} patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
                  <rect width={6 * k} height={6 * k} fill="#fff6cf" fillOpacity="0.55"></rect>
                  <line x1="0" y1="0" x2="0" y2={6 * k} stroke={LT_HATCH} strokeWidth={2.4 * k}></line>
                </pattern>
                {D.secs.map((s) => { const cor = marcaCor(s.idx); return (
                  <React.Fragment key={'pt' + s.idx}>
                    <pattern id={`${uid}-dots${s.idx}`} width={5 * k} height={5 * k} patternUnits="userSpaceOnUse"><circle cx={2.5 * k} cy={2.5 * k} r={1.6 * k} fill={cor}></circle></pattern>
                    <pattern id={`${uid}-hl${s.idx}`} width={5 * k} height={5 * k} patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><line x1="0" y1="0" x2="0" y2={5 * k} stroke={cor} strokeWidth={2 * k}></line></pattern>
                  </React.Fragment>
                ) })}
                {marcacao === 'contorno' && lotesLista.map((l) => <clipPath key={'cp' + l.cell} id={`${uid}-cp${l.cell}`}><path d={D.byId[l.cell].d}></path></clipPath>)}
                {D.secs.map((s) => (
                  <mask key={s.nome} id={`${uid}-m${s.idx}`} maskUnits="userSpaceOnUse" x={-100} y={-100} width={D.W + 200} height={D.H + 200}>
                    {s.cells.map((id) => <path key={id} d={D.byId[id].d} fill="#fff"></path>)}
                  </mask>
                ))}
              </defs>
              {D.polys.filter((p) => !D.cellInfo[p.id]).map((p) => <path key={'u' + p.id} d={p.d} fill="#e3e6de" stroke="#dde1d7" strokeWidth={0.4 * k} pointerEvents="none"></path>)}
              {D.rivers.map((d, i) => <path key={'r' + i} d={d} fill="none" stroke={plano ? '#a3bfcd' : '#7fb0cf'} strokeWidth={(plano ? 1 : 1.3) * k} strokeLinecap="round" opacity="0.9" pointerEvents="none"></path>)}
              {D.secs.map((s) => { const t = T[s.idx]; return (
                <g key={'s' + s.idx} mask={`url(#${uid}-m${s.idx})`}>
                  {s.cells.map((id) => {
                    const c = D.cellInfo[id], m = D.mods[c.mod]
                    const marca = variante !== 'C' && m.lotes.some((l) => l.piquete === c.piq)
                    const vazio = m.indisp && indispCor === 'cinza' ? LT_CINZA : estilo === 'atual' ? t.shades[m.shade] : (m.tone ? t.l2 : t.l1)
                    const fill = marca && marcacao === 'solido' ? loteSec(s.idx) : vazio
                    return <path key={id} d={D.byId[id].d} onClick={tap(id)} style={{ cursor: 'pointer', fill, stroke: estilo === 'tecnico' ? '#b9c0b3' : estilo === 'atual' ? '#fff' : t.dark }}
                      strokeOpacity={estilo === 'tecnico' ? 1 : estilo === 'atual' ? 0.6 : marca ? 0.7 : 0.45} strokeWidth={0.55 * k} strokeLinejoin="round"></path>
                  })}
                  {variante !== 'C' && marcacao !== 'solido' && s.cells.map((id) => {
                    const c = D.cellInfo[id], m = D.mods[c.mod]
                    if (!m.lotes.some((l) => l.piquete === c.piq)) return null
                    const p = D.byId[id], cor = marcaCor(s.idx)
                    if (marcacao === 'pontos') return <path key={'mk' + id} d={p.d} fill={`url(#${uid}-dots${s.idx})`} pointerEvents="none"></path>
                    if (marcacao === 'hachura') return <path key={'mk' + id} d={p.d} fill={`url(#${uid}-hl${s.idx})`} pointerEvents="none"></path>
                    if (marcacao === 'contorno') return <g key={'mk' + id} clipPath={`url(#${uid}-cp${id})`} pointerEvents="none"><path d={p.d} fill="none" stroke={cor} strokeWidth={6 * k} strokeLinejoin="round"></path></g>
                    return <circle key={'mk' + id} cx={p.cx} cy={p.cy} r={5.5 * k} fill={cor} stroke="#fff" strokeWidth={1.4 * k} pointerEvents="none"></circle>
                  })}
                  {indispCor === 'amarela' && D.mods.filter((m) => m.sec === s.idx && m.indisp).map((m) => m.cells.map((id) => <path key={'h' + id} d={D.byId[id].d} fill={`url(#${hatchId})`} pointerEvents="none"></path>))}
                  {D.modOut.filter((o) => D.mods[+o.g].sec === s.idx && (estilo === 'tecnico' || (D.mods[+o.g].indisp && indispCor === 'cinza'))).map((o) => <path key={'mo' + o.g} d={o.d} fill="none"
                    stroke={estilo === 'tecnico' && !D.mods[+o.g].indisp ? t.dark : 'oklch(0.6 0.004 130)'} strokeWidth={(estilo === 'tecnico' ? 1.2 : 1.6) * k} strokeLinecap="round" strokeLinejoin="round" pointerEvents="none"></path>)}
                  {D.secOut.filter((o) => +o.g === s.idx).map((o) => estilo === 'tecnico' ? (
                    <path key={'so' + o.g} d={o.d} fill="none" stroke={t.dark} strokeWidth={1.6 * k} strokeDasharray={`${5 * k} ${3 * k}`} pointerEvents="none"></path>
                  ) : (
                    <path key={'so' + o.g} d={o.d} fill="none" stroke={LT_BG} strokeWidth={2 * gap * k} strokeLinecap="round" strokeLinejoin="round" pointerEvents="none"></path>
                  ))}
                </g>
              ) })}
              {!detalhe && D.secs.map((s) => (
                <text key={'sl' + s.idx} x={(s.box.x0 + s.box.x1) / 2} y={(s.box.y0 + s.box.y1) / 2} textAnchor="middle" fontSize={11.5 * k} fontWeight="800" letterSpacing={1.2 * k}
                  fontFamily="var(--font-family)" pointerEvents="none" style={{ fill: estilo === 'atual' ? 'oklch(0.3 0.06 ' + T[s.idx].h + ')' : T[s.idx].dark, paintOrder: 'stroke', stroke: 'rgba(255,255,255,0.92)', strokeWidth: 3.2 * k }}>{s.nome.toUpperCase()}</text>
              ))}
              {sel && <path d={D.byId[sel.cell].d} fill="none" stroke="#111" strokeWidth={2.6 * k} strokeLinejoin="round" pointerEvents="none"></path>}
              {pontoGps && (() => {
                const x = pontoGps.cx, y = pontoGps.cy + (gpsVivo === 'vazio' ? 0 : 14 * k)
                if (gpsVivo === 'buscando') return (
                  <g pointerEvents="none">
                    <circle cx={x} cy={y} r={34 * k} fill="#2f7cd6" fillOpacity="0.1" stroke="#2f7cd6" strokeOpacity="0.35" strokeWidth={1 * k}></circle>
                    <circle cx={x} cy={y} r={6 * k} fill="#2f7cd6" fillOpacity="0.35" stroke="#2f7cd6" strokeWidth={1.4 * k}>
                      <animate attributeName="r" values={`${6 * k};${34 * k}`} dur="1.6s" repeatCount="indefinite"></animate>
                      <animate attributeName="fill-opacity" values="0.35;0" dur="1.6s" repeatCount="indefinite"></animate>
                      <animate attributeName="stroke-opacity" values="0.8;0" dur="1.6s" repeatCount="indefinite"></animate>
                    </circle>
                    <circle cx={x} cy={y} r={5 * k} fill="#9db7d8" stroke="#fff" strokeWidth={2 * k}></circle>
                  </g>
                )
                if (gpsVivo === 'aproximada') return (
                  <g pointerEvents="none">
                    <circle cx={x} cy={y} r={70 * k} fill="#2f7cd6" fillOpacity="0.12" stroke="#2f7cd6" strokeOpacity="0.45" strokeWidth={1.2 * k}></circle>
                    <circle cx={x} cy={y} r={5.5 * k} fill="#2f7cd6" stroke="#fff" strokeWidth={2.2 * k}></circle>
                  </g>
                )
                if (gpsVivo === 'indisponivel') return (
                  <g pointerEvents="none">
                    <circle cx={x} cy={y} r={22 * k} fill="#8a9099" fillOpacity="0.1" stroke="#8a9099" strokeWidth={1.1 * k} strokeDasharray={`${3 * k} ${2.5 * k}`}></circle>
                    <circle cx={x} cy={y} r={5.5 * k} fill="#9aa0a8" stroke="#fff" strokeWidth={2.2 * k}></circle>
                  </g>
                )
                return (
                  <g pointerEvents="none">
                    <circle cx={x} cy={y} r={14 * k} fill="#2f7cd6" opacity="0.16"></circle>
                    <circle cx={x} cy={y} r={5.5 * k} fill="#2f7cd6" stroke="#fff" strokeWidth={2.2 * k}></circle>
                  </g>
                )
              })()}
              {variante === 'C' && lotesLista.map((l, i) => {
                const c = D.byId[l.cell], f = ltFaixa(l.qtd)
                const rr = (detalhe ? [10, 12.5, 15] : [4, 6, 8.5])[f] * k
                return (
                  <g key={'mk' + i} pointerEvents="none">
                    <circle cx={c.cx} cy={c.cy} r={rr} fill={LT_SELO} stroke="#fff" strokeWidth={1.4 * k}></circle>
                    {detalhe && <text x={c.cx} y={c.cy + 3.9 * k} textAnchor="middle" fontSize={(f === 2 ? 11.5 : 10.5) * k} fontWeight="800" fontFamily="var(--font-family)" fill="#fff">{ltFmt(l.qtd)}</text>}
                  </g>
                )
              })}
              {ehE && lotesLista[cardSel] && (
                <g pointerEvents="none">
                  <path d={D.byId[lotesLista[cardSel].cell].d} fill="none" stroke="#fff" strokeWidth={6.5 * k} strokeLinejoin="round"></path>
                  <path d={D.byId[lotesLista[cardSel].cell].d} fill="none" stroke={LT_SELO} strokeWidth={4 * k} strokeLinejoin="round"></path>
                </g>
              )}
              {codigos.map((c) => (
                <text key={c.txt} x={c.x} y={c.y} textAnchor="middle" fontSize={c.fs} fontWeight="800" fontFamily="var(--font-family)" pointerEvents="none"
                  style={{ fill: c.cor, paintOrder: 'stroke', stroke: 'rgba(255,255,255,0.92)', strokeWidth: 3 * k }}>{c.txt}</text>
              ))}
              {selos.map((b, i) => (
                <g key={'b' + i} pointerEvents="none">
                  <rect x={b.x0} y={b.y0} width={b.w} height={b.h} rx={plano ? 3 * k : b.h / 2} fill={LT_SELO} stroke={plano ? 'none' : '#fff'} strokeWidth={1.3 * k}></rect>
                  <text x={b.cx} y={b.cy + 4.4 * k} textAnchor="middle" fontSize={12.5 * k} fontWeight={plano ? 700 : 800} fontFamily="var(--font-family)" fill="#fff">{b.txt}</text>
                </g>
              ))}
            </svg>
          )}
          {ehE && (
            <div ref={stripRef} style={{ position: 'absolute', left: 0, right: 0, bottom: 10, display: 'flex', gap: 8, padding: '4px 12px', overflowX: 'auto', scrollbarWidth: 'none', scrollSnapType: 'x mandatory' }}>
              {lotesLista.map((l, i) => {
                const on = i === cardSel
                return (
                  <div key={i} onClick={() => escolherCard(i)} style={{ flex: '0 0 auto', width: 190, display: 'flex', flexDirection: 'column', gap: 2, padding: '10px 12px', background: '#fff', borderRadius: 'var(--border-radius-default)', border: '2px solid ' + (on ? 'var(--color-primary)' : 'transparent'), boxShadow: '0 2px 10px rgba(0,0,0,0.18)', cursor: 'pointer', scrollSnapAlign: 'center' }}>
                    <span style={{ fontSize: 13, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-grayish-dark)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{l.categoria}</span>
                    <span style={{ fontSize: 12, color: 'var(--color-grayish)' }}>Módulo {l.code} · Piquete {l.piquete}</span>
                    <span style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginTop: 2 }}>
                      <span style={{ fontSize: 18, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-primary)', lineHeight: 1.1 }}>{ltFmt(l.qtd)}</span>
                      <span style={{ fontSize: 12, fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-grayish)' }}>cabeças</span>
                    </span>
                  </div>
                )
              })}
            </div>
          )}
          {gpsChip && (
            <div style={{ position: 'absolute', top: 12, left: 12, right: 68, display: 'flex' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, minHeight: 44, padding: '4px 6px 4px 12px', background: '#fff', borderRadius: 'var(--border-radius-default)', boxShadow: '0 2px 10px rgba(0,0,0,0.16)', border: gpsChip.forte ? '1.5px solid var(--color-alert-dark)' : '1px solid rgba(0,0,0,0.08)', maxWidth: '100%' }}>
                {gpsChip.spin
                  ? <svg width="16" height="16" viewBox="0 0 16 16" style={{ flexShrink: 0 }}><circle cx="8" cy="8" r="6" fill="none" stroke="#d6e2f1" strokeWidth="2"></circle><path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="#2f7cd6" strokeWidth="2" strokeLinecap="round"><animateTransform attributeName="transform" type="rotate" from="0 8 8" to="360 8 8" dur="0.9s" repeatCount="indefinite"></animateTransform></path></svg>
                  : <svg width="16" height="16" viewBox="0 0 20 20" fill="none" stroke="var(--color-grayish)" strokeWidth="1.8" strokeLinecap="round" style={{ flexShrink: 0 }}><circle cx="10" cy="10" r="3.2"></circle><circle cx="10" cy="10" r="6.6"></circle><path d="M10 1.5v2.2M10 16.3v2.2M1.5 10h2.2M16.3 10h2.2"></path></svg>}
                <span style={{ flex: 1, minWidth: 0, fontSize: 13, color: 'var(--color-grayish-dark)', lineHeight: 1.25, padding: '6px 0', paddingRight: gpsChip.acao ? 0 : 6 }}>{gpsChip.txt}</span>
                {gpsChip.acao && <button onClick={gpsChip.fn} style={{ minHeight: 36, padding: '0 8px', border: 'none', background: 'transparent', cursor: 'pointer', fontFamily: 'var(--font-family)', fontSize: 13, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-primary)', whiteSpace: 'nowrap' }}>{gpsChip.acao}</button>}
              </div>
            </div>
          )}
          <div style={{ position: 'absolute', top: 12, right: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
            <LtMapBtn icon="add" label="Aproximar" onClick={() => zoom(0.72)} />
            <LtMapBtn icon="remove" label="Afastar" onClick={() => zoom(1.38)} />
            <LtMapBtn label="Ver fazenda toda" onClick={fit} node={<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="var(--color-grayish-dark)" strokeWidth="1.8" strokeLinecap="round"><path d="M3 7V3h4M17 7V3h-4M3 13v4h4M17 13v4h-4"></path></svg>} />
            <LtMapBtn label="Onde estou" onClick={focusPonto} bg={centrado && !semLoc ? '#2f7cd6' : undefined} node={(() => {
              const c = semLoc ? '#8a9099' : centrado ? '#fff' : '#2f7cd6'
              return <svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round">
                <circle cx="10" cy="10" r="3.4" fill={centrado && !semLoc ? c : 'none'}></circle><circle cx="10" cy="10" r="6.6"></circle><path d="M10 1.5v2.2M10 16.3v2.2M1.5 10h2.2M16.3 10h2.2"></path>
                {semLoc && <path d="M3 17L17 3" stroke="#8a9099" strokeWidth="2"></path>}
              </svg>
            })()} />
          </div>
        </div>
      </div>
    </window.MPhone>
  )
}

window.LotacaoMapa = LotacaoMapa
window.ltData = ltData
