thucdangvan020999 commited on
Commit
afad3ba
·
verified ·
1 Parent(s): 8994b24

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1269 -18
index.html CHANGED
@@ -1,19 +1,1270 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>Vietnam Economic Growth Report 2025 — Interactive Presentation</title>
7
+ <!-- Icon Library -->
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
9
+ <style>
10
+ :root{
11
+ --bg: #0f1724;
12
+ --card: #0b1220;
13
+ --muted: #94a3b8;
14
+ --accent: #06b6d4;
15
+ --accent-2: #7c3aed;
16
+ --success: #10b981;
17
+ --danger: #ef4444;
18
+ --glass: rgba(255,255,255,0.03);
19
+ --radius: 12px;
20
+ --max-width: 1200px;
21
+ --gap: clamp(12px,2vw,24px);
22
+ --ff-sans: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
23
+ color-scheme: dark;
24
+ }
25
+ *{box-sizing:border-box}
26
+ html,body{
27
+ height:100%;
28
+ margin:0;
29
+ font-family:var(--ff-sans);
30
+ background:linear-gradient(180deg,var(--bg),#071021 60%);
31
+ -webkit-font-smoothing:antialiased;
32
+ -moz-osx-font-smoothing:grayscale;
33
+ color:#e6eef6;
34
+ scroll-behavior:smooth;
35
+ font-size:clamp(14px,1.6vw,18px);
36
+ line-height:1.45;
37
+ padding:24px;
38
+ }
39
+
40
+ /* Layout */
41
+ .container{
42
+ max-width:var(--max-width);
43
+ margin:0 auto;
44
+ display:grid;
45
+ grid-template-columns: 280px 1fr;
46
+ gap:var(--gap);
47
+ align-items:start;
48
+ }
49
+ @media (max-width:1024px){ .container{ grid-template-columns: 1fr; } }
50
+
51
+ /* TOC */
52
+ .toc{
53
+ position:sticky;
54
+ top:24px;
55
+ height: calc(100vh - 48px);
56
+ background:linear-gradient(180deg, rgba(255,255,255,0.02), transparent);
57
+ border-radius:var(--radius);
58
+ padding:18px;
59
+ display:flex;
60
+ flex-direction:column;
61
+ gap:16px;
62
+ min-height:220px;
63
+ box-shadow: 0 6px 18px rgba(2,6,23,0.6);
64
+ border:1px solid rgba(255,255,255,0.03);
65
+ }
66
+ .brand{
67
+ display:flex;
68
+ gap:12px;
69
+ align-items:center;
70
+ }
71
+ .logo{
72
+ width:44px;
73
+ height:44px;
74
+ border-radius:8px;
75
+ display:grid;
76
+ place-items:center;
77
+ background:linear-gradient(135deg,var(--accent),var(--accent-2));
78
+ font-weight:700;
79
+ font-size:18px;
80
+ color:white;
81
+ box-shadow:0 6px 20px rgba(7,11,27,0.6);
82
+ }
83
+ h1{font-size:clamp(16px,2vw,20px);margin:0}
84
+ .toc-content{overflow:auto;padding-right:6px}
85
+ .toc ul{list-style:none;padding:0;margin:6px 0 0 0;display:flex;flex-direction:column;gap:8px}
86
+ .toc a{
87
+ color:var(--muted);
88
+ text-decoration:none;
89
+ font-size:14px;
90
+ display:flex;
91
+ gap:8px;
92
+ align-items:center;
93
+ padding:8px;
94
+ border-radius:8px;
95
+ }
96
+ .toc a.active, .toc a:hover{ background:var(--glass); color:#fff;}
97
+ .toc .small{font-size:12px;color:var(--muted)}
98
+ .search-toc{display:flex;gap:8px}
99
+ .search-toc input{
100
+ background:transparent;border:1px solid rgba(255,255,255,0.04);padding:8px;border-radius:8px;color:var(--muted);width:100%;
101
+ }
102
+ .controls{display:flex;gap:8px;align-items:center;margin-top:auto}
103
+ .btn{
104
+ background:transparent;border:1px solid rgba(255,255,255,0.06);color:var(--muted);padding:8px 10px;border-radius:8px;cursor:pointer;font-size:13px;
105
+ }
106
+ .btn.primary{background:linear-gradient(90deg,var(--accent),var(--accent-2));border:none;color:white;box-shadow:0 8px 24px rgba(7,11,27,0.6)}
107
+ .progress-mini{height:6px;background:rgba(255,255,255,0.03);border-radius:6px;overflow:hidden}
108
+ .progress-mini > i{display:block;height:100%;background:linear-gradient(90deg,var(--accent),var(--accent-2));width:0%}
109
+
110
+ /* Main area */
111
+ main{
112
+ display:grid;
113
+ gap:var(--gap);
114
+ }
115
+ .card{
116
+ background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
117
+ border-radius:var(--radius);
118
+ padding:18px;
119
+ box-shadow:0 8px 30px rgba(2,6,23,0.6);
120
+ border:1px solid rgba(255,255,255,0.03);
121
+ }
122
+ header.card{
123
+ display:flex;
124
+ gap:18px;
125
+ align-items:flex-start;
126
+ position:relative;
127
+ overflow:visible;
128
+ }
129
+ .summary{
130
+ flex:1;
131
+ display:flex;
132
+ flex-direction:column;
133
+ gap:8px;
134
+ }
135
+ .kpis{display:flex;gap:12px;flex-wrap:wrap}
136
+ .kpi{
137
+ background:rgba(255,255,255,0.02);
138
+ padding:12px;border-radius:10px;min-width:120px;flex:1;
139
+ display:flex;flex-direction:column;gap:6px;
140
+ }
141
+ .kpi .value{font-size:clamp(18px,2.4vw,24px);font-weight:700}
142
+ .kpi .label{font-size:12px;color:var(--muted)}
143
+
144
+ .actions{display:flex;gap:8px;align-items:center}
145
+ .chip{padding:6px 10px;border-radius:999px;background:rgba(255,255,255,0.02);font-size:13px;color:var(--muted);display:inline-flex;gap:8px;align-items:center}
146
+
147
+ /* Content sections */
148
+ section{display:grid;gap:12px}
149
+ .section-title{display:flex;justify-content:space-between;align-items:center;gap:12px}
150
+ .section-title h2{margin:0;font-size:18px}
151
+ .subtle{color:var(--muted);font-size:13px}
152
+
153
+ /* Grid for visualizations */
154
+ .viz-grid{
155
+ display:grid;
156
+ gap:12px;
157
+ grid-template-columns:repeat(2,1fr);
158
+ }
159
+ @media (max-width:1024px){ .viz-grid{ grid-template-columns:1fr; } }
160
+ .chart{
161
+ background:linear-gradient(180deg, rgba(255,255,255,0.015), transparent);
162
+ padding:12px;border-radius:10px;min-height:220px;display:flex;flex-direction:column;gap:8px;
163
+ border:1px solid rgba(255,255,255,0.02);
164
+ }
165
+ .chart .title{display:flex;justify-content:space-between;align-items:center}
166
+ .svg-wrap{flex:1;display:grid;place-items:center;padding:6px}
167
+ svg{width:100%;height:100%}
168
+
169
+ /* Table */
170
+ .data-table{overflow:auto;border-radius:10px;border:1px solid rgba(255,255,255,0.03)}
171
+ table{width:100%;border-collapse:collapse;color:var(--muted);min-width:640px}
172
+ th,td{padding:12px 10px;text-align:left;border-bottom:1px dashed rgba(255,255,255,0.02)}
173
+ th{color:#cfe8f1;cursor:pointer;position:relative}
174
+ th .sort{opacity:0.6;margin-left:8px}
175
+ tr:hover td{background:linear-gradient(90deg, rgba(255,255,255,0.01), transparent);color:#fff}
176
+
177
+ /* Callouts & insights */
178
+ .callouts{display:flex;gap:12px;flex-wrap:wrap}
179
+ .callout{flex:1;min-width:220px;padding:12px;border-radius:10px;background:linear-gradient(180deg, rgba(124,58,237,0.06), rgba(6,182,212,0.03));border:1px solid rgba(255,255,255,0.02)}
180
+ .callout h3{margin:0 0 6px 0;font-size:14px}
181
+ .callout p{margin:0;color:var(--muted);font-size:13px}
182
+
183
+ /* Collapsible */
184
+ .collapsible{border-radius:8px;overflow:hidden}
185
+ .collapsible summary{list-style:none;cursor:pointer;padding:12px;background:rgba(255,255,255,0.02);display:flex;justify-content:space-between;align-items:center}
186
+ .collapsible details{background:transparent}
187
+
188
+ /* Footer */
189
+ footer{display:flex;justify-content:space-between;align-items:center;color:var(--muted);font-size:13px;padding-top:6px}
190
+ footer .sources{display:flex;gap:8px;flex-wrap:wrap}
191
+
192
+ /* Annotations and badges */
193
+ .badge{padding:6px 8px;border-radius:8px;background:rgba(255,255,255,0.02);font-size:12px}
194
+ .legend{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
195
+ .legend span{display:flex;gap:8px;align-items:center;padding:6px;border-radius:8px;background:rgba(255,255,255,0.02)}
196
+ .legend i{width:14px;height:14px;border-radius:3px;display:inline-block}
197
+
198
+ /* Responsive typography with clamp */
199
+ h2{font-size:clamp(16px,2.2vw,20px)}
200
+ p{margin:0}
201
+
202
+ /* heatmap cells */
203
+ .heatmap-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:6px}
204
+ .heat-cell{aspect-ratio:1/1;border-radius:6px;display:grid;place-items:center;font-size:12px;color:#02111a;padding:6px;font-weight:700;}
205
+
206
+ /* small helpers */
207
+ .muted{color:var(--muted)}
208
+ .right{text-align:right}
209
+ .hidden{display:none}
210
+ /* Container query example */
211
+ .card:container(min-width:600px){
212
+ padding:20px;
213
+ }
214
+ </style>
215
+ </head>
216
+ <body>
217
+ <div class="container">
218
+ <!-- TOC -->
219
+ <aside class="toc card" aria-label="Table of contents">
220
+ <div class="brand">
221
+ <div class="logo">VN</div>
222
+ <div>
223
+ <h1>Vietnam Economic Growth 2025</h1>
224
+ <div class="small muted">Interactive report & visualization</div>
225
+ </div>
226
+ </div>
227
+
228
+ <div class="search-toc">
229
+ <input id="tocFilter" placeholder="Search sections..." aria-label="Search sections" />
230
+ <button class="btn" id="themeToggle" title="Toggle theme"><i class="fa fa-sun"></i></button>
231
+ </div>
232
+
233
+ <nav class="toc-content" id="tocList">
234
+ <ul>
235
+ <li><a href="#executive" data-target="executive"><i class="fa fa-file-lines"></i> Executive Summary</a></li>
236
+ <li><a href="#indicators" data-target="indicators"><i class="fa fa-chart-line"></i> Key Indicators</a></li>
237
+ <li><a href="#sectoral" data-target="sectoral"><i class="fa fa-industry"></i> Sectoral Analysis</a></li>
238
+ <li><a href="#visualizations" data-target="visualizations"><i class="fa fa-chart-pie"></i> Visualizations</a></li>
239
+ <li><a href="#table" data-target="table"><i class="fa fa-table"></i> Data Table</a></li>
240
+ <li><a href="#outlook" data-target="outlook"><i class="fa fa-eye"></i> Outlook & Risks</a></li>
241
+ <li><a href="#appendix" data-target="appendix"><i class="fa fa-book"></i> References & Appendix</a></li>
242
+ </ul>
243
+ </nav>
244
+
245
+ <div class="controls">
246
+ <button class="btn" id="copySummary" title="Copy executive summary"><i class="fa fa-copy"></i></button>
247
+ <button class="btn primary" id="exportCSV" title="Export table CSV"><i class="fa fa-download"></i> Export</button>
248
+ </div>
249
+
250
+ <div style="margin-top:10px">
251
+ <div class="small muted">Progress</div>
252
+ <div class="progress-mini"><i id="overallProgress" style="width:12%"></i></div>
253
+ </div>
254
+ </aside>
255
+
256
+ <!-- Main -->
257
+ <main>
258
+ <!-- Header / Executive summary -->
259
+ <header id="executive" class="card">
260
+ <div class="summary">
261
+ <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:12px">
262
+ <div>
263
+ <h2>Executive Summary</h2>
264
+ <div class="subtle">Vietnam's economy shows robust mid-year growth led by services and manufacturing.</div>
265
+ </div>
266
+ <div class="badge">Report 2025 • Updated</div>
267
+ </div>
268
+
269
+ <p class="muted" style="margin-top:8px">
270
+ Vietnam recorded 7.52% GDP growth in H1 2025 — the highest mid-year performance since 2011. Q2 2025 grew 7.96% YoY. Strong FDI, low unemployment and controlled inflation underpin growth despite global trade tensions.
271
+ </p>
272
+
273
+ <div class="kpis" style="margin-top:12px">
274
+ <div class="kpi">
275
+ <div class="value" id="kpiGdp">7.52%</div>
276
+ <div class="label">H1 2025 GDP Growth (YoY)</div>
277
+ </div>
278
+ <div class="kpi">
279
+ <div class="value" id="kpiQ2">7.96%</div>
280
+ <div class="label">Q2 2025 GDP Growth (YoY)</div>
281
+ </div>
282
+ <div class="kpi">
283
+ <div class="value" id="kpiInfl">3.57%</div>
284
+ <div class="label">June 2025 Inflation</div>
285
+ </div>
286
+ <div class="kpi">
287
+ <div class="value" id="kpiUnemp">2.20%</div>
288
+ <div class="label">Unemployment Q1 2025</div>
289
+ </div>
290
+ </div>
291
+
292
+ <div style="display:flex;gap:12px;margin-top:12px;align-items:center">
293
+ <div class="chip"><i class="fa fa-building"></i> FDI Inflows +32.6% YoY</div>
294
+ <div class="chip muted">Government target: 8.3–8.5%</div>
295
+ <div class="chip muted">World Bank: 5.8% • IMF: 5.2%</div>
296
+ </div>
297
+ </div>
298
+
299
+ <div style="width:320px;display:flex;flex-direction:column;gap:12px">
300
+ <div class="card" style="background:linear-gradient(180deg,#061626,#082038);padding:12px">
301
+ <div style="display:flex;justify-content:space-between;align-items:center">
302
+ <div class="muted">Forecast vs Actual</div>
303
+ <div class="badge">Citations</div>
304
+ </div>
305
+ <div style="margin-top:12px;display:grid;gap:8px">
306
+ <div style="display:flex;justify-content:space-between">
307
+ <div class="small muted">Govt Target</div><strong>8.3–8.5%</strong>
308
+ </div>
309
+ <div style="display:flex;justify-content:space-between">
310
+ <div class="small muted">ADB</div><strong>6.6%</strong>
311
+ </div>
312
+ <div style="display:flex;justify-content:space-between">
313
+ <div class="small muted">World Bank</div><strong>5.8%</strong>
314
+ </div>
315
+ <div style="display:flex;justify-content:space-between">
316
+ <div class="small muted">IMF</div><strong>5.2%</strong>
317
+ </div>
318
+ </div>
319
+ <div style="margin-top:10px" class="subtle">Sources: IMF, ADB, World Bank, Government statistics (see References)</div>
320
+ </div>
321
+
322
+ <div class="card" style="padding:10px">
323
+ <div style="display:flex;justify-content:space-between;align-items:center">
324
+ <div><strong>Actionable Insight</strong><div class="muted" style="font-size:13px">Policy & Business Implication</div></div>
325
+ <i class="fa fa-lightbulb" style="color:var(--accent);font-size:20px"></i>
326
+ </div>
327
+ <p class="muted" style="margin-top:8px">Sustain growth while prioritizing macroeconomic stability: diversify export markets, manage credit growth and use targeted fiscal support to handle external shocks.</p>
328
+ </div>
329
+ </div>
330
+ </header>
331
+
332
+ <!-- Key Indicators -->
333
+ <section id="indicators" class="card">
334
+ <div class="section-title">
335
+ <h2>Key Economic Indicators</h2>
336
+ <div class="muted">Press a chart's legend to toggle series. Hover for details.</div>
337
+ </div>
338
+
339
+ <div class="viz-grid">
340
+ <div class="chart" id="chartGDPQuarterly" data-src="gdp-quarter">
341
+ <div class="title">
342
+ <div><strong>Quarterly GDP Growth (2024–2025)</strong><div class="muted">YoY %</div></div>
343
+ <div class="legend" id="legendGDPQ"></div>
344
+ </div>
345
+ <div class="svg-wrap"><svg id="svgGDPQ" viewBox="0 0 800 300" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Quarterly GDP Growth chart"></svg></div>
346
+ <div class="subtle">Source: General Statistics Office, IMF</div>
347
+ </div>
348
+
349
+ <div class="chart" id="chartGDPAnnual" data-src="gdp-annual">
350
+ <div class="title">
351
+ <div><strong>Annual GDP Growth (2020–2025)</strong><div class="muted">Yearly %</div></div>
352
+ <div class="legend" id="legendGPDA"></div>
353
+ </div>
354
+ <div class="svg-wrap"><svg id="svgGPDA" viewBox="0 0 800 300" preserveAspectRatio="xMidYMid meet"></svg></div>
355
+ <div class="subtle">Includes forecast & historical comparison</div>
356
+ </div>
357
+
358
+ <div class="chart" id="chartInflation">
359
+ <div class="title">
360
+ <div><strong>Inflation (May–June 2025) & Forecasts</strong><div class="muted">%, month & forecast</div></div>
361
+ <div class="legend" id="legendInfl"></div>
362
+ </div>
363
+ <div class="svg-wrap"><svg id="svgInfl" viewBox="0 0 800 220" preserveAspectRatio="xMidYMid meet"></svg></div>
364
+ <div class="subtle">IMF: 2.9% • ADB: 4.0%</div>
365
+ </div>
366
+
367
+ <div class="chart" id="chartSector">
368
+ <div class="title">
369
+ <div><strong>Sector Contribution (Est.)</strong><div class="muted">Share of GDP</div></div>
370
+ <div class="legend" id="legendSector"></div>
371
+ </div>
372
+ <div class="svg-wrap"><svg id="svgSector" viewBox="0 0 800 300" preserveAspectRatio="xMidYMid meet"></svg></div>
373
+ <div class="subtle">Services & Manufacturing drive growth</div>
374
+ </div>
375
+ </div>
376
+ </section>
377
+
378
+ <!-- Sectoral Analysis -->
379
+ <section id="sectoral" class="card">
380
+ <div class="section-title">
381
+ <h2>Sectoral Analysis</h2>
382
+ <div class="muted">Performance breakdown & retail trends</div>
383
+ </div>
384
+
385
+ <div style="display:grid;grid-template-columns:1fr 320px;gap:12px">
386
+ <div>
387
+ <div class="callouts">
388
+ <div class="callout">
389
+ <h3>Services</h3>
390
+ <p>Primary contributor to H1 2025 growth — strong domestic consumption and tourism recovery.</p>
391
+ </div>
392
+ <div class="callout">
393
+ <h3>Manufacturing</h3>
394
+ <p>Export-oriented manufacturing supported by FDI inflows and supply chain relocation.</p>
395
+ </div>
396
+ <div class="callout">
397
+ <h3>Banking</h3>
398
+ <p>Projected earnings +17% in 2025 on credit growth ~15%.</p>
399
+ </div>
400
+ </div>
401
+
402
+ <div style="margin-top:12px">
403
+ <details class="collapsible" open>
404
+ <summary><strong>Retail Performance & Numbers</strong><span class="muted"> (Q1 2025)</span></summary>
405
+ <div style="padding:12px">
406
+ <p class="muted">Retail sales reached 1.708 quadrillion VND (US$66.83B), +9.9% YoY, indicating resilient domestic demand.</p>
407
+ <div style="margin-top:8px">
408
+ <button class="btn" id="copyRetail">Copy retail data</button>
409
+ <button class="btn" id="openScatter">View FDI vs GDP scatter</button>
410
+ </div>
411
+ </div>
412
+ </details>
413
+ </div>
414
+ </div>
415
+
416
+ <aside style="display:flex;flex-direction:column;gap:12px">
417
+ <div class="card" style="padding:12px;background:linear-gradient(180deg, rgba(255,255,255,0.01), transparent)">
418
+ <div style="display:flex;justify-content:space-between;align-items:center">
419
+ <div><strong>FDI Snapshot</strong><div class="muted" style="font-size:13px">First half 2025</div></div>
420
+ <div class="badge">+32.6% YoY</div>
421
+ </div>
422
+ <div style="margin-top:12px">
423
+ <div style="display:flex;justify-content:space-between"><div class="muted">Registered capital (5 months)</div><strong>$18.4B</strong></div>
424
+ <div style="display:flex;justify-content:space-between"><div class="muted">Disbursed capital</div><strong>$8.9B</strong></div>
425
+ <div style="display:flex;justify-content:space-between"><div class="muted">Total FDI (H1)</div><strong>$21.51B</strong></div>
426
+ </div>
427
+ </div>
428
+
429
+ <div class="card" style="padding:12px">
430
+ <strong>Risks</strong>
431
+ <ul class="muted" style="margin:8px 0 0 18px;line-height:1.6">
432
+ <li>Trade tensions & tariffs</li>
433
+ <li>Overdependence on FDI</li>
434
+ <li>Geopolitical uncertainty</li>
435
+ <li>Macro stability vs fast growth</li>
436
+ </ul>
437
+ </div>
438
+ </aside>
439
+ </div>
440
+ </section>
441
+
442
+ <!-- Visualizations & interactive -->
443
+ <section id="visualizations" class="card">
444
+ <div class="section-title">
445
+ <h2>Interactive Visualizations</h2>
446
+ <div class="muted">Engage with data — export charts, toggle series, and filter datasets.</div>
447
+ </div>
448
+
449
+ <div style="display:grid;gap:12px">
450
+ <div style="display:flex;gap:12px;flex-wrap:wrap;align-items:center">
451
+ <input id="filterSeries" placeholder="Filter by sector (e.g., Services)" style="padding:8px;border-radius:8px;background:transparent;border:1px solid rgba(255,255,255,0.03);color:var(--muted);width:240px"/>
452
+ <button class="btn" id="exportPNG"><i class="fa fa-image"></i> Export Selected Chart PNG</button>
453
+ <button class="btn" id="resetView">Reset filters</button>
454
+ <div class="muted" style="margin-left:auto">Tip: Click legends to toggle series</div>
455
+ </div>
456
+
457
+ <div class="viz-grid">
458
+ <div class="chart" id="chartScatter">
459
+ <div class="title">
460
+ <div><strong>FDI vs GDP Growth (H1 2025)</strong><div class="muted">Scatter plot</div></div>
461
+ <div class="legend" id="legendScatter"></div>
462
+ </div>
463
+ <div class="svg-wrap"><svg id="svgScatter" viewBox="0 0 800 300" preserveAspectRatio="xMidYMid meet"></svg></div>
464
+ <div class="subtle">Shows correlation between regional FDI & quarter growth</div>
465
+ </div>
466
+
467
+ <div class="chart" id="chartHeatmap">
468
+ <div class="title">
469
+ <div><strong>Monthly Indicator Heatmap (Jan–Jun 2025)</strong><div class="muted">Inflation, FDI & Retail</div></div>
470
+ </div>
471
+ <div class="svg-wrap" style="padding:10px">
472
+ <div id="heatmap" class="heatmap-grid" style="width:100%"></div>
473
+ </div>
474
+ <div class="subtle">Color intensity reflects magnitude; hover for exact values</div>
475
+ </div>
476
+ </div>
477
+ </div>
478
+ </section>
479
+
480
+ <!-- Data table -->
481
+ <section id="table" class="card">
482
+ <div class="section-title">
483
+ <h2>Searchable & Sortable Data Table</h2>
484
+ <div style="display:flex;gap:8px;align-items:center">
485
+ <input id="tableSearch" placeholder="Search table..." style="padding:8px;border-radius:8px;background:transparent;border:1px solid rgba(255,255,255,0.03);color:var(--muted)"/>
486
+ <button class="btn" id="copyTable">Copy selection</button>
487
+ </div>
488
+ </div>
489
+
490
+ <div class="data-table" style="margin-top:8px">
491
+ <table id="dataTable" aria-label="Economic indicators table">
492
+ <thead>
493
+ <tr>
494
+ <th data-key="period">Period <span class="sort muted">↕</span></th>
495
+ <th data-key="gdp">GDP Growth %</th>
496
+ <th data-key="inflation">Inflation %</th>
497
+ <th data-key="unemployment">Unemployment %</th>
498
+ <th data-key="fdi">FDI (US$B)</th>
499
+ </tr>
500
+ </thead>
501
+ <tbody id="tableBody"></tbody>
502
+ </table>
503
+ </div>
504
+ </section>
505
+
506
+ <!-- Outlook and references -->
507
+ <section id="outlook" class="card">
508
+ <div class="section-title">
509
+ <h2>Outlook & Policy Recommendations</h2>
510
+ <div class="muted">Near-term prospects, risks and mitigation</div>
511
+ </div>
512
+
513
+ <div style="display:grid;grid-template-columns:1fr 320px;gap:12px">
514
+ <div>
515
+ <h3 style="margin-top:0">Near-term Prospects</h3>
516
+ <p class="muted">Vietnam began 2025 strongly. However, external trade tensions and uncertainty may moderate growth. Strong domestic fundamentals (FDI, low unemployment, controlled inflation) support a resilient outcome.</p>
517
+
518
+ <h3 style="margin-top:12px">Risk Mitigation</h3>
519
+ <ul class="muted" style="margin:6px 0 0 18px;line-height:1.6">
520
+ <li>Diversify export markets and move up value chains</li>
521
+ <li>Manage credit growth to avoid overheating and inflation</li>
522
+ <li>Strengthen social safety nets and fiscal space for targeted support</li>
523
+ <li>Encourage sustainable FDI and local value addition</li>
524
+ </ul>
525
+ </div>
526
+
527
+ <aside>
528
+ <div class="card" style="padding:12px">
529
+ <div style="display:flex;justify-content:space-between;align-items:center">
530
+ <div><strong>Projection Scenarios</strong><div class="muted" style="font-size:13px">Base / Optimistic / Downside</div></div>
531
+ <div class="badge">2025</div>
532
+ </div>
533
+ <div style="margin-top:10px">
534
+ <div style="display:flex;justify-content:space-between"><div class="muted">Base</div><strong>5.8–6.6%</strong></div>
535
+ <div style="display:flex;justify-content:space-between"><div class="muted">Optimistic</div><strong>7.0–8.5%</strong></div>
536
+ <div style="display:flex;justify-content:space-between"><div class="muted">Downside</div><strong>3.5–5.0%</strong></div>
537
+ </div>
538
+ </div>
539
+
540
+ <div class="card" style="padding:10px;margin-top:12px">
541
+ <strong>Quick Links</strong>
542
+ <div class="muted" style="margin-top:8px;font-size:13px">IMF • ADB • World Bank • GSO</div>
543
+ </div>
544
+ </aside>
545
+ </div>
546
+ </section>
547
+
548
+ <!-- Appendix / References -->
549
+ <section id="appendix" class="card">
550
+ <div class="section-title">
551
+ <h2>References & Appendix</h2>
552
+ <div class="muted">Source attribution and raw data</div>
553
+ </div>
554
+
555
+ <div style="display:flex;gap:12px;align-items:flex-start;flex-wrap:wrap">
556
+ <div style="flex:1">
557
+ <h3 style="margin-top:0">Sources</h3>
558
+ <ol class="muted" id="references">
559
+ <li>Trading Economics - Vietnam GDP Annual Growth Rate (tradingeconomics.com)</li>
560
+ <li>International Monetary Fund - Vietnam Country Profile (imf.org)</li>
561
+ <li>World Bank - Vietnam (worldbank.org)</li>
562
+ <li>General Statistics Office - Vietnam (gso.gov.vn)</li>
563
+ <li>FocusEconomics, Vietnam Briefing, Vietnam Investment Review</li>
564
+ </ol>
565
+ </div>
566
+
567
+ <div style="width:320px">
568
+ <details open>
569
+ <summary><strong>Appendix: Raw Data</strong></summary>
570
+ <pre id="rawData" style="white-space:pre-wrap;background:transparent;color:var(--muted);margin-top:8px;font-size:13px"></pre>
571
+ </details>
572
+ </div>
573
+ </div>
574
+
575
+ <footer style="margin-top:12px">
576
+ <div class="muted">© Vietnam Economic Growth Report 2025 — Interactive</div>
577
+ <div class="sources"><span class="muted">Data sources available in references</span></div>
578
+ </footer>
579
+ </section>
580
+ </main>
581
+ </div>
582
+
583
+ <script>
584
+ /* ========= Data Model ========= */
585
+ const DATA = {
586
+ // Quarterly GDP growth (YoY) for 2024 Q1-Q4 and 2025 Q1-Q2
587
+ gdpQuarterly: [
588
+ {period:'2024 Q1', value:5.98}, {period:'2024 Q2', value:6.3}, {period:'2024 Q3', value:6.8}, {period:'2024 Q4', value:7.1},
589
+ {period:'2025 Q1', value:6.9}, {period:'2025 Q2', value:7.96}
590
+ ],
591
+ // Annual GDP 2020-2025 (last is 2025 forecast/highlight)
592
+ gdpAnnual: [
593
+ {year:2020, value:3.21}, {year:2021, value:4.85}, {year:2022, value:5.42}, {year:2023, value:3.46}, {year:2024, value:5.98}, {year:2025, value:7.52}
594
+ ],
595
+ inflation: [
596
+ {period:'May 2025', value:3.24}, {period:'June 2025', value:3.57}, {period:'IMF Forecast 2025', value:2.9}, {period:'ADB Forecast 2025', value:4.0}
597
+ ],
598
+ sectors: [
599
+ {name:'Services', share:45, color:'#06b6d4'},
600
+ {name:'Manufacturing', share:28, color:'#7c3aed'},
601
+ {name:'Agriculture', share:10, color:'#34d399'},
602
+ {name:'Construction', share:8, color:'#f59e0b'},
603
+ {name:'Other', share:9, color:'#ef4444'}
604
+ ],
605
+ monthly: {
606
+ months: ['Jan','Feb','Mar','Apr','May','Jun'],
607
+ inflation: [2.8, 2.9, 3.1, 3.0, 3.24, 3.57],
608
+ fdiMonthlyB: [2.8, 3.1, 3.4, 4.0, 3.5, 4.7], // illustrative
609
+ retailIndex: [98,99,100,102,104,106]
610
+ },
611
+ regionsFDI: [
612
+ {region:'North', fdi:7.0, gdpGrowth:7.2, pts:1},
613
+ {region:'Central', fdi:3.5, gdpGrowth:6.1, pts:2},
614
+ {region:'South', fdi:11.0, gdpGrowth:8.0, pts:3}
615
+ ],
616
+ tableRows: [
617
+ {period:'2020', gdp:3.21, inflation:3.0, unemployment:3.2, fdi:9.1},
618
+ {period:'2021', gdp:4.85, inflation:2.6, unemployment:2.9, fdi:10.5},
619
+ {period:'2022', gdp:5.42, inflation:3.5, unemployment:2.7, fdi:12.0},
620
+ {period:'2023', gdp:3.46, inflation:2.9, unemployment:2.6, fdi:14.8},
621
+ {period:'2024', gdp:5.98, inflation:3.1, unemployment:2.22, fdi:16.3},
622
+ {period:'2025 H1', gdp:7.52, inflation:3.57, unemployment:2.20, fdi:21.51}
623
+ ],
624
+ references: [
625
+ {title:'Trading Economics - Vietnam GDP Annual Growth Rate', url:'https://tradingeconomics.com/vietnam/gdp-growth-annual'},
626
+ {title:'IMF - Vietnam Country Profile', url:'https://www.imf.org/en/Countries/VNM'},
627
+ {title:'World Bank - Vietnam', url:'https://www.worldbank.org/en/country/vietnam'},
628
+ {title:'General Statistics Office - Vietnam', url:'https://www.gso.gov.vn/en/'}
629
+ ]
630
+ };
631
+
632
+ /* ========= Utilities ========= */
633
+ function q(selector, ctx=document){ return ctx.querySelector(selector) }
634
+ function qq(selector, ctx=document){ return Array.from(ctx.querySelectorAll(selector)) }
635
+ function format(n, decimals=2){ return (Math.round(n*Math.pow(10,decimals))/Math.pow(10,decimals)).toLocaleString(); }
636
+
637
+ /* ========= Populate Raw Data & References ========= */
638
+ document.getElementById('rawData').textContent = JSON.stringify(DATA, null, 2);
639
+ const refsEl = q('#references');
640
+ refsEl.innerHTML = '';
641
+ DATA.references.forEach(r=>{
642
+ const li = document.createElement('li');
643
+ li.className = 'muted';
644
+ li.innerHTML = `<a href="${r.url}" target="_blank" rel="noreferrer" style="color:var(--muted)">${r.title}</a>`;
645
+ refsEl.appendChild(li);
646
+ });
647
+
648
+ /* ========= Table Rendering, Search & Sorting ========= */
649
+ const tableBody = q('#tableBody');
650
+ let tableRows = [...DATA.tableRows];
651
+ let currentSort = {key:null, dir:1};
652
+
653
+ function renderTable(rows){
654
+ tableBody.innerHTML = '';
655
+ rows.forEach(r=>{
656
+ const tr = document.createElement('tr');
657
+ tr.innerHTML = `<td>${r.period}</td><td>${r.gdp}</td><td>${r.inflation}</td><td>${r.unemployment}</td><td>${r.fdi}</td>`;
658
+ tableBody.appendChild(tr);
659
+ });
660
+ }
661
+ renderTable(tableRows);
662
+
663
+ q('#tableSearch').addEventListener('input', e=>{
664
+ const qv = e.target.value.toLowerCase();
665
+ const filtered = tableRows.filter(r => JSON.stringify(r).toLowerCase().includes(qv));
666
+ renderTable(filtered);
667
+ });
668
+
669
+ qq('th[data-key]').forEach(th=>{
670
+ th.addEventListener('click', ()=>{
671
+ const key = th.getAttribute('data-key');
672
+ const dir = currentSort.key === key ? -currentSort.dir : 1;
673
+ currentSort = {key, dir};
674
+ tableRows.sort((a,b)=>{
675
+ if (a[key] < b[key]) return -1*dir;
676
+ if (a[key] > b[key]) return 1*dir;
677
+ return 0;
678
+ });
679
+ renderTable(tableRows);
680
+ });
681
+ });
682
+
683
+ /* ========= Copy & Export Handlers ========= */
684
+ q('#copySummary').addEventListener('click', async ()=>{
685
+ const text = `Executive Summary:\nVietnam recorded 7.52% GDP growth in H1 2025. Q2 2025 grew 7.96% YoY. Strong FDI, low unemployment (2.20%), and controlled inflation (3.57% June) underpin growth. Sources: GSO, IMF, ADB, World Bank.`;
686
+ await navigator.clipboard.writeText(text);
687
+ flashButton(q('#copySummary'), 'Copied');
688
+ });
689
+
690
+ q('#copyRetail').addEventListener('click', async ()=>{
691
+ const txt = 'Q1 2025 Retail Sales: 1.708 quadrillion VND (~US$66.83B), +9.9% YoY.';
692
+ await navigator.clipboard.writeText(txt);
693
+ flashButton(q('#copyRetail'),'Copied');
694
+ });
695
+
696
+ q('#copyTable').addEventListener('click', async ()=>{
697
+ // copy current table view
698
+ let rows = Array.from(tableBody.querySelectorAll('tr')).map(tr => Array.from(tr.children).map(td=>td.textContent).join('\t')).join('\n');
699
+ await navigator.clipboard.writeText(rows);
700
+ flashButton(q('#copyTable'),'Copied');
701
+ });
702
+
703
+ q('#exportCSV').addEventListener('click', ()=>{
704
+ const rows = tableRows;
705
+ const csv = ['Period,GDP Growth %,Inflation %,Unemployment %,FDI (US$B)', ...rows.map(r=>`${r.period},${r.gdp},${r.inflation},${r.unemployment},${r.fdi}`)].join('\n');
706
+ const blob = new Blob([csv], {type:'text/csv'});
707
+ const url = URL.createObjectURL(blob);
708
+ const a = document.createElement('a'); a.href=url; a.download='vietnam_econ_2025.csv'; a.click(); URL.revokeObjectURL(url);
709
+ });
710
+
711
+ function flashButton(btn, text){
712
+ const prev = btn.innerHTML;
713
+ btn.innerHTML = `<i class="fa fa-check"></i> ${text}`;
714
+ setTimeout(()=>btn.innerHTML = prev, 1400);
715
+ }
716
+
717
+ /* ========= Theme toggle using localStorage ========= */
718
+ const themeToggle = q('#themeToggle');
719
+ function applyTheme(dark=true){
720
+ if(!dark){
721
+ document.documentElement.style.setProperty('--bg','#f7fbff');
722
+ document.documentElement.style.setProperty('--card','#ffffff');
723
+ document.documentElement.style.setProperty('--muted','#475569');
724
+ document.documentElement.style.setProperty('--accent','#0ea5a4');
725
+ document.documentElement.style.setProperty('--accent-2','#8b5cf6');
726
+ document.documentElement.style.setProperty('--glass','rgba(0,0,0,0.04)');
727
+ document.body.style.color = '#071a2a';
728
+ } else {
729
+ document.documentElement.style.setProperty('--bg','#0f1724');
730
+ document.documentElement.style.setProperty('--card','#0b1220');
731
+ document.documentElement.style.setProperty('--muted','#94a3b8');
732
+ document.documentElement.style.setProperty('--accent','#06b6d4');
733
+ document.documentElement.style.setProperty('--accent-2','#7c3aed');
734
+ document.documentElement.style.setProperty('--glass','rgba(255,255,255,0.03)');
735
+ document.body.style.color = '#e6eef6';
736
+ }
737
+ localStorage.setItem('themeDark', dark ? '1' : '0');
738
+ themeToggle.innerHTML = `<i class="fa ${dark ? 'fa-moon' : 'fa-sun'}"></i>`;
739
+ }
740
+ themeToggle.addEventListener('click', ()=> applyTheme(localStorage.getItem('themeDark')==='1' ? false : true));
741
+ applyTheme(localStorage.getItem('themeDark') !== '0');
742
+
743
+ /* ========= SVG Chart Helpers ========= */
744
+ function createSVG(w=800,h=300){
745
+ const svgns = "http://www.w3.org/2000/svg";
746
+ const svg = document.createElementNS(svgns,'svg');
747
+ svg.setAttribute('viewBox',`0 0 ${w} ${h}`);
748
+ return svg;
749
+ }
750
+
751
+ // utility to map value onto axis
752
+ function linearScale(domainMin, domainMax, outMin, outMax){
753
+ const m = (outMax - outMin) / (domainMax - domainMin || 1);
754
+ return function(v){ return outMin + (v - domainMin) * m; };
755
+ }
756
+
757
+ // tooltip helper
758
+ let tooltip = document.createElement('div');
759
+ tooltip.style.position='fixed';
760
+ tooltip.style.pointerEvents='none';
761
+ tooltip.style.background='rgba(2,6,23,0.9)';
762
+ tooltip.style.padding='8px 10px';
763
+ tooltip.style.borderRadius='8px';
764
+ tooltip.style.color='#dff6ff';
765
+ tooltip.style.fontSize='13px';
766
+ tooltip.style.display='none';
767
+ tooltip.style.zIndex=9999;
768
+ document.body.appendChild(tooltip);
769
+
770
+ function showTip(html, x, y){
771
+ tooltip.innerHTML = html;
772
+ tooltip.style.left = (x+12)+'px';
773
+ tooltip.style.top = (y+12)+'px';
774
+ tooltip.style.display = 'block';
775
+ tooltip.style.opacity = '1';
776
+ }
777
+ function hideTip(){ tooltip.style.display='none'; }
778
+
779
+ /* ========= Draw Quarterly GDP (Line + Bars) ========= */
780
+ function drawGDPQuarter(){
781
+ const svg = q('#svgGDPQ');
782
+ svg.innerHTML = '';
783
+ const w = 800, h = 300, pad = {l:48,r:20,t:20,b:40};
784
+ const data = DATA.gdpQuarterly;
785
+ const xs = data.map((d,i)=> pad.l + i*((w-pad.l-pad.r)/(data.length-1 || 1)));
786
+ const yVals = data.map(d=>d.value);
787
+ const yMin = Math.min(...yVals) - 1, yMax = Math.max(...yVals) + 1;
788
+ const yScale = linearScale(yMin,yMax,h-pad.b,pad.t);
789
+ // axes lines & labels
790
+ const yAxis = document.createElementNS("http://www.w3.org/2000/svg",'g');
791
+ for(let i=0;i<=4;i++){
792
+ const val = yMin + (i/4)*(yMax-yMin);
793
+ const y = yScale(val);
794
+ const line = document.createElementNS("http://www.w3.org/2000/svg",'line');
795
+ line.setAttribute('x1',pad.l);line.setAttribute('x2',w-pad.r);
796
+ line.setAttribute('y1',y);line.setAttribute('y2',y);
797
+ line.setAttribute('stroke','rgba(255,255,255,0.03)');
798
+ yAxis.appendChild(line);
799
+ const txt = document.createElementNS("http://www.w3.org/2000/svg",'text');
800
+ txt.setAttribute('x',10);txt.setAttribute('y',y+4);
801
+ txt.setAttribute('fill','var(--muted)');
802
+ txt.setAttribute('font-size',12);
803
+ txt.textContent = format(val,2) + '%';
804
+ yAxis.appendChild(txt);
805
+ }
806
+ svg.appendChild(yAxis);
807
+
808
+ // bars
809
+ data.forEach((d,i)=>{
810
+ const barW = 28;
811
+ const x = xs[i] - barW/2;
812
+ const y = yScale(d.value);
813
+ const height = h - pad.b - y;
814
+ const rect = document.createElementNS("http://www.w3.org/2000/svg",'rect');
815
+ rect.setAttribute('x',x); rect.setAttribute('y',y); rect.setAttribute('width',barW); rect.setAttribute('height',height);
816
+ rect.setAttribute('fill','rgba(6,182,212,0.25)');
817
+ rect.setAttribute('rx',6);
818
+ svg.appendChild(rect);
819
+ rect.addEventListener('mousemove', (ev)=> showTip(`${d.period}<br><strong>${d.value}%</strong>`, ev.clientX, ev.clientY));
820
+ rect.addEventListener('mouseleave', hideTip);
821
+ });
822
+
823
+ // line path
824
+ const pathD = data.map((d,i)=>{
825
+ const x = xs[i]; const y = yScale(d.value);
826
+ return (i===0 ? `M ${x} ${y}` : `L ${x} ${y}`);
827
+ }).join(' ');
828
+ const path = document.createElementNS("http://www.w3.org/2000/svg",'path');
829
+ path.setAttribute('d', pathD);
830
+ path.setAttribute('fill','none');
831
+ path.setAttribute('stroke','url(#gline)');
832
+ path.setAttribute('stroke-width',3);
833
+ path.setAttribute('stroke-linecap','round');
834
+ path.setAttribute('stroke-linejoin','round');
835
+ // gradient
836
+ const defs = document.createElementNS("http://www.w3.org/2000/svg",'defs');
837
+ defs.innerHTML = `<linearGradient id="gline" x1="0" x2="1" y1="0" y2="0"><stop offset="0" stop-color="${getComputedStyle(document.documentElement).getPropertyValue('--accent').trim()}" /><stop offset="1" stop-color="${getComputedStyle(document.documentElement).getPropertyValue('--accent-2').trim()}" /></linearGradient>`;
838
+ svg.appendChild(defs);
839
+ svg.appendChild(path);
840
+
841
+ // points
842
+ data.forEach((d,i)=>{
843
+ const x = xs[i]; const y = yScale(d.value);
844
+ const circle = document.createElementNS("http://www.w3.org/2000/svg",'circle');
845
+ circle.setAttribute('cx',x); circle.setAttribute('cy',y); circle.setAttribute('r',6);
846
+ circle.setAttribute('fill','#061426');
847
+ circle.setAttribute('stroke','url(#gline)');
848
+ circle.setAttribute('stroke-width',3);
849
+ svg.appendChild(circle);
850
+ circle.addEventListener('mousemove',(ev)=> showTip(`${d.period}<br><strong>${d.value}%</strong>`,ev.clientX,ev.clientY));
851
+ circle.addEventListener('mouseleave', hideTip);
852
+ });
853
+
854
+ // x labels
855
+ data.forEach((d,i)=>{
856
+ const x = xs[i];
857
+ const txt = document.createElementNS("http://www.w3.org/2000/svg",'text');
858
+ txt.setAttribute('x',x); txt.setAttribute('y', h - 8);
859
+ txt.setAttribute('text-anchor','middle');
860
+ txt.setAttribute('fill','var(--muted)');
861
+ txt.setAttribute('font-size',12);
862
+ txt.textContent = d.period.replace('2025 ','');
863
+ svg.appendChild(txt);
864
+ });
865
+ }
866
+
867
+ /* ========= Annual Bar Chart ========= */
868
+ function drawGPDA(){
869
+ const svg = q('#svgGPDA');
870
+ svg.innerHTML = '';
871
+ const data = DATA.gdpAnnual;
872
+ const w=800,h=300,pad={l:48,r:20,t:30,b:40};
873
+ const xs = data.map((d,i)=> pad.l + i*((w-pad.l-pad.r)/(data.length-1 || 1)));
874
+ const yVals = data.map(d=>d.value);
875
+ const yMin = Math.min(...yVals) - 1, yMax = Math.max(...yVals)+1;
876
+ const yScale = linearScale(yMin,yMax,h-pad.b,pad.t);
877
+
878
+ // bars & labels
879
+ data.forEach((d,i)=>{
880
+ const barW = 40;
881
+ const x = xs[i] - barW/2;
882
+ const y = yScale(d.value);
883
+ const height = h - pad.b - y;
884
+ const rect = document.createElementNS("http://www.w3.org/2000/svg",'rect');
885
+ rect.setAttribute('x',x); rect.setAttribute('y',y); rect.setAttribute('width',barW); rect.setAttribute('height',height);
886
+ rect.setAttribute('rx',6);
887
+ rect.setAttribute('fill', i===data.length-1 ? 'url(#gbar)' : 'rgba(124,58,237,0.6)');
888
+ svg.appendChild(rect);
889
+ rect.addEventListener('mousemove',(ev)=> showTip(`${d.year}<br><strong>${d.value}%</strong>`,ev.clientX,ev.clientY));
890
+ rect.addEventListener('mouseleave', hideTip);
891
+
892
+ const txt = document.createElementNS("http://www.w3.org/2000/svg",'text');
893
+ txt.setAttribute('x',x+barW/2); txt.setAttribute('y',h-8); txt.setAttribute('text-anchor','middle');
894
+ txt.setAttribute('fill','var(--muted)');
895
+ txt.textContent = d.year;
896
+ svg.appendChild(txt);
897
+ });
898
+
899
+ const defs = document.createElementNS("http://www.w3.org/2000/svg",'defs');
900
+ defs.innerHTML = `<linearGradient id="gbar" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="${getComputedStyle(document.documentElement).getPropertyValue('--accent-2').trim()}" /><stop offset="1" stop-color="${getComputedStyle(document.documentElement).getPropertyValue('--accent').trim()}" /></linearGradient>`;
901
+ svg.appendChild(defs);
902
+ }
903
+
904
+ /* ========= Inflation Chart (bars & forecast markers) ========= */
905
+ function drawInflation(){
906
+ const svg = q('#svgInfl');
907
+ svg.innerHTML = '';
908
+ const data = DATA.inflation;
909
+ const w=800,h=220,pad={l:60,r:20,t:20,b:40};
910
+ const xs = data.map((d,i)=> pad.l + i*((w-pad.l-pad.r)/(data.length-1 || 1)));
911
+ const yVals = data.map(d=>d.value);
912
+ const yMin = 0, yMax = Math.max(...yVals)+1;
913
+ const yScale = linearScale(yMin,yMax,h-pad.b,pad.t);
914
+
915
+ // bars for May/June, markers for forecasts
916
+ data.forEach((d,i)=>{
917
+ const x = xs[i];
918
+ if(d.period.includes('Forecast')){
919
+ const cx = x;
920
+ const cy = yScale(d.value);
921
+ const circle = document.createElementNS("http://www.w3.org/2000/svg",'circle');
922
+ circle.setAttribute('cx',cx); circle.setAttribute('cy',cy); circle.setAttribute('r',8);
923
+ circle.setAttribute('fill',d.period.includes('IMF') ? '#f97316' : '#60a5fa');
924
+ svg.appendChild(circle);
925
+ circle.addEventListener('mousemove',(ev)=> showTip(`${d.period}<br><strong>${d.value}%</strong>`,ev.clientX,ev.clientY));
926
+ circle.addEventListener('mouseleave', hideTip);
927
+ } else {
928
+ const barW = 40;
929
+ const y = yScale(d.value);
930
+ const height = h - pad.b - y;
931
+ const rect = document.createElementNS("http://www.w3.org/2000/svg",'rect');
932
+ rect.setAttribute('x',x - barW/2); rect.setAttribute('y',y); rect.setAttribute('width',barW); rect.setAttribute('height',height);
933
+ rect.setAttribute('fill','rgba(124,58,237,0.5)');
934
+ rect.setAttribute('rx',8);
935
+ svg.appendChild(rect);
936
+ rect.addEventListener('mousemove',(ev)=> showTip(`${d.period}<br><strong>${d.value}%</strong>`,ev.clientX,ev.clientY));
937
+ rect.addEventListener('mouseleave', hideTip);
938
+ }
939
+
940
+ const txt = document.createElementNS("http://www.w3.org/2000/svg",'text');
941
+ txt.setAttribute('x',x); txt.setAttribute('y',h-8); txt.setAttribute('text-anchor','middle'); txt.setAttribute('fill','var(--muted)');
942
+ txt.setAttribute('font-size',12);
943
+ txt.textContent = d.period.replace(' 2025','').replace('Forecast ','');
944
+ svg.appendChild(txt);
945
+ });
946
+ }
947
+
948
+ /* ========= Sector Pie Chart ========= */
949
+ function drawSectorPie(){
950
+ const svg = q('#svgSector');
951
+ svg.innerHTML = '';
952
+ const w=800,h=300,cx=w/2,cy=h/2,r=90;
953
+ const total = DATA.sectors.reduce((s,el)=>s+el.share,0);
954
+ let startAngle = -Math.PI/2;
955
+ DATA.sectors.forEach(s=>{
956
+ const slice = (s.share/total) * Math.PI*2;
957
+ const end = startAngle + slice;
958
+ const x1 = cx + r*Math.cos(startAngle), y1 = cy + r*Math.sin(startAngle);
959
+ const x2 = cx + r*Math.cos(end), y2 = cy + r*Math.sin(end);
960
+ const large = slice > Math.PI ? 1 : 0;
961
+ const path = document.createElementNS("http://www.w3.org/2000/svg",'path');
962
+ const d = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
963
+ path.setAttribute('d', d);
964
+ path.setAttribute('fill', s.color);
965
+ path.setAttribute('stroke','rgba(0,0,0,0.15)');
966
+ path.setAttribute('stroke-width',1);
967
+ svg.appendChild(path);
968
+ path.addEventListener('mousemove',(ev)=> showTip(`${s.name}<br><strong>${s.share}%</strong>`,ev.clientX,ev.clientY));
969
+ path.addEventListener('mouseleave', hideTip);
970
+ startAngle = end;
971
+ });
972
+
973
+ // legend
974
+ const legend = q('#legendSector');
975
+ legend.innerHTML = '';
976
+ DATA.sectors.forEach(s=>{
977
+ const sp = document.createElement('span');
978
+ sp.innerHTML = `<i style="background:${s.color}"></i> ${s.name} <strong style="margin-left:6px">${s.share}%</strong>`;
979
+ legend.appendChild(sp);
980
+ });
981
+ }
982
+
983
+ /* ========= Scatter (FDI vs GDP) ========= */
984
+ function drawScatter(){
985
+ const svg = q('#svgScatter');
986
+ svg.innerHTML = '';
987
+ const data = DATA.regionsFDI;
988
+ const w=800,h=300,pad={l:60,r:40,t:20,b:40};
989
+ const xVals = data.map(d=>d.fdi), yVals = data.map(d=>d.gdpGrowth);
990
+ const xMin = 0, xMax = Math.max(...xVals)+2, yMin = Math.min(...yVals)-1, yMax = Math.max(...yVals)+1;
991
+ const xScale = linearScale(xMin,xMax,pad.l,w-pad.r);
992
+ const yScale = linearScale(yMin,yMax,h-pad.b,pad.t);
993
+
994
+ // axes grid
995
+ for(let i=0;i<=4;i++){
996
+ const y = yScale(yMin + (i/4)*(yMax-yMin));
997
+ const line = document.createElementNS("http://www.w3.org/2000/svg",'line');
998
+ line.setAttribute('x1',pad.l); line.setAttribute('x2',w-pad.r); line.setAttribute('y1',y); line.setAttribute('y2',y);
999
+ line.setAttribute('stroke','rgba(255,255,255,0.03)');
1000
+ svg.appendChild(line);
1001
+ }
1002
+
1003
+ // points
1004
+ data.forEach(d=>{
1005
+ const cx = xScale(d.fdi);
1006
+ const cy = yScale(d.gdpGrowth);
1007
+ const g = document.createElementNS("http://www.w3.org/2000/svg",'g');
1008
+ const circle = document.createElementNS("http://www.w3.org/2000/svg",'circle');
1009
+ circle.setAttribute('cx',cx); circle.setAttribute('cy',cy); circle.setAttribute('r',10);
1010
+ circle.setAttribute('fill','rgba(124,58,237,0.85)');
1011
+ circle.setAttribute('opacity',0.95);
1012
+ const txt = document.createElementNS("http://www.w3.org/2000/svg",'text');
1013
+ txt.setAttribute('x',cx+14); txt.setAttribute('y',cy+4); txt.setAttribute('fill','var(--muted)'); txt.textContent = d.region;
1014
+ g.appendChild(circle); g.appendChild(txt);
1015
+ svg.appendChild(g);
1016
+ g.addEventListener('mousemove',(ev)=> showTip(`${d.region}<br>FDI: ${d.fdi}B<br>GDP Growth: ${d.gdpGrowth}%`,ev.clientX,ev.clientY));
1017
+ g.addEventListener('mouseleave', hideTip);
1018
+ });
1019
+
1020
+ // axes labels
1021
+ const xlabel = document.createElementNS("http://www.w3.org/2000/svg",'text');
1022
+ xlabel.setAttribute('x',w/2); xlabel.setAttribute('y',h-6); xlabel.setAttribute('text-anchor','middle'); xlabel.setAttribute('fill','var(--muted)');
1023
+ xlabel.textContent = 'FDI (US$B)';
1024
+ svg.appendChild(xlabel);
1025
+ const ylabel = document.createElementNS("http://www.w3.org/2000/svg",'text');
1026
+ ylabel.setAttribute('x',14); ylabel.setAttribute('y',20); ylabel.setAttribute('fill','var(--muted)');
1027
+ ylabel.textContent = 'GDP Growth %';
1028
+ svg.appendChild(ylabel);
1029
+ }
1030
+
1031
+ /* ========= Heatmap ========= */
1032
+ function drawHeatmap(){
1033
+ const container = q('#heatmap');
1034
+ container.innerHTML = '';
1035
+ const m = DATA.monthly.months;
1036
+ const maxInfl = Math.max(...DATA.monthly.inflation);
1037
+ const maxFdi = Math.max(...DATA.monthly.fdiMonthlyB);
1038
+ const maxRetail = Math.max(...DATA.monthly.retailIndex);
1039
+ // we'll create 3 rows: inflation, fdi, retail
1040
+ const rows = [
1041
+ {label:'Inflation %', values:DATA.monthly.inflation, max:maxInfl, color:'#f97316'},
1042
+ {label:'Monthly FDI (US$B)', values:DATA.monthly.fdiMonthlyB, max:maxFdi, color:'#06b6d4'},
1043
+ {label:'Retail Index', values:DATA.monthly.retailIndex, max:maxRetail, color:'#7c3aed'}
1044
+ ];
1045
+
1046
+ rows.forEach(row=>{
1047
+ // header cell
1048
+ const head = document.createElement('div');
1049
+ head.className='heat-cell';
1050
+ head.style.background='transparent';
1051
+ head.style.alignItems='center';
1052
+ head.style.justifyContent='flex-start';
1053
+ head.style.fontWeight='600';
1054
+ head.style.color='var(--muted)';
1055
+ head.textContent = row.label;
1056
+ head.style.gridColumn = 'span 2';
1057
+ container.appendChild(head);
1058
+ // months
1059
+ row.values.forEach((v,i)=>{
1060
+ const cell = document.createElement('div');
1061
+ cell.className='heat-cell';
1062
+ const intensity = v/row.max;
1063
+ const base = hexToRgb(row.color);
1064
+ const bg = `rgba(${base.r},${base.g},${base.b},${0.2+intensity*0.75})`;
1065
+ cell.style.background = bg;
1066
+ cell.textContent = v;
1067
+ cell.title = `${row.label} ${m[i]}: ${v}`;
1068
+ cell.addEventListener('mouseenter', (ev)=> showTip(`${row.label} — ${m[i]}<br><strong>${v}</strong>`, ev.clientX, ev.clientY));
1069
+ cell.addEventListener('mouseleave', hideTip);
1070
+ container.appendChild(cell);
1071
+ });
1072
+ });
1073
+ }
1074
+ function hexToRgb(hex){
1075
+ const c = hex.replace('#','');
1076
+ return { r: parseInt(c.substring(0,2),16), g: parseInt(c.substring(2,4),16), b: parseInt(c.substring(4,6),16) };
1077
+ }
1078
+
1079
+ /* ========= Legend & Interactivity ========= */
1080
+ function initLegends(){
1081
+ q('#legendGDPQ').innerHTML = `<span><i style="background:linear-gradient(90deg,var(--accent),var(--accent-2))"></i> Line</span>`;
1082
+ q('#legendGPDA').innerHTML = `<span><i style="background:var(--accent-2)"></i> Annual</span>`;
1083
+ q('#legendInfl').innerHTML = `<span><i style="background:rgba(124,58,237,0.6)"></i> May/June</span><span><i style="background:#f97316"></i> IMF</span><span><i style="background:#60a5fa"></i> ADB</span>`;
1084
+ q('#legendScatter').innerHTML = `<span class="muted">Regions (size ~ relative)</span>`;
1085
+ }
1086
+
1087
+ /* ========= Render everything initially and on visibility ========= */
1088
+ function renderAll(){
1089
+ drawGDPQuarter();
1090
+ drawGPDA();
1091
+ drawInflation();
1092
+ drawSectorPie();
1093
+ drawScatter();
1094
+ drawHeatmap();
1095
+ initLegends();
1096
+ }
1097
+ renderAll();
1098
+
1099
+ /* Animate on scroll into view using IntersectionObserver */
1100
+ const io = new IntersectionObserver((entries)=>{
1101
+ entries.forEach(en=>{
1102
+ if(en.isIntersecting){
1103
+ const id = en.target.id;
1104
+ if(id==='visualizations' || id==='indicators'){
1105
+ triggerChartDraws();
1106
+ }
1107
+ }
1108
+ });
1109
+ }, {threshold:0.2});
1110
+ ['indicators','visualizations','table','sectoral','appendix'].forEach(id=>{
1111
+ const el = document.getElementById(id);
1112
+ if(el) io.observe(el);
1113
+ });
1114
+
1115
+ function triggerChartDraws(){
1116
+ // small animation: update KPIs from dataset
1117
+ q('#kpiGdp').animate([{opacity:0},{opacity:1}],{duration:600});
1118
+ q('#kpiGdp').textContent = DATA.gdpAnnual[DATA.gdpAnnual.length-1].value + '%';
1119
+ }
1120
+
1121
+ /* ========= Export SVG to PNG ========= */
1122
+ q('#exportPNG').addEventListener('click', async ()=>{
1123
+ const svgEl = document.querySelector('#svgGPDA') || document.querySelector('svg');
1124
+ const serializer = new XMLSerializer();
1125
+ const source = serializer.serializeToString(svgEl);
1126
+ const svgBlob = new Blob([source], {type:'image/svg+xml;charset=utf-8'});
1127
+ const url = URL.createObjectURL(svgBlob);
1128
+ const img = new Image();
1129
+ img.onload = function(){
1130
+ const canvas = document.createElement('canvas');
1131
+ canvas.width = img.width; canvas.height = img.height;
1132
+ const ctx = canvas.getContext('2d');
1133
+ ctx.fillStyle = getComputedStyle(document.body).backgroundColor || '#071021';
1134
+ ctx.fillRect(0,0,canvas.width,canvas.height);
1135
+ ctx.drawImage(img,0,0);
1136
+ URL.revokeObjectURL(url);
1137
+ canvas.toBlob(function(blob){
1138
+ const link = document.createElement('a');
1139
+ link.href = URL.createObjectURL(blob);
1140
+ link.download = 'chart.png';
1141
+ link.click();
1142
+ });
1143
+ };
1144
+ img.src = url;
1145
+ });
1146
+
1147
+ /* ========= TOC interactions & progress indicator ========= */
1148
+ const tocLinks = qq('.toc a');
1149
+ tocLinks.forEach(a=>{
1150
+ a.addEventListener('click', (e)=>{
1151
+ tocLinks.forEach(x=>x.classList.remove('active'));
1152
+ a.classList.add('active');
1153
+ // smooth scroll handled by CSS scroll-behavior
1154
+ });
1155
+ });
1156
+
1157
+ // highlight on scroll
1158
+ const sections = Array.from(document.querySelectorAll('main > section, main > header'));
1159
+ const secObserver = new IntersectionObserver((entries)=>{
1160
+ entries.forEach(en=>{
1161
+ const id = en.target.id;
1162
+ const link = document.querySelector(`.toc a[data-target="${id}"]`);
1163
+ if(link){
1164
+ if(en.isIntersecting) link.classList.add('active');
1165
+ else link.classList.remove('active');
1166
+ }
1167
+ });
1168
+ // overall progress
1169
+ const visible = sections.filter(s=> s.getBoundingClientRect().top < window.innerHeight*0.6 && s.getBoundingClientRect().bottom > window.innerHeight*0.2);
1170
+ const idx = Math.max(0, Math.min(sections.indexOf(visible[0]) || 0, sections.length-1));
1171
+ const pct = Math.round(((idx+1)/sections.length)*100);
1172
+ q('#overallProgress').style.width = pct + '%';
1173
+ }, {threshold:0.35});
1174
+ sections.forEach(s=>secObserver.observe(s));
1175
+
1176
+ /* ========= Search TOC filter ========= */
1177
+ q('#tocFilter').addEventListener('input', (e)=>{
1178
+ const qv = e.target.value.toLowerCase();
1179
+ qq('.toc-content a').forEach(a=>{
1180
+ a.style.display = a.textContent.toLowerCase().includes(qv) ? 'flex' : 'none';
1181
+ });
1182
+ });
1183
+
1184
+ /* ========= Filter series and reset ========= */
1185
+ q('#filterSeries').addEventListener('input', (e)=>{
1186
+ const qv = e.target.value.toLowerCase();
1187
+ // filter sectors pie by name
1188
+ if(!qv){
1189
+ drawSectorPie();
1190
+ } else {
1191
+ const filtered = DATA.sectors.filter(s => s.name.toLowerCase().includes(qv));
1192
+ if(filtered.length>0){
1193
+ // temporarily draw simple pie
1194
+ const svg = q('#svgSector');
1195
+ svg.innerHTML = '';
1196
+ const w=800,h=300,cx=w/2,cy=h/2,r=90;
1197
+ const total = filtered.reduce((s,el)=>s+el.share,0);
1198
+ let startAngle = -Math.PI/2;
1199
+ filtered.forEach(s=>{
1200
+ const slice = (s.share/total) * Math.PI*2;
1201
+ const end = startAngle + slice;
1202
+ const x1 = cx + r*Math.cos(startAngle), y1 = cy + r*Math.sin(startAngle);
1203
+ const x2 = cx + r*Math.cos(end), y2 = cy + r*Math.sin(end);
1204
+ const large = slice > Math.PI ? 1 : 0;
1205
+ const path = document.createElementNS("http://www.w3.org/2000/svg",'path');
1206
+ const d = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
1207
+ path.setAttribute('d', d);
1208
+ path.setAttribute('fill', s.color);
1209
+ svg.appendChild(path);
1210
+ startAngle = end;
1211
+ });
1212
+ }
1213
+ }
1214
+ });
1215
+
1216
+ q('#resetView').addEventListener('click', ()=>{
1217
+ q('#filterSeries').value='';
1218
+ drawSectorPie();
1219
+ drawGDPQuarter();
1220
+ drawGPDA();
1221
+ drawInflation();
1222
+ drawScatter();
1223
+ drawHeatmap();
1224
+ });
1225
+
1226
+ /* ========= Small UX touches ========= */
1227
+ // copy table on double click row
1228
+ tableBody.addEventListener('dblclick', async (e)=>{
1229
+ const tr = e.target.closest('tr');
1230
+ if(!tr) return;
1231
+ const txt = Array.from(tr.children).map(td=>td.textContent).join('\t');
1232
+ await navigator.clipboard.writeText(txt);
1233
+ flashButton(q('#copyTable'),'Row copied');
1234
+ });
1235
+
1236
+ // localStorage remember last table search
1237
+ q('#tableSearch').value = localStorage.getItem('tableSearch') || '';
1238
+ q('#tableSearch').addEventListener('input', (e)=> localStorage.setItem('tableSearch', e.target.value));
1239
+
1240
+ // small keyboard shortcut: "/" focus table search
1241
+ window.addEventListener('keydown', (e)=>{
1242
+ if(e.key === '/') { e.preventDefault(); q('#tableSearch').focus(); }
1243
+ });
1244
+
1245
+ // enable opening scatter focus
1246
+ q('#openScatter').addEventListener('click', ()=> {
1247
+ document.getElementById('chartScatter').scrollIntoView({behavior:'smooth', block:'center'});
1248
+ });
1249
+
1250
+ /* ========= Accessibility & small helpers ========= */
1251
+ // set ARIA labels on interactive elements
1252
+ qq('.chart').forEach(c=> c.setAttribute('role','region'));
1253
+ qq('.btn').forEach(b=> b.setAttribute('role','button'));
1254
+
1255
+ // initial active toc
1256
+ const first = document.querySelector('.toc a');
1257
+ if(first) first.classList.add('active');
1258
+
1259
+ // small debounce helper
1260
+ function debounce(fn, wait=200){
1261
+ let t;
1262
+ return (...args)=> { clearTimeout(t); t = setTimeout(()=>fn(...args), wait); };
1263
+ }
1264
+
1265
+ // window resize => re-render SVG charts for responsive
1266
+ window.addEventListener('resize', debounce(()=>{ renderAll(); }, 200));
1267
+
1268
+ </script>
1269
+ </body>
1270
+ </html>