1767 lines
76 KiB
HTML
1767 lines
76 KiB
HTML
<!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);
|
||
--danger: #a8271f;
|
||
}
|
||
@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);
|
||
--danger: #f0847a;
|
||
}
|
||
}
|
||
: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);
|
||
--danger: #f0847a;
|
||
}
|
||
|
||
*{ 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; }
|
||
.page-top-row{ display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:10px; }
|
||
.page-top-links{ display:flex; flex-wrap:wrap; gap:10px; }
|
||
.page-link{
|
||
display:inline-flex; align-items:center; gap:6px;
|
||
font-family:"IBM Plex Mono",monospace; font-size:11px; letter-spacing:.06em; text-transform:uppercase;
|
||
color:var(--ink-2); border:1px solid var(--border); background:var(--surface); border-radius:999px;
|
||
padding:5px 11px; text-decoration:none; cursor:pointer;
|
||
}
|
||
.page-link svg{ flex:none; opacity:.7; }
|
||
.page-link:hover{ border-color:var(--accent); color:var(--ink); }
|
||
.page-link:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
|
||
.stamp{
|
||
font-family:"IBM Plex Mono",monospace; font-size:10.5px; letter-spacing:.09em; text-transform:uppercase;
|
||
color:var(--danger); border:1.4px solid var(--danger); border-radius:2px;
|
||
padding:4px 8px; transform:rotate(-2deg); white-space:nowrap; flex-shrink:0;
|
||
}
|
||
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;
|
||
text-decoration:none;
|
||
}
|
||
.lede a.mono:hover{ background:var(--accent); color:#fff; }
|
||
|
||
.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:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
|
||
.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-actions button:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; border-radius:3px; }
|
||
.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:hover{ border-color:var(--accent); color:var(--ink); }
|
||
.role-chip:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
|
||
.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; transition:background .15s ease, color .15s ease;
|
||
}
|
||
.segmented button + button{ border-left:1px solid var(--border); }
|
||
.segmented button:hover{ background:var(--accent-wash); color:var(--ink); }
|
||
.segmented button:focus-visible{ outline:2px solid var(--accent); outline-offset:-2px; }
|
||
.segmented button[aria-pressed="true"]{ background:var(--accent); color:#fff; }
|
||
.segmented button[aria-pressed="true"]:hover{ background:var(--accent); }
|
||
|
||
.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:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
|
||
.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-label{ font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--ink-3); margin-bottom:6px; }
|
||
#resumoPeopleBlock{ margin-bottom:14px; }
|
||
.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-file a{ color:inherit; text-decoration:none; }
|
||
.resumo-file a:hover{ color:var(--accent); text-decoration:underline; }
|
||
|
||
.resumo-processo-btn{
|
||
width:100%; text-align:center; 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; margin-top:12px;
|
||
}
|
||
.resumo-processo-btn:hover{ border-color:var(--accent); }
|
||
.resumo-processo-content{
|
||
margin-top:14px; max-height:60vh; overflow-y:auto; overflow-wrap:anywhere;
|
||
font-size:14px; line-height:1.65; color:var(--ink-2);
|
||
background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:16px 18px;
|
||
}
|
||
.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{
|
||
/* fixed (not absolute-in-wrap) so a tall/near-edge tooltip overlays the
|
||
page instead of expanding .detail-card's scrollable box and popping a
|
||
scrollbar */
|
||
position:fixed; 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{ cursor:pointer; }
|
||
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;
|
||
}
|
||
td.file a{ color:inherit; text-decoration:none; }
|
||
td.file a:hover{ color:var(--accent); text-decoration:underline; }
|
||
.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>
|
||
<div class="page-top-row">
|
||
<div class="stamp">Sob sigilo nos autos originais</div>
|
||
<div class="page-top-links">
|
||
<a class="page-link" href="https://git.rodado.xyz/rodado/pet16704" target="_blank" rel="noopener noreferrer">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3v12"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="6" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>
|
||
Fontes
|
||
</a>
|
||
<a class="page-link" href="revelacoes.html" target="_blank" rel="noopener noreferrer">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3v5h5"/><path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/></svg>
|
||
Dossiê
|
||
</a>
|
||
<a class="page-link" href="https://rafapolo.github.io/vorcaros/" target="_blank" rel="noopener noreferrer">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="6" r="2.5"/><circle cx="19" cy="6" r="2.5"/><circle cx="12" cy="18" r="2.5"/><path d="M7 7.3 10 16"/><path d="M17 7.3 14 16"/><path d="M7.4 6h9.2"/></svg>
|
||
Sociedades
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<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
|
||
<a class="mono" href="https://git.rodado.xyz/rodado/pet16704/src/branch/main/resumos/parte1" target="_blank" rel="noopener noreferrer">resumos/parte1/</a>
|
||
e <a class="mono" href="https://git.rodado.xyz/rodado/pet16704/src/branch/main/resumos/parte2" target="_blank" rel="noopener noreferrer">resumos/parte2/</a>). Clique em qualquer ponto da linha do tempo para abrir o resumo do
|
||
documento original.
|
||
</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 · 2018–2026 · 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 id="resumoPeopleBlock" hidden>
|
||
<div class="resumo-label">Pessoas mencionadas</div>
|
||
<div class="resumo-people" id="resumoPeople"></div>
|
||
</div>
|
||
<div>
|
||
<div class="resumo-label">Arquivo-fonte</div>
|
||
<div class="resumo-file mono" id="resumoFile"></div>
|
||
</div>
|
||
<div>
|
||
<button type="button" class="resumo-processo-btn" id="resumoProcessoBtn" aria-expanded="false">
|
||
Ver resumo do processo <span class="mono" id="resumoProcessoNum"></span>
|
||
</button>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
<div class="resumo-processo-content" id="resumoProcessoContent" hidden></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>
|
||
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">resumos/parte1/</span> ou <span class="mono">resumos/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);
|
||
|
||
// tt is position:fixed, so convert the canvas-local x/y into viewport
|
||
// coordinates via the canvas's own rect, then clamp to the viewport
|
||
// (not the scrollable card) so it never grows .detail-card's overflow.
|
||
var rect = dtCanvas.getBoundingClientRect();
|
||
var vx = rect.left+x, vy = rect.top+y;
|
||
var left = vx+16, top = vy-10;
|
||
if (left + 330 > window.innerWidth) left = vx-330-8;
|
||
var ttH = tt.offsetHeight;
|
||
if (top + ttH > window.innerHeight) top = window.innerHeight-ttH-8;
|
||
tt.style.left = left+"px";
|
||
tt.style.top = Math.max(8,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;
|
||
var fileEl = document.getElementById("resumoFile");
|
||
fileEl.innerHTML = "";
|
||
var fileLink = document.createElement("a");
|
||
fileLink.href = originalMdUrl(p.file);
|
||
fileLink.target = "_blank"; fileLink.rel = "noopener noreferrer";
|
||
fileLink.textContent = p.file;
|
||
fileEl.appendChild(fileLink);
|
||
|
||
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("resumoProcessoContent").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é (resumos/parte1,parte2 .md, fetched on demand) ---------------- */
|
||
|
||
function escapeHtml(s){
|
||
return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||
}
|
||
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 resumos/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");
|
||
}
|
||
|
||
// r.file (docs.json) is always a path under originals/Parte2/ — every lane
|
||
// in docs.json has a matching folder there (verified: no lane needs Parte1).
|
||
function originalMdUrl(file){
|
||
return "https://git.rodado.xyz/rodado/pet16704/src/branch/main/originals/Parte2/"
|
||
+ file.split("/").map(encodeURIComponent).join("/");
|
||
}
|
||
|
||
var processoCache = {}; // process number -> rendered HTML, or null if not found
|
||
// resumos/parte1/ and resumos/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 "../resumos/parteN/" and "resumos/parteN/" for each
|
||
// folder and use whichever resolves.
|
||
function fetchProcessoMd(num){
|
||
var candidates = [
|
||
"../resumos/parte2/"+num+".md", "../resumos/parte1/"+num+".md",
|
||
"resumos/parte2/"+num+".md", "resumos/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");
|
||
tr.title = "Ver resumo deste documento";
|
||
tr.addEventListener("click", function(){
|
||
setView("chart");
|
||
openResumo(r, laneIndex[r.lane]);
|
||
});
|
||
|
||
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.title = "Abrir o documento original";
|
||
var fileLink = document.createElement("a");
|
||
fileLink.href = originalMdUrl(r.file);
|
||
fileLink.target = "_blank"; fileLink.rel = "noopener noreferrer";
|
||
fileLink.textContent = r.file;
|
||
fileLink.addEventListener("click", function(e){ e.stopPropagation(); });
|
||
tdFile.appendChild(fileLink);
|
||
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>
|