Compare commits
4 Commits
894263e5c4
...
ace3562111
| Author | SHA1 | Date | |
|---|---|---|---|
| ace3562111 | |||
| f0d9fecfb8 | |||
| d921b2758b | |||
| 415e772e57 |
Generated
+423
@@ -12,6 +12,7 @@
|
|||||||
"@fontsource-variable/rubik": "^5.2.8",
|
"@fontsource-variable/rubik": "^5.2.8",
|
||||||
"@fontsource/inter": "^5.2.8",
|
"@fontsource/inter": "^5.2.8",
|
||||||
"@iconify/svelte": "^5.2.1",
|
"@iconify/svelte": "^5.2.1",
|
||||||
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
"@turf/buffer": "^7.3.5",
|
"@turf/buffer": "^7.3.5",
|
||||||
"@types/leaflet": "^1.9.21",
|
"@types/leaflet": "^1.9.21",
|
||||||
"blurhash": "^2.0.5",
|
"blurhash": "^2.0.5",
|
||||||
@@ -20,6 +21,8 @@
|
|||||||
"marked": "^17.0.2",
|
"marked": "^17.0.2",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"sanitize-html": "^2.13.1",
|
"sanitize-html": "^2.13.1",
|
||||||
|
"satori": "^0.26.0",
|
||||||
|
"satori-html": "^0.3.2",
|
||||||
"sharp": "^0.34.5"
|
"sharp": "^0.34.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1239,6 +1242,221 @@
|
|||||||
"npm": ">=9.5.0"
|
"npm": ">=9.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@resvg/resvg-js": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==",
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@resvg/resvg-js-android-arm-eabi": "2.6.2",
|
||||||
|
"@resvg/resvg-js-android-arm64": "2.6.2",
|
||||||
|
"@resvg/resvg-js-darwin-arm64": "2.6.2",
|
||||||
|
"@resvg/resvg-js-darwin-x64": "2.6.2",
|
||||||
|
"@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2",
|
||||||
|
"@resvg/resvg-js-linux-arm64-gnu": "2.6.2",
|
||||||
|
"@resvg/resvg-js-linux-arm64-musl": "2.6.2",
|
||||||
|
"@resvg/resvg-js-linux-x64-gnu": "2.6.2",
|
||||||
|
"@resvg/resvg-js-linux-x64-musl": "2.6.2",
|
||||||
|
"@resvg/resvg-js-win32-arm64-msvc": "2.6.2",
|
||||||
|
"@resvg/resvg-js-win32-ia32-msvc": "2.6.2",
|
||||||
|
"@resvg/resvg-js-win32-x64-msvc": "2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-android-arm-eabi": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-android-arm64": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-darwin-arm64": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-darwin-x64": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-linux-arm-gnueabihf": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-linux-arm64-gnu": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-linux-arm64-musl": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-linux-x64-gnu": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-linux-x64-musl": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-win32-arm64-msvc": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-win32-ia32-msvc": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@resvg/resvg-js-win32-x64-msvc": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-commonjs": {
|
"node_modules/@rollup/plugin-commonjs": {
|
||||||
"version": "29.0.2",
|
"version": "29.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.2.tgz",
|
||||||
@@ -1685,6 +1903,22 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@shuding/opentype.js": {
|
||||||
|
"version": "1.4.0-beta.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
|
||||||
|
"integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fflate": "^0.7.3",
|
||||||
|
"string.prototype.codepointat": "^0.2.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"ot": "bin/ot"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@standard-schema/spec": {
|
"node_modules/@standard-schema/spec": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||||
@@ -2559,6 +2793,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
|
||||||
|
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/blurhash": {
|
"node_modules/blurhash": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz",
|
||||||
@@ -2594,6 +2837,15 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/camelize": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chai": {
|
"node_modules/chai": {
|
||||||
"version": "5.3.3",
|
"version": "5.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
|
||||||
@@ -2803,6 +3055,47 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-background-parser": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/css-box-shadow": {
|
||||||
|
"version": "1.0.0-3",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
|
||||||
|
"integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/css-color-keywords": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-gradient-parser": {
|
||||||
|
"version": "0.0.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-gradient-parser/-/css-gradient-parser-0.0.17.tgz",
|
||||||
|
"integrity": "sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-to-react-native": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"camelize": "^1.0.0",
|
||||||
|
"css-color-keywords": "^1.0.0",
|
||||||
|
"postcss-value-parser": "^4.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-array": {
|
"node_modules/d3-array": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
|
||||||
@@ -2966,6 +3259,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/emoji-regex-xs": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.20.0",
|
"version": "5.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
|
||||||
@@ -3041,6 +3343,12 @@
|
|||||||
"@esbuild/win32-x64": "0.25.12"
|
"@esbuild/win32-x64": "0.25.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/escape-html": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/escape-string-regexp": {
|
"node_modules/escape-string-regexp": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
@@ -3111,6 +3419,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fflate": {
|
||||||
|
"version": "0.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
|
||||||
|
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/find-up": {
|
"node_modules/find-up": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||||
@@ -3243,6 +3557,18 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hex-rgb": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-escaper": {
|
"node_modules/html-escaper": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||||
@@ -3770,6 +4096,16 @@
|
|||||||
"url": "https://opencollective.com/parcel"
|
"url": "https://opencollective.com/parcel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/linebreak": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "0.0.8",
|
||||||
|
"unicode-trie": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/locate-character": {
|
"node_modules/locate-character": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||||
@@ -3983,6 +4319,22 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0"
|
"license": "BlueOak-1.0.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/pako": {
|
||||||
|
"version": "0.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||||
|
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/parse-css-color": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "^1.1.4",
|
||||||
|
"hex-rgb": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parse-json": {
|
"node_modules/parse-json": {
|
||||||
"version": "8.3.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
|
||||||
@@ -4133,6 +4485,12 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/qrcode": {
|
"node_modules/qrcode": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||||
@@ -4282,6 +4640,37 @@
|
|||||||
"postcss": "^8.3.11"
|
"postcss": "^8.3.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/satori": {
|
||||||
|
"version": "0.26.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/satori/-/satori-0.26.0.tgz",
|
||||||
|
"integrity": "sha512-tkMFrfIs3l2mQ2JEcyW0ADTy3zGggFRFzi6Ef8YozQSFsFKEqaSO1Y8F9wJg4//PJGQauMalHGTUEkPrFwhVPA==",
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@shuding/opentype.js": "1.4.0-beta.0",
|
||||||
|
"css-background-parser": "^0.1.0",
|
||||||
|
"css-box-shadow": "1.0.0-3",
|
||||||
|
"css-gradient-parser": "^0.0.17",
|
||||||
|
"css-to-react-native": "^3.0.0",
|
||||||
|
"emoji-regex-xs": "^2.0.1",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
|
"linebreak": "^1.1.0",
|
||||||
|
"parse-css-color": "^0.2.1",
|
||||||
|
"postcss-value-parser": "^4.2.0",
|
||||||
|
"yoga-layout": "^3.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/satori-html": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/satori-html/-/satori-html-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ultrahtml": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
@@ -4496,6 +4885,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string.prototype.codepointat": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/strip-ansi": {
|
"node_modules/strip-ansi": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||||
@@ -4697,6 +5092,12 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-inflate": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tinybench": {
|
"node_modules/tinybench": {
|
||||||
"version": "2.9.0",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||||
@@ -4801,6 +5202,12 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ultrahtml": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.21.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
@@ -4808,6 +5215,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/unicode-trie": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pako": "^0.2.5",
|
||||||
|
"tiny-inflate": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uri-js-replace": {
|
"node_modules/uri-js-replace": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
|
||||||
@@ -6252,6 +6669,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yoga-layout": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/zimmerframe": {
|
"node_modules/zimmerframe": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"@fontsource-variable/rubik": "^5.2.8",
|
"@fontsource-variable/rubik": "^5.2.8",
|
||||||
"@fontsource/inter": "^5.2.8",
|
"@fontsource/inter": "^5.2.8",
|
||||||
"@iconify/svelte": "^5.2.1",
|
"@iconify/svelte": "^5.2.1",
|
||||||
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
"@turf/buffer": "^7.3.5",
|
"@turf/buffer": "^7.3.5",
|
||||||
"@types/leaflet": "^1.9.21",
|
"@types/leaflet": "^1.9.21",
|
||||||
"blurhash": "^2.0.5",
|
"blurhash": "^2.0.5",
|
||||||
@@ -50,6 +51,8 @@
|
|||||||
"marked": "^17.0.2",
|
"marked": "^17.0.2",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"sanitize-html": "^2.13.1",
|
"sanitize-html": "^2.13.1",
|
||||||
|
"satori": "^0.26.0",
|
||||||
|
"satori-html": "^0.3.2",
|
||||||
"sharp": "^0.34.5"
|
"sharp": "^0.34.5"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ const EXTRA_ICONS = [
|
|||||||
'information-outline',
|
'information-outline',
|
||||||
// OrganisationsMapBlock: activate-overlay
|
// OrganisationsMapBlock: activate-overlay
|
||||||
'cursor-default-click-outline',
|
'cursor-default-click-outline',
|
||||||
|
// CalendarBlock: social-image modal button
|
||||||
|
'image-outline',
|
||||||
];
|
];
|
||||||
|
|
||||||
const STATIC_PATTERN = /icon="mdi:([a-z0-9-]+)"/g;
|
const STATIC_PATTERN = /icon="mdi:([a-z0-9-]+)"/g;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
let _hidden: string[] = [];
|
let _hidden: string[] = [];
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
_hidden = hiddenStatuses;
|
_hidden = hiddenStatuses;
|
||||||
|
syncLabelsFn?.();
|
||||||
if (!geojsonLayer) return;
|
if (!geojsonLayer) return;
|
||||||
restyleAll(selectedSlug);
|
restyleAll(selectedSlug);
|
||||||
// Deselect if selected area's status was just hidden.
|
// Deselect if selected area's status was just hidden.
|
||||||
@@ -58,7 +59,8 @@
|
|||||||
let bufferLayer: import("leaflet").LayerGroup | null = null;
|
let bufferLayer: import("leaflet").LayerGroup | null = null;
|
||||||
let ring2kmLayer: import("leaflet").LayerGroup | null = null;
|
let ring2kmLayer: import("leaflet").LayerGroup | null = null;
|
||||||
let ring5kmLayer: import("leaflet").LayerGroup | null = null;
|
let ring5kmLayer: import("leaflet").LayerGroup | null = null;
|
||||||
let labelLayer: import("leaflet").LayerGroup | null = null;
|
let labelLayers: Record<string, import("leaflet").LayerGroup> = {};
|
||||||
|
let syncLabelsFn: (() => void) | null = null;
|
||||||
let selectedSlug = $state<string | null>(null);
|
let selectedSlug = $state<string | null>(null);
|
||||||
let geojsonLayer: import("leaflet").GeoJSON | null = null;
|
let geojsonLayer: import("leaflet").GeoJSON | null = null;
|
||||||
let buffering = $state(false);
|
let buffering = $state(false);
|
||||||
@@ -198,7 +200,13 @@
|
|||||||
|
|
||||||
map = L.map(mapEl, { zoomControl: true, attributionControl: false }).setView([50.62, 10.42], 10);
|
map = L.map(mapEl, { zoomControl: true, attributionControl: false }).setView([50.62, 10.42], 10);
|
||||||
L.control.attribution({ position: "bottomleft" }).addTo(map);
|
L.control.attribution({ position: "bottomleft" }).addTo(map);
|
||||||
labelLayer = L.layerGroup(); // not added to map yet — shown only at zoom >= 13
|
// One label LayerGroup per status — lets syncLabels respect hiddenStatuses
|
||||||
|
labelLayers = {
|
||||||
|
rechtskraeftig: L.layerGroup(),
|
||||||
|
entwurf_2: L.layerGroup(),
|
||||||
|
entwurf_2_voraussichtlich: L.layerGroup(),
|
||||||
|
entwurf_3: L.layerGroup(),
|
||||||
|
};
|
||||||
onready?.({ reset: () => map!.setView([50.62, 10.42], 10) });
|
onready?.({ reset: () => map!.setView([50.62, 10.42], 10) });
|
||||||
|
|
||||||
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
@@ -295,7 +303,9 @@
|
|||||||
|
|
||||||
// Permanent label via divIcon marker at polygon center
|
// Permanent label via divIcon marker at polygon center
|
||||||
const nr = area.gebiets_nr ?? p.name;
|
const nr = area.gebiets_nr ?? p.name;
|
||||||
if (nr && labelLayer) {
|
const featStatus = (feature?.properties?.status ?? "rechtskraeftig") as string;
|
||||||
|
const targetGroup = labelLayers[featStatus] ?? labelLayers.rechtskraeftig;
|
||||||
|
if (nr && targetGroup) {
|
||||||
const center = (layer as import("leaflet").Polygon).getBounds().getCenter();
|
const center = (layer as import("leaflet").Polygon).getBounds().getCenter();
|
||||||
L.marker(center, {
|
L.marker(center, {
|
||||||
icon: L.divIcon({
|
icon: L.divIcon({
|
||||||
@@ -306,29 +316,35 @@
|
|||||||
}),
|
}),
|
||||||
interactive: false,
|
interactive: false,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
}).addTo(labelLayer);
|
}).addTo(targetGroup);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
restyleAll(selectedSlug);
|
restyleAll(selectedSlug);
|
||||||
|
|
||||||
// Labels: show from zoom 13, scale font proportionally with zoom
|
// Labels: show from zoom 13, scale font proportionally with zoom.
|
||||||
|
// Per-status LayerGroup so hidden statuses also hide their labels.
|
||||||
const syncLabels = () => {
|
const syncLabels = () => {
|
||||||
if (!labelLayer || !map) return;
|
if (!map) return;
|
||||||
const z = map.getZoom();
|
const z = map.getZoom();
|
||||||
if (z >= 13) {
|
const show = z >= 13;
|
||||||
if (!map.hasLayer(labelLayer)) map.addLayer(labelLayer);
|
const fontSize = Math.max(8, Math.min(26, Math.round(9 * Math.pow(2, z - 13))));
|
||||||
const fontSize = Math.max(8, Math.min(26, Math.round(9 * Math.pow(2, z - 13))));
|
for (const [status, layer] of Object.entries(labelLayers)) {
|
||||||
labelLayer.eachLayer((l) => {
|
const visible = show && !_hidden.includes(status);
|
||||||
const el = (l as import("leaflet").Marker).getElement();
|
if (visible) {
|
||||||
const span = el?.querySelector<HTMLElement>(".windkarte-label");
|
if (!map.hasLayer(layer)) map.addLayer(layer);
|
||||||
if (span) span.style.fontSize = fontSize + "px";
|
layer.eachLayer((l) => {
|
||||||
});
|
const el = (l as import("leaflet").Marker).getElement();
|
||||||
} else {
|
const span = el?.querySelector<HTMLElement>(".windkarte-label");
|
||||||
if (map.hasLayer(labelLayer)) map.removeLayer(labelLayer);
|
if (span) span.style.fontSize = fontSize + "px";
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (map.hasLayer(layer)) map.removeLayer(layer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
syncLabelsFn = syncLabels;
|
||||||
map.on("zoomend", syncLabels);
|
map.on("zoomend", syncLabels);
|
||||||
syncLabels();
|
syncLabels();
|
||||||
|
|
||||||
|
|||||||
@@ -446,6 +446,16 @@
|
|||||||
let copyToastKey = $state<string | null>(null);
|
let copyToastKey = $state<string | null>(null);
|
||||||
let modalImage = $state<{ src: string; alt: string } | null>(null);
|
let modalImage = $state<{ src: string; alt: string } | null>(null);
|
||||||
let linkedSlug = $state<string | null>(null);
|
let linkedSlug = $state<string | null>(null);
|
||||||
|
let socialModal = $state<{ slug: string; title: string } | null>(null);
|
||||||
|
|
||||||
|
function openSocial(item: EventCardItem) {
|
||||||
|
if (!item._slug) return;
|
||||||
|
socialModal = { slug: item._slug, title: item.title || "Termin" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSocial() {
|
||||||
|
socialModal = null;
|
||||||
|
}
|
||||||
|
|
||||||
function eventId(item: EventCardItem): string {
|
function eventId(item: EventCardItem): string {
|
||||||
if (item._slug) return `event-${item._slug}`;
|
if (item._slug) return `event-${item._slug}`;
|
||||||
@@ -886,6 +896,22 @@
|
|||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if item._slug}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => openSocial(item)}
|
||||||
|
class="btn-outline btn-sm group"
|
||||||
|
aria-label="Social-Bild als Vorschau öffnen"
|
||||||
|
title="Social-Bild Vorschau"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon="mdi:image-outline"
|
||||||
|
class="size-3.5 shrink-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<span class="whitespace-nowrap">Social-Bild</span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => jumpToEventInGrid(item)}
|
onclick={() => jumpToEventInGrid(item)}
|
||||||
@@ -1296,6 +1322,82 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if socialModal}
|
||||||
|
{@const ogUrl = `/api/social/calendar/${socialModal.slug}?format=og`}
|
||||||
|
{@const sqUrl = `/api/social/calendar/${socialModal.slug}?format=square`}
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 p-4"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="Social-Bild-Vorschau"
|
||||||
|
onclick={closeSocial}
|
||||||
|
onkeydown={(e) => { if (e.key === 'Escape') closeSocial(); }}
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="relative max-h-[92vh] w-full max-w-4xl overflow-y-auto rounded-lg bg-white p-5 shadow-2xl"
|
||||||
|
onclick={(e) => e.stopPropagation()}
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<div class="mb-4 flex items-start justify-between gap-3">
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p class="text-xs uppercase tracking-wider text-stein-500">Social-Bild</p>
|
||||||
|
<p class="truncate text-sm font-semibold text-stein-800">{socialModal.title}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={closeSocial}
|
||||||
|
class="shrink-0 text-stein-400 hover:text-stein-700"
|
||||||
|
aria-label="Schließen"
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:close" class="size-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-4 sm:grid-cols-2">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-xs font-medium text-stein-600">Quer · 1200 × 630</p>
|
||||||
|
<img
|
||||||
|
src={ogUrl}
|
||||||
|
alt="Social-Bild Quer"
|
||||||
|
class="w-full rounded-sm border border-stein-200 bg-stein-50"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
href={ogUrl}
|
||||||
|
download={`termin-${socialModal.slug}-og.png`}
|
||||||
|
class="btn-outline btn-sm justify-center no-underline!"
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:download" class="size-3.5" />
|
||||||
|
Quer herunterladen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-xs font-medium text-stein-600">Quadrat · 1080 × 1080</p>
|
||||||
|
<img
|
||||||
|
src={sqUrl}
|
||||||
|
alt="Social-Bild Quadrat"
|
||||||
|
class="mx-auto w-full max-w-[300px] rounded-sm border border-stein-200 bg-stein-50 sm:max-w-none"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
href={sqUrl}
|
||||||
|
download={`termin-${socialModal.slug}-square.png`}
|
||||||
|
class="btn-outline btn-sm justify-center no-underline!"
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:download" class="size-3.5" />
|
||||||
|
Quadrat herunterladen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-4 text-[11px] text-stein-400">
|
||||||
|
Die Bilder werden automatisch generiert. Für Posts auf Instagram, Facebook, WhatsApp oder als Hintergrund-Grafik.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Heute-Markierung im Kalendergrid: dezenter Rand + leichtes Glow.
|
/* Heute-Markierung im Kalendergrid: dezenter Rand + leichtes Glow.
|
||||||
Ergänzt das Event-Badge, damit auch leere Tage als "Heute"
|
Ergänzt das Event-Badge, damit auch leere Tage als "Heute"
|
||||||
|
|||||||
@@ -483,23 +483,37 @@ ${"=".repeat(64)}`;
|
|||||||
async function copyAiPrompt() {
|
async function copyAiPrompt() {
|
||||||
const toneDesc = aiToneLabels[aiTone - 1];
|
const toneDesc = aiToneLabels[aiTone - 1];
|
||||||
const sharpnessDesc = aiSharpnessLabels[aiSharpness - 1];
|
const sharpnessDesc = aiSharpnessLabels[aiSharpness - 1];
|
||||||
const customPart = aiCustomNote.trim() ? `\nZusätzliche Hinweise von mir: ${aiCustomNote.trim()}\n` : "";
|
const customPart = aiCustomNote.trim()
|
||||||
const personalPart = persoenlicheEingabe.trim()
|
? `\nZusätzliche Hinweise des Verfassers:\n${aiCustomNote.trim()}\n`
|
||||||
? `\nMeine persönliche Betroffenheit (bitte besonders authentisch und individuell einbauen):\n${persoenlicheEingabe.trim()}\n`
|
|
||||||
: "";
|
: "";
|
||||||
const prompt = `Du bist ein Schreibassistent. Erstelle aus der folgenden Muster-Stellungnahme eine individuell formulierte Stellungnahme für mich.
|
const personalPart = persoenlicheEingabe.trim()
|
||||||
|
? `\nPersönliche Betroffenheit des Verfassers (zentral und authentisch einbinden, möglichst wörtlich verarbeiten, nichts dazuerfinden):\n${persoenlicheEingabe.trim()}\n`
|
||||||
|
: "";
|
||||||
|
const gebietPart = windArea
|
||||||
|
? `\nKontext zum betroffenen Windvorranggebiet: ${windArea.gebiets_nr ?? ""}${windArea.bezeichnung ? " – " + windArea.bezeichnung : ""}${windArea.gemeinden?.length ? " (Gemeinden: " + windArea.gemeinden.slice(0, 5).join(", ") + ")" : ""}\n`
|
||||||
|
: "";
|
||||||
|
const prompt = `Rolle: Du bist ein erfahrener Verfasser von Bürger-Einwendungen im Raumordnungs- und Genehmigungsverfahren. Du formulierst eine individuelle, juristisch und sachlich saubere Stellungnahme im Namen des Verfassers.
|
||||||
|
|
||||||
Gewünschter Stil:
|
Aufgabe: Schreibe die unten als "MUSTER-STELLUNGNAHME" beigefügte Vorlage so um, dass sie wie eine eigenständig verfasste Einwendung dieser einen Person wirkt – nicht wie eine Vorlage und nicht wie eine generierte Massenstellungnahme.
|
||||||
|
|
||||||
|
Stilvorgaben:
|
||||||
- Ton: ${toneDesc}
|
- Ton: ${toneDesc}
|
||||||
- Haltung/Schärfe: ${sharpnessDesc}
|
- Haltung/Schärfe: ${sharpnessDesc}
|
||||||
${personalPart}${customPart}
|
- Sprache: durchgängig Deutsch, Sie-Form gegenüber der Behörde, klar adressiert.
|
||||||
Variiere außerdem:
|
${gebietPart}${personalPart}${customPart}
|
||||||
- Die Reihenfolge der Argumente
|
Zwingende Regeln:
|
||||||
- Die konkreten Formulierungen (keine Sätze 1:1 übernehmen)
|
1. ALLE inhaltlichen Argumente, Fakten, Zahlen, Gesetzes- und Quellenverweise aus der Muster-Stellungnahme MÜSSEN vollständig erhalten bleiben. Nichts weglassen, nichts inhaltlich abschwächen.
|
||||||
|
2. KEINE neuen Fakten, Zahlen, Gesetzesnormen oder Quellen erfinden. Wenn etwas in der Vorlage nicht steht, taucht es im Output nicht auf.
|
||||||
|
3. Briefkopf, Behörden-Adresse, Aktenzeichen/Betreff, Datum, Anrede und Schlussformel inkl. Absender bleiben strukturell erhalten (Reihenfolge, Felder, Angaben). Nur Formulierungen im Fließtext darfst du umarbeiten.
|
||||||
|
4. Argument-Reihenfolge im Hauptteil darfst du variieren, sofern Logik und Lesefluss stimmen.
|
||||||
|
5. Formulierungen variieren – keine Satzbausteine 1:1 übernehmen. Mindestens 70 % des Fließtextes neu formulieren, ohne Inhalt zu verändern.
|
||||||
|
6. Persönliche Betroffenheit (falls oben angegeben) wird konkret und ich-bezogen in den Text eingewoben, nicht als separater Absatz angehängt.
|
||||||
|
7. Länge: ungefähr im Rahmen der Vorlage (±20 %). Keine Aufblähung mit Floskeln, keine starke Kürzung.
|
||||||
|
|
||||||
Behalte alle inhaltlichen Punkte und Argumente vollständig bei.
|
Ausgabeformat:
|
||||||
|
- Gib AUSSCHLIESSLICH den fertigen Stellungnahme-Text aus. Kein Vorwort, keine Erklärung, keine Markdown-Codeblöcke, keine Hinweise wie "Hier ist deine Stellungnahme:". Direkt mit dem Briefkopf beginnen.
|
||||||
|
|
||||||
Muster-Stellungnahme:
|
MUSTER-STELLUNGNAHME:
|
||||||
---
|
---
|
||||||
${outputText}
|
${outputText}
|
||||||
---`;
|
---`;
|
||||||
|
|||||||
@@ -114,6 +114,9 @@
|
|||||||
"google": {
|
"google": {
|
||||||
"body": "<path fill=\"currentColor\" d=\"M21.35 11.1h-9.17v2.73h6.51c-.33 3.81-3.5 5.44-6.5 5.44C8.36 19.27 5 16.25 5 12c0-4.1 3.2-7.27 7.2-7.27c3.09 0 4.9 1.97 4.9 1.97L19 4.72S16.56 2 12.1 2C6.42 2 2.03 6.8 2.03 12c0 5.05 4.13 10 10.22 10c5.35 0 9.25-3.67 9.25-9.09c0-1.15-.15-1.81-.15-1.81\"/>"
|
"body": "<path fill=\"currentColor\" d=\"M21.35 11.1h-9.17v2.73h6.51c-.33 3.81-3.5 5.44-6.5 5.44C8.36 19.27 5 16.25 5 12c0-4.1 3.2-7.27 7.2-7.27c3.09 0 4.9 1.97 4.9 1.97L19 4.72S16.56 2 12.1 2C6.42 2 2.03 6.8 2.03 12c0 5.05 4.13 10 10.22 10c5.35 0 9.25-3.67 9.25-9.09c0-1.15-.15-1.81-.15-1.81\"/>"
|
||||||
},
|
},
|
||||||
|
"image-outline": {
|
||||||
|
"body": "<path fill=\"currentColor\" d=\"M19 19H5V5h14m0-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2m-5.04 9.29l-2.75 3.54l-1.96-2.36L6.5 17h11z\"/>"
|
||||||
|
},
|
||||||
"calendar-search": {
|
"calendar-search": {
|
||||||
"body": "<path fill=\"currentColor\" d=\"M15.5 12c2.5 0 4.5 2 4.5 4.5c0 .88-.25 1.71-.69 2.4l3.08 3.1L21 23.39l-3.12-3.07c-.69.43-1.51.68-2.38.68c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5m0 2a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0 2.5-2.5a2.5 2.5 0 0 0-2.5-2.5M19 8H5v11h4.5c.31.75.76 1.42 1.31 2H5a2 2 0 0 1-2-2V5c0-1.11.89-2 2-2h1V1h2v2h8V1h2v2h1a2 2 0 0 1 2 2v8.03c-.5-.81-1.2-1.49-2-2.03z\"/>"
|
"body": "<path fill=\"currentColor\" d=\"M15.5 12c2.5 0 4.5 2 4.5 4.5c0 .88-.25 1.71-.69 2.4l3.08 3.1L21 23.39l-3.12-3.07c-.69.43-1.51.68-2.38.68c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5m0 2a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0 2.5-2.5a2.5 2.5 0 0 0-2.5-2.5M19 8H5v11h4.5c.31.75.76 1.42 1.31 2H5a2 2 0 0 1-2-2V5c0-1.11.89-2 2-2h1V1h2v2h8V1h2v2h1a2 2 0 0 1 2 2v8.03c-.5-.81-1.2-1.49-2-2.03z\"/>"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
import type { RequestHandler } from "./$types";
|
||||||
|
import { error } from "@sveltejs/kit";
|
||||||
|
import satori from "satori";
|
||||||
|
import { html as htmlToReact } from "satori-html";
|
||||||
|
import { Resvg } from "@resvg/resvg-js";
|
||||||
|
import { toBuffer as qrToBuffer } from "qrcode";
|
||||||
|
import { env } from "$env/dynamic/public";
|
||||||
|
import { getCalendarItemBySlug } from "$lib/cms";
|
||||||
|
|
||||||
|
type Format = "og" | "square";
|
||||||
|
|
||||||
|
const SIZES: Record<Format, { w: number; h: number }> = {
|
||||||
|
og: { w: 1200, h: 630 },
|
||||||
|
square: { w: 1080, h: 1080 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const FONT_URLS: Record<string, string> = {
|
||||||
|
regular: "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfMZhrib2Bg-4.ttf",
|
||||||
|
semibold: "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuFuYMZhrib2Bg-4.ttf",
|
||||||
|
bold: "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuGKYMZhrib2Bg-4.ttf",
|
||||||
|
};
|
||||||
|
|
||||||
|
let fontCache: { regular: ArrayBuffer; semibold: ArrayBuffer; bold: ArrayBuffer } | null = null;
|
||||||
|
|
||||||
|
async function loadFonts() {
|
||||||
|
if (fontCache) return fontCache;
|
||||||
|
const [regular, semibold, bold] = await Promise.all(
|
||||||
|
(["regular", "semibold", "bold"] as const).map(async (w) => {
|
||||||
|
const res = await fetch(FONT_URLS[w]);
|
||||||
|
if (!res.ok) throw new Error(`Font fetch failed (${w}): ${res.status}`);
|
||||||
|
return res.arrayBuffer();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
fontCache = { regular, semibold, bold };
|
||||||
|
return fontCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(iso: string | undefined): { date: string; time: string | null } {
|
||||||
|
if (!iso) return { date: "", time: null };
|
||||||
|
const d = new Date(iso);
|
||||||
|
const date = d.toLocaleDateString("de-DE", { day: "2-digit", month: "long", year: "numeric" });
|
||||||
|
const hasTime = !/T00:00:00/.test(iso);
|
||||||
|
const time = hasTime ? d.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" }) : null;
|
||||||
|
return { date, time };
|
||||||
|
}
|
||||||
|
|
||||||
|
function weekday(iso: string | undefined): string {
|
||||||
|
if (!iso) return "";
|
||||||
|
return new Date(iso).toLocaleDateString("de-DE", { weekday: "long" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const cache = new Map<string, { png: Buffer; ts: number }>();
|
||||||
|
const CACHE_TTL = 10 * 60_000; // 10min
|
||||||
|
const CACHE_VERSION = "v4";
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ params, url, setHeaders }) => {
|
||||||
|
const slug = params.slug;
|
||||||
|
const formatParam = url.searchParams.get("format") === "square" ? "square" : "og";
|
||||||
|
const cacheKey = `${CACHE_VERSION}:${slug}:${formatParam}`;
|
||||||
|
const now = Date.now();
|
||||||
|
const cached = cache.get(cacheKey);
|
||||||
|
if (cached && now - cached.ts < CACHE_TTL) {
|
||||||
|
setHeaders({
|
||||||
|
"content-type": "image/png",
|
||||||
|
"cache-control": "public, max-age=600, stale-while-revalidate=86400",
|
||||||
|
});
|
||||||
|
return new Response(new Uint8Array(cached.png), { status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemRaw = await getCalendarItemBySlug(slug, { locale: "de" });
|
||||||
|
if (!itemRaw) throw error(404, "Termin nicht gefunden");
|
||||||
|
const item = itemRaw as typeof itemRaw & { location?: unknown };
|
||||||
|
|
||||||
|
const { w, h } = SIZES[formatParam];
|
||||||
|
const fonts = await loadFonts();
|
||||||
|
|
||||||
|
const title = (item.title ?? "").trim();
|
||||||
|
const { date, time } = formatDate(item.terminZeit);
|
||||||
|
const day = weekday(item.terminZeit);
|
||||||
|
const locationText =
|
||||||
|
typeof item.location === "object" && item.location !== null
|
||||||
|
? ((item.location as { text?: string }).text ?? "").trim()
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const siteUrl = (env.PUBLIC_SITE_URL || "https://windwiderstand.de").replace(/\/$/, "");
|
||||||
|
const eventUrl = `${siteUrl}/kalender/#event-${slug}`;
|
||||||
|
// QR als PNG (satori/resvg embeddet PNG zuverlässiger als SVG mit kleinen viewBox).
|
||||||
|
const qrPngBuf = await qrToBuffer(eventUrl, {
|
||||||
|
errorCorrectionLevel: "M",
|
||||||
|
margin: 1,
|
||||||
|
scale: 8,
|
||||||
|
color: { dark: "#0a2011ff", light: "#ffffffff" },
|
||||||
|
});
|
||||||
|
const qrDataUrl = `data:image/png;base64,${qrPngBuf.toString("base64")}`;
|
||||||
|
|
||||||
|
const square = formatParam === "square";
|
||||||
|
const titleFs = square ? 56 : 52;
|
||||||
|
const dateFs = square ? 84 : 76;
|
||||||
|
const padX = square ? 80 : 90;
|
||||||
|
const qrSize = square ? 220 : 180;
|
||||||
|
const titleClamped = title.length > 110 ? title.slice(0, 107) + "…" : title;
|
||||||
|
const locClamped =
|
||||||
|
locationText.length > 70 ? locationText.slice(0, 67) + "…" : locationText;
|
||||||
|
|
||||||
|
const pinSvg = `<svg width="30" height="30" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 22s7-7.58 7-13a7 7 0 1 0-14 0c0 5.42 7 13 7 13z" fill="#7fb58a"/><circle cx="12" cy="9" r="2.5" fill="#0a2011"/></svg>`;
|
||||||
|
const pinDataUrl = `data:image/svg+xml;base64,${Buffer.from(pinSvg).toString("base64")}`;
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
<div style="display:flex;flex-direction:column;width:${w}px;height:${h}px;background:linear-gradient(135deg,#0a2011 0%,#12361c 55%,#1a4d28 100%);color:#f0f5f1;padding:${padX}px;font-family:Inter;position:relative;">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;font-size:20px;letter-spacing:0.18em;font-weight:600;color:#7fb58a;text-transform:uppercase;">
|
||||||
|
<div style="display:flex;align-items:center;">Windwiderstand Thüringen</div>
|
||||||
|
<div style="display:flex;align-items:center;color:#b3d1b9;">Termin</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex;margin-top:${square ? 48 : 24}px;flex:1;gap:40px;">
|
||||||
|
<div style="display:flex;flex-direction:column;flex:1;min-width:0;">
|
||||||
|
<div style="display:flex;font-size:26px;color:#b3d1b9;font-weight:500;text-transform:capitalize;margin-bottom:6px;">${day}</div>
|
||||||
|
<div style="display:flex;align-items:baseline;flex-wrap:wrap;">
|
||||||
|
<div style="display:flex;font-size:${dateFs}px;font-weight:700;color:#ffffff;letter-spacing:-0.02em;line-height:1;">${date}</div>
|
||||||
|
${time ? `<div style="display:flex;font-size:40px;font-weight:600;color:#d9e8dc;margin-left:24px;line-height:1;">${time} Uhr</div>` : ""}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex;font-size:${titleFs}px;font-weight:700;color:#ffffff;line-height:1.15;margin-top:${square ? 40 : 26}px;letter-spacing:-0.015em;">${escapeHtml(titleClamped)}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex;flex-direction:column;align-items:center;gap:8px;">
|
||||||
|
<div style="display:flex;background:#ffffff;padding:14px;border-radius:12px;">
|
||||||
|
<img src="${qrDataUrl}" style="width:${qrSize}px;height:${qrSize}px;" />
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;font-size:16px;color:#b3d1b9;font-weight:500;letter-spacing:0.05em;">Mehr Infos</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${
|
||||||
|
locClamped
|
||||||
|
? `<div style="display:flex;align-items:center;font-size:24px;color:#d9e8dc;font-weight:500;margin-bottom:24px;gap:12px;"><img src="${pinDataUrl}" style="width:28px;height:28px;" /><div style="display:flex;">${escapeHtml(locClamped)}</div></div>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;font-size:20px;color:#7fb58a;border-top:1px solid #1a4d28;padding-top:18px;">
|
||||||
|
<div style="display:flex;align-items:center;font-weight:600;">windwiderstand.de</div>
|
||||||
|
<div style="display:flex;align-items:center;font-weight:500;letter-spacing:0.04em;">Mit Vernunft in die Zukunft</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const reactLike = htmlToReact(template);
|
||||||
|
|
||||||
|
const svg = await satori(reactLike as Parameters<typeof satori>[0], {
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
fonts: [
|
||||||
|
{ name: "Inter", data: fonts.regular, weight: 400, style: "normal" },
|
||||||
|
{ name: "Inter", data: fonts.semibold, weight: 500, style: "normal" },
|
||||||
|
{ name: "Inter", data: fonts.semibold, weight: 600, style: "normal" },
|
||||||
|
{ name: "Inter", data: fonts.bold, weight: 700, style: "normal" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const resvg = new Resvg(svg, { fitTo: { mode: "width", value: w } });
|
||||||
|
const png = resvg.render().asPng();
|
||||||
|
const pngBuf = Buffer.from(png);
|
||||||
|
cache.set(cacheKey, { png: pngBuf, ts: now });
|
||||||
|
|
||||||
|
setHeaders({
|
||||||
|
"content-type": "image/png",
|
||||||
|
"cache-control": "public, max-age=600, stale-while-revalidate=86400",
|
||||||
|
});
|
||||||
|
return new Response(new Uint8Array(pngBuf), { status: 200 });
|
||||||
|
};
|
||||||
|
|
||||||
|
// satori-html / ultrahtml dekodiert HTML-Entities im Text-Node NICHT zurück,
|
||||||
|
// d.h. "&" würde 1:1 als Text gerendert. Daher nur die Zeichen entfernen,
|
||||||
|
// die das HTML-Parsing brechen würden ("<" und ">"). "&" bleibt raw —
|
||||||
|
// ultrahtml akzeptiert das und satori rendert es als "&".
|
||||||
|
function escapeHtml(s: string): string {
|
||||||
|
return s.replace(/</g, "‹").replace(/>/g, "›");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user