thucdangvan020999 commited on
Commit
86d223f
·
verified ·
1 Parent(s): bc092bc

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1138 -18
index.html CHANGED
@@ -1,19 +1,1139 @@
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 Dashboard</title>
7
+
8
+ <!-- Fonts & Icons -->
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;800&display=swap" rel="stylesheet">
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" integrity="sha512-w3q7F3rj7q8qA1m3lKX8pR4pQGm6g5YV0w1N5aKxJr9b3p2Y6s8Zs5R6d3j4k1v2bW8z5c6d9f0g3h5k2j1g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
11
+
12
+ <style>
13
+ :root{
14
+ --bg:#0f1724;
15
+ --card:#0b1220;
16
+ --muted:#9aa4b2;
17
+ --accent:#0ea5a4;
18
+ --accent-2:#7c3aed;
19
+ --glass: rgba(255,255,255,0.03);
20
+ --success:#10b981;
21
+ --danger:#ef4444;
22
+ --gap: clamp(0.5rem, 0.9vw, 1.25rem);
23
+ --radius: 12px;
24
+ color-scheme: dark;
25
+ }
26
+ *{box-sizing:border-box}
27
+ html,body{height:100%}
28
+ body{
29
+ margin:0;
30
+ font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial;
31
+ background:linear-gradient(180deg,#071224 0%, #071422 45%, #04101b 100%);
32
+ color: #e6eef6;
33
+ -webkit-font-smoothing:antialiased;
34
+ -moz-osx-font-smoothing:grayscale;
35
+ padding: clamp(0.5rem,1.5vw,1.5rem);
36
+ display:flex;
37
+ flex-direction:column;
38
+ gap:var(--gap);
39
+ min-height:100%;
40
+ }
41
+
42
+ /* Layout */
43
+ .app{
44
+ display:grid;
45
+ grid-template-columns: 1fr;
46
+ gap:var(--gap);
47
+ align-items:start;
48
+ width:100%;
49
+ max-width:1400px;
50
+ margin:0 auto;
51
+ }
52
+
53
+ header{
54
+ background:linear-gradient(90deg, rgba(255,255,255,0.02), transparent);
55
+ border-radius:var(--radius);
56
+ padding: clamp(0.75rem,1.6vw,1.4rem);
57
+ display:flex;
58
+ gap:var(--gap);
59
+ align-items:center;
60
+ position:relative;
61
+ overflow:hidden;
62
+ }
63
+ .brand{
64
+ display:flex;
65
+ gap:0.75rem;
66
+ align-items:center;
67
+ min-width:0;
68
+ }
69
+ .logo{
70
+ width:56px;height:56px;border-radius:12px;
71
+ background:linear-gradient(135deg,var(--accent),var(--accent-2));
72
+ display:flex;align-items:center;justify-content:center;
73
+ font-weight:800;font-size:20px;
74
+ box-shadow: 0 6px 18px rgba(15,120,120,0.15);
75
+ }
76
+ .title{
77
+ min-width:0;
78
+ }
79
+ .title h1{
80
+ margin:0;font-size:clamp(1rem,2.3vw,1.45rem);letter-spacing:-0.3px;
81
+ font-weight:700;
82
+ }
83
+ .title p{margin:0;color:var(--muted);font-size:0.85rem}
84
+
85
+ /* Controls */
86
+ .controls{margin-left:auto;display:flex;gap:0.5rem;align-items:center}
87
+ .search{
88
+ display:flex;align-items:center;gap:0.5rem;background:var(--glass);
89
+ padding:6px 10px;border-radius:10px;color:var(--muted);
90
+ width:220px;
91
+ }
92
+ .search input{background:transparent;border:0;outline:none;color:inherit;font-size:0.95rem;width:100%}
93
+ .btn{
94
+ background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
95
+ border:1px solid rgba(255,255,255,0.04);
96
+ padding:8px 10px;border-radius:10px;color:inherit;cursor:pointer;
97
+ display:inline-flex;gap:0.5rem;align-items:center;font-weight:600;font-size:0.9rem;
98
+ }
99
+ .btn.ghost{background:transparent;border:1px dashed rgba(255,255,255,0.03)}
100
+ .btn.primary{background:linear-gradient(90deg,var(--accent),var(--accent-2)); color:#04111a;border:0}
101
+
102
+ /* Main grid: TOC + content */
103
+ .main{
104
+ display:grid;
105
+ grid-template-columns: 1fr;
106
+ gap:var(--gap);
107
+ }
108
+
109
+ /* Sticky TOC for wide screens */
110
+ .layout{
111
+ display:grid;
112
+ gap:var(--gap);
113
+ grid-template-columns: 300px 1fr;
114
+ }
115
+
116
+ .toc{
117
+ position:sticky;top:calc(1rem + 16px);align-self:start;
118
+ background:linear-gradient(180deg, rgba(255,255,255,0.01), transparent);
119
+ border-radius:var(--radius);
120
+ padding:var(--gap);
121
+ min-height:200px;
122
+ display:flex;flex-direction:column;gap:0.75rem;
123
+ font-size:0.95rem;color:var(--muted);
124
+ }
125
+ .toc h3{margin:0 0 0.25rem 0;font-size:0.95rem;color:#d9eef6}
126
+ .toc nav{display:flex;flex-direction:column;gap:0.25rem}
127
+ .toc a{
128
+ color:var(--muted);text-decoration:none;padding:6px 8px;border-radius:8px;display:flex;gap:8px;align-items:center;
129
+ }
130
+ .toc a.active, .toc a:hover{background:rgba(255,255,255,0.02);color:var(--accent);font-weight:600}
131
+
132
+ /* Content */
133
+ .content{
134
+ display:grid;gap:var(--gap);
135
+ }
136
+
137
+ /* KPI strip */
138
+ .kpis{
139
+ display:grid;grid-template-columns:repeat(2,1fr);gap:var(--gap);
140
+ }
141
+ .kpi{
142
+ background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
143
+ padding:var(--gap);border-radius:var(--radius);display:flex;align-items:center;gap:0.75rem;
144
+ min-height:84px;
145
+ }
146
+ .kpi .icon{width:52px;height:52px;border-radius:10px;background:rgba(255,255,255,0.02);display:flex;align-items:center;justify-content:center;font-size:1.2rem}
147
+ .kpi h4{margin:0;font-size:1.05rem}
148
+ .kpi p{margin:0;color:var(--muted);font-size:0.9rem}
149
+
150
+ .kpi-grid{
151
+ display:grid;grid-template-columns:repeat(4,1fr);gap:var(--gap);
152
+ }
153
+
154
+ /* Cards */
155
+ .card{
156
+ background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
157
+ padding:var(--gap);border-radius:var(--radius);overflow:hidden;
158
+ }
159
+
160
+ .card h3{margin:0 0 0.5rem 0;font-size:1rem}
161
+ .card .meta{color:var(--muted);font-size:0.9rem;margin-bottom:0.6rem}
162
+
163
+ /* Charts area */
164
+ .charts{
165
+ display:grid;
166
+ grid-template-columns: 1fr;
167
+ gap:var(--gap);
168
+ }
169
+ .chart-row{
170
+ display:grid;
171
+ grid-template-columns: 2fr 1fr;
172
+ gap:var(--gap);
173
+ }
174
+ .mini-charts{
175
+ display:grid;grid-template-columns:repeat(3,1fr);gap:var(--gap);
176
+ }
177
+
178
+ /* Table */
179
+ table{width:100%;border-collapse:collapse;font-size:0.95rem}
180
+ th,td{padding:10px 12px;text-align:left;border-bottom:1px dashed rgba(255,255,255,0.03)}
181
+ th{color:var(--muted);font-weight:700;cursor:pointer}
182
+ tr:hover td{background:rgba(255,255,255,0.01)}
183
+
184
+ /* Collapsible */
185
+ .collapsible{border-radius:10px;overflow:hidden}
186
+ .collapsible summary{
187
+ list-style:none;cursor:pointer;padding:10px;background:rgba(255,255,255,0.01);display:flex;justify-content:space-between;align-items:center;border:1px solid rgba(255,255,255,0.02)
188
+ }
189
+ .collapsible summary::-webkit-details-marker{display:none}
190
+
191
+ /* References */
192
+ .sources{display:grid;gap:8px;font-size:0.92rem;color:var(--muted)}
193
+
194
+ /* Progress & footer */
195
+ .progress-wrap{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;z-index:60;display:flex;gap:8px;align-items:center}
196
+ .progress{width:260px;height:8px;background:rgba(255,255,255,0.03);border-radius:999px;overflow:hidden}
197
+ .progress > i{display:block;height:100%;background:linear-gradient(90deg,var(--accent),var(--accent-2));width:0%}
198
+
199
+ footer{padding:10px 12px;border-radius:12px;background:transparent;color:var(--muted);display:flex;justify-content:space-between;align-items:center;font-size:0.9rem}
200
+
201
+ /* Tooltips */
202
+ .tooltip{
203
+ position:fixed;pointer-events:none;padding:8px 10px;background:rgba(10,14,18,0.95);border:1px solid rgba(255,255,255,0.04);
204
+ color:#dff6f5;border-radius:8px;font-size:0.9rem;transform:translate(-50%,-120%);white-space:nowrap;z-index:80;opacity:0;transition:opacity .15s ease;
205
+ }
206
+
207
+ /* Small screens adjustments */
208
+ @media (max-width:1024px){
209
+ .layout{grid-template-columns: 1fr}
210
+ .toc{position:relative;top:0;order:2}
211
+ .chart-row{grid-template-columns:1fr}
212
+ .mini-charts{grid-template-columns:repeat(2,1fr)}
213
+ .kpi-grid{grid-template-columns:repeat(2,1fr)}
214
+ }
215
+
216
+ @media (max-width:768px){
217
+ header{flex-direction:column;align-items:flex-start}
218
+ .controls{width:100%;justify-content:space-between}
219
+ .search{width:100%}
220
+ .kpi-grid{grid-template-columns:1fr}
221
+ .mini-charts{grid-template-columns:1fr}
222
+ }
223
+
224
+ /* Container queries */
225
+ .card[style]{
226
+ container-type: inline-size;
227
+ }
228
+ @container (min-width:420px){
229
+ .card h3{font-size:1.05rem}
230
+ }
231
+
232
+ /* Utility */
233
+ .muted{color:var(--muted)}
234
+ .tag{padding:6px 8px;border-radius:999px;background:rgba(255,255,255,0.03);font-size:0.85rem;color:var(--muted)}
235
+ .positive{color:var(--success);font-weight:700}
236
+ .negative{color:var(--danger);font-weight:700}
237
+ .small{font-size:0.85rem;color:var(--muted)}
238
+ .legend{display:flex;gap:0.5rem;flex-wrap:wrap}
239
+ .legend button{border:0;background:rgba(255,255,255,0.02);padding:6px 8px;border-radius:8px;color:var(--muted);cursor:pointer}
240
+ </style>
241
+ </head>
242
+ <body>
243
+ <div class="app" id="app">
244
+ <header>
245
+ <div class="brand">
246
+ <div class="logo" aria-hidden>VN</div>
247
+ <div class="title">
248
+ <h1>Vietnam Economic Growth Report — 2025</h1>
249
+ <p>Executive analytics dashboard — KPIs, trends, sectoral insights & risk analysis</p>
250
+ </div>
251
+ </div>
252
+
253
+ <div class="controls">
254
+ <div class="search" title="Search in report">
255
+ <i class="fa fa-magnifying-glass"></i>
256
+ <input id="globalSearch" placeholder="Search report..." aria-label="Search report" />
257
+ </div>
258
+
259
+ <button class="btn ghost" id="toggleTheme" title="Copy executive summary"><i class="fa fa-sun"></i></button>
260
+ <button class="btn" id="copySummary" title="Copy executive summary"><i class="fa fa-copy"></i> Copy</button>
261
+ <button class="btn primary" id="exportCsv" title="Export indicators CSV"><i class="fa fa-file-export"></i> Export</button>
262
+ </div>
263
+ </header>
264
+
265
+ <main class="main">
266
+ <section class="layout">
267
+ <aside class="toc" id="toc">
268
+ <h3>Contents</h3>
269
+ <nav>
270
+ <a href="#executive" data-target="executive" class="active"><i class="fa fa-star"></i> Executive Summary</a>
271
+ <a href="#kpis" data-target="kpis"><i class="fa fa-tachometer-alt"></i> Key Indicators</a>
272
+ <a href="#charts" data-target="charts"><i class="fa fa-chart-line"></i> Charts & Trends</a>
273
+ <a href="#sector" data-target="sector"><i class="fa fa-industry"></i> Sectoral Analysis</a>
274
+ <a href="#risks" data-target="risks"><i class="fa fa-exclamation-triangle"></i> Risks & Mitigation</a>
275
+ <a href="#methodology" data-target="methodology"><i class="fa fa-cogs"></i> Methodology</a>
276
+ <a href="#references" data-target="references"><i class="fa fa-book"></i> Sources</a>
277
+ </nav>
278
+
279
+ <div style="margin-top:8px">
280
+ <div class="small muted">Jump to</div>
281
+ <div style="display:flex;gap:6px;margin-top:6px">
282
+ <button class="btn ghost" id="backTop"><i class="fa fa-arrow-up"></i></button>
283
+ <button class="btn ghost" id="saveView"><i class="fa fa-save"></i></button>
284
+ </div>
285
+ </div>
286
+ </aside>
287
+
288
+ <section class="content" id="content">
289
+ <article id="executive" class="card">
290
+ <h3>Executive Summary</h3>
291
+ <div class="meta">High-level synthesis and context — 2025 performance highlights</div>
292
+
293
+ <div style="display:flex;gap:var(--gap);flex-direction:column">
294
+ <p id="execText" class="small" style="line-height:1.5">
295
+ Vietnam's economy continues to demonstrate robust growth momentum in 2025, with GDP expanding 7.96% year-on-year in Q2 2025 and recording 7.52% growth for H1 2025 — the strongest mid-year result since 2011. Industry and services lead growth despite global trade tensions. Core fundamentals such as low unemployment, controlled inflation, and strong FDI inflows underpin near-term prospects, while external risks (tariffs, geopolitical instability) could moderate outcomes. Government targets remain ambitious relative to multilateral forecasts.
296
+ </p>
297
+
298
+ <div style="display:flex;gap:10px;flex-wrap:wrap">
299
+ <button class="btn" id="copyExec"><i class="fa fa-clipboard"></i> Copy Summary</button>
300
+ <button class="btn ghost" id="downloadJson"><i class="fa fa-download"></i> Download JSON</button>
301
+ <button class="btn" id="expandExec"><i class="fa fa-chevron-down"></i> Expand</button>
302
+ </div>
303
+ </div>
304
+ </article>
305
+
306
+ <section id="kpis" class="card">
307
+ <h3>Key Performance Indicators (KPIs)</h3>
308
+ <div class="meta">Snapshot of main metrics — click to explore</div>
309
+
310
+ <div class="kpi-grid" style="margin-top:var(--gap)">
311
+ <div class="kpi" data-kpi="gdp">
312
+ <div class="icon"><i class="fa fa-chart-simple"></i></div>
313
+ <div>
314
+ <h4 id="gdpNow">GDP H1 2025: 7.52%</h4>
315
+ <p class="small">Q2 YoY: 7.96% • Q1: 6.9% • Government Target: 8.3–8.5%</p>
316
+ </div>
317
+ </div>
318
+ <div class="kpi" data-kpi="inflation">
319
+ <div class="icon"><i class="fa fa-percent"></i></div>
320
+ <div>
321
+ <h4 id="inflNow">Inflation June 2025: 3.57%</h4>
322
+ <p class="small">IMF: 2.9% • ADB: 4.0% — within 3–4.5% target</p>
323
+ </div>
324
+ </div>
325
+ <div class="kpi" data-kpi="unemp">
326
+ <div class="icon"><i class="fa fa-briefcase"></i></div>
327
+ <div>
328
+ <h4 id="unempNow">Unemployment Q1 2025: 2.20%</h4>
329
+ <p class="small">Stable, historically low labor-market figure</p>
330
+ </div>
331
+ </div>
332
+ <div class="kpi" data-kpi="fdi">
333
+ <div class="icon"><i class="fa fa-hand-holding-dollar"></i></div>
334
+ <div>
335
+ <h4 id="fdiNow">FDI H1 2025: US$21.51B (+32.6% YoY)</h4>
336
+ <p class="small">Registered capital (5 months): $18.4B; Disbursed: $8.9B</p>
337
+ </div>
338
+ </div>
339
+ </div>
340
+ </section>
341
+
342
+ <section id="charts" class="card">
343
+ <h3>Charts & Trends</h3>
344
+ <div class="meta">Interactive visualizations — hover, toggle and filter</div>
345
+
346
+ <div class="charts" style="margin-top:var(--gap)">
347
+ <div class="chart-row">
348
+ <div class="card" id="gdpChartCard">
349
+ <h3>GDP Growth — Q1 (2020–2025) & H1 2025</h3>
350
+ <div class="small muted">Year-on-year (%) — historical and short-run trend</div>
351
+ <div id="gdpChart" style="height:260px;margin-top:12px"></div>
352
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-top:10px">
353
+ <div class="legend" id="gdpLegend"></div>
354
+ <div>
355
+ <button class="btn ghost" id="toggleSmooth">Toggle smoothing</button>
356
+ <button class="btn" id="copyChartSvg"><i class="fa fa-clone"></i></button>
357
+ </div>
358
+ </div>
359
+ </div>
360
+
361
+ <div class="card">
362
+ <h3>Forecast Comparison 2025</h3>
363
+ <div class="small muted">World Bank, ADB, IMF, Government</div>
364
+ <div id="forecastBar" style="height:260px;margin-top:10px"></div>
365
+ <div style="margin-top:10px" class="small muted">Click legend to highlight</div>
366
+ </div>
367
+ </div>
368
+
369
+ <div class="mini-charts" style="margin-top:var(--gap)">
370
+ <div class="card">
371
+ <h3>Inflation (May–June 2025)</h3>
372
+ <div class="small muted">Monthly CPI (%)</div>
373
+ <div id="inflationSpark" style="height:140px;margin-top:12px"></div>
374
+ </div>
375
+
376
+ <div class="card">
377
+ <h3>FDI Inflows — H1 2025</h3>
378
+ <div class="small muted">Registered vs Disbursed (US$B)</div>
379
+ <div id="fdiDonut" style="height:140px;margin-top:12px"></div>
380
+ </div>
381
+
382
+ <div class="card">
383
+ <h3>Retail Sales Q1 2025</h3>
384
+ <div class="small muted">1.708 quadrillion VND — YoY growth 9.9%</div>
385
+ <div style="margin-top:12px">
386
+ <div class="tag">Consumption-led recovery</div>
387
+ </div>
388
+ </div>
389
+ </div>
390
+ </div>
391
+ </section>
392
+
393
+ <section id="sector" class="card">
394
+ <h3>Sectoral Analysis</h3>
395
+ <div class="meta">Drivers, traction and structural context</div>
396
+
397
+ <div style="display:grid;grid-template-columns:1fr;gap:var(--gap);margin-top:var(--gap)">
398
+ <div class="collapsible">
399
+ <details open>
400
+ <summary>
401
+ <div style="display:flex;gap:10px;align-items:center">
402
+ <strong>Primary Growth Drivers</strong>
403
+ <div class="small muted">Services, Manufacturing, Exports, Banking</div>
404
+ </div>
405
+ <i class="fa fa-chevron-down"></i>
406
+ </summary>
407
+ <div style="padding:12px">
408
+ <ul class="small muted">
409
+ <li><strong>Services:</strong> Leading contributor to GDP growth in H1 2025.</li>
410
+ <li><strong>Manufacturing:</strong> Recovery supported by foreign investment and export demand.</li>
411
+ <li><strong>Exports:</strong> Remain the backbone despite trade tensions.</li>
412
+ <li><strong>Banking:</strong> Projected +17% earnings growth in 2025 based on credit growth of 15%.</li>
413
+ </ul>
414
+ <div style="margin-top:10px">
415
+ <button class="btn ghost" id="drillSector">Drill into sectors</button>
416
+ </div>
417
+ </div>
418
+ </details>
419
+ </div>
420
+
421
+ <div class="card">
422
+ <h3>Comparative Industry Snapshot</h3>
423
+ <div class="small muted">Normalized contributions & narrative insights</div>
424
+ <div style="margin-top:12px" id="sectorBars" style="height:160px"></div>
425
+ <div style="margin-top:8px" class="small muted">Hover bars for details • Click to pin insight</div>
426
+ </div>
427
+ </div>
428
+ </section>
429
+
430
+ <section id="risks" class="card">
431
+ <h3>Challenges & Risk Factors</h3>
432
+ <div class="meta">Scenario-aware planning and mitigation options</div>
433
+ <ul class="small muted" style="margin-top:12px">
434
+ <li><strong>Global trade tensions:</strong> Potential export slowdowns.</li>
435
+ <li><strong>US tariff policies:</strong> Pressure on export-oriented firms.</li>
436
+ <li><strong>Geopolitics:</strong> Elevated uncertainty for supply chains.</li>
437
+ <li><strong>FDI concentration:</strong> Overdependence risks and inflationary pressure.</li>
438
+ <li><strong>Macroeconomic trade-offs:</strong> Growth vs. inflation and public debt.</li>
439
+ </ul>
440
+ <div style="display:flex;gap:8px;margin-top:12px">
441
+ <button class="btn" id="mitigateBtn"><i class="fa fa-lightbulb"></i> Mitigation strategies</button>
442
+ <button class="btn ghost" id="scenarioBtn"><i class="fa fa-sliders-h"></i> Run scenario</button>
443
+ </div>
444
+ </section>
445
+
446
+ <section id="methodology" class="card">
447
+ <h3>Methodology</h3>
448
+ <div class="meta">Data processing, assumptions & limitations</div>
449
+ <ol class="small muted" style="margin-top:12px">
450
+ <li>Data aggregated from cited sources (GSO, IMF, ADB, Trading Economics).</li>
451
+ <li>YoY calculations based on official quarterly releases; forecasts compared across institutions.</li>
452
+ <li>Charts use normalized scaling for cross-metric comparison; interactivity does not alter source raw values.</li>
453
+ <li>Limitations: near-real-time revisions possible; cross-check with primary sources recommended.</li>
454
+ </ol>
455
+ </section>
456
+
457
+ <section id="references" class="card">
458
+ <h3>Sources & Citations</h3>
459
+ <div class="meta">Primary sources used to compile this dashboard</div>
460
+ <div class="sources" style="margin-top:12px">
461
+ <a href="https://tradingeconomics.com/vietnam/gdp-growth-annual" target="_blank">Trading Economics — Vietnam GDP</a>
462
+ <a href="https://www.imf.org/en/Countries/VNM" target="_blank">IMF — Vietnam Country Profile</a>
463
+ <a href="https://www.gso.gov.vn/en/" target="_blank">General Statistics Office of Vietnam</a>
464
+ <a href="https://www.adb.org/countries/viet-nam/main" target="_blank">Asian Development Bank — Vietnam</a>
465
+ <a href="https://vneconomictimes.com/" target="_blank">Vietnam Economic Times</a>
466
+ </div>
467
+ </section>
468
+
469
+ <section id="appendices" class="card">
470
+ <h3>Appendices</h3>
471
+ <div class="meta">Extended tables & raw figures</div>
472
+
473
+ <div style="margin-top:12px;overflow:auto">
474
+ <table id="dataTable">
475
+ <thead>
476
+ <tr>
477
+ <th data-key="metric">Metric</th>
478
+ <th data-key="q1">Q1 2025</th>
479
+ <th data-key="q2">Q2 2025</th>
480
+ <th data-key="h1">H1 2025</th>
481
+ <th data-key="note">Note</th>
482
+ </tr>
483
+ </thead>
484
+ <tbody>
485
+ <tr>
486
+ <td>GDP Growth (YoY)</td>
487
+ <td>6.9%</td>
488
+ <td>7.96%</td>
489
+ <td>7.52%</td>
490
+ <td>Strong industry & services</td>
491
+ </tr>
492
+ <tr>
493
+ <td>Inflation</td>
494
+ <td>—</td>
495
+ <td>3.57% (June)</td>
496
+ <td>—</td>
497
+ <td>May: 3.24%</td>
498
+ </tr>
499
+ <tr>
500
+ <td>Unemployment</td>
501
+ <td>2.20%</td>
502
+ <td>—</td>
503
+ <td>—</td>
504
+ <td>Q1 2025</td>
505
+ </tr>
506
+ <tr>
507
+ <td>FDI (Registered, 5 months)</td>
508
+ <td colspan="2">$18.4B (registered)</td>
509
+ <td>$21.51B (H1 total)</td>
510
+ <td>+32.6% YoY</td>
511
+ </tr>
512
+ <tr>
513
+ <td>Retail Sales</td>
514
+ <td colspan="3">1.708 quadrillion VND (Q1) — 9.9% YoY</td>
515
+ <td>Consumption growth</td>
516
+ </tr>
517
+ </tbody>
518
+ </table>
519
+
520
+ <div style="display:flex;gap:8px;margin-top:8px">
521
+ <button class="btn" id="exportTableCsv"><i class="fa fa-file-csv"></i> Export CSV</button>
522
+ <button class="btn ghost" id="sortTable"><i class="fa fa-sort"></i> Sort GDP rows</button>
523
+ </div>
524
+ </div>
525
+ </section>
526
+
527
+ </section>
528
+ </section>
529
+
530
+ <div style="display:flex;gap:var(--gap);flex-wrap:wrap;align-items:center;justify-content:space-between">
531
+ <div class="small muted">Last updated: 2025 • Dashboard generated from report text</div>
532
+ <div style="display:flex;gap:8px">
533
+ <div class="small muted">Saved view: <span id="savedLabel">none</span></div>
534
+ </div>
535
+ </div>
536
+
537
+ </main>
538
+
539
+ <div class="progress-wrap" aria-hidden>
540
+ <div class="progress"><i id="progBar"></i></div>
541
+ <div class="tag small muted" id="progPct">0%</div>
542
+ </div>
543
+
544
+ <div class="tooltip" id="tooltip" role="tooltip" aria-hidden="true"></div>
545
+
546
+ <footer>
547
+ <div>© Vietnam Economic Dashboard — 2025</div>
548
+ <div class="small muted">Built with Web APIs • Interactive • Single-file</div>
549
+ </footer>
550
+ </div>
551
+
552
+ <script>
553
+ /***************
554
+ * Data model
555
+ ***************/
556
+ const DATA = {
557
+ historyQ1: [
558
+ {year:2020, q1:3.21},
559
+ {year:2021, q1:4.85},
560
+ {year:2022, q1:5.42},
561
+ {year:2023, q1:3.46},
562
+ {year:2024, q1:5.98},
563
+ {year:2025, q1:6.93}
564
+ ],
565
+ q2025: {q1:6.9, q2:7.96, h1:7.52},
566
+ forecasts: [
567
+ {name:"World Bank", value:5.8, color:"#0ea5a4"},
568
+ {name:"ADB", value:6.6, color:"#7c3aed"},
569
+ {name:"IMF", value:5.2, color:"#f59e0b"},
570
+ {name:"Government", value:8.4, color:"#ef4444"}
571
+ ],
572
+ inflation: [
573
+ {label:"May 2025", value:3.24},
574
+ {label:"June 2025", value:3.57}
575
+ ],
576
+ unemployment_q1:2.20,
577
+ fdi: {
578
+ registered_5m:18.4,
579
+ disbursed_5m:8.9,
580
+ total_h1:21.51,
581
+ yoy:32.6
582
+ },
583
+ retail_q1_vnd:1708, // trillion? using quadrillion as textual; keep numeric for note
584
+ sectorShares: [
585
+ {name:"Services", value:45, color:"#06b6d4"},
586
+ {name:"Manufacturing", value:30, color:"#7c3aed"},
587
+ {name:"Exports", value:15, color:"#34d399"},
588
+ {name:"Banking", value:10, color:"#f97316"}
589
+ ]
590
+ };
591
+
592
+ /*******************
593
+ * Utilities
594
+ *******************/
595
+ function el(sel){return document.querySelector(sel)}
596
+ function els(sel){return Array.from(document.querySelectorAll(sel))}
597
+
598
+ // Smooth scroll for TOC links
599
+ els('.toc a').forEach(a=>{
600
+ a.addEventListener('click', (e)=>{
601
+ e.preventDefault();
602
+ const id = a.getAttribute('data-target');
603
+ const node = el('#'+id);
604
+ if(node) node.scrollIntoView({behavior:'smooth', block:'start'});
605
+ els('.toc a').forEach(x=>x.classList.remove('active')); a.classList.add('active');
606
+ });
607
+ });
608
+
609
+ // Back to top & save view
610
+ el('#backTop').addEventListener('click', ()=>window.scrollTo({top:0,behavior:'smooth'}));
611
+ el('#saveView').addEventListener('click', ()=>{
612
+ const view = {
613
+ timestamp: Date.now(),
614
+ filters: {highlight:el('#globalSearch').value||null}
615
+ };
616
+ localStorage.setItem('vn_report_view', JSON.stringify(view));
617
+ el('#savedLabel').textContent = new Date(view.timestamp).toLocaleString();
618
+ flash('Saved view');
619
+ });
620
+
621
+ // Load saved view if present
622
+ (function(){
623
+ const v = localStorage.getItem('vn_report_view');
624
+ if(v){ try{
625
+ const view = JSON.parse(v);
626
+ el('#savedLabel').textContent = new Date(view.timestamp).toLocaleString();
627
+ }catch(e){}}
628
+ })();
629
+
630
+ // Simple flash notification using tooltip element
631
+ function flash(text, ms=1200){
632
+ const t = el('#tooltip');
633
+ t.textContent = text; t.style.opacity=1; t.style.transform='translate(-50%,-60%)';
634
+ t.style.left = '50%'; t.style.top='20%';
635
+ setTimeout(()=>{t.style.opacity=0}, ms);
636
+ }
637
+
638
+ /*******************
639
+ * Search & highlight
640
+ *******************/
641
+ el('#globalSearch').addEventListener('input', (e)=>{
642
+ const q = e.target.value.trim().toLowerCase();
643
+ if(!q){ // clear highlights
644
+ els('.content *').forEach(n=>n.style.outline='');
645
+ return;
646
+ }
647
+ // naive search: highlight elements containing text
648
+ els('.content p, .content li, .content td, .content h3, .content .small').forEach(node=>{
649
+ const txt = node.textContent.toLowerCase();
650
+ if(txt.includes(q)){
651
+ node.style.outline = '2px solid rgba(14,165,164,0.15)';
652
+ } else {
653
+ node.style.outline = '';
654
+ }
655
+ });
656
+ });
657
+
658
+ /*******************
659
+ * Copy executive summary & export
660
+ *******************/
661
+ const execText = el('#execText').textContent.trim();
662
+ el('#copyExec').addEventListener('click', async ()=>{
663
+ try{
664
+ await navigator.clipboard.writeText(execText);
665
+ flash('Executive summary copied');
666
+ }catch(e){flash('Unable to copy')};
667
+ });
668
+
669
+ el('#copySummary').addEventListener('click', async ()=>{
670
+ const fullText = `Vietnam Economic Growth Report 2025 — Executive Summary:\n\n${execText}\n\nSources: GSO, IMF, ADB, Trading Economics`;
671
+ try{ await navigator.clipboard.writeText(fullText); flash('Summary copied'); }catch(e){ flash('Copy failed') }
672
+ });
673
+
674
+ el('#downloadJson').addEventListener('click', ()=>{
675
+ const payload = {
676
+ summary: execText,
677
+ data: DATA,
678
+ generated: new Date().toISOString()
679
+ };
680
+ const blob = new Blob([JSON.stringify(payload, null, 2)], {type:'application/json'});
681
+ const url = URL.createObjectURL(blob);
682
+ const a = document.createElement('a'); a.href=url; a.download = 'vn-economic-report-2025.json'; a.click();
683
+ URL.revokeObjectURL(url);
684
+ flash('JSON downloaded');
685
+ });
686
+
687
+ // Export CSV (indicators)
688
+ function csvFromTable(rows){
689
+ return rows.map(r => r.map(c => `"${String(c).replace(/"/g,'""')}"`).join(',')).join('\n');
690
+ }
691
+ el('#exportCsv').addEventListener('click', ()=>{
692
+ const rows = [
693
+ ['Metric','Q1 2025','Q2 2025','H1 2025','Note'],
694
+ ['GDP Growth (YoY)', DATA.q2025.q1+'%', DATA.q2025.q2+'%', DATA.q2025.h1+'%','Strong industry & services'],
695
+ ['Inflation','May: 3.24%','June: 3.57%','','IMF:2.9%, ADB:4.0%'],
696
+ ['Unemployment','2.20%','','','Q1 2025'],
697
+ ['FDI','Registered (5 months) $18.4B','Disbursed $8.9B','Total H1 $21.51B','+32.6% YoY']
698
+ ];
699
+ const csv = csvFromTable(rows);
700
+ const blob = new Blob([csv], {type:'text/csv'});
701
+ const link = document.createElement('a');
702
+ link.href = URL.createObjectURL(blob);
703
+ link.download = 'vn_indicators_2025.csv';
704
+ link.click();
705
+ URL.revokeObjectURL(link.href);
706
+ flash('CSV exported');
707
+ });
708
+
709
+ // Export table CSV
710
+ el('#exportTableCsv').addEventListener('click', ()=>{
711
+ const tbody = Array.from(el('#dataTable tbody').rows);
712
+ const rows = [['Metric','Q1 2025','Q2 2025','H1 2025','Note']];
713
+ tbody.forEach(r=>{
714
+ const cells = Array.from(r.cells).map(c=>c.textContent.trim());
715
+ rows.push(cells);
716
+ });
717
+ const csv = csvFromTable(rows);
718
+ const blob = new Blob([csv], {type:'text/csv'});
719
+ const a = document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='vn_appendix.csv'; a.click();
720
+ URL.revokeObjectURL(a.href);
721
+ flash('Table CSV exported');
722
+ });
723
+
724
+ // Sort table (basic)
725
+ el('#sortTable').addEventListener('click', ()=>{
726
+ const tbody = el('#dataTable tbody');
727
+ const rows = Array.from(tbody.querySelectorAll('tr'));
728
+ // sort by numeric in first numeric-like cell (attempt)
729
+ rows.sort((a,b)=>{
730
+ const ta = a.cells[1].textContent.match(/[\d\.]+/);
731
+ const tb = b.cells[1].textContent.match(/[\d\.]+/);
732
+ const na = ta? parseFloat(ta[0]) : 0;
733
+ const nb = tb? parseFloat(tb[0]) : 0;
734
+ return nb - na;
735
+ });
736
+ rows.forEach(r=>tbody.appendChild(r));
737
+ flash('Table sorted');
738
+ });
739
+
740
+ /*******************
741
+ * Intersection observer for reveal & progress
742
+ *******************/
743
+ const progBar = el('#progBar'), progPct = el('#progPct');
744
+ function updateProgress(){
745
+ const doc = document.documentElement;
746
+ const top = doc.scrollTop || document.body.scrollTop;
747
+ const h = doc.scrollHeight - doc.clientHeight;
748
+ const pct = Math.round((top/h)*100);
749
+ progBar.style.width = pct + '%';
750
+ progPct.textContent = pct + '%';
751
+ }
752
+ window.addEventListener('scroll', updateProgress);
753
+ window.addEventListener('resize', updateProgress);
754
+ updateProgress();
755
+
756
+ // Reveal animations using IntersectionObserver
757
+ const observer = new IntersectionObserver((entries)=>{
758
+ entries.forEach(entry=>{
759
+ if(entry.isIntersecting){
760
+ entry.target.style.transform = 'translateY(0)';
761
+ entry.target.style.opacity = 1;
762
+ observer.unobserve(entry.target);
763
+ }
764
+ });
765
+ }, {threshold:0.12});
766
+ // apply to cards
767
+ els('.card').forEach(c=>{
768
+ c.style.opacity=0; c.style.transform='translateY(10px)'; c.style.transition='opacity .6s ease, transform .6s ease';
769
+ observer.observe(c);
770
+ });
771
+
772
+ /*******************
773
+ * Simple SVG charting functions
774
+ *******************/
775
+ function createSVG(width, height){
776
+ const svgNS = "http://www.w3.org/2000/svg";
777
+ const svg = document.createElementNS(svgNS,'svg');
778
+ svg.setAttribute('width','100%');
779
+ svg.setAttribute('viewBox',`0 0 ${width} ${height}`);
780
+ svg.setAttribute('preserveAspectRatio','none');
781
+ svg.style.display='block';
782
+ return svg;
783
+ }
784
+
785
+ // Line chart for GDP q1 history
786
+ function drawGDPLine(elm, data, opts={}){
787
+ elm.innerHTML='';
788
+ const w = 760, h = 260, pad = 36;
789
+ const svg = createSVG(w,h);
790
+ const svgNS = "http://www.w3.org/2000/svg";
791
+
792
+ const values = data.map(d => d.q1);
793
+ const minVal = Math.min(...values) - 1;
794
+ const maxVal = Math.max(...values) + 1;
795
+ const xStep = (w - pad*2) / (data.length - 1);
796
+
797
+ // grid lines
798
+ for(let i=0;i<5;i++){
799
+ const y = pad + i*(h - pad*2)/4;
800
+ const line = document.createElementNS(svgNS,'line');
801
+ line.setAttribute('x1',pad); line.setAttribute('x2',w-pad);
802
+ line.setAttribute('y1',y); line.setAttribute('y2',y);
803
+ line.setAttribute('stroke','rgba(255,255,255,0.03)');
804
+ line.setAttribute('stroke-width','1');
805
+ svg.appendChild(line);
806
+ }
807
+
808
+ // path
809
+ const pts = data.map((d,i)=>{
810
+ const x = pad + i*xStep;
811
+ const y = pad + (1 - (d.q1 - minVal)/(maxVal - minVal))*(h - pad*2);
812
+ return [x,y];
813
+ });
814
+
815
+ // optionally do smoothing
816
+ const pathData = opts.smooth ? catmullRom2bezier(pts) : ('M '+pts.map(p=>p.join(' ')).join(' L '));
817
+
818
+ const path = document.createElementNS(svgNS,'path');
819
+ path.setAttribute('d', pathData);
820
+ path.setAttribute('fill','none');
821
+ path.setAttribute('stroke','url(#ggrad)');
822
+ path.setAttribute('stroke-width','3');
823
+ path.setAttribute('stroke-linejoin','round');
824
+ path.setAttribute('stroke-linecap','round');
825
+ svg.appendChild(path);
826
+
827
+ // gradient
828
+ const defs = document.createElementNS(svgNS,'defs');
829
+ defs.innerHTML = `<linearGradient id="ggrad" x1="0" x2="1" y1="0" y2="0">
830
+ <stop offset="0%" stop-color="${DATA.forecasts[0].color}" />
831
+ <stop offset="100%" stop-color="${DATA.forecasts[1].color}" />
832
+ </linearGradient>`;
833
+ svg.appendChild(defs);
834
+
835
+ // points & labels & hover
836
+ const gPoints = document.createElementNS(svgNS,'g');
837
+ pts.forEach((p,i)=>{
838
+ const c = document.createElementNS(svgNS,'circle');
839
+ c.setAttribute('cx',p[0]); c.setAttribute('cy',p[1]); c.setAttribute('r',4);
840
+ c.setAttribute('fill','#fff'); c.setAttribute('stroke','rgba(0,0,0,0.2)'); c.setAttribute('stroke-width',1);
841
+ c.style.cursor='pointer';
842
+ c.addEventListener('mouseenter', (ev)=>showTip(`${data[i].year} Q1: ${data[i].q1}%`, ev.clientX, ev.clientY));
843
+ c.addEventListener('mouseleave', hideTip);
844
+ gPoints.appendChild(c);
845
+
846
+ // year labels
847
+ const t = document.createElementNS(svgNS,'text');
848
+ t.setAttribute('x', p[0]); t.setAttribute('y', h - 6);
849
+ t.setAttribute('font-size', '10'); t.setAttribute('text-anchor','middle');
850
+ t.setAttribute('fill','rgba(255,255,255,0.6)');
851
+ t.textContent = data[i].year;
852
+ svg.appendChild(t);
853
+ });
854
+ svg.appendChild(gPoints);
855
+
856
+ // axis left values
857
+ for(let i=0;i<5;i++){
858
+ const v = (maxVal - i*(maxVal - minVal)/4).toFixed(1);
859
+ const y = pad + i*(h - pad*2)/4;
860
+ const t = document.createElementNS(svgNS,'text');
861
+ t.setAttribute('x',6); t.setAttribute('y', y+4);
862
+ t.setAttribute('font-size','10'); t.setAttribute('fill','rgba(255,255,255,0.6)');
863
+ t.textContent = v + '%';
864
+ svg.appendChild(t);
865
+ }
866
+
867
+ elm.appendChild(svg);
868
+ }
869
+
870
+ // Simple bar chart for forecasts
871
+ function drawForecastBar(elm, data){
872
+ elm.innerHTML='';
873
+ const w = 360, h = 240, pad=40;
874
+ const svg = createSVG(w,h);
875
+ const svgNS="http://www.w3.org/2000/svg";
876
+ const max = Math.max(...data.map(d=>d.value)) + 1;
877
+ const bw = (w - pad*2)/data.length - 8;
878
+
879
+ data.forEach((d,i)=>{
880
+ const x = pad + i*((w - pad*2)/data.length) + 4;
881
+ const y = pad + (1 - d.value/max)*(h - pad*2);
882
+ const rect = document.createElementNS(svgNS,'rect');
883
+ rect.setAttribute('x', x); rect.setAttribute('y', y);
884
+ rect.setAttribute('width', bw); rect.setAttribute('height', h - pad - y);
885
+ rect.setAttribute('fill', d.color);
886
+ rect.style.cursor='pointer';
887
+ rect.addEventListener('mouseenter', (ev)=>showTip(`${d.name}: ${d.value}%`, ev.clientX, ev.clientY));
888
+ rect.addEventListener('mouseleave', hideTip);
889
+ rect.addEventListener('click', ()=>flash(`${d.name} highlighted`));
890
+ svg.appendChild(rect);
891
+
892
+ const t = document.createElementNS(svgNS,'text');
893
+ t.setAttribute('x', x + bw/2); t.setAttribute('y', h - 8); t.setAttribute('text-anchor','middle');
894
+ t.setAttribute('font-size','11'); t.setAttribute('fill','rgba(255,255,255,0.8)');
895
+ t.textContent = d.name;
896
+ svg.appendChild(t);
897
+ });
898
+ elm.appendChild(svg);
899
+ }
900
+
901
+ // Small sparkline for inflation
902
+ function drawSparkline(elm, data){
903
+ elm.innerHTML='';
904
+ const w = 320, h = 120, pad=20;
905
+ const svg = createSVG(w,h); const NS="http://www.w3.org/2000/svg";
906
+ const values = data.map(d=>d.value);
907
+ const max = Math.max(...values)+0.5;
908
+ const min = Math.min(...values)-0.5;
909
+ const step = (w - pad*2)/(values.length-1);
910
+
911
+ const pts = values.map((v,i)=>[pad + i*step, pad + (1-(v-min)/(max-min))*(h - pad*2)]);
912
+ // path
913
+ const dpath = 'M '+pts.map(p=>p.join(' ')).join(' L ');
914
+ const path = document.createElementNS(NS,'path'); path.setAttribute('d',dpath); path.setAttribute('fill','none');
915
+ path.setAttribute('stroke','url(#g2)'); path.setAttribute('stroke-width','2');
916
+ svg.appendChild(path);
917
+
918
+ const defs = document.createElementNS(NS,'defs');
919
+ defs.innerHTML = `<linearGradient id="g2"><stop offset="0%" stop-color="${DATA.forecasts[0].color}"/><stop offset="100%" stop-color="${DATA.forecasts[2].color}"/></linearGradient>`;
920
+ svg.appendChild(defs);
921
+
922
+ pts.forEach((p,i)=>{
923
+ const c = document.createElementNS(NS,'circle'); c.setAttribute('cx',p[0]); c.setAttribute('cy',p[1]); c.setAttribute('r',3); c.setAttribute('fill','#fff');
924
+ c.addEventListener('mouseenter', (ev)=>showTip(`${data[i].label}: ${data[i].value}%`, ev.clientX, ev.clientY));
925
+ c.addEventListener('mouseleave', hideTip);
926
+ svg.appendChild(c);
927
+ });
928
+
929
+ elm.appendChild(svg);
930
+ }
931
+
932
+ // Donut chart for FDI breakdown
933
+ function drawDonut(elm, reg, disb){
934
+ elm.innerHTML='';
935
+ const w = 320, h = 140; const NS="http://www.w3.org/2000/svg";
936
+ const svg = createSVG(w,h);
937
+ const cx = w/2, cy = h/2, r = 48;
938
+ const total = Math.max(reg, disb);
939
+ const vals = [{name:'Registered',v:reg,color:'#06b6d4'},{name:'Disbursed',v:disb,color:'#7c3aed'}];
940
+ let angle = -90;
941
+ vals.forEach(v=>{
942
+ const slice = (v.v/total)*360;
943
+ const [x1,y1] = pol(angle, r, cx, cy);
944
+ angle += slice;
945
+ const [x2,y2] = pol(angle, r, cx, cy);
946
+ const large = slice>180?1:0;
947
+ const d = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} z`;
948
+ const p = document.createElementNS(NS,'path'); p.setAttribute('d',d); p.setAttribute('fill',v.color);
949
+ p.addEventListener('mouseenter', (ev)=>showTip(`${v.name}: $${v.v}B`, ev.clientX, ev.clientY));
950
+ p.addEventListener('mouseleave', hideTip);
951
+ svg.appendChild(p);
952
+ });
953
+
954
+ // centre label
955
+ const text = document.createElementNS(NS,'text'); text.setAttribute('x',cx); text.setAttribute('y',cy+6);
956
+ text.setAttribute('text-anchor','middle'); text.setAttribute('font-size','12'); text.setAttribute('fill','rgba(255,255,255,0.9)');
957
+ text.textContent = `${DATA.fdi.total_h1}B (H1)`;
958
+ svg.appendChild(text);
959
+
960
+ elm.appendChild(svg);
961
+ }
962
+
963
+ // Sector bars
964
+ function drawSectorBars(elm, data){
965
+ elm.innerHTML='';
966
+ const w = 680, h = 140, pad=20;
967
+ const svg = createSVG(w,h); const NS="http://www.w3.org/2000/svg";
968
+ const max = Math.max(...data.map(d=>d.value));
969
+ const bw = (w - pad*2)/data.length - 12;
970
+ data.forEach((d,i)=>{
971
+ const x = pad + i*((w - pad*2)/data.length) + 6;
972
+ const barH = (d.value/max)*(h - pad*2);
973
+ const y = h - pad - barH;
974
+ const rect = document.createElementNS(NS,'rect');
975
+ rect.setAttribute('x',x); rect.setAttribute('y',y); rect.setAttribute('height',barH); rect.setAttribute('width',bw);
976
+ rect.setAttribute('rx',6); rect.setAttribute('fill',d.color);
977
+ rect.addEventListener('mouseenter', (ev)=>showTip(`${d.name}: ${d.value}% share`, ev.clientX, ev.clientY));
978
+ rect.addEventListener('mouseleave', hideTip);
979
+ rect.addEventListener('click', ()=>flash(`${d.name} pinned`));
980
+ svg.appendChild(rect);
981
+ const t = document.createElementNS(NS,'text'); t.setAttribute('x', x + bw/2); t.setAttribute('y', h - 6);
982
+ t.setAttribute('text-anchor','middle'); t.setAttribute('font-size','11'); t.setAttribute('fill','rgba(255,255,255,0.8)');
983
+ t.textContent = d.name;
984
+ svg.appendChild(t);
985
+ });
986
+ elm.appendChild(svg);
987
+ }
988
+
989
+ // Helpers
990
+ function pol(angleDeg, r, cx, cy){
991
+ const rad = angleDeg * Math.PI/180;
992
+ return [cx + r*Math.cos(rad), cy + r*Math.sin(rad)];
993
+ }
994
+
995
+ function showTip(text, x, y){
996
+ const t = el('#tooltip');
997
+ t.textContent = text;
998
+ t.style.opacity = 1;
999
+ t.style.left = (x)+'px';
1000
+ t.style.top = (y - 16)+'px';
1001
+ t.setAttribute('aria-hidden','false');
1002
+ }
1003
+ function hideTip(){ const t = el('#tooltip'); t.style.opacity = 0; t.setAttribute('aria-hidden','true'); }
1004
+
1005
+ // Catmull-Rom spline smoothing to bezier
1006
+ function catmullRom2bezier(pts){
1007
+ // pts: array of [x,y]
1008
+ let d = '';
1009
+ for(let i=0;i<pts.length-1;i++){
1010
+ const p0 = i==0 ? pts[i] : pts[i-1];
1011
+ const p1 = pts[i];
1012
+ const p2 = pts[i+1];
1013
+ const p3 = i+2<pts.length ? pts[i+2] : p2;
1014
+ const bp1x = p1[0] + (p2[0] - p0[0]) / 6;
1015
+ const bp1y = p1[1] + (p2[1] - p0[1]) / 6;
1016
+ const bp2x = p2[0] - (p3[0] - p1[0]) / 6;
1017
+ const bp2y = p2[1] - (p3[1] - p1[1]) / 6;
1018
+ if(i==0) d += 'M '+p1[0]+' '+p1[1];
1019
+ d += ' C '+bp1x+' '+bp1y+','+bp2x+' '+bp2y+','+p2[0]+' '+p2[1];
1020
+ }
1021
+ return d;
1022
+ }
1023
+
1024
+ /*******************
1025
+ * Initial draw
1026
+ *******************/
1027
+ function renderAll(){
1028
+ drawGDPLine(el('#gdpChart'), DATA.historyQ1.map(d=>({year:d.year, q1:d.q1})), {smooth:false});
1029
+ drawForecastBar(el('#forecastBar'), DATA.forecasts);
1030
+ drawSparkline(el('#inflationSpark'), DATA.inflation);
1031
+ drawDonut(el('#fdiDonut'), DATA.fdi.registered_5m, DATA.fdi.disbursed_5m);
1032
+ drawSectorBars(el('#sectorBars'), DATA.sectorShares);
1033
+
1034
+ // legend for GDP chart
1035
+ const lg = el('#gdpLegend'); lg.innerHTML='';
1036
+ DATA.forecasts.slice(0,2).forEach(f=>{
1037
+ const btn = document.createElement('button');
1038
+ btn.textContent = f.name; btn.style.borderLeft = '4px solid ' + f.color;
1039
+ btn.addEventListener('click', ()=>flash(f.name + ' selected'));
1040
+ lg.appendChild(btn);
1041
+ });
1042
+ }
1043
+ renderAll();
1044
+
1045
+ // Toggle smoothing
1046
+ let smooth = false;
1047
+ el('#toggleSmooth').addEventListener('click', ()=>{
1048
+ smooth = !smooth;
1049
+ drawGDPLine(el('#gdpChart'), DATA.historyQ1.map(d=>({year:d.year, q1:d.q1})), {smooth});
1050
+ flash(smooth ? 'Smoothing enabled' : 'Smoothing disabled');
1051
+ });
1052
+
1053
+ // Copy chart SVG (gdp chart)
1054
+ el('#copyChartSvg').addEventListener('click', async ()=>{
1055
+ const svgEl = el('#gdpChart svg');
1056
+ if(!svgEl){ flash('No chart'); return; }
1057
+ const s = new XMLSerializer().serializeToString(svgEl);
1058
+ try{
1059
+ await navigator.clipboard.writeText(s);
1060
+ flash('Chart SVG copied to clipboard');
1061
+ }catch(e){ flash('Copy failed') }
1062
+ });
1063
+
1064
+ /*******************
1065
+ * Interactive drill
1066
+ *******************/
1067
+ el('#drillSector').addEventListener('click', ()=>{
1068
+ // quick filter: highlight services card
1069
+ flash('Services prioritized: monitor urban services & tourism');
1070
+ });
1071
+
1072
+ // scenario button: quick scenario simulation using simple scaling
1073
+ el('#scenarioBtn').addEventListener('click', ()=>{
1074
+ // simulate a downside: global shock reduces exports by 3pp
1075
+ const sim = {...DATA.q2025};
1076
+ const drop = 0.9;
1077
+ const newH1 = +(sim.h1 * drop).toFixed(2);
1078
+ el('#gdpNow').textContent = `Simulated H1: ${newH1}% (stress)`;
1079
+ flash('Scenario applied: -10% to H1 growth (exports shock)');
1080
+ setTimeout(()=>{ el('#gdpNow').textContent = `GDP H1 2025: ${DATA.q2025.h1}%`; flash('Scenario cleared') }, 3500);
1081
+ });
1082
+
1083
+ // Mitigation strategies button
1084
+ el('#mitigateBtn').addEventListener('click', ()=>{
1085
+ alert('Recommended mitigation:\n- Diversify export markets\n- Strengthen domestic demand\n- Preserve macro stability\n- Use fiscal buffers to cushion shocks\n- Leverage FDI into high-value sectors');
1086
+ });
1087
+
1088
+ /*******************
1089
+ * Clipboard copy summary via header theme toggle as extra functionality
1090
+ *******************/
1091
+ el('#toggleTheme').addEventListener('click', async ()=>{
1092
+ // toggles accent color and also copies a mini summary to clipboard
1093
+ document.documentElement.style.setProperty('--accent', getRandomColor());
1094
+ try{ await navigator.clipboard.writeText('Vietnam — strong H1 2025: GDP H1 7.52%, Q2 7.96%'); flash('Mini-summary copied'); }catch(e){flash('Accent changed') }
1095
+ });
1096
+
1097
+ function getRandomColor(){
1098
+ const colors = ['#06b6d4','#f59e0b','#ef4444','#7c3aed','#0ea5a4'];
1099
+ return colors[Math.floor(Math.random()*colors.length)];
1100
+ }
1101
+
1102
+ /*******************
1103
+ * Accessibility & keyboard shortcuts
1104
+ *******************/
1105
+ window.addEventListener('keydown', (e)=>{
1106
+ if(e.ctrlKey && e.key === 'k'){
1107
+ e.preventDefault();
1108
+ el('#globalSearch').focus();
1109
+ }
1110
+ if(e.ctrlKey && e.key === 'b'){
1111
+ e.preventDefault(); el('#backTop').click();
1112
+ }
1113
+ });
1114
+
1115
+ /*******************
1116
+ * Final touches: adaptive text content from data
1117
+ *******************/
1118
+ // populate dynamic KPI text
1119
+ el('#gdpNow').textContent = `GDP H1 2025: ${DATA.q2025.h1}%`;
1120
+ el('#inflNow').textContent = `Inflation June 2025: ${DATA.inflation[1].value}%`;
1121
+ el('#unempNow').textContent = `Unemployment Q1 2025: ${DATA.unemployment_q1.toFixed(2)}%`;
1122
+ el('#fdiNow').textContent = `FDI H1 2025: US$${DATA.fdi.total_h1}B (+${DATA.fdi.yoy}% YoY)`;
1123
+
1124
+ // Simple cross-reference linking: clicking KPI scrolls to charts
1125
+ els('.kpi').forEach(k=>{
1126
+ k.style.cursor='pointer';
1127
+ k.addEventListener('click', ()=>{
1128
+ const which = k.getAttribute('data-kpi');
1129
+ if(which === 'gdp') el('#charts').scrollIntoView({behavior:'smooth'});
1130
+ if(which === 'inflation') el('#charts').scrollIntoView({behavior:'smooth'});
1131
+ if(which === 'fdi') el('#sector').scrollIntoView({behavior:'smooth'});
1132
+ });
1133
+ });
1134
+
1135
+ // On load, small animation
1136
+ setTimeout(()=>flash('Dashboard ready'), 600);
1137
+ </script>
1138
+ </body>
1139
+ </html>