diff --git a/package-lock.json b/package-lock.json
index b4d865740dbb3c446007dc429f1584b8e4ca54d6..53e6302d007663e57102636b7e3460b35dc4cbdd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -45,6 +45,7 @@
         "eslint-plugin-import": "^2.29.1",
         "eslint-plugin-svelte": "^2.33.0",
         "fs-extra": "^11.1.0",
+        "html-to-image": "^1.11.11",
         "iconify-icon": "^2.1.0",
         "intro.js": "^7.0.1",
         "js-yaml": "^4.1.0",
@@ -2048,12 +2049,12 @@
       }
     },
     "node_modules/braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "dev": true,
       "dependencies": {
-        "fill-range": "^7.0.1"
+        "fill-range": "^7.1.1"
       },
       "engines": {
         "node": ">=8"
@@ -3568,9 +3569,9 @@
       "dev": true
     },
     "node_modules/fast-xml-parser": {
-      "version": "4.3.5",
-      "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.5.tgz",
-      "integrity": "sha512-sWvP1Pl8H03B8oFJpFR3HE31HUfwtX7Rlf9BNsvdpujD4n7WMhfmu8h9wOV2u+c1k0ZilTADhPqypzx2J690ZQ==",
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz",
+      "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==",
       "dev": true,
       "funding": [
         {
@@ -3640,9 +3641,9 @@
       }
     },
     "node_modules/fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "dev": true,
       "dependencies": {
         "to-regex-range": "^5.0.1"
@@ -4125,6 +4126,12 @@
         "node": ">=18"
       }
     },
+    "node_modules/html-to-image": {
+      "version": "1.11.11",
+      "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz",
+      "integrity": "sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==",
+      "dev": true
+    },
     "node_modules/http-proxy-agent": {
       "version": "7.0.2",
       "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
@@ -5035,12 +5042,12 @@
       }
     },
     "node_modules/micromatch": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
-      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
       "dev": true,
       "dependencies": {
-        "braces": "^3.0.2",
+        "braces": "^3.0.3",
         "picomatch": "^2.3.1"
       },
       "engines": {
@@ -5577,9 +5584,9 @@
       }
     },
     "node_modules/picocolors": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
-      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
+      "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
       "dev": true
     },
     "node_modules/picomatch": {
@@ -5652,9 +5659,9 @@
       }
     },
     "node_modules/postcss": {
-      "version": "8.4.35",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
-      "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+      "version": "8.4.41",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
+      "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
       "dev": true,
       "funding": [
         {
@@ -5672,8 +5679,8 @@
       ],
       "dependencies": {
         "nanoid": "^3.3.7",
-        "picocolors": "^1.0.0",
-        "source-map-js": "^1.0.2"
+        "picocolors": "^1.0.1",
+        "source-map-js": "^1.2.0"
       },
       "engines": {
         "node": "^10 || ^12 || >=14"
@@ -6561,9 +6568,9 @@
       }
     },
     "node_modules/source-map-js": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
-      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+      "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
       "dev": true,
       "engines": {
         "node": ">=0.10.0"
@@ -7590,14 +7597,14 @@
       }
     },
     "node_modules/vite": {
-      "version": "5.1.6",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz",
-      "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==",
+      "version": "5.4.1",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.1.tgz",
+      "integrity": "sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==",
       "dev": true,
       "dependencies": {
-        "esbuild": "^0.19.3",
-        "postcss": "^8.4.35",
-        "rollup": "^4.2.0"
+        "esbuild": "^0.21.3",
+        "postcss": "^8.4.41",
+        "rollup": "^4.13.0"
       },
       "bin": {
         "vite": "bin/vite.js"
@@ -7616,6 +7623,7 @@
         "less": "*",
         "lightningcss": "^1.21.0",
         "sass": "*",
+        "sass-embedded": "*",
         "stylus": "*",
         "sugarss": "*",
         "terser": "^5.4.0"
@@ -7633,6 +7641,9 @@
         "sass": {
           "optional": true
         },
+        "sass-embedded": {
+          "optional": true
+        },
         "stylus": {
           "optional": true
         },
@@ -7644,6 +7655,412 @@
         }
       }
     },
