diff --git a/package-lock.json b/package-lock.json
index 9f0cbc88b7ecfd26ebbbdc8a058424f6fdf6242c..10a74150a4155aacf85938351a2b321ec7cb22b0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,9 +4,7 @@
 	"requires": true,
 	"packages": {
 		"": {
-			"dependencies": {
-				"dompurify": "^3.1.0"
-			},
+			"name": "adorsaz.ch",
 			"devDependencies": {
 				"@eslint/eslintrc": "^3.1.0",
 				"@eslint/js": "^9.10.0",
@@ -275,21 +273,21 @@
 			}
 		},
 		"node_modules/@eslint-community/regexpp": {
-			"version": "4.11.0",
-			"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
-			"integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
+			"version": "4.12.1",
+			"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+			"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
 			"dev": true,
 			"engines": {
 				"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
 			}
 		},
 		"node_modules/@eslint/config-array": {
-			"version": "0.18.0",
-			"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
-			"integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
+			"version": "0.19.2",
+			"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz",
+			"integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==",
 			"dev": true,
 			"dependencies": {
-				"@eslint/object-schema": "^2.1.4",
+				"@eslint/object-schema": "^2.1.6",
 				"debug": "^4.3.1",
 				"minimatch": "^3.1.2"
 			},
@@ -297,10 +295,31 @@
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 			}
 		},
+		"node_modules/@eslint/config-helpers": {
+			"version": "0.2.1",
+			"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz",
+			"integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==",
+			"dev": true,
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			}
+		},
+		"node_modules/@eslint/core": {
+			"version": "0.12.0",
+			"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
+			"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
+			"dev": true,
+			"dependencies": {
+				"@types/json-schema": "^7.0.15"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			}
+		},
 		"node_modules/@eslint/eslintrc": {
-			"version": "3.1.0",
-			"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
-			"integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
+			"version": "3.3.1",
+			"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+			"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
 			"dev": true,
 			"dependencies": {
 				"ajv": "^6.12.4",
@@ -333,35 +352,83 @@
 			}
 		},
 		"node_modules/@eslint/js": {
-			"version": "9.10.0",
-			"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
-			"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
+			"version": "9.23.0",
+			"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz",
+			"integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 			}
 		},
 		"node_modules/@eslint/object-schema": {
-			"version": "2.1.4",
-			"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
-			"integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
+			"version": "2.1.6",
+			"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+			"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 			}
 		},
 		"node_modules/@eslint/plugin-kit": {
-			"version": "0.1.0",
-			"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
-			"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
+			"version": "0.2.8",
+			"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
+			"integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
 			"dev": true,
 			"dependencies": {
+				"@eslint/core": "^0.13.0",
 				"levn": "^0.4.1"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 			}
 		},
+		"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
+			"version": "0.13.0",
+			"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
+			"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
+			"dev": true,
+			"dependencies": {
+				"@types/json-schema": "^7.0.15"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			}
+		},
+		"node_modules/@humanfs/core": {
+			"version": "0.19.1",
+			"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+			"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+			"dev": true,
+			"engines": {
+				"node": ">=18.18.0"
+			}
+		},
+		"node_modules/@humanfs/node": {
+			"version": "0.16.6",
+			"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+			"integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+			"dev": true,
+			"dependencies": {
+				"@humanfs/core": "^0.19.1",
+				"@humanwhocodes/retry": "^0.3.0"
+			},
+			"engines": {
+				"node": ">=18.18.0"
+			}
+		},
+		"node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+			"version": "0.3.1",
+			"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+			"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+			"dev": true,
+			"engines": {
+				"node": ">=18.18"
+			},
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/nzakas"
+			}
+		},
 		"node_modules/@humanwhocodes/gitignore-to-minimatch": {
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz",
@@ -386,9 +453,9 @@
 			}
 		},
 		"node_modules/@humanwhocodes/retry": {
-			"version": "0.3.0",
-			"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz",
-			"integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==",
+			"version": "0.4.2",
+			"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
+			"integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
 			"dev": true,
 			"engines": {
 				"node": ">=18.18"
@@ -742,9 +809,9 @@
 			}
 		},
 		"node_modules/@types/estree": {
-			"version": "1.0.5",
-			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
-			"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+			"version": "1.0.7",
+			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
+			"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
 			"dev": true
 		},
 		"node_modules/@types/istanbul-lib-coverage": {
@@ -1023,9 +1090,9 @@
 			}
 		},
 		"node_modules/acorn": {
-			"version": "8.12.1",
-			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
-			"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
+			"version": "8.14.1",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+			"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
 			"dev": true,
 			"bin": {
 				"acorn": "bin/acorn"
@@ -1591,9 +1658,9 @@
 			}
 		},
 		"node_modules/cross-spawn": {
-			"version": "7.0.3",
-			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
-			"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+			"version": "7.0.6",
+			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+			"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
 			"dev": true,
 			"dependencies": {
 				"path-key": "^3.1.0",
@@ -1909,11 +1976,6 @@
 				"url": "https://github.com/fb55/domhandler?sponsor=1"
 			}
 		},
-		"node_modules/dompurify": {
-			"version": "3.1.6",
-			"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
-			"integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="
-		},
 		"node_modules/domutils": {
 			"version": "3.1.0",
 			"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
@@ -2163,28 +2225,32 @@
 			}
 		},
 		"node_modules/eslint": {
-			"version": "9.10.0",
-			"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
-			"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
+			"version": "9.23.0",
+			"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz",
+			"integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==",
 			"dev": true,
 			"dependencies": {
 				"@eslint-community/eslint-utils": "^4.2.0",
-				"@eslint-community/regexpp": "^4.11.0",
-				"@eslint/config-array": "^0.18.0",
-				"@eslint/eslintrc": "^3.1.0",
-				"@eslint/js": "9.10.0",
-				"@eslint/plugin-kit": "^0.1.0",
+				"@eslint-community/regexpp": "^4.12.1",
+				"@eslint/config-array": "^0.19.2",
+				"@eslint/config-helpers": "^0.2.0",
+				"@eslint/core": "^0.12.0",
+				"@eslint/eslintrc": "^3.3.1",
+				"@eslint/js": "9.23.0",
+				"@eslint/plugin-kit": "^0.2.7",
+				"@humanfs/node": "^0.16.6",
 				"@humanwhocodes/module-importer": "^1.0.1",
-				"@humanwhocodes/retry": "^0.3.0",
-				"@nodelib/fs.walk": "^1.2.8",
+				"@humanwhocodes/retry": "^0.4.2",
+				"@types/estree": "^1.0.6",
+				"@types/json-schema": "^7.0.15",
 				"ajv": "^6.12.4",
 				"chalk": "^4.0.0",
-				"cross-spawn": "^7.0.2",
+				"cross-spawn": "^7.0.6",
 				"debug": "^4.3.2",
 				"escape-string-regexp": "^4.0.0",
-				"eslint-scope": "^8.0.2",
-				"eslint-visitor-keys": "^4.0.0",
-				"espree": "^10.1.0",
+				"eslint-scope": "^8.3.0",
+				"eslint-visitor-keys": "^4.2.0",
+				"espree": "^10.3.0",
 				"esquery": "^1.5.0",
 				"esutils": "^2.0.2",
 				"fast-deep-equal": "^3.1.3",
@@ -2194,14 +2260,11 @@
 				"ignore": "^5.2.0",
 				"imurmurhash": "^0.1.4",
 				"is-glob": "^4.0.0",
-				"is-path-inside": "^3.0.3",
 				"json-stable-stringify-without-jsonify": "^1.0.1",
 				"lodash.merge": "^4.6.2",
 				"minimatch": "^3.1.2",
 				"natural-compare": "^1.4.0",
-				"optionator": "^0.9.3",
-				"strip-ansi": "^6.0.1",
-				"text-table": "^0.2.0"
+				"optionator": "^0.9.3"
 			},
 			"bin": {
 				"eslint": "bin/eslint.js"
@@ -2501,9 +2564,9 @@
 			}
 		},
 		"node_modules/eslint-scope": {
-			"version": "8.0.2",
-			"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
-			"integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==",
+			"version": "8.3.0",
+			"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
+			"integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
 			"dev": true,
 			"dependencies": {
 				"esrecurse": "^4.3.0",
@@ -2529,9 +2592,9 @@
 			}
 		},
 		"node_modules/eslint/node_modules/eslint-visitor-keys": {
-			"version": "4.0.0",
-			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
-			"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
+			"version": "4.2.0",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+			"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2541,14 +2604,14 @@
 			}
 		},
 		"node_modules/espree": {
-			"version": "10.1.0",
-			"resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
-			"integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
+			"version": "10.3.0",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+			"integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
 			"dev": true,
 			"dependencies": {
-				"acorn": "^8.12.0",
+				"acorn": "^8.14.0",
 				"acorn-jsx": "^5.3.2",
-				"eslint-visitor-keys": "^4.0.0"
+				"eslint-visitor-keys": "^4.2.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2558,9 +2621,9 @@
 			}
 		},
 		"node_modules/espree/node_modules/eslint-visitor-keys": {
-			"version": "4.0.0",
-			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
-			"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
+			"version": "4.2.0",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+			"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3624,15 +3687,6 @@
 				"url": "https://github.com/sponsors/ljharb"
 			}
 		},
-		"node_modules/is-path-inside": {
-			"version": "3.0.3",
-			"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
-			"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
-			"dev": true,
-			"engines": {
-				"node": ">=8"
-			}
-		},
 		"node_modules/is-plain-obj": {
 			"version": "1.1.0",
 			"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
@@ -4427,9 +4481,9 @@
 			}
 		},
 		"node_modules/nanoid": {
-			"version": "3.3.7",
-			"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
-			"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+			"version": "3.3.11",
+			"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+			"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
 			"dev": true,
 			"funding": [
 				{
@@ -5976,12 +6030,6 @@
 				"node": ">= 16"
 			}
 		},
-		"node_modules/text-table": {
-			"version": "0.2.0",
-			"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-			"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
-			"dev": true
-		},
 		"node_modules/theredoc": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/theredoc/-/theredoc-1.0.0.tgz",
diff --git a/package.json b/package.json
index 6c9bfba6e75089c199495dd8c5f76360634374fa..189f0f07486b5973db666fc3141655c7bb76569d 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,8 @@
 			"author": {
 				"name": "Adrien Dorsaz",
 				"email": "adrien@adorsaz.ch",
-				"url": "https://adorsaz.ch"
+				"url": "https://adorsaz.ch",
+				"fediverse": "@adrien@adorsaz.ch"
 			}
 		}
 	},
@@ -28,9 +29,6 @@
 		"test:javascript": "mocha",
 		"coverage:javascript": "c8 mocha"
 	},
