From 1ab9c91df1df799f7a1004200ff890b144d00f19 Mon Sep 17 00:00:00 2001
From: Aaron Pham <contact@aarnphm.xyz>
Date: Sun, 10 Nov 2024 23:13:12 +0000
Subject: [PATCH] feat(mermaid): improvement navigation (#1575)

---
 package-lock.json                             |  631 +++++++++++++++++++++++++++--
 quartz/components/scripts/clipboard.inline.ts |    4 
 quartz/components/scripts/mermaid.inline.ts   |  242 +++++++++++
 quartz/plugins/transformers/ofm.ts            |  161 ++++++-
 package.json                                  |    1 
 quartz/components/styles/mermaid.inline.scss  |  163 +++++++
 6 files changed, 1,118 insertions(+), 84 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index d8da109..18d2c93 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32,6 +32,7 @@
         "mdast-util-find-and-replace": "^3.0.1",
         "mdast-util-to-hast": "^13.2.0",
         "mdast-util-to-string": "^4.0.0",
+        "mermaid": "^11.4.0",
         "micromorph": "^0.4.5",
         "pixi.js": "^8.5.2",
         "preact": "^10.24.3",
@@ -91,6 +92,28 @@
         "npm": ">=9.3.1"
       }
     },
+    "node_modules/@antfu/install-pkg": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz",
+      "integrity": "sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==",
+      "license": "MIT",
+      "dependencies": {
+        "package-manager-detector": "^0.2.0",
+        "tinyexec": "^0.3.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@antfu/utils": {
+      "version": "0.7.10",
+      "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz",
+      "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
     "node_modules/@asamuzakjp/dom-selector": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-2.0.2.tgz",
@@ -101,12 +124,57 @@
         "is-potential-custom-element-name": "^1.0.1"
       }
     },
+    "node_modules/@braintree/sanitize-url": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.0.tgz",
+      "integrity": "sha512-o+UlMLt49RvtCASlOMW0AkHnabN9wR9rwCCherxO0yG4Npy34GkvrAqdXQvrhNs+jh+gkK8gB8Lf05qL/O7KWg==",
+      "license": "MIT"
+    },
     "node_modules/@bufbuild/protobuf": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.0.tgz",
       "integrity": "sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==",
       "peer": true
     },
+    "node_modules/@chevrotain/cst-dts-gen": {
+      "version": "11.0.3",
+      "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
+      "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@chevrotain/gast": "11.0.3",
+        "@chevrotain/types": "11.0.3",
+        "lodash-es": "4.17.21"
+      }
+    },
+    "node_modules/@chevrotain/gast": {
+      "version": "11.0.3",
+      "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
+      "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@chevrotain/types": "11.0.3",
+        "lodash-es": "4.17.21"
+      }
+    },
+    "node_modules/@chevrotain/regexp-to-ast": {
+      "version": "11.0.3",
+      "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
+      "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@chevrotain/types": {
+      "version": "11.0.3",
+      "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
+      "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@chevrotain/utils": {
+      "version": "11.0.3",
+      "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
+      "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
+      "license": "Apache-2.0"
+    },
     "node_modules/@citation-js/core": {
       "version": "0.7.14",
       "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.14.tgz",
@@ -600,6 +668,27 @@
       "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
       "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig=="
     },
+    "node_modules/@iconify/types": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
+      "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
+      "license": "MIT"
+    },
+    "node_modules/@iconify/utils": {
+      "version": "2.1.33",
+      "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.33.tgz",
+      "integrity": "sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==",
+      "license": "MIT",
+      "dependencies": {
+        "@antfu/install-pkg": "^0.4.0",
+        "@antfu/utils": "^0.7.10",
+        "@iconify/types": "^2.0.0",
+        "debug": "^4.3.6",
+        "kolorist": "^1.8.0",
+        "local-pkg": "^0.5.0",
+        "mlly": "^1.7.1"
+      }
+    },
     "node_modules/@isaacs/cliui": {
       "version": "8.0.2",
       "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -616,6 +705,15 @@
         "node": ">=12"
       }
     },
+    "node_modules/@mermaid-js/parser": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz",
+      "integrity": "sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==",
+      "license": "MIT",
+      "dependencies": {
+        "langium": "3.0.0"
+      }
+    },
     "node_modules/@napi-rs/simple-git": {
       "version": "0.1.19",
       "resolved": "https://registry.npmjs.org/@napi-rs/simple-git/-/simple-git-0.1.19.tgz",
@@ -979,7 +1077,6 @@
       "version": "7.4.3",
       "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
       "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
-      "dev": true,
       "dependencies": {
         "@types/d3-array": "*",
         "@types/d3-axis": "*",
@@ -1016,14 +1113,12 @@
     "node_modules/@types/d3-array": {
       "version": "3.0.5",
       "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.5.tgz",
-      "integrity": "sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A==",
-      "dev": true
+      "integrity": "sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A=="
     },
     "node_modules/@types/d3-axis": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.2.tgz",
       "integrity": "sha512-uGC7DBh0TZrU/LY43Fd8Qr+2ja1FKmH07q2FoZFHo1eYl8aj87GhfVoY1saJVJiq24rp1+wpI6BvQJMKgQm8oA==",
-      "dev": true,
       "dependencies": {
         "@types/d3-selection": "*"
       }
@@ -1032,7 +1127,6 @@
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.2.tgz",
       "integrity": "sha512-2TEm8KzUG3N7z0TrSKPmbxByBx54M+S9lHoP2J55QuLU0VSQ9mE96EJSAOVNEqd1bbynMjeTS9VHmz8/bSw8rA==",
-      "dev": true,
       "dependencies": {
         "@types/d3-selection": "*"
       }
@@ -1040,20 +1134,17 @@
     "node_modules/@types/d3-chord": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.2.tgz",
-      "integrity": "sha512-abT/iLHD3sGZwqMTX1TYCMEulr+wBd0SzyOQnjYNLp7sngdOHYtNkMRI5v3w5thoN+BWtlHVDx2Osvq6fxhZWw==",
-      "dev": true
+      "integrity": "sha512-abT/iLHD3sGZwqMTX1TYCMEulr+wBd0SzyOQnjYNLp7sngdOHYtNkMRI5v3w5thoN+BWtlHVDx2Osvq6fxhZWw=="
     },
     "node_modules/@types/d3-color": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz",
-      "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==",
-      "dev": true
+      "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA=="
     },
     "node_modules/@types/d3-contour": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.2.tgz",
       "integrity": "sha512-k6/bGDoAGJZnZWaKzeB+9glgXCYGvh6YlluxzBREiVo8f/X2vpTEdgPy9DN7Z2i42PZOZ4JDhVdlTSTSkLDPlQ==",
