thucdangvan020999 commited on
Commit
6f75235
·
verified ·
1 Parent(s): 0f92d4d

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1403 -18
index.html CHANGED
@@ -1,19 +1,1404 @@
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
+
8
+ <!-- Icon library -->
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" integrity="sha512-YWzhKL2whUzgiheMoBX6J0wX4k7Y7f2Qf5bq6Zx9q5c5U4e/N1eG1Gzv6+zR5qG7kZzqX3Qb7x3M4f3eXK0zLg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
10
+
11
+ <style>
12
+ /* ========== Root variables ========== */
13
+ :root{
14
+ --bg: #f6f8fb;
15
+ --card: #ffffff;
16
+ --muted: #6b7280;
17
+ --accent: linear-gradient(90deg,#0ea5a9 0%, #3b82f6 100%);
18
+ --accent-solid: #0ea5a9;
19
+ --accent-2: #3b82f6;
20
+ --success: #10b981;
21
+ --danger: #ef4444;
22
+ --glass: rgba(255,255,255,0.6);
23
+ --shadow: 0 6px 20px rgba(23,26,33,0.07);
24
+ --radius: 12px;
25
+ --max-width: 1200px;
26
+ --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
27
+ }
28
+
29
+ /* ========== Global ========== */
30
+ *{box-sizing:border-box}
31
+ html,body{height:100%}
32
+ body{
33
+ margin:0;
34
+ font-family:var(--font-sans);
35
+ background:linear-gradient(180deg,#f3f6fb 0%, #f6f8fb 100%);
36
+ color:#0f172a;
37
+ -webkit-font-smoothing:antialiased;
38
+ -moz-osx-font-smoothing:grayscale;
39
+ line-height:1.45;
40
+ font-size:clamp(14px, 1.6vw, 16px);
41
+ padding-bottom:64px;
42
+ }
43
+
44
+ /* container layout */
45
+ .wrap{
46
+ width: min(96%, var(--max-width));
47
+ margin:16px auto;
48
+ display:grid;
49
+ grid-template-columns: 1fr;
50
+ gap:16px;
51
+ }
52
+
53
+ header.topbar{
54
+ background:var(--card);
55
+ border-radius:var(--radius);
56
+ padding:14px;
57
+ display:flex;
58
+ gap:12px;
59
+ align-items:center;
60
+ box-shadow:var(--shadow);
61
+ position:relative;
62
+ overflow:hidden;
63
+ }
64
+ .brand{
65
+ display:flex;
66
+ gap:12px;
67
+ align-items:center;
68
+ }
69
+ .logo{
70
+ width:52px;
71
+ height:52px;
72
+ border-radius:10px;
73
+ display:grid;
74
+ place-items:center;
75
+ background:var(--accent);
76
+ color:#fff;
77
+ font-weight:700;
78
+ font-size:1.15rem;
79
+ box-shadow:0 4px 14px rgba(11,99,151,0.12);
80
+ }
81
+ .title h1{
82
+ margin:0;
83
+ font-size:clamp(16px,2.2vw,20px);
84
+ letter-spacing:-0.2px;
85
+ }
86
+ .title p{
87
+ margin:0;
88
+ font-size:12px;
89
+ color:var(--muted);
90
+ }
91
+
92
+ /* top progress bar reading */
93
+ .reading-progress{
94
+ position:absolute;
95
+ left:0; right:0; bottom:0;
96
+ height:4px; background:linear-gradient(90deg, rgba(14,165,169,0.12), rgba(59,130,246,0.12));
97
+ }
98
+ .reading-progress > i{
99
+ display:block;
100
+ height:100%;
101
+ width:0%;
102
+ background:var(--accent);
103
+ transition:width 220ms linear;
104
+ }
105
+
106
+ /* layout for content: TOC + main + sidebar */
107
+ main.dashboard{
108
+ display:grid;
109
+ grid-template-columns: 1fr;
110
+ gap:16px;
111
+ }
112
+
113
+ /* TOC sticky mobile-friendly */
114
+ nav.toc{
115
+ background:var(--card);
116
+ padding:12px;
117
+ border-radius:var(--radius);
118
+ box-shadow:var(--shadow);
119
+ display:flex;
120
+ gap:8px;
121
+ align-items:center;
122
+ }
123
+ .toc-toggle{
124
+ display:flex;
125
+ gap:8px;
126
+ align-items:center;
127
+ width:100%;
128
+ background:transparent;
129
+ border:0;
130
+ font-size:14px;
131
+ }
132
+ .toc-list{
133
+ margin-top:8px;
134
+ display:none;
135
+ padding:8px 0 0 0;
136
+ width:100%;
137
+ }
138
+ .toc-list a{
139
+ display:inline-flex;
140
+ gap:8px;
141
+ align-items:center;
142
+ text-decoration:none;
143
+ padding:8px;
144
+ border-radius:8px;
145
+ color:var(--muted);
146
+ font-size:13px;
147
+ }
148
+ .toc-list a.active, .toc-list a:hover{
149
+ background:linear-gradient(90deg, rgba(59,130,246,0.08), rgba(14,165,169,0.04));
150
+ color:var(--accent-2);
151
+ font-weight:600;
152
+ }
153
+
154
+ /* main article cards */
155
+ .grid{
156
+ display:grid;
157
+ gap:12px;
158
+ }
159
+ section.card{
160
+ background:var(--card);
161
+ padding:16px;
162
+ border-radius:12px;
163
+ box-shadow:var(--shadow);
164
+ position:relative;
165
+ overflow:visible;
166
+ }
167
+ .card h2{
168
+ margin:0 0 8px 0;
169
+ font-size:clamp(15px,2vw,18px);
170
+ }
171
+ .meta{
172
+ font-size:13px;
173
+ color:var(--muted);
174
+ }
175
+
176
+ /* summary box */
177
+ .summary{
178
+ display:block;
179
+ gap:12px;
180
+ }
181
+ .summary .lead{
182
+ font-size:clamp(15px,2vw,17px);
183
+ font-weight:600;
184
+ color:#05263a;
185
+ }
186
+ .summary p{margin:8px 0;color:var(--muted)}
187
+ .summary .actions{
188
+ display:flex;
189
+ gap:8px;
190
+ margin-top:8px;
191
+ }
192
+ button.btn{
193
+ background:var(--accent-2);
194
+ color:#fff;
195
+ border:0;
196
+ padding:8px 12px;
197
+ border-radius:10px;
198
+ cursor:pointer;
199
+ display:inline-flex;
200
+ gap:8px;
201
+ align-items:center;
202
+ font-size:14px;
203
+ }
204
+ button.ghost{
205
+ background:transparent;
206
+ color:var(--accent-2);
207
+ border:1px solid rgba(59,130,246,0.14);
208
+ }
209
+
210
+ /* stat cards */
211
+ .stats{
212
+ display:flex;
213
+ gap:8px;
214
+ flex-wrap:wrap;
215
+ margin-top:12px;
216
+ }
217
+ .stat{
218
+ background:linear-gradient(180deg, rgba(255,255,255,0.9), rgba(255,255,255,0.7));
219
+ border-radius:10px;
220
+ padding:10px;
221
+ min-width:140px;
222
+ box-shadow:0 6px 18px rgba(9,18,30,0.04);
223
+ flex:1 1 140px;
224
+ }
225
+ .stat .num{font-weight:700;font-size:clamp(16px,2.2vw,18px)}
226
+ .stat .label{font-size:12px;color:var(--muted)}
227
+
228
+ /* charts */
229
+ .charts{
230
+ display:grid;
231
+ gap:12px;
232
+ }
233
+ .chart{
234
+ padding:10px;
235
+ border-radius:10px;
236
+ background:linear-gradient(90deg, rgba(59,130,246,0.03), rgba(14,165,169,0.02));
237
+ }
238
+ .chart svg{width:100%;height:auto;display:block;background:transparent}
239
+ .legend{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
240
+ .legend span{display:inline-flex;gap:8px;align-items:center;font-size:13px;color:var(--muted)}
241
+ .legend i{width:12px;height:12px;border-radius:3px;display:inline-block}
242
+
243
+ /* table */
244
+ table.data{
245
+ width:100%;
246
+ border-collapse:collapse;
247
+ font-size:14px;
248
+ }
249
+ table.data thead th{
250
+ text-align:left;padding:10px;background:transparent;color:var(--muted);cursor:pointer;
251
+ position:sticky; top:0;
252
+ }
253
+ table.data tbody td{padding:10px;border-top:1px solid #f1f3f6}
254
+ table.data tbody tr:hover{background:linear-gradient(90deg, rgba(59,130,246,0.03), rgba(14,165,169,0.02))}
255
+
256
+ /* controls / sidebar area */
257
+ .controls{
258
+ display:flex;
259
+ gap:8px;
260
+ align-items:center;
261
+ flex-wrap:wrap;
262
+ }
263
+ .filter{
264
+ display:flex;
265
+ gap:8px;
266
+ align-items:center;
267
+ padding:8px;
268
+ background:var(--card);
269
+ border-radius:10px;
270
+ box-shadow:var(--shadow);
271
+ }
272
+ .filter select, .filter input[type="range"]{
273
+ border:1px solid #e6eef6;padding:8px;border-radius:8px;background:transparent;
274
+ }
275
+
276
+ /* heatmap */
277
+ .heatmap{
278
+ display:grid;
279
+ gap:8px;
280
+ grid-template-columns: repeat(3,1fr);
281
+ }
282
+ .heatcell{
283
+ padding:8px;border-radius:8px;color:#fff;
284
+ display:flex;align-items:center;gap:8px;
285
+ }
286
+
287
+ /* collapsible */
288
+ .collapsible{
289
+ border-radius:10px;
290
+ overflow:hidden;
291
+ }
292
+ .collapse-toggle{
293
+ display:flex;justify-content:space-between;align-items:center;gap:12px;width:100%;padding:10px;border:none;background:transparent;font-size:15px;
294
+ }
295
+ .collapse-body{padding:8px 10px;border-top:1px solid #f1f3f6}
296
+
297
+ /* references */
298
+ .refs a{color:var(--accent-2);font-size:14px}
299
+
300
+ /* small utilities */
301
+ .badge{background:rgba(14,165,169,0.1);padding:6px 8px;border-radius:20px;color:var(--accent-2);font-weight:600;font-size:12px}
302
+ .muted{color:var(--muted)}
303
+ .center{display:flex;justify-content:center;align-items:center}
304
+ .note{font-size:13px;color:var(--muted);margin-top:8px}
305
+
306
+ /* Responsive grid for desktop: show TOC left and sidebar right */
307
+ @media(min-width:768px){
308
+ .wrap{width:min(94%, var(--max-width))}
309
+ main.dashboard{
310
+ grid-template-columns:220px 1fr 300px;
311
+ align-items:start;
312
+ gap:16px;
313
+ }
314
+ nav.toc{display:block;position:sticky;top:16px;height:fit-content}
315
+ .toc-list{display:block}
316
+ .toc-toggle{display:none}
317
+ .grid{grid-column:span 1}
318
+ }
319
+
320
+ @media(min-width:1024px){
321
+ .wrap{width:min(96%, var(--max-width))}
322
+ .charts{grid-template-columns: 1fr 1fr}
323
+ .heatmap{grid-template-columns:repeat(4,1fr)}
324
+ }
325
+
326
+ @media(min-width:1440px){
327
+ .charts{grid-template-columns: 1fr 1fr 1fr}
328
+ }
329
+
330
+ /* container queries for cards */
331
+ .card{
332
+ container-type: inline-size;
333
+ container-name: card;
334
+ }
335
+ @container card (min-width:420px){
336
+ .card .meta{font-size:13px}
337
+ }
338
+
339
+ /* small aesthetics */
340
+ footer.foot{
341
+ text-align:center;color:var(--muted);padding:12px;margin-top:6px;font-size:13px;
342
+ }
343
+
344
+ /* tooltip */
345
+ .tooltip{
346
+ position:fixed;pointer-events:none;background:#0b1220;color:#fff;padding:8px 10px;border-radius:8px;font-size:13px;transform:translate(-50%,-120%);
347
+ box-shadow:0 8px 30px rgba(9,20,40,0.3);z-index:9999;opacity:0;transition:opacity 180ms ease,transform 180ms ease;
348
+ }
349
+ .tooltip.show{opacity:1;transform:translate(-50%,-140%)}
350
+ </style>
351
+ </head>
352
+ <body>
353
+ <div class="wrap">
354
+
355
+ <header class="topbar" aria-label="Report header">
356
+ <div class="brand">
357
+ <div class="logo" aria-hidden="true">VN</div>
358
+ <div class="title">
359
+ <h1>Vietnam Economic Growth Report — 2025</h1>
360
+ <p>Interactive summary, visualizations and dataset — compiled from cited sources</p>
361
+ </div>
362
+ </div>
363
+
364
+ <div style="margin-left:auto;display:flex;gap:8px;align-items:center">
365
+ <div class="badge">GDP H1 2025: 7.52% ↑</div>
366
+ <div class="muted" style="font-size:13px">Updated: 2025</div>
367
+ </div>
368
+
369
+ <div class="reading-progress" aria-hidden="true"><i id="progressBar"></i></div>
370
+ </header>
371
+
372
+ <main class="dashboard" aria-live="polite">
373
+
374
+ <!-- TOC -->
375
+ <nav class="toc" role="navigation" aria-label="Table of contents">
376
+ <button class="toc-toggle" id="tocToggle"><i class="fa fa-bars"></i> Table of Contents <i class="fa fa-chevron-down" style="margin-left:auto"></i></button>
377
+ <div class="toc-list" id="tocList">
378
+ <a href="#executive" data-id="executive"><i class="fa fa-file-lines"></i> Executive Summary</a>
379
+ <a href="#methodology" data-id="methodology"><i class="fa fa-flask"></i> Methodology</a>
380
+ <a href="#indicators" data-id="indicators"><i class="fa fa-chart-line"></i> Key Indicators</a>
381
+ <a href="#sectors" data-id="sectors"><i class="fa fa-industry"></i> Sectoral Analysis</a>
382
+ <a href="#risks" data-id="risks"><i class="fa fa-triangle-exclamation"></i> Challenges & Risks</a>
383
+ <a href="#history" data-id="history"><i class="fa fa-clock-rotate-left"></i> Historical Comparison</a>
384
+ <a href="#appendix" data-id="appendix"><i class="fa fa-folder-open"></i> Appendices & Data</a>
385
+ <a href="#references" data-id="references"><i class="fa fa-book"></i> Sources & Citations</a>
386
+ </div>
387
+ </nav>
388
+
389
+ <!-- Main content -->
390
+ <div class="grid">
391
+
392
+ <!-- Executive Summary -->
393
+ <section class="card" id="executive">
394
+ <div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start">
395
+ <div>
396
+ <h2>Executive Summary</h2>
397
+ <div class="meta">Snapshot of Vietnam's 2025 growth performance with key insights and policy implications</div>
398
+ </div>
399
+ <div style="text-align:right">
400
+ <div class="muted">Citation: GSO & IMF, 2025</div>
401
+ <div class="note">Click to copy summary • Save for later</div>
402
+ </div>
403
+ </div>
404
+
405
+ <div class="summary">
406
+ <div class="lead">Vietnam's economy shows robust momentum in H1 2025 — GDP grew 7.52% (H1) and 7.96% in Q2 year-on-year, supported by services, manufacturing and strong FDI inflows.</div>
407
+
408
+ <p class="muted">Key takeaways: low unemployment (2.2% Q1), controlled inflation (~3.5% mid-2025), FDI expansion (21.51B H1), and retail rebound (Q1 retail sales up 9.9%). Government targets remain ambitious compared to multilateral forecasts<sup><a href="#ref1">1</a></sup>.</p>
409
+
410
+ <div class="actions">
411
+ <button class="btn" id="copySummary"><i class="fa fa-clipboard"></i> Copy Summary</button>
412
+ <button class="btn ghost" id="saveView"><i class="fa fa-star"></i> Save View</button>
413
+ <button class="btn" id="printReport"><i class="fa fa-print"></i> Print</button>
414
+ </div>
415
+
416
+ <div class="stats" role="list" aria-label="Key Figures">
417
+ <div class="stat" role="listitem">
418
+ <div class="num">7.52%</div>
419
+ <div class="label">GDP growth (H1 2025)</div>
420
+ </div>
421
+ <div class="stat">
422
+ <div class="num">7.96%</div>
423
+ <div class="label">GDP growth (Q2 2025 YoY)</div>
424
+ </div>
425
+ <div class="stat">
426
+ <div class="num">2.20%</div>
427
+ <div class="label">Unemployment (Q1 2025)</div>
428
+ </div>
429
+ <div class="stat">
430
+ <div class="num">$21.51B</div>
431
+ <div class="label">FDI (H1 2025)</div>
432
+ </div>
433
+ </div>
434
+ </div>
435
+ </section>
436
+
437
+ <!-- Methodology -->
438
+ <section class="card" id="methodology">
439
+ <h2>Methodology</h2>
440
+ <div class="meta">Data sources, processing and visualization approach</div>
441
+
442
+ <div style="margin-top:10px">
443
+ <p class="muted">This interactive report consolidates official statistics (GSO), multilateral forecasts (IMF, World Bank, ADB) and market data (Trading Economics). Visualizations are built with responsive SVG and accessible interactions. All figures include citation markers linking to the reference list.</p>
444
+
445
+ <div class="note">Processing: numbers were harmonized to quarter/year aggregates; FDI is presented per published reporting periods (first 5 months / H1), forecasts are shown for comparative context.</div>
446
+ </div>
447
+ </section>
448
+
449
+ <!-- Key Indicators with charts -->
450
+ <section class="card" id="indicators">
451
+ <h2>Key Indicators</h2>
452
+ <div class="meta">Interactive charts and a sortable table of primary economic metrics</div>
453
+
454
+ <div class="charts" style="margin-top:12px">
455
+ <!-- Line: Historical GDP Q1 2020-2025 -->
456
+ <div class="chart" id="chartGDPHistory" aria-label="Historical GDP growth">
457
+ <h3 style="margin:0">Historical Q1 GDP Growth (2020–2025)</h3>
458
+ <svg viewBox="0 0 600 220" preserveAspectRatio="xMinYMin meet" role="img" aria-labelledby="gdpTitle">
459
+ <title id="gdpTitle">GDP growth rate by year</title>
460
+ <defs>
461
+ <linearGradient id="gdpGrad" x1="0" x2="1">
462
+ <stop offset="0" stop-color="#0ea5a9" stop-opacity="0.7" />
463
+ <stop offset="1" stop-color="#3b82f6" stop-opacity="0.9" />
464
+ </linearGradient>
465
+ </defs>
466
+ <rect width="100%" height="100%" fill="transparent"></rect>
467
+ <g transform="translate(40,18)">
468
+ <g class="axes">
469
+ <!-- y lines -->
470
+ <g stroke="#e6eef6" stroke-width="1">
471
+ <line x1="0" x2="520" y1="0" y2="0"></line>
472
+ <line x1="0" x2="520" y1="40" y2="40"></line>
473
+ <line x1="0" x2="520" y1="80" y2="80"></line>
474
+ <line x1="0" x2="520" y1="120" y2="120"></line>
475
+ <line x1="0" x2="520" y1="160" y2="160"></line>
476
+ <line x1="0" x2="520" y1="200" y2="200"></line>
477
+ </g>
478
+ </g>
479
+ <g id="gdpPath"></g>
480
+ <!-- x labels -->
481
+ <g transform="translate(0,205)" fill="#6b7280" font-size="12">
482
+ <text x="0">2020</text>
483
+ <text x="95">2021</text>
484
+ <text x="190">2022</text>
485
+ <text x="285">2023</text>
486
+ <text x="380">2024</text>
487
+ <text x="475">2025</text>
488
+ </g>
489
+ </g>
490
+ </svg>
491
+ <div class="legend">
492
+ <span><i style="background:linear-gradient(90deg,#0ea5a9,#3b82f6)"></i> YoY Q1 Growth (%)</span>
493
+ <span class="muted">Data: GSO / IMF / Trading Economics<sup><a href="#ref4">4</a></sup></span>
494
+ </div>
495
+ </div>
496
+
497
+ <!-- Bar: Q1 Q2 H1 actual -->
498
+ <div class="chart" id="chartQuarter">
499
+ <h3 style="margin:0">2025 Growth — Q1, Q2, H1</h3>
500
+ <svg viewBox="0 0 600 240" preserveAspectRatio="xMinYMin meet" role="img">
501
+ <g transform="translate(40,20)">
502
+ <g id="bars"></g>
503
+ <g transform="translate(0,200)" fill="#6b7280" font-size="13">
504
+ <text x="0">Q1</text>
505
+ <text x="95">Q2</text>
506
+ <text x="190">H1</text>
507
+ </g>
508
+ <!-- y labels -->
509
+ <g transform="translate(-18,0)" fill="#6b7280" font-size="12">
510
+ <text y="200">0%</text>
511
+ <text y="160">2%</text>
512
+ <text y="120">4%</text>
513
+ <text y="80">6%</text>
514
+ <text y="40">8%</text>
515
+ </g>
516
+ </g>
517
+ </svg>
518
+ <div class="legend">
519
+ <span><i style="background:#3b82f6"></i> Actual growth (%)</span>
520
+ <span class="muted">Q2 figure is YoY</span>
521
+ </div>
522
+ </div>
523
+
524
+ <!-- Forecasts bar -->
525
+ <div class="chart" id="chartForecast">
526
+ <h3 style="margin:0">2025 Growth Forecasts (Institutions)</h3>
527
+ <svg viewBox="0 0 600 220" preserveAspectRatio="xMinYMin meet" role="img">
528
+ <g transform="translate(40,20)">
529
+ <g id="fBars"></g>
530
+ <g transform="translate(0,170)" fill="#6b7280" font-size="13">
531
+ <text x="0">World Bank</text>
532
+ <text x="120">ADB</text>
533
+ <text x="240">IMF</text>
534
+ <text x="360">Govt Target</text>
535
+ </g>
536
+ </g>
537
+ </svg>
538
+ <div class="legend">
539
+ <span><i style="background:#10b981"></i> Forecast (%)</span>
540
+ <span class="muted">Sources: World Bank, ADB, IMF, Government targets<sup><a href="#ref2">2</a></sup></span>
541
+ </div>
542
+ </div>
543
+ </div>
544
+
545
+ <!-- sortable table -->
546
+ <div style="margin-top:12px">
547
+ <h3 style="margin:0 0 8px 0">Key Indicators Table</h3>
548
+ <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:8px">
549
+ <div class="filter">
550
+ <label class="muted">Filter by min GDP (%)</label>
551
+ <input type="range" id="filterGDP" min="0" max="10" step="0.1" value="0" />
552
+ <span id="gdpVal" class="muted">0%</span>
553
+ </div>
554
+ <div style="margin-left:auto;display:flex;gap:8px">
555
+ <button class="btn ghost" id="exportCSV"><i class="fa fa-file-csv"></i> Export CSV</button>
556
+ <button class="btn" id="copyTable"><i class="fa fa-copy"></i> Copy Data</button>
557
+ </div>
558
+ </div>
559
+
560
+ <div style="overflow:auto;border-radius:8px">
561
+ <table class="data" id="indTable" aria-label="Economic indicators">
562
+ <thead>
563
+ <tr>
564
+ <th data-key="indicator">Indicator</th>
565
+ <th data-key="value" style="text-align:right">Value</th>
566
+ <th data-key="period" style="text-align:right">Period</th>
567
+ <th data-key="source" style="text-align:left">Source</th>
568
+ </tr>
569
+ </thead>
570
+ <tbody></tbody>
571
+ </table>
572
+ </div>
573
+
574
+ </div>
575
+ </section>
576
+
577
+ <!-- Sectors -->
578
+ <section class="card" id="sectors">
579
+ <h2>Sectoral Analysis</h2>
580
+ <div class="meta">Contributions to GDP and sector performance (interactive)</div>
581
+
582
+ <div style="display:flex;gap:12px;margin-top:12px;flex-wrap:wrap">
583
+ <div style="flex:1;min-width:260px" class="chart" id="sectorPie">
584
+ <h3 style="margin:0">Estimated Sector Contributions to GDP (2025)</h3>
585
+ <svg viewBox="0 0 300 300" preserveAspectRatio="xMinYMin meet" role="img">
586
+ <g transform="translate(150,150)" id="pieGroup"></g>
587
+ </svg>
588
+ <div class="legend" id="sectorLegend"></div>
589
+ </div>
590
+
591
+ <div style="flex:1;min-width:220px" class="chart">
592
+ <h3 style="margin:0">Retail & Banking Snapshot</h3>
593
+ <div style="display:flex;gap:8px;flex-direction:column;margin-top:8px">
594
+ <div class="stat" style="display:flex;justify-content:space-between">
595
+ <div>
596
+ <div class="num">1.708 Q VND</div>
597
+ <div class="label">Retail sales (Q1 2025) — 9.9% YoY</div>
598
+ </div>
599
+ <div class="muted">US$66.83B</div>
600
+ </div>
601
+ <div class="stat" style="display:flex;justify-content:space-between">
602
+ <div>
603
+ <div class="num">+17%</div>
604
+ <div class="label">Bank earnings projected (2025)</div>
605
+ </div>
606
+ <div class="muted">Credit +15%</div>
607
+ </div>
608
+ </div>
609
+ <div class="note">Sector shares are illustrative, derived from report narrative and public estimates<sup><a href="#ref12">12</a></sup>.</div>
610
+ </div>
611
+ </div>
612
+ </section>
613
+
614
+ <!-- Risks heatmap -->
615
+ <section class="card" id="risks">
616
+ <h2>Challenges & Risk Factors</h2>
617
+ <div class="meta">Severity assessment of headline risks and mitigation status</div>
618
+
619
+ <div class="heatmap" style="margin-top:12px" id="riskGrid">
620
+ <!-- generated cells -->
621
+ </div>
622
+
623
+ <div style="margin-top:12px" class="note">Mitigation strategies: diversify export markets, strengthen domestic demand, maintain macro stability and fiscal buffers.</div>
624
+ </section>
625
+
626
+ <!-- Historical comparison -->
627
+ <section class="card" id="history">
628
+ <h2>Historical Comparison & Outlook</h2>
629
+ <div class="meta">Trend analysis and near-term projections</div>
630
+
631
+ <div style="margin-top:12px">
632
+ <p class="muted">Year-on-year Q1 GDP growth 2020–2025: 3.21%, 4.85%, 5.42%, 3.46%, 5.98%, 6.93% (2025 preliminary) — demonstrates recovery trajectory since 2020 shocks<sup><a href="#ref1">1</a></sup>.</p>
633
+
634
+ <div class="chart" style="margin-top:12px" id="outlookChart">
635
+ <h3 style="margin:0">Scenario Outlook — Baseline vs Downside</h3>
636
+ <svg viewBox="0 0 620 240" preserveAspectRatio="xMinYMin meet" role="img">
637
+ <g transform="translate(40,16)">
638
+ <g id="outPaths"></g>
639
+ <g transform="translate(0,200)" fill="#6b7280" font-size="12">
640
+ <text x="0">2023</text>
641
+ <text x="95">2024</text>
642
+ <text x="190">2025</text>
643
+ <text x="285">2026</text>
644
+ <text x="380">2027</text>
645
+ </g>
646
+ </g>
647
+ </svg>
648
+ <div class="legend">
649
+ <span><i style="background:#0ea5a9"></i> Baseline</span>
650
+ <span><i style="background:#ef4444"></i> Downside</span>
651
+ </div>
652
+ </div>
653
+
654
+ </div>
655
+ </section>
656
+
657
+ <!-- Appendices and data -->
658
+ <section class="card" id="appendix">
659
+ <h2>Appendices & Data</h2>
660
+ <div class="meta">Raw dataset snapshots and downloadable exports</div>
661
+
662
+ <div style="margin-top:12px">
663
+ <div style="display:flex;gap:8px;flex-wrap:wrap">
664
+ <button class="btn" id="downloadJSON"><i class="fa fa-download"></i> Download JSON</button>
665
+ <button class="btn ghost" id="resetView"><i class="fa fa-rotate-left"></i> Reset Saved View</button>
666
+ </div>
667
+
668
+ <div style="margin-top:12px">
669
+ <h3 style="margin:0 0 8px 0">Quick Data Preview</h3>
670
+ <pre id="dataPreview" style="background:#f8fafc;padding:12px;border-radius:8px;max-height:240px;overflow:auto;font-size:13px"></pre>
671
+ </div>
672
+ </div>
673
+ </section>
674
+
675
+ <!-- References -->
676
+ <section class="card" id="references">
677
+ <h2>Sources & Citations</h2>
678
+ <div class="meta">Primary sources and links referenced in the report</div>
679
+
680
+ <div class="refs" style="margin-top:12px">
681
+ <ol>
682
+ <li id="ref1"><a href="https://tradingeconomics.com/vietnam/gdp-growth-annual" target="_blank" rel="noopener">Trading Economics - Vietnam GDP Annual Growth Rate</a></li>
683
+ <li id="ref2"><a href="https://www.imf.org/en/Countries/VNM" target="_blank" rel="noopener">International Monetary Fund - Vietnam Country Profile</a></li>
684
+ <li id="ref4"><a href="https://www.gso.gov.vn/en/" target="_blank" rel="noopener">Government of Vietnam - General Statistics Office (GSO)</a></li>
685
+ <li id="ref12"><a href="https://vir.com.vn/" target="_blank" rel="noopener">Vietnam Investment Review - FDI Statistics</a></li>
686
+ <li><a href="https://www.adb.org/countries/viet-nam/main" target="_blank" rel="noopener">Asian Development Bank - Vietnam</a></li>
687
+ <li><a href="https://www.worldeconomics.com/GDP/Vietnam.gdp" target="_blank" rel="noopener">World Economics - Vietnam GDP Estimates</a></li>
688
+ <li><a href="https://www.vietnam-briefing.com/" target="_blank" rel="noopener">Vietnam Briefing</a></li>
689
+ <li><a href="https://www.imf.org/en/Publications/CR" target="_blank" rel="noopener">IMF - Article IV Mission Reports</a></li>
690
+ </ol>
691
+ </div>
692
+ </section>
693
+
694
+ </div>
695
+
696
+ <!-- Right-side controls / quick filters -->
697
+ <aside style="position:sticky;top:16px" aria-label="Quick actions">
698
+ <div class="card" style="display:flex;flex-direction:column;gap:12px;min-width:260px;">
699
+ <h3 style="margin:0">Quick Filters & Export</h3>
700
+ <div class="controls" style="margin-top:8px">
701
+ <div class="filter" style="flex-direction:column;align-items:flex-start">
702
+ <label class="muted">Select sector</label>
703
+ <select id="sectorFilter" aria-label="Sector filter">
704
+ <option value="all">All sectors</option>
705
+ <option value="services">Services</option>
706
+ <option value="manufacturing">Manufacturing</option>
707
+ <option value="agri">Agriculture</option>
708
+ <option value="other">Other</option>
709
+ </select>
710
+ </div>
711
+
712
+ <div class="filter" style="flex-direction:column;align-items:flex-start">
713
+ <label class="muted">Risk severity (min)</label>
714
+ <select id="riskFilter">
715
+ <option value="1">1 - Low</option>
716
+ <option value="2">2</option>
717
+ <option value="3" selected>3 - Moderate</option>
718
+ <option value="4">4</option>
719
+ <option value="5">5 - High</option>
720
+ </select>
721
+ </div>
722
+ </div>
723
+
724
+ <div style="display:flex;gap:8px">
725
+ <button class="btn" id="downloadPDF"><i class="fa fa-file-pdf"></i> Export PDF</button>
726
+ <button class="btn ghost" id="copyReport"><i class="fa fa-copy"></i> Copy Exec. Summary</button>
727
+ </div>
728
+
729
+ <div class="note" style="margin-top:8px">Saved filters will be stored locally. Use the "Save View" action to bookmark your dashboard state.</div>
730
+ </div>
731
+ </aside>
732
+
733
+ </main>
734
+
735
+ <footer class="foot">© Vietnam Economic Growth Report 2025 — Interactive. Data compiled from cited institutional sources.</footer>
736
+
737
+ </div>
738
+
739
+ <!-- Tooltip -->
740
+ <div class="tooltip" id="tooltip" role="status" aria-hidden="true"></div>
741
+
742
+ <script>
743
+ /* ===============================
744
+ Data models - derived from report
745
+ - all logic in vanilla JS
746
+ - uses IntersectionObserver, LocalStorage, Clipboard, dynamic SVG
747
+ =============================== */
748
+
749
+ const DATA = {
750
+ meta: {
751
+ title: "Vietnam Economic Growth Report 2025",
752
+ updated: "2025"
753
+ },
754
+ historicalQ1: [
755
+ {year:2020, value:3.21},
756
+ {year:2021, value:4.85},
757
+ {year:2022, value:5.42},
758
+ {year:2023, value:3.46},
759
+ {year:2024, value:5.98},
760
+ {year:2025, value:6.93}
761
+ ],
762
+ q2025: [
763
+ {label:"Q1 2025", value:6.9},
764
+ {label:"Q2 2025", value:7.96},
765
+ {label:"H1 2025", value:7.52}
766
+ ],
767
+ forecasts: [
768
+ {name:"World Bank", value:5.8},
769
+ {name:"ADB", value:6.6},
770
+ {name:"IMF", value:5.2},
771
+ {name:"Government Target", value:8.4} // midpoint of 8.3-8.5
772
+ ],
773
+ indicators: [
774
+ {indicator:"GDP growth (Q1 2025)", value:6.9, period:"Q1 2025", source:"GSO"},
775
+ {indicator:"GDP growth (Q2 2025 YoY)", value:7.96, period:"Q2 2025", source:"GSO"},
776
+ {indicator:"GDP growth (H1 2025)", value:7.52, period:"H1 2025", source:"GSO"},
777
+ {indicator:"Inflation (May 2025)", value:3.24, period:"May 2025", source:"GSO"},
778
+ {indicator:"Inflation (June 2025)", value:3.57, period:"June 2025", source:"GSO"},
779
+ {indicator:"Unemployment (Q1 2025)", value:2.20, period:"Q1 2025", source:"GSO"},
780
+ {indicator:"FDI Registered (first 5 months)", value:18.4, period:"Jan–May 2025 (US$B)", source:"GSO / VIR"},
781
+ {indicator:"FDI Disbursed (first 5 months)", value:8.9, period:"Jan–May 2025 (US$B)", source:"GSO / VIR"},
782
+ {indicator:"Retail sales (Q1 2025, VND quads)", value:1.708, period:"Q1 2025", source:"GSO"},
783
+ {indicator:"Bank earnings growth (projected 2025)", value:17, period:"2025", source:"Market estimates"}
784
+ ],
785
+ sectors: [
786
+ {name:"Services", share:45},
787
+ {name:"Manufacturing", share:30},
788
+ {name:"Agriculture", share:10},
789
+ {name:"Other", share:15}
790
+ ],
791
+ risks: [
792
+ {name:"Global trade tensions", severity:4, mitigation:"Diversify export markets"},
793
+ {name:"US tariff policies", severity:4, mitigation:"Support export-oriented SMEs"},
794
+ {name:"Geopolitical instability", severity:3, mitigation:"Enhance supply chain resilience"},
795
+ {name:"FDI overdependence", severity:3, mitigation:"Promote domestic investment"},
796
+ {name:"Macroeconomic instability", severity:2, mitigation:"Monitor public debt & inflation"},
797
+ {name:"Commodity price shocks", severity:3, mitigation:"Strategic reserves & hedging"}
798
+ ],
799
+ outlook: {
800
+ baseline:[5.0,5.9,7.5,7.2,7.0], // 2023-2027 (approx)
801
+ downside:[4.2,5.1,4.8,4.5,4.2]
802
+ }
803
+ };
804
+
805
+ /* ===============================
806
+ Utilities
807
+ =============================== */
808
+ const el = sel => document.querySelector(sel);
809
+ const els = sel => Array.from(document.querySelectorAll(sel));
810
+ const fmt = (v, digits=2) => Number(v).toLocaleString(undefined,{maximumFractionDigits:digits,minimumFractionDigits:0});
811
+
812
+ /* ========== Tooltip ========== */
813
+ const tooltip = el('#tooltip');
814
+ function showTooltip(text, x, y){
815
+ tooltip.textContent = text;
816
+ tooltip.style.left = x + 'px';
817
+ tooltip.style.top = y + 'px';
818
+ tooltip.classList.add('show');
819
+ tooltip.setAttribute('aria-hidden','false');
820
+ }
821
+ function hideTooltip(){
822
+ tooltip.classList.remove('show');
823
+ tooltip.setAttribute('aria-hidden','true');
824
+ }
825
+
826
+ /* ========== TOC behavior ========== */
827
+ const tocToggle = el('#tocToggle');
828
+ if(tocToggle){
829
+ tocToggle.addEventListener('click', ()=>{
830
+ const list = el('#tocList');
831
+ list.style.display = list.style.display === 'block' ? 'none' : 'block';
832
+ });
833
+ }
834
+
835
+ const tocLinks = els('.toc-list a');
836
+ tocLinks.forEach(a=>a.addEventListener('click', e=>{
837
+ e.preventDefault();
838
+ const id = a.getAttribute('href');
839
+ document.querySelector(id).scrollIntoView({behavior:'smooth',block:'start'});
840
+ // collapse on mobile
841
+ if(window.innerWidth < 768) el('#tocList').style.display='none';
842
+ }));
843
+
844
+ /* Smooth scroll behavior for all in-page anchors */
845
+ els('a[href^="#"]').forEach(a=>{
846
+ a.addEventListener('click', (e)=>{
847
+ const href = a.getAttribute('href');
848
+ if(!href || href === '#') return;
849
+ const target = document.querySelector(href);
850
+ if(target){
851
+ e.preventDefault();
852
+ target.scrollIntoView({behavior:'smooth',block:'start'});
853
+ }
854
+ });
855
+ });
856
+
857
+ /* ========== Reading progress via IntersectionObserver ========== */
858
+ const sections = els('main.dashboard .card');
859
+ const progressBar = el('#progressBar');
860
+ const sectionPositions = [];
861
+ function updateSectionPositions(){
862
+ sectionPositions.length = 0;
863
+ sections.forEach(s=>{
864
+ const rect = s.getBoundingClientRect();
865
+ sectionPositions.push({el:s, top: s.offsetTop});
866
+ });
867
+ }
868
+ window.addEventListener('resize', updateSectionPositions);
869
+ updateSectionPositions();
870
+
871
+ window.addEventListener('scroll', ()=>{
872
+ // compute progress through document
873
+ const scrollY = window.scrollY || window.pageYOffset;
874
+ const docHeight = document.body.scrollHeight - window.innerHeight;
875
+ const pct = Math.min(100, Math.max(0, (scrollY/docHeight)*100));
876
+ progressBar.style.width = pct + '%';
877
+ });
878
+
879
+ /* use IntersectionObserver to set active TOC link and lazy animate charts */
880
+ const observer = new IntersectionObserver((entries)=>{
881
+ entries.forEach(entry=>{
882
+ const id = entry.target.id;
883
+ const link = el('.toc-list a[data-id="'+id+'"]');
884
+ if(entry.isIntersecting){
885
+ tocLinks.forEach(l=>l.classList.remove('active'));
886
+ if(link) link.classList.add('active');
887
+ // save last section
888
+ localStorage.setItem('lastSection', id);
889
+ // dispatch custom event for entering section
890
+ entry.target.dispatchEvent(new CustomEvent('enteredSection',{detail:{id}}));
891
+ }
892
+ });
893
+ }, {root:null, threshold:0.4});
894
+
895
+ sections.forEach(s => observer.observe(s));
896
+
897
+ /* Restore last section */
898
+ const last = localStorage.getItem('lastSection');
899
+ if(last){
900
+ const lastEl = document.getElementById(last);
901
+ if(lastEl) lastEl.scrollIntoView({behavior:'auto',block:'start'});
902
+ }
903
+
904
+ /* ========== Copy & Save actions (Clipboard, LocalStorage) ========== */
905
+ el('#copySummary').addEventListener('click', async ()=>{
906
+ const text = `Executive Summary — Vietnam Economic Growth Report 2025\n\nVietnam's economy shows robust momentum in H1 2025 — GDP grew 7.52% (H1) and 7.96% in Q2 YoY, supported by services, manufacturing and strong FDI inflows. Low unemployment (~2.2%), controlled inflation (~3.5%), and FDI of ~US$21.51B in H1 2025. Sources: GSO / IMF / World Bank / ADB.`;
907
+ try{
908
+ await navigator.clipboard.writeText(text);
909
+ showTooltip('Summary copied to clipboard', window.innerWidth/2, 80);
910
+ setTimeout(hideTooltip,1500);
911
+ }catch(e){
912
+ alert('Clipboard copy not supported.');
913
+ }
914
+ });
915
+
916
+ el('#copyReport').addEventListener('click', ()=> el('#copySummary').click());
917
+
918
+ el('#saveView').addEventListener('click', ()=>{
919
+ const state = {
920
+ sector: el('#sectorFilter').value,
921
+ riskMin: el('#riskFilter').value,
922
+ gdpFilter: el('#filterGDP').value,
923
+ timestamp: Date.now()
924
+ };
925
+ localStorage.setItem('dashboardState', JSON.stringify(state));
926
+ showTooltip('View saved locally', window.innerWidth/2, 80);
927
+ setTimeout(hideTooltip,1200);
928
+ });
929
+
930
+ el('#resetView').addEventListener('click', ()=>{
931
+ localStorage.removeItem('dashboardState');
932
+ el('#sectorFilter').value='all';
933
+ el('#riskFilter').value='3';
934
+ el('#filterGDP').value=0;
935
+ el('#gdpVal').textContent='0%';
936
+ renderTable();
937
+ renderRiskGrid();
938
+ showTooltip('Saved view reset', window.innerWidth/2, 80);
939
+ setTimeout(hideTooltip,1000);
940
+ });
941
+
942
+ /* Restore saved filters if any */
943
+ (function restoreState(){
944
+ const state = JSON.parse(localStorage.getItem('dashboardState')||'null');
945
+ if(state){
946
+ el('#sectorFilter').value = state.sector || 'all';
947
+ el('#riskFilter').value = state.riskMin || '3';
948
+ el('#filterGDP').value = state.gdpFilter || 0;
949
+ el('#gdpVal').textContent = state.gdpFilter + '%';
950
+ }
951
+ })();
952
+
953
+ /* Print */
954
+ el('#printReport').addEventListener('click', ()=> window.print());
955
+
956
+ /* ========== Charts rendering ========== */
957
+
958
+ /* Helper for scaling */
959
+ function scaleLinear(domain, range){
960
+ const [d0,d1] = domain, [r0,r1] = range;
961
+ const m = (r1-r0)/(d1-d0 || 1);
962
+ return v => r0 + (v - d0)*m;
963
+ }
964
+
965
+ /* 1. Line chart for historical Q1 */
966
+ function drawGDPHistory(){
967
+ const svg = document.querySelector('#chartGDPHistory svg');
968
+ const g = svg.querySelector('#gdpPath');
969
+ g.innerHTML = '';
970
+ const data = DATA.historicalQ1;
971
+ const w = 520, h = 200;
972
+ const x = scaleLinear([0,data.length-1],[0,w]);
973
+ const y = scaleLinear([0,Math.max(...data.map(d=>d.value))+1],[h,0]);
974
+
975
+ // path
976
+ let dpath = 'M ' + x(0) + ' ' + y(data[0].value);
977
+ data.forEach((pt,i)=>{
978
+ const cx = x(i);
979
+ const cy = y(pt.value);
980
+ dpath += ' L ' + cx + ' ' + cy;
981
+ });
982
+
983
+ const pathEl = document.createElementNS('http://www.w3.org/2000/svg','path');
984
+ pathEl.setAttribute('d', dpath);
985
+ pathEl.setAttribute('fill','none');
986
+ pathEl.setAttribute('stroke','url(#gdpGrad)');
987
+ pathEl.setAttribute('stroke-width','3');
988
+ pathEl.setAttribute('stroke-linejoin','round');
989
+ pathEl.setAttribute('stroke-linecap','round');
990
+ pathEl.style.opacity = 0;
991
+ g.appendChild(pathEl);
992
+
993
+ // points and hover interactivity
994
+ data.forEach((pt,i)=>{
995
+ const cx = x(i);
996
+ const cy = y(pt.value);
997
+ const circle = document.createElementNS('http://www.w3.org/2000/svg','circle');
998
+ circle.setAttribute('cx', cx);
999
+ circle.setAttribute('cy', cy);
1000
+ circle.setAttribute('r', 5);
1001
+ circle.setAttribute('fill', '#fff');
1002
+ circle.setAttribute('stroke', '#0ea5a9');
1003
+ circle.setAttribute('stroke-width', 2);
1004
+ circle.style.cursor='pointer';
1005
+ circle.addEventListener('mouseenter', (ev)=>{
1006
+ showTooltip(`${pt.year} — ${pt.value}%`, ev.clientX, ev.clientY-10);
1007
+ });
1008
+ circle.addEventListener('mouseleave', hideTooltip);
1009
+ g.appendChild(circle);
1010
+ });
1011
+
1012
+ // animate in on section enter
1013
+ document.getElementById('chartGDPHistory').addEventListener('enteredSection', ()=>{
1014
+ pathEl.style.transition = 'opacity 400ms ease';
1015
+ pathEl.style.opacity = 1;
1016
+ });
1017
+ }
1018
+ drawGDPHistory();
1019
+
1020
+ /* 2. Bars for 2025 quarters */
1021
+ function drawQuarterBars(){
1022
+ const svg = document.querySelector('#chartQuarter svg');
1023
+ const g = svg.querySelector('#bars');
1024
+ g.innerHTML='';
1025
+ const data = DATA.q2025;
1026
+ const w = 520;
1027
+ const maxv = Math.max(...data.map(d=>d.value), 10);
1028
+ const x = scaleLinear([0,data.length],[0,w]);
1029
+ const y = scaleLinear([0,maxv],[200,20]);
1030
+
1031
+ data.forEach((d,i)=>{
1032
+ const bx = i*95 + 10;
1033
+ const bw = 60;
1034
+ const bh = 200 - y(d.value);
1035
+ const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
1036
+ rect.setAttribute('x', bx);
1037
+ rect.setAttribute('y', y(d.value));
1038
+ rect.setAttribute('width', bw);
1039
+ rect.setAttribute('height', bh);
1040
+ rect.setAttribute('fill', '#3b82f6');
1041
+ rect.setAttribute('rx', 6);
1042
+ rect.style.opacity=0;
1043
+ rect.addEventListener('mouseenter', (ev)=> showTooltip(`${d.label}\n${d.value}%`, ev.clientX, ev.clientY-10));
1044
+ rect.addEventListener('mouseleave', hideTooltip);
1045
+ g.appendChild(rect);
1046
+
1047
+ // animate
1048
+ setTimeout(()=> rect.style.transition='opacity 500ms ease, transform 400ms cubic-bezier(.2,.9,.4,1)', rect.style.opacity=1, 80*i);
1049
+ });
1050
+ }
1051
+ drawQuarterBars();
1052
+
1053
+ /* 3. Forecast bars */
1054
+ function drawForecastBars(){
1055
+ const svg = document.querySelector('#chartForecast svg');
1056
+ const g = svg.querySelector('#fBars');
1057
+ g.innerHTML='';
1058
+ const data = DATA.forecasts;
1059
+ const xbase = 0;
1060
+ const maxv = Math.max(...data.map(d=>d.value),10);
1061
+ const y = scaleLinear([0,maxv],[140,20]);
1062
+
1063
+ data.forEach((d,i)=>{
1064
+ const bx = i*120;
1065
+ const bw = 60;
1066
+ const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
1067
+ rect.setAttribute('x', bx);
1068
+ rect.setAttribute('y', y(d.value));
1069
+ rect.setAttribute('width', bw);
1070
+ rect.setAttribute('height', 140 - (y(d.value)-20));
1071
+ rect.setAttribute('fill', '#10b981');
1072
+ rect.setAttribute('rx', 6);
1073
+ rect.addEventListener('mouseenter', (ev)=> showTooltip(`${d.name} — ${d.value}%`, ev.clientX, ev.clientY-10));
1074
+ rect.addEventListener('mouseleave', hideTooltip);
1075
+ g.appendChild(rect);
1076
+
1077
+ const t = document.createElementNS('http://www.w3.org/2000/svg','text');
1078
+ t.setAttribute('x', bx);
1079
+ t.setAttribute('y', y(d.value)-6);
1080
+ t.setAttribute('fill','#05263a');
1081
+ t.setAttribute('font-size','12');
1082
+ t.textContent = d.value + '%';
1083
+ g.appendChild(t);
1084
+ });
1085
+ }
1086
+ drawForecastBars();
1087
+
1088
+ /* 4. Sector pie */
1089
+ function drawSectorPie(){
1090
+ const group = document.querySelector('#pieGroup');
1091
+ group.innerHTML='';
1092
+ const data = DATA.sectors;
1093
+ const total = data.reduce((s,d)=>s+d.share,0);
1094
+ let angle = -Math.PI/2;
1095
+ const r = 100;
1096
+ const colors = ['#3b82f6','#0ea5a9','#f59e0b','#6366f1'];
1097
+ const legend = el('#sectorLegend');
1098
+ legend.innerHTML='';
1099
+
1100
+ data.forEach((d,i)=>{
1101
+ const frac = d.share/total;
1102
+ const delta = frac*Math.PI*2;
1103
+ const x1 = Math.cos(angle)*r, y1 = Math.sin(angle)*r;
1104
+ const x2 = Math.cos(angle+delta)*r, y2 = Math.sin(angle+delta)*r;
1105
+ const large = delta > Math.PI ? 1 : 0;
1106
+ const path = `M 0 0 L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
1107
+ const p = document.createElementNS('http://www.w3.org/2000/svg','path');
1108
+ p.setAttribute('d', path);
1109
+ p.setAttribute('fill', colors[i % colors.length]);
1110
+ p.setAttribute('stroke','#fff');
1111
+ p.setAttribute('stroke-width','1');
1112
+ p.style.cursor='pointer';
1113
+ p.addEventListener('mouseenter', (ev)=> showTooltip(`${d.name}: ${d.share}%`, ev.clientX, ev.clientY-10));
1114
+ p.addEventListener('mouseleave', hideTooltip);
1115
+ group.appendChild(p);
1116
+
1117
+ // legend
1118
+ const leg = document.createElement('span');
1119
+ const sw = document.createElement('i');
1120
+ sw.style.background = colors[i % colors.length];
1121
+ leg.appendChild(sw);
1122
+ leg.appendChild(document.createTextNode(` ${d.name} — ${d.share}%`));
1123
+ legend.appendChild(leg);
1124
+
1125
+ angle += delta;
1126
+ });
1127
+ }
1128
+ drawSectorPie();
1129
+
1130
+ /* 5. Risk heatmap */
1131
+ function renderRiskGrid(){
1132
+ const grid = el('#riskGrid');
1133
+ grid.innerHTML = '';
1134
+ const minSeverity = parseInt(el('#riskFilter').value || 1,10);
1135
+ DATA.risks.forEach(r=>{
1136
+ if(r.severity < minSeverity) return;
1137
+ const div = document.createElement('div');
1138
+ div.className = 'heatcell';
1139
+ const color = severityColor(r.severity);
1140
+ div.style.background = color;
1141
+ div.innerHTML = `<div style="flex:1"><strong>${r.name}</strong><div class="muted" style="font-size:13px">${r.mitigation}</div></div><div style="font-weight:700">${r.severity}</div>`;
1142
+ div.addEventListener('mouseenter', (ev)=> showTooltip(`${r.name}\nSeverity: ${r.severity}\nMitigation: ${r.mitigation}`, ev.clientX, ev.clientY-10));
1143
+ div.addEventListener('mouseleave', hideTooltip);
1144
+ grid.appendChild(div);
1145
+ });
1146
+ }
1147
+ function severityColor(sev){
1148
+ if(sev>=5) return 'linear-gradient(180deg,#ef4444,#b91c1c)';
1149
+ if(sev===4) return 'linear-gradient(180deg,#fb923c,#fb5607)';
1150
+ if(sev===3) return 'linear-gradient(180deg,#f59e0b,#d97706)';
1151
+ if(sev===2) return 'linear-gradient(180deg,#10b981,#059669)';
1152
+ return 'linear-gradient(180deg,#60a5fa,#2563eb)';
1153
+ }
1154
+ renderRiskGrid();
1155
+
1156
+ /* bind risk filter */
1157
+ el('#riskFilter').addEventListener('change', ()=>{
1158
+ renderRiskGrid();
1159
+ localStorage.setItem('riskFilterMin', el('#riskFilter').value);
1160
+ });
1161
+
1162
+ /* ========== Key indicators table (sortable & filterable) ========== */
1163
+ let currentSort = {key:'indicator',dir:1};
1164
+ function renderTable(){
1165
+ const tbody = el('#indTable tbody');
1166
+ tbody.innerHTML='';
1167
+ const minGDP = parseFloat(el('#filterGDP').value || 0);
1168
+ const sector = el('#sectorFilter').value;
1169
+
1170
+ let rows = DATA.indicators.slice();
1171
+
1172
+ // apply filter: if an indicator is a GDP value numeric and >= minGDP include
1173
+ rows = rows.filter(r=>{
1174
+ if(r.indicator.toLowerCase().includes('gdp') && typeof r.value === 'number'){
1175
+ return r.value >= minGDP;
1176
+ }
1177
+ return true;
1178
+ });
1179
+
1180
+ // apply sector filter (basic matching)
1181
+ if(sector !== 'all'){
1182
+ const map = {services:'services', manufacturing:'manufacturing', agri:'agri', other:'other'};
1183
+ rows = rows.filter(r => {
1184
+ const s = r.indicator.toLowerCase();
1185
+ if(sector === 'services') return s.includes('service');
1186
+ if(sector === 'manufacturing') return s.includes('bank') || s.includes('manufact') ? true : s.includes('manufacturing');
1187
+ if(sector === 'agri') return s.includes('agri') || s.includes('agriculture');
1188
+ return true;
1189
+ });
1190
+ }
1191
+
1192
+ // sort
1193
+ rows.sort((a,b)=>{
1194
+ const key = currentSort.key;
1195
+ const va = a[key], vb = b[key];
1196
+ if(typeof va === 'number' && typeof vb === 'number') return currentSort.dir*(va - vb);
1197
+ return currentSort.dir*(String(va).localeCompare(String(vb)));
1198
+ });
1199
+
1200
+ rows.forEach(r=>{
1201
+ const tr = document.createElement('tr');
1202
+ tr.innerHTML = `<td>${r.indicator}</td><td style="text-align:right">${r.value}${r.period.includes('US$')? ' B':''}${typeof r.value==='number' && !r.period.includes('US$')? (r.value%1? '':'') : ''}</td><td style="text-align:right">${r.period}</td><td>${r.source}</td>`;
1203
+ tbody.appendChild(tr);
1204
+ });
1205
+ }
1206
+ renderTable();
1207
+
1208
+ /* sorting handlers */
1209
+ els('#indTable thead th').forEach(th=>{
1210
+ th.addEventListener('click', ()=>{
1211
+ const key = th.getAttribute('data-key');
1212
+ if(currentSort.key === key) currentSort.dir = -currentSort.dir;
1213
+ else { currentSort.key = key; currentSort.dir = 1; }
1214
+ renderTable();
1215
+ });
1216
+ });
1217
+
1218
+ /* filter GDP slider */
1219
+ el('#filterGDP').addEventListener('input', (e)=>{
1220
+ el('#gdpVal').textContent = e.target.value + '%';
1221
+ renderTable();
1222
+ });
1223
+
1224
+ /* sector filter */
1225
+ el('#sectorFilter').addEventListener('change', ()=>{
1226
+ renderTable();
1227
+ localStorage.setItem('sectorFilter', el('#sectorFilter').value);
1228
+ });
1229
+
1230
+ /* Export CSV / copy table */
1231
+ function toCSV(rows){
1232
+ const header = ['Indicator','Value','Period','Source'];
1233
+ const lines = [header.join(',')];
1234
+ rows.forEach(r=>{
1235
+ const v = r.value;
1236
+ lines.push([`"${r.indicator}"`, v, `"${r.period}"`, `"${r.source}"`].join(','));
1237
+ });
1238
+ return lines.join('\n');
1239
+ }
1240
+ el('#exportCSV').addEventListener('click', ()=>{
1241
+ const rows = DATA.indicators;
1242
+ const csv = toCSV(rows);
1243
+ const blob = new Blob([csv], {type:'text/csv;charset=utf-8;'});
1244
+ const url = URL.createObjectURL(blob);
1245
+ const a = document.createElement('a');
1246
+ a.href = url;
1247
+ a.download = 'vietnam_econ_2025_indicators.csv';
1248
+ a.click();
1249
+ URL.revokeObjectURL(url);
1250
+ });
1251
+
1252
+ el('#copyTable').addEventListener('click', async ()=>{
1253
+ const csv = toCSV(DATA.indicators);
1254
+ try{
1255
+ await navigator.clipboard.writeText(csv);
1256
+ showTooltip('Table copied as CSV', window.innerWidth/2, 80);
1257
+ setTimeout(hideTooltip,1200);
1258
+ }catch(e){
1259
+ alert('Clipboard not available');
1260
+ }
1261
+ });
1262
+
1263
+ /* Download JSON */
1264
+ el('#downloadJSON').addEventListener('click', ()=>{
1265
+ const blob = new Blob([JSON.stringify(DATA, null, 2)], {type:'application/json'});
1266
+ const url = URL.createObjectURL(blob);
1267
+ const a = document.createElement('a');
1268
+ a.href = url; a.download = 'vietnam_econ_2025.json'; a.click();
1269
+ URL.revokeObjectURL(url);
1270
+ });
1271
+
1272
+ /* Fill data preview */
1273
+ el('#dataPreview').textContent = JSON.stringify({
1274
+ metadata: DATA.meta,
1275
+ key_indicators: DATA.indicators,
1276
+ sectors: DATA.sectors,
1277
+ risks: DATA.risks
1278
+ }, null, 2);
1279
+
1280
+ /* Outlook chart */
1281
+ function drawOutlook(){
1282
+ const svg = document.querySelector('#outlookChart svg');
1283
+ const g = svg.querySelector('#outPaths');
1284
+ g.innerHTML='';
1285
+ const base = DATA.outlook.baseline;
1286
+ const down = DATA.outlook.downside;
1287
+ const points = base.map((v,i)=>({x:i*95,y:v}));
1288
+ const yScale = scaleLinear([0, Math.max(...base.concat(down))+2],[200,20]);
1289
+ const xScale = i => i*95;
1290
+
1291
+ // baseline path
1292
+ const bpath = document.createElementNS('http://www.w3.org/2000/svg','path');
1293
+ bpath.setAttribute('d', linePath(base, xScale, yScale));
1294
+ bpath.setAttribute('fill','none');
1295
+ bpath.setAttribute('stroke','#0ea5a9');
1296
+ bpath.setAttribute('stroke-width','3');
1297
+ bpath.setAttribute('stroke-linecap','round');
1298
+ bpath.style.opacity=0;
1299
+ g.appendChild(bpath);
1300
+
1301
+ // downside path
1302
+ const dpath = document.createElementNS('http://www.w3.org/2000/svg','path');
1303
+ dpath.setAttribute('d', linePath(down, xScale, yScale));
1304
+ dpath.setAttribute('fill','none');
1305
+ dpath.setAttribute('stroke','#ef4444');
1306
+ dpath.setAttribute('stroke-width','2');
1307
+ dpath.setAttribute('stroke-dasharray','6 6');
1308
+ g.appendChild(dpath);
1309
+
1310
+ // labels
1311
+ base.forEach((v,i)=>{
1312
+ const cx = xScale(i);
1313
+ const cy = yScale(v);
1314
+ const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
1315
+ c.setAttribute('cx', cx);
1316
+ c.setAttribute('cy', cy);
1317
+ c.setAttribute('r', 4);
1318
+ c.setAttribute('fill','#0ea5a9');
1319
+ c.addEventListener('mouseenter',(ev)=> showTooltip(`Baseline ${2023+i}: ${v}%`, ev.clientX, ev.clientY-10));
1320
+ c.addEventListener('mouseleave', hideTooltip);
1321
+ g.appendChild(c);
1322
+ });
1323
+
1324
+ document.getElementById('outlookChart').addEventListener('enteredSection', ()=>{
1325
+ bpath.style.transition='opacity 420ms ease';
1326
+ bpath.style.opacity = 1;
1327
+ });
1328
+ }
1329
+ function linePath(arr, xFn, yFn){
1330
+ return 'M ' + xFn(0) + ' ' + yFn(arr[0]) + arr.slice(1).map((v,i)=> ' L ' + xFn(i+1) + ' ' + yFn(v)).join('');
1331
+ }
1332
+ drawOutlook();
1333
+
1334
+ /* ========== Heatmap + sector + table interactivity done above ========== */
1335
+
1336
+ /* ========== Data export PDF (stub with print) ========== */
1337
+ el('#downloadPDF').addEventListener('click', ()=>{
1338
+ // best-effort: open print dialog - user can save PDF
1339
+ window.print();
1340
+ });
1341
+
1342
+ /* ========== Accessibility: keyboard navigation for TOC ========== */
1343
+ tocLinks.forEach((a,i)=>{
1344
+ a.setAttribute('tabindex',0);
1345
+ a.addEventListener('keydown', (e)=>{
1346
+ if(e.key === 'Enter') a.click();
1347
+ if(e.key === 'ArrowDown') tocLinks[Math.min(tocLinks.length-1,i+1)].focus();
1348
+ if(e.key === 'ArrowUp') tocLinks[Math.max(0,i-1)].focus();
1349
+ });
1350
+ });
1351
+
1352
+ /* ========== Save last scroll position to LocalStorage on unload ========== */
1353
+ window.addEventListener('beforeunload', ()=>{
1354
+ localStorage.setItem('scrollY', window.scrollY || window.pageYOffset);
1355
+ });
1356
+ const lastScroll = parseFloat(localStorage.getItem('scrollY')||'0');
1357
+ if(lastScroll) window.scrollTo(0,lastScroll);
1358
+
1359
+ /* ========== Small UX bits: highlight search in page (simple) ========== */
1360
+ const searchInput = document.createElement('input');
1361
+ searchInput.type='search';
1362
+ searchInput.placeholder='Search report...';
1363
+ searchInput.style.padding='8px';
1364
+ searchInput.style.borderRadius='10px';
1365
+ searchInput.style.border='1px solid #e6eef6';
1366
+ searchInput.style.width='100%';
1367
+
1368
+ const initialTOC = el('.toc');
1369
+ initialTOC.insertAdjacentElement('afterbegin', searchInput);
1370
+ searchInput.addEventListener('input', ()=>{
1371
+ const q = searchInput.value.trim().toLowerCase();
1372
+ if(!q){
1373
+ sections.forEach(s=> s.style.display='block');
1374
+ return;
1375
+ }
1376
+ sections.forEach(s=>{
1377
+ const text = s.textContent.toLowerCase();
1378
+ s.style.display = text.includes(q) ? 'block' : 'none';
1379
+ });
1380
+ });
1381
+
1382
+ /* ========== Small animations reveal on scroll for charts (Intersection) ========== */
1383
+ const chartSections = ['chartGDPHistory','chartQuarter','chartForecast','sectorPie','outlookChart'];
1384
+ chartSections.forEach(id=>{
1385
+ const node = document.getElementById(id);
1386
+ if(node) observer.observe(node);
1387
+ });
1388
+
1389
+ /* ========== Tooltip hide on scroll ========== */
1390
+ window.addEventListener('scroll', hideTooltip);
1391
+
1392
+ /* ========== Final init: ensure everything rendered & bind events ========== */
1393
+ document.addEventListener('DOMContentLoaded', ()=>{
1394
+ renderTable();
1395
+ drawGDPHistory();
1396
+ drawQuarterBars();
1397
+ drawForecastBars();
1398
+ drawSectorPie();
1399
+ drawOutlook();
1400
+ renderRiskGrid();
1401
+ });
1402
+ </script>
1403
+ </body>
1404
+ </html>