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