-      "dev": true,
       "dependencies": {
         "@types/d3-array": "*",
         "@types/geojson": "*"
@@ -1062,20 +1153,17 @@
     "node_modules/@types/d3-delaunay": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz",
-      "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==",
-      "dev": true
+      "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ=="
     },
     "node_modules/@types/d3-dispatch": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.2.tgz",
-      "integrity": "sha512-rxN6sHUXEZYCKV05MEh4z4WpPSqIw+aP7n9ZN6WYAAvZoEAghEK1WeVZMZcHRBwyaKflU43PCUAJNjFxCzPDjg==",
-      "dev": true
+      "integrity": "sha512-rxN6sHUXEZYCKV05MEh4z4WpPSqIw+aP7n9ZN6WYAAvZoEAghEK1WeVZMZcHRBwyaKflU43PCUAJNjFxCzPDjg=="
     },
     "node_modules/@types/d3-drag": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.2.tgz",
       "integrity": "sha512-qmODKEDvyKWVHcWWCOVcuVcOwikLVsyc4q4EBJMREsoQnR2Qoc2cZQUyFUPgO9q4S3qdSqJKBsuefv+h0Qy+tw==",
-      "dev": true,
       "dependencies": {
         "@types/d3-selection": "*"
       }
@@ -1083,20 +1171,17 @@
     "node_modules/@types/d3-dsv": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.1.tgz",
-      "integrity": "sha512-76pBHCMTvPLt44wFOieouXcGXWOF0AJCceUvaFkxSZEu4VDUdv93JfpMa6VGNFs01FHfuP4a5Ou68eRG1KBfTw==",
-      "dev": true
+      "integrity": "sha512-76pBHCMTvPLt44wFOieouXcGXWOF0AJCceUvaFkxSZEu4VDUdv93JfpMa6VGNFs01FHfuP4a5Ou68eRG1KBfTw=="
     },
     "node_modules/@types/d3-ease": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz",
-      "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==",
-      "dev": true
+      "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA=="
     },
     "node_modules/@types/d3-fetch": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.2.tgz",
       "integrity": "sha512-gllwYWozWfbep16N9fByNBDTkJW/SyhH6SGRlXloR7WdtAaBui4plTP+gbUgiEot7vGw/ZZop1yDZlgXXSuzjA==",
-      "dev": true,
       "dependencies": {
         "@types/d3-dsv": "*"
       }
@@ -1104,20 +1189,17 @@
     "node_modules/@types/d3-force": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.4.tgz",
-      "integrity": "sha512-q7xbVLrWcXvSBBEoadowIUJ7sRpS1yvgMWnzHJggFy5cUZBq2HZL5k/pBSm0GdYWS1vs5/EDwMjSKF55PDY4Aw==",
-      "dev": true
+      "integrity": "sha512-q7xbVLrWcXvSBBEoadowIUJ7sRpS1yvgMWnzHJggFy5cUZBq2HZL5k/pBSm0GdYWS1vs5/EDwMjSKF55PDY4Aw=="
     },
     "node_modules/@types/d3-format": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz",
-      "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==",
-      "dev": true
+      "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg=="
     },
     "node_modules/@types/d3-geo": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.3.tgz",
       "integrity": "sha512-bK9uZJS3vuDCNeeXQ4z3u0E7OeJZXjUgzFdSOtNtMCJCLvDtWDwfpRVWlyt3y8EvRzI0ccOu9xlMVirawolSCw==",
-      "dev": true,
       "dependencies": {
         "@types/geojson": "*"
       }
@@ -1125,14 +1207,12 @@
     "node_modules/@types/d3-hierarchy": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
-      "integrity": "sha512-9hjRTVoZjRFR6xo8igAJyNXQyPX6Aq++Nhb5ebrUF414dv4jr2MitM2fWiOY475wa3Za7TOS2Gh9fmqEhLTt0A==",
-      "dev": true
+      "integrity": "sha512-9hjRTVoZjRFR6xo8igAJyNXQyPX6Aq++Nhb5ebrUF414dv4jr2MitM2fWiOY475wa3Za7TOS2Gh9fmqEhLTt0A=="
     },
     "node_modules/@types/d3-interpolate": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
       "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==",
-      "dev": true,
       "dependencies": {
         "@types/d3-color": "*"
       }
@@ -1140,32 +1220,27 @@
     "node_modules/@types/d3-path": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz",
-      "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==",
-      "dev": true
+      "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg=="
     },
     "node_modules/@types/d3-polygon": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz",
-      "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==",
-      "dev": true
+      "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw=="
     },
     "node_modules/@types/d3-quadtree": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz",
-      "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==",
-      "dev": true
+      "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw=="
     },
     "node_modules/@types/d3-random": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz",
-      "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==",
-      "dev": true
+      "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ=="
     },
     "node_modules/@types/d3-scale": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz",
       "integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==",
-      "dev": true,
       "dependencies": {
         "@types/d3-time": "*"
       }
@@ -1173,20 +1248,17 @@
     "node_modules/@types/d3-scale-chromatic": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz",
