Files
pet16704/web/index.html
T
rodado fcc9eeeaa2 fix: caminhos relativos para parte1/parte2 a partir de web/
- fetchProcessoMd tenta ../parteN/ e parteN/, cobrindo servidor local iniciado dentro de web/
- corrige link para parte1/parte2 em revelacoes.html
2026-09-13 13:25:24 +02:00

1673 lines
71 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Análise da Petição Nº 16704</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Source+Serif+4:opsz,wght@8..60,500;8..60,600;8..60,700&family=Public+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap">
<style>
[hidden]:not([hidden=until-found i]){ display:none !important; }
:root{
color-scheme: light;
--page: #f9f9f7;
--surface: #fcfcfb;
--ink: #0b0b0b;
--ink-2: #52514e;
--ink-3: #898781;
--grid: #e1e0d9;
--baseline: #c3c2b7;
--border: rgba(11,11,11,0.10);
--ring: #0b0b0b;
--s1: #2a78d6; /* STF · Relatoria */
--s2: #eb6834; /* PGR */
--s3: #1baf7a; /* Polícia Federal */
--s4: #eda100; /* Partes e Defesas */
--s5: #e87ba4; /* Secretaria e Cartório */
--accent: var(--s1);
--accent-wash: rgba(42,120,214,0.10);
}
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
color-scheme: dark;
--page: #0d0d0d;
--surface: #1a1a19;
--ink: #ffffff;
--ink-2: #c3c2b7;
--ink-3: #898781;
--grid: #2c2c2a;
--baseline: #383835;
--border: rgba(255,255,255,0.10);
--ring: #ffffff;
--s1: #3987e5;
--s2: #d95926;
--s3: #199e70;
--s4: #c98500;
--s5: #d55181;
--accent-wash: rgba(57,135,229,0.16);
}
}
:root[data-theme="dark"]{
color-scheme: dark;
--page: #0d0d0d;
--surface: #1a1a19;
--ink: #ffffff;
--ink-2: #c3c2b7;
--ink-3: #898781;
--grid: #2c2c2a;
--baseline: #383835;
--border: rgba(255,255,255,0.10);
--ring: #ffffff;
--s1: #3987e5;
--s2: #d95926;
--s3: #199e70;
--s4: #c98500;
--s5: #d55181;
--accent-wash: rgba(57,135,229,0.16);
}
*{ box-sizing:border-box; }
body{
background:var(--page);
color:var(--ink);
font-family:"Public Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
padding-inline: max(16px, calc((100vw - 1180px)/2));
padding-block: 28px 60px;
}
h1,h2,h3{ font-family:"Source Serif 4", Georgia, serif; text-wrap:balance; margin:0; }
.mono{ font-family:"IBM Plex Mono", ui-monospace, SFMono-Regular, monospace; }
.tabnum{ font-variant-numeric: tabular-nums; }
/* ---- header ---- */
header{ display:flex; flex-direction:column; gap:14px; margin-bottom:22px; }
h1{ font-size:clamp(24px,3.2vw,32px); font-weight:600; letter-spacing:-0.01em; }
.lede{ max-width:62ch; color:var(--ink-2); font-size:14.5px; line-height:1.55; margin:0; }
.lede .mono{ font-size:12.5px; background:var(--accent-wash); color:var(--ink); padding:1px 6px; border-radius:4px; }
.stats{ display:flex; flex-wrap:wrap; gap:10px; margin-top:4px; }
.stat{
background:var(--surface); border:1px solid var(--border); border-radius:10px;
padding:10px 14px; min-width:126px; flex:1 1 126px;
}
.stat .k{ font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--ink-3); }
.stat .v{
font-family:"Source Serif 4",serif; font-size:20px; font-weight:600; margin-top:2px;
white-space:nowrap; font-variant-numeric:tabular-nums;
}
.stat .v.compact{ font-family:"IBM Plex Mono",monospace; font-size:14px; letter-spacing:-0.01em; font-weight:500; }
.stat.link{ cursor:pointer; }
.stat.link:hover{ border-color: var(--accent); }
.stat.link:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
/* ---- controls ---- */
.controls{
display:flex; flex-wrap:wrap; align-items:center; gap:10px 16px;
padding:10px 0 16px; border-bottom:1px solid var(--grid); margin-bottom:16px;
}
.search{
display:flex; align-items:center; gap:8px;
background:var(--surface); border:1px solid var(--border); border-radius:8px;
padding:7px 10px; min-width:220px; flex:1 1 220px; max-width:320px;
}
.search svg{ flex:none; opacity:.55; }
.search input{
border:0; outline:0; background:transparent; color:var(--ink); font:inherit; width:100%; font-size:13.5px;
}
.search input::placeholder{ color:var(--ink-3); }
.legend{ display:flex; flex-wrap:wrap; gap:6px; }
.chip{
display:flex; align-items:center; gap:6px;
border:1px solid var(--border); background:var(--surface); border-radius:999px;
padding:5px 10px 5px 8px; font-size:12.5px; color:var(--ink-2); cursor:pointer; user-select:none;
transition: opacity .15s ease;
}
.chip:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.chip .dot{ width:9px; height:9px; border-radius:50%; flex:none; }
.chip .n{ color:var(--ink-3); }
.chip[data-off="true"]{ opacity:.4; }
.chip[data-off="true"] .dot{ background:transparent !important; border:1.5px solid var(--ink-3); }
.dropdown{ position:relative; }
.dropdown-btn{
display:flex; align-items:center; gap:6px;
border:1px solid var(--border); background:var(--surface); color:var(--ink-2);
font:inherit; font-size:12.5px; padding:7px 12px; border-radius:8px; cursor:pointer;
}
.dropdown-btn:hover{ border-color:var(--accent); color:var(--ink); }
.dropdown-btn .arrow{ font-size:10px; opacity:.6; }
.dropdown-btn[aria-expanded="true"]{ border-color:var(--accent); background:var(--accent-wash); color:var(--ink); }
.dropdown-btn .tag{ background:var(--accent); color:#fff; border-radius:999px; font-size:10.5px; padding:1px 6px; }
.dropdown-panel{
position:absolute; top:calc(100% + 6px); left:0; z-index:8; width:280px;
background:var(--surface); border:1px solid var(--border); border-radius:10px;
box-shadow:0 10px 28px rgba(0,0,0,.18); padding:10px; display:flex; flex-direction:column; gap:8px;
}
.dropdown-search input{
width:100%; border:1px solid var(--border); background:var(--page); color:var(--ink);
border-radius:6px; padding:6px 8px; font:inherit; font-size:12.5px; outline:0;
}
.dropdown-search input:focus{ border-color:var(--accent); }
.dropdown-actions{ display:flex; gap:8px; }
.dropdown-actions button{
border:0; background:none; color:var(--accent); font:inherit; font-size:12px; cursor:pointer; padding:0;
}
.dropdown-actions button:hover{ text-decoration:underline; }
.dropdown-list{ max-height:260px; overflow-y:auto; display:flex; flex-direction:column; gap:1px; }
.type-row{
display:flex; align-items:center; gap:8px; padding:5px 4px; border-radius:6px; cursor:pointer; font-size:12.5px;
}
.type-row:hover{ background:var(--accent-wash); }
.type-row input{ flex:none; accent-color: var(--accent); }
.type-row .lbl{ flex:1 1 auto; color:var(--ink); }
.type-row .n{ color:var(--ink-3); font-size:11.5px; }
.type-row.hidden-by-search{ display:none; }
.dropdown-panel-wide{ width:340px; }
.dropdown-hint{ font-size:11px; color:var(--ink-3); }
.role-chips{ display:flex; flex-wrap:wrap; gap:5px; padding-bottom:4px; border-bottom:1px solid var(--grid); }
.role-chip{
border:1px solid var(--border); background:var(--page); color:var(--ink-2);
font:inherit; font-size:11px; padding:3px 8px; border-radius:999px; cursor:pointer;
}
.role-chip[aria-pressed="true"]{ background:var(--accent-wash); border-color:var(--accent); color:var(--ink); }
.type-row .role-badge{
flex:none; font-size:10px; color:var(--ink-3); background:var(--page);
border:1px solid var(--border); border-radius:5px; padding:1px 5px; white-space:nowrap;
}
.type-row.hidden-by-role{ display:none; }
.spacer{ flex:1 1 auto; }
.segmented{ display:flex; border:1px solid var(--border); border-radius:8px; overflow:hidden; }
.segmented button{
border:0; background:var(--surface); color:var(--ink-2); font:inherit; font-size:12.5px;
padding:7px 12px; cursor:pointer;
}
.segmented button + button{ border-left:1px solid var(--border); }
.segmented button[aria-pressed="true"]{ background:var(--accent); color:#fff; }
.presets{ display:flex; gap:6px; }
.presets button{
border:1px solid var(--border); background:var(--surface); color:var(--ink-2);
font:inherit; font-size:12.5px; padding:6px 10px; border-radius:7px; cursor:pointer;
}
.presets button:hover{ border-color:var(--accent); color:var(--ink); }
.presets button[aria-pressed="true"]{ background:var(--accent-wash); border-color:var(--accent); color:var(--ink); }
label.check{ display:flex; align-items:center; gap:6px; font-size:12.5px; color:var(--ink-2); cursor:pointer; }
/* ---- chart ---- */
#chartView{ display:flex; flex-direction:column; gap:4px; }
.chart-layout{ display:flex; align-items:flex-start; gap:14px; }
.chart-main{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:4px; }
.overview-card, .detail-card{
background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:12px;
}
.resumo-panel{
flex:none; width:320px; position:sticky; top:12px;
background:var(--surface); border:1px solid var(--border); border-radius:12px;
padding:16px 16px 14px; position:relative;
}
.resumo-close{
position:absolute; top:10px; right:10px; border:0; background:transparent; color:var(--ink-3);
font-size:14px; line-height:1; cursor:pointer; padding:4px; border-radius:6px;
}
.resumo-close:hover{ color:var(--ink); background:var(--accent-wash); }
.resumo-head{ display:flex; align-items:center; gap:7px; margin:0 24px 6px 0; }
.resumo-dot{ width:9px; height:9px; border-radius:50%; flex:none; }
.resumo-date{ font-family:"IBM Plex Mono",monospace; font-size:13px; color:var(--ink); font-weight:500; }
.resumo-date-note{ font-size:10.5px; color:var(--ink-3); font-style:italic; margin:-4px 0 6px; }
.resumo-lane{ font-size:12px; color:var(--ink-3); }
.resumo-id{ font-size:11px; color:var(--ink-3); margin-bottom:8px; }
.resumo-type{
display:inline-block; font-size:11px; color:var(--ink-2); background:var(--accent-wash);
border-radius:999px; padding:2px 9px; margin-bottom:10px;
}
.resumo-text{ font-size:13.5px; line-height:1.6; color:var(--ink); margin:0 0 14px; }
.resumo-block{ margin-top:12px; padding-top:12px; border-top:1px solid var(--grid); }
.resumo-label{ font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--ink-3); margin-bottom:6px; }
.resumo-people{ display:flex; flex-wrap:wrap; gap:5px; }
.resumo-people span{
font-size:12px; color:var(--ink-2); background:var(--page); border:1px solid var(--border);
border-radius:999px; padding:2px 9px;
}
.resumo-file{ font-size:10.5px; color:var(--ink-3); line-height:1.5; overflow-wrap:anywhere; }
.resumo-processo-btn{
width:100%; text-align:left; font:inherit; font-size:12.5px; font-weight:600; color:var(--accent);
background:var(--accent-wash); border:1px solid var(--border); border-radius:8px; padding:8px 10px;
cursor:pointer;
}
.resumo-processo-btn:hover{ border-color:var(--accent); }
.resumo-processo-content{
margin-top:10px; max-height:46vh; overflow-y:auto; overflow-wrap:anywhere; font-size:12px;
line-height:1.55; color:var(--ink-2);
}
.resumo-processo-content h1, .resumo-processo-content h2, .resumo-processo-content h3{
font-family:"Source Serif 4",serif; color:var(--ink); margin:12px 0 4px; line-height:1.3;
}
.resumo-processo-content h1{ font-size:14.5px; margin-top:0; }
.resumo-processo-content h2{ font-size:13px; }
.resumo-processo-content h3{ font-size:12.5px; }
.resumo-processo-content p{ margin:0 0 8px; }
.resumo-processo-content ul{ margin:0 0 8px; padding-left:18px; }
.resumo-processo-content li{ margin-bottom:3px; }
.resumo-processo-content strong{ color:var(--ink); }
.resumo-processo-content code{
font-family:"IBM Plex Mono",monospace; font-size:11px; background:var(--page); padding:1px 4px; border-radius:4px;
}
.resumo-processo-content a{ color:var(--accent); }
.resumo-processo-content hr{ border:0; border-top:1px solid var(--grid); margin:10px 0; }
.resumo-processo-content .resumo-processo-empty{ color:var(--ink-3); font-style:italic; margin:0; }
.overview-card{ position:relative; }
.overview-title{ font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--ink-3); margin-bottom:6px; }
.overview-wrap{ position:relative; }
#overviewCanvas{ display:block; width:100%; cursor:crosshair; touch-action:none; }
.brush{
position:absolute; top:0; bottom:0; background:var(--accent-wash);
border-left:2px solid var(--accent); border-right:2px solid var(--accent);
cursor:grab;
}
.brush::before, .brush::after{
content:""; position:absolute; top:0; bottom:0; width:8px; cursor:ew-resize;
}
.brush::before{ left:-4px; }
.brush::after{ right:-4px; }
.detail-card{ overflow-x:auto; }
.detail-row{ display:flex; min-width:600px; }
.lane-sidebar{ flex:none; width:132px; padding-top:0; }
.lane-row{ display:flex; align-items:center; gap:7px; box-sizing:border-box; border-bottom:1px solid var(--grid); }
.lane-row .dot{ width:9px; height:9px; border-radius:50%; flex:none; }
.lane-row .lbl{ font-size:12px; line-height:1.25; color:var(--ink-2); }
.lane-row .lbl b{ display:block; color:var(--ink); font-weight:600; }
.detail-canvas-wrap{ position:relative; flex:1 1 auto; min-width:0; }
#detailCanvas{ display:block; width:100%; cursor:grab; touch-action:none; }
#detailCanvas.dragging{ cursor:grabbing; }
.hover-ring{
position:absolute; pointer-events:none; border-radius:50%;
border:1.5px solid var(--ring); background:transparent;
transform:translate(-50%,-50%); display:none;
}
.selection-ring{ border:2px solid var(--accent); box-shadow:0 0 0 3px var(--accent-wash); }
#tooltip{
position:absolute; pointer-events:none; max-width:320px; z-index:5;
background:var(--surface); border:1px solid var(--border); border-radius:9px;
box-shadow:0 6px 20px rgba(0,0,0,.16); padding:10px 12px; font-size:12.5px;
color:var(--ink-2); line-height:1.5; display:none;
}
#tooltip .row1{ display:flex; align-items:center; gap:7px; margin-bottom:4px; }
#tooltip .row1 .dot{ width:8px; height:8px; border-radius:50%; flex:none; }
#tooltip .date{ font-family:"IBM Plex Mono",monospace; font-size:12px; color:var(--ink); font-weight:500; }
#tooltip .lane{ font-size:11px; color:var(--ink-3); }
#tooltip .id{ font-family:"IBM Plex Mono",monospace; font-size:11px; color:var(--ink-3); margin-bottom:3px; }
#tooltip .text{ color:var(--ink); }
.count-line{ font-size:12px; color:var(--ink-3); margin:8px 2px 0; }
/* ---- table ---- */
#tableView{ background:var(--surface); border:1px solid var(--border); border-radius:12px; overflow:hidden; }
.table-scroll{ overflow-x:auto; }
table{ width:100%; border-collapse:collapse; font-size:13px; min-width:760px; }
thead th{
text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:.05em; color:var(--ink-3);
font-weight:600; padding:10px 12px; border-bottom:1px solid var(--grid); background:var(--surface);
position:sticky; top:0; cursor:pointer; white-space:nowrap;
}
thead th:hover{ color:var(--ink); }
thead th .arrow{ opacity:.5; margin-left:3px; }
tbody td{ padding:9px 12px; border-bottom:1px solid var(--grid); vertical-align:top; color:var(--ink-2); }
tbody tr:hover td{ background:var(--accent-wash); }
td.date{ font-family:"IBM Plex Mono",monospace; font-size:12px; white-space:nowrap; color:var(--ink); }
td.lane{ white-space:nowrap; }
td.lane .dot{ width:8px; height:8px; border-radius:50%; display:inline-block; margin-right:6px; }
td.idcol{ font-family:"IBM Plex Mono",monospace; font-size:11.5px; color:var(--ink-3); white-space:nowrap; }
td.type{ white-space:nowrap; color:var(--ink-2); }
td.people{ color:var(--ink-2); max-width:220px; }
td.text{ color:var(--ink); max-width:520px; }
td.file{ font-family:"IBM Plex Mono",monospace; font-size:10.5px; color:var(--ink-3); max-width:220px; overflow-wrap:anywhere; }
.table-foot{ display:flex; align-items:center; justify-content:center; gap:12px; padding:14px; }
.table-foot button{
border:1px solid var(--border); background:var(--page); color:var(--ink);
font:inherit; font-size:13px; padding:8px 16px; border-radius:8px; cursor:pointer;
}
.table-foot button:hover{ border-color:var(--accent); }
footer{ margin-top:28px; font-size:11.5px; color:var(--ink-3); border-top:1px solid var(--grid); padding-top:12px; }
footer p{ margin:0 0 8px; line-height:1.5; }
footer p:last-child{ margin-bottom:0; }
@media (max-width: 900px){
/* align-items:flex-start on .chart-layout is for the desktop row layout
(each column sized by its own content, not stretched to match the
other's height). Stacked as a column here, "start" instead governs
the cross axis (width) — sizing chart-main to its widest descendant's
shrink-to-fit width (the 600px-min-width detail-row) instead of the
viewport, which pushed the whole page into horizontal scroll. */
.chart-layout{ flex-direction:column; align-items:stretch; }
.resumo-panel{ width:100%; position:static; }
}
@media (max-width: 640px){
.lane-sidebar{ width:92px; }
.lane-row .lbl b{ font-size:11.5px; }
.controls{ flex-direction:column; align-items:stretch; }
.search{ flex:none; max-width:none; }
.dropdown{ width:100%; }
.dropdown-btn{ width:100%; justify-content:space-between; }
.dropdown-panel, .dropdown-panel-wide{ position:static; width:100%; margin-top:6px; box-shadow:none; }
.legend{ width:100%; }
.spacer{ display:none; }
.presets{ width:100%; }
.presets button{ flex:1 1 auto; }
.segmented{ width:100%; }
.segmented button{ flex:1; }
.stat{ min-width:0; flex-basis:calc(50% - 5px); }
.stat .v.compact{ font-size:12.5px; }
}
</style>
</head>
<body>
<header>
<h1>Análise da Petição Nº 16704</h1>
<p class="lede">
Linha do tempo interativa da <span class="mono">Pet16704</span> (STF) — um ponto por documento, agrupado
pela instituição de origem e posicionado pela data real de juntada — cruzada com o resumo de cada um dos
<strong>53 processos conexos</strong> (o inquérito-mãe <span class="mono">Inq 5026</span> — Operação
Compliance Zero — e os demais inquéritos e petições apartadas, reunidos em <span class="mono">parte1/</span>
e <span class="mono">parte2/</span>). Clique em qualquer ponto da linha do tempo para abrir o resumo do
documento e, dali, o resumo completo do processo a que ele pertence.
</p>
<div class="stats" id="stats"></div>
</header>
<div class="controls">
<div class="search">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input id="search" type="text" placeholder="Buscar por texto ou nº do documento…" autocomplete="off">
</div>
<div class="dropdown" id="typeDropdown">
<button type="button" id="typeBtn" class="dropdown-btn" aria-haspopup="true" aria-expanded="false">
Tipo de documento <span class="arrow"></span>
</button>
<div class="dropdown-panel" id="typePanel" hidden>
<div class="dropdown-search">
<input id="typeSearch" type="text" placeholder="Filtrar tipos…" autocomplete="off">
</div>
<div class="dropdown-actions">
<button type="button" id="typeAll">Selecionar todos</button>
<button type="button" id="typeNone">Limpar</button>
</div>
<div class="dropdown-list" id="typeList"></div>
</div>
</div>
<div class="dropdown" id="peopleDropdown">
<button type="button" id="peopleBtn" class="dropdown-btn" aria-haspopup="true" aria-expanded="false">
Pessoas mencionadas <span class="arrow"></span>
</button>
<div class="dropdown-panel dropdown-panel-wide" id="peoplePanel" hidden>
<div class="dropdown-hint">1. Papel (filtra a lista abaixo) · 2. Marque as pessoas</div>
<div class="role-chips" id="roleChips"></div>
<div class="dropdown-search">
<input id="peopleSearch" type="text" placeholder="Filtrar por nome…" autocomplete="off">
</div>
<div class="dropdown-actions">
<button type="button" id="peopleAllVisible">Marcar visíveis</button>
<button type="button" id="peopleNone">Limpar tudo</button>
</div>
<div class="dropdown-list" id="peopleList"></div>
</div>
</div>
<div class="legend" id="legend"></div>
<div class="spacer"></div>
<div class="presets" id="presets"></div>
<div class="segmented" role="group" aria-label="Modo de visualização">
<button id="viewChartBtn" aria-pressed="true">Linha do tempo</button>
<button id="viewTableBtn" aria-pressed="false">Tabela</button>
</div>
</div>
<section id="chartView">
<div class="chart-layout">
<div class="chart-main">
<div class="overview-card">
<div class="overview-title">Visão geral · 20182026 · arraste para navegar, arraste as bordas para ajustar o período</div>
<div class="overview-wrap">
<canvas id="overviewCanvas" height="72"></canvas>
<div class="brush" id="brush"></div>
</div>
</div>
<div class="detail-card">
<div class="detail-row">
<div class="lane-sidebar" id="laneSidebar"></div>
<div class="detail-canvas-wrap">
<canvas id="detailCanvas"></canvas>
<div class="hover-ring" id="hoverRing"></div>
<div class="hover-ring selection-ring" id="selectionRing"></div>
<div id="tooltip"></div>
</div>
</div>
</div>
<p class="count-line" id="chartCount"></p>
</div>
<aside class="resumo-panel" id="resumoPanel" hidden>
<button type="button" class="resumo-close" id="resumoClose" aria-label="Fechar resumo"></button>
<div class="resumo-head">
<span class="resumo-dot" id="resumoDot"></span>
<span class="resumo-date" id="resumoDate"></span>
<span class="resumo-lane" id="resumoLane"></span>
</div>
<div class="resumo-date-note" id="resumoDateNote" hidden>data lida no texto do documento, não da juntada nos autos</div>
<div class="resumo-id mono" id="resumoId"></div>
<div class="resumo-type" id="resumoType"></div>
<p class="resumo-text" id="resumoText"></p>
<div class="resumo-block" id="resumoPeopleBlock" hidden>
<div class="resumo-label">Pessoas mencionadas</div>
<div class="resumo-people" id="resumoPeople"></div>
</div>
<div class="resumo-block">
<div class="resumo-label">Arquivo-fonte</div>
<div class="resumo-file mono" id="resumoFile"></div>
</div>
<div class="resumo-block">
<button type="button" class="resumo-processo-btn" id="resumoProcessoBtn" aria-expanded="false">
Ver resumo do processo <span class="mono" id="resumoProcessoNum"></span>
</button>
<div class="resumo-processo-content" id="resumoProcessoContent" hidden></div>
</div>
</aside>
</div>
</section>
<section id="tableView" hidden>
<div class="table-scroll">
<table>
<thead>
<tr>
<th data-sort="date">Data <span class="arrow"></span></th>
<th data-sort="lane">Instituição <span class="arrow"></span></th>
<th data-sort="type">Tipo <span class="arrow"></span></th>
<th>ID</th>
<th>Descrição</th>
<th>Pessoas</th>
<th>Arquivo-fonte</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
<div class="table-foot">
<span class="mono" id="tableCount"></span>
<button id="loadMore">Carregar mais</button>
</div>
</section>
<footer>
<p>
Linha do tempo: <span class="mono">TIMELINE.md</span> e <span class="mono">gitgraph.md</span> (atribuição de
instituição por documento). Da leva original de 615 documentos sem data de juntada identificada, 366 tiveram
uma data recuperada a partir do próprio texto do documento (data de assinatura/expedição, ex. "Brasília, 10 de
abril de 2026") — marcados no resumo do documento como "data lida no texto", já que não é necessariamente a
data de juntada nos autos. Os 249 restantes ficam de fora do gráfico e aparecem só na tabela. As 292 pessoas do
filtro "Pessoas mencionadas" (lista completa em <span class="mono">PESSOAS.md</span>) foram extraídas
automaticamente com um modelo de NER treinado em texto jurídico brasileiro (LeNER-Br); nomes e papéis
processuais são inferidos e podem conter imprecisões — a maioria (185) fica sem papel classificado
("Mencionado(a)").
</p>
<p>
Resumo dos processos: ao abrir o resumo de um documento, o botão "Ver resumo do processo" busca o arquivo
correspondente em <span class="mono">parte1/</span> ou <span class="mono">parte2/</span> — 53 processos
vinculados à Pet16704, lidos e resumidos a partir dos autos originais.
</p>
<p>
Este site reúne informações de processos públicos em curso no STF. Nomes, datas e resumos refletem o
conteúdo dos autos tal como disponibilizado; erros de leitura automática (OCR, NER, digitação no material de
origem) podem existir. Nada aqui constitui manifestação jurídica sobre culpa, inocência ou qualquer
qualificação processual das pessoas citadas.
</p>
</footer>
<script>
(function(){
"use strict";
var LANES = [
{ key:"STF · Relatoria", label:"STF", full:"STF · Relatoria", color:"var(--s1)" },
{ key:"PGR · Procuradoria-Geral da República", label:"PGR", full:"PGR · Procuradoria-Geral da República", color:"var(--s2)" },
{ key:"Polícia Federal · CINQ e diligências", label:"Polícia Federal", full:"Polícia Federal · CINQ e diligências", color:"var(--s3)" },
{ key:"Partes e Defesas", label:"Partes e Defesas", full:"Partes e Defesas", color:"var(--s4)" },
{ key:"Secretaria e Cartório", label:"Secretaria e Cartório", full:"Secretaria e Cartório", color:"var(--s5)" }
];
var ROW_H = 64;
var AXIS_H = 28;
var laneIndex = {};
LANES.forEach(function(l,i){ laneIndex[l.key]=i; });
function cssVar(name){
return getComputedStyle(document.documentElement).getPropertyValue(name.replace("var(","").replace(")","")).trim();
}
LANES.forEach(function(l){ l._hex = null; });
var state = {
points: [], // dated points {id,lane,ms,text,file}
undated: [],
activeLanes: new Set(LANES.map(function(l){return l.key;})),
search: "",
view: "chart",
domainMin: 0, domainMax: 0,
t0: 0, t1: 0,
dragging: null,
hover: null,
selected: null,
sortKey: "date", sortDir: "asc",
pageSize: 200,
onlyUndated: false
};
function hashJitter(id){
var h = 0;
for (var i=0;i<id.length;i++){ h = (h*31 + id.charCodeAt(i)) | 0; }
// h overflows into negative 32-bit ints for most ids, and JS's % keeps
// the dividend's sign — left unnormalized this sent frac (and the final
// jitter) outside [-1,1), pushing some points' y clear into a
// neighboring lane's row, breaking both their draw position and the
// hover hit-test (which infers the row from the mouse y).
var m = ((h % 10007) + 10007) % 10007;
var frac = m / 10007;
return (frac*2-1) * (ROW_H/2 - 12);
}
function fmtDate(ms){
var d = new Date(ms);
var dd = String(d.getUTCDate()).padStart(2,"0");
var mm = String(d.getUTCMonth()+1).padStart(2,"0");
return dd+"/"+mm+"/"+d.getUTCFullYear();
}
function monthLabel(ms){
var d = new Date(ms);
var names = ["jan","fev","mar","abr","mai","jun","jul","ago","set","out","nov","dez"];
return names[d.getUTCMonth()]+"/"+String(d.getUTCFullYear()).slice(2);
}
Promise.all([
fetch("docs.json").then(function(r){ return r.json(); }),
fetch("people.json").then(function(r){ return r.json(); })
]).then(function(results){
var rows = results[0];
state.PEOPLE = results[1]; // [{key, count, role}], sorted by count desc
state.activePeople = new Set(state.PEOPLE.map(function(p){return p.key;}));
state.activeRoleFilter = new Set(state.PEOPLE.map(function(p){return p.role;}));
var minMs = Infinity, maxMs = -Infinity;
rows.forEach(function(r){
var ms = r.date ? Date.parse(r.date+"T00:00:00Z") : null;
var rec = { id:r.id, lane:r.branch, ms:ms, text:r.text, file:r.file, type:r.type, people:r.people||[], order:r.order, dateInferred:!!r.dateInferred };
if (ms==null){ state.undated.push(rec); }
else {
rec.jitter = hashJitter(r.id);
state.points.push(rec);
if (ms<minMs) minMs=ms;
if (ms>maxMs) maxMs=ms;
}
});
state.domainMin = minMs;
state.domainMax = maxMs;
var pad = (maxMs-minMs)*0.015;
state.fullMin = minMs - pad;
state.fullMax = maxMs + pad;
state.t0 = Math.max(state.fullMin, Date.parse("2026-01-01T00:00:00Z"));
state.t1 = state.fullMax;
state.byLane = {};
LANES.forEach(function(l){ state.byLane[l.key] = state.points.filter(function(p){return p.lane===l.key;}).sort(function(a,b){return a.ms-b.ms;}); });
var typeCounts = {};
state.points.concat(state.undated).forEach(function(r){
typeCounts[r.type] = (typeCounts[r.type]||0)+1;
});
state.TYPES = Object.keys(typeCounts)
.map(function(k){ return { key:k, count:typeCounts[k] }; })
.sort(function(a,b){ return b.count-a.count; });
state.activeTypes = new Set(state.TYPES.map(function(t){return t.key;}));
init();
});
function init(){
LANES.forEach(function(l){ l._hex = cssVar(l.color); });
renderStats();
renderLegend();
renderTypeDropdown();
renderPeopleDropdown();
renderPresets();
buildLaneSidebar();
bindControls();
bindChartInteraction();
resizeAll();
renderTable(true);
window.addEventListener("resize", debounce(resizeAll, 120));
var onThemeChange = function(){
LANES.forEach(function(l){ l._hex = cssVar(l.color); });
drawOverview(); drawDetail();
};
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", onThemeChange);
new MutationObserver(onThemeChange).observe(document.documentElement, { attributes:true, attributeFilter:["data-theme"] });
}
function debounce(fn,ms){ var t; return function(){ clearTimeout(t); var a=arguments; t=setTimeout(function(){fn.apply(null,a);},ms); }; }
function renderStats(){
var total = state.points.length + state.undated.length;
var pct = Math.round(state.undated.length/total*100);
var el = document.getElementById("stats");
el.innerHTML = "";
var items = [
{ k:"Documentos", v: total.toLocaleString("pt-BR") },
{ k:"Período", v: fmtDate(state.domainMin)+""+fmtDate(state.domainMax) },
{ k:"Instituições", v: LANES.length },
{ k:"Sem data", v: state.undated.length.toLocaleString("pt-BR")+" ("+pct+"%)", onClick:function(){
state.onlyUndated = true;
setView("table");
renderTable(true);
} },
{ k:"Pessoas identificadas", v: state.PEOPLE.length.toLocaleString("pt-BR"), onClick:function(){
document.getElementById("peopleBtn").click();
} }
];
items.forEach(function(it){
var d = document.createElement("div");
d.className = "stat" + (it.onClick?" link":"");
var vClass = "v tabnum" + (String(it.v).length>10 ? " compact" : "");
d.innerHTML = '<div class="k">'+it.k+'</div><div class="'+vClass+'">'+it.v+'</div>';
if (it.onClick){
d.tabIndex = 0;
d.addEventListener("click", it.onClick);
d.addEventListener("keydown", function(e){ if(e.key==="Enter") d.click(); });
}
el.appendChild(d);
});
}
function laneCount(key){
return state.points.filter(function(p){return p.lane===key;}).length + state.undated.filter(function(p){return p.lane===key;}).length;
}
function renderLegend(){
var el = document.getElementById("legend");
el.innerHTML = "";
LANES.forEach(function(l){
var chip = document.createElement("button");
chip.className = "chip";
chip.type = "button";
chip.setAttribute("aria-pressed","true");
chip.innerHTML = '<span class="dot" style="background:'+l.color+'"></span>'+l.label+' <span class="n tabnum">'+laneCount(l.key).toLocaleString("pt-BR")+'</span>';
chip.addEventListener("click", function(){
if (state.activeLanes.has(l.key)){ state.activeLanes.delete(l.key); chip.dataset.off="true"; chip.setAttribute("aria-pressed","false"); }
else { state.activeLanes.add(l.key); delete chip.dataset.off; chip.setAttribute("aria-pressed","true"); }
drawDetail(); drawOverview(); renderTable(true);
});
el.appendChild(chip);
});
}
function updateTypeBtnLabel(){
var btn = document.getElementById("typeBtn");
var total = state.TYPES.length;
var active = state.activeTypes.size;
btn.innerHTML = "";
btn.appendChild(document.createTextNode(active===total ? "Tipo de documento" : "Tipo de documento "));
if (active!==total){
var tag = document.createElement("span");
tag.className = "tag";
tag.textContent = active + "/" + total;
btn.appendChild(tag);
}
var arrow = document.createElement("span");
arrow.className = "arrow"; arrow.textContent = "▾";
btn.appendChild(arrow);
}
function renderTypeDropdown(){
var btn = document.getElementById("typeBtn");
var panel = document.getElementById("typePanel");
var list = document.getElementById("typeList");
updateTypeBtnLabel();
list.innerHTML = "";
state.TYPES.forEach(function(t){
var row = document.createElement("label");
row.className = "type-row";
row.dataset.key = t.key.toLowerCase();
var cb = document.createElement("input");
cb.type = "checkbox"; cb.checked = true;
cb.addEventListener("change", function(){
if (cb.checked) state.activeTypes.add(t.key);
else state.activeTypes.delete(t.key);
updateTypeBtnLabel();
drawDetail(); drawOverview(); renderTable(true);
});
var lbl = document.createElement("span"); lbl.className = "lbl"; lbl.textContent = t.key;
var n = document.createElement("span"); n.className = "n tabnum"; n.textContent = t.count.toLocaleString("pt-BR");
row.appendChild(cb); row.appendChild(lbl); row.appendChild(n);
list.appendChild(row);
});
function setAll(on){
list.querySelectorAll("input[type=checkbox]").forEach(function(cb){ cb.checked = on; });
state.activeTypes = on ? new Set(state.TYPES.map(function(t){return t.key;})) : new Set();
updateTypeBtnLabel();
drawDetail(); drawOverview(); renderTable(true);
}
document.getElementById("typeAll").addEventListener("click", function(){ setAll(true); });
document.getElementById("typeNone").addEventListener("click", function(){ setAll(false); });
document.getElementById("typeSearch").addEventListener("input", function(e){
var q = e.target.value.trim().toLowerCase();
list.querySelectorAll(".type-row").forEach(function(row){
row.classList.toggle("hidden-by-search", q && row.dataset.key.indexOf(q)<0);
});
});
btn.addEventListener("click", function(){
var open = panel.hidden;
panel.hidden = !open;
btn.setAttribute("aria-expanded", String(open));
});
document.addEventListener("click", function(e){
if (!panel.hidden && !document.getElementById("typeDropdown").contains(e.target)){
panel.hidden = true;
btn.setAttribute("aria-expanded","false");
}
});
document.addEventListener("keydown", function(e){
if (e.key==="Escape" && !panel.hidden){ panel.hidden=true; btn.setAttribute("aria-expanded","false"); }
});
}
function updatePeopleBtnLabel(){
var btn = document.getElementById("peopleBtn");
var total = state.PEOPLE.length;
var active = state.activePeople.size;
btn.innerHTML = "";
btn.appendChild(document.createTextNode("Pessoas mencionadas "));
if (active!==total){
var tag = document.createElement("span");
tag.className = "tag";
tag.textContent = active + "/" + total;
btn.appendChild(tag);
}
var arrow = document.createElement("span");
arrow.className = "arrow"; arrow.textContent = "▾";
btn.appendChild(arrow);
}
function renderPeopleDropdown(){
var btn = document.getElementById("peopleBtn");
var panel = document.getElementById("peoplePanel");
var list = document.getElementById("peopleList");
var roleChips = document.getElementById("roleChips");
updatePeopleBtnLabel();
var roleCounts = {};
state.PEOPLE.forEach(function(p){ roleCounts[p.role] = (roleCounts[p.role]||0)+1; });
var roles = Object.keys(roleCounts).sort(function(a,b){ return roleCounts[b]-roleCounts[a]; });
roleChips.innerHTML = "";
roles.forEach(function(role){
var chip = document.createElement("button");
chip.type = "button"; chip.className = "role-chip";
chip.setAttribute("aria-pressed","true");
chip.textContent = role + " (" + roleCounts[role] + ")";
chip.addEventListener("click", function(){
var on = chip.getAttribute("aria-pressed")==="true";
if (on){ state.activeRoleFilter.delete(role); chip.setAttribute("aria-pressed","false"); }
else { state.activeRoleFilter.add(role); chip.setAttribute("aria-pressed","true"); }
applyRoleNarrowing();
});
roleChips.appendChild(chip);
});
list.innerHTML = "";
state.PEOPLE.forEach(function(p){
var row = document.createElement("label");
row.className = "type-row";
row.dataset.key = p.key.toLowerCase();
row.dataset.origKey = p.key;
row.dataset.role = p.role;
var cb = document.createElement("input");
cb.type = "checkbox"; cb.checked = true;
cb.addEventListener("change", function(){
if (cb.checked) state.activePeople.add(p.key);
else state.activePeople.delete(p.key);
updatePeopleBtnLabel();
drawDetail(); drawOverview(); renderTable(true);
});
var lbl = document.createElement("span"); lbl.className = "lbl"; lbl.textContent = p.key;
var badge = document.createElement("span"); badge.className = "role-badge"; badge.textContent = p.role;
var n = document.createElement("span"); n.className = "n tabnum"; n.textContent = p.count.toLocaleString("pt-BR");
row.appendChild(cb); row.appendChild(lbl); row.appendChild(badge); row.appendChild(n);
list.appendChild(row);
});
function applyRoleNarrowing(){
list.querySelectorAll(".type-row").forEach(function(row){
row.classList.toggle("hidden-by-role", !state.activeRoleFilter.has(row.dataset.role));
});
}
function visibleRows(){
return Array.prototype.filter.call(list.querySelectorAll(".type-row"), function(row){
return !row.classList.contains("hidden-by-role") && !row.classList.contains("hidden-by-search");
});
}
document.getElementById("peopleAllVisible").addEventListener("click", function(){
visibleRows().forEach(function(row){
row.querySelector("input").checked = true;
state.activePeople.add(row.dataset.origKey);
});
updatePeopleBtnLabel();
drawDetail(); drawOverview(); renderTable(true);
});
document.getElementById("peopleNone").addEventListener("click", function(){
state.activePeople.clear();
list.querySelectorAll("input[type=checkbox]").forEach(function(cb){ cb.checked = false; });
updatePeopleBtnLabel();
drawDetail(); drawOverview(); renderTable(true);
});
document.getElementById("peopleSearch").addEventListener("input", function(e){
var q = e.target.value.trim().toLowerCase();
list.querySelectorAll(".type-row").forEach(function(row){
row.classList.toggle("hidden-by-search", q && row.dataset.key.indexOf(q)<0);
});
});
btn.addEventListener("click", function(){
var open = panel.hidden;
panel.hidden = !open;
btn.setAttribute("aria-expanded", String(open));
});
document.addEventListener("click", function(e){
if (!panel.hidden && !document.getElementById("peopleDropdown").contains(e.target)){
panel.hidden = true;
btn.setAttribute("aria-expanded","false");
}
});
document.addEventListener("keydown", function(e){
if (e.key==="Escape" && !panel.hidden){ panel.hidden=true; btn.setAttribute("aria-expanded","false"); }
});
}
function renderPresets(){
var el = document.getElementById("presets");
var presets = [
{ label:"Tudo", fn:function(){ return [state.fullMin, state.fullMax]; } },
{ label:"Pré-2026", fn:function(){ return [state.fullMin, Date.parse("2026-01-01T00:00:00Z")]; } },
{ label:"2026", fn:function(){ return [Date.parse("2026-01-01T00:00:00Z"), state.fullMax]; } },
{ label:"Últimos 90 dias", fn:function(){ return [state.domainMax-90*86400000, state.fullMax]; } }
];
presets.forEach(function(p){
var b = document.createElement("button");
b.type = "button"; b.textContent = p.label;
if (p.label==="2026") b.setAttribute("aria-pressed","true");
b.addEventListener("click", function(){
var r = p.fn(); state.t0=r[0]; state.t1=r[1];
Array.prototype.forEach.call(el.children, function(c){ c.removeAttribute("aria-pressed"); });
b.setAttribute("aria-pressed","true");
drawDetail(); positionBrush();
});
el.appendChild(b);
});
}
function buildLaneSidebar(){
var el = document.getElementById("laneSidebar");
el.innerHTML = "";
LANES.forEach(function(l){
var row = document.createElement("div");
row.className = "lane-row";
row.style.height = ROW_H+"px";
row.innerHTML = '<span class="dot" style="background:'+l.color+'"></span><span class="lbl"><b>'+l.label+'</b>'+laneCount(l.key)+' docs</span>';
el.appendChild(row);
});
}
function bindControls(){
document.getElementById("search").addEventListener("input", function(e){
state.search = e.target.value.trim().toLowerCase();
drawDetail(); renderTable(true);
});
document.getElementById("viewChartBtn").addEventListener("click", function(){ setView("chart"); });
document.getElementById("viewTableBtn").addEventListener("click", function(){ setView("table"); });
}
function setView(v){
state.view = v;
document.getElementById("chartView").hidden = v!=="chart";
document.getElementById("tableView").hidden = v!=="table";
document.getElementById("viewChartBtn").setAttribute("aria-pressed", v==="chart");
document.getElementById("viewTableBtn").setAttribute("aria-pressed", v==="table");
if (v==="chart"){ resizeAll(); }
}
function matchesSearch(rec){
if (!state.search) return true;
return rec.id.toLowerCase().indexOf(state.search)>=0 || rec.text.toLowerCase().indexOf(state.search)>=0;
}
function peopleOk(rec){
if (state.activePeople.size === state.PEOPLE.length) return true;
var people = rec.people || [];
for (var i=0;i<people.length;i++){ if (state.activePeople.has(people[i])) return true; }
return false;
}
/* ---------------- overview + detail canvases ---------------- */
var ovCanvas = document.getElementById("overviewCanvas");
var ovCtx;
var dtCanvas = document.getElementById("detailCanvas");
var dtCtx;
var ovWidth=0, dtWidth=0;
function resizeAll(){
var ovWrap = document.querySelector(".overview-wrap");
ovWidth = ovWrap.clientWidth;
ovCanvas.width = ovWidth * devicePixelRatio;
ovCanvas.height = 72 * devicePixelRatio;
ovCanvas.style.width = ovWidth+"px";
ovCanvas.style.height = "72px";
ovCtx = ovCanvas.getContext("2d");
ovCtx.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0);
var dtWrap = document.querySelector(".detail-canvas-wrap");
dtWidth = Math.max(dtWrap.clientWidth, 460);
var dtHeight = LANES.length*ROW_H + AXIS_H;
dtCanvas.width = dtWidth * devicePixelRatio;
dtCanvas.height = dtHeight * devicePixelRatio;
dtCanvas.style.width = dtWidth+"px";
dtCanvas.style.height = dtHeight+"px";
dtCtx = dtCanvas.getContext("2d");
dtCtx.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0);
if (state.points.length){ drawOverview(); drawDetail(); positionBrush(); }
}
function scaleFull(ms){ return (ms-state.fullMin)/(state.fullMax-state.fullMin) * ovWidth; }
function scaleDetail(ms){ return (ms-state.t0)/(state.t1-state.t0) * dtWidth; }
function invDetail(px){ return state.t0 + (px/dtWidth)*(state.t1-state.t0); }
function monthBuckets(){
var buckets = [];
var d = new Date(Date.UTC(new Date(state.domainMin).getUTCFullYear(), new Date(state.domainMin).getUTCMonth(), 1));
var end = state.domainMax;
while (d.getTime() <= end){
var next = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth()+1, 1));
buckets.push({ start:d.getTime(), end:next.getTime(), counts:new Array(LANES.length).fill(0) });
d = next;
}
return buckets;
}
function getBuckets(){
var buckets = monthBuckets();
var start = new Date(buckets[0].start);
var startIdx = start.getUTCFullYear()*12 + start.getUTCMonth();
state.points.forEach(function(p){
if (!state.activeTypes.has(p.type)) return;
if (!peopleOk(p)) return;
var d = new Date(p.ms);
var idx = (d.getUTCFullYear()*12 + d.getUTCMonth()) - startIdx;
if (idx>=0 && idx<buckets.length) buckets[idx].counts[laneIndex[p.lane]]++;
});
return buckets;
}
function drawOverview(){
var ctx = ovCtx;
ctx.clearRect(0,0,ovWidth,72);
var buckets = getBuckets();
var maxTotal = 1;
buckets.forEach(function(b){
var t = b.counts.reduce(function(a,c,i){ return a + (state.activeLanes.has(LANES[i].key)?c:0); },0);
if (t>maxTotal) maxTotal=t;
});
var H = 60, base = 66;
buckets.forEach(function(b){
var x0 = scaleFull(b.start), x1 = scaleFull(b.end);
var w = Math.max(1, x1-x0-1);
var y = base;
for (var i=0;i<LANES.length;i++){
if (!state.activeLanes.has(LANES[i].key)) continue;
var c = b.counts[i];
if (!c) continue;
var h = (c/maxTotal)*H;
ctx.fillStyle = LANES[i]._hex;
ctx.globalAlpha = 0.85;
ctx.fillRect(x0, y-h, w, h);
y -= h;
}
});
ctx.globalAlpha = 1;
ctx.strokeStyle = cssVar("--baseline");
ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(0,base+0.5); ctx.lineTo(ovWidth,base+0.5); ctx.stroke();
// year ticks
ctx.fillStyle = cssVar("--ink-3");
ctx.font = "10.5px 'IBM Plex Mono', monospace";
var y0 = new Date(state.domainMin).getUTCFullYear();
var y1 = new Date(state.domainMax).getUTCFullYear();
for (var yr=y0; yr<=y1; yr++){
var x = scaleFull(Date.UTC(yr,0,1));
if (x<0||x>ovWidth) continue;
ctx.fillText(String(yr), x+3, 10);
ctx.strokeStyle = cssVar("--grid");
ctx.beginPath(); ctx.moveTo(x+0.5,0); ctx.lineTo(x+0.5,base); ctx.stroke();
}
}
function niceTicks(span){
var day=86400000;
var steps = [
[day,"d"], [7*day,"w"], [30*day,"m"], [91*day,"q"], [365*day,"y"], [365*2*day,"y2"], [365*4*day,"y4"]
];
for (var i=0;i<steps.length;i++){
if (span / steps[i][0] <= 10) return steps[i];
}
return steps[steps.length-1];
}
function drawDetail(){
var ctx = dtCtx;
var H = LANES.length*ROW_H;
ctx.clearRect(0,0,dtWidth,H+AXIS_H);
ctx.fillStyle = cssVar("--surface");
ctx.fillRect(0,0,dtWidth,H+AXIS_H);
var searchActive = !!state.search;
// lane separators
ctx.strokeStyle = cssVar("--grid");
ctx.lineWidth = 1;
for (var i=0;i<=LANES.length;i++){
var y = i*ROW_H+0.5;
ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(dtWidth,y); ctx.stroke();
}
// points
LANES.forEach(function(l,li){
if (!state.activeLanes.has(l.key)) return;
var arr = state.byLane[l.key];
var cy = li*ROW_H + ROW_H/2;
var hex = l._hex;
for (var i=0;i<arr.length;i++){
var p = arr[i];
if (!state.activeTypes.has(p.type)) continue;
if (!peopleOk(p)) continue;
var x = scaleDetail(p.ms);
if (x < -10 || x > dtWidth+10) continue;
var match = matchesSearch(p);
var y = cy + p.jitter;
var isMatch = !searchActive || match;
ctx.globalAlpha = isMatch ? 0.72 : 0.10;
ctx.fillStyle = hex;
ctx.beginPath();
ctx.arc(x, y, isMatch && searchActive ? 4 : 3, 0, Math.PI*2);
ctx.fill();
if (searchActive && match){
ctx.globalAlpha = 1;
ctx.strokeStyle = cssVar("--ring");
ctx.lineWidth = 1.3;
ctx.stroke();
}
}
});
ctx.globalAlpha = 1;
// axis
ctx.fillStyle = cssVar("--surface");
ctx.fillRect(0,H,dtWidth,AXIS_H);
ctx.strokeStyle = cssVar("--baseline");
ctx.beginPath(); ctx.moveTo(0,H+0.5); ctx.lineTo(dtWidth,H+0.5); ctx.stroke();
var span = state.t1-state.t0;
var step = niceTicks(span)[0];
var d = new Date(state.t0);
var startMs = Math.floor(state.t0/step)*step;
ctx.font = "11px 'IBM Plex Mono', monospace";
ctx.fillStyle = cssVar("--ink-3");
for (var t=startMs; t<=state.t1; t+=step){
var x = scaleDetail(t);
if (x<0||x>dtWidth) continue;
ctx.strokeStyle = cssVar("--grid");
ctx.beginPath(); ctx.moveTo(x+0.5,0); ctx.lineTo(x+0.5,H); ctx.stroke();
var label = step >= 350*86400000 ? String(new Date(t).getUTCFullYear()) : monthLabel(t);
if (step < 20*86400000) label = fmtDate(t).slice(0,5);
ctx.fillText(label, x+3, H+18);
}
positionSelectionRing();
}
/* ---------------- interaction ---------------- */
// shared hit-test: nearest visible point to a client (mx,my), within a
// small pixel radius. Same-day clusters run up to 50+ documents in a
// single lane, so the candidate window must be bounded by actual time
// proximity, not a fixed number of array slots either side of the
// binary-search hit.
function findPointAt(clientX, clientY){
var rect = dtCanvas.getBoundingClientRect();
var mx = clientX-rect.left, my = clientY-rect.top;
var li = Math.floor(my/ROW_H);
if (li<0||li>=LANES.length || !state.activeLanes.has(LANES[li].key)) return null;
var arr = state.byLane[LANES[li].key];
if (!arr.length) return null;
var targetMs = invDetail(mx);
var msPerPixel = Math.abs((state.t1-state.t0)/dtWidth);
var timeRadius = 18 * msPerPixel;
var lo=0, hi=arr.length-1;
while (lo<hi){ var mid=(lo+hi)>>1; if (arr[mid].ms<targetMs) lo=mid+1; else hi=mid; }
var best=null, bestD=Infinity;
function consider(p){
if (!state.activeTypes.has(p.type) || !peopleOk(p)) return;
var x = scaleDetail(p.ms);
var y = li*ROW_H+ROW_H/2+p.jitter;
var d = Math.hypot(mx-x, my-y);
if (d<bestD){ bestD=d; best=p; }
}
var k;
for (k=lo; k<arr.length && arr[k].ms<=targetMs+timeRadius; k++){ consider(arr[k]); }
for (k=lo-1; k>=0 && arr[k].ms>=targetMs-timeRadius; k--){ consider(arr[k]); }
return (best && bestD<=16) ? { p:best, li:li } : null;
}
function handleHover(e){
var hit = findPointAt(e.clientX, e.clientY);
// the résumé panel already shows this point in full, so the little
// floating preview would just be a redundant overlap right next to it
if (hit && !(state.selected && hit.p===state.selected.p)) showHover(hit.p, hit.li);
else clearHover();
}
function showHover(p, li){
state.hover = p;
var x = scaleDetail(p.ms), y = li*ROW_H+ROW_H/2+p.jitter;
var ring = document.getElementById("hoverRing");
ring.style.display="block";
ring.style.left = x+"px"; ring.style.top = y+"px";
ring.style.width = "12px"; ring.style.height="12px";
var lane = LANES[laneIndex[p.lane]];
var tt = document.getElementById("tooltip");
tt.style.display = "block";
tt.innerHTML = "";
var row1 = document.createElement("div"); row1.className = "row1";
var dotEl = document.createElement("span"); dotEl.className = "dot"; dotEl.style.background = lane.color;
var dateEl = document.createElement("span"); dateEl.className = "date"; dateEl.textContent = fmtDate(p.ms);
var laneEl = document.createElement("span"); laneEl.className = "lane"; laneEl.textContent = "· " + lane.full;
row1.appendChild(dotEl); row1.appendChild(dateEl); row1.appendChild(laneEl);
var idEl = document.createElement("div"); idEl.className = "id"; idEl.textContent = p.id;
var textEl = document.createElement("div"); textEl.className = "text"; textEl.textContent = p.text;
tt.appendChild(row1); tt.appendChild(idEl); tt.appendChild(textEl);
var left = x+16, top = y-10;
if (left + 330 > dtWidth) left = x-330-8;
tt.style.left = left+"px";
tt.style.top = Math.max(0,top)+"px";
}
function clearHover(){
state.hover = null;
document.getElementById("hoverRing").style.display="none";
document.getElementById("tooltip").style.display="none";
}
/* ---------------- resumo panel (opens beside the chart on click/tap) ---------------- */
function openResumo(p, li){
state.selected = { p:p, li:li };
clearHover();
var lane = LANES[laneIndex[p.lane]];
document.getElementById("resumoDot").style.background = lane.color;
document.getElementById("resumoDate").textContent = fmtDate(p.ms);
document.getElementById("resumoLane").textContent = "· " + lane.full;
document.getElementById("resumoDateNote").hidden = !p.dateInferred;
document.getElementById("resumoId").textContent = p.id;
document.getElementById("resumoType").textContent = p.type;
document.getElementById("resumoText").textContent = p.text;
document.getElementById("resumoFile").textContent = p.file;
var peopleBlock = document.getElementById("resumoPeopleBlock");
var peopleWrap = document.getElementById("resumoPeople");
peopleWrap.innerHTML = "";
if (p.people && p.people.length){
p.people.forEach(function(name){
var span = document.createElement("span");
span.textContent = name;
peopleWrap.appendChild(span);
});
peopleBlock.hidden = false;
} else {
peopleBlock.hidden = true;
}
var procNum = p.id.split("-")[0];
document.getElementById("resumoProcessoNum").textContent = procNum;
document.getElementById("resumoProcessoBtn").setAttribute("aria-expanded","false");
var procContent = document.getElementById("resumoProcessoContent");
procContent.hidden = true;
procContent.innerHTML = "";
var panel = document.getElementById("resumoPanel");
var wasHidden = panel.hidden;
panel.hidden = false;
if (wasHidden) resizeAll(); // panel claims width from the canvas, so canvases must resize
else { drawDetail(); }
}
function closeResumo(){
state.selected = null;
document.getElementById("resumoPanel").hidden = true;
document.getElementById("selectionRing").style.display = "none";
resizeAll();
}
function positionSelectionRing(){
var ring = document.getElementById("selectionRing");
var sel = state.selected;
if (!sel || !state.activeLanes.has(sel.p.lane)){ ring.style.display="none"; return; }
var x = scaleDetail(sel.p.ms);
if (x < -10 || x > dtWidth+10){ ring.style.display="none"; return; }
var y = sel.li*ROW_H + ROW_H/2 + sel.p.jitter;
ring.style.display = "block";
ring.style.left = x+"px"; ring.style.top = y+"px";
ring.style.width = "14px"; ring.style.height = "14px";
}
document.getElementById("resumoClose").addEventListener("click", closeResumo);
document.addEventListener("keydown", function(e){
if (e.key==="Escape" && !document.getElementById("resumoPanel").hidden) closeResumo();
});
/* ---------------- per-process résumé (parte1/parte2 .md, fetched on demand) ---------------- */
function escapeHtml(s){
return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
function inlineMd(s){
s = escapeHtml(s);
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, function(_, text, url){
return /^https?:\/\//.test(url) ? '<a href="'+url+'" target="_blank" rel="noopener">'+text+'</a>' : text;
});
s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
s = s.replace(/`([^`]+)`/g, "<code>$1</code>");
return s;
}
// small, purpose-built markdown→HTML pass for the parte1/parte2 résumé
// files (headers, bold, inline code, links, "- " lists, paragraphs) — not
// a general CommonMark renderer, just enough for these fairly plain files.
function mdToHtml(md){
var lines = md.replace(/\r\n/g,"\n").split("\n");
var out = [], para = [], items = null;
function flushPara(){ if (para.length){ out.push("<p>"+para.map(inlineMd).join(" ")+"</p>"); para=[]; } }
function flushList(){ if (items){ out.push("<ul>"+items.map(function(it){return "<li>"+inlineMd(it)+"</li>";}).join("")+"</ul>"); items=null; } }
lines.forEach(function(line){
var h = line.match(/^(#{1,6})\s+(.*)$/);
var li = line.match(/^\s*[-*]\s+(.*)$/);
if (h){
flushPara(); flushList();
var lvl = Math.min(h[1].length,6);
out.push("<h"+lvl+">"+inlineMd(h[2])+"</h"+lvl+">");
} else if (li){
flushPara();
if (!items) items = [];
items.push(li[1]);
} else if (/^\s*-{3,}\s*$/.test(line)){
flushPara(); flushList(); out.push("<hr>");
} else if (line.trim()===""){
flushPara(); flushList();
} else {
flushList();
para.push(line.trim());
}
});
flushPara(); flushList();
return out.join("\n");
}
var processoCache = {}; // process number -> rendered HTML, or null if not found
// parte1/ and parte2/ live one level above web/ in the intended (repo-root)
// deployment, but a local server started from inside web/ (as the README's
// own quickstart does) has no parent directory to serve — so try both
// "../parteN/" and "parteN/" for each folder and use whichever resolves.
function fetchProcessoMd(num){
var candidates = [
"../parte2/"+num+".md", "../parte1/"+num+".md",
"parte2/"+num+".md", "parte1/"+num+".md"
];
function tryNext(i){
if (i >= candidates.length) return null;
return fetch(candidates[i])
.then(function(r){ if (!r.ok) throw 0; return r.text(); })
.catch(function(){ return tryNext(i+1); });
}
return Promise.resolve(tryNext(0)).then(function(md){ return md ? mdToHtml(md) : null; });
}
document.getElementById("resumoProcessoBtn").addEventListener("click", function(){
var num = document.getElementById("resumoProcessoNum").textContent;
var box = document.getElementById("resumoProcessoContent");
var btn = document.getElementById("resumoProcessoBtn");
if (!box.hidden){ box.hidden = true; btn.setAttribute("aria-expanded","false"); return; }
box.hidden = false;
btn.setAttribute("aria-expanded","true");
var empty = '<p class="resumo-processo-empty">Resumo não disponível para este processo.</p>';
if (Object.prototype.hasOwnProperty.call(processoCache, num)){
box.innerHTML = processoCache[num] || empty;
return;
}
box.innerHTML = '<p class="resumo-processo-empty">Carregando…</p>';
fetchProcessoMd(num).then(function(html){
processoCache[num] = html;
// the panel may have moved on to a different document by the time this resolves
if (document.getElementById("resumoProcessoNum").textContent === num && !box.hidden){
box.innerHTML = html || empty;
}
});
});
function bindChartInteraction(){
var dragState = null;
dtCanvas.addEventListener("mousedown", function(e){
dragState = { x: e.clientX, y: e.clientY, t0: state.t0, t1: state.t1 };
dtCanvas.classList.add("dragging");
});
window.addEventListener("mousemove", function(e){
if (dragState){
var dx = e.clientX - dragState.x;
var dt = -(dx/dtWidth)*(dragState.t1-dragState.t0);
var span = dragState.t1-dragState.t0;
var nt0 = dragState.t0+dt, nt1 = dragState.t1+dt;
if (nt0 < state.fullMin){ nt0=state.fullMin; nt1=nt0+span; }
if (nt1 > state.fullMax){ nt1=state.fullMax; nt0=nt1-span; }
state.t0=nt0; state.t1=nt1;
drawDetail(); positionBrush();
} else {
handleHover(e);
}
});
window.addEventListener("mouseup", function(e){
// a mouseup that never moved (or barely did) is a click, not a pan —
// open the clicked point's résumé alongside the chart.
if (dragState){
var moved = Math.hypot(e.clientX-dragState.x, e.clientY-dragState.y);
if (moved < 4){
var hit = findPointAt(e.clientX, e.clientY);
if (hit) openResumo(hit.p, hit.li);
}
}
dragState=null; dtCanvas.classList.remove("dragging");
});
dtCanvas.addEventListener("wheel", function(e){
e.preventDefault();
var rect = dtCanvas.getBoundingClientRect();
var mx = e.clientX-rect.left;
var anchor = invDetail(mx);
var factor = Math.exp(e.deltaY*0.0012);
var span = (state.t1-state.t0)*factor;
var minSpan = 6*3600000, maxSpan = state.fullMax-state.fullMin;
span = Math.max(minSpan, Math.min(maxSpan, span));
var ratio = (anchor-state.t0)/(state.t1-state.t0);
var nt0 = anchor - ratio*span, nt1 = anchor + (1-ratio)*span;
if (nt0<state.fullMin){ nt1+= state.fullMin-nt0; nt0=state.fullMin; }
if (nt1>state.fullMax){ nt0-= nt1-state.fullMax; nt1=state.fullMax; }
state.t0=Math.max(state.fullMin,nt0); state.t1=Math.min(state.fullMax,nt1);
drawDetail(); positionBrush();
}, { passive:false });
dtCanvas.addEventListener("mouseleave", function(){ clearHover(); });
// touch: one-finger drag pans, two-finger pinch zooms, a tap (no drag) shows the tooltip
var touchState = null;
function touchDist(touches){
return Math.hypot(touches[0].clientX-touches[1].clientX, touches[0].clientY-touches[1].clientY);
}
function touchMidX(touches){ return (touches[0].clientX+touches[1].clientX)/2; }
dtCanvas.addEventListener("touchstart", function(e){
if (e.touches.length === 1){
var t = e.touches[0];
touchState = { mode:"pan", x:t.clientX, y:t.clientY, t0:state.t0, t1:state.t1, moved:false, time:Date.now() };
} else if (e.touches.length === 2){
var rect = dtCanvas.getBoundingClientRect();
touchState = { mode:"pinch", dist:touchDist(e.touches), anchorMs: invDetail(touchMidX(e.touches)-rect.left), t0:state.t0, t1:state.t1 };
}
}, { passive:true });
dtCanvas.addEventListener("touchmove", function(e){
if (!touchState) return;
e.preventDefault();
if (touchState.mode==="pan" && e.touches.length===1){
var t = e.touches[0];
var dx = t.clientX - touchState.x;
if (Math.abs(dx) > 4 || Math.abs(t.clientY-touchState.y) > 4) touchState.moved = true;
var dt = -(dx/dtWidth)*(touchState.t1-touchState.t0);
var span = touchState.t1-touchState.t0;
var nt0 = touchState.t0+dt, nt1 = touchState.t1+dt;
if (nt0 < state.fullMin){ nt0=state.fullMin; nt1=nt0+span; }
if (nt1 > state.fullMax){ nt1=state.fullMax; nt0=nt1-span; }
state.t0=nt0; state.t1=nt1;
drawDetail(); positionBrush();
} else if (touchState.mode==="pinch" && e.touches.length===2){
var newDist = touchDist(e.touches);
var factor = touchState.dist / Math.max(newDist,1);
var span = (touchState.t1-touchState.t0)*factor;
var minSpan = 6*3600000, maxSpan = state.fullMax-state.fullMin;
span = Math.max(minSpan, Math.min(maxSpan, span));
var anchor = touchState.anchorMs;
var ratio = (anchor-touchState.t0)/(touchState.t1-touchState.t0);
var nt0p = anchor - ratio*span, nt1p = anchor + (1-ratio)*span;
if (nt0p<state.fullMin){ nt1p+= state.fullMin-nt0p; nt0p=state.fullMin; }
if (nt1p>state.fullMax){ nt0p-= nt1p-state.fullMax; nt1p=state.fullMax; }
state.t0=Math.max(state.fullMin,nt0p); state.t1=Math.min(state.fullMax,nt1p);
drawDetail(); positionBrush();
}
}, { passive:false });
dtCanvas.addEventListener("touchend", function(){
// a tap that never turned into a pan opens the résumé directly — touch
// has no hover state to preview in first, so tap goes straight to it.
if (touchState && touchState.mode==="pan" && !touchState.moved && (Date.now()-touchState.time)<400){
var hit = findPointAt(touchState.x, touchState.y);
if (hit) openResumo(hit.p, hit.li);
}
touchState = null;
});
// overview brush
var brush = document.getElementById("brush");
var ovState = null;
var EDGE_PX = 7;
function startBrushGesture(mx, edgePx){
var bx0 = scaleFull(state.t0), bx1 = scaleFull(state.t1);
var mode;
if (Math.abs(mx-bx0)<edgePx) mode="left";
else if (Math.abs(mx-bx1)<edgePx) mode="right";
else if (mx>bx0 && mx<bx1) mode="move";
else { mode="move"; var span=state.t1-state.t0; var center = state.fullMin+(mx/ovWidth)*(state.fullMax-state.fullMin);
state.t0=center-span/2; state.t1=center+span/2;
clampT(); drawDetail(); positionBrush();
}
return { mode:mode, startX:mx, t0:state.t0, t1:state.t1 };
}
function moveBrushGesture(gs, mx){
var dt = (mx-gs.startX)/ovWidth*(state.fullMax-state.fullMin);
if (gs.mode==="move"){ state.t0=gs.t0+dt; state.t1=gs.t1+dt; }
else if (gs.mode==="left"){ state.t0=Math.min(gs.t1-6*3600000, gs.t0+dt); }
else if (gs.mode==="right"){ state.t1=Math.max(gs.t0+6*3600000, gs.t1+dt); }
clampT();
drawDetail(); positionBrush();
}
ovCanvas.addEventListener("mousedown", function(e){
var rect = ovCanvas.getBoundingClientRect();
ovState = startBrushGesture(e.clientX-rect.left, EDGE_PX);
});
window.addEventListener("mousemove", function(e){
if (!ovState) return;
var rect = ovCanvas.getBoundingClientRect();
moveBrushGesture(ovState, e.clientX-rect.left);
});
window.addEventListener("mouseup", function(){ ovState=null; });
var ovTouchState = null;
ovCanvas.addEventListener("touchstart", function(e){
if (e.touches.length !== 1) return;
var rect = ovCanvas.getBoundingClientRect();
ovTouchState = startBrushGesture(e.touches[0].clientX-rect.left, 14);
}, { passive:true });
ovCanvas.addEventListener("touchmove", function(e){
if (!ovTouchState || e.touches.length!==1) return;
e.preventDefault();
var rect = ovCanvas.getBoundingClientRect();
moveBrushGesture(ovTouchState, e.touches[0].clientX-rect.left);
}, { passive:false });
ovCanvas.addEventListener("touchend", function(){ ovTouchState=null; });
function clampT(){
var gs = ovState || ovTouchState;
if (state.t0<state.fullMin){ var span=state.t1-state.t0; state.t0=state.fullMin; if(gs&&gs.mode==="move") state.t1=state.t0+span; }
if (state.t1>state.fullMax){ var span2=state.t1-state.t0; state.t1=state.fullMax; if(gs&&gs.mode==="move") state.t0=state.t1-span2; }
}
}
function positionBrush(){
var brush = document.getElementById("brush");
var x0 = scaleFull(state.t0), x1 = scaleFull(state.t1);
brush.style.left = Math.max(0,x0)+"px";
brush.style.width = Math.max(2,x1-x0)+"px";
}
/* ---------------- table view ---------------- */
function allRows(){
var rows = state.points.concat(state.undated);
return rows.filter(function(r){
if (!state.activeLanes.has(r.lane)) return false;
if (!state.activeTypes.has(r.type)) return false;
if (!peopleOk(r)) return false;
if (state.onlyUndated && r.ms!=null) return false;
return matchesSearch(r);
});
}
function renderTable(reset){
if (reset) state.pageSize = 200;
var rows = allRows();
rows.sort(function(a,b){
if (state.sortKey==="date"){
var am=a.ms, bm=b.ms;
if (am==null && bm==null) return a.order-b.order;
if (am==null) return 1;
if (bm==null) return -1;
return state.sortDir==="asc" ? am-bm : bm-am;
} else if (state.sortKey==="type"){
if (a.type<b.type) return state.sortDir==="asc" ? -1:1;
if (a.type>b.type) return state.sortDir==="asc" ? 1:-1;
return (a.ms||0)-(b.ms||0);
} else {
var al = LANES[laneIndex[a.lane]].label, bl = LANES[laneIndex[b.lane]].label;
if (al<bl) return state.sortDir==="asc" ? -1:1;
if (al>bl) return state.sortDir==="asc" ? 1:-1;
return (a.ms||0)-(b.ms||0);
}
});
var slice = rows.slice(0, state.pageSize);
var tbody = document.getElementById("tbody");
tbody.innerHTML = "";
var frag = document.createDocumentFragment();
slice.forEach(function(r){
var lane = LANES[laneIndex[r.lane]];
var tr = document.createElement("tr");
var tdDate = document.createElement("td");
tdDate.className = "date";
tdDate.textContent = r.ms!=null ? fmtDate(r.ms) : "—";
tr.appendChild(tdDate);
var tdLane = document.createElement("td");
tdLane.className = "lane";
var dot = document.createElement("span"); dot.className="dot"; dot.style.background = lane.color;
tdLane.appendChild(dot);
tdLane.appendChild(document.createTextNode(lane.label));
tr.appendChild(tdLane);
var tdType = document.createElement("td"); tdType.className="type"; tdType.textContent = r.type;
tr.appendChild(tdType);
var tdId = document.createElement("td"); tdId.className="idcol"; tdId.textContent = r.id;
tr.appendChild(tdId);
var tdText = document.createElement("td"); tdText.className="text"; tdText.textContent = r.text;
tr.appendChild(tdText);
var tdPeople = document.createElement("td"); tdPeople.className="people";
tdPeople.textContent = (r.people && r.people.length) ? r.people.join(", ") : "—";
tr.appendChild(tdPeople);
var tdFile = document.createElement("td"); tdFile.className="file"; tdFile.textContent = r.file;
tr.appendChild(tdFile);
frag.appendChild(tr);
});
tbody.appendChild(frag);
document.getElementById("tableCount").textContent =
"Mostrando " + Math.min(state.pageSize, rows.length).toLocaleString("pt-BR") + " de " + rows.length.toLocaleString("pt-BR");
document.getElementById("loadMore").style.display = rows.length>state.pageSize ? "inline-block" : "none";
document.querySelectorAll("thead th[data-sort]").forEach(function(th){
var arrow = th.querySelector(".arrow");
if (th.dataset.sort===state.sortKey) arrow.textContent = state.sortDir==="asc" ? "↑" : "↓";
else arrow.textContent = "";
});
var total = state.points.length+state.undated.length;
document.getElementById("chartCount").textContent =
(state.search ? "Buscando “"+state.search+"” — " : "") +
rows.length.toLocaleString("pt-BR") + " de " + total.toLocaleString("pt-BR") + " documentos visíveis nos filtros atuais.";
}
document.getElementById("loadMore").addEventListener("click", function(){
state.pageSize += 200;
renderTable(false);
});
document.querySelectorAll("thead th[data-sort]").forEach(function(th){
th.addEventListener("click", function(){
var key = th.dataset.sort;
if (state.sortKey===key) state.sortDir = state.sortDir==="asc" ? "desc":"asc";
else { state.sortKey=key; state.sortDir="asc"; }
renderTable(false);
});
});
})();
</script>
</body>
</html>