PageSourceSearch

https://jusmonitor.vercel.app/app.js

js jusmonitor.vercel.app collected 2026-10-03 07:10:02 UTC 20,633 bytes, 586 lines download raw bytes

1(function () {
2  "use strict";
3
4  const TRACK_LABELS = {
5    todos: "Todos",
6    captura_institucional: "Captura institucional",
7    decisoes_impacto: "Decisões de impacto",
8  };
9
10  const GROUP_LABELS = {
11    todos: "Todos os grupos",
12    corrupcao_judicial: "Corrupção judicial",
13    cnj_disciplinar: "CNJ disciplinar",
14    penduricalhos: "Penduricalhos",
15    chokepoint_stf: "Chokepoint STF",
16    eleitoral_tse: "Eleitoral / TSE",
17    outros_judiciario: "Outros (judiciário)",
18    soltura_hc: "Soltura / HC",
19    progressao_regime: "Progressão de regime",
20    arquivamento: "Arquivamento / nulidade",
21    jurisprudencia_estrutural: "Jurisprudência estrutural",
22    foragidos_impacto: "Foragidos",
23  };
24
25  const CRIME_LABELS = {
26    todos: "Todos crimes",
27    trafico: "Tráfico",
28    homicidio: "Homicídio",
29    estupro: "Crimes sexuais",
30    latrocinio: "Latrocínio",
31    foragido: "Foragido",
32    corrupcao: "Corrupção",
33    milicia: "Milícia",
34    pcc: "PCC",
35  };
36
37  const EV_LABELS = {
38    todos: "Toda evidência",
39    "ev-confirmed": "Confirmado",
40    "ev-alleged": "Alegado / pendente",
41  };
42
43  const ICONS = {
44    penduricalhos:
45      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 00-2-2h-4a2 2 0 00-2 2v2"/><path d="M12 12v4"/><path d="M10 14h4"/></svg>',
46    corrupcao_judicial:
47      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v18"/><path d="M5 8h14"/><path d="M7 8l5 5 5-5"/></svg>',
48    cnj_disciplinar:
49      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>',
50    chokepoint_stf:
51      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 21h18"/><path d="M5 21V8l7-4 7 4v13"/><path d="M9 21v-6h6v6"/></svg>',
52    eleitoral_tse:
53      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>',
54    soltura_hc:
55      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 019.9-1"/><path d="M12 16v2"/></svg>',
56    progressao_regime:
57      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/><path d="M13 5l7 7-7 7"/></svg>',
58    arquivamento:
59      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>',
60    jurisprudencia_estrutural:
61      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>',
62    foragidos_impacto:
63      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
64    trafico:
65      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v6l4 2"/></svg>',
66    default:
67      '<svg class="icon icon-stroke card-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>',
68  };
69
70  const state = {
71    track: "todos",
72    grupo: "todos",
73    crime: "todos",
74    evidence: "todos",
75    q: "",
76  };
77
78  let raw = null;
79  let cards = [];
80
81  function esc(s) {
82    return String(s || "").replace(/[&<>"']/g, function (c) {
83      return {
84        "&": "&amp;",
85        "<": "&lt;",
86        ">": "&gt;",
87        '"': "&quot;",
88        "'": "&#39;",
89      }[c];
90    });
91  }
92
93  function iconFor(card) {
94    if (card.crime_tags && card.crime_tags.indexOf("foragido") !== -1) {
95      return ICONS.foragidos_impacto;
96    }
97    if (card.crime_tags && card.crime_tags.indexOf("trafico") !== -1) {
98      return ICONS.trafico;
99    }
100    return ICONS[card.grupo] || ICONS.default;
101  }
102
103  function fmtNum(n) {
104    if (n == null) return "—";
105    if (typeof n === "string") return n;
106    return n.toLocaleString("pt-BR");
107  }
108
109  function hcStjDisplay() {
110    const divs = raw.divergencias_nao_reconciliadas || [];
111    const d = divs.find(function (x) {
112      return x.indicador === "hc_traficantes_stj_2024";
113    });
114    if (d && d.valor_a && d.valor_b) {
115      return fmtNum(d.valor_a.numero) + " / " + fmtNum(d.valor_b.numero);
116    }
117    return fmtNum((raw.stats || {}).hc_traficantes_stj_2024);
118  }
119
120  function renderStats() {
121    const s = raw.stats || {};
122    const el = document.getElementById("statGrid");
123    const items = [
124      [raw.total, "casos no painel", ""],
125      [s.captura, "captura institucional", "gold"],
126      [s.decisoes, "decisões de impacto", "teal"],
127      [s.ev_confirmed, "ev-confirmed", ""],
128      [hcStjDisplay(), "HC tráfico STJ (2024, R4)", "red"],
129      [s.foragidos_hc_marco_aurelio_2020, "foragidos via HC (2020)", "red"],
130    ];
131    el.innerHTML = items
132      .map(function (item) {
133        return (
134          '<div class="stat-cell"><span class="stat-num ' +
135          (item[2] || "") +
136          '">' +
137          (typeof item[0] === "string" ? esc(item[0]) : fmtNum(item[0])) +
138          '</span><span class="stat-lbl">' +
139          item[1] +
140          "</span></div>"
141        );
142      })
143      .join("");
144  }
145
146  function renderAlertas() {
147    const list = raw.alertas_sistemicos || [];
148    const el = document.getElementById("alertsGrid");
149    if (!list.length) {
150      el.innerHTML =
151        '<p class="empty-state">Nenhum alerta sistêmico no corpus atual.</p>';
152      return;
153    }
154    el.innerHTML = list
155      .map(function (a) {
156        const nivel = a.nivel || "medio";
157        const label =
158          nivel === "alto"
159            ? "Risco alto"
160            : nivel === "baixo"
161              ? "Atenção"
162              : "Risco médio";
163        return (
164          '<div class="alert-card ' +
165          esc(nivel) +
166          '"><div class="alert-level">' +
167          esc(label) +
168          '</div><div class="alert-title">' +
169          esc(a.titulo) +
170          '</div><div class="alert-text">' +
171          esc(a.descricao) +
172          "</div></div>"
173        );
174      })
175      .join("");
176  }
177
178  function renderDataTable() {
179    const s = raw.stats || {};
180    const tbody = document.getElementById("dataTableBody");
181    const rows = [
182      ["Casos captura institucional", s.captura, raw.sync_date, "lawfare-timeline", "Extract JusMonitor"],
183      ["Casos decisões de impacto", s.decisoes, "2005–2026", "T-209 JustiçaWatch", "Sidecar reconciliado R1–R7"],
184      ["HC traficantes STJ (R4)", hcStjDisplay(), "2024", "STJ/ConJur × dossiê Seif", "Divergência não reconciliada"],
185      ["HC/RHC tráfico STJ (ConJur)", s.hc_stj_2024_conjur, "2024", "ConJur/STJ", "Até 26/dez/2024"],
186      ["HC tráfico privilegiado STJ", s.hc_stj_2024_privilegiado, "2024", "ConJur/STJ", "Aplicação do §4º"],
187      ["Concessões HC/RHC STJ (total)", s.hc_stj_2024_concessoes_total, "2024", "ConJur/STJ", "Base do percentual 49,1%"],
188      ["HC tráfico STF", s.hc_trafico_stf_2024, "2024", "Fontes públicas", "Agregado"],
189      ["Taxa reincidência BR", s.taxa_reincidencia_br, "—", "Fontes públicas", "Indicador contextual"],
190      ["População carcerária", s.populacao_carceraria, "—", "Fontes públicas", "Contexto"],
191      ["Foragidos HC Marco Aurélio", s.foragidos_hc_marco_aurelio_2020, "2020", "Estadão", "Investigação"],
192      ["Pendentes enriquecimento", raw.excluidos_pendente_enriquecimento, raw.sync_date, "corpus", "Lacuna de dados"],
193      ["Conflitos resolvidos (R3)", (raw.conflitos_resolvidos || []).length, raw.sync_date, "curadoria", "Ver envelope unified"],
194      ["Divergências abertas (R4)", (raw.divergencias_nao_reconciliadas || []).length, raw.sync_date, "curadoria", "Dois valores exibidos"],
195    ];
196    tbody.innerHTML = rows
197      .map(function (r) {
198        const val = typeof r[1] === "string" ? r[1] : fmtNum(r[1]);
199        return (
200          "<tr><td>" +
201          esc(r[0]) +
202          '</td><td class="num-up">' +
203          esc(val) +
204          "</td><td>" +
205          esc(r[2]) +
206          "</td><td>" +
207          esc(r[3]) +
208          "</td><td>" +
209          esc(r[4]) +
210          "</td></tr>"
211        );
212      })
213      .join("");
214  }
215
216  function renderDivergencias() {
217    const el = document.getElementById("divergenciasBox");
218    if (!el) return;
219    const list = raw.divergencias_nao_reconciliadas || [];
220    if (!list.length) {
221      el.innerHTML =
222        "<p>Nenhuma divergência numérica aberta no envelope atual.</p>";
223      return;
224    }
225    el.innerHTML = list
226      .map(function (d) {
227        const a = d.valor_a || {};
228        const b = d.valor_b || {};
229        return (
230          "<p><strong>" +
231          esc(d.indicador || "indicador") +
232          "</strong> (" +
233          esc(d.regra_aplicada || "R4") +
234          "): " +
235          esc(fmtNum(a.numero)) +
236          " (" +
237          esc(a.fonte || "fonte A") +
238          ") vs " +
239          esc(fmtNum(b.numero)) +
240          " (" +
241          esc(b.fonte || "fonte B") +
242          "). " +
243          esc(d.status || "") +
244          "</p>"
245        );
246      })
247      .join("");
248  }
249
250  function chip(label, count, attrs, active) {
251    const extra = Object.keys(attrs)
252      .map(function (k) {
253        return k + '="' + esc(attrs[k]) + '"';
254      })
255      .join(" ");
256    return (
257      '<button type="button" class="chip' +
258      (active ? " active" : "") +
259      '" ' +
260      extra +
261      ">" +
262      esc(label) +
263      (count != null ? " (" + count + ")" : "") +
264      "</button>"
265    );
266  }
267
268  function renderFilters() {
269    const tracks = document.getElementById("filterTrack");
270    const groups = document.getElementById("filterGroup");
271    const crimes = document.getElementById("filterCrime");
272    const evidence = document.getElementById("filterEvidence");
273
274    const trackCounts = { todos: cards.length };
275    const groupCounts = {};
276    const crimeCounts = {};
277    const evCounts = { todos: cards.length };
278
279    cards.forEach(function (c) {
280      trackCounts[c.track] = (trackCounts[c.track] || 0) + 1;
281      groupCounts[c.grupo] = (groupCounts[c.grupo] || 0) + 1;
282      evCounts[c.evidence_status] = (evCounts[c.evidence_status] || 0) + 1;
283      (c.crime_tags || []).forEach(function (t) {
284        crimeCounts[t] = (crimeCounts[t] || 0) + 1;
285      });
286    });
287
288    tracks.innerHTML =
289      '<span class="filters-label">Trilha:</span>' +
290      Object.keys(TRACK_LABELS)
291        .map(function (k) {
292          if (k !== "todos" && !trackCounts[k]) return "";
293          return chip(TRACK_LABELS[k], trackCounts[k] || 0, { "data-track": k }, state.track === k);
294        })
295        .join("");
296
297    const groupOrder = [
298      "todos",
299      "penduricalhos",
300      "corrupcao_judicial",
301      "cnj_disciplinar",
302      "chokepoint_stf",
303      "eleitoral_tse",
304      "soltura_hc",
305      "progressao_regime",
306      "arquivamento",
307      "jurisprudencia_estrutural",
308      "foragidos_impacto",
309      "outros_judiciario",
310    ];
311    groups.innerHTML =
312      '<span class="filters-label">Grupo:</span>' +
313      groupOrder
314        .map(function (k) {
315          if (k !== "todos" && !groupCounts[k]) return "";
316          const count = k === "todos" ? cards.length : groupCounts[k];
317          return chip(GROUP_LABELS[k], count, { "data-grp": k }, state.grupo === k);
318        })
319        .join("");
320
321    const crimeKeys = ["todos"].concat(Object.keys(CRIME_LABELS).filter(function (k) {
322      return k !== "todos" && crimeCounts[k];
323    }));
324    crimes.innerHTML =
325      '<span class="filters-label">Crime:</span>' +
326      crimeKeys
327        .map(function (k) {
328          const count = k === "todos" ? null : crimeCounts[k];
329          return chip(CRIME_LABELS[k], count, { "data-crime": k }, state.crime === k);
330        })
331        .join("");
332
333    evidence.innerHTML =
334      '<span class="filters-label">Evidência:</span>' +
335      Object.keys(EV_LABELS)
336        .map(function (k) {
337          if (k !== "todos" && !evCounts[k]) return "";
338          return chip(EV_LABELS[k], evCounts[k] || 0, { "data-ev": k }, state.evidence === k);
339        })
340        .join("");
341  }
342
343  function matches(c) {
344    if (state.track !== "todos" && c.track !== state.track) return false;
345    if (state.grupo !== "todos" && c.grupo !== state.grupo) return false;
346    if (state.evidence !== "todos" && c.evidence_status !== state.evidence) return false;
347    if (state.crime !== "todos") {
348      if (!(c.crime_tags || []).includes(state.crime) && !(c.tags || []).includes(state.crime)) {
349        return false;
350      }
351    }
352    if (!state.q) return true;
353    const hay = [
354      c.titulo,
355      c.descricao,
356      (c.tags || []).join(" "),
357      (c.instituicoes || []).join(" "),
358      (c.crime_tags || []).join(" "),
359      c.ref || "",
360      c.tribunal || "",
361      c.tipo_decisao || "",
362      c.lacuna_investigativa || "",
363      c.id != null ? String(c.id) : "",
364    ]
365      .join(" ")
366      .toLowerCase();
367    return hay.indexOf(state.q) !== -1;
368  }
369
370  function cardHTML(c) {
371    const fontesHTML =
372      c.fontes && c.fontes.length
373        ? '<div class="card-sources">' +
374          c.fontes
375            .slice(0, 4)
376            .map(function (u) {
377              return (
378                '<a href="' +
379                esc(u) +
380                '" target="_blank" rel="noopener">↗ ' +
381                esc(u) +
382                "</a>"
383              );
384            })
385            .join("") +
386          "</div>"
387        : "";
388    const tagsHTML = (c.tags || [])
389      .slice(0, 6)
390      .map(function (t) {
391        return '<span class="tag">' + esc(t) + "</span>";
392      })
393      .join("");
394    const valorHTML = c.valor_envolvido
395      ? '<div class="card-valor">' + esc(c.valor_envolvido) + "</div>"
396      : "";
397    const refHTML = c.ref
398      ? '<div class="card-ref">' + esc(c.ref) + (c.tribunal ? " · " + esc(c.tribunal) : "") + "</div>"
399      : "";
400    const lacunaHTML = c.lacuna_investigativa
401      ? '<div class="card-ref">Lacuna: ' + esc(c.lacuna_investigativa) + "</div>"
402      : "";
403    const evClass = c.evidence_status === "ev-confirmed" ? "confirmed" : "alleged";
404    const trackLabel =
405      c.track === "decisoes_impacto" ? "Decisões" : "Captura";
406
407    return (
408      '<article class="card" data-track="' +
409      esc(c.track) +
410      '">' +
411      '<div class="card-top">' +
412      '<span class="card-date">' +
413      iconFor(c) +
414      esc(c.data) +
415      "</span>" +
416      '<div class="card-badges">' +
417      '<span class="badge badge-track-' +
418      esc(c.track) +
419      '">' +
420      trackLabel +
421      "</span>" +
422      '<span class="badge badge-grp-' +
423      esc(c.grupo) +
424      '">' +
425      esc(GROUP_LABELS[c.grupo] || c.grupo) +
426      "</span>" +
427      '<span class="badge badge-ev ' +
428      evClass +
429      '">' +
430      esc(c.evidence_status) +
431      "</span>" +
432      "</div></div>" +
433      "<h3>" +
434      esc(c.titulo) +
435      "</h3>" +
436      "<p>" +
437      esc(c.descricao) +
438      "</p>" +
439      valorHTML +
440      refHTML +
441      lacunaHTML +
442      '<div class="card-meta">' +
443      tagsHTML +
444      "</div>" +
445      fontesHTML +
446      "</article>"
447    );
448  }
449
450  function renderFeed() {
451    const list = cards.filter(matches);
452    document.getElementById("countRow").innerHTML =
453      "<b>" + list.length + "</b>
453 de " + cards.length + " casos exibidos";
454    const container = document.getElementById("cardList");
455    container.innerHTML = list.length
456      ? list.map(cardHTML).join("")
457      : '<div class="empty-state">Nenhum caso corresponde aos filtros atuais.</div>';
458  }
459
460  function bindFilters() {
461    document.getElementById("filterTrack").addEventListener("click", function (e) {
462      const btn = e.target.closest("[data-track]");
463      if (!btn) return;
464      state.track = btn.getAttribute("data-track");
465      renderFilters();
466      renderFeed();
467    });
468    document.getElementById("filterGroup").addEventListener("click", function (e) {
469      const btn = e.target.closest("[data-grp]");
470      if (!btn) return;
471      state.grupo = btn.getAttribute("data-grp");
472      renderFilters();
473      renderFeed();
474    });
475    document.getElementById("filterCrime").addEventListener("click", function (e) {
476      const btn = e.target.closest("[data-crime]");
477      if (!btn) return;
478      state.crime = btn.getAttribute("data-crime");
479      renderFilters();
480      renderFeed();
481    });
482    document.getElementById("filterEvidence").addEventListener("click", function (e) {
483      const btn = e.target.closest("[data-ev]");
484      if (!btn) return;
485      state.evidence = btn.getAttribute("data-ev");
486      renderFilters();
487      renderFeed();
488    });
489    document.getElementById("search").addEventListener("input", function (e) {
490      state.q = e.target.value.trim().toLowerCase();
491      renderFeed();
492    });
493  }
494
495  function renderEcosystem(hubs) {
496    const el = document.getElementById("ecoGrid");
497    if (!el) return;
498    if (!hubs || !hubs.length) {
499      el.innerHTML = '<div class="eco-empty">Ecossistema indisponível.</div>';
500      return;
501    }
502    el.innerHTML = hubs
503      .map(function (h) {
504        const current = !!h.current;
505        const title = esc(h.title || "");
506        const desc = esc(h.description || "");
507        const href = esc(h.href || "#");
508        const badge = current ? '<span class="eco-badge">este hub</span>' : "";
509        if (current) {
510          return (
511            '<div class="eco-card is-current" aria-current="page">' +
512            '<div class="eco-card-title">' +
513            title +
514            badge +
515            "</div>" +
516            '<p class="eco-card-desc">' +
517            desc +
518            "</p></div>"
519          );
520        }
521        return (
522          '<a class="eco-card" href="' +
523          href +
524          '" target="_blank" rel="noopener noreferrer">' +
525          '<div class="eco-card-title">' +
526          title +
527          "</div>" +
528          '<p class="eco-card-desc">' +
529          desc +
530          "</p></a>"
531        );
532      })
533      .join("");
534  }
535
536  function boot(data) {
537    raw = data;
538    cards = data.cards || [];
539    const syncText = data.sync_date || "—";
540    document.getElementById("syncDate").textContent = syncText;
541    const footerSync = document.getElementById("footer-sync-date");
542    if (footerSync) footerSync.textContent = syncText;
543    document.getElementById("lacunaText").textContent =
544      (data.excluidos_pendente_enriquecimento || 0) +
545      " entradas do corpus de captura correspondem ao escopo temático mas carecem de descrição/fonte estruturada — excluídas do painel até enriquecimento.";
546    renderStats();
547    renderAlertas();
548    renderDataTable();
549    renderDivergencias();
550    renderFilters();
551    renderFeed();
552    bindFilters();
553  }
554
555  const gotop = document.getElementById("gotop");
556  window.addEventListener("scroll", function () {
557    gotop.classList.toggle("show", window.scrollY > 500);
558  });
559  gotop.addEventListener("click", function () {
560    window.scrollTo({ top: 0, behavior: "smooth" });
561  });
562
563  fetch("data/unified.json")
564    .then(function (r) {
565      if (!r.ok) throw new Error("HTTP " + r.status);
566      return r.json();
567    })
568    .then(boot)
569    .catch(function (err) {
570      document.getElementById("cardList").innerHTML =
571        '<div class="error-state">Falha ao carregar data/unified.json — ' +
572        esc(err.message) +
573        "</div>";
574    });
575
576  fetch("data/ecosystem.json")
577    .then(function (r) {
578      if (!r.ok) throw new Error("HTTP " + r.status);
579      return r.json();
580    })
581    .then(renderEcosystem)
582    .catch(function () {
583      const el = document.getElementById("ecoGrid");
584      if (el) el.innerHTML = '<div class="eco-empty">Falha ao carregar data/ecosystem.json.</div>';
585    });
586})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.