-      "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==",
-      "dev": true
+      "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw=="
     },
     "node_modules/@types/d3-selection": {
       "version": "3.0.5",
       "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.5.tgz",
-      "integrity": "sha512-xCB0z3Hi8eFIqyja3vW8iV01+OHGYR2di/+e+AiOcXIOrY82lcvWW8Ke1DYE/EUVMsBl4Db9RppSBS3X1U6J0w==",
-      "dev": true
+      "integrity": "sha512-xCB0z3Hi8eFIqyja3vW8iV01+OHGYR2di/+e+AiOcXIOrY82lcvWW8Ke1DYE/EUVMsBl4Db9RppSBS3X1U6J0w=="
     },
     "node_modules/@types/d3-shape": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.1.tgz",
       "integrity": "sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==",
-      "dev": true,
       "dependencies": {
         "@types/d3-path": "*"
       }
@@ -1194,26 +1266,22 @@
     "node_modules/@types/d3-time": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
-      "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==",
-      "dev": true
+      "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg=="
     },
     "node_modules/@types/d3-time-format": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz",
-      "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==",
-      "dev": true
+      "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw=="
     },
     "node_modules/@types/d3-timer": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz",
-      "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==",
-      "dev": true
+      "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g=="
     },
     "node_modules/@types/d3-transition": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.3.tgz",
       "integrity": "sha512-/S90Od8Id1wgQNvIA8iFv9jRhCiZcGhPd2qX0bKF/PS+y0W5CrXKgIiELd2CvG1mlQrWK/qlYh3VxicqG1ZvgA==",
-      "dev": true,
       "dependencies": {
         "@types/d3-selection": "*"
       }
@@ -1222,7 +1290,6 @@
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.3.tgz",
       "integrity": "sha512-OWk1yYIIWcZ07+igN6BeoG6rqhnJ/pYe+R1qWFM2DtW49zsoSjgb9G5xB0ZXA8hh2jAzey1XuRmMSoXdKw8MDA==",
-      "dev": true,
       "dependencies": {
         "@types/d3-interpolate": "*",
         "@types/d3-selection": "*"
@@ -1236,6 +1303,15 @@
         "@types/ms": "*"
       }
     },
+    "node_modules/@types/dompurify": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
+      "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/trusted-types": "*"
+      }
+    },
     "node_modules/@types/earcut": {
       "version": "2.1.4",
       "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz",
@@ -1258,8 +1334,7 @@
     "node_modules/@types/geojson": {
       "version": "7946.0.10",
       "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
-      "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==",
-      "dev": true
+      "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
     },
     "node_modules/@types/hast": {
       "version": "3.0.4",
@@ -1330,6 +1405,12 @@
         "source-map": "^0.6.0"
       }
     },
+    "node_modules/@types/trusted-types": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+      "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+      "license": "MIT"
+    },
     "node_modules/@types/unist": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
@@ -1379,6 +1460,18 @@
         "node": ">=10.0.0"
       }
     },
+    "node_modules/acorn": {
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+      "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+      "license": "MIT",
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/agent-base": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
@@ -1600,6 +1693,32 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/chevrotain": {
+      "version": "11.0.3",
+      "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
+      "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@chevrotain/cst-dts-gen": "11.0.3",
+        "@chevrotain/gast": "11.0.3",
+        "@chevrotain/regexp-to-ast": "11.0.3",
+        "@chevrotain/types": "11.0.3",
+        "@chevrotain/utils": "11.0.3",
+        "lodash-es": "4.17.21"
+      }
+    },
+    "node_modules/chevrotain-allstar": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz",
+      "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==",
+      "license": "MIT",
+      "dependencies": {
+        "lodash-es": "^4.17.21"
+      },
+      "peerDependencies": {
+        "chevrotain": "^11.0.0"
+      }
+    },
     "node_modules/chokidar": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
@@ -1748,6 +1867,12 @@
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
     },
+    "node_modules/confbox": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+      "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+      "license": "MIT"
+    },
     "node_modules/content-disposition": {
       "version": "0.5.2",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
@@ -1756,6 +1881,15 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/cose-base": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
+      "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
+      "license": "MIT",
+      "dependencies": {
+        "layout-base": "^1.0.0"
+      }
+    },
     "node_modules/cross-fetch": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
@@ -1800,6 +1934,54 @@
         "node": ">=18"
       }
     },
+    "node_modules/cytoscape": {
+      "version": "3.30.3",
+      "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.3.tgz",
+      "integrity": "sha512-HncJ9gGJbVtw7YXtIs3+6YAFSSiKsom0amWc33Z7QbylbY2JGMrA0yz4EwrdTScZxnwclXeEZHzO5pxoy0ZE4g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/cytoscape-cose-bilkent": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
+      "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
+      "license": "MIT",
+      "dependencies": {
+        "cose-base": "^1.0.0"
+      },
+      "peerDependencies": {
+        "cytoscape": "^3.2.0"
+      }
+    },
+    "node_modules/cytoscape-fcose": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
+      "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
+      "license": "MIT",
+      "dependencies": {
+        "cose-base": "^2.2.0"
+      },
+      "peerDependencies": {
+        "cytoscape": "^3.2.0"
+      }
+    },
+    "node_modules/cytoscape-fcose/node_modules/cose-base": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
+      "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
+      "license": "MIT",
+      "dependencies": {
+        "layout-base": "^2.0.0"
+      }
+    },
+    "node_modules/cytoscape-fcose/node_modules/layout-base": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
+      "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==",
+      "license": "MIT"
+    },
     "node_modules/d3": {
       "version": "7.9.0",
       "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
@@ -2061,6 +2243,46 @@
         "node": ">=12"
       }
     },
+    "node_modules/d3-sankey": {
+      "version": "0.12.3",
+      "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
+      "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "d3-array": "1 - 2",
+        "d3-shape": "^1.2.0"
+      }
+    },
+    "node_modules/d3-sankey/node_modules/d3-array": {
+      "version": "2.12.1",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+      "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "internmap": "^1.0.0"
+      }
+    },
+    "node_modules/d3-sankey/node_modules/d3-path": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
+      "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/d3-sankey/node_modules/d3-shape": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
+      "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "d3-path": "1"
+      }
+    },
+    "node_modules/d3-sankey/node_modules/internmap": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+      "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
+      "license": "ISC"
+    },
     "node_modules/d3-scale": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