+    "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/android-arm": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+      "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/android-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+      "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/android-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+      "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+      "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+      "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+      "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-arm": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+      "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+      "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+      "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+      "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+      "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+      "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+      "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+      "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+      "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+      "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+      "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/win32-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+      "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/esbuild": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+      "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.21.5",
+        "@esbuild/android-arm": "0.21.5",
+        "@esbuild/android-arm64": "0.21.5",
+        "@esbuild/android-x64": "0.21.5",
+        "@esbuild/darwin-arm64": "0.21.5",
+        "@esbuild/darwin-x64": "0.21.5",
+        "@esbuild/freebsd-arm64": "0.21.5",
+        "@esbuild/freebsd-x64": "0.21.5",
+        "@esbuild/linux-arm": "0.21.5",
+        "@esbuild/linux-arm64": "0.21.5",
+        "@esbuild/linux-ia32": "0.21.5",
+        "@esbuild/linux-loong64": "0.21.5",
+        "@esbuild/linux-mips64el": "0.21.5",
+        "@esbuild/linux-ppc64": "0.21.5",
+        "@esbuild/linux-riscv64": "0.21.5",
+        "@esbuild/linux-s390x": "0.21.5",
+        "@esbuild/linux-x64": "0.21.5",
+        "@esbuild/netbsd-x64": "0.21.5",
+        "@esbuild/openbsd-x64": "0.21.5",
+        "@esbuild/sunos-x64": "0.21.5",
+        "@esbuild/win32-arm64": "0.21.5",
+        "@esbuild/win32-ia32": "0.21.5",
+        "@esbuild/win32-x64": "0.21.5"
+      }
+    },
     "node_modules/vite/node_modules/fsevents": {
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -7932,9 +8349,9 @@
       "dev": true
     },
     "node_modules/ws": {
-      "version": "8.16.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
-      "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+      "version": "8.18.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
       "dev": true,
       "engines": {
         "node": ">=10.0.0"
diff --git a/package.json b/package.json
index b27756ce1e405d9cf38e0dc619efe406d8c8b224..fb10e9f15511a44ac306ed7dc21c009a26d9224a 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
     "eslint-plugin-import": "^2.29.1",
     "eslint-plugin-svelte": "^2.33.0",
     "fs-extra": "^11.1.0",
+    "html-to-image": "^1.11.11",
     "iconify-icon": "^2.1.0",
     "intro.js": "^7.0.1",
     "js-yaml": "^4.1.0",
diff --git a/src/lib/components/piece_of_cake/Svg.svelte b/src/lib/components/piece_of_cake/Svg.svelte
index 8b6ab1501f731f7404ce1f2474e7453e91a4683e..791b51a5608eb580d30e192ad3749b2db9d35a32 100644
--- a/src/lib/components/piece_of_cake/Svg.svelte
+++ b/src/lib/components/piece_of_cake/Svg.svelte
@@ -21,6 +21,11 @@
   /// The layer's z-index.
   export let zIndex: number | undefined = undefined
 
+  const paddingRotatedTextAxisX = {
+    bottom: 40,
+    right: 20,
+  }
+
   $: modifiedModelGroups = modelGroups.map((modelGroup) =>
     modelGroup.map((model) => model.setPadding(padding)),
   )
@@ -29,14 +34,16 @@
 <svg
   bind:this={element}
   class="absolute overflow-visible top-0 left-0"
-  height={modifiedModelGroups?.[0]?.[0]?.containerHeight}
+  height={modifiedModelGroups?.[0]?.[0]?.containerHeight +
+    paddingRotatedTextAxisX.bottom}
   style:clip-path={clipOverflow
     ? `inset(${padding.top - 1}px 0 ${padding.bottom - 1}px 0)`
     : null}
   style:pointer-events={pointerEvents === false ? "none" : null}
   style:z-index={zIndex}
   {viewBox}
-  width={modifiedModelGroups?.[0]?.[0]?.containerWidth}
+  width={modifiedModelGroups?.[0]?.[0]?.containerWidth +
+    paddingRotatedTextAxisX.right}
 >
   <defs>
     <slot name="defs" />
diff --git a/src/lib/components/test_cases/TestCaseGraph.svelte b/src/lib/components/test_cases/TestCaseGraph.svelte
index 6bdff891096040dcea8b206369802c399456f81f..1937242fff857d80916540e3e90a52ea661e0f9d 100644
--- a/src/lib/components/test_cases/TestCaseGraph.svelte
+++ b/src/lib/components/test_cases/TestCaseGraph.svelte
@@ -1156,7 +1156,7 @@
             </span>
           </div>
         </div>
-        <div class="w-full h-screen max-h-64">
+        <div id="test_case_graph" class="w-full h-screen max-h-64 pb-10 pr-5 bg-white">
           <PieceOfCake
             let:modelGroups
             modelGroups={variableValues.map((variableValuesGroup) =>
@@ -1434,7 +1434,7 @@
           </PieceOfCake>
         </div>
         <div
-          class="mt-10 col-start-2 w-full flex justify-between items-start"
+          class="col-start-2 w-full flex justify-between items-start"
           style:padding-right="{svgPadding.right}px"
         >
           <div class="flex flex-col justify-center">
diff --git a/src/lib/server/test_cases.ts b/src/lib/server/test_cases.ts
index 609f7a7666bcfd437b7d71d39f37af3af8965996..1b10fbe81a0d1e08eb3a2ce21349c80bee8a83d4 100644
--- a/src/lib/server/test_cases.ts
+++ b/src/lib/server/test_cases.ts
@@ -23,3 +23,14 @@ export function getTestCasesCacheDirPath(digest: string) {
 export function getTestCasesCacheFilePath(digest: string) {
   return path.join(getTestCasesCacheDirPath(digest), `${digest}.json`)
 }
+
+export function getTestCasesOpenGraphsDirPath(digest: string) {
+  return path.join(
+    config.simulationsTestCasesDir,
+    "opengraphs",
+    digest.substring(0, 2),
+  )
+}
+export function getTestCasesOpenGraphsFilePath(digest: string) {
+  return path.join(getTestCasesOpenGraphsDirPath(digest), `${digest}.png`)
+}
diff --git a/src/lib/simulations.ts b/src/lib/simulations.ts
index 1d9532c7a7770511a73eadbe42114c5fcf969cef..32ea3b316344b18cd69ac36d1598671be9ec802c 100644
--- a/src/lib/simulations.ts
+++ b/src/lib/simulations.ts
@@ -41,6 +41,7 @@ export interface Simulation {
 }
 
 export async function publishTestCaseSimulation(
+  blobImageOpenGraph: Blob | null | undefined,
   displayMode: DisplayMode,
   inputInstantsByVariableNameArray: Array<{
     [name: string]: Set<string>
@@ -49,8 +50,16 @@ export async function publishTestCaseSimulation(
   testCases: Situation[],
 ): Promise<string | undefined> {
   const urlString = "/test_cases/simulations"
-  const res = await fetch(urlString, {
-    body: JSON.stringify({
+
+  const formData = new FormData()
+
+  if (blobImageOpenGraph !== undefined && blobImageOpenGraph !== null) {
+    formData.append("blob", blobImageOpenGraph)
+  }
+
+  formData.append(
+    "body",
+    JSON.stringify({
       displayMode: {
         ...displayMode,
         parameterHash: undefined,
@@ -69,12 +78,13 @@ export async function publishTestCaseSimulation(
       parametricReform: parametricReform,
       testCases: testCases,
     }),
-    headers: {
-      Accept: "application/json",
-      "Content-Type": "application/json; charset=utf-8",
-    },
+  )
+
+  const res = await fetch(urlString, {
+    body: formData,
     method: "POST",
   })
+
   if (!res.ok) {
     console.error(
       `Error ${
@@ -83,6 +93,8 @@ export async function publishTestCaseSimulation(
     )
     return
   }
+
   const { token } = await res.json()
+
   return token
 }
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 25299071c8b94b5b41289608c16ee735b7d145f5..d49447f7f32f80fc9de1658314bd11806428559b 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -12,6 +12,7 @@
     auditOptions,
   } from "@auditors/core"
   import type { PopulationWithoutId, Waterfall } from "@openfisca/json-model"
+  import * as htmlToImage from "html-to-image"
   import introJs from "intro.js"
   import type { IntroStep } from "intro.js/src/core/steps"
   import { getContext, setContext } from "svelte"
@@ -886,52 +887,27 @@
   }
 
   async function shareTestCaseSimulationLink(): Promise<void> {
-    const urlString = "/test_cases/simulations"
-    const res = await fetch(urlString, {
-      body: JSON.stringify({
-        displayMode: {
-          ...$displayModeWritable,
-          parameterHash: undefined,
-        },
-        inputInstantsByVariableNameArray: $inputInstantsByVariableNameArray.map(
-          (inputInstantsByVariableName) =>
-            Object.fromEntries(
-              Object.entries(inputInstantsByVariableName).map(
-                ([variableName, inputInstants]) => [
-                  variableName,
-                  [...inputInstants],
-                ],
-              ),
-            ),
-        ),
-        parametricReform: $parametricReform,
-        testCases: $testCases,
-      }),
-      headers: {
-        Accept: "application/json",
-        "Content-Type": "application/json; charset=utf-8",
-      },
-      method: "POST",
-    })
-    if (!res.ok) {
-      console.error(
-        `Error ${
-          res.status
-        } while creating a share link at ${urlString}\n\n${await res.text()}`,
-      )
-      return
+    let blob: Blob | null | undefined
+
+    const testCaseGraphEl = document.getElementById("test_case_graph")
+    if (testCaseGraphEl) {
+      blob = await htmlToImage.toBlob(testCaseGraphEl)
     }
+
     const token = await publishTestCaseSimulation(
+      blob,
       $displayModeWritable!,
       $inputInstantsByVariableNameArray,
       $parametricReform,
       $testCases,
     )
+
     if (token !== undefined) {
       testCaseSharingModal = {
         open: true,
         token,
       }
+
       trackTestCaseShare()
     }
   }
diff --git a/src/routes/test_cases/og_image/[simulation]/+server.ts b/src/routes/test_cases/og_image/[simulation]/+server.ts
new file mode 100644
index 0000000000000000000000000000000000000000..79c03bffe102aa0f172f39c9cc36642ec66c63ec
--- /dev/null
+++ b/src/routes/test_cases/og_image/[simulation]/+server.ts
@@ -0,0 +1,19 @@
+import fs from "fs-extra"
+
+import type { RequestHandler } from "./$types"
+
+import { getTestCasesOpenGraphsFilePath } from "$lib/server/test_cases"
+
+export const GET: RequestHandler = async ({ params }) => {
+  const { simulation: digest } = params as { simulation: string }
+
+  const openGraphsFilePath = getTestCasesOpenGraphsFilePath(digest)
+  const image = await fs.readFile(openGraphsFilePath)
+  const response = new Response(image)
+  response.headers.append("Content-Type", "image/png")
+  response.headers.append(
+    "Cache-Control",
+    "s-maxage=604800, stale-while-revalidate=604800",
+  )
+  return response
+}
diff --git a/src/routes/test_cases/simulations/+server.ts b/src/routes/test_cases/simulations/+server.ts
index 43fe2a2d3098297dd4ae8a42f16149a684dc0faf..8308baf2fba92e06c0c4f8dc62a8a37c9aa8c869 100644
--- a/src/routes/test_cases/simulations/+server.ts
+++ b/src/routes/test_cases/simulations/+server.ts
@@ -11,6 +11,8 @@ import type { RequestHandler } from "./$types"
 
 import { hashString } from "$lib/hash"
 import {
+  getTestCasesOpenGraphsDirPath,
+  getTestCasesOpenGraphsFilePath,
   getTestCasesRequestsDirPath,
   getTestCasesRequestsFilePath,
 } from "$lib/server/test_cases"
@@ -69,7 +71,12 @@ function auditBody(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
 }
 
 export const POST: RequestHandler = async ({ request, url }) => {
-  const [body, bodyError] = auditBody(cleanAudit, await request.json())
+  const formData = await request.formData()
+
+  const [body, bodyError] = auditBody(
+    cleanAudit,
+    JSON.parse(formData.get("body") as string),
+  )
   if (bodyError !== null) {
     console.error(
       `Error in ${url.pathname} body:\n${JSON.stringify(
@@ -84,6 +91,24 @@ export const POST: RequestHandler = async ({ request, url }) => {
 
   const digest = hashString(bodyJson)
 
+  const blob = formData.get("blob") as Blob | null | undefined
+
+  if (blob !== null && blob !== undefined) {
+    const openGraphsDir = getTestCasesOpenGraphsDirPath(digest)
+    const openGraphsFilePath = getTestCasesOpenGraphsFilePath(digest)
+    if (!(await fs.pathExists(openGraphsFilePath))) {
+      await fs.ensureDir(openGraphsDir)
+
+      const fileStream = fs.createWriteStream(openGraphsFilePath)
+      const arrayBuffer = await new File([blob], `${digest}.png`, {
+        type: blob.type,
+      }).arrayBuffer()
+      const buffer = Buffer.from(arrayBuffer)
+      fileStream.write(buffer)
+      fileStream.end()
+    }
+  }
+
   const simulationDir = getTestCasesRequestsDirPath(digest)
   const simulationFilePath = getTestCasesRequestsFilePath(digest)
   if (!(await fs.pathExists(simulationFilePath))) {
diff --git a/src/routes/test_cases/simulations/[simulation]/+page.svelte b/src/routes/test_cases/simulations/[simulation]/+page.svelte
index 66387d2f8a29d685b6d0f75a6d13340d6f336e20..17f502e786d6c1923bac9c5423a674fd97460686 100644
--- a/src/routes/test_cases/simulations/[simulation]/+page.svelte
+++ b/src/routes/test_cases/simulations/[simulation]/+page.svelte
@@ -5,10 +5,12 @@
   import type { PageData } from "./$types"
 
   import { goto } from "$app/navigation"
+  import { page } from "$app/stores"
   import {
     requestAllTestCasesCalculations,
     type RequestedCalculations,
   } from "$lib/calculations"
+  import OpenGraph from "$lib/components/transverse_pages/OpenGraph.svelte"
   import type { EvaluationByName } from "$lib/decompositions"
   import type { ParametricReform } from "$lib/reforms"
   import type { Situation } from "$lib/situations"
@@ -71,3 +73,17 @@
     )
   })
 </script>
+
+<svelte:head>
+  <title>
+    Votre simulation cas type
+  </title>
+</svelte:head>
+
+<OpenGraph
+  description="{simulation.testCases[simulation.displayMode.testCasesIndex[0]].title}"
+  image={new URL(`test_cases/og_image/${$page.params.simulation}`, data.baseUrl).toString()}
+  title="Votre simulation cas type"
+  titleSuffix={null}
+  url={$page.url.toString()}
+/>