-	"dependencies": {
-		"dompurify": "^3.1.0"
-	},
 	"devDependencies": {
 		"@eslint/eslintrc": "^3.1.0",
 		"@eslint/js": "^9.10.0",
diff --git a/src/articles/a-propos-d-ipv4-et-d-ipv6.html b/src/articles/a-propos-d-ipv4-et-d-ipv6.html
index b98adc17d45a81fa02ced5af51363b0b07866b56..75c6d03a6ed32118609222011d9de707ebe92904 100644
--- a/src/articles/a-propos-d-ipv4-et-d-ipv6.html
+++ b/src/articles/a-propos-d-ipv4-et-d-ipv6.html
@@ -40,6 +40,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
diff --git a/src/articles/astuces-pour-ecrire-des-scripts-shell-et-des-definitions-de-conteneurs.html b/src/articles/astuces-pour-ecrire-des-scripts-shell-et-des-definitions-de-conteneurs.html
index 6e9289db5e37e38ba2520f168a816e6afe3c3153..68ac7493fbfd29b569516202a44acc338d292738 100644
--- a/src/articles/astuces-pour-ecrire-des-scripts-shell-et-des-definitions-de-conteneurs.html
+++ b/src/articles/astuces-pour-ecrire-des-scripts-shell-et-des-definitions-de-conteneurs.html
@@ -15,12 +15,6 @@
 			media="screen"
 			href="../css/screen.css"
 		/>
-		<link
-			rel="stylesheet"
-			type="text/css"
-			media="screen"
-			href="../css/comment.css"
-		/>
 		<link
 			rel="stylesheet"
 			type="text/css"
@@ -44,6 +38,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
@@ -60,11 +55,10 @@
 			content="https://gitlab.adorsaz.ch/adrien/adorsaz.ch/-/commits/main/src/articles/astuces-pour-ecrire-des-scripts-shell-et-des-definitions-de-conteneurs.html"
 		/>
 		<meta
-			name="mastodon-comment-url"
+			name="mastodon-share-url"
 			content="https://mastodon.adorsaz.ch/@adrien/111953486356181833"
 		/>
 		<script type="module" src="../javascript/article.js"></script>
-		<script type="module" src="../javascript/comment.js"></script>
 		<script type="module" src="../javascript/header.js"></script>
 	</head>
 
@@ -242,26 +236,50 @@ EOF</code>
 </code>
 </pre>
 
-			<section id="comments">
-				<h2>Commentaires</h2>
-				<p>
-					Répondez à
-					<a href="https://mastodon.adorsaz.ch/@adrien/111953486356181833"
-						>ce message</a
-					>
-					du
-					<a href="https://fr.wikipedia.org/wiki/Fediverse">Fediverse</a> pour
-					ajouter un commentaire sur cet article.
-				</p>
+			<section id="notes">
+				<h2>Notes</h2>
+				<article class="note">
+					<h3>
+						<time datetime="2024-03-27T21:08:52.287Z"
+							>27 mars 2024 à 22:08</time
+						>
+					</h3>
+					<p>
+						Si jamais, la fonctionnalité "heredoc" dans les Containerfile est
+						arrivée pour podman dans son moteur de build <i>buildah</i> version
+						v1.33.0.
+					</p>
+					<p>
+						Il est actuellement disponible dans Debian trixie (testing) et
+						Fedora 39 si jamais.
+					</p>
+				</article>
 
-				<div id="mastodon-comments">
-					<noscript
-						><p>
-							Les réponses du Fediverse seront affichées ici si vous avctivez le
-							moteur JavaScript de votre navigateur web.
-						</p>
-					</noscript>
-				</div>
+				<article class="note">
+					<h3>
+						<time datetime="2024-04-26T12:06:33.230Z"
+							>26 avril 2024 à 14:06</time
+						>
+					</h3>
+					<p>
+						En cherchant un marque-pages dans mes favoris de Mastodon, je viens
+						de me rendre compte que j'avais ajouté en janvier ce message qui
+						partageait déjà des astuces pour les scripts :
+						<a href="https://framapiaf.org/@sebsauvage/111719827048771422"
+							>https://framapiaf.org/@sebsauvage/111719827048771422</a
+						>
+					</p>
+					<p>
+						Lien direct vers l'article partagé :
+						<a href="https://sharats.me/posts/shell-script-best-practices/"
+							>https://sharats.me/posts/shell-script-best-practices/</a
+						>
+					</p>
+					<p>
+						Il y a quelques points en plus intéressants (notamment, l'affichage
+						de l'aide) et un modèle prêt à être utilisé.
+					</p>
+				</article>
 			</section>
 		</article>
 	</body>
diff --git a/src/articles/blog.atom b/src/articles/blog.atom
index b3c83808ef238eafe3fa5a2afbb2048eb4df3dd4..557811179bc1b969c12628381a84267c8e41314d 100644
--- a/src/articles/blog.atom
+++ b/src/articles/blog.atom
@@ -17,7 +17,7 @@
   </author>
   <rights
 	>Licence CC BY-SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr</rights>
-  <updated>2024-09-17T21:29:57.801Z</updated>
+  <updated>2025-04-04T19:55:04.470Z</updated>
 
   <entry>
     <title
@@ -303,7 +303,7 @@ $ sudo apt install debootstrap&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ensuite, je déb
 				Avant d’entrer dans le contexte de l’installation (&lt;i&gt;chroot&lt;/i&gt;), je
 				copie l’état actuel des points de montage qui ciblent le dossier
 				&lt;i class&#x3D;&quot;text-no-wrap&quot;&gt;/mnt&lt;/i&gt; du système autonome dans le fichier
-				&lt;i&gt;fstab&lt;/i&gt; du &lt;i&gt;chroot&lt;/i&gt; (ce fichier a été crée par
+				&lt;i&gt;fstab&lt;/i&gt; du &lt;i&gt;chroot&lt;/i&gt; (ce fichier a été créé par
 				&lt;i&gt;debootstrap&lt;/i&gt;)&amp;nbsp;:
 			&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ grep /mnt /etc/mtab | sudo tee -a /mnt/etc/fstab&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
 				Pour la partie «&amp;nbsp;&lt;i&gt;Créer les fichiers des périphériques&lt;/i&gt;&amp;nbsp;» du manuel
@@ -671,17 +671,6 @@ $ sudo systemctl enable --now systemd-boot-sign.path&lt;/code&gt;&lt;/pre&gt;&lt
 						&lt;/p&gt;
 					&lt;/li&gt;
 				&lt;/ol&gt;
-			&lt;/section&gt;&lt;section id&#x3D;&quot;comments&quot;&gt;
-				&lt;h2&gt;Commentaires&lt;/h2&gt;
-				&lt;p&gt;
-					Répondez à
-					&lt;a href&#x3D;&quot;https://mastodon.adorsaz.ch/@adrien/113155013129979818&quot;&gt;ce message&lt;/a&gt;
-					du
-					&lt;a href&#x3D;&quot;https://fr.wikipedia.org/wiki/Fediverse&quot;&gt;Fediverse&lt;/a&gt; pour
-					ajouter un commentaire sur cet article.
-				&lt;/p&gt;
-
-				
 			&lt;/section&gt;</content>
   </entry>
   <entry>
@@ -906,17 +895,42 @@ EOF&lt;/code&gt;
     apt install python3
     apt-get clean
 &lt;/code&gt;
-&lt;/pre&gt;&lt;section id&#x3D;&quot;comments&quot;&gt;
-				&lt;h2&gt;Commentaires&lt;/h2&gt;
-				&lt;p&gt;
-					Répondez à
-					&lt;a href&#x3D;&quot;https://mastodon.adorsaz.ch/@adrien/111953486356181833&quot;&gt;ce message&lt;/a&gt;
-					du
-					&lt;a href&#x3D;&quot;https://fr.wikipedia.org/wiki/Fediverse&quot;&gt;Fediverse&lt;/a&gt; pour
-					ajouter un commentaire sur cet article.
-				&lt;/p&gt;
+&lt;/pre&gt;&lt;section id&#x3D;&quot;notes&quot;&gt;
+				&lt;h2&gt;Notes&lt;/h2&gt;
+				&lt;article class&#x3D;&quot;note&quot;&gt;
+					&lt;h3&gt;
+						&lt;time datetime&#x3D;&quot;2024-03-27T21:08:52.287Z&quot;&gt;27 mars 2024 à 22:08&lt;/time&gt;
+					&lt;/h3&gt;
+					&lt;p&gt;
+						Si jamais, la fonctionnalité &quot;heredoc&quot; dans les Containerfile est
+						arrivée pour podman dans son moteur de build &lt;i&gt;buildah&lt;/i&gt; version
+						v1.33.0.
+					&lt;/p&gt;
+					&lt;p&gt;
+						Il est actuellement disponible dans Debian trixie (testing) et
+						Fedora 39 si jamais.
+					&lt;/p&gt;
+				&lt;/article&gt;
 
-				
+				&lt;article class&#x3D;&quot;note&quot;&gt;
+					&lt;h3&gt;
+						&lt;time datetime&#x3D;&quot;2024-04-26T12:06:33.230Z&quot;&gt;26 avril 2024 à 14:06&lt;/time&gt;
+					&lt;/h3&gt;
+					&lt;p&gt;
+						En cherchant un marque-pages dans mes favoris de Mastodon, je viens
+						de me rendre compte que j&#x27;avais ajouté en janvier ce message qui
+						partageait déjà des astuces pour les scripts :
+						&lt;a href&#x3D;&quot;https://framapiaf.org/@sebsauvage/111719827048771422&quot;&gt;https://framapiaf.org/@sebsauvage/111719827048771422&lt;/a&gt;
+					&lt;/p&gt;
+					&lt;p&gt;
+						Lien direct vers l&#x27;article partagé :
+						&lt;a href&#x3D;&quot;https://sharats.me/posts/shell-script-best-practices/&quot;&gt;https://sharats.me/posts/shell-script-best-practices/&lt;/a&gt;
+					&lt;/p&gt;
+					&lt;p&gt;
+						Il y a quelques points en plus intéressants (notamment, l&#x27;affichage
+						de l&#x27;aide) et un modèle prêt à être utilisé.
+					&lt;/p&gt;
+				&lt;/article&gt;
 			&lt;/section&gt;</content>
   </entry>
   <entry>
@@ -962,18 +976,7 @@ EOF&lt;/code&gt;
 			&lt;/p&gt;&lt;p&gt;
 				Ce dossier contient des fichiers textes, je n&#x27;ai donc même pas eu besoin
 				de démarrer un serveur web pour visualiser la documentation.
-			&lt;/p&gt;&lt;section id&#x3D;&quot;comments&quot;&gt;
-				&lt;h2&gt;Commentaires&lt;/h2&gt;
-				&lt;p&gt;
-					Répondez à
-					&lt;a href&#x3D;&quot;https://mastodon.adorsaz.ch/@adrien/111530014777140306&quot;&gt;ce message&lt;/a&gt;
-					du
-					&lt;a href&#x3D;&quot;https://fr.wikipedia.org/wiki/Fediverse&quot;&gt;Fediverse&lt;/a&gt; pour
-					ajouter un commentaire sur cet article.
-				&lt;/p&gt;
-
-				
-			&lt;/section&gt;</content>
+			&lt;/p&gt;</content>
   </entry>
   <entry>
     <title type="text">Comment savoir si le style sombre est actif ?</title>
@@ -1101,18 +1104,7 @@ EOF&lt;/code&gt;
 				Toute cette recherche m’a amené à ce
 				&lt;a href&#x3D;&quot;https://gitlab.adorsaz.ch/adrien/config/-/commit/f003dcb0b85ad3a1cfae01b9f8d601e9e1995bc3?view&#x3D;parallel&quot;&gt;petit correctif&lt;/a&gt;
 				de quelques lignes dans mon fichier &lt;em&gt;vimrc&lt;/em&gt;.
-			&lt;/p&gt;&lt;section id&#x3D;&quot;comments&quot;&gt;
-				&lt;h2&gt;Commentaires&lt;/h2&gt;
-				&lt;p&gt;
-					Répondez à
-					&lt;a href&#x3D;&quot;https://mastodon.adorsaz.ch/@adrien/111425807298246655&quot;&gt;ce message&lt;/a&gt;
-					du
-					&lt;a href&#x3D;&quot;https://fr.wikipedia.org/wiki/Fediverse&quot;&gt;Fediverse&lt;/a&gt; pour
-					ajouter un commentaire sur cet article.
-				&lt;/p&gt;
-
-				
-			&lt;/section&gt;</content>
+			&lt;/p&gt;</content>
   </entry>
   <entry>
     <title type="text">Découvertes de logiciels libres - été 2023</title>
@@ -1373,18 +1365,7 @@ EOF&lt;/code&gt;
 				HTML ne sont pas vraiment liés, il est très difficile de placer les
 				marqueurs de manière exacte et il est nécessaire d’avoir toujours un
 				espace équivalent entre chaque marqueur.
-			&lt;/p&gt;&lt;section id&#x3D;&quot;comments&quot;&gt;
-				&lt;h2&gt;Commentaires&lt;/h2&gt;
-				&lt;p&gt;
-					Répondez à
-					&lt;a href&#x3D;&quot;https://mastodon.adorsaz.ch/@adrien/111388345894959194&quot;&gt;ce message&lt;/a&gt;
-					du
-					&lt;a href&#x3D;&quot;https://fr.wikipedia.org/wiki/Fediverse&quot;&gt;Fediverse&lt;/a&gt; pour
-					ajouter un commentaire sur cet article.
-				&lt;/p&gt;
-
-				
-			&lt;/section&gt;</content>
+			&lt;/p&gt;</content>
   </entry>
   <entry>
     <title type="text">Vous avez dit &quot;caractère&quot; ?</title>
@@ -1988,17 +1969,27 @@ git clone https://github.com/kien/ctrlp.vim&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
 				projets persos, j’ai préparé un répertoire &lt;em&gt;git&lt;/em&gt; avec
 				&lt;a href&#x3D;&quot;https://projects.adorsaz.ch/adrien/config/&quot;&gt;mes fichiers de configuration&lt;/a&gt;
 				si vous voulez tous les détails.
-			&lt;/p&gt;&lt;section id&#x3D;&quot;comments&quot;&gt;
-				&lt;h2&gt;Commentaires&lt;/h2&gt;
-				&lt;p&gt;
-					Répondez à
-					&lt;a href&#x3D;&quot;https://mastodon.adorsaz.ch/@adrien/111693530165472878&quot;&gt;ce message&lt;/a&gt;
-					du
-					&lt;a href&#x3D;&quot;https://fr.wikipedia.org/wiki/Fediverse&quot;&gt;Fediverse&lt;/a&gt; pour
-					ajouter un commentaire sur cet article.
-				&lt;/p&gt;
-
-				
+			&lt;/p&gt;&lt;section id&#x3D;&quot;notes&quot;&gt;
+				&lt;h2&gt;Notes&lt;/h2&gt;
+				&lt;article class&#x3D;&quot;note&quot;&gt;
+					&lt;h3&gt;
+						&lt;time datetime&#x3D;&quot;2024-01-03T19:12:26.524Z&quot;&gt;3 janvier 2024 à 20:12&lt;/time&gt;
+					&lt;/h3&gt;
+					&lt;p&gt;
+						Ça fait 3 ans que j&#x27;utilise vim comme ide, c&#x27;est toujours aussi bien
+						:)
+					&lt;/p&gt;
+					&lt;p&gt;
+						Pour mon nouveau job, je suis en train d&#x27;utiliser PHP Storm, mais je
+						vais revenir à vim: l&#x27;indexation des fichiers est toujours lente et
+						l&#x27;ide consomme facilement 3-4 Gio de mémoire à la fin de la journée.
+					&lt;/p&gt;
+					&lt;p&gt;
+						Il faudrait que j&#x27;écrive un petit article sur les quelques
+						configurations que j&#x27;ai faite en plus depuis, mais l&#x27;essentiel est
+						resté identique.
+					&lt;/p&gt;
+				&lt;/article&gt;
 			&lt;/section&gt;</content>
   </entry>
   <entry>
diff --git a/src/articles/comment-savoir-si-le-style-sombre-est-actif.html b/src/articles/comment-savoir-si-le-style-sombre-est-actif.html
index db67543ca0f60c8c10f5bc3fba3fef8c0e218267..dc5f399e793bd0d9bb8a7c9627ca418b9c6bfd8c 100644
--- a/src/articles/comment-savoir-si-le-style-sombre-est-actif.html
+++ b/src/articles/comment-savoir-si-le-style-sombre-est-actif.html
@@ -15,12 +15,6 @@
 			media="screen"
 			href="../css/screen.css"
 		/>
-		<link
-			rel="stylesheet"
-			type="text/css"
-			media="screen"
-			href="../css/comment.css"
-		/>
 		<link
 			rel="stylesheet"
 			type="text/css"
@@ -41,6 +35,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
@@ -59,11 +54,10 @@
 			content="https://gitlab.adorsaz.ch/adrien/adorsaz.ch/-/commits/main/src/articles/comment-savoir-si-le-style-sombre-est-actif.html"
 		/>
 		<meta
-			name="mastodon-comment-url"
+			name="mastodon-share-url"
 			content="https://mastodon.adorsaz.ch/@adrien/111425807298246655"
 		/>
 		<script type="module" src="../javascript/article.js"></script>
-		<script type="module" src="../javascript/comment.js"></script>
 		<script type="module" src="../javascript/header.js"></script>
 	</head>
 
@@ -236,28 +230,6 @@
 				>
 				de quelques lignes dans mon fichier <em>vimrc</em>.
 			</p>
-
-			<section id="comments">
-				<h2>Commentaires</h2>
-				<p>
-					Répondez à
-					<a href="https://mastodon.adorsaz.ch/@adrien/111425807298246655"
-						>ce message</a
-					>
-					du
-					<a href="https://fr.wikipedia.org/wiki/Fediverse">Fediverse</a> pour
-					ajouter un commentaire sur cet article.
-				</p>
-
-				<div id="mastodon-comments">
-					<noscript
-						><p>
-							Les réponses du Fediverse seront affichées ici si vous avctivez le
-							moteur JavaScript de votre navigateur web.
-						</p>
-					</noscript>
-				</div>
-			</section>
 		</article>
 	</body>
 </html>
diff --git a/src/articles/creer-des-certificats-avec-acme-let-s-encrypt-et-des-verifications-dns.html b/src/articles/creer-des-certificats-avec-acme-let-s-encrypt-et-des-verifications-dns.html
index 776a2b1ab730922af61292b3ed396dc72a0ac5de..a1d43ab0a09027b592b9c37f499b5b0aeddc445c 100644
--- a/src/articles/creer-des-certificats-avec-acme-let-s-encrypt-et-des-verifications-dns.html
+++ b/src/articles/creer-des-certificats-avec-acme-let-s-encrypt-et-des-verifications-dns.html
@@ -42,6 +42,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
diff --git a/src/articles/decouvertes-logiciels-libres-ete-2023.html b/src/articles/decouvertes-logiciels-libres-ete-2023.html
index be9400f70e22c24ac80451ddc11bbc5ebec320fe..a8656589bf214cd75af11f7b420b3c8b4d0f4a22 100644
--- a/src/articles/decouvertes-logiciels-libres-ete-2023.html
+++ b/src/articles/decouvertes-logiciels-libres-ete-2023.html
@@ -15,12 +15,6 @@
 			media="screen"
 			href="../css/screen.css"
 		/>
-		<link
-			rel="stylesheet"
-			type="text/css"
-			media="screen"
-			href="../css/comment.css"
-		/>
 		<link
 			rel="stylesheet"
 			type="text/css"
@@ -41,6 +35,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
@@ -63,7 +58,6 @@
 			content="https://linuxfr.org/users/trim/journaux/decouvertes-de-logiciels-libres-ete-2023"
 		/>
 		<script type="module" src="../javascript/article.js"></script>
-		<script type="module" src="../javascript/comment.js"></script>
 		<script type="module" src="../javascript/header.js"></script>
 	</head>
 
@@ -392,28 +386,6 @@
 				marqueurs de manière exacte et il est nécessaire d’avoir toujours un
 				espace équivalent entre chaque marqueur.
 			</p>
-
-			<section id="comments">
-				<h2>Commentaires</h2>
-				<p>
-					Répondez à
-					<a href="https://mastodon.adorsaz.ch/@adrien/111388345894959194"
-						>ce message</a
-					>
-					du
-					<a href="https://fr.wikipedia.org/wiki/Fediverse">Fediverse</a> pour
-					ajouter un commentaire sur cet article.
-				</p>
-
-				<div id="mastodon-comments">
-					<noscript
-						><p>
-							Les réponses du Fediverse seront affichées ici si vous avctivez le
-							moteur JavaScript de votre navigateur web.
-						</p>
-					</noscript>
-				</div>
-			</section>
 		</article>
 	</body>
 </html>
diff --git a/src/articles/gnome-et-logitech-collaborent-pour-vous-proposer-des-mises-a-jour-de-leur-solution-unify.html b/src/articles/gnome-et-logitech-collaborent-pour-vous-proposer-des-mises-a-jour-de-leur-solution-unify.html
index 70640e2d7a6a721dbd04bc5d5501bd6be744452f..c393b75e8bc3438d6ad128731479cd808da67018 100644
--- a/src/articles/gnome-et-logitech-collaborent-pour-vous-proposer-des-mises-a-jour-de-leur-solution-unify.html
+++ b/src/articles/gnome-et-logitech-collaborent-pour-vous-proposer-des-mises-a-jour-de-leur-solution-unify.html
@@ -43,6 +43,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
diff --git "a/src/articles/installation-personnalis\303\251e-de-debian.html" "b/src/articles/installation-personnalis\303\251e-de-debian.html"
index caa4e2354f7c69e27a6ed2a7a8bbe98f6eaf003c..a75be54defd1f328ce4566f71a2b041f0dfff350 100644
--- "a/src/articles/installation-personnalis\303\251e-de-debian.html"
+++ "b/src/articles/installation-personnalis\303\251e-de-debian.html"
@@ -44,6 +44,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC BY-SA" />
 		<meta
@@ -60,7 +61,7 @@
 			content="https://gitlab.adorsaz.ch/adrien/adorsaz.ch/-/commits/main/src/articles/installation-personnalisée-de-debian.html"
 		/>
 		<meta
-			name="mastodon-comment-url"
+			name="mastodon-share-url"
 			content="https://mastodon.adorsaz.ch/@adrien/113155013129979818"
 		/>
 		<meta name="duplicate-1-name" content="LinuxFr.org" />
@@ -69,7 +70,6 @@
 			content="https://linuxfr.org/users/trim/journaux/installation-personnalisee-de-debian-avec-luks-v2-volumes-btrfs-systemd-boot-et-secure-boot"
 		/>
 		<script type="module" src="../javascript/article.js"></script>
-		<script type="module" src="../javascript/comment.js"></script>
 		<script type="module" src="../javascript/header.js"></script>
 	</head>
 
@@ -947,27 +947,6 @@ $ sudo systemctl enable --now systemd-boot-sign.path</code></pre>
 					</li>
 				</ol>
 			</section>
-			<section id="comments">
-				<h2>Commentaires</h2>
-				<p>
-					Répondez à
-					<a href="https://mastodon.adorsaz.ch/@adrien/113155013129979818"
-						>ce message</a
-					>
-					du
-					<a href="https://fr.wikipedia.org/wiki/Fediverse">Fediverse</a> pour
-					ajouter un commentaire sur cet article.
-				</p>
-
-				<div id="mastodon-comments">
-					<noscript
-						><p>
-							Les réponses du Fediverse seront affichées ici si vous activez le
-							moteur JavaScript de votre navigateur web.
-						</p>
-					</noscript>
-				</div>
-			</section>
 		</article>
 	</body>
 </html>
diff --git a/src/articles/livrer-la-documentation-avec-le-code-source.html b/src/articles/livrer-la-documentation-avec-le-code-source.html
index 694860cefba2d3fe90b478a12a35a7f1ba115431..b0afe341ae8e73a17f3b58e32d6d83bfad669b2f 100644
--- a/src/articles/livrer-la-documentation-avec-le-code-source.html
+++ b/src/articles/livrer-la-documentation-avec-le-code-source.html
@@ -15,12 +15,6 @@
 			media="screen"
 			href="../css/screen.css"
 		/>
-		<link
-			rel="stylesheet"
-			type="text/css"
-			media="screen"
-			href="../css/comment.css"
-		/>
 		<link
 			rel="stylesheet"
 			type="text/css"
@@ -41,6 +35,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
@@ -54,11 +49,10 @@
 			content="https://gitlab.adorsaz.ch/adrien/adorsaz.ch/-/commits/main/src/articles/livrer-la-documentation-avec-le-code-source.html"
 		/>
 		<meta
-			name="mastodon-comment-url"
+			name="mastodon-share-url"
 			content="https://mastodon.adorsaz.ch/@adrien/111530014777140306"
 		/>
 		<script type="module" src="../javascript/article.js"></script>
-		<script type="module" src="../javascript/comment.js"></script>
 		<script type="module" src="../javascript/header.js"></script>
 	</head>
 
@@ -115,28 +109,6 @@
 				Ce dossier contient des fichiers textes, je n'ai donc même pas eu besoin
 				de démarrer un serveur web pour visualiser la documentation.
 			</p>
-
-			<section id="comments">
-				<h2>Commentaires</h2>
-				<p>
-					Répondez à
-					<a href="https://mastodon.adorsaz.ch/@adrien/111530014777140306"
-						>ce message</a
-					>
-					du
-					<a href="https://fr.wikipedia.org/wiki/Fediverse">Fediverse</a> pour
-					ajouter un commentaire sur cet article.
-				</p>
-
-				<div id="mastodon-comments">
-					<noscript
-						><p>
-							Les réponses du Fediverse seront affichées ici si vous avctivez le
-							moteur JavaScript de votre navigateur web.
-						</p>
-					</noscript>
-				</div>
-			</section>
 		</article>
 	</body>
 </html>
diff --git a/src/articles/mise-a-jour-simplifiee-des-routeurs-openwrt.html b/src/articles/mise-a-jour-simplifiee-des-routeurs-openwrt.html
index c337c44d5beeff3293e3d8e6664f84aa4ac225c8..8971843a7c32f64d1d1d66c357f5a85ca7cead6d 100644
--- a/src/articles/mise-a-jour-simplifiee-des-routeurs-openwrt.html
+++ b/src/articles/mise-a-jour-simplifiee-des-routeurs-openwrt.html
@@ -40,6 +40,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC BY-SA" />
 		<meta
diff --git a/src/articles/proteger-sa-vie-privee-avec-l-ipv6.html b/src/articles/proteger-sa-vie-privee-avec-l-ipv6.html
index 492d338668411750c11f072bfa4e22edff652d70..d83a8fc2ccc4ccf02abd92ee2e46245895dacae0 100644
--- a/src/articles/proteger-sa-vie-privee-avec-l-ipv6.html
+++ b/src/articles/proteger-sa-vie-privee-avec-l-ipv6.html
@@ -40,6 +40,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="contributor-1-name" content="Yves Bourguignon" />
 		<meta name="contributor-1-url" content="https://linuxfr.org/users/biomin" />
 		<meta name="contributor-2-name" content="Lucas" />
diff --git a/src/articles/publication-d-acme-dns-tiny-et-du-rfc-8555.html b/src/articles/publication-d-acme-dns-tiny-et-du-rfc-8555.html
index 11efb92fbf445b1a250281651b1d02670a3f5abe..1d4c70122ee517b0d0b323fc9fbee49e9a0d5db8 100644
--- a/src/articles/publication-d-acme-dns-tiny-et-du-rfc-8555.html
+++ b/src/articles/publication-d-acme-dns-tiny-et-du-rfc-8555.html
@@ -40,6 +40,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
diff --git a/src/articles/rootless-container.html b/src/articles/rootless-container.html
index 1f7484753e12b1249f1fb2e4a51bb2e7ad180da1..82c6e1d70b860b3ec21a96496b72b369d4145405 100644
--- a/src/articles/rootless-container.html
+++ b/src/articles/rootless-container.html
@@ -43,6 +43,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
diff --git a/src/articles/transformer-vim-en-ide-avec-lsp-et-dap.html b/src/articles/transformer-vim-en-ide-avec-lsp-et-dap.html
index e133178046985504067ff7af154d7ace66cc3e58..d7d751308cb493e44cc66898e898407be0b40ee2 100644
--- a/src/articles/transformer-vim-en-ide-avec-lsp-et-dap.html
+++ b/src/articles/transformer-vim-en-ide-avec-lsp-et-dap.html
@@ -15,12 +15,6 @@
 			media="screen"
 			href="../css/screen.css"
 		/>
-		<link
-			rel="stylesheet"
-			type="text/css"
-			media="screen"
-			href="../css/comment.css"
-		/>
 		<link
 			rel="stylesheet"
 			type="text/css"
@@ -46,6 +40,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
@@ -62,11 +57,10 @@
 			content="https://gitlab.adorsaz.ch/adrien/adorsaz.ch/-/commits/main/src/articles/transformer-vim-en-ide-avec-lsp-et-dap.html"
 		/>
 		<meta
-			name="mastodon-comment-url"
+			name="mastodon-share-url"
 			content="https://mastodon.adorsaz.ch/@adrien/111693530165472878"
 		/>
 		<script type="module" src="../javascript/article.js"></script>
-		<script type="module" src="../javascript/comment.js"></script>
 		<script type="module" src="../javascript/header.js"></script>
 	</head>
 
@@ -325,26 +319,29 @@ git clone https://github.com/kien/ctrlp.vim</code></pre>
 				si vous voulez tous les détails.
 			</p>
 
-			<section id="comments">
-				<h2>Commentaires</h2>
-				<p>
-					Répondez à
-					<a href="https://mastodon.adorsaz.ch/@adrien/111693530165472878"
-						>ce message</a
-					>
-					du
-					<a href="https://fr.wikipedia.org/wiki/Fediverse">Fediverse</a> pour
-					ajouter un commentaire sur cet article.
-				</p>
-
-				<div id="mastodon-comments">
-					<noscript
-						><p>
-							Les réponses du Fediverse seront affichées ici si vous avctivez le
-							moteur JavaScript de votre navigateur web.
-						</p>
-					</noscript>
-				</div>
+			<section id="notes">
+				<h2>Notes</h2>
+				<article class="note">
+					<h3>
+						<time datetime="2024-01-03T19:12:26.524Z"
+							>3 janvier 2024 à 20:12</time
+						>
+					</h3>
+					<p>
+						Ça fait 3 ans que j'utilise vim comme ide, c'est toujours aussi bien
+						:)
+					</p>
+					<p>
+						Pour mon nouveau job, je suis en train d'utiliser PHP Storm, mais je
+						vais revenir à vim: l'indexation des fichiers est toujours lente et
+						l'ide consomme facilement 3-4 Gio de mémoire à la fin de la journée.
+					</p>
+					<p>
+						Il faudrait que j'écrive un petit article sur les quelques
+						configurations que j'ai faite en plus depuis, mais l'essentiel est
+						resté identique.
+					</p>
+				</article>
 			</section>
 		</article>
 	</body>
diff --git a/src/articles/vous-avez-dit-caractere.html b/src/articles/vous-avez-dit-caractere.html
index 6dbd5b8619e429b967b0e16c7f5f91d606b6a2ad..b16bc0d5f14913eda403b68194a41c3639e1f7a5 100644
--- a/src/articles/vous-avez-dit-caractere.html
+++ b/src/articles/vous-avez-dit-caractere.html
@@ -40,6 +40,7 @@
 		<meta name="author-name" content="Adrien Dorsaz" />
 		<meta name="author-url" content="https://adorsaz.ch" />
 		<meta name="author-email" content="adrien@adorsaz.ch" />
+		<meta name="fediverse:creator" content="@adrien@adorsaz.ch" />
 		<meta name="lang" content="fr" />
 		<meta name="license-name" content="CC-BY-SA 4.0" />
 		<meta
diff --git a/src/css/comment.css b/src/css/comment.css
deleted file mode 100644
index e1ea902b1d8c694f16bc4e598f8ee7ea21e9bcd6..0000000000000000000000000000000000000000
--- a/src/css/comment.css
+++ /dev/null
@@ -1,98 +0,0 @@
-/* template is defined in sized css files */
-#comments .comment {
-	display: grid;
-	column-gap: 1rem;
-	padding: 0.5rem;
-	border: solid 1px hsl(var(--text-color-hsl) / 40%);
-	margin-block: 1rem;
-}
-
-#comments .comment-reply {
-	margin-inline-start: 2rem;
-	position: relative;
-	overflow: unset;
-}
-
-#comments .comment-reply::before {
-	content: "⤷";
-	font-size: 2rem;
-	position: absolute;
-	left: -1.5rem;
-}
-
-#comments .comment .avatar-link {
-	height: 3rem;
-	aspect-ratio: 1/1;
-	grid-area: avatar;
-	position: relative;
-}
-
-#comments .comment header {
-	grid-area: name;
-}
-
-#comments .comment header .instance {
-	margin-inline-start: 0.5rem;
-}
-
-#comments .comment aside {
-	grid-area: aside;
-	justify-self: end;
-	white-space: nowrap;
-}
-
-#comments .comment aside .permalink {
-	margin-inline-start: 0.5rem;
-}
-
-#comments .comment > div {
-	grid-area: post;
-}
-
-#comments .comment footer {
-	grid-area: interactions;
-}
-
-#comments .comment .avatar-link img.avatar {
-	width: 100%;
-	height: 100%;
-	object-fit: scale-down;
-}
-
-#comments .comment.op .avatar-link::after {
-	content: "✏";
-	text-decoration: none;
-	color: black;
-	background-color: hsl(var(--background-color-hsl) / 100%);
-	border: solid 1px hsl(var(--accent-color-hsl) / 100%);
-	border-radius: 50%;
-	width: 1.5rem;
-	height: 1.5rem;
-	text-align: center;
-	font-size: 1rem;
-	padding-top: 0.1rem;
-	position: absolute;
-	bottom: -0.5rem;
-	right: -0.5rem;
-	display: block;
-}
-
-#comments img.emoji {
-	height: 0.8em;
-	aspect-ratio: 1/1;
-}
-
-#comments .faves {
-	color: unset;
-	text-decoration: none;
-}
-
-@media (prefers-color-scheme: dark) {
-	#comments .comment {
-		background: color-mix(
-			in sRGB,
-			hsl(var(--background-color-hsl) / 100%) 95%,
-			hsl(var(--text-color-hsl) / 100%)
-		);
-	}
-}
diff --git a/src/css/screen.css b/src/css/screen.css
index 0b757a8e2c708a62ee82065d7ed8cc1a67dcd472..03ade15335be4671bfc81533d1d88e9adefc25b9 100644
--- a/src/css/screen.css
+++ b/src/css/screen.css
@@ -418,7 +418,7 @@ button .icon,
 }
 
 dialog > header button {
-	aspect-ratio: 1;
+	aspect-ratio: 1 / 1;
 	padding: 0.2rem;
 	font-size: 1rem;
 	font-weight: 900;
@@ -533,6 +533,14 @@ details[open] > summary::after {
 	content: "⤴";
 }
 
+#notes .note {
+	display: grid;
+	column-gap: 1rem;
+	padding: 0.5rem;
+	border: solid 1px hsl(var(--text-color-hsl) / 40%);
+	margin-block: 1rem;
+}
+
 @media (prefers-color-scheme: dark) {
 	:root {
 		/* Main colors */
@@ -568,4 +576,12 @@ details[open] > summary::after {
 	.button {
 		background: no-repeat hsl(var(--accent-color-hsl) / 25%);
 	}
+
+	#notes .note {
+		background: color-mix(
+			in sRGB,
+			hsl(var(--background-color-hsl) / 100%) 95%,
+			hsl(var(--text-color-hsl) / 100%)
+		);
+	}
 }
diff --git a/src/javascript/article.js b/src/javascript/article.js
index 0d1954492e7307f385c249a35f11ecce5cbc51ca..a3730754431e45145b3aba8cbcfb60ba408cf0b3 100644
--- a/src/javascript/article.js
+++ b/src/javascript/article.js
@@ -192,6 +192,13 @@ async function addMetadataInArticle() {
 			}
 		}
 