@@ -2170,6 +2392,16 @@
         "node": ">=12"
       }
     },
+    "node_modules/dagre-d3-es": {
+      "version": "7.0.11",
+      "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz",
+      "integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==",
+      "license": "MIT",
+      "dependencies": {
+        "d3": "^7.9.0",
+        "lodash-es": "^4.17.21"
+      }
+    },
     "node_modules/data-urls": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
@@ -2182,12 +2414,19 @@
         "node": ">=18"
       }
     },
+    "node_modules/dayjs": {
+      "version": "1.11.13",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+      "license": "MIT"
+    },
     "node_modules/debug": {
-      "version": "4.3.4",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
       "dependencies": {
-        "ms": "2.1.2"
+        "ms": "^2.1.3"
       },
       "engines": {
         "node": ">=6.0"
@@ -2262,6 +2501,12 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/dompurify": {
+      "version": "3.1.6",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
+      "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==",
+      "license": "(MPL-2.0 OR Apache-2.0)"
+    },
     "node_modules/earcut": {
       "version": "2.2.4",
       "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
@@ -2692,6 +2937,12 @@
         "js-yaml": "bin/js-yaml.js"
       }
     },
+    "node_modules/hachure-fill": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
+      "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==",
+      "license": "MIT"
+    },
     "node_modules/has-flag": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3338,13 +3589,14 @@
       }
     },
     "node_modules/katex": {
-      "version": "0.16.8",
-      "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz",
-      "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==",
+      "version": "0.16.11",
+      "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz",
+      "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==",
       "funding": [
         "https://opencollective.com/katex",
         "https://github.com/sponsors/katex"
       ],
+      "license": "MIT",
       "dependencies": {
         "commander": "^8.3.0"
       },
@@ -3360,6 +3612,11 @@
         "node": ">= 12"
       }
     },
+    "node_modules/khroma": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
+      "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
+    },
     "node_modules/kind-of": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -3368,6 +3625,34 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/kolorist": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
+      "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
+      "license": "MIT"
+    },
+    "node_modules/langium": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/langium/-/langium-3.0.0.tgz",
+      "integrity": "sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==",
+      "license": "MIT",
+      "dependencies": {
+        "chevrotain": "~11.0.3",
+        "chevrotain-allstar": "~0.3.0",
+        "vscode-languageserver": "~9.0.1",
+        "vscode-languageserver-textdocument": "~1.0.11",
+        "vscode-uri": "~3.0.8"
+      },
+      "engines": {
+        "node": ">=16.0.0"
+      }
+    },
+    "node_modules/layout-base": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
+      "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
+      "license": "MIT"
+    },
     "node_modules/lightningcss": {
       "version": "1.28.1",
       "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.28.1.tgz",
@@ -3585,6 +3870,28 @@
         "url": "https://opencollective.com/parcel"
       }
     },
+    "node_modules/local-pkg": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
+      "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==",
+      "license": "MIT",
+      "dependencies": {
+        "mlly": "^1.4.2",
+        "pkg-types": "^1.0.3"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+      "license": "MIT"
+    },
     "node_modules/longest-streak": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
@@ -3611,6 +3918,18 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/marked": {
+      "version": "13.0.3",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz",
+      "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==",
+      "license": "MIT",
+      "bin": {
+        "marked": "bin/marked.js"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
+    },
     "node_modules/mathjax-full": {
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz",
@@ -4037,6 +4356,35 @@
         "node": ">= 8"
       }
     },
+    "node_modules/mermaid": {
+      "version": "11.4.0",
+      "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.4.0.tgz",
+      "integrity": "sha512-mxCfEYvADJqOiHfGpJXLs4/fAjHz448rH0pfY5fAoxiz70rQiDSzUUy4dNET2T08i46IVpjohPd6WWbzmRHiPA==",
+      "license": "MIT",
+      "dependencies": {
+        "@braintree/sanitize-url": "^7.0.1",
+        "@iconify/utils": "^2.1.32",
+        "@mermaid-js/parser": "^0.3.0",
+        "@types/d3": "^7.4.3",
+        "@types/dompurify": "^3.0.5",
+        "cytoscape": "^3.29.2",
+        "cytoscape-cose-bilkent": "^4.1.0",
+        "cytoscape-fcose": "^2.2.0",
+        "d3": "^7.9.0",
+        "d3-sankey": "^0.12.3",
+        "dagre-d3-es": "7.0.11",
+        "dayjs": "^1.11.10",
+        "dompurify": "^3.0.11 <3.1.7",
+        "katex": "^0.16.9",
+        "khroma": "^2.1.0",
+        "lodash-es": "^4.17.21",
+        "marked": "^13.0.2",
+        "roughjs": "^4.6.6",
+        "stylis": "^4.3.1",
+        "ts-dedent": "^2.2.0",
+        "uuid": "^9.0.1"
+      }
+    },
     "node_modules/mhchemparser": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz",
@@ -4673,15 +5021,28 @@
       "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
       "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
     },
+    "node_modules/mlly": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.2.tgz",
+      "integrity": "sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==",
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.12.1",
+        "pathe": "^1.1.2",
+        "pkg-types": "^1.2.0",
+        "ufo": "^1.5.4"
+      }
+    },
     "node_modules/moo": {
       "version": "0.5.2",
       "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
       "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q=="
     },
     "node_modules/ms": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
     },
     "node_modules/nlcst-to-string": {
       "version": "4.0.0",
@@ -4749,6 +5110,12 @@
       "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
       "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw=="
     },
