noranisa's picture
Update templates/result.html
3890fa2 verified
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SentiScope โ€” Hasil Analisis</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Cabinet+Grotesk:wght@400;500;700;800&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"></script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #080a0f;
--s1: #0e1117;
--s2: #141820;
--s3: #1a2030;
--b1: rgba(255,255,255,0.06);
--b2: rgba(255,255,255,0.10);
--text: #e8eaf0;
--muted: #4a5568;
--m2: #8892a4;
--accent: #4f9cf9;
--a2: #7b61ff;
--pos: #22c55e;
--neg: #ef4444;
--neu: #94a3b8;
--gold: #f59e0b;
--display:'Cabinet Grotesk', sans-serif;
--mono: 'JetBrains Mono', monospace;
}
html { scroll-behavior: smooth; }
body { font-family: var(--display); background: var(--bg); color: var(--text); min-height: 100vh; }
body::before {
content:''; position:fixed; inset:0;
background-image: linear-gradient(rgba(79,156,249,0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(79,156,249,0.02) 1px, transparent 1px);
background-size: 64px 64px; pointer-events:none; z-index:0;
}
/* โ”€โ”€ TOPBAR โ”€โ”€ */
.topbar {
position: sticky; top: 0; z-index: 100;
display: grid; grid-template-columns: auto 1fr auto;
align-items: center; gap: 1.5rem;
padding: 0.9rem 2rem;
background: rgba(8,10,15,0.88); backdrop-filter: blur(20px);
border-bottom: 1px solid var(--b1);
}
.back {
display: flex; align-items: center; gap: 0.4rem;
font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase;
color: var(--m2); text-decoration: none; transition: color .2s;
}
.back:hover { color: var(--text); }
.kw-display { font-size: 0.9rem; font-weight: 700; letter-spacing: -0.01em; }
.kw-display span { color: var(--accent); }
.topbar-right { display: flex; align-items: center; gap: 1rem; }
.tb-stat { display: flex; flex-direction: column; align-items: flex-end; gap: 0.1rem; }
.tb-stat-l { font-family: var(--mono); font-size: 0.5rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); }
.tb-stat-v { font-family: var(--mono); font-size: 0.8rem; font-weight: 500; color: var(--text); }
.btn-dl {
font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.08em; text-transform: uppercase;
background: var(--s2); color: var(--m2); border: 1px solid var(--b2);
padding: 0.5rem 1rem; border-radius: 6px; text-decoration: none;
transition: border-color .2s, color .2s;
}
.btn-dl:hover { border-color: var(--accent); color: var(--accent); }
/* โ”€โ”€ METRIC CARDS โ”€โ”€ */
.metrics {
display: grid; grid-template-columns: repeat(5, 1fr);
gap: 1px; background: var(--b1);
border-bottom: 1px solid var(--b1);
position: relative; z-index: 1;
}
.metric {
background: var(--s1); padding: 1.5rem 2rem;
display: flex; flex-direction: column; gap: 0.5rem;
transition: background .2s;
opacity: 0; transform: translateY(12px);
animation: fu 0.45s both;
}
.metric:hover { background: var(--s2); }
.metric:nth-child(1){animation-delay:.05s}
.metric:nth-child(2){animation-delay:.1s}
.metric:nth-child(3){animation-delay:.15s}
.metric:nth-child(4){animation-delay:.2s}
.metric:nth-child(5){animation-delay:.25s}
.metric-l { font-family: var(--mono); font-size: 0.52rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); }
.metric-n { font-size: 2.8rem; font-weight: 800; letter-spacing: -0.04em; line-height: 1; }
.metric-n.c-pos { color: var(--pos); }
.metric-n.c-neg { color: var(--neg); }
.metric-n.c-neu { color: var(--neu); }
.metric-n.c-up { color: var(--pos); }
.metric-n.c-dn { color: var(--neg); }
.metric-n.c-base{ color: var(--text); }
.metric-sub { font-family: var(--mono); font-size: 0.58rem; color: var(--muted); }
/* โ”€โ”€ DISTRIBUTION BAR โ”€โ”€ */
.dist-bar {
position: relative; z-index: 1;
display: flex; align-items: center; gap: 1.5rem;
padding: 1rem 2rem; border-bottom: 1px solid var(--b1);
background: var(--s1);
}
.dist-l { font-family: var(--mono); font-size: 0.55rem; letter-spacing: 0.15em; text-transform: uppercase; color: var(--muted); white-space: nowrap; }
.dist-track { flex: 1; height: 5px; background: var(--s2); border-radius: 3px; display: flex; overflow: hidden; gap: 2px; }
.ds { height: 100%; transition: width 1.2s cubic-bezier(.25,.46,.45,.94); border-radius: 2px; }
.ds-p { background: var(--pos); }
.ds-n { background: var(--neg); }
.ds-u { background: var(--neu); }
.dist-info { font-family: var(--mono); font-size: 0.58rem; color: var(--m2); white-space: nowrap; }
/* โ”€โ”€ GRID LAYOUT โ”€โ”€ */
.main-grid {
position: relative; z-index: 1;
display: grid; grid-template-columns: 1fr 1fr;
gap: 1px; background: var(--b1);
border-bottom: 1px solid var(--b1);
}
.three-col {
display: grid; grid-template-columns: 1fr 1fr 1fr;
gap: 1px; background: var(--b1);
border-bottom: 1px solid var(--b1);
}
.full-row {
position: relative; z-index: 1;
border-bottom: 1px solid var(--b1);
}
/* โ”€โ”€ PANEL โ”€โ”€ */
.panel { background: var(--s1); padding: 1.75rem 2rem; }
.panel:hover { background: var(--s1); }
.panel-tag {
font-family: var(--mono); font-size: 0.5rem; letter-spacing: 0.18em; text-transform: uppercase;
color: var(--muted); margin-bottom: 1.25rem;
display: flex; align-items: center; gap: 0.8rem;
}
.panel-tag::after { content:''; flex:1; height:1px; background: var(--b2); }
.panel-title { font-size: 1.1rem; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 1.25rem; }
/* โ”€โ”€ CHART โ”€โ”€ */
.chart-wrap { position:relative; height:240px; }
/* โ”€โ”€ TABLE โ”€โ”€ */
.tbl-wrap { overflow-x:auto; max-height: 320px; overflow-y:auto; }
.tbl-wrap::-webkit-scrollbar { width: 3px; height: 3px; }
.tbl-wrap::-webkit-scrollbar-track { background: transparent; }
.tbl-wrap::-webkit-scrollbar-thumb { background: var(--b2); border-radius: 2px; }
table { width:100%; border-collapse:collapse; font-family: var(--mono); font-size:0.68rem; }
th {
text-align:left; font-size:0.5rem; letter-spacing:.15em; text-transform:uppercase;
color: var(--muted); padding: 0.5rem 0.75rem; border-bottom:1px solid var(--b2);
position: sticky; top: 0; background: var(--s1);
}
td { padding: 0.65rem 0.75rem; border-bottom: 1px solid rgba(255,255,255,0.03); vertical-align:top; line-height:1.45; }
tr:last-child td { border-bottom:none; }
tr:hover td { background: var(--s2); }
.tag {
display:inline-block; font-size:0.5rem; letter-spacing:.08em; text-transform:uppercase;
padding: 0.15rem 0.4rem; border-radius: 4px; border: 1px solid; white-space:nowrap;
}
.tag-p { color: var(--pos); border-color: rgba(34,197,94,0.3); background: rgba(34,197,94,0.08); }
.tag-n { color: var(--neg); border-color: rgba(239,68,68,0.3); background: rgba(239,68,68,0.08); }
.tag-u { color: var(--neu); border-color: rgba(148,163,184,0.3); background: rgba(148,163,184,0.08); }
.src-badge {
font-size: 0.48rem; letter-spacing: .06em; text-transform:uppercase;
padding: 0.12rem 0.35rem; border-radius: 3px; border: 1px solid var(--b2); color: var(--muted);
}
/* โ”€โ”€ WORD CLOUD โ”€โ”€ */
.word-cloud { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: baseline; }
.w-chip { font-family: var(--mono); color: var(--m2); cursor:default; transition: color .2s; line-height: 1.2; }
.w-chip:hover { color: var(--accent); }
/* โ”€โ”€ TOPIC CARDS โ”€โ”€ */
.topic-list { display: flex; flex-direction: column; gap: 0.75rem; }
.topic-card { background: var(--s2); border: 1px solid var(--b2); border-radius: 8px; padding: 0.9rem 1rem; }
.topic-card-h { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.6rem; }
.topic-num { font-family: var(--mono); font-size: 0.5rem; letter-spacing: .15em; text-transform: uppercase; color: var(--muted); }
.topic-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); opacity: 0.6; }
.topic-words { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.tw { font-family: var(--mono); font-size: 0.62rem; background: var(--s3); border: 1px solid var(--b2); padding: 0.18rem 0.45rem; border-radius: 4px; color: var(--m2); }
/* โ”€โ”€ CLUSTERS โ”€โ”€ */
.cluster-list { display: flex; flex-direction: column; gap: 0.75rem; }
.cl-item { border-left: 2px solid var(--b2); padding-left: 0.9rem; }
.cl-head { font-family: var(--mono); font-size: 0.52rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin-bottom: 0.35rem; }
.cl-text { font-family: var(--mono); font-size: 0.65rem; color: var(--m2); line-height: 1.5; }
/* โ”€โ”€ HOAX LIST โ”€โ”€ */
.hoax-list { display: flex; flex-direction: column; gap: 0.5rem; }
.hoax-item {
display: flex; align-items: flex-start; gap: 0.7rem;
background: var(--s2); border: 1px solid var(--b1); border-radius: 6px; padding: 0.65rem 0.75rem;
transition: border-color .2s;
}
.hoax-item:hover { border-color: var(--b2); }
.h-badge {
font-family: var(--mono); font-size: 0.48rem; letter-spacing: .08em; text-transform: uppercase;
padding: 0.15rem 0.4rem; border-radius: 3px; white-space: nowrap; flex-shrink: 0; margin-top: 0.1rem;
}
.hb-hoax { background: rgba(239,68,68,0.15); color: var(--neg); border: 1px solid rgba(239,68,68,0.25); }
.hb-norm { background: var(--s3); color: var(--muted); border: 1px solid var(--b2); }
.h-text { font-family: var(--mono); font-size: 0.65rem; color: var(--m2); line-height: 1.5; }
/* โ”€โ”€ BOT LIST โ”€โ”€ */
.bot-list { display: flex; flex-direction: column; gap: 0.5rem; }
.bot-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem 0; border-bottom: 1px solid var(--b1); }
.bot-item:last-child { border-bottom: none; }
.bot-id { font-family: var(--mono); font-size: 0.62rem; color: var(--m2); min-width: 3.5rem; }
.bot-bar { flex: 1; height: 3px; background: var(--s2); border-radius: 2px; overflow: hidden; }
.bot-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--a2)); border-radius: 2px; transition: width 1s ease; }
.bot-score { font-family: var(--mono); font-size: 0.6rem; color: var(--muted); min-width: 2.5rem; text-align: right; }
/* โ”€โ”€ INSIGHT โ”€โ”€ */
.insight-block {
position: relative; z-index: 1;
background: linear-gradient(135deg, var(--s2) 0%, var(--s3) 100%);
border-bottom: 1px solid var(--b1);
padding: 2.5rem 2rem;
display: grid; grid-template-columns: 1fr auto; gap: 2rem; align-items: center;
overflow: hidden;
}
.insight-block::before {
content:''; position:absolute; top:0;left:0;right:0;height:1px;
background: linear-gradient(90deg, transparent, rgba(79,156,249,0.4), transparent);
}
.insight-text { font-size: 1.2rem; font-weight: 700; letter-spacing: -0.02em; line-height: 1.5; }
.insight-text em { font-style: normal; color: var(--accent); }
.insight-meta { font-family: var(--mono); font-size: 0.58rem; color: var(--muted); line-height: 1.7; white-space: nowrap; text-align: right; }
/* โ”€โ”€ IMAGES โ”€โ”€ */
.img-grid {
position: relative; z-index: 1;
display: grid; grid-template-columns: 1fr 1fr;
gap: 1px; background: var(--b1);
border-bottom: 1px solid var(--b1);
}
.img-panel { background: var(--s1); padding: 1.75rem 2rem; }
.img-panel img { width:100%; height:auto; display:block; border: 1px solid var(--b2); border-radius: 6px; }
.img-ph {
width:100%; height:160px; background: var(--s2); border: 1px dashed var(--b2); border-radius: 6px;
display:flex; align-items:center; justify-content:center;
font-family: var(--mono); font-size:0.62rem; color: var(--muted);
}
/* โ”€โ”€ FOOTER โ”€โ”€ */
footer {
position: relative; z-index: 1;
padding: 1.5rem 2rem;
display: flex; align-items: center; justify-content: space-between;
border-top: 1px solid var(--b1);
}
.ft-kw { font-family: var(--mono); font-size: 0.6rem; color: var(--muted); }
.ft-kw strong { font-family: var(--display); font-size: 0.8rem; font-weight: 700; color: var(--accent); }
.ft-r { display: flex; gap: 1rem; }
.ft-r a {
font-family: var(--mono); font-size: 0.6rem; letter-spacing: .08em; text-transform: uppercase;
color: var(--muted); text-decoration: none; transition: color .2s;
}
.ft-r a:hover { color: var(--text); }
/* โ”€โ”€ EMPTY โ”€โ”€ */
.empty { font-family: var(--mono); font-size: 0.65rem; color: var(--muted); font-style: italic; }
/* โ”€โ”€ ANIM โ”€โ”€ */
@keyframes fu { from{opacity:0;transform:translateY(14px)} to{opacity:1;transform:translateY(0)} }
/* โ”€โ”€ RESPONSIVE โ”€โ”€ */
@media(max-width:960px){
.metrics { grid-template-columns: repeat(3,1fr); }
.main-grid { grid-template-columns: 1fr; }
.three-col { grid-template-columns: 1fr; }
.img-grid { grid-template-columns: 1fr; }
.insight-block { grid-template-columns: 1fr; }
.insight-meta { text-align: left; white-space: normal; }
.topbar { padding: 0.8rem 1.2rem; }
.panel { padding: 1.5rem; }
}
</style>
</head>
<body>
<!-- TOPBAR -->
<header class="topbar">
<a href="/" class="back">โ† Kembali</a>
<div class="kw-display">Analisis: <span id="h-kw">โ€”</span></div>
<div class="topbar-right">
<div class="tb-stat">
<span class="tb-stat-l">Total</span>
<span class="tb-stat-v" id="h-total">โ€”</span>
</div>
<div class="tb-stat">
<span class="tb-stat-l">Sumber</span>
<span class="tb-stat-v" id="h-src">โ€”</span>
</div>
<a href="/download" class="btn-dl">โ†“ CSV</a>
</div>
</header>
<!-- METRICS -->
<section class="metrics">
<div class="metric">
<span class="metric-l">Total Komentar</span>
<div class="metric-n c-base" id="m-total">โ€”</div>
<span class="metric-sub">data diproses</span>
</div>
<div class="metric">
<span class="metric-l">Positif</span>
<div class="metric-n c-pos" id="m-pos">โ€”</div>
<span class="metric-sub" id="m-pos-p">0%</span>
</div>
<div class="metric">
<span class="metric-l">Negatif</span>
<div class="metric-n c-neg" id="m-neg">โ€”</div>
<span class="metric-sub" id="m-neg-p">0%</span>
</div>
<div class="metric">
<span class="metric-l">Netral</span>
<div class="metric-n c-neu" id="m-neu">โ€”</div>
<span class="metric-sub" id="m-neu-p">0%</span>
</div>
<div class="metric">
<span class="metric-l">Prediksi Tren</span>
<div class="metric-n" id="m-trend" style="font-size:1.4rem;line-height:1.6">โ€”</div>
<span class="metric-sub">regresi linier</span>
</div>
</section>
<!-- DISTRIBUTION -->
<div class="dist-bar">
<span class="dist-l">Distribusi Sentimen</span>
<div class="dist-track">
<div class="ds ds-p" id="ds-p" style="width:0%"></div>
<div class="ds ds-n" id="ds-n" style="width:0%"></div>
<div class="ds ds-u" id="ds-u" style="width:0%"></div>
</div>
<span class="dist-info" id="dist-info">โ€”</span>
</div>
<!-- CHART + WORDS -->
<div class="main-grid">
<div class="panel">
<div class="panel-tag">01 โ€” Distribusi Sentimen</div>
<div class="chart-wrap"><canvas id="c-pie"></canvas></div>
</div>
<div class="panel">
<div class="panel-tag">02 โ€” Kata Dominan</div>
<div class="word-cloud" id="words"><span class="empty">Memuatโ€ฆ</span></div>
</div>
</div>
<!-- TABLE + TOPICS -->
<div class="main-grid">
<div class="panel">
<div class="panel-tag">03 โ€” Data Komentar</div>
<div class="tbl-wrap">
<table>
<thead><tr><th>#</th><th>Sumber</th><th>Komentar</th><th>Sentimen</th><th>Conf.</th></tr></thead>
<tbody id="tbl"></tbody>
</table>
</div>
</div>
<div class="panel">
<div class="panel-tag">04 โ€” Topic Modeling (LDA)</div>
<div class="topic-list" id="topics"><span class="empty">Memuatโ€ฆ</span></div>
</div>
</div>
<!-- TIMELINE -->
<div class="full-row">
<div class="panel">
<div class="panel-tag">05 โ€” Timeline Sentimen</div>
<div class="chart-wrap" style="height:200px"><canvas id="c-line"></canvas></div>
</div>
</div>
<!-- CLUSTER + HOAX + BOT -->
<div class="three-col">
<div class="panel">
<div class="panel-tag">06 โ€” Kluster Opini</div>
<div class="cluster-list" id="clusters"><span class="empty">Memuatโ€ฆ</span></div>
</div>
<div class="panel">
<div class="panel-tag">07 โ€” Deteksi Konten Hoaks</div>
<div class="hoax-list" id="hoax"><span class="empty">Memuatโ€ฆ</span></div>
</div>
<div class="panel">
<div class="panel-tag">08 โ€” Bot Network Score</div>
<div class="bot-list" id="bots"><span class="empty">Memuatโ€ฆ</span></div>
</div>
</div>
<!-- IMAGES -->
<div class="img-grid">
<div class="img-panel">
<div class="panel-tag">09 โ€” Word Cloud</div>
<div id="wc-box"><div class="img-ph">Wordcloud diproses server</div></div>
</div>
<div class="img-panel">
<div class="panel-tag">10 โ€” Heatmap Sumber ร— Sentimen</div>
<div id="hm-box"><div class="img-ph">Heatmap diproses server</div></div>
</div>
</div>
<!-- CROSS-PLATFORM ANALYSIS -->
<div class="full-row" id="cross-section" style="display:none">
<div class="panel" style="border-bottom:1px solid var(--b1)">
<div class="panel-tag">11 โ€” Analisis Komparatif Lintas Platform</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--b1);border-radius:8px;overflow:hidden;margin-bottom:1.25rem">
<div style="background:var(--s2);padding:1rem 1.25rem">
<div style="font-family:var(--mono);font-size:0.5rem;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);margin-bottom:.75rem">Platform Insights</div>
<div id="cross-insights" class="empty">Memuatโ€ฆ</div>
</div>
<div style="background:var(--s2);padding:1rem 1.25rem">
<div style="font-family:var(--mono);font-size:0.5rem;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);margin-bottom:.75rem">Pairwise Comparison</div>
<div id="cross-pairwise" class="empty">Memuatโ€ฆ</div>
</div>
</div>
<div id="cross-img-box"><div class="img-ph">Chart komparatif diproses server</div></div>
</div>
</div>
<!-- CONFIDENCE STATS -->
<div class="full-row">
<div class="panel" style="border-bottom:1px solid var(--b1)">
<div class="panel-tag">12 โ€” Statistik Confidence Model</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--b1);border-radius:8px;overflow:hidden">
<div id="cs-pos" style="background:var(--s2);padding:1rem 1.25rem"></div>
<div id="cs-neg" style="background:var(--s2);padding:1rem 1.25rem"></div>
<div id="cs-neu" style="background:var(--s2);padding:1rem 1.25rem"></div>
<div id="cs-unc" style="background:var(--s2);padding:1rem 1.25rem"></div>
</div>
<div style="display:flex;gap:1.5rem;margin-top:1rem;flex-wrap:wrap" id="conf-buckets"></div>
</div>
</div>
<!-- INSIGHT -->
<div class="insight-block">
<p class="insight-text" id="insight-text">Mengolah data analisisโ€ฆ</p>
<p class="insight-meta" id="insight-meta"></p>
</div>
<!-- ABSA + NER -->
<div class="main-grid" style="border-bottom:1px solid var(--b1);position:relative;z-index:1">
<div class="panel">
<div class="panel-tag">13 โ€” Aspect-Based Sentiment Analysis (ABSA)</div>
<div id="absa-grid" class="empty">Memuatโ€ฆ</div>
</div>
<div class="panel">
<div class="panel-tag">14 โ€” Named Entity Recognition (NER)</div>
<div id="ner-grid" class="empty">Memuatโ€ฆ</div>
</div>
</div>
<!-- STANCE + EMOTION -->
<div class="main-grid" style="border-bottom:1px solid var(--b1);position:relative;z-index:1">
<div class="panel">
<div class="panel-tag">15 โ€” Stance Detection</div>
<div id="stance-grid" class="empty">Memuatโ€ฆ</div>
</div>
<div class="panel">
<div class="panel-tag">16 โ€” Emotion Detection</div>
<div id="emotion-grid" class="empty">Memuatโ€ฆ</div>
</div>
</div>
<!-- KEYWORDS + SUMMARY -->
<div class="main-grid" style="border-bottom:1px solid var(--b1);position:relative;z-index:1">
<div class="panel">
<div class="panel-tag">17 โ€” Keyword & Phrase Extraction</div>
<div id="kw-grid" class="empty">Memuatโ€ฆ</div>
</div>
<div class="panel">
<div class="panel-tag">18 โ€” Ringkasan Otomatis per Platform</div>
<div id="sum-grid" class="empty">Memuatโ€ฆ</div>
</div>
</div>
<!-- FOOTER -->
<footer>
<div class="ft-kw">Kata kunci: <strong id="ft-kw">โ€”</strong></div>
<div class="ft-r">
<a href="/">โ† Analisis Baru</a>
<a href="/download">โ†“ Unduh CSV</a>
<a href="/download/summary" class="ft-r" style="text-decoration:none;font-family:var(--mono);font-size:0.6rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)">โ†“ Summary CSV</a>
</div>
</footer>
<script>
// โ”€โ”€ CHART.JS DEFAULTS โ”€โ”€
Chart.defaults.color = '#4a5568';
Chart.defaults.borderColor = 'rgba(255,255,255,0.05)';
Chart.defaults.font.family = "'JetBrains Mono', monospace";
// โ”€โ”€ HELPERS โ”€โ”€
const pct = (n, t) => Math.round((n / (t || 1)) * 100);
const esc = s => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
const sc = s => { if(!s)return'u'; const l=s.toLowerCase(); return l.includes('pos')?'p':l.includes('neg')?'n':'u'; };
const $ = id => document.getElementById(id);
// โ”€โ”€ LOAD DATA โ”€โ”€
let D = null;
try { const r = sessionStorage.getItem('analysisResult'); if(r) D = JSON.parse(r); } catch(e){}
if(!D && typeof window.__DATA__!=='undefined') D = window.__DATA__;
if(D) render(D);
else $('insight-text').textContent = 'Tidak ada data. Kembali ke halaman utama untuk memulai analisis baru.';
function render(d) {
const data = d.data || [];
const crossData = d.cross_platform || {};
const confStats = d.conf_stats || {};
const expSum = d.export_summary || {};
const words = d.top_words || [];
const topics= d.topics || [];
const clust = d.clusters || [];
const hoax = d.hoax || [];
const botN = d.bot_network || { nodes:[], edges:[], bots:[] };
const trend = d.trend || 'โ€”';
const kw = d.keyword || 'โ€”';
const src = d.source || 'all';
const pos = data.filter(x=>x.sentiment==='Positive').length;
const neg = data.filter(x=>x.sentiment==='Negative').length;
const neu = data.filter(x=>x.sentiment==='Neutral').length;
const tot = data.length || 1;
// HEADER
$('h-kw').textContent = kw;
$('h-total').textContent = data.length;
const srcLabel = {
'all': 'All (5 Sources)', 'youtube': 'YouTube',
'reddit': 'Reddit', 'instagram': 'Instagram',
'tiktok': 'TikTok', 'news': 'Google News',
'youtube,tiktok': 'YT+TT', 'instagram,tiktok': 'IG+TT',
'tiktok,news': 'TT+News', 'youtube,news': 'YT+News',
'youtube,instagram': 'YT+IG', 'reddit,news': 'RD+News'
};
$('h-src').textContent = srcLabel[src] || src;
$('ft-kw').textContent = kw;
// METRICS
$('m-total').textContent = data.length;
$('m-pos').textContent = pos;
$('m-neg').textContent = neg;
$('m-neu').textContent = neu;
$('m-pos-p').textContent = pct(pos,tot)+'%';
$('m-neg-p').textContent = pct(neg,tot)+'%';
$('m-neu-p').textContent = pct(neu,tot)+'%';
// TREND โ€” sekarang object, bukan string
const tEl = $('m-trend');
const tObj = (typeof trend === 'object' && trend !== null) ? trend : { label: trend, dominant: '', confidence: 0 };
tEl.textContent = tObj.label || trend;
const isUp = (tObj.label || '').toLowerCase().includes('positif');
tEl.className = 'metric-n ' + (isUp ? 'c-up' : 'c-dn');
// DIST BAR
setTimeout(() => {
$('ds-p').style.width = pct(pos,tot)+'%';
$('ds-n').style.width = pct(neg,tot)+'%';
$('ds-u').style.width = pct(neu,tot)+'%';
}, 100);
$('dist-info').textContent = `${pct(pos,tot)}% Pos ยท ${pct(neg,tot)}% Neg ยท ${pct(neu,tot)}% Neu`;
// PIE
new Chart($('c-pie').getContext('2d'), {
type: 'doughnut',
data: {
labels: ['Positif','Negatif','Netral'],
datasets: [{
data: [pos, neg, neu],
backgroundColor: ['rgba(34,197,94,0.85)','rgba(239,68,68,0.85)','rgba(148,163,184,0.85)'],
borderColor: ['rgba(34,197,94,0.2)','rgba(239,68,68,0.2)','rgba(148,163,184,0.2)'],
borderWidth: 1, hoverOffset: 6
}]
},
options: {
cutout: '68%', responsive: true, maintainAspectRatio: false,
plugins: {
legend: { position:'right', labels:{ font:{size:11}, color:'#8892a4', padding:16, usePointStyle:true, pointStyleWidth:8 } },
tooltip: { callbacks: { label: ctx => ` ${ctx.label}: ${ctx.parsed} (${pct(ctx.parsed,tot)}%)` } }
}
}
});
// LINE
const roll = (arr, n=5) => arr.map((_,i)=>{ const sl=arr.slice(Math.max(0,i-n),i+1); return sl.reduce((a,b)=>a+b,0)/sl.length; });
const pl = data.map(x=>x.sentiment==='Positive'?1:0);
const nl = data.map(x=>x.sentiment==='Negative'?1:0);
const ul = data.map(x=>x.sentiment==='Neutral'?1:0);
new Chart($('c-line').getContext('2d'), {
type: 'line',
data: {
labels: data.map((_,i)=>i+1),
datasets: [
{ label:'Positif', data:roll(pl), borderColor:'rgba(34,197,94,0.8)', backgroundColor:'rgba(34,197,94,0.06)', borderWidth:1.5, tension:0.4, pointRadius:0, fill:true },
{ label:'Negatif', data:roll(nl), borderColor:'rgba(239,68,68,0.8)', backgroundColor:'rgba(239,68,68,0.06)', borderWidth:1.5, tension:0.4, pointRadius:0, fill:true },
{ label:'Netral', data:roll(ul), borderColor:'rgba(148,163,184,0.5)',backgroundColor:'rgba(148,163,184,0.03)',borderWidth:1, tension:0.4, pointRadius:0, fill:true },
]
},
options: {
responsive: true, maintainAspectRatio: false,
interaction: { mode:'index', intersect:false },
scales: {
x: { ticks:{ maxTicksLimit:12, font:{size:9} }, grid:{ color:'rgba(255,255,255,0.04)' } },
y: { ticks:{ font:{size:9} }, grid:{ color:'rgba(255,255,255,0.04)' } }
},
plugins: { legend:{ labels:{ font:{size:10}, color:'#8892a4', usePointStyle:true } } }
}
});
// WORDS
const wg = $('words');
if(words.length) {
const mx = words[0].count || 1;
wg.innerHTML = words.map(w=>{
const sz = 0.65 + (w.count/mx)*0.95;
const op = 0.45 + (w.count/mx)*0.55;
return `<span class="w-chip" style="font-size:${sz}rem;opacity:${op}" title="${w.count}x">${w.word}</span>`;
}).join('');
} else wg.innerHTML = '<span class="empty">Tidak ada data kata.</span>';
// TABLE โ€” dengan confidence score
const tb = $('tbl');
if(data.length) {
tb.innerHTML = data.slice(0,50).map((r,i)=>{
const conf = r.confidence ? Math.round(r.confidence*100)+'%' : 'โ€”';
const confColor = r.confidence >= 0.8 ? 'var(--pos)' : r.confidence >= 0.6 ? 'var(--gold)' : 'var(--neg)';
return `<tr>
<td style="color:var(--muted)">${i+1}</td>
<td><span class="src-badge">${esc(r.source||'โ€”')}</span></td>
<td style="max-width:240px;color:var(--m2)">${esc((r.text||'').substring(0,90))}</td>
<td><span class="tag tag-${sc(r.sentiment)}">${r.sentiment||'โ€”'}</span></td>
<td style="font-family:var(--mono);font-size:0.6rem;color:${confColor}">${conf}</td>
</tr>`;
}).join('');
} else tb.innerHTML = '<tr><td colspan="5" class="empty" style="padding:.75rem">Tidak ada data.</td></tr>';
// TOPICS
const tg = $('topics');
if(topics.length) {
tg.innerHTML = topics.map((t,i)=>`
<div class="topic-card">
<div class="topic-card-h"><span class="topic-num">Topik ${i+1}</span><span class="topic-dot"></span></div>
<div class="topic-words">${(Array.isArray(t)?t:[]).map(w=>`<span class="tw">${w}</span>`).join('')}</div>
</div>`).join('');
} else tg.innerHTML = '<span class="empty">Data tidak cukup untuk topic modeling.</span>';
// CLUSTERS
const cg = $('clusters');
if(clust.length) {
cg.innerHTML = clust.map(c=>`
<div class="cl-item">
<div class="cl-head">Kluster ${c.cluster}</div>
${(c.samples||[]).slice(0,2).map(s=>`<div class="cl-text">"${esc(s.substring(0,85))}โ€ฆ"</div>`).join('')}
</div>`).join('');
} else cg.innerHTML = '<span class="empty">Data tidak cukup untuk clustering.</span>';
// HOAX โ€” dengan confidence score dan method
const hg = $('hoax');
if(hoax.length) {
hg.innerHTML = hoax.map(h=>{
const conf = h.confidence ? Math.round(h.confidence*100)+'%' : '';
const method = h.method === 'ml' ? 'ยทML' : 'ยทKW';
return `<div class="hoax-item">
<span class="h-badge ${h.label==='Hoax'?'hb-hoax':'hb-norm'}">${h.label}</span>
<span class="h-text">${esc((h.text||'').substring(0,80))}</span>
${conf ? `<span style="font-family:var(--mono);font-size:0.5rem;color:var(--muted);white-space:nowrap;margin-left:auto">${conf}${method}</span>` : ''}
</div>`;
}).join('');
} else hg.innerHTML = '<span class="empty">Tidak ada konten terdeteksi.</span>';
// BOT
const bg = $('bots');
const bots = (botN.bots||[]);
if(bots.length) {
bg.innerHTML = bots.slice(0,8).map(b=>`
<div class="bot-item">
<span class="bot-id">Node ${b.node}</span>
<div class="bot-bar"><div class="bot-fill" style="width:${b.score*100}%"></div></div>
<span class="bot-score">${b.score}</span>
</div>`).join('');
} else bg.innerHTML = '<span class="empty">Tidak ada bot terdeteksi.</span>';
// INSIGHT โ€” gunakan trend object
const avgConf = data.length ? (data.reduce((s,r)=>s+(r.confidence||0),0)/data.length*100).toFixed(1) : 0;
const trendLabel = tObj.label || String(trend);
const trendSum = tObj.summary || trendLabel;
const polarity = tObj.polarity !== undefined ? tObj.polarity : 'โ€”';
$('insight-text').innerHTML = `Dari <em>${data.length}</em> komentar yang dianalisis, opini publik terhadap "<em>${esc(kw)}</em>" bersifat <em>${trendLabel.toLowerCase()}</em>. ${trendSum}`;
$('insight-meta').innerHTML = `Model: IndoBERT (w11wo/indonesian-roberta-base)<br>Avg. confidence: ${avgConf}% ยท Polarisasi: ${polarity}<br>${new Date().toLocaleDateString('id-ID',{weekday:'long',year:'numeric',month:'long',day:'numeric'})}`;
// IMAGES
loadImg('static/wordcloud.png','wc-box');
loadImg('static/heatmap.png','hm-box');
// โ”€โ”€ NEW FEATURES โ”€โ”€
renderNewFeatures(d);
// โ”€โ”€ CROSS-PLATFORM ANALYSIS โ”€โ”€
const platforms = crossData.platforms || {};
if (Object.keys(platforms).length >= 2) {
document.getElementById('cross-section').style.display = '';
// insights
const ins = crossData.insights || [];
const insEl = document.getElementById('cross-insights');
insEl.innerHTML = ins.length
? ins.map(i => `<div style="font-family:var(--mono);font-size:0.65rem;color:var(--m2);line-height:1.6;margin-bottom:.4rem">โ€ข ${esc(i)}</div>`).join('')
: '<span class="empty">Hanya satu platform โ€” tidak ada perbandingan.</span>';
// pairwise
const pw = crossData.pairwise || [];
const pwEl = document.getElementById('cross-pairwise');
pwEl.innerHTML = pw.length
? pw.map(p => `<div style="font-family:var(--mono);font-size:0.62rem;color:var(--m2);line-height:1.7">
<span style="color:var(--accent)">${esc(p.platform_a)}</span> vs
<span style="color:var(--accent)">${esc(p.platform_b)}</span>:
selisih positif <strong style="color:var(--text)">${p.pos_diff}%</strong>
</div>`).join('')
: '<span class="empty">โ€”</span>';
loadImg('static/comparative.png', 'cross-img-box');
}
// โ”€โ”€ CONFIDENCE STATS โ”€โ”€
const byClass = confStats.by_class || {};
const clsMap = [
['Positive','cs-pos','var(--pos)'],
['Negative','cs-neg','var(--neg)'],
['Neutral', 'cs-neu','var(--neu)'],
['Uncertain','cs-unc','var(--gold)'],
];
clsMap.forEach(([cls, elId, color]) => {
const s = byClass[cls] || {};
document.getElementById(elId).innerHTML = `
<div style="font-family:var(--mono);font-size:0.5rem;letter-spacing:.12em;text-transform:uppercase;color:${color};margin-bottom:.5rem">${cls}</div>
<div style="font-size:1.6rem;font-weight:800;color:${color};letter-spacing:-0.03em">${s.count||0}</div>
<div style="font-family:var(--mono);font-size:0.58rem;color:var(--muted);margin-top:.35rem">
mean: <b style="color:var(--m2)">${s.mean||0}</b><br>
std: <b style="color:var(--m2)">${s.std||0}</b>
</div>`;
});
const buckets = confStats.buckets || {};
const bEl = document.getElementById('conf-buckets');
bEl.innerHTML = Object.entries(buckets).map(([k,v]) => `
<div style="font-family:var(--mono);font-size:0.6rem;background:var(--s2);border:1px solid var(--b2);border-radius:6px;padding:.4rem .8rem;color:var(--m2)">
${k}: <b style="color:var(--text)">${v}</b>
</div>`).join('');
}
function loadImg(src, boxId) {
const img = new Image();
img.onload = () => {
img.style.width='100%'; img.style.borderRadius='6px';
$(boxId).innerHTML = ''; $(boxId).appendChild(img);
};
img.src = '/' + src + '?t=' + Date.now();
}
// โ”€โ”€ ABSA โ”€โ”€
function renderABSA(absa) {
const el = $('absa-grid');
const top = absa.top_aspects || [];
if (!top.length) { el.innerHTML = '<span class="empty">Tidak ada aspek terdeteksi.</span>'; return; }
const COLORS = {Positive:'var(--pos)',Negative:'var(--neg)',Neutral:'var(--neu)',Uncertain:'var(--gold)'};
el.innerHTML = `
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem">
${top.map(a => `
<div style="background:var(--s2);border:1px solid var(--b2);border-radius:7px;padding:0.75rem 1rem">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.4rem">
<span style="font-weight:700;font-size:0.78rem;text-transform:capitalize">${esc(a.aspect)}</span>
<span style="font-family:var(--mono);font-size:0.55rem;padding:0.1rem 0.4rem;border-radius:3px;border:1px solid;color:${COLORS[a.dominant]||'var(--neu)'};border-color:${COLORS[a.dominant]||'var(--neu)'}">${a.dominant}</span>
</div>
<div style="display:flex;gap:3px;height:4px;border-radius:2px;overflow:hidden">
<div style="width:${a.pos_pct}%;background:var(--pos);border-radius:2px"></div>
<div style="width:${a.neg_pct}%;background:var(--neg);border-radius:2px"></div>
<div style="width:${a.neu_pct}%;background:var(--neu);border-radius:2px"></div>
</div>
<div style="font-family:var(--mono);font-size:0.5rem;color:var(--muted);margin-top:0.3rem">${a.total} sebutan ยท ${a.pos_pct}% pos ยท ${a.neg_pct}% neg</div>
</div>`).join('')}
</div>
<div style="font-family:var(--mono);font-size:0.58rem;color:var(--muted);margin-top:0.75rem">
${absa.aspects_found || 0} aspek ditemukan dari ${absa.total_texts_analyzed || 0} komentar
</div>`;
}
// โ”€โ”€ NER โ”€โ”€
function renderNER(ner) {
const el = $('ner-grid');
const top = ner.top_entities || [];
if (!top.length) { el.innerHTML = '<span class="empty">Tidak ada entitas terdeteksi.</span>'; return; }
const TYPE_COLOR = {PER:'#4f9cf9',ORG:'#7b61ff',LOC:'#22c55e',PROD:'#f59e0b',EVENT:'#ef4444',MISC:'#94a3b8'};
const TYPE_LABEL = {PER:'Tokoh',ORG:'Institusi',LOC:'Lokasi',PROD:'Produk',EVENT:'Kejadian',MISC:'Lainnya'};
el.innerHTML = `
<div style="display:flex;flex-wrap:wrap;gap:0.4rem;margin-bottom:1rem">
${top.map(e => `
<div style="display:flex;align-items:center;gap:0.4rem;background:var(--s2);border:1px solid var(--b2);border-radius:6px;padding:0.3rem 0.65rem">
<span style="font-family:var(--mono);font-size:0.48rem;padding:0.1rem 0.35rem;border-radius:3px;background:${TYPE_COLOR[e.type]||'#94a3b8'}22;color:${TYPE_COLOR[e.type]||'#94a3b8'};border:1px solid ${TYPE_COLOR[e.type]||'#94a3b8'}44">${TYPE_LABEL[e.type]||e.type}</span>
<span style="font-size:0.72rem;font-weight:600">${esc(e.entity)}</span>
<span style="font-family:var(--mono);font-size:0.55rem;color:var(--muted)">${e.count}ร—</span>
</div>`).join('')}
</div>
<div style="font-family:var(--mono);font-size:0.58rem;color:var(--muted)">${ner.total_entities||0} entitas unik ยท ${ner.total_mentions||0} total sebutan</div>`;
}
// โ”€โ”€ STANCE โ”€โ”€
function renderStance(stance) {
const el = $('stance-grid');
const c = stance.counts || {};
const total = (c.Favor||0) + (c.Against||0) + (c.Neutral||0) || 1;
el.innerHTML = `
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:0.5rem;margin-bottom:1rem">
${[['Favor','var(--pos)','Mendukung'],['Against','var(--neg)','Menolak'],['Neutral','var(--neu)','Netral']].map(([s,color,label]) => `
<div style="background:var(--s2);border:1px solid var(--b2);border-radius:7px;padding:0.75rem;text-align:center">
<div style="font-size:1.6rem;font-weight:800;color:${color};letter-spacing:-0.03em">${c[s]||0}</div>
<div style="font-family:var(--mono);font-size:0.5rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-top:0.2rem">${label}</div>
<div style="font-family:var(--mono);font-size:0.6rem;color:${color};margin-top:0.2rem">${Math.round((c[s]||0)/total*100)}%</div>
</div>`).join('')}
</div>
<div style="height:5px;background:var(--s2);border-radius:3px;display:flex;overflow:hidden;gap:2px">
<div style="width:${stance.favor_pct||0}%;background:var(--pos);border-radius:2px;transition:width 1s ease"></div>
<div style="width:${stance.against_pct||0}%;background:var(--neg);border-radius:2px;transition:width 1s ease"></div>
<div style="width:${stance.neutral_pct||0}%;background:var(--neu);border-radius:2px;transition:width 1s ease"></div>
</div>
<div style="font-family:var(--mono);font-size:0.58rem;color:var(--muted);margin-top:0.6rem">
Dominan: <b style="color:var(--text)">${stance.dominant||'โ€”'}</b> ยท Target: <b style="color:var(--accent)">${esc(stance.target||'โ€”')}</b>
</div>`;
}
// โ”€โ”€ EMOTIONS โ”€โ”€
function renderEmotions(emotions) {
const el = $('emotion-grid');
const dist = emotions.distribution || {};
const EMO_ICON = {joy:'๐Ÿ˜Š',anger:'๐Ÿ˜ก',sadness:'๐Ÿ˜ข',fear:'๐Ÿ˜จ',surprise:'๐Ÿ˜ฎ',disgust:'๐Ÿคข',neutral:'๐Ÿ˜'};
const EMO_COLOR = {joy:'#22c55e',anger:'#ef4444',sadness:'#4f9cf9',fear:'#f59e0b',surprise:'#7b61ff',disgust:'#94a3b8',neutral:'#5a6070'};
const entries = Object.entries(dist).sort((a,b)=>(b[1].count||0)-(a[1].count||0));
el.innerHTML = `
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:0.4rem;margin-bottom:0.75rem">
${entries.slice(0,8).map(([emo,data]) => `
<div style="background:var(--s2);border:1px solid var(--b2);border-radius:7px;padding:0.6rem;text-align:center;${emotions.dominant===emo?'border-color:'+EMO_COLOR[emo]||'var(--accent)':''}">
<div style="font-size:1.2rem">${EMO_ICON[emo]||'โ“'}</div>
<div style="font-family:var(--mono);font-size:0.5rem;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-top:0.2rem">${emo}</div>
<div style="font-size:1rem;font-weight:700;color:${EMO_COLOR[emo]||'var(--text)'}">${data.count||0}</div>
<div style="font-family:var(--mono);font-size:0.5rem;color:var(--muted)">${data.pct||0}%</div>
</div>`).join('')}
</div>
<div style="font-family:var(--mono);font-size:0.58rem;color:var(--muted)">
Emosi dominan: <b style="color:var(--text)">${emotions.dominant||'โ€”'}</b> ยท ${emotions.emotional_pct||0}% bersifat emosional
</div>`;
}
// โ”€โ”€ KEYWORDS โ”€โ”€
function renderKeywords(keywords) {
const el = $('kw-grid');
if (!keywords.length) { el.innerHTML = '<span class="empty">Tidak ada frasa kunci.</span>'; return; }
const maxScore = keywords[0].score || 1;
const TYPE_COLOR = {'word':'var(--accent)','phrase':'var(--a2)','multi-phrase':'var(--gold)'};
el.innerHTML = `
<div style="display:flex;flex-wrap:wrap;gap:0.4rem">
${keywords.map(kw => {
const sz = 0.62 + (kw.score/maxScore)*0.5;
const color = TYPE_COLOR[kw.type]||'var(--m2)';
return `<span style="font-family:var(--mono);font-size:${sz}rem;background:var(--s2);border:1px solid var(--b2);border-radius:5px;padding:0.2rem 0.55rem;color:${color};cursor:default;transition:border-color .15s" title="${kw.type} ยท ${kw.frequency}ร— ยท score:${kw.score}">${esc(kw.phrase)}</span>`;
}).join('')}
</div>
<div style="font-family:var(--mono);font-size:0.55rem;color:var(--muted);margin-top:0.75rem;display:flex;gap:1rem">
<span><span style="color:var(--accent)">โ– </span> Kata</span>
<span><span style="color:var(--a2)">โ– </span> Frasa 2 kata</span>
<span><span style="color:var(--gold)">โ– </span> Frasa 3+ kata</span>
</div>`;
}
// โ”€โ”€ SUMMARIES โ”€โ”€
function renderSummaries(summaries) {
const el = $('sum-grid');
if (!summaries || !Object.keys(summaries).length) {
el.innerHTML = '<span class="empty">Tidak ada ringkasan.</span>'; return;
}
const overall = summaries['_overall'];
const platforms = Object.entries(summaries).filter(([k]) => k !== '_overall');
el.innerHTML = `
${overall ? `
<div style="background:var(--s2);border:1px solid rgba(79,156,249,0.2);border-radius:7px;padding:1rem;margin-bottom:0.75rem">
<div style="font-family:var(--mono);font-size:0.5rem;letter-spacing:.15em;text-transform:uppercase;color:var(--accent);margin-bottom:0.5rem">Ringkasan Keseluruhan</div>
<div style="font-size:0.8rem;line-height:1.65;color:var(--text)">${esc(overall.summary||'')}</div>
<div style="font-family:var(--mono);font-size:0.52rem;color:var(--muted);margin-top:0.4rem">${overall.text_count} komentar dirangkum</div>
</div>` : ''}
${platforms.map(([src,data]) => `
<div style="background:var(--s2);border:1px solid var(--b2);border-radius:7px;padding:0.75rem;margin-bottom:0.5rem">
<div style="font-family:var(--mono);font-size:0.5rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-bottom:0.4rem">${esc(src.capitalize?.() || src)}</div>
<div style="font-size:0.75rem;line-height:1.6;color:var(--m2)">${esc(data.summary||'')}</div>
</div>`).join('')}`;
}
// โ”€โ”€ CALL ALL NEW RENDERS โ”€โ”€
function renderNewFeatures(d) {
if (d.absa) renderABSA(d.absa);
if (d.ner) renderNER(d.ner);
if (d.stance) renderStance(d.stance);
if (d.emotions) renderEmotions(d.emotions);
if (d.keywords) renderKeywords(d.keywords);
if (d.summaries)renderSummaries(d.summaries);
}
</script>
</body>
</html>