+		if (metadata["mastodon-share-url"]) {
+			linkSpan.append(", ")
+			linkSpan.appendChild(
+				createLink(metadata["mastodon-share-url"], "Mastodon"),
+			)
+		}
+
 		aside.appendChild(linkSpan)
 	}
 	articleTitle.parentNode.insertBefore(aside, articleTitle.nextSibling)
diff --git a/src/javascript/comment.js b/src/javascript/comment.js
deleted file mode 100644
index 95e0af4593fbfaf1fcecd7bf827e8943ae5038b5..0000000000000000000000000000000000000000
--- a/src/javascript/comment.js
+++ /dev/null
@@ -1,219 +0,0 @@
-import DOMPurify from "../vendor/javascript/purify.es.js"
-import Http from "./http.js"
-import { getLocalizations } from "./localization.js"
-
-/*
- * Licensed under CC BY-SA 4.0
- *
- * Original code created by Carl Schwan, Cassidy James Blaede and Veronica Olsen:
- *   * https://carlschwan.eu/2020/12/29/adding-comments-to-your-static-blog-with-mastodon/
- */
-
-function escapeHtml(unsafe) {
-	return unsafe
-		.replace(/&/g, "&amp;")
-		.replace(/</g, "&lt;")
-		.replace(/>/g, "&gt;")
-		.replace(/"/g, "&quot;")
-		.replace(/'/g, "&#039;")
-}
-
-function emojify(input, emojis) {
-	let output = input
-
-	emojis.forEach((emoji) => {
-		const picture = document.createElement("picture")
-
-		const source = document.createElement("source")
-		source.setAttribute("srcset", new URL(emoji.url))
-		source.setAttribute("media", "(prefers-reduced-motion: no-preference)")
-
-		const img = new Image()
-		img.className = "emoji"
-		img.src = new URL(emoji.static_url)
-		img.alt = `:${emoji.shortcode}:`
-		img.title = `:${emoji.shortcode}:`
-
-		picture.appendChild(source)
-		picture.appendChild(img)
-
-		output = output.replace(`:${emoji.shortcode}:`, picture.outerHTML)
-	})
-
-	return output
-}
-
-async function loadComments() {
-	const mastodonComments = document.getElementById("mastodon-comments")
-	const mastodonUrl = new URL(
-		document
-			.querySelector('meta[name="mastodon-comment-url"]')
-			.getAttribute("content"),
-	)
-	const mastodonUser = mastodonUrl.pathname.split("/")[1].replace("@", "")
-	const mastodonId = mastodonUrl.pathname.split("/")[2]
-
-	if (!(mastodonComments && mastodonUrl && mastodonUser && mastodonId)) {
-		console.warn("Mastodon URL is missing or malformed.")
-		return
-	}
-
-	const data = await Http({
-		url: new URL(`/api/v1/statuses/${mastodonId}/context`, mastodonUrl),
-		responseType: "json",
-	})
-
-	const descendants = data.descendants
-	if (descendants && Array.isArray(descendants) && descendants.length > 0) {
-		mastodonComments.innerHTML = ""
-
-		const l10n = await getLocalizations("comment")
-
-		descendants.forEach(function (status) {
-			if (status.account.display_name.length > 0) {
-				status.account.display_name = escapeHtml(status.account.display_name)
-				status.account.display_name = emojify(
-					status.account.display_name,
-					status.account.emojis,
-				)
-			} else {
-				status.account.display_name = status.account.username
-			}
-
-			let instance = mastodonUrl.hostname
-			if (status.account.acct.includes("@")) {
-				instance = status.account.acct.split("@")[1]
-			}
-
-			const isReply = status.in_reply_to_id !== mastodonId
-
-			const originalPoster = status.account.acct === mastodonUser
-
-			status.content = emojify(status.content, status.emojis)
-
-			const avatarSource = document.createElement("source")
-			avatarSource.setAttribute("srcset", new URL(status.account.avatar))
-			avatarSource.setAttribute(
-				"media",
-				"(prefers-reduced-motion: no-preference)",
-			)
-
-			const avatarImg = new Image()
-			avatarImg.className = "avatar"
-			avatarImg.alt = `${l10n["avatar-of"]} @${status.account.username}@${instance}`
-			avatarImg.src = new URL(status.account.avatar_static)
-
-			const avatarPicture = document.createElement("picture")
-			avatarPicture.appendChild(avatarSource)
-			avatarPicture.appendChild(avatarImg)
-
-			const avatar = document.createElement("a")
-			avatar.className = "avatar-link"
-			avatar.setAttribute("href", status.account.url)
-			avatar.setAttribute("rel", "external nofollow")
-			avatar.setAttribute(
-				"title",
-				`${l10n["view-profile-at"]} @${status.account.username}@${instance}`,
-			)
-			avatar.appendChild(avatarPicture)
-
-			const instanceBadge = document.createElement("span")
-			instanceBadge.className = "instance tag"
-
-			const instanceBadgeIcon = document.createElement("span")
-			instanceBadgeIcon.className = "icon"
-			instanceBadgeIcon.textContent = `🌐`
-			instanceBadgeIcon.setAttribute("arria-hidden", "true")
-
-			const instanceBadgeText = document.createElement("span")
-			instanceBadgeText.textContent = instance
-
-			instanceBadge.appendChild(instanceBadgeIcon)
-			instanceBadge.appendChild(instanceBadgeText)
-
-			const name = document.createElement("a")
-			name.setAttribute("itemprop", "author")
-			name.setAttribute("itemtype", "http://schema.org/Person")
-			name.setAttribute("href", status.account.url)
-			name.setAttribute("title", `@${status.account.username}@${instance}`)
-			name.setAttribute("rel", "external nofollow")
-			name.innerHTML = status.account.display_name
-
-			const header = document.createElement("header")
-			header.className = "author"
-			header.appendChild(name)
-			header.appendChild(instanceBadge)
-
-			const aside = document.createElement("aside")
-
-			const timestamp = document.createElement("time")
-			timestamp.setAttribute("datetime", status.created_at)
-			timestamp.innerText = new Date(status.created_at).toLocaleString(
-				document.querySelector("meta[name=lang]").getAttribute("content") ??
-					(navigator.language || "fr-CH"),
-				{
-					dateStyle: "long",
-					timeStyle: "short",
-				},
-			)
-			aside.appendChild(timestamp)
-
-			const permalink = document.createElement("a")
-			permalink.className = "permalink"
-			permalink.setAttribute("href", status.url)
-			permalink.setAttribute("itemprop", "url")
-			permalink.setAttribute("title", l10n["comment-permalink"])
-			permalink.setAttribute("rel", "external nofollow")
-			permalink.textContent = "#"
-			aside.appendChild(permalink)
-
-			const div = document.createElement("div")
-			div.setAttribute("itemprop", "text")
-			div.innerHTML = status.content
-
-			const interactions = document.createElement("footer")
-			if (status.favourites_count > 0) {
-				const faves = document.createElement("a")
-				faves.className = "faves"
-				faves.setAttribute("href", `${status.url}/favourites`)
-				faves.setAttribute("title", `${l10n["favorites-from"]} ${instance}`)
-				faves.textContent = `🌟 ${status.favourites_count}`
-
-				interactions.appendChild(faves)
-			}
-
-			const comment = document.createElement("article")
-			comment.id = `comment-${status.id}`
-			comment.className = isReply ? "comment comment-reply" : "comment"
-			comment.setAttribute("itemprop", "comment")
-			comment.setAttribute("itemtype", "http://schema.org/Comment")
-			comment.appendChild(avatar)
-			comment.appendChild(header)
-			comment.appendChild(aside)
-			comment.appendChild(div)
-			comment.appendChild(interactions)
-
-			if (originalPoster) {
-				comment.classList.add("op")
-
-				avatar.classList.add("op")
-				avatar.setAttribute(
-					"title",
-					`${l10n["original-poster"]} ${avatar.getAttribute("title").toLowerCase()}`,
-				)
-
-				instanceBadge.classList.add("op")
-				instanceBadge.setAttribute(
-					"title",
-					`${l10n["original-poster"]} ${instanceBadge.innerText}`,
-				)
-			}
-
-			mastodonComments.innerHTML += DOMPurify.sanitize(comment.outerHTML)
-		})
-	}
-}
-
-document.addEventListener("DOMContentLoaded", () => {
-	loadComments()
-})
diff --git a/src/vendor/javascript/purify.es.js b/src/vendor/javascript/purify.es.js
deleted file mode 100644
index a872c1166bb381a07d4c2da86ce78ef2f46fe83a..0000000000000000000000000000000000000000
--- a/src/vendor/javascript/purify.es.js
+++ /dev/null
@@ -1,2443 +0,0 @@
-/*! @license DOMPurify 3.1.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.6/LICENSE */
-
-const {
-	entries,
-	setPrototypeOf,
-	isFrozen,
-	getPrototypeOf,
-	getOwnPropertyDescriptor,
-} = Object
-let { freeze, seal, create } = Object // eslint-disable-line import/no-mutable-exports
-let { apply, construct } = typeof Reflect !== "undefined" && Reflect
-if (!freeze) {
-	freeze = function freeze(x) {
-		return x
-	}
-}
-if (!seal) {
-	seal = function seal(x) {
-		return x
-	}
-}
-if (!apply) {
-	apply = function apply(fun, thisValue, args) {
-		return fun.apply(thisValue, args)
-	}
-}
-if (!construct) {
-	construct = function construct(Func, args) {
-		return new Func(...args)
-	}
-}
-const arrayForEach = unapply(Array.prototype.forEach)
-const arrayPop = unapply(Array.prototype.pop)
-const arrayPush = unapply(Array.prototype.push)
-const stringToLowerCase = unapply(String.prototype.toLowerCase)
-const stringToString = unapply(String.prototype.toString)
-const stringMatch = unapply(String.prototype.match)
-const stringReplace = unapply(String.prototype.replace)
-const stringIndexOf = unapply(String.prototype.indexOf)
-const stringTrim = unapply(String.prototype.trim)
-const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty)
-const regExpTest = unapply(RegExp.prototype.test)
-const typeErrorCreate = unconstruct(TypeError)
-
-/**
- * Creates a new function that calls the given function with a specified thisArg and arguments.
- *
- * @param {Function} func - The function to be wrapped and called.
- * @returns {Function} A new function that calls the given function with a specified thisArg and arguments.
- */
-function unapply(func) {
-	return function (thisArg) {
-		for (
-			var _len = arguments.length,
-				args = new Array(_len > 1 ? _len - 1 : 0),
-				_key = 1;
-			_key < _len;
-			_key++
-		) {
-			args[_key - 1] = arguments[_key]
-		}
-		return apply(func, thisArg, args)
-	}
-}
-
-/**
- * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
- *
- * @param {Function} func - The constructor function to be wrapped and called.
- * @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments.
- */
-function unconstruct(func) {
-	return function () {
-		for (
-			var _len2 = arguments.length, args = new Array(_len2), _key2 = 0;
-			_key2 < _len2;
-			_key2++
-		) {
-			args[_key2] = arguments[_key2]
-		}
-		return construct(func, args)
-	}
-}
-
-/**
- * Add properties to a lookup table
- *
- * @param {Object} set - The set to which elements will be added.
- * @param {Array} array - The array containing elements to be added to the set.
- * @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set.
- * @returns {Object} The modified set with added elements.
- */
-function addToSet(set, array) {
-	let transformCaseFunc =
-		arguments.length > 2 && arguments[2] !== undefined
-			? arguments[2]
-			: stringToLowerCase
-	if (setPrototypeOf) {
-		// Make 'in' and truthy checks like Boolean(set.constructor)
-		// independent of any properties defined on Object.prototype.
-		// Prevent prototype setters from intercepting set as a this value.
-		setPrototypeOf(set, null)
-	}
-	let l = array.length
-	while (l--) {
-		let element = array[l]
-		if (typeof element === "string") {
-			const lcElement = transformCaseFunc(element)
-			if (lcElement !== element) {
-				// Config presets (e.g. tags.js, attrs.js) are immutable.
-				if (!isFrozen(array)) {
-					array[l] = lcElement
-				}
-				element = lcElement
-			}
-		}
-		set[element] = true
-	}
-	return set
-}
-
-/**
- * Clean up an array to harden against CSPP
- *
- * @param {Array} array - The array to be cleaned.
- * @returns {Array} The cleaned version of the array
- */
-function cleanArray(array) {
-	for (let index = 0; index < array.length; index++) {
-		const isPropertyExist = objectHasOwnProperty(array, index)
-		if (!isPropertyExist) {
-			array[index] = null
-		}
-	}
-	return array
-}
-
-/**
- * Shallow clone an object
- *
- * @param {Object} object - The object to be cloned.
- * @returns {Object} A new object that copies the original.
- */
-function clone(object) {
-	const newObject = create(null)
-	for (const [property, value] of entries(object)) {
-		const isPropertyExist = objectHasOwnProperty(object, property)
-		if (isPropertyExist) {
-			if (Array.isArray(value)) {
-				newObject[property] = cleanArray(value)
-			} else if (
-				value &&
-				typeof value === "object" &&
-				value.constructor === Object
-			) {
-				newObject[property] = clone(value)
-			} else {
-				newObject[property] = value
-			}
-		}
-	}
-	return newObject
-}
-
-/**
- * This method automatically checks if the prop is function or getter and behaves accordingly.
- *
- * @param {Object} object - The object to look up the getter function in its prototype chain.
- * @param {String} prop - The property name for which to find the getter function.
- * @returns {Function} The getter function found in the prototype chain or a fallback function.
- */
-function lookupGetter(object, prop) {
-	while (object !== null) {
-		const desc = getOwnPropertyDescriptor(object, prop)
-		if (desc) {
-			if (desc.get) {
-				return unapply(desc.get)
-			}
-			if (typeof desc.value === "function") {
-				return unapply(desc.value)
-			}
-		}
-		object = getPrototypeOf(object)
-	}
-	function fallbackValue() {
-		return null
-	}
-	return fallbackValue
-}
-
-const html$1 = freeze([
-	"a",
-	"abbr",
-	"acronym",
-	"address",
-	"area",
-	"article",
-	"aside",
-	"audio",
-	"b",
-	"bdi",
-	"bdo",
-	"big",
-	"blink",
-	"blockquote",
-	"body",
-	"br",
-	"button",
-	"canvas",
-	"caption",
-	"center",
-	"cite",
-	"code",
-	"col",
-	"colgroup",
-	"content",
-	"data",
-	"datalist",
-	"dd",
-	"decorator",
-	"del",
-	"details",
-	"dfn",
-	"dialog",
-	"dir",
-	"div",
-	"dl",
-	"dt",
-	"element",
-	"em",
-	"fieldset",
-	"figcaption",
-	"figure",
-	"font",
-	"footer",
-	"form",
-	"h1",
-	"h2",
-	"h3",
-	"h4",
-	"h5",
-	"h6",
-	"head",
-	"header",
-	"hgroup",
-	"hr",
-	"html",
-	"i",
-	"img",
-	"input",
-	"ins",
-	"kbd",
-	"label",
-	"legend",
-	"li",
-	"main",
-	"map",
-	"mark",
-	"marquee",
-	"menu",
-	"menuitem",
-	"meter",
-	"nav",
-	"nobr",
-	"ol",
-	"optgroup",
-	"option",
-	"output",
-	"p",
-	"picture",
-	"pre",
-	"progress",
-	"q",
-	"rp",
-	"rt",
-	"ruby",
-	"s",
-	"samp",
-	"section",
-	"select",
-	"shadow",
-	"small",
-	"source",
-	"spacer",
-	"span",
-	"strike",
-	"strong",
-	"style",
-	"sub",
-	"summary",
-	"sup",
-	"table",
-	"tbody",
-	"td",
-	"template",
-	"textarea",
-	"tfoot",
-	"th",
-	"thead",
-	"time",
-	"tr",
-	"track",
-	"tt",
-	"u",
-	"ul",
-	"var",
-	"video",
-	"wbr",
-])
-
-// SVG
-const svg$1 = freeze([
-	"svg",
-	"a",
-	"altglyph",
-	"altglyphdef",
-	"altglyphitem",
-	"animatecolor",
-	"animatemotion",
-	"animatetransform",
-	"circle",
-	"clippath",
-	"defs",
-	"desc",
-	"ellipse",
-	"filter",
-	"font",
-	"g",
-	"glyph",
-	"glyphref",
-	"hkern",
-	"image",
-	"line",
-	"lineargradient",
-	"marker",
-	"mask",
-	"metadata",
-	"mpath",
-	"path",
-	"pattern",
-	"polygon",
-	"polyline",
-	"radialgradient",
-	"rect",
-	"stop",
-	"style",
-	"switch",
-	"symbol",
-	"text",
-	"textpath",
-	"title",
-	"tref",
-	"tspan",
-	"view",
-	"vkern",
-])
-const svgFilters = freeze([
-	"feBlend",
-	"feColorMatrix",
-	"feComponentTransfer",
-	"feComposite",
-	"feConvolveMatrix",
-	"feDiffuseLighting",
-	"feDisplacementMap",
-	"feDistantLight",
-	"feDropShadow",
-	"feFlood",
-	"feFuncA",
-	"feFuncB",
-	"feFuncG",
-	"feFuncR",
-	"feGaussianBlur",
-	"feImage",
-	"feMerge",
-	"feMergeNode",
-	"feMorphology",
-	"feOffset",
-	"fePointLight",
-	"feSpecularLighting",
-	"feSpotLight",
-	"feTile",
-	"feTurbulence",
-])
-
-// List of SVG elements that are disallowed by default.
-// We still need to know them so that we can do namespace
-// checks properly in case one wants to add them to
-// allow-list.
-const svgDisallowed = freeze([
-	"animate",
-	"color-profile",
-	"cursor",
-	"discard",
-	"font-face",
-	"font-face-format",
-	"font-face-name",
-	"font-face-src",
-	"font-face-uri",
-	"foreignobject",
-	"hatch",
-	"hatchpath",
-	"mesh",
-	"meshgradient",
-	"meshpatch",
-	"meshrow",
-	"missing-glyph",
-	"script",
-	"set",
-	"solidcolor",
-	"unknown",
-	"use",
-])
-const mathMl$1 = freeze([
-	"math",
-	"menclose",
-	"merror",
-	"mfenced",
-	"mfrac",
-	"mglyph",
-	"mi",
-	"mlabeledtr",
-	"mmultiscripts",
-	"mn",
-	"mo",
-	"mover",
-	"mpadded",
-	"mphantom",
-	"mroot",
-	"mrow",
-	"ms",
-	"mspace",
-	"msqrt",
-	"mstyle",
-	"msub",
-	"msup",
-	"msubsup",
-	"mtable",
-	"mtd",
-	"mtext",
-	"mtr",
-	"munder",
-	"munderover",
-	"mprescripts",
-])
-
-// Similarly to SVG, we want to know all MathML elements,
-// even those that we disallow by default.
-const mathMlDisallowed = freeze([
-	"maction",
-	"maligngroup",
-	"malignmark",
-	"mlongdiv",
-	"mscarries",
-	"mscarry",
-	"msgroup",
-	"mstack",
-	"msline",
-	"msrow",
-	"semantics",
-	"annotation",
-	"annotation-xml",
-	"mprescripts",
-	"none",
-])
-const text = freeze(["#text"])
-
-const html = freeze([
-	"accept",
-	"action",
-	"align",
-	"alt",
-	"autocapitalize",
-	"autocomplete",
-	"autopictureinpicture",
-	"autoplay",
-	"background",
-	"bgcolor",
-	"border",
-	"capture",
-	"cellpadding",
-	"cellspacing",
-	"checked",
-	"cite",
-	"class",
-	"clear",
-	"color",
-	"cols",
-	"colspan",
-	"controls",
-	"controlslist",
-	"coords",
-	"crossorigin",
-	"datetime",
-	"decoding",
-	"default",
-	"dir",
-	"disabled",
-	"disablepictureinpicture",
-	"disableremoteplayback",
-	"download",
-	"draggable",
-	"enctype",
-	"enterkeyhint",
-	"face",
-	"for",
-	"headers",
-	"height",
-	"hidden",
-	"high",
-	"href",
-	"hreflang",
-	"id",
-	"inputmode",
-	"integrity",
-	"ismap",
-	"kind",
-	"label",
-	"lang",
-	"list",
-	"loading",
-	"loop",
-	"low",
-	"max",
-	"maxlength",
-	"media",
-	"method",
-	"min",
-	"minlength",
-	"multiple",
-	"muted",
-	"name",
-	"nonce",
-	"noshade",
-	"novalidate",
-	"nowrap",
-	"open",
-	"optimum",
-	"pattern",
-	"placeholder",
-	"playsinline",
-	"popover",
-	"popovertarget",
-	"popovertargetaction",
-	"poster",
-	"preload",
-	"pubdate",
-	"radiogroup",
-	"readonly",
-	"rel",
-	"required",
-	"rev",
-	"reversed",
-	"role",
-	"rows",
-	"rowspan",
-	"spellcheck",
-	"scope",
-	"selected",
-	"shape",
-	"size",
-	"sizes",
-	"span",
-	"srclang",
-	"start",
-	"src",
-	"srcset",
-	"step",
-	"style",
-	"summary",
-	"tabindex",
-	"title",
-	"translate",
-	"type",
-	"usemap",
-	"valign",
-	"value",
-	"width",
-	"wrap",
-	"xmlns",
-	"slot",
-])
-const svg = freeze([
-	"accent-height",
-	"accumulate",
-	"additive",
-	"alignment-baseline",
-	"ascent",
-	"attributename",
-	"attributetype",
-	"azimuth",
-	"basefrequency",
-	"baseline-shift",
-	"begin",
-	"bias",
-	"by",
-	"class",
-	"clip",
-	"clippathunits",
-	"clip-path",
-	"clip-rule",
-	"color",
-	"color-interpolation",
-	"color-interpolation-filters",
-	"color-profile",
-	"color-rendering",
-	"cx",
-	"cy",
-	"d",
-	"dx",
-	"dy",
-	"diffuseconstant",
-	"direction",
-	"display",
-	"divisor",
-	"dur",
-	"edgemode",
-	"elevation",
-	"end",
-	"fill",
-	"fill-opacity",
-	"fill-rule",
-	"filter",
-	"filterunits",
-	"flood-color",
-	"flood-opacity",
-	"font-family",
-	"font-size",
-	"font-size-adjust",
-	"font-stretch",
-	"font-style",
-	"font-variant",
-	"font-weight",
-	"fx",
-	"fy",
-	"g1",
-	"g2",
-	"glyph-name",
-	"glyphref",
-	"gradientunits",
-	"gradienttransform",
-	"height",
-	"href",
-	"id",
-	"image-rendering",
-	"in",
-	"in2",
-	"k",
-	"k1",
-	"k2",
-	"k3",
-	"k4",
-	"kerning",
-	"keypoints",
-	"keysplines",
-	"keytimes",
-	"lang",
-	"lengthadjust",
-	"letter-spacing",
-	"kernelmatrix",
-	"kernelunitlength",
-	"lighting-color",
-	"local",
-	"marker-end",
-	"marker-mid",
-	"marker-start",
-	"markerheight",
-	"markerunits",
-	"markerwidth",
-	"maskcontentunits",
-	"maskunits",
-	"max",
-	"mask",
-	"media",
-	"method",
-	"mode",
-	"min",
-	"name",
-	"numoctaves",
-	"offset",
-	"operator",
-	"opacity",
-	"order",
-	"orient",
-	"orientation",
-	"origin",
-	"overflow",
-	"paint-order",
-	"path",
-	"pathlength",
-	"patterncontentunits",
-	"patterntransform",
-	"patternunits",
-	"points",
-	"preservealpha",
-	"preserveaspectratio",
-	"primitiveunits",
-	"r",
-	"rx",
-	"ry",
-	"radius",
-	"refx",
-	"refy",
-	"repeatcount",
-	"repeatdur",
-	"restart",
-	"result",
-	"rotate",
-	"scale",
-	"seed",
-	"shape-rendering",
-	"specularconstant",
-	"specularexponent",
-	"spreadmethod",
-	"startoffset",
-	"stddeviation",
-	"stitchtiles",
-	"stop-color",
-	"stop-opacity",
-	"stroke-dasharray",
-	"stroke-dashoffset",
-	"stroke-linecap",
-	"stroke-linejoin",
-	"stroke-miterlimit",
-	"stroke-opacity",
-	"stroke",
-	"stroke-width",
-	"style",
-	"surfacescale",
-	"systemlanguage",
-	"tabindex",
-	"targetx",
-	"targety",
-	"transform",
-	"transform-origin",
-	"text-anchor",
-	"text-decoration",
-	"text-rendering",
-	"textlength",
-	"type",
-	"u1",
-	"u2",
-	"unicode",
-	"values",
-	"viewbox",
-	"visibility",
-	"version",
-	"vert-adv-y",
-	"vert-origin-x",
-	"vert-origin-y",
-	"width",
-	"word-spacing",
-	"wrap",
-	"writing-mode",
-	"xchannelselector",
-	"ychannelselector",
-	"x",
-	"x1",
-	"x2",
-	"xmlns",
-	"y",
-	"y1",
-	"y2",
-	"z",
-	"zoomandpan",
-])
-const mathMl = freeze([
-	"accent",
-	"accentunder",
-	"align",
-	"bevelled",
-	"close",
-	"columnsalign",
-	"columnlines",
-	"columnspan",
-	"denomalign",
-	"depth",
-	"dir",
-	"display",
-	"displaystyle",
-	"encoding",
-	"fence",
-	"frame",
-	"height",
-	"href",
-	"id",
-	"largeop",
-	"length",
-	"linethickness",
-	"lspace",
-	"lquote",
-	"mathbackground",
-	"mathcolor",
-	"mathsize",
-	"mathvariant",
-	"maxsize",
-	"minsize",
-	"movablelimits",
-	"notation",
-	"numalign",
-	"open",
-	"rowalign",
-	"rowlines",
-	"rowspacing",
-	"rowspan",
-	"rspace",
-	"rquote",
-	"scriptlevel",
-	"scriptminsize",
-	"scriptsizemultiplier",
-	"selection",
-	"separator",
-	"separators",
-	"stretchy",
-	"subscriptshift",
-	"supscriptshift",
-	"symmetric",
-	"voffset",
-	"width",
-	"xmlns",
-])
-const xml = freeze([
-	"xlink:href",
-	"xml:id",
-	"xlink:title",
-	"xml:space",
-	"xmlns:xlink",
-])
-
-// eslint-disable-next-line unicorn/better-regex
-const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm) // Specify template detection regex for SAFE_FOR_TEMPLATES mode
-const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm)
-const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm)
-const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/) // eslint-disable-line no-useless-escape
-const ARIA_ATTR = seal(/^aria-[\-\w]+$/) // eslint-disable-line no-useless-escape
-const IS_ALLOWED_URI = seal(
-	/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i, // eslint-disable-line no-useless-escape
-)
-const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i)
-const ATTR_WHITESPACE = seal(
-	/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g, // eslint-disable-line no-control-regex
-)
-const DOCTYPE_NAME = seal(/^html$/i)
-const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i)
-
-var EXPRESSIONS = /*#__PURE__*/ Object.freeze({
-	__proto__: null,
-	MUSTACHE_EXPR: MUSTACHE_EXPR,
-	ERB_EXPR: ERB_EXPR,
-	TMPLIT_EXPR: TMPLIT_EXPR,
-	DATA_ATTR: DATA_ATTR,
-	ARIA_ATTR: ARIA_ATTR,
-	IS_ALLOWED_URI: IS_ALLOWED_URI,
-	IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
-	ATTR_WHITESPACE: ATTR_WHITESPACE,
-	DOCTYPE_NAME: DOCTYPE_NAME,
-	CUSTOM_ELEMENT: CUSTOM_ELEMENT,
-})
-
-// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
-const NODE_TYPE = {
-	element: 1,
-	attribute: 2,
-	text: 3,
-	cdataSection: 4,
-	entityReference: 5,
-	// Deprecated
-	entityNode: 6,
-	// Deprecated
-	progressingInstruction: 7,
-	comment: 8,
-	document: 9,
-	documentType: 10,
-	documentFragment: 11,
-	notation: 12, // Deprecated
-}
-const getGlobal = function getGlobal() {
-	return typeof window === "undefined" ? null : window
-}
-
-/**
- * Creates a no-op policy for internal use only.
- * Don't export this function outside this module!
- * @param {TrustedTypePolicyFactory} trustedTypes The policy factory.
- * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
- * @return {TrustedTypePolicy} The policy created (or null, if Trusted Types
- * are not supported or creating the policy failed).
- */
-const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(
-	trustedTypes,
-	purifyHostElement,
-) {
-	if (
-		typeof trustedTypes !== "object" ||
-		typeof trustedTypes.createPolicy !== "function"
-	) {
-		return null
-	}
-
-	// Allow the callers to control the unique policy name
-	// by adding a data-tt-policy-suffix to the script element with the DOMPurify.
-	// Policy creation with duplicate names throws in Trusted Types.
-	let suffix = null
-	const ATTR_NAME = "data-tt-policy-suffix"
-	if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
-		suffix = purifyHostElement.getAttribute(ATTR_NAME)
-	}
-	const policyName = "dompurify" + (suffix ? "#" + suffix : "")
-	try {
-		return trustedTypes.createPolicy(policyName, {
-			createHTML(html) {
-				return html
-			},
-			createScriptURL(scriptUrl) {
-				return scriptUrl
-			},
-		})
-	} catch (_) {
-		// Policy creation failed (most likely another DOMPurify script has
-		// already run). Skip creating the policy, as this will only cause errors
-		// if TT are enforced.
-		console.warn("TrustedTypes policy " + policyName + " could not be created.")
-		return null
-	}
-}
-function createDOMPurify() {
-	let window =
-		arguments.length > 0 && arguments[0] !== undefined
-			? arguments[0]
-			: getGlobal()
-	const DOMPurify = (root) => createDOMPurify(root)
-
-	/**
-	 * Version label, exposed for easier checks
-	 * if DOMPurify is up to date or not
-	 */
-	DOMPurify.version = "3.1.6"
-
-	/**
-	 * Array of elements that DOMPurify removed during sanitation.
-	 * Empty if nothing was removed.
-	 */
-	DOMPurify.removed = []
-	if (
-		!window ||
-		!window.document ||
-		window.document.nodeType !== NODE_TYPE.document
-	) {
-		// Not running in a browser, provide a factory function
-		// so that you can pass your own Window
-		DOMPurify.isSupported = false
-		return DOMPurify
-	}
-	let { document } = window
-	const originalDocument = document
-	const currentScript = originalDocument.currentScript
-	const {
-		DocumentFragment,
-		HTMLTemplateElement,
-		Node,
-		Element,
-		NodeFilter,
-		NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
-		HTMLFormElement,
-		DOMParser,
-		trustedTypes,
-	} = window
-	const ElementPrototype = Element.prototype
-	const cloneNode = lookupGetter(ElementPrototype, "cloneNode")
-	const remove = lookupGetter(ElementPrototype, "remove")
-	const getNextSibling = lookupGetter(ElementPrototype, "nextSibling")
-	const getChildNodes = lookupGetter(ElementPrototype, "childNodes")
-	const getParentNode = lookupGetter(ElementPrototype, "parentNode")
-
-	// As per issue #47, the web-components registry is inherited by a
-	// new document created via createHTMLDocument. As per the spec
-	// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
-	// a new empty registry is used when creating a template contents owner
-	// document, so we use that as our parent document to ensure nothing
-	// is inherited.
-	if (typeof HTMLTemplateElement === "function") {
-		const template = document.createElement("template")
-		if (template.content && template.content.ownerDocument) {
-			document = template.content.ownerDocument
-		}
-	}
-	let trustedTypesPolicy
-	let emptyHTML = ""
-	const {
-		implementation,
-		createNodeIterator,
-		createDocumentFragment,
-		getElementsByTagName,
-	} = document
-	const { importNode } = originalDocument
-	let hooks = {}
-
-	/**
-	 * Expose whether this browser supports running the full DOMPurify.
-	 */
-	DOMPurify.isSupported =
-		typeof entries === "function" &&
-		typeof getParentNode === "function" &&
-		implementation &&
-		implementation.createHTMLDocument !== undefined
-	const {
-		MUSTACHE_EXPR,
-		ERB_EXPR,
-		TMPLIT_EXPR,
-		DATA_ATTR,
-		ARIA_ATTR,
-		IS_SCRIPT_OR_DATA,
-		ATTR_WHITESPACE,
-		CUSTOM_ELEMENT,
-	} = EXPRESSIONS
-	let { IS_ALLOWED_URI: IS_ALLOWED_URI$1 } = EXPRESSIONS
-
-	/**
-	 * We consider the elements and attributes below to be safe. Ideally
-	 * don't add any new ones but feel free to remove unwanted ones.
-	 */
-
-	/* allowed element names */
-	let ALLOWED_TAGS = null
-	const DEFAULT_ALLOWED_TAGS = addToSet({}, [
-		...html$1,
-		...svg$1,
-		...svgFilters,
-		...mathMl$1,
-		...text,
-	])
-
-	/* Allowed attribute names */
-	let ALLOWED_ATTR = null
-	const DEFAULT_ALLOWED_ATTR = addToSet({}, [
-		...html,
-		...svg,
-		...mathMl,
-		...xml,
-	])
-
-	/*
-	 * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
-	 * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
-	 * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
-	 * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
-	 */
-	let CUSTOM_ELEMENT_HANDLING = Object.seal(
-		create(null, {
-			tagNameCheck: {
-				writable: true,
-				configurable: false,
-				enumerable: true,
-				value: null,
-			},
-			attributeNameCheck: {
-				writable: true,
-				configurable: false,
-				enumerable: true,
-				value: null,
-			},
-			allowCustomizedBuiltInElements: {
-				writable: true,
-				configurable: false,
-				enumerable: true,
-				value: false,
-			},
-		}),
-	)
-
-	/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
-	let FORBID_TAGS = null
-
-	/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
-	let FORBID_ATTR = null
-
-	/* Decide if ARIA attributes are okay */
-	let ALLOW_ARIA_ATTR = true
-
-	/* Decide if custom data attributes are okay */
-	let ALLOW_DATA_ATTR = true
-
-	/* Decide if unknown protocols are okay */
-	let ALLOW_UNKNOWN_PROTOCOLS = false
-
-	/* Decide if self-closing tags in attributes are allowed.
-	 * Usually removed due to a mXSS issue in jQuery 3.0 */
-	let ALLOW_SELF_CLOSE_IN_ATTR = true
-
-	/* Output should be safe for common template engines.
-	 * This means, DOMPurify removes data attributes, mustaches and ERB
-	 */
-	let SAFE_FOR_TEMPLATES = false
-
-	/* Output should be safe even for XML used within HTML and alike.
-	 * This means, DOMPurify removes comments when containing risky content.
-	 */
-	let SAFE_FOR_XML = true
-
-	/* Decide if document with <html>... should be returned */
-	let WHOLE_DOCUMENT = false
-
-	/* Track whether config is already set on this instance of DOMPurify. */
-	let SET_CONFIG = false
-
-	/* Decide if all elements (e.g. style, script) must be children of
-	 * document.body. By default, browsers might move them to document.head */
-	let FORCE_BODY = false
-
-	/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
-	 * string (or a TrustedHTML object if Trusted Types are supported).
-	 * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
-	 */
-	let RETURN_DOM = false
-
-	/* Decide if a DOM `DocumentFragment` should be returned, instead of a html
-	 * string  (or a TrustedHTML object if Trusted Types are supported) */
-	let RETURN_DOM_FRAGMENT = false
-
-	/* Try to return a Trusted Type object instead of a string, return a string in
-	 * case Trusted Types are not supported  */
-	let RETURN_TRUSTED_TYPE = false
-
-	/* Output should be free from DOM clobbering attacks?
-	 * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
-	 */
-	let SANITIZE_DOM = true
-
-	/* Achieve full DOM Clobbering protection by isolating the namespace of named
-	 * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
-	 *
-	 * HTML/DOM spec rules that enable DOM Clobbering:
-	 *   - Named Access on Window (§7.3.3)
-	 *   - DOM Tree Accessors (§3.1.5)
-	 *   - Form Element Parent-Child Relations (§4.10.3)
-	 *   - Iframe srcdoc / Nested WindowProxies (§4.8.5)
-	 *   - HTMLCollection (§4.2.10.2)
-	 *
-	 * Namespace isolation is implemented by prefixing `id` and `name` attributes
-	 * with a constant string, i.e., `user-content-`
-	 */
-	let SANITIZE_NAMED_PROPS = false
-	const SANITIZE_NAMED_PROPS_PREFIX = "user-content-"
-
-	/* Keep element content when removing element? */
-	let KEEP_CONTENT = true
-
-	/* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
-	 * of importing it into a new Document and returning a sanitized copy */
-	let IN_PLACE = false
-
-	/* Allow usage of profiles like html, svg and mathMl */
-	let USE_PROFILES = {}
-
-	/* Tags to ignore content of when KEEP_CONTENT is true */
-	let FORBID_CONTENTS = null
-	const DEFAULT_FORBID_CONTENTS = addToSet({}, [
-		"annotation-xml",
-		"audio",
-		"colgroup",
-		"desc",
-		"foreignobject",
-		"head",
-		"iframe",
-		"math",
-		"mi",
-		"mn",
-		"mo",
-		"ms",
-		"mtext",
-		"noembed",
-		"noframes",
-		"noscript",
-		"plaintext",
-		"script",
-		"style",
-		"svg",
-		"template",
-		"thead",
-		"title",
-		"video",
-		"xmp",
-	])
-
-	/* Tags that are safe for data: URIs */
-	let DATA_URI_TAGS = null
-	const DEFAULT_DATA_URI_TAGS = addToSet({}, [
-		"audio",
-		"video",
-		"img",
-		"source",
-		"image",
-		"track",
-	])
-
-	/* Attributes safe for values like "javascript:" */
-	let URI_SAFE_ATTRIBUTES = null
-	const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [
-		"alt",
-		"class",
-		"for",
-		"id",
-		"label",
-		"name",
-		"pattern",
-		"placeholder",
-		"role",
-		"summary",
-		"title",
-		"value",
-		"style",
-		"xmlns",
-	])
-	const MATHML_NAMESPACE = "http://www.w3.org/1998/Math/MathML"
-	const SVG_NAMESPACE = "http://www.w3.org/2000/svg"
-	const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"
-	/* Document namespace */
-	let NAMESPACE = HTML_NAMESPACE
-	let IS_EMPTY_INPUT = false
-
-	/* Allowed XHTML+XML namespaces */
-	let ALLOWED_NAMESPACES = null
-	const DEFAULT_ALLOWED_NAMESPACES = addToSet(
-		{},
-		[MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE],
-		stringToString,
-	)
-
-	/* Parsing of strict XHTML documents */
-	let PARSER_MEDIA_TYPE = null
-	const SUPPORTED_PARSER_MEDIA_TYPES = ["application/xhtml+xml", "text/html"]
-	const DEFAULT_PARSER_MEDIA_TYPE = "text/html"
-	let transformCaseFunc = null
-
-	/* Keep a reference to config to pass to hooks */
-	let CONFIG = null
-
-	/* Ideally, do not touch anything below this line */
-	/* ______________________________________________ */
-
-	const formElement = document.createElement("form")
-	const isRegexOrFunction = function isRegexOrFunction(testValue) {
-		return testValue instanceof RegExp || testValue instanceof Function
-	}
-
-	/**
-	 * _parseConfig
-	 *
-	 * @param  {Object} cfg optional config literal
-	 */
-	// eslint-disable-next-line complexity
-	const _parseConfig = function _parseConfig() {
-		let cfg =
-			arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}
-		if (CONFIG && CONFIG === cfg) {
-			return
-		}
-
-		/* Shield configuration object from tampering */
-		if (!cfg || typeof cfg !== "object") {
-			cfg = {}
-		}
-
-		/* Shield configuration object from prototype pollution */
-		cfg = clone(cfg)
-		PARSER_MEDIA_TYPE =
-			// eslint-disable-next-line unicorn/prefer-includes
-			SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1
-				? DEFAULT_PARSER_MEDIA_TYPE
-				: cfg.PARSER_MEDIA_TYPE
-
-		// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
-		transformCaseFunc =
-			PARSER_MEDIA_TYPE === "application/xhtml+xml"
-				? stringToString
-				: stringToLowerCase
-
-		/* Set configuration parameters */
-		ALLOWED_TAGS = objectHasOwnProperty(cfg, "ALLOWED_TAGS")
-			? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc)
-			: DEFAULT_ALLOWED_TAGS
-		ALLOWED_ATTR = objectHasOwnProperty(cfg, "ALLOWED_ATTR")
-			? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc)
-			: DEFAULT_ALLOWED_ATTR
-		ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, "ALLOWED_NAMESPACES")
-			? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString)
-			: DEFAULT_ALLOWED_NAMESPACES
-		URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, "ADD_URI_SAFE_ATTR")
-			? addToSet(
-					clone(DEFAULT_URI_SAFE_ATTRIBUTES),
-					// eslint-disable-line indent
-					cfg.ADD_URI_SAFE_ATTR,
-					// eslint-disable-line indent
-					transformCaseFunc, // eslint-disable-line indent
-				) // eslint-disable-line indent
-			: DEFAULT_URI_SAFE_ATTRIBUTES
-		DATA_URI_TAGS = objectHasOwnProperty(cfg, "ADD_DATA_URI_TAGS")
-			? addToSet(
-					clone(DEFAULT_DATA_URI_TAGS),
-					// eslint-disable-line indent
-					cfg.ADD_DATA_URI_TAGS,
-					// eslint-disable-line indent
-					transformCaseFunc, // eslint-disable-line indent
-				) // eslint-disable-line indent
-			: DEFAULT_DATA_URI_TAGS
-		FORBID_CONTENTS = objectHasOwnProperty(cfg, "FORBID_CONTENTS")
-			? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc)
-			: DEFAULT_FORBID_CONTENTS
-		FORBID_TAGS = objectHasOwnProperty(cfg, "FORBID_TAGS")
-			? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc)
-			: {}
-		FORBID_ATTR = objectHasOwnProperty(cfg, "FORBID_ATTR")
-			? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc)
-			: {}
-		USE_PROFILES = objectHasOwnProperty(cfg, "USE_PROFILES")
-			? cfg.USE_PROFILES
-			: false
-		ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false // Default true
-		ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false // Default true
-		ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false // Default false
-		ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false // Default true
-		SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false // Default false
-		SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false // Default true
-		WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false // Default false
-		RETURN_DOM = cfg.RETURN_DOM || false // Default false
-		RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false // Default false
-		RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false // Default false
-		FORCE_BODY = cfg.FORCE_BODY || false // Default false
-		SANITIZE_DOM = cfg.SANITIZE_DOM !== false // Default true
-		SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false // Default false
-		KEEP_CONTENT = cfg.KEEP_CONTENT !== false // Default true
-		IN_PLACE = cfg.IN_PLACE || false // Default false
-		IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI
-		NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE
-		CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {}
-		if (
-			cfg.CUSTOM_ELEMENT_HANDLING &&
-			isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)
-		) {
-			CUSTOM_ELEMENT_HANDLING.tagNameCheck =
-				cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck
-		}
-		if (
-			cfg.CUSTOM_ELEMENT_HANDLING &&
-			isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)
-		) {
-			CUSTOM_ELEMENT_HANDLING.attributeNameCheck =
-				cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck
-		}
-		if (
-			cfg.CUSTOM_ELEMENT_HANDLING &&
-			typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements ===
-				"boolean"
-		) {
-			CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements =
-				cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements
-		}
-		if (SAFE_FOR_TEMPLATES) {
-			ALLOW_DATA_ATTR = false
-		}
-		if (RETURN_DOM_FRAGMENT) {
-			RETURN_DOM = true
-		}
-
-		/* Parse profile info */
-		if (USE_PROFILES) {
-			ALLOWED_TAGS = addToSet({}, text)
-			ALLOWED_ATTR = []
-			if (USE_PROFILES.html === true) {
-				addToSet(ALLOWED_TAGS, html$1)
-				addToSet(ALLOWED_ATTR, html)
-			}
-			if (USE_PROFILES.svg === true) {
-				addToSet(ALLOWED_TAGS, svg$1)
-				addToSet(ALLOWED_ATTR, svg)
-				addToSet(ALLOWED_ATTR, xml)
-			}
-			if (USE_PROFILES.svgFilters === true) {
-				addToSet(ALLOWED_TAGS, svgFilters)
-				addToSet(ALLOWED_ATTR, svg)
-				addToSet(ALLOWED_ATTR, xml)
-			}
-			if (USE_PROFILES.mathMl === true) {
-				addToSet(ALLOWED_TAGS, mathMl$1)
-				addToSet(ALLOWED_ATTR, mathMl)
-				addToSet(ALLOWED_ATTR, xml)
-			}
-		}
-
-		/* Merge configuration parameters */
-		if (cfg.ADD_TAGS) {
-			if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
-				ALLOWED_TAGS = clone(ALLOWED_TAGS)
-			}
-			addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc)
-		}
-		if (cfg.ADD_ATTR) {
-			if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
-				ALLOWED_ATTR = clone(ALLOWED_ATTR)
-			}
-			addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc)
-		}
-		if (cfg.ADD_URI_SAFE_ATTR) {
-			addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc)
-		}
-		if (cfg.FORBID_CONTENTS) {
-			if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
-				FORBID_CONTENTS = clone(FORBID_CONTENTS)
-			}
-			addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc)
-		}
-
-		/* Add #text in case KEEP_CONTENT is set to true */
-		if (KEEP_CONTENT) {
-			ALLOWED_TAGS["#text"] = true
-		}
-
-		/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
-		if (WHOLE_DOCUMENT) {
-			addToSet(ALLOWED_TAGS, ["html", "head", "body"])
-		}
-
-		/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
-		if (ALLOWED_TAGS.table) {
-			addToSet(ALLOWED_TAGS, ["tbody"])
-			delete FORBID_TAGS.tbody
-		}
-		if (cfg.TRUSTED_TYPES_POLICY) {
-			if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== "function") {
-				throw typeErrorCreate(
-					'TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.',
-				)
-			}
-			if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== "function") {
-				throw typeErrorCreate(
-					'TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.',
-				)
-			}
-
-			// Overwrite existing TrustedTypes policy.
-			trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY
-
-			// Sign local variables required by `sanitize`.
-			emptyHTML = trustedTypesPolicy.createHTML("")
-		} else {
-			// Uninitialized policy, attempt to initialize the internal dompurify policy.
-			if (trustedTypesPolicy === undefined) {
-				trustedTypesPolicy = _createTrustedTypesPolicy(
-					trustedTypes,
-					currentScript,
-				)
-			}
-
-			// If creating the internal policy succeeded sign internal variables.
-			if (trustedTypesPolicy !== null && typeof emptyHTML === "string") {
-				emptyHTML = trustedTypesPolicy.createHTML("")
-			}
-		}
-
-		// Prevent further manipulation of configuration.
-		// Not available in IE8, Safari 5, etc.
-		if (freeze) {
-			freeze(cfg)
-		}
-		CONFIG = cfg
-	}
-	const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, [
-		"mi",
-		"mo",
-		"mn",
-		"ms",
-		"mtext",
-	])
-	const HTML_INTEGRATION_POINTS = addToSet({}, [
-		"foreignobject",
-		"annotation-xml",
-	])
-
-	// Certain elements are allowed in both SVG and HTML
-	// namespace. We need to specify them explicitly
-	// so that they don't get erroneously deleted from
-	// HTML namespace.
-	const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [
-		"title",
-		"style",
-		"font",
-		"a",
-		"script",
-	])
-
-	/* Keep track of all possible SVG and MathML tags
-	 * so that we can perform the namespace checks
-	 * correctly. */
-	const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed])
-	const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed])
-
-	/**
-	 * @param  {Element} element a DOM element whose namespace is being checked
-	 * @returns {boolean} Return false if the element has a
-	 *  namespace that a spec-compliant parser would never
-	 *  return. Return true otherwise.
-	 */
-	const _checkValidNamespace = function _checkValidNamespace(element) {
-		let parent = getParentNode(element)
-
-		// In JSDOM, if we're inside shadow DOM, then parentNode
-		// can be null. We just simulate parent in this case.
-		if (!parent || !parent.tagName) {
-			parent = {
-				namespaceURI: NAMESPACE,
-				tagName: "template",
-			}
-		}
-		const tagName = stringToLowerCase(element.tagName)
-		const parentTagName = stringToLowerCase(parent.tagName)
-		if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
-			return false
-		}
-		if (element.namespaceURI === SVG_NAMESPACE) {
-			// The only way to switch from HTML namespace to SVG
-			// is via <svg>. If it happens via any other tag, then
-			// it should be killed.
-			if (parent.namespaceURI === HTML_NAMESPACE) {
-				return tagName === "svg"
-			}
-
-			// The only way to switch from MathML to SVG is via`
-			// svg if parent is either <annotation-xml> or MathML
-			// text integration points.
-			if (parent.namespaceURI === MATHML_NAMESPACE) {
-				return (
-					tagName === "svg" &&
-					(parentTagName === "annotation-xml" ||
-						MATHML_TEXT_INTEGRATION_POINTS[parentTagName])
-				)
-			}
-
-			// We only allow elements that are defined in SVG
-			// spec. All others are disallowed in SVG namespace.
-			return Boolean(ALL_SVG_TAGS[tagName])
-		}
-		if (element.namespaceURI === MATHML_NAMESPACE) {
-			// The only way to switch from HTML namespace to MathML
-			// is via <math>. If it happens via any other tag, then
-			// it should be killed.
-			if (parent.namespaceURI === HTML_NAMESPACE) {
-				return tagName === "math"
-			}
-
-			// The only way to switch from SVG to MathML is via
-			// <math> and HTML integration points
-			if (parent.namespaceURI === SVG_NAMESPACE) {
-				return tagName === "math" && HTML_INTEGRATION_POINTS[parentTagName]
-			}
-
-			// We only allow elements that are defined in MathML
-			// spec. All others are disallowed in MathML namespace.
-			return Boolean(ALL_MATHML_TAGS[tagName])
-		}
-		if (element.namespaceURI === HTML_NAMESPACE) {
-			// The only way to switch from SVG to HTML is via
-			// HTML integration points, and from MathML to HTML
-			// is via MathML text integration points
-			if (
-				parent.namespaceURI === SVG_NAMESPACE &&
-				!HTML_INTEGRATION_POINTS[parentTagName]
-			) {
-				return false
-			}
-			if (
-				parent.namespaceURI === MATHML_NAMESPACE &&
-				!MATHML_TEXT_INTEGRATION_POINTS[parentTagName]
-			) {
-				return false
-			}
-
-			// We disallow tags that are specific for MathML
-			// or SVG and should never appear in HTML namespace
-			return (
-				!ALL_MATHML_TAGS[tagName] &&
-				(COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName])
-			)
-		}
-
-		// For XHTML and XML documents that support custom namespaces
-		if (
-			PARSER_MEDIA_TYPE === "application/xhtml+xml" &&
-			ALLOWED_NAMESPACES[element.namespaceURI]
-		) {
-			return true
-		}
-
-		// The code should never reach this place (this means
-		// that the element somehow got namespace that is not
-		// HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
-		// Return false just in case.
-		return false
-	}
-
-	/**
-	 * _forceRemove
-	 *
-	 * @param  {Node} node a DOM node
-	 */
-	const _forceRemove = function _forceRemove(node) {
-		arrayPush(DOMPurify.removed, {
-			element: node,
-		})
-		try {
-			// eslint-disable-next-line unicorn/prefer-dom-node-remove
-			getParentNode(node).removeChild(node)
-		} catch (_) {
-			remove(node)
-		}
-	}
-
-	/**
-	 * _removeAttribute
-	 *
-	 * @param  {String} name an Attribute name
-	 * @param  {Node} node a DOM node
-	 */
-	const _removeAttribute = function _removeAttribute(name, node) {
-		try {
-			arrayPush(DOMPurify.removed, {
-				attribute: node.getAttributeNode(name),
-				from: node,
-			})
-		} catch (_) {
-			arrayPush(DOMPurify.removed, {
-				attribute: null,
-				from: node,
-			})
-		}
-		node.removeAttribute(name)
-
-		// We void attribute values for unremovable "is"" attributes
-		if (name === "is" && !ALLOWED_ATTR[name]) {
-			if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
-				try {
-					_forceRemove(node)
-				} catch (_) {}
-			} else {
-				try {
-					node.setAttribute(name, "")
-				} catch (_) {}
-			}
-		}
-	}
-
-	/**
-	 * _initDocument
-	 *
-	 * @param  {String} dirty a string of dirty markup
-	 * @return {Document} a DOM, filled with the dirty markup
-	 */
-	const _initDocument = function _initDocument(dirty) {
-		/* Create a HTML document */
-		let doc = null
-		let leadingWhitespace = null
-		if (FORCE_BODY) {
-			dirty = "<remove></remove>" + dirty
-		} else {
-			/* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
-			const matches = stringMatch(dirty, /^[\r\n\t ]+/)
-			leadingWhitespace = matches && matches[0]
-		}
-		if (
-			PARSER_MEDIA_TYPE === "application/xhtml+xml" &&
-			NAMESPACE === HTML_NAMESPACE
-		) {
-			// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
-			dirty =
-				'<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' +
-				dirty +
-				"</body></html>"
-		}
-		const dirtyPayload = trustedTypesPolicy
-			? trustedTypesPolicy.createHTML(dirty)
-			: dirty
-		/*
-		 * Use the DOMParser API by default, fallback later if needs be
-		 * DOMParser not work for svg when has multiple root element.
-		 */
-		if (NAMESPACE === HTML_NAMESPACE) {
-			try {
-				doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE)
-			} catch (_) {}
-		}
-
-		/* Use createHTMLDocument in case DOMParser is not available */
-		if (!doc || !doc.documentElement) {
-			doc = implementation.createDocument(NAMESPACE, "template", null)
-			try {
-				doc.documentElement.innerHTML = IS_EMPTY_INPUT
-					? emptyHTML
-					: dirtyPayload
-			} catch (_) {
-				// Syntax error if dirtyPayload is invalid xml
-			}
-		}
-		const body = doc.body || doc.documentElement
-		if (dirty && leadingWhitespace) {
-			body.insertBefore(
-				document.createTextNode(leadingWhitespace),
-				body.childNodes[0] || null,
-			)
-		}
-
-		/* Work on whole document or just its body */
-		if (NAMESPACE === HTML_NAMESPACE) {
-			return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? "html" : "body")[0]
-		}
-		return WHOLE_DOCUMENT ? doc.documentElement : body
-	}
-
-	/**
-	 * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
-	 *
-	 * @param  {Node} root The root element or node to start traversing on.
-	 * @return {NodeIterator} The created NodeIterator
-	 */
-	const _createNodeIterator = function _createNodeIterator(root) {
-		return createNodeIterator.call(
-			root.ownerDocument || root,
-			root,
-			// eslint-disable-next-line no-bitwise
-			NodeFilter.SHOW_ELEMENT |
-				NodeFilter.SHOW_COMMENT |
-				NodeFilter.SHOW_TEXT |
-				NodeFilter.SHOW_PROCESSING_INSTRUCTION |
-				NodeFilter.SHOW_CDATA_SECTION,
-			null,
-		)
-	}
-
-	/**
-	 * _isClobbered
-	 *
-	 * @param  {Node} elm element to check for clobbering attacks
-	 * @return {Boolean} true if clobbered, false if safe
-	 */
-	const _isClobbered = function _isClobbered(elm) {
-		return (
-			elm instanceof HTMLFormElement &&
-			(typeof elm.nodeName !== "string" ||
-				typeof elm.textContent !== "string" ||
-				typeof elm.removeChild !== "function" ||
-				!(elm.attributes instanceof NamedNodeMap) ||
-				typeof elm.removeAttribute !== "function" ||
-				typeof elm.setAttribute !== "function" ||
-				typeof elm.namespaceURI !== "string" ||
-				typeof elm.insertBefore !== "function" ||
-				typeof elm.hasChildNodes !== "function")
-		)
-	}
-
-	/**
-	 * Checks whether the given object is a DOM node.
-	 *
-	 * @param  {Node} object object to check whether it's a DOM node
-	 * @return {Boolean} true is object is a DOM node
-	 */
-	const _isNode = function _isNode(object) {
-		return typeof Node === "function" && object instanceof Node
-	}
-
-	/**
-	 * _executeHook
-	 * Execute user configurable hooks
-	 *
-	 * @param  {String} entryPoint  Name of the hook's entry point
-	 * @param  {Node} currentNode node to work on with the hook
-	 * @param  {Object} data additional hook parameters
-	 */
-	const _executeHook = function _executeHook(entryPoint, currentNode, data) {
-		if (!hooks[entryPoint]) {
-			return
-		}
-		arrayForEach(hooks[entryPoint], (hook) => {
-			hook.call(DOMPurify, currentNode, data, CONFIG)
-		})
-	}
-
-	/**
-	 * _sanitizeElements
-	 *
-	 * @protect nodeName
-	 * @protect textContent
-	 * @protect removeChild
-	 *
-	 * @param   {Node} currentNode to check for permission to exist
-	 * @return  {Boolean} true if node was killed, false if left alive
-	 */
-	const _sanitizeElements = function _sanitizeElements(currentNode) {
-		let content = null
-
-		/* Execute a hook if present */
-		_executeHook("beforeSanitizeElements", currentNode, null)
-
-		/* Check if element is clobbered or can clobber */
-		if (_isClobbered(currentNode)) {
-			_forceRemove(currentNode)
-			return true
-		}
-
-		/* Now let's check the element's type and name */
-		const tagName = transformCaseFunc(currentNode.nodeName)
-
-		/* Execute a hook if present */
-		_executeHook("uponSanitizeElement", currentNode, {
-			tagName,
-			allowedTags: ALLOWED_TAGS,
-		})
-
-		/* Detect mXSS attempts abusing namespace confusion */
-		if (
-			currentNode.hasChildNodes() &&
-			!_isNode(currentNode.firstElementChild) &&
-			regExpTest(/<[/\w]/g, currentNode.innerHTML) &&
-			regExpTest(/<[/\w]/g, currentNode.textContent)
-		) {
-			_forceRemove(currentNode)
-			return true
-		}
-
-		/* Remove any occurrence of processing instructions */
-		if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
-			_forceRemove(currentNode)
-			return true
-		}
-
-		/* Remove any kind of possibly harmful comments */
-		if (
-			SAFE_FOR_XML &&
-			currentNode.nodeType === NODE_TYPE.comment &&
-			regExpTest(/<[/\w]/g, currentNode.data)
-		) {
-			_forceRemove(currentNode)
-			return true
-		}
-
-		/* Remove element if anything forbids its presence */
-		if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
-			/* Check if we have a custom element to handle */
-			if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
-				if (
-					CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&
-					regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)
-				) {
-					return false
-				}
-				if (
-					CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&
-					CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)
-				) {
-					return false
-				}
-			}
-
-			/* Keep content except for bad-listed elements */
-			if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
-				const parentNode = getParentNode(currentNode) || currentNode.parentNode
-				const childNodes = getChildNodes(currentNode) || currentNode.childNodes
-				if (childNodes && parentNode) {
-					const childCount = childNodes.length
-					for (let i = childCount - 1; i >= 0; --i) {
-						const childClone = cloneNode(childNodes[i], true)
-						childClone.__removalCount = (currentNode.__removalCount || 0) + 1
-						parentNode.insertBefore(childClone, getNextSibling(currentNode))
-					}
-				}
-			}
-			_forceRemove(currentNode)
-			return true
-		}
-
-		/* Check whether element has a valid namespace */
-		if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
-			_forceRemove(currentNode)
-			return true
-		}
-
-		/* Make sure that older browsers don't get fallback-tag mXSS */
-		if (
-			(tagName === "noscript" ||
-				tagName === "noembed" ||
-				tagName === "noframes") &&
-			regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)
-		) {
-			_forceRemove(currentNode)
-			return true
-		}
-
-		/* Sanitize element content to be template-safe */
-		if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
-			/* Get the element's text content */
-			content = currentNode.textContent
-			arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => {
-				content = stringReplace(content, expr, " ")
-			})
-			if (currentNode.textContent !== content) {
-				arrayPush(DOMPurify.removed, {
-					element: currentNode.cloneNode(),
-				})
-				currentNode.textContent = content
-			}
-		}
-
-		/* Execute a hook if present */
-		_executeHook("afterSanitizeElements", currentNode, null)
-		return false
-	}
-
-	/**
-	 * _isValidAttribute
-	 *
-	 * @param  {string} lcTag Lowercase tag name of containing element.
-	 * @param  {string} lcName Lowercase attribute name.
-	 * @param  {string} value Attribute value.
-	 * @return {Boolean} Returns true if `value` is valid, otherwise false.
-	 */
-	// eslint-disable-next-line complexity
-	const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
-		/* Make sure attribute cannot clobber */
-		if (
-			SANITIZE_DOM &&
-			(lcName === "id" || lcName === "name") &&
-			(value in document || value in formElement)
-		) {
-			return false
-		}
-
-		/* Allow valid data-* attributes: At least one character after "-"
-        (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
-        XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
-        We don't need to check the value; it's always URI safe. */
-		if (
-			ALLOW_DATA_ATTR &&
-			!FORBID_ATTR[lcName] &&
-			regExpTest(DATA_ATTR, lcName)
-		);
-		else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName));
-		else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
-			if (
-				// First condition does a very basic check if a) it's basically a valid custom element tagname AND
-				// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
-				// and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
-				(_isBasicCustomElement(lcTag) &&
-					((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&
-						regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag)) ||
-						(CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&
-							CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag))) &&
-					((CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp &&
-						regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName)) ||
-						(CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function &&
-							CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)))) ||
-				// Alternative, second condition checks if it's an `is`-attribute, AND
-				// the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
-				(lcName === "is" &&
-					CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements &&
-					((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&
-						regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value)) ||
-						(CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&
-							CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))))
-			);
-			else {
-				return false
-			}
-			/* Check value is safe. First, is attr inert? If so, is safe */
-		} else if (URI_SAFE_ATTRIBUTES[lcName]);
-		else if (
-			regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ""))
-		);
-		else if (
-			(lcName === "src" || lcName === "xlink:href" || lcName === "href") &&
-			lcTag !== "script" &&
-			stringIndexOf(value, "data:") === 0 &&
-			DATA_URI_TAGS[lcTag]
-		);
-		else if (
-			ALLOW_UNKNOWN_PROTOCOLS &&
-			!regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ""))
-		);
-		else if (value) {
-			return false
-		} else;
-		return true
-	}
-
-	/**
-	 * _isBasicCustomElement
-	 * checks if at least one dash is included in tagName, and it's not the first char
-	 * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
-	 *
-	 * @param {string} tagName name of the tag of the node to sanitize
-	 * @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
-	 */
-	const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
-		return tagName !== "annotation-xml" && stringMatch(tagName, CUSTOM_ELEMENT)
-	}
-
-	/**
-	 * _sanitizeAttributes
-	 *
-	 * @protect attributes
-	 * @protect nodeName
-	 * @protect removeAttribute
-	 * @protect setAttribute
-	 *
-	 * @param  {Node} currentNode to sanitize
-	 */
-	const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
-		/* Execute a hook if present */
-		_executeHook("beforeSanitizeAttributes", currentNode, null)
-		const { attributes } = currentNode
-
-		/* Check if we have attributes; if not we might have a text node */
-		if (!attributes) {
-			return
-		}
-		const hookEvent = {
-			attrName: "",
-			attrValue: "",
-			keepAttr: true,
-			allowedAttributes: ALLOWED_ATTR,
-		}
-		let l = attributes.length
-
-		/* Go backwards over all attributes; safely remove bad ones */
-		while (l--) {
-			const attr = attributes[l]
-			const { name, namespaceURI, value: attrValue } = attr
-			const lcName = transformCaseFunc(name)
-			let value = name === "value" ? attrValue : stringTrim(attrValue)
-
-			/* Execute a hook if present */
-			hookEvent.attrName = lcName
-			hookEvent.attrValue = value
-			hookEvent.keepAttr = true
-			hookEvent.forceKeepAttr = undefined // Allows developers to see this is a property they can set
-			_executeHook("uponSanitizeAttribute", currentNode, hookEvent)
-			value = hookEvent.attrValue
-
-			/* Work around a security issue with comments inside attributes */
-			if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
-				_removeAttribute(name, currentNode)
-				continue
-			}
-
-			/* Did the hooks approve of the attribute? */
-			if (hookEvent.forceKeepAttr) {
-				continue
-			}
-
-			/* Remove attribute */
-			_removeAttribute(name, currentNode)
-
-			/* Did the hooks approve of the attribute? */
-			if (!hookEvent.keepAttr) {
-				continue
-			}
-
-			/* Work around a security issue in jQuery 3.0 */
-			if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
-				_removeAttribute(name, currentNode)
-				continue
-			}
-
-			/* Sanitize attribute content to be template-safe */
-			if (SAFE_FOR_TEMPLATES) {
-				arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => {
-					value = stringReplace(value, expr, " ")
-				})
-			}
-
-			/* Is `value` valid for this attribute? */
-			const lcTag = transformCaseFunc(currentNode.nodeName)
-			if (!_isValidAttribute(lcTag, lcName, value)) {
-				continue
-			}
-
-			/* Full DOM Clobbering protection via namespace isolation,
-			 * Prefix id and name attributes with `user-content-`
-			 */
-			if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name")) {
-				// Remove the attribute with this value
-				_removeAttribute(name, currentNode)
-
-				// Prefix the value and later re-create the attribute with the sanitized value
-				value = SANITIZE_NAMED_PROPS_PREFIX + value
-			}
-
-			/* Handle attributes that require Trusted Types */
-			if (
-				trustedTypesPolicy &&
-				typeof trustedTypes === "object" &&
-				typeof trustedTypes.getAttributeType === "function"
-			) {
-				if (namespaceURI);
-				else {
-					switch (trustedTypes.getAttributeType(lcTag, lcName)) {
-						case "TrustedHTML": {
-							value = trustedTypesPolicy.createHTML(value)
-							break
-						}
-						case "TrustedScriptURL": {
-							value = trustedTypesPolicy.createScriptURL(value)
-							break
-						}
-					}
-				}
-			}
-
-			/* Handle invalid data-* attribute set by try-catching it */
-			try {
-				if (namespaceURI) {
-					currentNode.setAttributeNS(namespaceURI, name, value)
-				} else {
-					/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
-					currentNode.setAttribute(name, value)
-				}
-				if (_isClobbered(currentNode)) {
-					_forceRemove(currentNode)
-				} else {
-					arrayPop(DOMPurify.removed)
-				}
-			} catch (_) {}
-		}
-
-		/* Execute a hook if present */
-		_executeHook("afterSanitizeAttributes", currentNode, null)
-	}
-
-	/**
-	 * _sanitizeShadowDOM
-	 *
-	 * @param  {DocumentFragment} fragment to iterate over recursively
-	 */
-	const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
-		let shadowNode = null
-		const shadowIterator = _createNodeIterator(fragment)
-
-		/* Execute a hook if present */
-		_executeHook("beforeSanitizeShadowDOM", fragment, null)
-		while ((shadowNode = shadowIterator.nextNode())) {
-			/* Execute a hook if present */
-			_executeHook("uponSanitizeShadowNode", shadowNode, null)
-
-			/* Sanitize tags and elements */
-			if (_sanitizeElements(shadowNode)) {
-				continue
-			}
-
-			/* Deep shadow DOM detected */
-			if (shadowNode.content instanceof DocumentFragment) {
-				_sanitizeShadowDOM(shadowNode.content)
-			}
-
-			/* Check attributes, sanitize if necessary */
-			_sanitizeAttributes(shadowNode)
-		}
-
-		/* Execute a hook if present */
-		_executeHook("afterSanitizeShadowDOM", fragment, null)
-	}
-
-	/**
-	 * Sanitize
-	 * Public method providing core sanitation functionality
-	 *
-	 * @param {String|Node} dirty string or DOM node
-	 * @param {Object} cfg object
-	 */
-	// eslint-disable-next-line complexity
-	DOMPurify.sanitize = function (dirty) {
-		let cfg =
-			arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}
-		let body = null
-		let importedNode = null
-		let currentNode = null
-		let returnNode = null
-		/* Make sure we have a string to sanitize.
-      DO NOT return early, as this will return the wrong type if
-      the user has requested a DOM object rather than a string */
-		IS_EMPTY_INPUT = !dirty
-		if (IS_EMPTY_INPUT) {
-			dirty = "<!-->"
-		}
-
-		/* Stringify, in case dirty is an object */
-		if (typeof dirty !== "string" && !_isNode(dirty)) {
-			if (typeof dirty.toString === "function") {
-				dirty = dirty.toString()
-				if (typeof dirty !== "string") {
-					throw typeErrorCreate("dirty is not a string, aborting")
-				}
-			} else {
-				throw typeErrorCreate("toString is not a function")
-			}
-		}
-
-		/* Return dirty HTML if DOMPurify cannot run */
-		if (!DOMPurify.isSupported) {
-			return dirty
-		}
-
-		/* Assign config vars */
-		if (!SET_CONFIG) {
-			_parseConfig(cfg)
-		}
-
-		/* Clean up removed elements */
-		DOMPurify.removed = []
-
-		/* Check if dirty is correctly typed for IN_PLACE */
-		if (typeof dirty === "string") {
-			IN_PLACE = false
-		}
-		if (IN_PLACE) {
-			/* Do some early pre-sanitization to avoid unsafe root nodes */
-			if (dirty.nodeName) {
-				const tagName = transformCaseFunc(dirty.nodeName)
-				if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
-					throw typeErrorCreate(
-						"root node is forbidden and cannot be sanitized in-place",
-					)
-				}
-			}
-		} else if (dirty instanceof Node) {
-			/* If dirty is a DOM element, append to an empty document to avoid
-         elements being stripped by the parser */
-			body = _initDocument("<!---->")
-			importedNode = body.ownerDocument.importNode(dirty, true)
-			if (
-				importedNode.nodeType === NODE_TYPE.element &&
-				importedNode.nodeName === "BODY"
-			) {
-				/* Node is already a body, use as is */
-				body = importedNode
-			} else if (importedNode.nodeName === "HTML") {
-				body = importedNode
-			} else {
-				// eslint-disable-next-line unicorn/prefer-dom-node-append
-				body.appendChild(importedNode)
-			}
-		} else {
-			/* Exit directly if we have nothing to do */
-			if (
-				!RETURN_DOM &&
-				!SAFE_FOR_TEMPLATES &&
-				!WHOLE_DOCUMENT &&
-				// eslint-disable-next-line unicorn/prefer-includes
-				dirty.indexOf("<") === -1
-			) {
-				return trustedTypesPolicy && RETURN_TRUSTED_TYPE
-					? trustedTypesPolicy.createHTML(dirty)
-					: dirty
-			}
-
-			/* Initialize the document to work on */
-			body = _initDocument(dirty)
-
-			/* Check we have a DOM node from the data */
-			if (!body) {
-				return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : ""
-			}
-		}
-
-		/* Remove first element node (ours) if FORCE_BODY is set */
-		if (body && FORCE_BODY) {
-			_forceRemove(body.firstChild)
-		}
-
-		/* Get node iterator */
-		const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body)
-
-		/* Now start iterating over the created document */
-		while ((currentNode = nodeIterator.nextNode())) {
-			/* Sanitize tags and elements */
-			if (_sanitizeElements(currentNode)) {
-				continue
-			}
-
-			/* Shadow DOM detected, sanitize it */
-			if (currentNode.content instanceof DocumentFragment) {
-				_sanitizeShadowDOM(currentNode.content)
-			}
-
-			/* Check attributes, sanitize if necessary */
-			_sanitizeAttributes(currentNode)
-		}
-
-		/* If we sanitized `dirty` in-place, return it. */
-		if (IN_PLACE) {
-			return dirty
-		}
-
-		/* Return sanitized string or DOM */
-		if (RETURN_DOM) {
-			if (RETURN_DOM_FRAGMENT) {
-				returnNode = createDocumentFragment.call(body.ownerDocument)
-				while (body.firstChild) {
-					// eslint-disable-next-line unicorn/prefer-dom-node-append
-					returnNode.appendChild(body.firstChild)
-				}
-			} else {
-				returnNode = body
-			}
-			if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
-				/*
-          AdoptNode() is not used because internal state is not reset
-          (e.g. the past names map of a HTMLFormElement), this is safe
-          in theory but we would rather not risk another attack vector.
-          The state that is cloned by importNode() is explicitly defined
-          by the specs.
-        */
-				returnNode = importNode.call(originalDocument, returnNode, true)
-			}
-			return returnNode
-		}
-		let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML
-
-		/* Serialize doctype if allowed */
-		if (
-			WHOLE_DOCUMENT &&
-			ALLOWED_TAGS["!doctype"] &&
-			body.ownerDocument &&
-			body.ownerDocument.doctype &&
-			body.ownerDocument.doctype.name &&
-			regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)
-		) {
-			serializedHTML =
-				"<!DOCTYPE " + body.ownerDocument.doctype.name + ">\n" + serializedHTML
-		}
-
-		/* Sanitize final string template-safe */
-		if (SAFE_FOR_TEMPLATES) {
-			arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => {
-				serializedHTML = stringReplace(serializedHTML, expr, " ")
-			})
-		}
-		return trustedTypesPolicy && RETURN_TRUSTED_TYPE
-			? trustedTypesPolicy.createHTML(serializedHTML)
-			: serializedHTML
-	}
-
-	/**
-	 * Public method to set the configuration once
-	 * setConfig
-	 *
-	 * @param {Object} cfg configuration object
-	 */
-	DOMPurify.setConfig = function () {
-		let cfg =
-			arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}
-		_parseConfig(cfg)
-		SET_CONFIG = true
-	}
-
-	/**
-	 * Public method to remove the configuration
-	 * clearConfig
-	 *
-	 */
-	DOMPurify.clearConfig = function () {
-		CONFIG = null
-		SET_CONFIG = false
-	}
-
-	/**
-	 * Public method to check if an attribute value is valid.
-	 * Uses last set config, if any. Otherwise, uses config defaults.
-	 * isValidAttribute
-	 *
-	 * @param  {String} tag Tag name of containing element.
-	 * @param  {String} attr Attribute name.
-	 * @param  {String} value Attribute value.
-	 * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
-	 */
-	DOMPurify.isValidAttribute = function (tag, attr, value) {
-		/* Initialize shared config vars if necessary. */
-		if (!CONFIG) {
-			_parseConfig({})
-		}
-		const lcTag = transformCaseFunc(tag)
-		const lcName = transformCaseFunc(attr)
-		return _isValidAttribute(lcTag, lcName, value)
-	}
-
-	/**
-	 * AddHook
-	 * Public method to add DOMPurify hooks
-	 *
-	 * @param {String} entryPoint entry point for the hook to add
-	 * @param {Function} hookFunction function to execute
-	 */
-	DOMPurify.addHook = function (entryPoint, hookFunction) {
-		if (typeof hookFunction !== "function") {
-			return
-		}
-		hooks[entryPoint] = hooks[entryPoint] || []
-		arrayPush(hooks[entryPoint], hookFunction)
-	}
-
-	/**
-	 * RemoveHook
-	 * Public method to remove a DOMPurify hook at a given entryPoint
-	 * (pops it from the stack of hooks if more are present)
-	 *
-	 * @param {String} entryPoint entry point for the hook to remove
-	 * @return {Function} removed(popped) hook
-	 */
-	DOMPurify.removeHook = function (entryPoint) {
-		if (hooks[entryPoint]) {
-			return arrayPop(hooks[entryPoint])
-		}
-	}
-
-	/**
-	 * RemoveHooks
-	 * Public method to remove all DOMPurify hooks at a given entryPoint
-	 *
-	 * @param  {String} entryPoint entry point for the hooks to remove
-	 */
-	DOMPurify.removeHooks = function (entryPoint) {
-		if (hooks[entryPoint]) {
-			hooks[entryPoint] = []
-		}
-	}
-
-	/**
-	 * RemoveAllHooks
-	 * Public method to remove all DOMPurify hooks
-	 */
-	DOMPurify.removeAllHooks = function () {
-		hooks = {}
-	}
-	return DOMPurify
-}
-var purify = createDOMPurify()
-
-export { purify as default }
-//# sourceMappingURL=purify.es.mjs.map
diff --git a/tools/create_article.mjs b/tools/create_article.mjs
index cadbb360ca3e253280b639820b2baea93c9ddd8a..32b26bc26f03c7d3cdfa9f386e8238a26742f45b 100644
--- a/tools/create_article.mjs
+++ b/tools/create_article.mjs
@@ -27,7 +27,7 @@ try {
 		license: config.blog.license,
 		tags: [],
 		options: {
-			mastodonCommentUrl: "",
+			mastodonShareUrl: "",
 		},
 		blog: config.blog,
 	}