+    "node_modules/package-manager-detector": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.2.tgz",
+      "integrity": "sha512-VgXbyrSNsml4eHWIvxxG/nTL4wgybMTXCV2Un/+yEc3aDKKU6nQBZjbeP3Pl3qm9Qg92X/1ng4ffvCeD/zwHgg==",
+      "license": "MIT"
+    },
     "node_modules/parse-entities": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
@@ -4812,6 +5179,12 @@
         "url": "https://github.com/inikulin/parse5?sponsor=1"
       }
     },
+    "node_modules/path-data-parser": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz",
+      "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==",
+      "license": "MIT"
+    },
     "node_modules/path-is-inside": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
@@ -4861,6 +5234,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/pathe": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+      "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+      "license": "MIT"
+    },
     "node_modules/picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -4893,6 +5272,33 @@
         "parse-svg-path": "^0.1.2"
       }
     },
+    "node_modules/pkg-types": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz",
+      "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==",
+      "license": "MIT",
+      "dependencies": {
+        "confbox": "^0.1.8",
+        "mlly": "^1.7.2",
+        "pathe": "^1.1.2"
+      }
+    },
+    "node_modules/points-on-curve": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz",
+      "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==",
+      "license": "MIT"
+    },
+    "node_modules/points-on-path": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz",
+      "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==",
+      "license": "MIT",
+      "dependencies": {
+        "path-data-parser": "0.1.0",
+        "points-on-curve": "0.2.0"
+      }
+    },
     "node_modules/preact": {
       "version": "10.24.3",
       "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
@@ -5485,6 +5891,18 @@
       "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
       "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
     },
+    "node_modules/roughjs": {
+      "version": "4.6.6",
+      "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz",
+      "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==",
+      "license": "MIT",
+      "dependencies": {
+        "hachure-fill": "^0.5.2",
+        "path-data-parser": "^0.1.0",
+        "points-on-curve": "^0.2.0",
+        "points-on-path": "^0.2.1"
+      }
+    },
     "node_modules/rrweb-cssom": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
@@ -6208,6 +6626,12 @@
         "inline-style-parser": "0.2.2"
       }
     },
+    "node_modules/stylis": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz",
+      "integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==",
+      "license": "MIT"
+    },
     "node_modules/supports-color": {
       "version": "8.1.1",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -6251,6 +6675,12 @@
         "node": ">=14"
       }
     },
+    "node_modules/tinyexec": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz",
+      "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
+      "license": "MIT"
+    },
     "node_modules/to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -6338,6 +6768,15 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/ts-dedent": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
+      "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.10"
+      }
+    },
     "node_modules/tslib": {
       "version": "2.6.2",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
@@ -6782,6 +7221,12 @@
         "node": ">=14.17"
       }
     },
+    "node_modules/ufo": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
+      "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
+      "license": "MIT"
+    },
     "node_modules/undici-types": {
       "version": "6.19.8",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
@@ -6983,6 +7428,19 @@
         "requires-port": "^1.0.0"
       }
     },
+    "node_modules/uuid": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/varint": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
@@ -7050,6 +7508,55 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/vscode-jsonrpc": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
+      "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/vscode-languageserver": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
+      "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
+      "license": "MIT",
+      "dependencies": {
+        "vscode-languageserver-protocol": "3.17.5"
+      },
+      "bin": {
+        "installServerIntoExtension": "bin/installServerIntoExtension"
+      }
+    },
+    "node_modules/vscode-languageserver-protocol": {
+      "version": "3.17.5",
+      "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
+      "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
+      "license": "MIT",
+      "dependencies": {
+        "vscode-jsonrpc": "8.2.0",
+        "vscode-languageserver-types": "3.17.5"
+      }
+    },
+    "node_modules/vscode-languageserver-textdocument": {
+      "version": "1.0.12",
+      "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
+      "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
+      "license": "MIT"
+    },
+    "node_modules/vscode-languageserver-types": {
+      "version": "3.17.5",
+      "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
+      "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
+      "license": "MIT"
+    },
+    "node_modules/vscode-uri": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
+      "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
+      "license": "MIT"
+    },
     "node_modules/w3c-xmlserializer": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
diff --git a/package.json b/package.json
index abe6de1..3956afc 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,7 @@
     "mdast-util-find-and-replace": "^3.0.1",
     "mdast-util-to-hast": "^13.2.0",
     "mdast-util-to-string": "^4.0.0",
+    "mermaid": "^11.4.0",
     "micromorph": "^0.4.5",
     "pixi.js": "^8.5.2",
     "preact": "^10.24.3",
