gpt5-mini-v2 / index.html
thucdangvan020999's picture
Upload index.html with huggingface_hub
1423d2c verified
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Vietnam Economic Growth Report β€” 2025 Dashboard</title>
<!-- Icon library -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<style>
/* ---------------------------
CSS Variables & Base
--------------------------- */
:root{
--bg:#0f1724;
--card:#0b1320;
--muted:#9aa6b2;
--accent:#0ea5a4;
--accent-2:#6ee7b7;
--glass: rgba(255,255,255,0.03);
--glass-2: rgba(255,255,255,0.02);
--success: #10b981;
--danger:#f97373;
--max-width:1200px;
--radius:12px;
--ff-sans: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
color-scheme: dark;
}
html,body{height:100%}
body{
margin:0;
font-family:var(--ff-sans);
background:linear-gradient(180deg,#071025 0%, #071427 50%, #04121a 100%);
color: #e6eef3;
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
font-size:clamp(14px, 1.6vw, 16px);
line-height:1.45;
scroll-behavior:smooth;
padding:24px;
}
/* ---------------------------
Layout
--------------------------- */
.app {
max-width: var(--max-width);
margin: 0 auto;
display: grid;
grid-template-columns: 1fr;
gap: 18px;
}
header{
display:flex;
gap:12px;
align-items:center;
justify-content:space-between;
background:linear-gradient(180deg, rgba(255,255,255,0.02), transparent);
padding:14px;
border-radius:var(--radius);
border:1px solid rgba(255,255,255,0.03);
box-shadow: 0 6px 18px rgba(3,8,15,0.6);
}
.brand{
display:flex;
gap:12px;
align-items:center;
}
.logo{
width:56px;height:56px;
border-radius:10px;
background:linear-gradient(135deg,var(--accent),#065f66);
display:flex;align-items:center;justify-content:center;
font-weight:700;font-size:20px;
box-shadow: 0 6px 18px rgba(0,0,0,0.4);
}
.title{
display:flex;flex-direction:column;
}
.title h1{margin:0;font-size:clamp(18px,2.4vw,22px)}
.title p{margin:0;color:var(--muted);font-size:13px}
.header-actions{display:flex;gap:8px;align-items:center;}
.btn {
background:var(--glass);
border:1px solid rgba(255,255,255,0.04);
color:inherit;
padding:8px 12px;border-radius:10px;
display:inline-flex;align-items:center;gap:8px;
cursor:pointer;font-size:14px;
}
.btn.primary{background:linear-gradient(90deg,var(--accent),var(--accent-2)); color:#00201f;border:none}
.btn.ghost{background:transparent;border:1px solid rgba(255,255,255,0.04)}
.btn.small{padding:6px 8px;font-size:13px;border-radius:8px}
.icon {opacity:0.9}
/* ---------------------------
Top progress bar
--------------------------- */
.reading-progress{
position:fixed;left:0;right:0;top:0;height:4px;background:linear-gradient(90deg,var(--accent),var(--accent-2));
transform-origin:left center;transform:scaleX(0); z-index:999;
border-bottom:1px solid rgba(255,255,255,0.02)
}
/* ---------------------------
Content Grid
--------------------------- */
.content {
display:grid;
grid-template-columns: 1fr;
gap:16px;
}
/* TOC & Main container */
.panel {
display:grid;
grid-template-columns: 260px 1fr;
gap:16px;
}
.toc{
position:sticky; top:86px;
align-self:start;
background:var(--glass);
padding:12px;border-radius:12px;
border:1px solid rgba(255,255,255,0.03);
height:calc(100vh - 110px);
overflow:auto;
}
.toc h3{margin:0 0 8px 0;font-size:14px}
.toc ul{list-style:none;padding:0;margin:0;display:grid;gap:6px}
.toc a{
color:var(--muted);text-decoration:none;padding:8px;border-radius:8px;display:flex;align-items:center;gap:8px;
}
.toc a.active{background:linear-gradient(90deg, rgba(14,165,164,0.12), rgba(110,231,183,0.04));color:var(--accent-2)}
.toc .search{
display:flex;gap:8px;margin-bottom:8px;
}
.toc input{
background:transparent;border:1px solid rgba(255,255,255,0.03);padding:8px;border-radius:8px;color:inherit;width:100%;
}
main.report{
padding:14px;
border-radius:12px;background:linear-gradient(180deg, rgba(255,255,255,0.02), transparent);
border:1px solid rgba(255,255,255,0.03);
}
section.card{
background:linear-gradient(180deg, rgba(255,255,255,0.01), rgba(255,255,255,0.01));
border-radius:12px;padding:14px;margin-bottom:14px;
border:1px solid rgba(255,255,255,0.02);
}
.kpi-grid{
display:grid;
grid-template-columns: repeat(2,1fr);
gap:10px;
}
.kpi{
background:var(--card);
border-radius:10px;padding:12px;border:1px solid rgba(255,255,255,0.02);
display:flex;align-items:center;justify-content:space-between;
gap:12px;
}
.kpi .meta{display:flex;flex-direction:column}
.kpi .value{font-weight:700;font-size:clamp(18px,2.4vw,22px)}
.spark{
width:100px;height:36px; display:block;
}
/* Charts layout */
.charts{
display:grid;gap:12px;
grid-template-columns: 1fr;
}
.chart{
padding:12px;background:linear-gradient(180deg, rgba(255,255,255,0.01), transparent);
border-radius:10px;border:1px solid rgba(255,255,255,0.02);
}
.chart h4{margin:0 0 8px 0;font-size:15px}
.svg-wrap{width:100%;height:220px;aspect-ratio:4/1;display:block}
/* Table */
.table-wrap{overflow:auto;border-radius:10px;border:1px solid rgba(255,255,255,0.03)}
table{width:100%;border-collapse:collapse;font-size:14px}
th,td{padding:10px;text-align:left;border-bottom:1px dashed rgba(255,255,255,0.02)}
th{cursor:pointer;background:linear-gradient(180deg, rgba(255,255,255,0.01), transparent)}
th.sorted.asc::after{content:" β–²";font-size:12px;color:var(--muted)}
th.sorted.desc::after{content:" β–Ό";font-size:12px;color:var(--muted)}
tr:hover td{background:linear-gradient(90deg, rgba(255,255,255,0.01), rgba(255,255,255,0.01))}
/* Collapsible */
.collapsible{border-radius:10px;overflow:hidden;}
.collapsible .summary{
display:flex;align-items:center;justify-content:space-between;padding:10px;background:transparent;cursor:pointer;
}
.collapsible .body{padding:12px;border-top:1px solid rgba(255,255,255,0.02);display:none}
/* references */
.refs{font-size:13px;color:var(--muted);display:grid;gap:6px}
.cite{display:flex;gap:8px;align-items:flex-start}
/* Footer */
footer{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;color:var(--muted);font-size:13px}
/* Mobile-first adjustments */
@media (min-width:768px){
.app{padding:30px}
.content{grid-template-columns:1fr}
.panel{grid-template-columns: 280px 1fr}
.charts{grid-template-columns: repeat(2,1fr)}
.kpi-grid{grid-template-columns: repeat(4,1fr)}
}
@media (min-width:1024px){
.panel{grid-template-columns: 300px 1fr}
.charts{grid-template-columns: 1fr 1fr 1fr}
.kpi-grid{grid-template-columns: repeat(4,1fr)}
}
/* container query example */
.chart:where(.chart){
container-type: inline-size;
}
@container (min-width:425px){
.chart .svg-wrap{height:260px}
}
/* utility */
.muted{color:var(--muted)}
.tag{background:rgba(255,255,255,0.03);padding:6px 8px;border-radius:8px;font-size:13px}
.small{font-size:13px;color:var(--muted)}
.flex{display:flex;gap:8px;align-items:center}
.right{margin-left:auto}
.hidden{display:none}
/* highlight for search results */
mark{background:linear-gradient(90deg,#ffe8b3,#ffefc9);color:#302900;padding:2px 4px;border-radius:4px}
</style>
</head>
<body>
<div class="reading-progress" id="readingProgress" aria-hidden="true"></div>
<div class="app">
<header>
<div class="brand">
<div class="logo" aria-hidden="true">VN</div>
<div class="title">
<h1>Vietnam Economic Growth Report β€” 2025</h1>
<p class="muted">Interactive dashboard & insights β€’ Updated 2025 Q2</p>
</div>
</div>
<div class="header-actions">
<button class="btn small ghost" id="toggleTheme" title="Toggle theme"><i class="fa-solid fa-circle-half-stroke icon"></i> Theme</button>
<button class="btn small" id="copySummary" title="Copy Executive Summary"><i class="fa-solid fa-copy icon"></i> Copy Summary</button>
<button class="btn primary" id="exportCSV" title="Export dataset as CSV"><i class="fa-solid fa-download icon"></i> Export CSV</button>
<button class="btn ghost" id="printBtn" title="Print / Export PDF"><i class="fa-solid fa-print icon"></i> Print</button>
</div>
</header>
<div class="content">
<div class="panel">
<nav class="toc" aria-label="Table of contents">
<div class="search">
<input id="globalSearch" placeholder="Search report..." aria-label="Search report" />
<button class="btn small" id="clearSearch" title="Clear search"><i class="fa-solid fa-xmark"></i></button>
</div>
<h3>Contents</h3>
<ul id="tocList">
<li><a href="#executive" data-target="executive"><i class="fa-solid fa-star"></i> Executive Summary</a></li>
<li><a href="#methodology" data-target="methodology"><i class="fa-solid fa-wrench"></i> Methodology</a></li>
<li><a href="#indicators" data-target="indicators"><i class="fa-solid fa-chart-line"></i> Key Indicators</a></li>
<li><a href="#sectoral" data-target="sectoral"><i class="fa-solid fa-industry"></i> Sectoral Analysis</a></li>
<li><a href="#retail" data-target="retail"><i class="fa-solid fa-cart-shopping"></i> Retail Performance</a></li>
<li><a href="#challenges" data-target="challenges"><i class="fa-solid fa-triangle-exclamation"></i> Challenges & Risks</a></li>
<li><a href="#historical" data-target="historical"><i class="fa-solid fa-calendar-lines-pen"></i> Historical Comparison</a></li>
<li><a href="#outlook" data-target="outlook"><i class="fa-solid fa-eye"></i> Outlook & Projections</a></li>
<li><a href="#references" data-target="references"><i class="fa-solid fa-book"></i> Sources & References</a></li>
<li><a href="#appendix" data-target="appendix"><i class="fa-solid fa-table-list"></i> Appendix (Data)</a></li>
</ul>
<div style="margin-top:12px">
<div class="tag">Last viewed: <span id="lastViewed" class="small muted">β€”</span></div>
</div>
</nav>
<main class="report" id="report">
<!-- Executive Summary -->
<section id="executive" class="card" tabindex="0">
<div style="display:flex;gap:12px;align-items:flex-start">
<div>
<h2 style="margin:0">Executive Summary</h2>
<p class="muted small" style="margin-top:4px">Compact overview of Vietnam's economic performance in 2025</p>
</div>
<div class="right small muted">Updated: Q2 2025</div>
</div>
<div style="display:flex;flex-direction:column;gap:8px;margin-top:10px">
<p id="execSummaryText">
Vietnam's economy shows robust growth in 2025. GDP expanded 7.96% year-on-year in Q2 and 7.52% in H1, the strongest mid-year performance since 2011. Growth is led by services and manufacturing, with strong FDI inflows, controlled inflation, and low unemployment supporting domestic demand despite external trade tensions and tariff pressures.
</p>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<span class="tag">GDP H1: 7.52%</span>
<span class="tag">Q2 YoY: 7.96%</span>
<span class="tag">Inflation (Jun): 3.57%</span>
<span class="tag">Unemployment Q1: 2.20%</span>
</div>
<div class="collapsible" id="execNotes">
<div class="summary">
<div class="muted">Summary & Quick Actions</div>
<div class="flex">
<button class="btn small" id="copyExec"><i class="fa-solid fa-copy"></i> Copy</button>
<button class="btn small" id="exportExecJSON"><i class="fa-solid fa-file-export"></i> Export JSON</button>
<button class="btn small" data-toggle="exec" id="toggleExecDetails"><i class="fa-solid fa-chevron-down"></i></button>
</div>
</div>
<div class="body" id="execBody">
<p class="muted small">Key drivers include strong FDI inflows (US$21.51bn in H1), resilient services and manufacturing activity, and supportive fiscal measures. Risks remain from global trade tensions and policy uncertainties abroad.</p>
<div style="display:flex;gap:8px;margin-top:8px">
<button class="btn small ghost" id="focusGDP">Highlight GDP</button>
<button class="btn small ghost" id="focusFDI">Highlight FDI</button>
</div>
</div>
</div>
</div>
</section>
<!-- Methodology -->
<section id="methodology" class="card" tabindex="0">
<h3>Methodology</h3>
<p class="muted small">This dashboard consolidates official statistics (General Statistics Office), international institution forecasts (World Bank, IMF, ADB), and third-party aggregators. Charts are derived from extracted time series and reported quarter/year figures. Projections represent cited sources and government targets.</p>
<div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap">
<div class="tag">Sources cross-checked</div>
<div class="tag">Data normalized & rounded</div>
<div class="tag">Interactive β€” explore filters</div>
</div>
</section>
<!-- Key Indicators -->
<section id="indicators" class="card" tabindex="0">
<h3>Key Economic Indicators β€” 2025</h3>
<div class="kpi-grid" style="margin-top:12px">
<div class="kpi">
<div class="meta">
<div class="small muted">GDP Q2 (YoY)</div>
<div class="value">7.96%</div>
<div class="small muted">H1: 7.52% β€’ Q1: 6.9%</div>
</div>
<svg class="spark" id="sparkGDP" viewBox="0 0 100 36" preserveAspectRatio="none"></svg>
</div>
<div class="kpi">
<div class="meta">
<div class="small muted">Inflation (Jun)</div>
<div class="value">3.57%</div>
<div class="small muted">May: 3.24%</div>
</div>
<svg class="spark" id="sparkInflation" viewBox="0 0 100 36" preserveAspectRatio="none"></svg>
</div>
<div class="kpi">
<div class="meta">
<div class="small muted">Unemployment (Q1)</div>
<div class="value">2.20%</div>
<div class="small muted">Q4 2024: 2.22%</div>
</div>
<svg class="spark" id="sparkUnemp" viewBox="0 0 100 36" preserveAspectRatio="none"></svg>
</div>
<div class="kpi">
<div class="meta">
<div class="small muted">FDI H1 (US$)</div>
<div class="value">21.51B</div>
<div class="small muted">Registered (5m): $18.4B</div>
</div>
<svg class="spark" id="sparkFDI" viewBox="0 0 100 36" preserveAspectRatio="none"></svg>
</div>
</div>
<div class="charts" style="margin-top:12px">
<div class="chart" aria-hidden="false">
<h4>GDP Historical (2020–2025) β€” YoY</h4>
<div class="svg-wrap" id="chartGDP"></div>
<div class="small muted" style="margin-top:8px">2025 Q1–Q2 performance vs historical trend</div>
</div>
<div class="chart">
<h4>2025 Forecasts vs Government Target</h4>
<div class="svg-wrap" id="chartForecast"></div>
<div class="small muted" style="margin-top:8px">Comparative outlook (World Bank, ADB, IMF, Government)</div>
</div>
<div class="chart">
<h4>FDI Inflows β€” Recent Trend</h4>
<div class="svg-wrap" id="chartFDI"></div>
<div class="small muted" style="margin-top:8px">First 5 months registered & disbursed capital</div>
</div>
</div>
<div style="display:flex;gap:8px;margin-top:12px;align-items:center">
<div class="tag">Interactive charts β€” hover for details</div>
<div class="small muted right">Tip: Click forecast bars to filter appendix table</div>
</div>
</section>
<!-- Sectoral Analysis -->
<section id="sectoral" class="card" tabindex="0">
<h3>Sectoral Analysis</h3>
<p class="muted small">Services and manufacturing are the primary growth engines. Export-oriented industries remain crucial while the banking sector shows earnings momentum.</p>
<div style="display:grid;grid-template-columns:1fr;gap:12px;margin-top:12px">
<div class="chart">
<h4>Sector Contribution (Simplified)</h4>
<div class="svg-wrap" id="chartSectors"></div>
<div class="small muted" style="margin-top:8px">Proportional contribution to GDP growth (illustrative)</div>
</div>
<div class="chart">
<h4>Banking Sector β€” Earnings & Credit Growth (2025)</h4>
<div class="svg-wrap" id="chartBanking"></div>
<div class="small muted" style="margin-top:8px">Projected 17% earnings increase driven by ~15% credit growth</div>
</div>
</div>
</section>
<!-- Retail -->
<section id="retail" class="card" tabindex="0">
<h3>Retail Performance</h3>
<p class="muted small">Retail sales reached 1.708 quadrillion VND (~US$66.83B) in Q1 2025 β€” a 9.9% YoY increase.</p>
<div class="chart" style="margin-top:12px">
<h4>Retail Sales (Q1 2025) Breakdown</h4>
<div style="display:flex;gap:12px;align-items:center">
<div style="flex:1">
<svg class="svg-wrap" id="chartRetail"></svg>
</div>
<div style="width:220px">
<div class="small muted">Top drivers</div>
<ul style="margin:8px 0 0 0;padding:0;list-style:none;display:grid;gap:6px">
<li><strong>Domestic consumption</strong> β€” robust demand & low unemployment</li>
<li><strong>E-commerce</strong> β€” accelerated growth</li>
<li><strong>Services</strong> β€” tourism & hospitality improvements</li>
</ul>
<div style="margin-top:10px" class="small muted">Retail figure sourced from report and converted to USD at quoted rate.</div>
</div>
</div>
</div>
</section>
<!-- Challenges -->
<section id="challenges" class="card" tabindex="0">
<h3>Challenges & Risk Factors</h3>
<ul class="muted" style="margin-top:8px">
<li>Global trade tensions & US tariffs impacting exports</li>
<li>Geopolitical instability increasing uncertainty</li>
<li>Potential overdependence on FDI β€” inflationary pressures</li>
<li>Macroeconomic trade-offs: growth vs stability and public debt</li>
</ul>
<div style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap">
<button class="btn small ghost" id="riskMitigate">View Risk Mitigation Strategies</button>
<button class="btn small" id="downloadPolicy">Download Policy Note (sample)</button>
</div>
<div class="collapsible" id="mitigation" style="margin-top:8px">
<div class="summary">
<div class="muted">Government mitigation measures</div>
<div class="right small muted">Click to expand</div>
</div>
<div class="body">
<ol class="muted small">
<li>Diversify export markets</li>
<li>Strengthen domestic demand & social spending buffers</li>
<li>Enhance monetary resilience & fiscal space</li>
<li>Attract quality FDI while reducing vulnerability</li>
</ol>
</div>
</div>
</section>
<!-- Historical Comparison -->
<section id="historical" class="card" tabindex="0">
<h3>Historical Comparison</h3>
<p class="muted small">Annual and quarterly context for 2020–2025.</p>
<div style="display:flex;gap:12px;margin-top:10px;flex-direction:column">
<div class="table-wrap">
<table id="historyTable" aria-label="Historical GDP table">
<thead>
<tr>
<th data-key="year">Year / Quarter</th>
<th data-key="gdp">GDP YoY (%)</th>
<th data-key="note">Note</th>
</tr>
</thead>
<tbody>
<tr><td>2020 Q1</td><td>3.21</td><td class="small muted">Pandemic impact</td></tr>
<tr><td>2021 Q1</td><td>4.85</td><td class="small muted"></td></tr>
<tr><td>2022 Q1</td><td>5.42</td><td class="small muted"></td></tr>
<tr><td>2023 Q1</td><td>3.46</td><td class="small muted"></td></tr>
<tr><td>2024 Q1</td><td>5.98</td><td class="small muted"></td></tr>
<tr><td>2025 Q1</td><td>6.93</td><td class="small muted"></td></tr>
<tr><td>Q2 2025</td><td>7.96</td><td class="small muted">Reported</td></tr>
</tbody>
</table>
</div>
<div class="small muted">2024 annual growth: 7.1% β€’ Government target 2025: 8.3–8.5%</div>
</div>
</section>
<!-- Outlook -->
<section id="outlook" class="card" tabindex="0">
<h3>Economic Outlook & Projections</h3>
<p class="muted small">Near-term prospects remain solid but face external headwinds. Government targets are ambitious relative to international institutions.</p>
<div style="display:flex;gap:8px;margin-top:12px;flex-wrap:wrap">
<div class="tag">Support: FDI, low unemployment</div>
<div class="tag">Risks: trade & tariffs</div>
<div class="tag">Policy: fiscal & monetary buffers</div>
</div>
</section>
<!-- References -->
<section id="references" class="card" tabindex="0">
<h3>Sources & References</h3>
<div class="refs" style="margin-top:8px">
<div class="cite"><i class="fa-solid fa-link icon"></i><a href="https://tradingeconomics.com/vietnam/gdp-growth-annual" target="_blank" rel="noopener">Trading Economics β€” Vietnam GDP Annual Growth Rate</a></div>
<div class="cite"><i class="fa-solid fa-link icon"></i><a href="https://www.imf.org/en/Countries/VNM" target="_blank" rel="noopener">International Monetary Fund β€” Vietnam</a></div>
<div class="cite"><i class="fa-solid fa-link icon"></i><a href="https://www.adb.org/countries/viet-nam/main" target="_blank" rel="noopener">Asian Development Bank β€” Vietnam</a></div>
<div class="cite"><i class="fa-solid fa-link icon"></i><a href="https://www.gso.gov.vn/en/" target="_blank" rel="noopener">General Statistics Office β€” Vietnam</a></div>
<div class="cite"><i class="fa-solid fa-link icon"></i><a href="https://vneconomictimes.com/" target="_blank" rel="noopener">Vietnam Economic Times</a></div>
<div class="cite"><i class="fa-solid fa-link icon"></i><a href="https://vietnam-briefing.com/" target="_blank" rel="noopener">Vietnam Briefing</a></div>
</div>
</section>
<!-- Appendix -->
<section id="appendix" class="card" tabindex="0">
<h3>Appendix β€” Dataset & Interactive Table</h3>
<p class="muted small">Filterable, sortable dataset extracted from report. Click headers to sort. Use filter to focus on particular metrics.</p>
<div style="display:flex;gap:8px;margin-top:10px;align-items:center">
<label class="small muted">Filter:</label>
<select id="datasetFilter" class="small">
<option value="all">All</option>
<option value="gdp">GDP</option>
<option value="inflation">Inflation</option>
<option value="fdi">FDI</option>
<option value="retail">Retail</option>
</select>
<button class="btn small" id="downloadCSV">Download CSV</button>
<button class="btn small ghost" id="copyJSON">Copy JSON</button>
<div style="margin-left:auto" class="small muted">Saved view: <span id="savedView">none</span></div>
</div>
<div class="table-wrap" style="margin-top:10px">
<table id="dataTable">
<thead>
<tr>
<th data-key="metric">Metric</th>
<th data-key="period">Period</th>
<th data-key="value">Value</th>
<th data-key="unit">Unit</th>
<th data-key="note">Note</th>
</tr>
</thead>
<tbody>
<!-- Rows populated by JS -->
</tbody>
</table>
</div>
<div style="display:flex;gap:8px;margin-top:8px">
<button class="btn small" id="saveView">Save View</button>
<button class="btn small ghost" id="resetView">Reset</button>
</div>
</section>
<footer>
<div class="muted small">Vietnam Economic Growth Report β€’ Dashboard β€’ 2025</div>
<div class="muted small">Built with HTML, CSS & Vanilla JS β€” Interactive β€’ Exportable</div>
</footer>
</main>
</div>
</div>
</div>
<script>
/* ============================
Data extracted from report
============================ */
const dataset = [
{ metric: 'GDP YoY', period: '2020 Q1', value: 3.21, unit: '%', tag:'gdp', note:'Pandemic period' },
{ metric: 'GDP YoY', period: '2021 Q1', value: 4.85, unit: '%', tag:'gdp', note:'' },
{ metric: 'GDP YoY', period: '2022 Q1', value: 5.42, unit: '%', tag:'gdp', note:'' },
{ metric: 'GDP YoY', period: '2023 Q1', value: 3.46, unit: '%', tag:'gdp', note:'' },
{ metric: 'GDP YoY', period: '2024 Q1', value: 5.98, unit: '%', tag:'gdp', note:'' },
{ metric: 'GDP YoY', period: '2025 Q1', value: 6.93, unit: '%', tag:'gdp', note:'' },
{ metric: 'GDP YoY', period: '2025 Q2', value: 7.96, unit: '%', tag:'gdp', note:'Reported' },
{ metric: 'GDP H1', period: '2025 H1', value: 7.52, unit: '%', tag:'gdp', note:'Highest since 2011' },
{ metric: 'Inflation', period: 'May 2025', value: 3.24, unit: '%', tag:'inflation', note:'' },
{ metric: 'Inflation', period: 'June 2025', value: 3.57, unit: '%', tag:'inflation', note:'Highest so far in 2025' },
{ metric: 'Inflation Forecast (IMF)', period: '2025', value: 2.9, unit: '%', tag:'inflation', note:'IMF forecast' },
{ metric: 'Inflation Forecast (ADB)', period: '2025', value: 4.0, unit: '%', tag:'inflation', note:'ADB forecast' },
{ metric: 'Unemployment', period: 'Q1 2025', value: 2.20, unit:'%', tag:'unemployment', note:'Down from 2.22' },
{ metric: 'FDI Registered (5m)', period: 'First 5 months 2025', value: 18.4, unit:'B USD', tag:'fdi', note:'+51% YoY' },
{ metric: 'FDI Disbursed (5m)', period: 'First 5 months 2025', value: 8.9, unit:'B USD', tag:'fdi', note:'' },
{ metric: 'FDI Total H1', period: '2025 H1', value: 21.51, unit:'B USD', tag:'fdi', note:'+32.6% YoY' },
{ metric: 'Retail Sales', period: 'Q1 2025', value: 1708, unit:'trillion VND', tag:'retail', note:'1.708 quadrillion VND (US$66.83B) β€’ +9.9% YoY' },
{ metric: 'Forecast (World Bank)', period: '2025', value: 5.8, unit:'%', tag:'forecast', note:'' },
{ metric: 'Forecast (ADB)', period: '2025', value: 6.6, unit:'%', tag:'forecast', note:'' },
{ metric: 'Forecast (IMF)', period: '2025', value: 5.2, unit:'%', tag:'forecast', note:'' },
{ metric: 'Government Target', period: '2025', value: 8.4, unit:'%', tag:'forecast', note:'Range 8.3–8.5' },
{ metric: 'Banking Earnings (proj)', period: '2025', value: 17, unit:'%', tag:'banking', note:'Earnings increase' },
{ metric: 'Credit Growth (proj)', period: '2025', value: 15, unit:'%', tag:'banking', note:'System-wide credit' },
];
/* Utilities */
function el(sel){ return document.querySelector(sel) }
function els(sel){ return Array.from(document.querySelectorAll(sel)) }
// Save last viewed section in localStorage
const lastViewedEl = el('#lastViewed');
const savedSection = localStorage.getItem('lastSection');
lastViewedEl.textContent = savedSection || 'β€”';
// Reading progress
const readingProgress = el('#readingProgress');
document.addEventListener('scroll', () => {
const total = document.documentElement.scrollHeight - window.innerHeight;
const pct = total > 0 ? (window.scrollY / total) : 0;
readingProgress.style.transform = `scaleX(${Math.min(1, pct)})`;
}, { passive:true });
// TOC active link via IntersectionObserver
const tocLinks = els('.toc a');
const sections = tocLinks.map(a => document.getElementById(a.dataset.target));
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const id = entry.target.id;
const link = el(`.toc a[data-target="${id}"]`);
if(entry.isIntersecting){
tocLinks.forEach(l=>l.classList.remove('active'));
link && link.classList.add('active');
localStorage.setItem('lastSection', id);
lastViewedEl.textContent = id;
}
});
}, { root: null, rootMargin: '0px 0px -60% 0px', threshold: [0,0.25,0.5,0.9] });
sections.forEach(s=>s && observer.observe(s));
// Smooth scroll when clicking TOC β€” also close mobile TOC in future
tocLinks.forEach(a=>{
a.addEventListener('click', (e)=>{
e.preventDefault();
const target = document.getElementById(a.dataset.target);
target && target.scrollIntoView({behavior:'smooth', block:'start'});
});
});
// Collapsibles
els('.collapsible').forEach(block=>{
const summary = block.querySelector('.summary');
const body = block.querySelector('.body');
summary.addEventListener('click', ()=>{
const open = body.style.display === 'block';
body.style.display = open ? 'none' : 'block';
const iconBtn = summary.querySelector('button[data-toggle]') || summary.querySelector('i.fa-chevron-down');
// toggle icon if exists
const che = summary.querySelector('i.fa-chevron-down, i.fa-chevron-up');
if(che) che.classList.toggle('fa-chevron-down');
if(che) che.classList.toggle('fa-chevron-up');
});
});
// Small helper to draw sparklines
function drawSpark(svgEl, points, color='rgba(110,231,183,0.9)'){
const svg = svgEl;
while(svg.firstChild) svg.removeChild(svg.firstChild);
const w = 100, h = 36;
// normalize points
const max = Math.max(...points), min = Math.min(...points);
const range = max - min || 1;
const path = [];
points.forEach((p,i)=>{
const x = (i/(points.length-1))*w;
const y = h - ((p - min)/range)*h;
path.push(`${i===0?'M':'L'} ${x.toFixed(2)} ${y.toFixed(2)}`);
});
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
p.setAttribute('d', path.join(' '));
p.setAttribute('stroke', color);
p.setAttribute('fill','none');
p.setAttribute('stroke-width','2');
p.setAttribute('stroke-linejoin','round');
p.setAttribute('stroke-linecap','round');
svg.appendChild(p);
// end point circle
const last = points[points.length-1];
const x = w; const y = h - ((last - min)/range)*h;
const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
c.setAttribute('cx', x); c.setAttribute('cy', y);
c.setAttribute('r', 2.6); c.setAttribute('fill', color);
svg.appendChild(c);
}
// Draw sparklines using dataset
drawSpark(el('#sparkGDP'), [3.21,4.85,5.42,3.46,5.98,6.93,7.96], 'rgba(14,165,164,0.95)');
drawSpark(el('#sparkInflation'), [2.5,2.9,3.1,3.24,3.57], '#f59e0b');
drawSpark(el('#sparkUnemp'), [2.7,2.5,2.3,2.22,2.20], '#60a5fa');
drawSpark(el('#sparkFDI'), [10,12,14,18.4,21.51], '#a78bfa');
/* ============================
SVG Chart Helpers
============================ */
function createSVG(width=800, height=260){
const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
svg.setAttribute('width','100%');
svg.setAttribute('viewBox',`0 0 ${width} ${height}`);
svg.setAttribute('preserveAspectRatio','xMidYMid meet');
svg.style.display = 'block';
return svg;
}
function drawLineChart(containerId, dataPoints, labels, options={}){
const container = el('#' + containerId);
const svg = createSVG(800,260);
container.innerHTML = '';
container.appendChild(svg);
const width = 800, height = 260, pad = 40;
const max = Math.max(...dataPoints), min = Math.min(...dataPoints);
const range = (max - min) || 1;
// axes gridlines
const g = document.createElementNS('http://www.w3.org/2000/svg','g');
g.setAttribute('class','grid');
for(let i=0;i<=4;i++){
const y = pad + (i*(height - pad*2)/4);
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
line.setAttribute('x1', pad); line.setAttribute('x2', width - pad);
line.setAttribute('y1', y); line.setAttribute('y2', y);
line.setAttribute('stroke','rgba(255,255,255,0.02)');
line.setAttribute('stroke-width','1');
svg.appendChild(line);
const label = document.createElementNS('http://www.w3.org/2000/svg','text');
const val = (max - (i*(range/4))).toFixed(2);
label.setAttribute('x', 8);
label.setAttribute('y', y+4);
label.setAttribute('fill','rgba(255,255,255,0.35)');
label.setAttribute('font-size','11');
label.textContent = val;
svg.appendChild(label);
}
// path
const path = [];
dataPoints.forEach((v,i)=>{
const x = pad + (i/(dataPoints.length-1))*(width - pad*2);
const y = pad + ((max - v)/range)*(height - pad*2);
path.push(`${i===0?'M':'L'} ${x} ${y}`);
});
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
p.setAttribute('d', path.join(' '));
p.setAttribute('fill','none');
p.setAttribute('stroke', options.color || 'url(#gradLine)');
p.setAttribute('stroke-width','3');
p.setAttribute('stroke-linecap','round');
p.setAttribute('stroke-linejoin','round');
svg.appendChild(p);
// points & tooltips
dataPoints.forEach((v,i)=>{
const x = pad + (i/(dataPoints.length-1))*(width - pad*2);
const y = pad + ((max - v)/range)*(height - pad*2);
const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
c.setAttribute('cx', x); c.setAttribute('cy', y);
c.setAttribute('r', 4);
c.setAttribute('fill', options.pointColor || 'rgba(14,165,164,0.95)');
c.style.cursor = 'pointer';
svg.appendChild(c);
c.addEventListener('mouseenter', (ev)=>{
showTooltip(ev.clientX, ev.clientY, `${labels[i]} β€” ${v}${options.unit||''}`);
});
c.addEventListener('mouseleave', hideTooltip);
});
// x labels
labels.forEach((lab,i)=>{
const x = pad + (i/(labels.length-1))*(width - pad*2);
const t = document.createElementNS('http://www.w3.org/2000/svg','text');
t.setAttribute('x', x);
t.setAttribute('y', height - 8);
t.setAttribute('text-anchor','middle');
t.setAttribute('fill','rgba(255,255,255,0.4)');
t.setAttribute('font-size','11');
t.textContent = lab;
svg.appendChild(t);
});
}
function drawBarChart(containerId, items, options={}){
const container = el('#' + containerId);
const svg = createSVG(800,260);
container.innerHTML = '';
container.appendChild(svg);
const width = 800, height = 260, pad = 40;
const max = Math.max(...items.map(i=>i.value));
const barW = (width - pad*2) / items.length;
items.forEach((it, idx)=>{
const x = pad + idx * barW + barW*0.12;
const h = ((it.value/max) * (height - pad*2));
const y = height - pad - h;
const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
rect.setAttribute('x', x);
rect.setAttribute('y', y);
rect.setAttribute('width', Math.max(12, barW*0.76));
rect.setAttribute('height', h);
rect.setAttribute('fill', options.color || 'rgba(14,165,164,0.9)');
rect.setAttribute('rx','6');
rect.style.cursor = 'pointer';
svg.appendChild(rect);
rect.addEventListener('mouseenter', (ev)=>{
showTooltip(ev.clientX, ev.clientY, `${it.label}: ${it.value}${it.unit||''} ${it.note?('β€’ ' + it.note):''}`);
});
rect.addEventListener('mouseleave', hideTooltip);
rect.addEventListener('click', ()=> {
// filtering action: filter appendix table by forecast if clicked on forecasts
if(it.filterTag){
document.getElementById('datasetFilter').value = it.filterTag;
populateTable();
// scroll to appendix
document.getElementById('appendix').scrollIntoView({behavior:'smooth'});
}
});
const txt = document.createElementNS('http://www.w3.org/2000/svg','text');
txt.setAttribute('x', x + Math.max(12, barW*0.76)/2);
txt.setAttribute('y', height - pad + 16);
txt.setAttribute('text-anchor','middle');
txt.setAttribute('fill','rgba(255,255,255,0.5)');
txt.setAttribute('font-size','12');
txt.textContent = it.label;
svg.appendChild(txt);
});
// add baseline labels
const baseline = document.createElementNS('http://www.w3.org/2000/svg','text');
baseline.setAttribute('x', pad);
baseline.setAttribute('y', 18);
baseline.setAttribute('fill','rgba(255,255,255,0.4)');
baseline.setAttribute('font-size','11');
baseline.textContent = options.title||'';
svg.appendChild(baseline);
}
function drawDonut(containerId, parts){
const container = el('#' + containerId);
const svg = createSVG(420,260);
container.innerHTML = '';
container.appendChild(svg);
const cx = 200, cy = 120, r = 70;
const total = parts.reduce((s,p)=>s+p.value,0);
let start = -Math.PI/2;
parts.forEach((p, i)=>{
const slice = (p.value/total) * Math.PI*2;
const end = start + slice;
const x1 = cx + r*Math.cos(start);
const y1 = cy + r*Math.sin(start);
const x2 = cx + r*Math.cos(end);
const y2 = cy + r*Math.sin(end);
const large = slice > Math.PI ? 1 : 0;
const path = document.createElementNS('http://www.w3.org/2000/svg','path');
const d = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
path.setAttribute('d', d);
path.setAttribute('fill', p.color);
path.style.cursor = 'pointer';
svg.appendChild(path);
path.addEventListener('mouseenter', (e)=> showTooltip(e.clientX,e.clientY, `${p.label}: ${p.value}%`));
path.addEventListener('mouseleave', hideTooltip);
start = end;
});
const center = document.createElementNS('http://www.w3.org/2000/svg','circle');
center.setAttribute('cx',cx); center.setAttribute('cy',cy); center.setAttribute('r',40);
center.setAttribute('fill', 'rgba(7,10,15,0.9)');
svg.appendChild(center);
// legend
parts.forEach((p,i)=>{
const y = 20 + i*20;
const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
rect.setAttribute('x', 340); rect.setAttribute('y', y-10); rect.setAttribute('width', 12); rect.setAttribute('height', 12);
rect.setAttribute('fill', p.color); svg.appendChild(rect);
const t = document.createElementNS('http://www.w3.org/2000/svg','text');
t.setAttribute('x', 360); t.setAttribute('y', y); t.setAttribute('fill','rgba(255,255,255,0.8)');
t.setAttribute('font-size','12'); t.textContent = `${p.label} β€’ ${p.value}%`;
svg.appendChild(t);
});
}
// Tooltip layer
let tooltip;
function ensureTooltip(){
if(tooltip) return;
tooltip = document.createElement('div');
tooltip.style.position = 'fixed';
tooltip.style.zIndex = 9999;
tooltip.style.background = 'rgba(0,0,0,0.8)';
tooltip.style.padding = '8px 10px';
tooltip.style.borderRadius = '8px';
tooltip.style.color = '#fff';
tooltip.style.fontSize = '13px';
tooltip.style.pointerEvents = 'none';
tooltip.style.boxShadow = '0 8px 20px rgba(0,0,0,0.6)';
document.body.appendChild(tooltip);
}
function showTooltip(x,y,html){
ensureTooltip();
tooltip.innerHTML = html;
const w = tooltip.offsetWidth;
tooltip.style.left = (x + 12) + 'px';
tooltip.style.top = (y + 12) + 'px';
tooltip.style.opacity = '1';
tooltip.style.transform = 'translateY(0)';
}
function hideTooltip(){
if(tooltip) tooltip.style.opacity = '0';
}
/* Build charts */
// GDP historical 2020-2025
const gdpLabels = ['2020 Q1','2021 Q1','2022 Q1','2023 Q1','2024 Q1','2025 Q1','2025 Q2'];
const gdpPoints = [3.21,4.85,5.42,3.46,5.98,6.93,7.96];
drawLineChart('chartGDP', gdpPoints, gdpLabels, {unit:' %', color:'rgba(14,165,164,0.95)', pointColor:'rgba(14,165,164,0.95)'});
// Forecast bar chart
const forecastItems = [
{ label:'World Bank', value:5.8, unit:'%', color:'#60a5fa', note:'WB' , filterTag:'forecast'},
{ label:'ADB', value:6.6, unit:'%', color:'#a78bfa', note:'ADB' , filterTag:'forecast' },
{ label:'IMF', value:5.2, unit:'%', color:'#f59e0b', note:'IMF' , filterTag:'forecast' },
{ label:'Government', value:8.4, unit:'%', color:'#10b981', note:'Target 8.3–8.5', filterTag:'forecast' }
];
drawBarChart('chartForecast', forecastItems, { title:'Forecasts' });
// FDI trend (simple bars)
const fdiItems = [
{ label:'Registered (5m)', value:18.4, unit:'B USD', color:'#06b6d4', note:'+51%YoY' },
{ label:'Disbursed (5m)', value:8.9, unit:'B USD', color:'#7c3aed', note:'' },
{ label:'Total H1', value:21.51, unit:'B USD', color:'#06b6d4', note:'+32.6%YoY' },
];
drawBarChart('chartFDI', fdiItems);
// Sectors donut
const sectorsSimplified = [
{ label:'Services', value:45, color:'#06b6d4' },
{ label:'Manufacturing', value:30, color:'#10b981' },
{ label:'Exports (goods)', value:15, color:'#a78bfa' },
{ label:'Agriculture & others', value:10, color:'#f59e0b' }
];
drawDonut('chartSectors', sectorsSimplified);
// Banking chart (simple bar)
const bankingItems = [
{ label:'Earnings proj', value:17, unit:'%', color:'#06b6d4' },
{ label:'Credit growth', value:15, unit:'%', color:'#10b981' }
];
drawBarChart('chartBanking', bankingItems);
// Retail donut / radial
(function drawRetail(){
const container = el('#chartRetail');
const svg = createSVG(420,260);
svg.setAttribute('viewBox','0 0 420 260');
container.innerHTML = '';
container.appendChild(svg);
// dummy segmentation
const parts = [
{label:'Consumption', value:55, color:'#06b6d4'},
{label:'E-commerce', value:20, color:'#a78bfa'},
{label:'Services', value:15, color:'#10b981'},
{label:'Others', value:10, color:'#f59e0b'}
];
drawDonut('chartRetail', parts);
})();
/* ============================
Data Table β€” Appendix
============================ */
const dataTableBody = el('#dataTable tbody');
function populateTable(){
const filter = el('#datasetFilter').value;
const rows = dataset.filter(r => (filter === 'all') ? true : (r.tag === filter || r.metric.toLowerCase().includes(filter)));
dataTableBody.innerHTML = '';
rows.forEach(r=>{
const tr = document.createElement('tr');
tr.innerHTML = `<td>${escapeHTML(r.metric)}</td>
<td>${escapeHTML(r.period)}</td>
<td>${escapeHTML(r.value)}</td>
<td>${escapeHTML(r.unit)}</td>
<td class="small muted">${escapeHTML(r.note)}</td>`;
dataTableBody.appendChild(tr);
});
}
populateTable();
// Filter change
el('#datasetFilter').addEventListener('change', ()=>{
populateTable();
el('#savedView').textContent = 'unsaved';
});
// CSV & JSON export
function toCSV(rows){
const keys = ['metric','period','value','unit','note'];
const csv = [keys.join(',')].concat(rows.map(r=> keys.map(k=> `"${String(r[k]||'').replace(/"/g,'""')}"`).join(','))).join('\n');
return csv;
}
function downloadBlob(filename, content, type='text/csv'){
const blob = new Blob([content], {type});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
el('#exportCSV').addEventListener('click', ()=>{
downloadBlob('vietnam_economic_dataset.csv', toCSV(dataset), 'text/csv');
});
el('#downloadCSV').addEventListener('click', ()=>{
const filter = el('#datasetFilter').value;
const rows = dataset.filter(r => (filter === 'all') ? true : (r.tag === filter || r.metric.toLowerCase().includes(filter)));
downloadBlob(`vietnam_dataset_${filter}.csv`, toCSV(rows), 'text/csv');
});
el('#copyJSON').addEventListener('click', async ()=>{
const filter = el('#datasetFilter').value;
const rows = dataset.filter(r => (filter === 'all') ? true : (r.tag === filter || r.metric.toLowerCase().includes(filter)));
await navigator.clipboard.writeText(JSON.stringify(rows, null, 2));
showToast('Copied dataset JSON to clipboard');
});
el('#exportExecJSON').addEventListener('click', async ()=>{
const exec = { summary: el('#execSummaryText').textContent, tags:['gdp','fdi','inflation'] };
downloadBlob('exec_summary.json', JSON.stringify(exec, null, 2), 'application/json');
});
// Copy Executive summary text
el('#copySummary').addEventListener('click', async ()=>{
const text = el('#execSummaryText').textContent;
await navigator.clipboard.writeText(text);
showToast('Executive summary copied to clipboard');
});
el('#copyExec').addEventListener('click', async ()=>{
const text = el('#execSummaryText').textContent;
await navigator.clipboard.writeText(text);
showToast('Executive summary copied to clipboard');
});
// Print
el('#printBtn').addEventListener('click', ()=> window.print());
// Save view to localStorage
el('#saveView').addEventListener('click', ()=>{
const filter = el('#datasetFilter').value;
localStorage.setItem('savedView', filter);
el('#savedView').textContent = filter;
showToast('View saved');
});
el('#resetView').addEventListener('click', ()=>{
el('#datasetFilter').value = 'all';
populateTable();
localStorage.removeItem('savedView');
el('#savedView').textContent = 'none';
showToast('View reset');
});
const sv = localStorage.getItem('savedView');
if(sv){ el('#datasetFilter').value = sv; el('#savedView').textContent = sv; populateTable(); }
// Sortable tables (history table & data table)
function makeTableSortable(table){
const headers = table.querySelectorAll('th');
headers.forEach(th=>{
th.addEventListener('click', ()=>{
const key = th.dataset.key;
if(!key) return;
const tbody = table.tBodies[0];
const rows = Array.from(tbody.querySelectorAll('tr'));
const asc = !th.classList.contains('asc');
headers.forEach(h => h.classList.remove('sorted','asc','desc'));
th.classList.add('sorted', asc ? 'asc':'desc');
rows.sort((a,b)=>{
const aVal = a.querySelector(`td:nth-child(${Array.from(headers).indexOf(th)+1})`).textContent.trim();
const bVal = b.querySelector(`td:nth-child(${Array.from(headers).indexOf(th)+1})`).textContent.trim();
const aNum = parseFloat(aVal);
const bNum = parseFloat(bVal);
if(!isNaN(aNum) && !isNaN(bNum)) return asc ? aNum - bNum : bNum - aNum;
return asc ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
});
rows.forEach(r=> tbody.appendChild(r));
// save sort preference
localStorage.setItem(`sort_${table.id}`, JSON.stringify({key,asc}));
});
});
}
makeTableSortable(el('#historyTable'));
makeTableSortable(el('#dataTable'));
// Restore last sort for dataTable
const savedSort = localStorage.getItem('sort_dataTable');
if(savedSort){
const s = JSON.parse(savedSort);
const th = el(`#dataTable th[data-key="${s.key}"]`);
th && th.click();
if(!s.asc) th && th.click(); // toggle to saved direction
}
// Search functionality: find text across sections and highlight
const searchInput = el('#globalSearch');
searchInput.addEventListener('input', debounce((e)=>{
const q = e.target.value.trim().toLowerCase();
handleSearch(q);
}, 300));
el('#clearSearch').addEventListener('click', ()=>{
searchInput.value = '';
handleSearch('');
});
function handleSearch(q){
// clear marks
els('mark').forEach(m => m.replaceWith(document.createTextNode(m.textContent)));
if(!q){
// show all sections
sections.forEach(s=>s.style.display='block');
return;
}
// search across text in each section; hide those without matches
sections.forEach(s=>{
const text = s.textContent.toLowerCase();
if(text.includes(q)){
s.style.display='block';
// highlight matches in this section
highlightSection(s, q);
} else {
s.style.display='none';
}
});
}
function highlightSection(section, q){
const walker = document.createTreeWalker(section, NodeFilter.SHOW_TEXT, null);
const nodes = [];
while(walker.nextNode()) nodes.push(walker.currentNode);
nodes.forEach(node=>{
const idx = node.nodeValue.toLowerCase().indexOf(q);
if(idx >= 0 && node.nodeValue.trim() !== ''){
const span = document.createElement('span');
const before = node.nodeValue.slice(0, idx);
const match = node.nodeValue.slice(idx, idx + q.length);
const after = node.nodeValue.slice(idx + q.length);
const mark = document.createElement('mark');
mark.textContent = match;
span.appendChild(document.createTextNode(before));
span.appendChild(mark);
span.appendChild(document.createTextNode(after));
node.parentNode.replaceChild(span, node);
}
});
}
// Toasts for user feedback
let toastTimer;
function showToast(msg, duration=1800){
let t = document.getElementById('__toast');
if(!t){
t = document.createElement('div');
t.id = '__toast';
t.style.position = 'fixed';
t.style.right = '18px';
t.style.bottom = '18px';
t.style.background = 'rgba(0,0,0,0.8)';
t.style.padding = '10px 14px';
t.style.borderRadius = '10px';
t.style.color = '#fff';
t.style.zIndex = 99999;
document.body.appendChild(t);
}
t.textContent = msg;
t.style.opacity = '1';
clearTimeout(toastTimer);
toastTimer = setTimeout(()=> t.style.opacity = '0', duration);
}
// Misc actions: focus buttons highlight portions
el('#focusGDP').addEventListener('click', ()=> {
document.getElementById('indicators').scrollIntoView({behavior:'smooth'});
showToast('Focused on Key Indicators');
});
el('#focusFDI').addEventListener('click', ()=> {
document.getElementById('indicators').scrollIntoView({behavior:'smooth'});
showToast('Focused on FDI chart');
});
// Risk mitigation toggle
el('#riskMitigate').addEventListener('click', ()=> {
const elMit = el('#mitigation');
const body = elMit.querySelector('.body');
body.style.display = body.style.display === 'block' ? 'none' : 'block';
});
// Download sample policy (generate small TXT)
el('#downloadPolicy').addEventListener('click', ()=>{
const content = `Sample Policy Note\n\nFocus areas:\n- Diversify exports\n- Support domestic demand\n- Strengthen macro buffers\n\nGenerated: ${new Date().toLocaleString()}`;
downloadBlob('policy_note.txt', content, 'text/plain');
});
// Toggle theme (light/dark) β€” saved in localStorage
const themeToggle = el('#toggleTheme');
let dark = localStorage.getItem('theme') !== 'light';
function applyTheme(){
if(dark){
document.documentElement.style.setProperty('--bg','#071427');
document.documentElement.style.setProperty('--card','#0b1320');
} else {
document.documentElement.style.setProperty('--bg','#f6f9fb');
document.documentElement.style.setProperty('--card','#ffffff');
document.documentElement.style.setProperty('--muted','#475569');
document.documentElement.style.setProperty('--accent','#0b948f');
document.body.style.color = '#0b1720';
}
// minimal change; full theming would entail more adjustments
}
themeToggle.addEventListener('click', ()=>{
dark = !dark;
localStorage.setItem('theme', dark ? 'dark':'light');
applyTheme();
showToast('Theme toggled');
});
applyTheme();
// Copy dataset via main exportCSV
el('#exportCSV').addEventListener('click', ()=>{
showToast('CSV exported');
});
// Simple debounce
function debounce(fn, wait=200){ let t; return (...args)=>{ clearTimeout(t); t = setTimeout(()=>fn(...args), wait) } }
// Escape HTML
function escapeHTML(s){ return String(s).replace(/[&<>"']/g, m=> ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m])); }
/* Initialize dataset table rows (append more detailed rows) */
// Populate the appendix data table (already in populateTable)
// Save some user preferences in localStorage
window.addEventListener('beforeunload', ()=>{
const last = localStorage.getItem('lastSection');
if(last) localStorage.setItem('lastSection', last);
});
// Accessibility: keyboard nav for TOC
let tocIndex = 0;
document.addEventListener('keydown', (e)=>{
if(e.key === 'j'){ tocIndex = Math.min(tocLinks.length-1, tocIndex+1); tocLinks[tocIndex].focus(); }
if(e.key === 'k'){ tocIndex = Math.max(0, tocIndex-1); tocLinks[tocIndex].focus(); }
});
// initial small animations: reveal current TOC link
setTimeout(()=> {
const curr = document.querySelector('.toc a.active');
if(curr) curr.scrollIntoView({block:'center'});
}, 600);
// add aria labels
els('.chart').forEach(c=> c.setAttribute('role','region'));
// Expose some functions for debugging in console
window.__report = { dataset, populateTable, drawLineChart, drawBarChart };
</script>
</body>
</html>