@@ -63,7 +63,7 @@ try {
 		"no"
 
 	if (enableMastodon.match(/y(es)?/i)) {
-		context.options.mastodonCommentUrl = "https://mastodon.example.com"
+		context.options.mastodonShareUrl = "https://mastodon.example.com"
 	}
 
 	const articleLocales = new I18n({
diff --git a/tools/locales/article/en.json b/tools/locales/article/en.json
index 70f1cbd9d8e5d81c43bb53119748641b87833897..faa66004d308615bd4cd6019a0b56d6ae4dd5923 100644
--- a/tools/locales/article/en.json
+++ b/tools/locales/article/en.json
@@ -1,7 +1,4 @@
 {
 	"Publication data for this article are avaialable in the page source code (see tags <meta />).": "Publication data for this article are avaialable in the page source code (see tags <meta />).",
-	"If you enable the JavaScript engine in your web browser, they will be displayed here.": "If you enable the JavaScript engine in your web browser, they will be displayed here.",
-	"Comments": "Comments",
-	"Respond to <a href=\"{mastodonCommentUrl}\">this post</a> on the <a href=\"https://en.wikipedia.org/wiki/Fediverse\">Fediverse</a> to add a comment on this article.": "Respond to <a href=\"{mastodonCommentUrl}\">this post</a> on the <a href=\"https://en.wikipedia.org/wiki/Fediverse\">Fediverse</a> to add a comment on this article.",
-	"Fediverse's replies will be displayed here if you enable the JavaScript engine in your web browser.": "Fediverse's replies will be displayed here if you enable the JavaScript engine in your web browser."
+	"If you enable the JavaScript engine in your web browser, they will be displayed here.": "If you enable the JavaScript engine in your web browser, they will be displayed here."
 }
diff --git a/tools/locales/article/fr.json b/tools/locales/article/fr.json
index af018b5e3a4bdd0a441c7e11ff7d2630f65c06f5..47e415dca9cf4db78b8d7582acb48426c13eed63 100644
--- a/tools/locales/article/fr.json
+++ b/tools/locales/article/fr.json
@@ -1,7 +1,4 @@
 {
 	"Publication data for this article are avaialable in the page source code (see tags <meta />).": "Les données de publications de cet article sont disponibles dans le code source de la page (voire les balises <meta>)",
-	"If you enable the JavaScript engine in your web browser, they will be displayed here.": "Si vous activez le moteur JavaScript de votre navigateur web, elles seront affichées ici.",
-	"Comments": "Commentaires",
-	"Respond to <a href=\"{mastodonCommentUrl}\">this post</a> on the <a href=\"https://en.wikipedia.org/wiki/Fediverse\">Fediverse</a> to add a comment on this article.": "Répondez à <a href=\"{mastodonCommentUrl}\">ce message</a> du <a href=\"https://fr.wikipedia.org/wiki/Fediverse\">Fediverse</a> pour ajouter un commentaire sur cet article.",
-	"Fediverse's replies will be displayed here if you enable the JavaScript engine in your web browser.": "Les réponses du Fediverse seront affichées ici si vous avctivez le moteur JavaScript de votre navigateur web."
+	"If you enable the JavaScript engine in your web browser, they will be displayed here.": "Si vous activez le moteur JavaScript de votre navigateur web, elles seront affichées ici."
 }
diff --git a/tools/templates/article.html b/tools/templates/article.html
index 9081568cf8592eef5cd06e9da10280ff93945853..3cd1e55489ffd08fcdf26cb24c9cc059e79ca64d 100644
--- a/tools/templates/article.html
+++ b/tools/templates/article.html
@@ -15,14 +15,6 @@
 			media="screen"
 			href="../css/screen.css"
 		/>
-		{{#if options.mastodonCommentUrl}}
-		<link
-			rel="stylesheet"
-			type="text/css"
-			media="screen"
-			href="../css/comment.css"
-		/>
-		{{/if}}
 		<link
 			rel="stylesheet"
 			type="text/css"
@@ -40,6 +32,7 @@
 		<meta name="author-name" content="{{author.name}}" />
 		<meta name="author-url" content="{{{author.url}}}" />
 		<meta name="author-email" content="{{author.email}}" />
+		<meta name="fediverse:creator" content="{{author.fediverse}}" />
 		<meta name="lang" content="{{lang}}" />
 		<meta name="license-name" content="{{license.name}}" />
 		<meta name="license-url" content="{{{license.url}}}" />
@@ -49,16 +42,10 @@
 			name="history-url"
 			content="{{{blog.csv_history_url}}}/src/articles/{{{fileName}}}"
 		/>
-		{{#if options.mastodonCommentUrl}}
-		<meta
-			name="mastodon-comment-url"
-			content="{{{options.mastodonCommentUrl}}}"
-		/>
+		{{#if options.mastodonShareUrl}}
+		<meta name="mastodon-share-url" content="{{{options.mastodonShareUrl}}}" />
 		{{/if}}
 		<script type="module" src="../javascript/article.js"></script>
-		{{#if options.mastodonCommentUrl}}
-		<script type="module" src="../javascript/comment.js"></script>
-		{{/if}}
 		<script type="module" src="../javascript/header.js"></script>
 	</head>
 
@@ -80,27 +67,6 @@
 			</header>
 
 			<p>Lorem ipsum.</p>
-
-			{{#if options.mastodonCommentUrl}}
-			<section id="comments">
-				<h2>{{translate "Comments"}}</h2>
-				<!-- prettier-ignore -->
-				<p>
-        {{#with options}}
-        {{{translate 'Respond to <a href="{mastodonCommentUrl}">this post</a> on the <a href="https://en.wikipedia.org/wiki/Fediverse">Fediverse</a> to add a comment on this article.'}}}
-        {{/with}}
-				</p>
-
-				<div id="mastodon-comments">
-					<noscript>
-						<!-- prettier-ignore -->
-						<p>
-		  			  {{translate "Fediverse's replies will be displayed here if you enable the JavaScript engine in your web browser."}}
-            </p>
-					</noscript>
-				</div>
-			</section>
-			{{/if}}
 		</article>
 	</body>
 </html>