diff --git a/quartz/components/scripts/clipboard.inline.ts b/quartz/components/scripts/clipboard.inline.ts
index 87182a1..e16c112 100644
--- a/quartz/components/scripts/clipboard.inline.ts
+++ b/quartz/components/scripts/clipboard.inline.ts
@@ -8,7 +8,9 @@
   for (let i = 0; i < els.length; i++) {
     const codeBlock = els[i].getElementsByTagName("code")[0]
     if (codeBlock) {
-      const source = codeBlock.innerText.replace(/\n\n/g, "\n")
+      const source = (
+        codeBlock.dataset.clipboard ? JSON.parse(codeBlock.dataset.clipboard) : codeBlock.innerText
+      ).replace(/\n\n/g, "\n")
       const button = document.createElement("button")
       button.className = "clipboard-button"
       button.type = "button"
diff --git a/quartz/components/scripts/mermaid.inline.ts b/quartz/components/scripts/mermaid.inline.ts
new file mode 100644
index 0000000..77a3ebe
--- /dev/null
+++ b/quartz/components/scripts/mermaid.inline.ts
@@ -0,0 +1,242 @@
+import { removeAllChildren } from "./util"
+import mermaid from "mermaid"
+
+interface Position {
+  x: number
+  y: number
+}
+
+class DiagramPanZoom {
+  private isDragging = false
+  private startPan: Position = { x: 0, y: 0 }
+  private currentPan: Position = { x: 0, y: 0 }
+  private scale = 1
+  private readonly MIN_SCALE = 0.5
+  private readonly MAX_SCALE = 3
+  private readonly ZOOM_SENSITIVITY = 0.001
+
+  constructor(
+    private container: HTMLElement,
+    private content: HTMLElement,
+  ) {
+    this.setupEventListeners()
+    this.setupNavigationControls()
+  }
+
+  private setupEventListeners() {
+    // Mouse drag events
+    this.container.addEventListener("mousedown", this.onMouseDown.bind(this))
+    document.addEventListener("mousemove", this.onMouseMove.bind(this))
+    document.addEventListener("mouseup", this.onMouseUp.bind(this))
+
+    // Wheel zoom events
+    this.container.addEventListener("wheel", this.onWheel.bind(this), { passive: false })
+
+    // Reset on window resize
+    window.addEventListener("resize", this.resetTransform.bind(this))
+  }
+
+  private setupNavigationControls() {
+    const controls = document.createElement("div")
+    controls.className = "mermaid-controls"
+
+    // Zoom controls
+    const zoomIn = this.createButton("+", () => this.zoom(0.1))
+    const zoomOut = this.createButton("-", () => this.zoom(-0.1))
+    const resetBtn = this.createButton("Reset", () => this.resetTransform())
+
+    controls.appendChild(zoomOut)
+    controls.appendChild(resetBtn)
+    controls.appendChild(zoomIn)
+
+    this.container.appendChild(controls)
+  }
+
+  private createButton(text: string, onClick: () => void): HTMLButtonElement {
+    const button = document.createElement("button")
+    button.textContent = text
+    button.className = "mermaid-control-button"
+    button.addEventListener("click", onClick)
+    window.addCleanup(() => button.removeEventListener("click", onClick))
+    return button
+  }
+
+  private onMouseDown(e: MouseEvent) {
+    if (e.button !== 0) return // Only handle left click
+    this.isDragging = true
+    this.startPan = { x: e.clientX - this.currentPan.x, y: e.clientY - this.currentPan.y }
+    this.container.style.cursor = "grabbing"
+  }
+
+  private onMouseMove(e: MouseEvent) {
+    if (!this.isDragging) return
+    e.preventDefault()
+
+    this.currentPan = {
+      x: e.clientX - this.startPan.x,
+      y: e.clientY - this.startPan.y,
+    }
+
+    this.updateTransform()
+  }
+
+  private onMouseUp() {
+    this.isDragging = false
+    this.container.style.cursor = "grab"
+  }
+
+  private onWheel(e: WheelEvent) {
+    e.preventDefault()
+
+    const delta = -e.deltaY * this.ZOOM_SENSITIVITY
+    const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
+
+    // Calculate mouse position relative to content
+    const rect = this.content.getBoundingClientRect()
+    const mouseX = e.clientX - rect.left
+    const mouseY = e.clientY - rect.top
+
+    // Adjust pan to zoom around mouse position
+    const scaleDiff = newScale - this.scale
+    this.currentPan.x -= mouseX * scaleDiff
+    this.currentPan.y -= mouseY * scaleDiff
+
+    this.scale = newScale
+    this.updateTransform()
+  }
+
+  private zoom(delta: number) {
+    const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
+
+    // Zoom around center
+    const rect = this.content.getBoundingClientRect()
+    const centerX = rect.width / 2
+    const centerY = rect.height / 2
+
+    const scaleDiff = newScale - this.scale
+    this.currentPan.x -= centerX * scaleDiff
+    this.currentPan.y -= centerY * scaleDiff
+
+    this.scale = newScale
+    this.updateTransform()
+  }
+
+  private updateTransform() {
+    this.content.style.transform = `translate(${this.currentPan.x}px, ${this.currentPan.y}px) scale(${this.scale})`
+  }
+
+  private resetTransform() {
+    this.scale = 1
+    this.currentPan = { x: 0, y: 0 }
+    this.updateTransform()
+  }
+}
+
+const cssVars = [
+  "--secondary",
+  "--tertiary",
+  "--gray",
+  "--light",
+  "--lightgray",
+  "--highlight",
+  "--dark",
+  "--darkgray",
+  "--codeFont",
+] as const
+
+document.addEventListener("nav", async () => {
+  const center = document.querySelector(".center") as HTMLElement
+  const nodes = center.querySelectorAll("code.mermaid") as NodeListOf<HTMLElement>
+  if (nodes.length === 0) return
+
+  const computedStyleMap = cssVars.reduce(
+    (acc, key) => {
+      acc[key] = getComputedStyle(document.documentElement).getPropertyValue(key)
+      return acc
+    },
+    {} as Record<(typeof cssVars)[number], string>,
+  )
+
+  const darkMode = document.documentElement.getAttribute("saved-theme") === "dark"
+  mermaid.initialize({
+    startOnLoad: false,
+    securityLevel: "loose",
+    theme: darkMode ? "dark" : "base",
+    themeVariables: {
+      fontFamily: computedStyleMap["--codeFont"],
+      primaryColor: computedStyleMap["--light"],
+      primaryTextColor: computedStyleMap["--darkgray"],
+      primaryBorderColor: computedStyleMap["--tertiary"],
+      lineColor: computedStyleMap["--darkgray"],
+      secondaryColor: computedStyleMap["--secondary"],
+      tertiaryColor: computedStyleMap["--tertiary"],
+      clusterBkg: computedStyleMap["--light"],
+      edgeLabelBackground: computedStyleMap["--highlight"],
+    },
+  })
+  await mermaid.run({ nodes })
+
+  for (let i = 0; i < nodes.length; i++) {
+    const codeBlock = nodes[i] as HTMLElement
+    const pre = codeBlock.parentElement as HTMLPreElement
+    const clipboardBtn = pre.querySelector(".clipboard-button") as HTMLButtonElement
+    const expandBtn = pre.querySelector(".expand-button") as HTMLButtonElement
+
+    const clipboardStyle = window.getComputedStyle(clipboardBtn)
+    const clipboardWidth =
+      clipboardBtn.offsetWidth +
+      parseFloat(clipboardStyle.marginLeft || "0") +
+      parseFloat(clipboardStyle.marginRight || "0")
+
+    // Set expand button position
+    expandBtn.style.right = `calc(${clipboardWidth}px + 0.3rem)`
+    pre.prepend(expandBtn)
+
+    // query popup container
+    const popupContainer = pre.querySelector("#mermaid-container") as HTMLElement
+    if (!popupContainer) return
+
+    let panZoom: DiagramPanZoom | null = null
+
+    function showMermaid() {
+      const container = popupContainer.querySelector("#mermaid-space") as HTMLElement
+      const content = popupContainer.querySelector(".mermaid-content") as HTMLElement
+      if (!content) return
+      removeAllChildren(content)
+
+      // Clone the mermaid content
+      const mermaidContent = codeBlock.querySelector("svg")!.cloneNode(true) as SVGElement
+      content.appendChild(mermaidContent)
+
+      // Show container
+      popupContainer.classList.add("active")
+      container.style.cursor = "grab"
+
+      // Initialize pan-zoom after showing the popup
+      panZoom = new DiagramPanZoom(container, content)
+    }
+
+    function hideMermaid() {
+      popupContainer.classList.remove("active")
+      panZoom = null
+    }
+
+    function handleEscape(e: any) {
+      if (e.key === "Escape") {
+        hideMermaid()
+      }
+    }
+
+    const closeBtn = popupContainer.querySelector(".close-button") as HTMLButtonElement
+
+    closeBtn.addEventListener("click", hideMermaid)
+    expandBtn.addEventListener("click", showMermaid)
+    document.addEventListener("keydown", handleEscape)
+
+    window.addCleanup(() => {
+      closeBtn.removeEventListener("click", hideMermaid)
+      expandBtn.removeEventListener("click", showMermaid)
+      document.removeEventListener("keydown", handleEscape)
+    })
+  }
+})
diff --git a/quartz/components/styles/mermaid.inline.scss b/quartz/components/styles/mermaid.inline.scss
new file mode 100644
index 0000000..79a1c84
--- /dev/null
+++ b/quartz/components/styles/mermaid.inline.scss
@@ -0,0 +1,163 @@
+.expand-button {
+  position: absolute;
+  display: flex;
+  float: right;
+  padding: 0.4rem;
+  margin: 0.3rem;
+  right: 0; // NOTE: right will be set in mermaid.inline.ts
+  color: var(--gray);
+  border-color: var(--dark);
+  background-color: var(--light);
+  border: 1px solid;
+  border-radius: 5px;
+  opacity: 0;
+  transition: 0.2s;
+
+  & > svg {
+    fill: var(--light);
+    filter: contrast(0.3);
+  }
+
+  &:hover {
+    cursor: pointer;
+    border-color: var(--secondary);
+  }
+
+  &:focus {
+    outline: 0;
+  }
+}
+
+pre {
+  &:hover > .expand-button {
+    opacity: 1;
+    transition: 0.2s;
+  }
+}
+
+#mermaid-container {
+  position: fixed;
+  contain: layout;
+  z-index: 999;
+  left: 0;
+  top: 0;
+  width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+  display: none;
+  backdrop-filter: blur(4px);
+  background: rgba(0, 0, 0, 0.5);
+
+  &.active {
+    display: inline-block;
+  }
+
+  & > #mermaid-space {
+    display: grid;
+    width: 90%;
+    height: 90vh;
+    margin: 5vh auto;
+    background: var(--light);
+    box-shadow:
+      0 14px 50px rgba(27, 33, 48, 0.12),
+      0 10px 30px rgba(27, 33, 48, 0.16);
+    overflow: hidden;
+    position: relative;
+
+    & > .mermaid-header {
+      display: flex;
+      justify-content: flex-end;
+      padding: 1rem;
+      border-bottom: 1px solid var(--lightgray);
+      background: var(--light);
+      z-index: 2;
+      max-height: fit-content;
+
+      & > .close-button {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 32px;
+        height: 32px;
+        padding: 0;
+        background: transparent;
+        border: none;
+        border-radius: 4px;
+        color: var(--darkgray);
+        cursor: pointer;
+        transition: all 0.2s ease;
+
+        &:hover {
+          background: var(--lightgray);
+          color: var(--dark);
+        }
+      }
+    }
+
+    & > .mermaid-content {
+      padding: 2rem;
+      position: relative;
+      transform-origin: 0 0;
+      transition: transform 0.1s ease;
+      overflow: visible;
+      min-height: 200px;
+      min-width: 200px;
+
+      pre {
+        margin: 0;
+        border: none;
+      }
+
+      svg {
+        max-width: none;
+        height: auto;
+      }
+    }
+
+    & > .mermaid-controls {
+      position: absolute;
+      bottom: 20px;
+      right: 20px;
+      display: flex;
+      gap: 8px;
+      padding: 8px;
+      background: var(--light);
+      border: 1px solid var(--lightgray);
+      border-radius: 6px;
+      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+      z-index: 2;
+
+      .mermaid-control-button {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 32px;
+        height: 32px;
+        padding: 0;
+        border: 1px solid var(--lightgray);
+        background: var(--light);
+        color: var(--dark);
+        border-radius: 4px;
+        cursor: pointer;
+        font-size: 16px;
+        font-family: var(--bodyFont);
+        transition: all 0.2s ease;
+
+        &:hover {
+          background: var(--lightgray);
+        }
+
+        &:active {
+          transform: translateY(1px);
+        }
+
+        // Style the reset button differently
+        &:nth-child(2) {
+          width: auto;
+          padding: 0 12px;
+          font-size: 14px;
+        }
+      }
+    }
+  }
+}
diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts
index 70dce60..ab626bf 100644
--- a/quartz/plugins/transformers/ofm.ts
+++ b/quartz/plugins/transformers/ofm.ts
@@ -6,11 +6,14 @@
 import { SKIP, visit } from "unist-util-visit"
 import path from "path"
 import { splitAnchor } from "../../util/path"
