thucdangvan020999 commited on
Commit
9328c4f
·
verified ·
1 Parent(s): 3faa47a

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1459 -18
index.html CHANGED
@@ -1,19 +1,1460 @@
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
+ <!-- Font Awesome for icons -->
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" integrity="sha512-yH6fK7Y4W5r8Xwq5mZ4mN6kJm3rQ0y7s8gqO2x1Q7s6Rz5yG3qk9qE5a7Y6vN2uB4t8vZ3F3p2j9h6w=="
10
+ crossorigin="anonymous" referrerpolicy="no-referrer" />
11
+
12
+ <style>
13
+ :root{
14
+ --bg: #f7f9fb;
15
+ --card: #ffffff;
16
+ --muted: #6b7280;
17
+ --accent: linear-gradient(135deg,#0ea5a2 0%,#06b6d4 100%);
18
+ --primary: #0b7285;
19
+ --success: #16a34a;
20
+ --danger: #ef4444;
21
+ --glass: rgba(255,255,255,0.6);
22
+ --radius: 14px;
23
+ --max-width: 1200px;
24
+ --shadow: 0 6px 24px rgba(12,18,31,0.08);
25
+ --kb: 14px;
26
+ --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
27
+ }
28
+
29
+ /* Theme toggling */
30
+ :root[data-theme='dark']{
31
+ --bg: #071022;
32
+ --card: #0b1623;
33
+ --muted: #9aa6b2;
34
+ --primary: #2ad4e0;
35
+ --glass: rgba(255,255,255,0.03);
36
+ color-scheme: dark;
37
+ }
38
+
39
+ /* Global styles */
40
+ *{box-sizing:border-box}
41
+ html,body{height:100%;}
42
+ body{
43
+ margin:0;
44
+ background: radial-gradient(1200px 400px at 10% 10%, rgba(6,182,212,0.06), transparent), var(--bg);
45
+ font-family:var(--font-sans);
46
+ color:var(--muted);
47
+ -webkit-font-smoothing:antialiased;
48
+ -moz-osx-font-smoothing:grayscale;
49
+ line-height:1.45;
50
+ font-size:clamp(14px, 1.4vw, 16px);
51
+ padding:24px;
52
+ }
53
+
54
+ .container{
55
+ max-width:var(--max-width);
56
+ margin:0 auto;
57
+ display:grid;
58
+ grid-template-columns: 1fr;
59
+ gap:20px;
60
+ }
61
+
62
+ header.site-header{
63
+ display:flex;
64
+ gap:12px;
65
+ align-items:center;
66
+ justify-content:space-between;
67
+ }
68
+ .brand{
69
+ display:flex;
70
+ gap:12px;
71
+ align-items:center;
72
+ }
73
+ .logo{
74
+ width:56px;
75
+ height:56px;
76
+ border-radius:12px;
77
+ background:var(--accent);
78
+ display:flex;
79
+ align-items:center;
80
+ justify-content:center;
81
+ color:white;
82
+ font-weight:700;
83
+ box-shadow:var(--shadow);
84
+ flex:0 0 56px;
85
+ }
86
+ h1{
87
+ margin:0;
88
+ color:var(--primary);
89
+ font-size:clamp(18px,2.6vw,22px);
90
+ }
91
+ p.lead{
92
+ margin:0;
93
+ color:var(--muted);
94
+ font-size:clamp(12px,1.4vw,14px);
95
+ }
96
+
97
+ .controls{
98
+ display:flex;
99
+ gap:10px;
100
+ align-items:center;
101
+ }
102
+ .btn{
103
+ background:var(--card);
104
+ border-radius:10px;
105
+ padding:10px 12px;
106
+ box-shadow:var(--shadow);
107
+ border:1px solid rgba(0,0,0,0.04);
108
+ color:var(--muted);
109
+ cursor:pointer;
110
+ display:inline-flex;
111
+ align-items:center;
112
+ gap:8px;
113
+ font-size:13px;
114
+ }
115
+ .btn.icon{padding:8px}
116
+ .btn.primary{
117
+ background:linear-gradient(180deg, rgba(6,182,212,0.14), rgba(6,182,212,0.04));
118
+ color:var(--primary);
119
+ border:1px solid rgba(6,182,212,0.18);
120
+ }
121
+
122
+ /* Layout for main content with TOC sidebar */
123
+ .layout{
124
+ display:grid;
125
+ gap:18px;
126
+ grid-template-columns: 1fr;
127
+ }
128
+
129
+ /* TOC */
130
+ nav.toc{
131
+ position:sticky;
132
+ top:24px;
133
+ align-self:start;
134
+ background:var(--card);
135
+ border-radius:12px;
136
+ padding:12px;
137
+ box-shadow:var(--shadow);
138
+ display:flex;
139
+ flex-direction:column;
140
+ gap:10px;
141
+ }
142
+ .toc .search{
143
+ display:flex;
144
+ gap:8px;
145
+ align-items:center;
146
+ }
147
+ .toc input[type="search"]{
148
+ border:0;
149
+ outline:0;
150
+ background:var(--glass);
151
+ padding:8px 10px;
152
+ border-radius:8px;
153
+ width:100%;
154
+ color:var(--muted);
155
+ font-size:13px;
156
+ }
157
+ .toc .links{
158
+ display:flex;
159
+ flex-direction:column;
160
+ gap:6px;
161
+ margin-top:6px;
162
+ }
163
+ .toc a{
164
+ text-decoration:none;
165
+ color:var(--muted);
166
+ padding:8px;
167
+ border-radius:8px;
168
+ font-size:13px;
169
+ display:flex;
170
+ gap:8px;
171
+ align-items:center;
172
+ }
173
+ .toc a.active{
174
+ background:linear-gradient(90deg, rgba(6,182,212,0.08), rgba(6,182,212,0.02));
175
+ color:var(--primary);
176
+ font-weight:600;
177
+ }
178
+ .toc .progress-vert{
179
+ height:6px;
180
+ background:linear-gradient(90deg,#e6fdfd,transparent);
181
+ border-radius:99px;
182
+ overflow:hidden;
183
+ margin-top:6px;
184
+ }
185
+ .progress-vert > i{
186
+ display:block;
187
+ height:100%;
188
+ width:0%;
189
+ background:linear-gradient(90deg,#06b6d4,#0ea5a2);
190
+ }
191
+
192
+ /* Main content area */
193
+ main.content{
194
+ display:grid;
195
+ gap:18px;
196
+ }
197
+
198
+ .card{
199
+ background:var(--card);
200
+ border-radius:var(--radius);
201
+ padding:16px;
202
+ box-shadow:var(--shadow);
203
+ border:1px solid rgba(0,0,0,0.04);
204
+ }
205
+
206
+ /* Executive summary */
207
+ .summary{
208
+ display:flex;
209
+ flex-direction:column;
210
+ gap:12px;
211
+ }
212
+ .summary .kpi-row{
213
+ display:flex;
214
+ gap:12px;
215
+ flex-wrap:wrap;
216
+ }
217
+ .kpi{
218
+ flex:1 1 160px;
219
+ background:linear-gradient(180deg, rgba(6,182,212,0.06), rgba(6,182,212,0.02));
220
+ border-radius:10px;
221
+ padding:12px;
222
+ min-width:140px;
223
+ }
224
+ .kpi h3{margin:0;color:var(--primary);font-size:clamp(16px,2.2vw,18px)}
225
+ .kpi p{margin:6px 0 0 0;color:var(--muted);font-weight:600}
226
+
227
+ /* Indicators grid */
228
+ .indicators-grid{
229
+ display:grid;
230
+ gap:12px;
231
+ grid-template-columns: repeat(auto-fit,minmax(220px,1fr));
232
+ }
233
+
234
+ /* Charts area */
235
+ .charts{
236
+ display:grid;
237
+ gap:12px;
238
+ grid-template-columns: 1fr;
239
+ }
240
+ .chart{
241
+ height:260px;
242
+ aspect-ratio: 16 / 9;
243
+ position:relative;
244
+ overflow:visible;
245
+ }
246
+ .chart svg{width:100%;height:100%}
247
+ .tooltip{
248
+ position:fixed;
249
+ pointer-events:none;
250
+ background:var(--card);
251
+ color:var(--muted);
252
+ padding:8px 10px;
253
+ border-radius:8px;
254
+ box-shadow:var(--shadow);
255
+ font-size:13px;
256
+ border:1px solid rgba(0,0,0,0.04);
257
+ transform:translate(-50%,-120%);
258
+ white-space:nowrap;
259
+ z-index:9999;
260
+ display:none;
261
+ }
262
+
263
+ /* Sortable table */
264
+ table{
265
+ width:100%;
266
+ border-collapse:collapse;
267
+ font-size:13px;
268
+ }
269
+ thead th{
270
+ text-align:left;
271
+ padding:10px;
272
+ background:transparent;
273
+ color:var(--muted);
274
+ font-weight:700;
275
+ cursor:pointer;
276
+ }
277
+ tbody td{
278
+ padding:10px;
279
+ border-top:1px solid rgba(0,0,0,0.04);
280
+ color:var(--muted);
281
+ }
282
+ tbody tr:hover td{background:rgba(6,182,212,0.03)}
283
+
284
+ /* Collapsible lists */
285
+ details summary{
286
+ list-style:none;
287
+ cursor:pointer;
288
+ display:flex;
289
+ gap:10px;
290
+ align-items:center;
291
+ font-weight:700;
292
+ color:var(--primary);
293
+ }
294
+ details[open] summary .chev{transform:rotate(180deg)}
295
+
296
+ /* References */
297
+ .references li{margin-bottom:8px}
298
+ .citation{
299
+ display:flex;
300
+ gap:8px;
301
+ align-items:center;
302
+ font-size:13px;
303
+ color:var(--muted);
304
+ }
305
+ .badge{
306
+ background:linear-gradient(90deg,#06b6d4,#0ea5a2);
307
+ color:white;
308
+ padding:6px 8px;
309
+ border-radius:8px;
310
+ font-weight:700;
311
+ font-size:12px;
312
+ }
313
+
314
+ /* Responsive layout adjustments */
315
+ @media (min-width:768px){
316
+ .layout{grid-template-columns: 260px 1fr; align-items:start}
317
+ .container{grid-template-columns: 1fr}
318
+ .charts{grid-template-columns: 1fr 1fr}
319
+ }
320
+ @media (min-width:1024px){
321
+ .container{padding:0}
322
+ .charts{grid-template-columns: 2fr 1fr}
323
+ .chart.large{aspect-ratio: 21/9; height:340px}
324
+ }
325
+
326
+ /* Print-friendly */
327
+ @media print{
328
+ .controls, nav.toc, .btn, .tooltip{display:none}
329
+ body{background:white;padding:0}
330
+ }
331
+
332
+ /* Container query example for a card's content */
333
+ .card:where(:not(:root)){ container-type: inline-size;}
334
+ @container (min-width:420px){
335
+ .card .meta-row{display:flex;justify-content:space-between;align-items:center}
336
+ }
337
+
338
+ /* small utilities */
339
+ .muted{color:var(--muted)}
340
+ .small{font-size:12px}
341
+ .right{text-align:right}
342
+ .center{text-align:center}
343
+ .flex{display:flex}
344
+ .gap-8{gap:8px}
345
+ .pill{padding:6px 10px;border-radius:999px;background:var(--glass);font-weight:700;color:var(--muted)}
346
+ .legend{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
347
+ .legend-item{display:flex;gap:8px;align-items:center;font-size:13px}
348
+ .swatch{width:14px;height:14px;border-radius:4px}
349
+ footer{opacity:0.9;color:var(--muted);font-size:13px;text-align:center;padding:12px 0}
350
+ </style>
351
+ </head>
352
+ <body>
353
+
354
+ <div class="container">
355
+ <header class="site-header">
356
+ <div class="brand">
357
+ <div class="logo">VN</div>
358
+ <div>
359
+ <h1>Vietnam Economic Growth Report — 2025</h1>
360
+ <p class="lead">Interactive analysis of recent performance, outlook and risks</p>
361
+ </div>
362
+ </div>
363
+
364
+ <div class="controls" role="toolbar" aria-label="Controls">
365
+ <button class="btn" id="copySummary" title="Copy Executive Summary"><i class="fa fa-copy"></i> Copy Summary</button>
366
+ <button class="btn" id="exportCSV" title="Export indicators as CSV"><i class="fa fa-file-csv"></i> Export CSV</button>
367
+ <button class="btn" id="exportJSON" title="Export report as JSON"><i class="fa fa-code"></i> Export JSON</button>
368
+ <button class="btn" id="printBtn" title="Print view"><i class="fa fa-print"></i></button>
369
+ <button class="btn icon" id="themeToggle" title="Toggle theme"><i class="fa fa-moon"></i></button>
370
+ </div>
371
+ </header>
372
+
373
+ <div class="layout">
374
+ <!-- TOC Sidebar -->
375
+ <nav class="toc card" aria-label="Table of contents">
376
+ <div class="search">
377
+ <i class="fa fa-search" style="color:var(--muted)"></i>
378
+ <input type="search" id="globalSearch" placeholder="Search report..." aria-label="Search report"/>
379
+ </div>
380
+
381
+ <div class="links" id="tocLinks">
382
+ <a href="#executive" data-id="executive"><i class="fa fa-star"></i> Executive Summary</a>
383
+ <a href="#indicators" data-id="indicators"><i class="fa fa-chart-line"></i> Key Indicators</a>
384
+ <a href="#sectors" data-id="sectors"><i class="fa fa-industry"></i> Sectoral Analysis</a>
385
+ <a href="#fdi" data-id="fdi"><i class="fa fa-hand-holding-dollar"></i> FDI & Capital</a>
386
+ <a href="#history" data-id="history"><i class="fa fa-history"></i> Historical Comparison</a>
387
+ <a href="#outlook" data-id="outlook"><i class="fa fa-compass"></i> Economic Outlook</a>
388
+ <a href="#risks" data-id="risks"><i class="fa fa-exclamation-triangle"></i> Challenges & Risks</a>
389
+ <a href="#references" data-id="references"><i class="fa fa-book"></i> References</a>
390
+ </div>
391
+
392
+ <div class="progress-vert" aria-hidden="true">
393
+ <i id="tocProgress"></i>
394
+ </div>
395
+
396
+ <div style="display:flex;gap:8px;margin-top:8px">
397
+ <button class="btn" id="saveBookmark" title="Save current section"><i class="fa fa-bookmark"></i> Save</button>
398
+ <button class="btn" id="gotoBookmark" title="Open saved section"><i class="fa fa-location-arrow"></i> Go</button>
399
+ </div>
400
+ </nav>
401
+
402
+ <!-- Content -->
403
+ <main class="content">
404
+ <!-- Executive Summary -->
405
+ <section id="executive" class="card" tabindex="0" aria-labelledby="executiveTitle">
406
+ <div class="meta-row">
407
+ <div>
408
+ <h2 id="executiveTitle" style="margin:0;color:var(--primary)">Executive Summary</h2>
409
+ <p class="small muted">Snapshot of growth, drivers and immediate outlook</p>
410
+ </div>
411
+ <div class="pill small">Last updated: 2025</div>
412
+ </div>
413
+
414
+ <div class="summary">
415
+ <p class="muted">Vietnam's economy sustained strong momentum in 2025, achieving 7.52% growth in H1 and 7.96% YoY in Q2. The expansion is led by services and manufacturing, supported by robust FDI inflows and resilient domestic demand despite elevated global uncertainty.</p>
416
+
417
+ <div class="kpi-row">
418
+ <div class="kpi">
419
+ <h3>7.52%</h3>
420
+ <p>GDP Growth H1 2025 (highest H1 since 2011)</p>
421
+ </div>
422
+ <div class="kpi">
423
+ <h3>7.96%</h3>
424
+ <p>GDP Growth Q2 2025 YoY</p>
425
+ </div>
426
+ <div class="kpi">
427
+ <h3>3.57%</h3>
428
+ <p>Inflation (June 2025)</p>
429
+ </div>
430
+ <div class="kpi">
431
+ <h3>US$21.51B</h3>
432
+ <p>FDI (First Half 2025)</p>
433
+ </div>
434
+ </div>
435
+
436
+ <div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:6px">
437
+ <button class="btn primary" id="readMoreExec"><i class="fa fa-eye"></i> Expand Summary</button>
438
+ <div class="muted small">Use the table of contents to navigate sections. Use search to find keywords.</div>
439
+ </div>
440
+ </div>
441
+ </section>
442
+
443
+ <!-- Key Indicators -->
444
+ <section id="indicators" class="card" tabindex="0" aria-labelledby="indicatorsTitle">
445
+ <h2 id="indicatorsTitle" style="margin:0;color:var(--primary)">Key Economic Indicators</h2>
446
+ <p class="small muted">Core datasets and comparisons for 2025</p>
447
+
448
+ <div class="indicators-grid" style="margin-top:12px">
449
+ <div class="card" style="padding:12px">
450
+ <h3 style="margin:0;color:var(--primary)">GDP Series</h3>
451
+ <p class="small muted">Quarterly / mid-year performance</p>
452
+ <div style="display:flex;gap:8px;align-items:center;margin-top:10px;flex-wrap:wrap">
453
+ <div><strong>Q1 2025</strong><div class="muted small">6.9% YoY</div></div>
454
+ <div><strong>Q2 2025</strong><div class="muted small">7.96% YoY</div></div>
455
+ <div><strong>H1 2025</strong><div class="muted small">7.52% YoY</div></div>
456
+ </div>
457
+ </div>
458
+
459
+ <div class="card" style="padding:12px">
460
+ <h3 style="margin:0;color:var(--primary)">Inflation & Labor</h3>
461
+ <p class="small muted">Key macro indicators</p>
462
+ <div style="display:flex;gap:12px;align-items:center;margin-top:10px">
463
+ <div><strong>Inflation</strong><div class="muted small">May: 3.24% • Jun: 3.57%</div></div>
464
+ <div><strong>Unemployment</strong><div class="muted small">Q1 2025: 2.20%</div></div>
465
+ </div>
466
+ </div>
467
+
468
+ <div class="card" style="padding:12px">
469
+ <h3 style="margin:0;color:var(--primary)">FDI</h3>
470
+ <p class="small muted">Capital flows and confidence</p>
471
+ <div style="margin-top:10px">
472
+ <div><strong>Registered (Jan–May)</strong><div class="muted small">US$18.4B (+51% YoY)</div></div>
473
+ <div style="margin-top:6px"><strong>Disbursed (Jan–May)</strong><div class="muted small">US$8.9B</div></div>
474
+ </div>
475
+ </div>
476
+
477
+ <div class="card" style="padding:12px">
478
+ <h3 style="margin:0;color:var(--primary)">Forecasts</h3>
479
+ <p class="small muted">Cross-institution projections (2025)</p>
480
+ <ul style="margin:8px 0 0 16px" class="muted small">
481
+ <li>World Bank: 5.8%</li>
482
+ <li>ADB: 6.6%</li>
483
+ <li>IMF: 5.2%</li>
484
+ <li>Government Target: 8.3–8.5%</li>
485
+ </ul>
486
+ </div>
487
+ </div>
488
+
489
+ <!-- Charts -->
490
+ <div class="charts" style="margin-top:14px">
491
+ <div class="card chart large" id="chartHistoryHolder">
492
+ <h4 style="margin:0;color:var(--primary)">Historical GDP Growth (2020–2025 Q1 series)</h4>
493
+ <p class="small muted">Year-on-year growth in first quarter (2020–2025)</p>
494
+ <div id="chartHistory" class="chart" aria-hidden="false"></div>
495
+ </div>
496
+
497
+ <div class="card chart" id="chartForecastHolder">
498
+ <h4 style="margin:0;color:var(--primary)">2025 Growth Forecasts</h4>
499
+ <p class="small muted">Institutional consensus vs Government target</p>
500
+ <div id="chartForecast" class="chart"></div>
501
+ <div style="margin-top:8px" class="legend" id="forecastLegend"></div>
502
+ </div>
503
+ </div>
504
+
505
+ <!-- Sortable table -->
506
+ <div class="card" style="margin-top:12px">
507
+ <div style="display:flex;justify-content:space-between;align-items:center">
508
+ <h4 style="margin:0;color:var(--primary)">Indicator Table (sortable)</h4>
509
+ <div class="small muted">Click headers to sort • Use export buttons above</div>
510
+ </div>
511
+
512
+ <div style="margin-top:10px;overflow:auto">
513
+ <table id="indicatorTable" aria-label="Key indicators table">
514
+ <thead>
515
+ <tr>
516
+ <th data-key="indicator">Indicator <i class="fa fa-sort"></i></th>
517
+ <th data-key="value">Value</th>
518
+ <th data-key="period">Period</th>
519
+ <th data-key="change">Change</th>
520
+ </tr>
521
+ </thead>
522
+ <tbody></tbody>
523
+ </table>
524
+ </div>
525
+ </div>
526
+ </section>
527
+
528
+ <!-- Sectoral Analysis -->
529
+ <section id="sectors" class="card" tabindex="0" aria-labelledby="sectorsTitle">
530
+ <h2 id="sectorsTitle" style="margin:0;color:var(--primary)">Sectoral Analysis</h2>
531
+ <p class="small muted">Decomposition and performance drivers</p>
532
+
533
+ <div style="display:grid;grid-template-columns:1fr;gap:12px;margin-top:12px">
534
+ <div class="card" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
535
+ <div style="flex:1;min-width:220px">
536
+ <h4 style="margin:0;color:var(--primary)">Primary Growth Drivers</h4>
537
+ <ul class="muted small" style="margin-top:8px">
538
+ <li>Services: major contributor</li>
539
+ <li>Manufacturing: recovery & development</li>
540
+ <li>Export industries: export backbone</li>
541
+ <li>Banking: earnings projected +17% in 2025</li>
542
+ </ul>
543
+ </div>
544
+
545
+ <div style="width:320px">
546
+ <div id="donutSectors" class="chart" style="height:220px"></div>
547
+ <div class="legend" id="donutLegend" style="margin-top:10px"></div>
548
+ </div>
549
+ </div>
550
+
551
+ <details class="card" open>
552
+ <summary><i class="chev fa fa-chevron-down"></i> Retail & Consumption</summary>
553
+ <div style="margin-top:8px" class="muted small">
554
+ Retail sales Q1 2025: 1.708 quadrillion VND (~US$66.83B), +9.9% YoY — strong domestic consumption supports services and retail sectors.
555
+ </div>
556
+ </details>
557
+ </div>
558
+
559
+ </section>
560
+
561
+ <!-- FDI & dataset -->
562
+ <section id="fdi" class="card" tabindex="0" aria-labelledby="fdiTitle">
563
+ <h2 id="fdiTitle" style="margin:0;color:var(--primary)">FDI & Capital Flows</h2>
564
+ <p class="small muted">Monthly breakdown and filters</p>
565
+
566
+ <div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:12px">
567
+ <label class="muted small">Filter:</label>
568
+ <select id="fdifeature" class="btn" style="padding:8px;border-radius:10px">
569
+ <option value="all">Show all</option>
570
+ <option value="registered">Registered capital</option>
571
+ <option value="disbursed">Disbursed capital</option>
572
+ </select>
573
+
574
+ <div style="margin-left:auto;display:flex;gap:8px">
575
+ <button class="btn" id="copyFDI"><i class="fa fa-clipboard"></i> Copy FDI Table</button>
576
+ <button class="btn" id="downloadFDI"><i class="fa fa-download"></i> Download CSV</button>
577
+ </div>
578
+ </div>
579
+
580
+ <div style="margin-top:12px;overflow:auto" class="card">
581
+ <table id="fdiTable">
582
+ <thead>
583
+ <tr>
584
+ <th>Month</th>
585
+ <th>Registered (US$B)</th>
586
+ <th>Disbursed (US$B)</th>
587
+ </tr>
588
+ </thead>
589
+ <tbody></tbody>
590
+ </table>
591
+ </div>
592
+
593
+ <div class="card" style="margin-top:12px">
594
+ <h4 style="margin:0;color:var(--primary)">FDI Trend</h4>
595
+ <p class="small muted">Registered vs Disbursed (Jan–May 2025)</p>
596
+ <div id="fdiChart" class="chart"></div>
597
+ </div>
598
+ </section>
599
+
600
+ <!-- Historical Comparison -->
601
+ <section id="history" class="card" tabindex="0" aria-labelledby="historyTitle">
602
+ <h2 id="historyTitle" style="margin:0;color:var(--primary)">Historical Comparison</h2>
603
+ <p class="small muted">Contextualizing 2025 against recent years</p>
604
+
605
+ <div style="display:grid;grid-template-columns:1fr;gap:12px;margin-top:12px">
606
+ <div class="card">
607
+ <h4 style="margin:0;color:var(--primary)">Trendline</h4>
608
+ <p class="small muted">First-quarter YoY growth 2020–2025</p>
609
+ <div id="miniHistory" class="chart" style="height:180px"></div>
610
+ </div>
611
+
612
+ <details class="card">
613
+ <summary><i class="chev fa fa-chevron-down"></i> Year-by-year notes</summary>
614
+ <div class="muted small" style="margin-top:8px">
615
+ 2024: 7.1% growth overall. 2025 shows a strong start but may moderate with global headwinds. Long-term fundamentals remain resilient.
616
+ </div>
617
+ </details>
618
+ </div>
619
+ </section>
620
+
621
+ <!-- Outlook & projections -->
622
+ <section id="outlook" class="card" tabindex="0" aria-labelledby="outlookTitle">
623
+ <h2 id="outlookTitle" style="margin:0;color:var(--primary)">Economic Outlook & Projections</h2>
624
+ <p class="small muted">Scenarios, risks and recommended mitigations</p>
625
+
626
+ <div style="display:grid;grid-template-columns:1fr;gap:12px;margin-top:12px">
627
+ <div class="card">
628
+ <h4 style="margin:0;color:var(--primary)">Near-term Prospects</h4>
629
+ <ul class="muted small" style="margin-top:8px">
630
+ <li>Strong domestic fundamentals support continued growth.</li>
631
+ <li>Government target (8.3–8.5%) ambitious vs international forecasts.</li>
632
+ <li>Risks centered on trade tensions and geopolitical uncertainty.</li>
633
+ </ul>
634
+ </div>
635
+
636
+ <div class="card">
637
+ <h4 style="margin:0;color:var(--primary)">Policy Responses</h4>
638
+ <p class="muted small" style="margin-top:8px">Government measures: diversify export markets, strengthen domestic demand, preserve macro stability and use fiscal space for targeted support.</p>
639
+ </div>
640
+ </div>
641
+ </section>
642
+
643
+ <!-- Risks -->
644
+ <section id="risks" class="card" tabindex="0" aria-labelledby="risksTitle">
645
+ <h2 id="risksTitle" style="margin:0;color:var(--primary)">Challenges & Risk Factors</h2>
646
+ <p class="small muted">Key risks and suggested mitigations</p>
647
+
648
+ <div style="display:grid;gap:12px;margin-top:12px">
649
+ <div class="card">
650
+ <h4 style="margin:0;color:var(--primary)">Top Risks</h4>
651
+ <ol class="muted small" style="margin-top:8px">
652
+ <li>Global trade tensions affecting exports</li>
653
+ <li>US tariff policies pressuring export-oriented firms</li>
654
+ <li>Geopolitical instability raising uncertainty</li>
655
+ <li>Overreliance on FDI and inflationary pressures</li>
656
+ <li>Need to protect macroeconomic stability while pursuing growth</li>
657
+ </ol>
658
+ </div>
659
+
660
+ <details class="card">
661
+ <summary><i class="chev fa fa-chevron-down"></i> Mitigation Strategies</summary>
662
+ <div class="muted small" style="margin-top:8px">
663
+ Diversify export partners, boost domestic value chains, prudent fiscal policy, and strengthen financial oversight to reduce vulnerabilities.
664
+ </div>
665
+ </details>
666
+ </div>
667
+ </section>
668
+
669
+ <!-- References -->
670
+ <section id="references" class="card" tabindex="0" aria-labelledby="referencesTitle">
671
+ <h2 id="referencesTitle" style="margin:0;color:var(--primary)">Sources & References</h2>
672
+ <p class="small muted">Cited reports, datasets and links</p>
673
+
674
+ <ul class="references" style="margin-top:12px">
675
+ <li>
676
+ <div class="citation">
677
+ <span class="badge">1</span>
678
+ <div style="flex:1">
679
+ Trading Economics - Vietnam GDP Annual Growth Rate — <a href="https://tradingeconomics.com/vietnam/gdp-growth-annual" target="_blank">tradingeconomics.com</a>
680
+ </div>
681
+ <button class="btn" data-copy="Trading Economics - Vietnam GDP Annual Growth Rate — https://tradingeconomics.com/vietnam/gdp-growth-annual"><i class="fa fa-copy"></i></button>
682
+ </div>
683
+ </li>
684
+
685
+ <li>
686
+ <div class="citation">
687
+ <span class="badge">2</span>
688
+ <div style="flex:1">
689
+ IMF - Vietnam Country Profile — <a href="https://www.imf.org/en/Countries/VNM" target="_blank">imf.org</a>
690
+ </div>
691
+ <button class="btn" data-copy="IMF - Vietnam Country Profile — https://www.imf.org/en/Countries/VNM"><i class="fa fa-copy"></i></button>
692
+ </div>
693
+ </li>
694
+
695
+ <li>
696
+ <div class="citation">
697
+ <span class="badge">3</span>
698
+ <div style="flex:1">
699
+ World Economics - Vietnam GDP Estimates — <a href="https://www.worldeconomics.com/GDP/Vietnam.gdp" target="_blank">worldeconomics.com</a>
700
+ </div>
701
+ <button class="btn" data-copy="World Economics - Vietnam GDP Estimates — https://www.worldeconomics.com/GDP/Vietnam.gdp"><i class="fa fa-copy"></i></button>
702
+ </div>
703
+ </li>
704
+
705
+ <li style="margin-top:8px">
706
+ <div class="muted small">Full list includes government statistics (GSO), ADB, FocusEconomics, Vietnam Briefing and local news sources. Use citation buttons to copy links quickly.</div>
707
+ </li>
708
+ </ul>
709
+
710
+ <div style="display:flex;gap:8px;margin-top:12px">
711
+ <button class="btn" id="copyRefs"><i class="fa fa-clipboard"></i> Copy All References</button>
712
+ <button class="btn" id="exportRefsJSON"><i class="fa fa-file-export"></i> Export References (JSON)</button>
713
+ </div>
714
+ </section>
715
+
716
+ <footer>
717
+ <div>Prepared from public data sources • Interactive report © 2025</div>
718
+ </footer>
719
+ </main>
720
+ </div>
721
+ </div>
722
+
723
+ <div class="tooltip" id="tooltip"></div>
724
+
725
+ <script>
726
+ /* ===========================
727
+ Data
728
+ ===========================*/
729
+ const data = {
730
+ historyQ1: [
731
+ {year:2020, value:3.21},
732
+ {year:2021, value:4.85},
733
+ {year:2022, value:5.42},
734
+ {year:2023, value:3.46},
735
+ {year:2024, value:5.98},
736
+ {year:2025, value:6.93}
737
+ ],
738
+ forecasts: [
739
+ {name:"World Bank", value:5.8, color:"#06b6d4"},
740
+ {name:"ADB", value:6.6, color:"#0ea5a2"},
741
+ {name:"IMF", value:5.2, color:"#06a5d4"},
742
+ {name:"Government target", value:8.4, color:"#0b7285"}
743
+ ],
744
+ sectors: [
745
+ {name:"Services", value:45, color:"#06b6d4"},
746
+ {name:"Manufacturing", value:30, color:"#0ea5a2"},
747
+ {name:"Agriculture", value:8, color:"#7dd3fc"},
748
+ {name:"Export-related", value:12, color:"#38bdf8"},
749
+ {name:"Other", value:5, color:"#a7f3d0"}
750
+ ],
751
+ indicatorsTable:[
752
+ {indicator:"GDP Q1 2025", value:"6.9%", period:"Q1 2025", change:"–"},
753
+ {indicator:"GDP Q2 2025", value:"7.96%", period:"Q2 2025", change:"–"},
754
+ {indicator:"GDP H1 2025", value:"7.52%", period:"H1 2025", change:"Highest H1 since 2011"},
755
+ {indicator:"Inflation May 2025", value:"3.24%", period:"May 2025", change:"–"},
756
+ {indicator:"Inflation Jun 2025", value:"3.57%", period:"Jun 2025", change:"Highest YTD"},
757
+ {indicator:"Unemployment Q1 2025", value:"2.20%", period:"Q1 2025", change:"Down from 2.22%"},
758
+ {indicator:"FDI Registered (Jan–May)", value:"$18.4B", period:"Jan–May 2025", change:"+51% YoY"},
759
+ {indicator:"FDI Disbursed (Jan–May)", value:"$8.9B", period:"Jan–May 2025", change:"–"},
760
+ ],
761
+ fdiMonthly:[
762
+ {month:"Jan 2025", registered:5.2, disbursed:2.3},
763
+ {month:"Feb 2025", registered:3.8, disbursed:1.5},
764
+ {month:"Mar 2025", registered:4.1, disbursed:1.8},
765
+ {month:"Apr 2025", registered:2.9, disbursed:1.7},
766
+ {month:"May 2025", registered:2.4, disbursed:1.6} // sums approx to totals
767
+ ],
768
+ references:[
769
+ {id:1, text:"Trading Economics - Vietnam GDP Annual Growth Rate — https://tradingeconomics.com/vietnam/gdp-growth-annual"},
770
+ {id:2, text:"IMF - Vietnam Country Profile — https://www.imf.org/en/Countries/VNM"},
771
+ {id:3, text:"World Economics - Vietnam GDP Estimates — https://www.worldeconomics.com/GDP/Vietnam.gdp"},
772
+ {id:4, text:"Government of Vietnam - General Statistics Office — https://www.gso.gov.vn/en/"},
773
+ {id:5, text:"Wikipedia - Economy of Vietnam — https://en.wikipedia.org/wiki/Economy_of_Vietnam"},
774
+ {id:6, text:"ADB - Vietnam Country Partnership — https://www.adb.org/countries/viet-nam/main"}
775
+ ]
776
+ };
777
+
778
+ /* ===========================
779
+ Utilities
780
+ ===========================*/
781
+ const $ = sel => document.querySelector(sel);
782
+ const $$ = sel => Array.from(document.querySelectorAll(sel));
783
+ const fmt = n => (typeof n === "number" ? n.toFixed(2) : n);
784
+
785
+ /* Smooth scrolling for TOC links */
786
+ $$('.toc a').forEach(a => {
787
+ a.addEventListener('click', e => {
788
+ e.preventDefault();
789
+ const id = a.getAttribute('href').slice(1);
790
+ const target = document.getElementById(id);
791
+ if(target){
792
+ target.scrollIntoView({behavior:'smooth', block:'start'});
793
+ history.replaceState(null, '', '#'+id);
794
+ }
795
+ });
796
+ });
797
+
798
+ /* ===========================
799
+ Theme & LocalStorage
800
+ ===========================*/
801
+ const themeToggle = $('#themeToggle');
802
+ const root = document.documentElement;
803
+ const savedTheme = localStorage.getItem('vne_theme') || (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
804
+ if(savedTheme === 'dark') root.setAttribute('data-theme','dark');
805
+ else root.removeAttribute('data-theme');
806
+
807
+ themeToggle.addEventListener('click', () => {
808
+ if(root.getAttribute('data-theme') === 'dark'){
809
+ root.removeAttribute('data-theme');
810
+ localStorage.setItem('vne_theme','light');
811
+ themeToggle.innerHTML = '<i class="fa fa-moon"></i>';
812
+ } else {
813
+ root.setAttribute('data-theme','dark');
814
+ localStorage.setItem('vne_theme','dark');
815
+ themeToggle.innerHTML = '<i class="fa fa-sun"></i>';
816
+ }
817
+ });
818
+
819
+ /* ===========================
820
+ Populate Indicator Table (sortable)
821
+ ===========================*/
822
+ const indicatorTableBody = $('#indicatorTable tbody');
823
+ function renderIndicatorTable(rows){
824
+ indicatorTableBody.innerHTML = rows.map(r => `
825
+ <tr>
826
+ <td>${r.indicator}</td>
827
+ <td>${r.value}</td>
828
+ <td>${r.period}</td>
829
+ <td>${r.change}</td>
830
+ </tr>
831
+ `).join('');
832
+ }
833
+ renderIndicatorTable(data.indicatorsTable);
834
+
835
+ let sortState = {key:null,dir:1};
836
+ $$('#indicatorTable thead th').forEach(th => {
837
+ th.addEventListener('click', () => {
838
+ const key = th.dataset.key;
839
+ if(sortState.key === key) sortState.dir *= -1;
840
+ else { sortState.key = key; sortState.dir = 1; }
841
+ const sorted = [...data.indicatorsTable].sort((a,b) => {
842
+ let va = a[key] || '', vb = b[key] || '';
843
+ // numeric if possible
844
+ const na = parseFloat(String(va).replace(/[^0-9.-]+/g,'')), nb = parseFloat(String(vb).replace(/[^0-9.-]+/g,''));
845
+ if(!isNaN(na) && !isNaN(nb)) return (na-nb)*sortState.dir;
846
+ return String(va).localeCompare(String(vb)) * sortState.dir;
847
+ });
848
+ renderIndicatorTable(sorted);
849
+ });
850
+ });
851
+
852
+ /* ===========================
853
+ Chart Utilities (SVG)
854
+ ===========================*/
855
+ function createSVG(w=800,h=300){
856
+ const ns = "http://www.w3.org/2000/svg";
857
+ const svg = document.createElementNS(ns,'svg');
858
+ svg.setAttribute('viewBox',`0 0 ${w} ${h}`);
859
+ svg.setAttribute('width','100%');
860
+ svg.setAttribute('height','100%');
861
+ return svg;
862
+ }
863
+ function clearEl(el){ while(el.firstChild) el.removeChild(el.firstChild); }
864
+
865
+ /* Tooltip */
866
+ const tooltip = $('#tooltip');
867
+ function showTooltip(html,x,y){
868
+ tooltip.style.display = 'block';
869
+ tooltip.innerHTML = html;
870
+ const rect = tooltip.getBoundingClientRect();
871
+ tooltip.style.left = (x)+'px';
872
+ tooltip.style.top = (y)+'px';
873
+ }
874
+ function hideTooltip(){ tooltip.style.display = 'none'; }
875
+
876
+ /* ===========================
877
+ Historical Line Chart (2020–2025)
878
+ ===========================*/
879
+ function drawHistoryChart(){
880
+ const holder = $('#chartHistory');
881
+ clearEl(holder);
882
+ const w = 720, h = 320;
883
+ const svg = createSVG(w,h);
884
+ holder.appendChild(svg);
885
+
886
+ const padding = {t:24,r:24,b:36,l:56};
887
+ const innerW = w - padding.l - padding.r;
888
+ const innerH = h - padding.t - padding.b;
889
+ const values = data.historyQ1.map(d => d.value);
890
+ const minV = Math.min(...values) - 1;
891
+ const maxV = Math.max(...values) + 1;
892
+
893
+ // scales
894
+ const xs = idx => padding.l + (idx/(values.length-1))*innerW;
895
+ const ys = v => padding.t + innerH - ((v - minV)/(maxV - minV))*innerH;
896
+
897
+ // grid lines and y-axis ticks
898
+ const ns = "http://www.w3.org/2000/svg";
899
+ for(let t = Math.ceil(minV); t <= Math.floor(maxV); t+=1){
900
+ const y = ys(t);
901
+ const line = document.createElementNS(ns,'line');
902
+ line.setAttribute('x1',padding.l);
903
+ line.setAttribute('x2',w-padding.r);
904
+ line.setAttribute('y1',y);
905
+ line.setAttribute('y2',y);
906
+ line.setAttribute('stroke','rgba(0,0,0,0.04)');
907
+ line.setAttribute('stroke-width','1');
908
+ svg.appendChild(line);
909
+
910
+ const text = document.createElementNS(ns,'text');
911
+ text.setAttribute('x',padding.l-10);
912
+ text.setAttribute('y',y+4);
913
+ text.setAttribute('text-anchor','end');
914
+ text.setAttribute('fill','var(--muted)');
915
+ text.setAttribute('style','font-size:12px');
916
+ text.textContent = t + '%';
917
+ svg.appendChild(text);
918
+ }
919
+
920
+ // path
921
+ let d = '';
922
+ data.historyQ1.forEach((pt,i)=>{
923
+ const x = xs(i), y = ys(pt.value);
924
+ d += (i===0?'M':'L') + x + ' ' + y + ' ';
925
+ });
926
+ const path = document.createElementNS(ns,'path');
927
+ path.setAttribute('d', d);
928
+ path.setAttribute('fill','none');
929
+ path.setAttribute('stroke','#06b6d4');
930
+ path.setAttribute('stroke-width','3');
931
+ path.setAttribute('stroke-linecap','round');
932
+ svg.appendChild(path);
933
+
934
+ // area fill
935
+ const areaD = d + ` L ${padding.l+innerW} ${padding.t+innerH} L ${padding.l} ${padding.t+innerH} Z`;
936
+ const area = document.createElementNS(ns,'path');
937
+ area.setAttribute('d', areaD);
938
+ area.setAttribute('fill','rgba(6,182,212,0.08)');
939
+ svg.insertBefore(area, path);
940
+
941
+ // points and hover
942
+ data.historyQ1.forEach((pt,i)=>{
943
+ const x = xs(i), y = ys(pt.value);
944
+ const circle = document.createElementNS(ns,'circle');
945
+ circle.setAttribute('cx',x);
946
+ circle.setAttribute('cy',y);
947
+ circle.setAttribute('r',5);
948
+ circle.setAttribute('fill','#fff');
949
+ circle.setAttribute('stroke','#06b6d4');
950
+ circle.setAttribute('stroke-width',2);
951
+ circle.style.cursor='pointer';
952
+ svg.appendChild(circle);
953
+
954
+ circle.addEventListener('mouseenter', (e) => {
955
+ const html = `<strong>${pt.year}</strong><div class="muted small">${pt.value}% YoY</div>`;
956
+ const rect = holder.getBoundingClientRect();
957
+ showTooltip(html, rect.left + x, rect.top + y - 8);
958
+ });
959
+ circle.addEventListener('mouseleave', hideTooltip);
960
+ });
961
+
962
+ // labels (years)
963
+ data.historyQ1.forEach((pt,i)=>{
964
+ const x = xs(i), y = padding.t + innerH + 18;
965
+ const nsTxt = document.createElementNS("http://www.w3.org/2000/svg",'text');
966
+ nsTxt.setAttribute('x',x);
967
+ nsTxt.setAttribute('y',y);
968
+ nsTxt.setAttribute('text-anchor','middle');
969
+ nsTxt.setAttribute('fill','var(--muted)');
970
+ nsTxt.setAttribute('style','font-size:12px');
971
+ nsTxt.textContent = pt.year;
972
+ svg.appendChild(nsTxt);
973
+ });
974
+ }
975
+ drawHistoryChart();
976
+
977
+ /* ===========================
978
+ Forecast Bar Chart
979
+ ===========================*/
980
+ function drawForecastChart(){
981
+ const holder = $('#chartForecast');
982
+ clearEl(holder);
983
+ const w=420,h=260;
984
+ const svg = createSVG(w,h);
985
+ holder.appendChild(svg);
986
+ const ns = "http://www.w3.org/2000/svg";
987
+ const padding = {t:20,r:20,b:40,l:50};
988
+ const innerW = w - padding.l - padding.r;
989
+ const innerH = h - padding.t - padding.b;
990
+ const maxVal = Math.max(...data.forecasts.map(f=>f.value))+1;
991
+
992
+ data.forecasts.forEach((f,i)=>{
993
+ const barW = innerW / data.forecasts.length * 0.7;
994
+ const gap = innerW / data.forecasts.length;
995
+ const x = padding.l + i*gap + (gap - barW)/2;
996
+ const y = padding.t + innerH - (f.value/maxVal)*innerH;
997
+ const rect = document.createElementNS(ns,'rect');
998
+ rect.setAttribute('x', x);
999
+ rect.setAttribute('y', y);
1000
+ rect.setAttribute('width', barW);
1001
+ rect.setAttribute('height', padding.t + innerH - y);
1002
+ rect.setAttribute('rx',8);
1003
+ rect.setAttribute('fill', f.color);
1004
+ svg.appendChild(rect);
1005
+
1006
+ rect.addEventListener('mouseenter', (ev)=>{
1007
+ const rectEl = holder.getBoundingClientRect();
1008
+ showTooltip(`<strong>${f.name}</strong><div class="muted small">${f.value}%</div>`, rectEl.left + x + barW/2, rectEl.top + y);
1009
+ });
1010
+ rect.addEventListener('mouseleave', hideTooltip);
1011
+
1012
+ // labels
1013
+ const text = document.createElementNS(ns,'text');
1014
+ text.setAttribute('x', x + barW/2);
1015
+ text.setAttribute('y', padding.t + innerH + 18);
1016
+ text.setAttribute('text-anchor','middle');
1017
+ text.setAttribute('fill','var(--muted)');
1018
+ text.setAttribute('style','font-size:12px');
1019
+ text.textContent = f.name.replace('Government target','Gov target');
1020
+ svg.appendChild(text);
1021
+ });
1022
+
1023
+ // y-axis ticks
1024
+ for(let t=0;t<=Math.ceil(maxVal);t+=1){
1025
+ const y = padding.t + innerH - (t/maxVal)*innerH;
1026
+ const tx = document.createElementNS(ns,'text');
1027
+ tx.setAttribute('x',padding.l-8);
1028
+ tx.setAttribute('y',y+4);
1029
+ tx.setAttribute('text-anchor','end');
1030
+ tx.setAttribute('fill','var(--muted)');
1031
+ tx.setAttribute('style','font-size:11px');
1032
+ tx.textContent = t + '%';
1033
+ svg.appendChild(tx);
1034
+ }
1035
+
1036
+ // legend
1037
+ const legend = $('#forecastLegend');
1038
+ legend.innerHTML = '';
1039
+ data.forecasts.forEach(f => {
1040
+ const item = document.createElement('div');
1041
+ item.className='legend-item';
1042
+ item.innerHTML = `<span class="swatch" style="background:${f.color}"></span><div class="small muted">${f.name} — ${f.value}%</div>`;
1043
+ legend.appendChild(item);
1044
+ });
1045
+ }
1046
+ drawForecastChart();
1047
+
1048
+ /* ===========================
1049
+ Donut Chart for Sectors
1050
+ ===========================*/
1051
+ function drawDonut(){
1052
+ const holder = $('#donutSectors');
1053
+ clearEl(holder);
1054
+ const w=320,h=220;
1055
+ const svg = createSVG(w,h);
1056
+ holder.appendChild(svg);
1057
+ const ns = "http://www.w3.org/2000/svg";
1058
+ const cx = w/2, cy = h/2 - 10, r = Math.min(w,h)/3;
1059
+ const total = data.sectors.reduce((s,d)=>s+d.value,0);
1060
+ let angle = -Math.PI/2;
1061
+ const donutGroup = document.createElementNS(ns,'g');
1062
+ svg.appendChild(donutGroup);
1063
+
1064
+ data.sectors.forEach((s,idx)=>{
1065
+ const sliceAngle = (s.value/total)*(Math.PI*2);
1066
+ const end = angle + sliceAngle;
1067
+ const x1 = cx + r*Math.cos(angle);
1068
+ const y1 = cy + r*Math.sin(angle);
1069
+ const x2 = cx + r*Math.cos(end);
1070
+ const y2 = cy + r*Math.sin(end);
1071
+ const large = sliceAngle > Math.PI ? 1 : 0;
1072
+ const pathD = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
1073
+ const path = document.createElementNS(ns,'path');
1074
+ path.setAttribute('d', pathD);
1075
+ path.setAttribute('fill', s.color);
1076
+ path.setAttribute('opacity',0.95);
1077
+ donutGroup.appendChild(path);
1078
+
1079
+ path.addEventListener('mouseenter', () => {
1080
+ const rect = holder.getBoundingClientRect();
1081
+ showTooltip(`<strong>${s.name}</strong><div class="muted small">${s.value}%</div>`, rect.left + cx + (r*Math.cos(angle+sliceAngle/2)), rect.top + cy + (r*Math.sin(angle+sliceAngle/2)));
1082
+ });
1083
+ path.addEventListener('mouseleave', hideTooltip);
1084
+
1085
+ angle = end;
1086
+ });
1087
+
1088
+ // inner circle to make donut
1089
+ const inner = document.createElementNS(ns,'circle');
1090
+ inner.setAttribute('cx',cx);
1091
+ inner.setAttribute('cy',cy);
1092
+ inner.setAttribute('r',r*0.6);
1093
+ inner.setAttribute('fill','var(--card)');
1094
+ inner.setAttribute('stroke','rgba(0,0,0,0.02)');
1095
+ svg.appendChild(inner);
1096
+
1097
+ // legend mapping with toggles
1098
+ const legend = $('#donutLegend');
1099
+ legend.innerHTML = '';
1100
+ data.sectors.forEach(s=>{
1101
+ const item = document.createElement('div');
1102
+ item.className = 'legend-item';
1103
+ item.innerHTML = `<span class="swatch" style="background:${s.color}"></span><div class="small muted">${s.name} — ${s.value}%</div>`;
1104
+ legend.appendChild(item);
1105
+ });
1106
+ }
1107
+ drawDonut();
1108
+
1109
+ /* ===========================
1110
+ FDI Table and Chart
1111
+ ===========================*/
1112
+ function renderFDITable(filter='all'){
1113
+ const tbody = $('#fdiTable tbody');
1114
+ const rows = data.fdiMonthly.map(r => {
1115
+ return `<tr>
1116
+ <td>${r.month}</td>
1117
+ <td>${r.registered.toFixed(2)}</td>
1118
+ <td>${r.disbursed.toFixed(2)}</td>
1119
+ </tr>`;
1120
+ }).join('');
1121
+ tbody.innerHTML = rows;
1122
+ }
1123
+ renderFDITable();
1124
+
1125
+ function drawFDIChart(){
1126
+ const holder = $('#fdiChart');
1127
+ clearEl(holder);
1128
+ const w=640,h=260;
1129
+ const svg = createSVG(w,h);
1130
+ holder.appendChild(svg);
1131
+ const ns = "http://www.w3.org/2000/svg";
1132
+ const padding = {t:20,r:20,b:50,l:50};
1133
+ const innerW = w - padding.l - padding.r;
1134
+ const innerH = h - padding.t - padding.b;
1135
+ const maxV = Math.max(...data.fdiMonthly.flatMap(x=>[x.registered,x.disbursed]))+1;
1136
+ // stacked bars side-by-side
1137
+ data.fdiMonthly.forEach((m,i)=>{
1138
+ const gap = innerW / data.fdiMonthly.length;
1139
+ const barW = gap*0.35;
1140
+ const x = padding.l + i*gap;
1141
+ const yReg = padding.t + innerH - (m.registered/maxV)*innerH;
1142
+ const yDis = padding.t + innerH - (m.disbursed/maxV)*innerH;
1143
+
1144
+ const rectR = document.createElementNS(ns,'rect');
1145
+ rectR.setAttribute('x', x - barW/2);
1146
+ rectR.setAttribute('y', yReg);
1147
+ rectR.setAttribute('width', barW);
1148
+ rectR.setAttribute('height', padding.t + innerH - yReg);
1149
+ rectR.setAttribute('fill','#06b6d4');
1150
+ rectR.setAttribute('rx',6);
1151
+ svg.appendChild(rectR);
1152
+
1153
+ const rectD = document.createElementNS(ns,'rect');
1154
+ rectD.setAttribute('x', x + barW/2);
1155
+ rectD.setAttribute('y', yDis);
1156
+ rectD.setAttribute('width', barW);
1157
+ rectD.setAttribute('height', padding.t + innerH - yDis);
1158
+ rectD.setAttribute('fill','#0ea5a2');
1159
+ rectD.setAttribute('rx',6);
1160
+ svg.appendChild(rectD);
1161
+
1162
+ [rectR,rectD].forEach((el,kindIdx)=>{
1163
+ el.addEventListener('mouseenter',()=>{
1164
+ const rect = holder.getBoundingClientRect();
1165
+ const text = kindIdx===0 ? `Registered: ${m.registered}B` : `Disbursed: ${m.disbursed}B`;
1166
+ showTooltip(`<strong>${m.month}</strong><div class="muted small">${text}</div>`, rect.left + x, rect.top + (kindIdx===0?yReg:yDis));
1167
+ });
1168
+ el.addEventListener('mouseleave', hideTooltip);
1169
+ });
1170
+
1171
+ // label
1172
+ const label = document.createElementNS(ns,'text');
1173
+ label.setAttribute('x', x);
1174
+ label.setAttribute('y', padding.t + innerH + 22);
1175
+ label.setAttribute('text-anchor','middle');
1176
+ label.setAttribute('fill','var(--muted)');
1177
+ label.setAttribute('style','font-size:11px');
1178
+ label.textContent = m.month.split(' ')[0];
1179
+ svg.appendChild(label);
1180
+ });
1181
+
1182
+ // legend
1183
+ const legendDiv = document.createElement('div');
1184
+ legendDiv.style.marginTop = '8px';
1185
+ }
1186
+ drawFDIChart();
1187
+
1188
+ /* ===========================
1189
+ Interactions: search, copy summary, export
1190
+ ===========================*/
1191
+ const globalSearch = $('#globalSearch');
1192
+ globalSearch.addEventListener('input', (e) => {
1193
+ const q = e.target.value.trim().toLowerCase();
1194
+ // highlight and open matching sections, show first match
1195
+ const sections = Array.from(document.querySelectorAll('main.content section'));
1196
+ let firstMatch = null;
1197
+ sections.forEach(sec => {
1198
+ const txt = sec.textContent.toLowerCase();
1199
+ if(q && txt.includes(q)){
1200
+ sec.classList.add('highlight');
1201
+ sec.style.boxShadow = '0 6px 30px rgba(6,182,212,0.06)';
1202
+ if(!firstMatch) firstMatch = sec;
1203
+ } else {
1204
+ sec.classList.remove('highlight');
1205
+ sec.style.boxShadow = '';
1206
+ }
1207
+ });
1208
+ if(firstMatch){
1209
+ firstMatch.scrollIntoView({behavior:'smooth', block:'center'});
1210
+ }
1211
+ });
1212
+
1213
+ /* Copy executive summary to clipboard */
1214
+ $('#copySummary').addEventListener('click', async () => {
1215
+ const summary = document.querySelector('#executive .summary p').textContent.trim();
1216
+ try{
1217
+ await navigator.clipboard.writeText(summary);
1218
+ alert('Executive summary copied to clipboard.');
1219
+ }catch(e){
1220
+ console.warn(e);
1221
+ alert('Copy failed — permission denied.');
1222
+ }
1223
+ });
1224
+
1225
+ /* Export indicators to CSV */
1226
+ $('#exportCSV').addEventListener('click', () => {
1227
+ const rows = data.indicatorsTable;
1228
+ const csv = ['Indicator,Value,Period,Change', ...rows.map(r=>`"${r.indicator}","${r.value}","${r.period}","${r.change}"`)].join('\\n');
1229
+ const blob = new Blob([csv], {type:'text/csv'});
1230
+ const url = URL.createObjectURL(blob);
1231
+ const a = document.createElement('a');
1232
+ a.href = url; a.download = 'vietnam_indicators_2025.csv';
1233
+ document.body.appendChild(a); a.click(); a.remove();
1234
+ URL.revokeObjectURL(url);
1235
+ });
1236
+
1237
+ /* Export JSON */
1238
+ $('#exportJSON').addEventListener('click', () => {
1239
+ const payload = {
1240
+ meta:{title:'Vietnam Economic Growth Report 2025', date:2025},
1241
+ data
1242
+ };
1243
+ const blob = new Blob([JSON.stringify(payload,null,2)],{type:'application/json'});
1244
+ const url = URL.createObjectURL(blob);
1245
+ const a = document.createElement('a');
1246
+ a.href = url; a.download = 'vietnam_report_2025.json';
1247
+ document.body.appendChild(a); a.click(); a.remove();
1248
+ URL.revokeObjectURL(url);
1249
+ });
1250
+
1251
+ /* Print */
1252
+ $('#printBtn').addEventListener('click', () => window.print());
1253
+
1254
+ /* Copy FDI table */
1255
+ $('#copyFDI').addEventListener('click', async () => {
1256
+ const txt = data.fdiMonthly.map(r=>`${r.month}: Registered ${r.registered}B — Disbursed ${r.disbursed}B`).join('\\n');
1257
+ try{ await navigator.clipboard.writeText(txt); alert('FDI table copied to clipboard.'); }catch(e){ alert('Copy failed.'); }
1258
+ });
1259
+
1260
+ /* Download FDI CSV */
1261
+ $('#downloadFDI').addEventListener('click', () => {
1262
+ const csv = ['Month,Registered,Disbursed', ...data.fdiMonthly.map(r=>`${r.month},${r.registered},${r.disbursed}`)].join('\\n');
1263
+ const blob = new Blob([csv],{type:'text/csv'});
1264
+ const url = URL.createObjectURL(blob);
1265
+ const a = document.createElement('a');
1266
+ a.href = url; a.download = 'fdi_jan_may_2025.csv';
1267
+ document.body.appendChild(a); a.click(); a.remove();
1268
+ URL.revokeObjectURL(url);
1269
+ });
1270
+
1271
+ /* Copy references buttons */
1272
+ $$('.references button').forEach(btn=>{
1273
+ btn.addEventListener('click', async () => {
1274
+ const txt = btn.dataset.copy;
1275
+ try{ await navigator.clipboard.writeText(txt); alert('Citation copied.'); }catch(e){ alert('Copy failed.'); }
1276
+ });
1277
+ });
1278
+ $('#copyRefs').addEventListener('click', async () => {
1279
+ const all = data.references.map(r=>r.text).join('\\n');
1280
+ try{ await navigator.clipboard.writeText(all); alert('All references copied.'); }catch(e){ alert('Copy failed.'); }
1281
+ });
1282
+ $('#exportRefsJSON').addEventListener('click', () => {
1283
+ const blob = new Blob([JSON.stringify(data.references,null,2)],{type:'application/json'});
1284
+ const url = URL.createObjectURL(blob);
1285
+ const a = document.createElement('a');
1286
+ a.href = url; a.download = 'references.json';
1287
+ document.body.appendChild(a); a.click(); a.remove();
1288
+ URL.revokeObjectURL(url);
1289
+ });
1290
+
1291
+ /* Export entire visible report to JSON (quick) - reuse exportJSON */
1292
+
1293
+ $('#readMoreExec').addEventListener('click', () => {
1294
+ const more = `Vietnam's economy maintained robust growth in H1 2025 with leads from services and manufacturing. Macroeconomic control (inflation ~3.5%) and strong FDI flows underpin resilience, yet external trade tensions pose key risks.`;
1295
+ alert(more);
1296
+ });
1297
+
1298
+ /* ===========================
1299
+ Table filtering (FDI feature)
1300
+ ===========================*/
1301
+ $('#fdifeature').addEventListener('change', (e)=>{
1302
+ const v = e.target.value;
1303
+ const tbody = document.querySelectorAll('#fdiTable tbody tr');
1304
+ tbody.forEach(tr => tr.style.display = 'table-row'); // single view in table; filtering shows/hides columns
1305
+ const cells = $('#fdiTable thead').querySelectorAll('th');
1306
+ if(v === 'registered'){
1307
+ cells[1].style.display='table-cell';
1308
+ cells[2].style.display='none';
1309
+ $('#fdiTable tbody tr').forEach(tr=> tr.children[1].style.display='table-cell', tr.children[2].style.display='none');
1310
+ } else if(v === 'disbursed'){
1311
+ cells[1].style.display='none';
1312
+ cells[2].style.display='table-cell';
1313
+ $('#fdiTable tbody tr').forEach(tr=> tr.children[1].style.display='none', tr.children[2].style.display='table-cell');
1314
+ } else {
1315
+ cells[1].style.display='table-cell';
1316
+ cells[2].style.display='table-cell';
1317
+ $('#fdiTable tbody tr').forEach(tr=> tr.children[1].style.display='table-cell', tr.children[2].style.display='table-cell');
1318
+ }
1319
+ });
1320
+
1321
+ /* ===========================
1322
+ TOC Active Link and Progress (IntersectionObserver)
1323
+ ===========================*/
1324
+ const sections = Array.from(document.querySelectorAll('main.content section'));
1325
+ const tocLinks = Array.from(document.querySelectorAll('.toc a'));
1326
+ const progressEl = $('#tocProgress');
1327
+
1328
+ const obs = new IntersectionObserver(entries=>{
1329
+ entries.forEach(entry=>{
1330
+ const id = entry.target.id;
1331
+ const link = document.querySelector('.toc a[data-id="'+id+'"]');
1332
+ if(entry.isIntersecting){
1333
+ tocLinks.forEach(l=>l.classList.remove('active'));
1334
+ if(link) link.classList.add('active');
1335
+ localStorage.setItem('vne_last_section', id);
1336
+ }
1337
+ // calculate progress: visible proportion of document
1338
+ const visible = Math.max(0,entry.intersectionRatio);
1339
+ // approximate progress based on scroll position
1340
+ const docHeight = document.body.scrollHeight - window.innerHeight;
1341
+ const scrolled = window.scrollY;
1342
+ const progressPerc = Math.min(100, Math.round((scrolled/docHeight)*100));
1343
+ progressEl.style.width = progressPerc + '%';
1344
+ progressEl.style.display = 'block';
1345
+ // animate width using transform
1346
+ progressEl.style.transform = `translateX(${0}%)`;
1347
+ $('#tocProgress').style.width = progressPerc + '%';
1348
+ });
1349
+ }, {threshold:[0.15,0.45,0.75]});
1350
+
1351
+ sections.forEach(s => obs.observe(s));
1352
+
1353
+ /* Save & go bookmark (stores last open section id) */
1354
+ $('#saveBookmark').addEventListener('click', () => {
1355
+ const active = document.querySelector('.toc a.active');
1356
+ if(active){
1357
+ const id = active.dataset.id;
1358
+ localStorage.setItem('vne_bookmark', id);
1359
+ alert('Saved current section: '+id);
1360
+ } else alert('No active section selected.');
1361
+ });
1362
+ $('#gotoBookmark').addEventListener('click', () => {
1363
+ const id = localStorage.getItem('vne_bookmark');
1364
+ if(id){
1365
+ const target = document.getElementById(id);
1366
+ target.scrollIntoView({behavior:'smooth', block:'start'});
1367
+ } else alert('No bookmark saved.');
1368
+ });
1369
+
1370
+ /* Restore last section if any */
1371
+ window.addEventListener('load', () => {
1372
+ const last = localStorage.getItem('vne_last_section');
1373
+ if(last){
1374
+ const link = document.querySelector('.toc a[data-id="'+last+'"]');
1375
+ if(link) link.classList.add('active');
1376
+ }
1377
+ });
1378
+
1379
+ /* ===========================
1380
+ Small mini history chart
1381
+ ===========================*/
1382
+ function drawMiniHistory(){
1383
+ const holder = $('#miniHistory');
1384
+ clearEl(holder);
1385
+ const w=560,h=160;
1386
+ const svg = createSVG(w,h);
1387
+ holder.appendChild(svg);
1388
+ const padding={t:16,b:30,l:40,r:16};
1389
+ const innerW = w-padding.l-padding.r;
1390
+ const innerH = h-padding.t-padding.b;
1391
+ const values = data.historyQ1.map(d=>d.value);
1392
+ const minV = Math.min(...values)-1;
1393
+ const maxV = Math.max(...values)+1;
1394
+ const xs = idx => padding.l + (idx/(values.length-1))*innerW;
1395
+ const ys = v => padding.t + innerH - ((v-minV)/(maxV-minV))*innerH;
1396
+ const ns = "http://www.w3.org/2000/svg";
1397
+
1398
+ let d = '';
1399
+ data.historyQ1.forEach((pt,i)=>{
1400
+ d += (i===0?'M':'L') + xs(i) + ' ' + ys(pt.value) + ' ';
1401
+ });
1402
+ const path = document.createElementNS(ns,'path');
1403
+ path.setAttribute('d',d);
1404
+ path.setAttribute('fill','none');
1405
+ path.setAttribute('stroke','#0ea5a2');
1406
+ path.setAttribute('stroke-width','2');
1407
+ path.setAttribute('stroke-linecap','round');
1408
+ svg.appendChild(path);
1409
+
1410
+ data.historyQ1.forEach((pt,i)=>{
1411
+ const c = document.createElementNS(ns,'circle');
1412
+ c.setAttribute('cx',xs(i));
1413
+ c.setAttribute('cy',ys(pt.value));
1414
+ c.setAttribute('r',3);
1415
+ c.setAttribute('fill','#fff');
1416
+ c.setAttribute('stroke','#0ea5a2');
1417
+ c.setAttribute('stroke-width',1.5);
1418
+ svg.appendChild(c);
1419
+ });
1420
+ }
1421
+ drawMiniHistory();
1422
+
1423
+ /* ===========================
1424
+ Make FDI table rows clickable to copy
1425
+ ===========================*/
1426
+ $('#fdiTable tbody').addEventListener('click', async (e) => {
1427
+ let tr = e.target.closest('tr');
1428
+ if(!tr) return;
1429
+ const cells = Array.from(tr.children).map(td=>td.textContent.trim()).join(' | ');
1430
+ try{ await navigator.clipboard.writeText(cells); alert('Row copied: '+cells); }catch(e){ alert('Copy failed'); }
1431
+ });
1432
+
1433
+ /* Accessibility: keyboard navigation for TOC */
1434
+ $$('.toc a').forEach((a,i)=>{
1435
+ a.setAttribute('tabindex',0);
1436
+ a.addEventListener('keydown', (e)=>{
1437
+ if(e.key === 'Enter') a.click();
1438
+ });
1439
+ });
1440
+
1441
+ /* Resize handlers: redraw charts on resize to keep resolution appropriate */
1442
+ let resizeTimer;
1443
+ window.addEventListener('resize', () => {
1444
+ clearTimeout(resizeTimer);
1445
+ resizeTimer = setTimeout(()=>{ drawHistoryChart(); drawForecastChart(); drawDonut(); drawFDIChart(); drawMiniHistory(); }, 200);
1446
+ });
1447
+
1448
+ /* Initialize other UI states */
1449
+ (function init(){
1450
+ renderFDITable();
1451
+ // populate toc active
1452
+ const hash = location.hash.replace('#','');
1453
+ if(hash){
1454
+ const target = document.getElementById(hash);
1455
+ if(target) target.scrollIntoView();
1456
+ }
1457
+ })();
1458
+ </script>
1459
+ </body>
1460
+ </html>