-import { JSResource } from "../../util/resources"
+import { JSResource, CSSResource } from "../../util/resources"
 // @ts-ignore
 import calloutScript from "../../components/scripts/callout.inline.ts"
 // @ts-ignore
 import checkboxScript from "../../components/scripts/checkbox.inline.ts"
+// @ts-ignore
+import mermaidExtensionScript from "../../components/scripts/mermaid.inline.ts"
+import mermaidStyle from "../../components/styles/mermaid.inline.scss"
 import { FilePath, pathToRoot, slugTag, slugifyFilePath } from "../../util/path"
 import { toHast } from "mdast-util-to-hast"
 import { toHtml } from "hast-util-to-html"
@@ -279,6 +282,7 @@
 
                 // internal link
                 const url = fp + anchor
+
                 return {
                   type: "link",
                   url,
@@ -515,6 +519,7 @@
                 node.data = {
                   hProperties: {
                     className: ["mermaid"],
+                    "data-clipboard": JSON.stringify(node.value),
                   },
                 }
               }
@@ -659,10 +664,138 @@
         })
       }
 
+      if (opts.mermaid) {
+        plugins.push(() => {
+          return (tree: HtmlRoot, _file) => {
+            visit(tree, "element", (node: Element, _idx, parent) => {
+              if (
+                node.tagName === "code" &&
+                ((node.properties?.className ?? []) as string[])?.includes("mermaid")
+              ) {
+                parent!.children = [
+                  {
+                    type: "element",
+                    tagName: "button",
+                    properties: {
+                      className: ["expand-button"],
+                      "aria-label": "Expand mermaid diagram",
+                      "aria-hidden": "true",
+                      "data-view-component": true,
+                    },
+                    children: [
+                      {
+                        type: "element",
+                        tagName: "svg",
+                        properties: {
+                          width: 16,
+                          height: 16,
+                          viewBox: "0 0 16 16",
+                          fill: "currentColor",
+                        },
+                        children: [
+                          {
+                            type: "element",
+                            tagName: "path",
+                            properties: {
+                              fillRule: "evenodd",
+                              d: "M3.72 3.72a.75.75 0 011.06 1.06L2.56 7h10.88l-2.22-2.22a.75.75 0 011.06-1.06l3.5 3.5a.75.75 0 010 1.06l-3.5 3.5a.75.75 0 11-1.06-1.06l2.22-2.22H2.56l2.22 2.22a.75.75 0 11-1.06 1.06l-3.5-3.5a.75.75 0 010-1.06l3.5-3.5z",
+                            },
+                            children: [],
+                          },
+                        ],
+                      },
+                    ],
+                  },
+                  node,
+                  {
+                    type: "element",
+                    tagName: "div",
+                    properties: { id: "mermaid-container" },
+                    children: [
+                      {
+                        type: "element",
+                        tagName: "div",
+                        properties: { id: "mermaid-space" },
+                        children: [
+                          {
+                            type: "element",
+                            tagName: "div",
+                            properties: { className: ["mermaid-header"] },
+                            children: [
+                              {
+                                type: "element",
+                                tagName: "button",
+                                properties: {
+                                  className: ["close-button"],
+                                  "aria-label": "close button",
+                                },
+                                children: [
+                                  {
+                                    type: "element",
+                                    tagName: "svg",
+                                    properties: {
+                                      "aria-hidden": "true",
+                                      xmlns: "http://www.w3.org/2000/svg",
+                                      width: 24,
+                                      height: 24,
+                                      viewBox: "0 0 24 24",
+                                      fill: "none",
+                                      stroke: "currentColor",
+                                      "stroke-width": "2",
+                                      "stroke-linecap": "round",
+                                      "stroke-linejoin": "round",
+                                    },
+                                    children: [
+                                      {
+                                        type: "element",
+                                        tagName: "line",
+                                        properties: {
+                                          x1: 18,
+                                          y1: 6,
+                                          x2: 6,
+                                          y2: 18,
+                                        },
+                                        children: [],
+                                      },
+                                      {
+                                        type: "element",
+                                        tagName: "line",
+                                        properties: {
+                                          x1: 6,
+                                          y1: 6,
+                                          x2: 18,
+                                          y2: 18,
+                                        },
+                                        children: [],
+                                      },
+                                    ],
+                                  },
+                                ],
+                              },
+                            ],
+                          },
+                          {
+                            type: "element",
+                            tagName: "div",
+                            properties: { className: ["mermaid-content"] },
+                            children: [],
+                          },
+                        ],
+                      },
+                    ],
+                  },
+                ]
+              }
+            })
+          }
+        })
+      }
+
       return plugins
     },
     externalResources() {
       const js: JSResource[] = []
+      const css: CSSResource[] = []
 
       if (opts.enableCheckbox) {
         js.push({
@@ -682,32 +815,18 @@
 
       if (opts.mermaid) {
         js.push({
-          script: `
-          let mermaidImport = undefined
-          document.addEventListener('nav', async () => {
-            if (document.querySelector("code.mermaid")) {
-              mermaidImport ||= await import('https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.7.0/mermaid.esm.min.mjs')
-              const mermaid = mermaidImport.default
-              const darkMode = document.documentElement.getAttribute('saved-theme') === 'dark'
-              mermaid.initialize({
-                startOnLoad: false,
-                securityLevel: 'loose',
-                theme: darkMode ? 'dark' : 'default'
-              })
-
-              await mermaid.run({
-                querySelector: '.mermaid'
-              })
-            }
-          });
-          `,
+          script: mermaidExtensionScript,
           loadTime: "afterDOMReady",
           moduleType: "module",
           contentType: "inline",
         })
+        css.push({
+          content: mermaidStyle,
+          inline: true,
+        })
       }
 
-      return { js }
+      return { js, css }
     },
   }
 }

--
Gitblit v1.10.0