diff --git a/package.json b/package.json index 18245ff..52ef3eb 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@iconify-icons/simple-icons": "^1.2.74", "@melt-ui/pp": "^0.1.4", "@playwright/test": "^1.40.1", + "@resvg/resvg-js": "^2.6.0", "@sveltejs/adapter-static": "^2.0.3", "@sveltejs/adapter-vercel": "^1.0.6", "@sveltejs/enhanced-img": "^0.1.5", @@ -44,6 +45,8 @@ "prettier": "^2.8.8", "prettier-plugin-svelte": "^2.10.1", "sass": "^1.69.5", + "satori": "^0.10.11", + "satori-html": "^0.3.2", "scrape-it": "^6.1.0", "sharp": "^0.32.6", "svelte": "^4.2.8", @@ -57,7 +60,7 @@ "vanilla-lazyload": "^17.8.5", "vite": "^4.5.1", "vite-imagetools": "^5.1.2", - "vitest": "^0.32.2" + "vitest": "^0.32.4" }, "type": "module", "engines": { @@ -67,6 +70,7 @@ "dependencies": { "@melt-ui/svelte": "^0.50.1", "@types/nprogress": "^0.2.3", + "@vercel/og": "^0.5.20", "ioredis": "^5.3.2", "nprogress": "^0.2.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 634dd50..492b175 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@types/nprogress': specifier: ^0.2.3 version: 0.2.3 + '@vercel/og': + specifier: ^0.5.20 + version: 0.5.20 ioredis: specifier: ^5.3.2 version: 5.3.2 @@ -37,6 +40,9 @@ devDependencies: '@playwright/test': specifier: ^1.40.1 version: 1.40.1 + '@resvg/resvg-js': + specifier: ^2.6.0 + version: 2.6.0 '@sveltejs/adapter-static': specifier: ^2.0.3 version: 2.0.3(@sveltejs/kit@1.27.7) @@ -103,6 +109,12 @@ devDependencies: sass: specifier: ^1.69.5 version: 1.69.5 + satori: + specifier: ^0.10.11 + version: 0.10.11 + satori-html: + specifier: ^0.3.2 + version: 0.3.2 scrape-it: specifier: ^6.1.0 version: 6.1.0 @@ -143,7 +155,7 @@ devDependencies: specifier: ^5.1.2 version: 5.1.2 vitest: - specifier: ^0.32.2 + specifier: ^0.32.4 version: 0.32.4(sass@1.69.5) packages: @@ -1382,6 +1394,137 @@ packages: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true + /@resvg/resvg-js-android-arm-eabi@2.6.0: + resolution: {integrity: sha512-lJnZ/2P5aMocrFMW7HWhVne5gH82I8xH6zsfH75MYr4+/JOaVcGCTEQ06XFohGMdYRP3v05SSPLPvTM/RHjxfA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-android-arm64@2.6.0: + resolution: {integrity: sha512-N527f529bjMwYWShZYfBD60dXA4Fux+D695QsHQ93BDYZSHUoOh1CUGUyICevnTxs7VgEl98XpArmUWBZQVMfQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-darwin-arm64@2.6.0: + resolution: {integrity: sha512-MabUKLVayEwlPo0mIqAmMt+qESN8LltCvv5+GLgVga1avpUrkxj/fkU1TKm8kQegutUjbP/B0QuMuUr0uhF8ew==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-darwin-x64@2.6.0: + resolution: {integrity: sha512-zrFetdnSw/suXjmyxSjfDV7i61hahv6DDG6kM7BYN2yJ3Es5+BZtqYZTcIWogPJedYKmzN1YTMWGd/3f0ubFiA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-linux-arm-gnueabihf@2.6.0: + resolution: {integrity: sha512-sH4gxXt7v7dGwjGyzLwn7SFGvwZG6DQqLaZ11MmzbCwd9Zosy1TnmrMJfn6TJ7RHezmQMgBPi18bl55FZ1AT4A==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-linux-arm64-gnu@2.6.0: + resolution: {integrity: sha512-fCyMncqCJtrlANADIduYF4IfnWQ295UKib7DAxFXQhBsM9PLDTpizr0qemZcCNadcwSVHnAIzL4tliZhCM8P6A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-linux-arm64-musl@2.6.0: + resolution: {integrity: sha512-ouLjTgBQHQyxLht4FdMPTvuY8xzJigM9EM2Tlu0llWkN1mKyTQrvYWi6TA6XnKdzDJHy7ZLpWpjZi7F5+Pg+Vg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-linux-x64-gnu@2.6.0: + resolution: {integrity: sha512-n3zC8DWsvxC1AwxpKFclIPapDFibs5XdIRoV/mcIlxlh0vseW1F49b97F33BtJQRmlntsqqN6GMMqx8byB7B+Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-linux-x64-musl@2.6.0: + resolution: {integrity: sha512-n4tasK1HOlAxdTEROgYA1aCfsEKk0UOFDNd/AQTTZlTmCbHKXPq+O8npaaKlwXquxlVK8vrkcWbksbiGqbCAcw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-win32-arm64-msvc@2.6.0: + resolution: {integrity: sha512-X2+EoBJFwDI5LDVb51Sk7ldnVLitMGr9WwU/i21i3fAeAXZb3hM16k67DeTy16OYkT2dk/RfU1tP1wG+rWbz2Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-win32-ia32-msvc@2.6.0: + resolution: {integrity: sha512-L7oevWjQoUgK5W1fCKn0euSVemhDXVhrjtwqpc7MwBKKimYeiOshO1Li1pa8bBt5PESahenhWgdB6lav9O0fEg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js-win32-x64-msvc@2.6.0: + resolution: {integrity: sha512-8lJlghb+Unki5AyKgsnFbRJwkEj9r1NpwyuBG8yEJiG1W9eEGl03R3I7bsVa3haof/3J1NlWf0rzSa1G++A2iw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@resvg/resvg-js@2.6.0: + resolution: {integrity: sha512-Tf3YpbBKcQn991KKcw/vg7vZf98v01seSv6CVxZBbRkL/xyjnoYB6KgrFL6zskT1A4dWC/vg77KyNOW+ePaNlA==} + engines: {node: '>= 10'} + optionalDependencies: + '@resvg/resvg-js-android-arm-eabi': 2.6.0 + '@resvg/resvg-js-android-arm64': 2.6.0 + '@resvg/resvg-js-darwin-arm64': 2.6.0 + '@resvg/resvg-js-darwin-x64': 2.6.0 + '@resvg/resvg-js-linux-arm-gnueabihf': 2.6.0 + '@resvg/resvg-js-linux-arm64-gnu': 2.6.0 + '@resvg/resvg-js-linux-arm64-musl': 2.6.0 + '@resvg/resvg-js-linux-x64-gnu': 2.6.0 + '@resvg/resvg-js-linux-x64-musl': 2.6.0 + '@resvg/resvg-js-win32-arm64-msvc': 2.6.0 + '@resvg/resvg-js-win32-ia32-msvc': 2.6.0 + '@resvg/resvg-js-win32-x64-msvc': 2.6.0 + dev: true + + /@resvg/resvg-wasm@2.6.0: + resolution: {integrity: sha512-iDkBM6Ivex8nULtBu8cX670/lfsGxq8U1cuqE+qS9xFpPQP1enPdVm/33Kq3+B+bAldA+AHNZnCgpmlHo/fZrQ==} + engines: {node: '>= 10'} + dev: false + /@rollup/pluginutils@4.2.1: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -1418,6 +1561,14 @@ packages: picomatch: 2.3.1 dev: true + /@shuding/opentype.js@1.4.0-beta.0: + resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==} + engines: {node: '>= 8.0.0'} + hasBin: true + dependencies: + fflate: 0.7.4 + string.prototype.codepointat: 0.2.1 + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true @@ -1724,6 +1875,15 @@ packages: - supports-color dev: true + /@vercel/og@0.5.20: + resolution: {integrity: sha512-zi+ZXSx/peXA+1lq7s/5Vzmm/TTfTSf/5P1qNYnh42+7X+pZmahWoXt0i7SWiq3WagfsNUNA4hUDapDiHRoXqA==} + engines: {node: '>=16'} + dependencies: + '@resvg/resvg-wasm': 2.6.0 + satori: 0.10.9 + yoga-wasm-web: 0.3.3 + dev: false + /@vitest/expect@0.32.4: resolution: {integrity: sha512-m7EPUqmGIwIeoU763N+ivkFjTzbaBn0n9evsTOcde03ugy2avPs3kZbYmw3DkcH1j5mxhMhdamJkLQ6dM1bk/A==} dependencies: @@ -1904,7 +2064,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.11 - caniuse-lite: 1.0.30001539 + caniuse-lite: 1.0.30001566 fraction.js: 4.3.6 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -1943,6 +2103,10 @@ packages: typpy: 2.3.13 dev: true + /base64-js@0.0.8: + resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} + engines: {node: '>= 0.4'} + /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: true @@ -1989,7 +2153,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001539 + caniuse-lite: 1.0.30001566 electron-to-chromium: 1.4.529 node-releases: 2.0.13 update-browserslist-db: 1.0.13(browserslist@4.21.11) @@ -2000,7 +2164,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001510 + caniuse-lite: 1.0.30001566 electron-to-chromium: 1.4.447 node-releases: 2.0.12 update-browserslist-db: 1.0.11(browserslist@4.21.9) @@ -2027,12 +2191,11 @@ packages: engines: {node: '>=6'} dev: true - /caniuse-lite@1.0.30001510: - resolution: {integrity: sha512-z35lD6xjHklPNgjW4P68R30Er5OCIZE0C1bUf8IMXSh34WJYXfIy+GxIEraQXYJ2dvTU8TumjYAeLrPhpMlsuw==} - dev: true + /camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} - /caniuse-lite@1.0.30001539: - resolution: {integrity: sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==} + /caniuse-lite@1.0.30001566: + resolution: {integrity: sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==} dev: true /chai@4.3.7: @@ -2140,7 +2303,6 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true /color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} @@ -2192,6 +2354,9 @@ packages: which: 2.0.2 dev: true + /css-background-parser@0.1.0: + resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} + /css-blank-pseudo@5.0.2(postcss@8.4.32): resolution: {integrity: sha512-aCU4AZ7uEcVSUzagTlA9pHciz7aWPKA/YzrEkpdSopJ2pvhIxiQ5sYeMz1/KByxlIo4XBdvMNJAVKMg/GRnhfw==} engines: {node: ^14 || ^16 || >=18} @@ -2202,6 +2367,13 @@ packages: postcss-selector-parser: 6.0.13 dev: true + /css-box-shadow@1.0.0-3: + resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==} + + /css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + /css-has-pseudo@5.0.2(postcss@8.4.32): resolution: {integrity: sha512-q+U+4QdwwB7T9VEW/LyO6CFrLAeLqOykC5mDqJXc7aKZAhDbq7BvGT13VGJe+IwBfdN2o3Xdw2kJ5IxwV1Sc9Q==} engines: {node: ^14 || ^16 || >=18} @@ -2233,6 +2405,13 @@ packages: nth-check: 2.1.1 dev: true + /css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + /css-tree@2.3.1: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -2387,6 +2566,9 @@ packages: resolution: {integrity: sha512-6uyPyXTo8lkv8SWAmjKFbG42U073TXlzD4R8rW3EzuznhFS2olCIAfjjQtV2dV2ar/vRF55KUd3zQYnCB0dd3A==} dev: true + /emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true @@ -2479,6 +2661,9 @@ packages: engines: {node: '>=6'} dev: true + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -2693,6 +2878,9 @@ packages: reusify: 1.0.4 dev: true + /fflate@0.7.4: + resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2899,6 +3087,10 @@ packages: function-bind: 1.1.1 dev: true + /hex-rgb@4.3.0: + resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} + engines: {node: '>=6'} + /htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} dependencies: @@ -3118,6 +3310,12 @@ packages: engines: {node: '>=14'} dev: true + /linebreak@1.1.0: + resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} + dependencies: + base64-js: 0.0.8 + unicode-trie: 2.0.0 + /local-pkg@0.4.3: resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} engines: {node: '>=14'} @@ -3465,6 +3663,9 @@ packages: p-limit: 3.1.0 dev: true + /pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -3472,6 +3673,12 @@ packages: callsites: 3.1.0 dev: true + /parse-css-color@0.2.1: + resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==} + dependencies: + color-name: 1.1.4 + hex-rgb: 4.3.0 + /parse5-htmlparser2-tree-adapter@7.0.0: resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} dependencies: @@ -3966,7 +4173,6 @@ packages: /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: true /postcss@8.4.32: resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==} @@ -4193,6 +4399,44 @@ packages: source-map-js: 1.0.2 dev: true + /satori-html@0.3.2: + resolution: {integrity: sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==} + dependencies: + ultrahtml: 1.5.2 + dev: true + + /satori@0.10.11: + resolution: {integrity: sha512-yLm1xPRPZUaKcBZJ6nmezoJjHB4MqV8x7Mu0PyZUJodRWRDD27UbeMwzuY9LEGG57WYLO4CQsGPlbHWV1Ex9TQ==} + engines: {node: '>=16'} + dependencies: + '@shuding/opentype.js': 1.4.0-beta.0 + css-background-parser: 0.1.0 + css-box-shadow: 1.0.0-3 + css-to-react-native: 3.2.0 + emoji-regex: 10.3.0 + escape-html: 1.0.3 + linebreak: 1.1.0 + parse-css-color: 0.2.1 + postcss-value-parser: 4.2.0 + yoga-wasm-web: 0.3.3 + dev: true + + /satori@0.10.9: + resolution: {integrity: sha512-XU9EELUEZuioT4acLIpCXxHcFzrsC8muvg0MY28d+TlqwxbkTzBmWbw+3+hnCzXT7YZ0Qm8k3eXktDaEu+qmEw==} + engines: {node: '>=16'} + dependencies: + '@shuding/opentype.js': 1.4.0-beta.0 + css-background-parser: 0.1.0 + css-box-shadow: 1.0.0-3 + css-to-react-native: 3.2.0 + emoji-regex: 10.3.0 + escape-html: 1.0.3 + linebreak: 1.1.0 + parse-css-color: 0.2.1 + postcss-value-parser: 4.2.0 + yoga-wasm-web: 0.3.3 + dev: false + /schema-dts@1.1.2(typescript@5.3.3): resolution: {integrity: sha512-MpNwH0dZJHinVxk9bT8XUdjKTxMYrA5bLtrrGmFA6PTLwlOKnhi67XoRd6/ty+Djt6ZC0slR57qFhZDNMI6DhQ==} peerDependencies: @@ -4404,6 +4648,9 @@ packages: strip-ansi: 6.0.1 dev: true + /string.prototype.codepointat@0.2.1: + resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: @@ -4666,6 +4913,9 @@ packages: globrex: 0.1.2 dev: true + /tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + /tinybench@2.5.1: resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} dev: true @@ -4753,6 +5003,10 @@ packages: resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==} dev: true + /ultrahtml@1.5.2: + resolution: {integrity: sha512-qh4mBffhlkiXwDAOxvSGxhL0QEQsTbnP9BozOK3OYPEGvPvdWzvAUaXNtUSMdNsKDtuyjEbyVUPFZ52SSLhLqw==} + dev: true + /undici@5.26.5: resolution: {integrity: sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==} engines: {node: '>=14.0'} @@ -4760,6 +5014,12 @@ packages: '@fastify/busboy': 2.0.0 dev: true + /unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + /unist-util-is@5.2.0: resolution: {integrity: sha512-Glt17jWwZeyqrFqOK0pF1Ded5U3yzJnFr8CG1GMjCWTp9zDo2p+cmD6pWbZU8AgM5WU3IzRv6+rBwhzsGh6hBQ==} dev: true @@ -5054,3 +5314,6 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} dev: true + + /yoga-wasm-web@0.3.3: + resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} diff --git a/src/lib/assets/images/Bradley_Shellnut_New_Site.png b/src/lib/assets/images/portfolio/Bradley_Shellnut_New_Site.png similarity index 100% rename from src/lib/assets/images/Bradley_Shellnut_New_Site.png rename to src/lib/assets/images/portfolio/Bradley_Shellnut_New_Site.png diff --git a/src/lib/assets/images/Mark_Shellnut_Architect.png b/src/lib/assets/images/portfolio/Mark_Shellnut_Architect.png similarity index 100% rename from src/lib/assets/images/Mark_Shellnut_Architect.png rename to src/lib/assets/images/portfolio/Mark_Shellnut_Architect.png diff --git a/src/lib/assets/images/Old_Website_Bradley_Shellnut.png b/src/lib/assets/images/portfolio/Old_Website_Bradley_Shellnut.png similarity index 100% rename from src/lib/assets/images/Old_Website_Bradley_Shellnut.png rename to src/lib/assets/images/portfolio/Old_Website_Bradley_Shellnut.png diff --git a/src/lib/assets/images/Wedding_Website.png b/src/lib/assets/images/portfolio/Wedding_Website.png similarity index 100% rename from src/lib/assets/images/Wedding_Website.png rename to src/lib/assets/images/portfolio/Wedding_Website.png diff --git a/src/lib/components/socialImageCard.svelte b/src/lib/components/socialImageCard.svelte new file mode 100644 index 0000000..08a5c0e --- /dev/null +++ b/src/lib/components/socialImageCard.svelte @@ -0,0 +1,98 @@ + + +
+
+ +
+
+

{page}

+

{content}

+
+ +
+ + \ No newline at end of file diff --git a/src/lib/content/portfolio/personal-website-gatsby.ts b/src/lib/content/portfolio/personal-website-gatsby.ts index 217c61a..a14e1cd 100644 --- a/src/lib/content/portfolio/personal-website-gatsby.ts +++ b/src/lib/content/portfolio/personal-website-gatsby.ts @@ -1,6 +1,6 @@ -import meta from '$lib/assets/images/Bradley_Shellnut_New_Site.png?metadata'; -import formatMeta from '$lib/assets/images/Bradley_Shellnut_New_Site.png?format=webp;avif;png&metadata'; -import placeholder from '$lib/assets/images/Bradley_Shellnut_New_Site.png?w=100&png&blur=10'; +import meta from '$lib/assets/images/portfolio/Bradley_Shellnut_New_Site.png?metadata'; +import formatMeta from '$lib/assets/images/portfolio/Bradley_Shellnut_New_Site.png?format=webp;avif;png&metadata'; +import placeholder from '$lib/assets/images/portfolio/Bradley_Shellnut_New_Site.png?w=100&png&blur=10'; type ImageMeta = { format: string; diff --git a/src/lib/fonts/FiraSans-Bold.ttf b/src/lib/fonts/FiraSans-Bold.ttf new file mode 100644 index 0000000..e3593fb Binary files /dev/null and b/src/lib/fonts/FiraSans-Bold.ttf differ diff --git a/src/lib/fonts/FiraSans-SemiBold.ttf b/src/lib/fonts/FiraSans-SemiBold.ttf new file mode 100644 index 0000000..0c93b7e Binary files /dev/null and b/src/lib/fonts/FiraSans-SemiBold.ttf differ diff --git a/src/lib/fonts/NotoSans-Regular.ttf b/src/lib/fonts/NotoSans-Regular.ttf new file mode 100644 index 0000000..7552fbe Binary files /dev/null and b/src/lib/fonts/NotoSans-Regular.ttf differ diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index 801b62f..d81ddad 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -19,10 +19,10 @@ export const load: PageServerLoad = async ({ fetch, setHeaders, url }) => { locale: 'en_US', images: [ { - url: `${baseUrl}b_shell_nut_favicon.gif`, - alt: 'Bradley Shellnut Website Logo', - width: 512, - height: 512 + url: `${baseUrl}og?header=Home | bradleyshellnut.com&page=Hi I'm Bradley Shellnut.&content=I'm a full stack software engineer currently working on Java Spring, PostgreSQL, and React / Angular JS.`, + alt: 'Bradley Shellnut Website Home Page', + width: 1200, + height: 630 } ] }, @@ -30,7 +30,7 @@ export const load: PageServerLoad = async ({ fetch, setHeaders, url }) => { title: 'Home', description: 'Home page', card: 'summary_large_image', - image: `${baseUrl}b_shell_nut_favicon.gif`, + image: `${baseUrl}og?header=Home | bradleyshellnut.com&page=Hi I'm Bradley Shellnut.&content=I'm a full stack software engineer currently working on Java Spring, PostgreSQL, and React / Angular JS.`, imageAlt: 'Bradley Shellnut Website Logo' }, url: currentPageUrl diff --git a/src/routes/about/+page.ts b/src/routes/about/+page.ts index a721ccd..dd199ec 100644 --- a/src/routes/about/+page.ts +++ b/src/routes/about/+page.ts @@ -19,10 +19,10 @@ export const load: PageLoad = async ({ url }) => { locale: 'en_US', images: [ { - url: `${baseUrl}b_shell_nut_favicon.gif`, - alt: 'Bradley Shellnut Website Logo', - width: 512, - height: 512 + url: `${baseUrl}og?header=About | bradleyshellnut.com&page=Hey! My name is Bradley Shellnut.&content=I am a full stack software engineer who's interested in new tech and not afraid to discover new interests.`, + alt: 'About Bradley Shellnut', + width: 1200, + height: 630 } ] }, @@ -30,7 +30,7 @@ export const load: PageLoad = async ({ url }) => { title: 'About', description: 'About page', card: 'summary_large_image', - image: `${baseUrl}b_shell_nut_favicon.gif`, + image: `${baseUrl}og?header=About | bradleyshellnut.com&page=Hey! My name is Bradley Shellnut.&content=I am a full stack software engineer who's interested in new tech and not afraid to discover new interests.`, imageAlt: 'Bradley Shellnut Website Logo' }, url: currentPageUrl diff --git a/src/routes/articles/[page]/+page.server.ts b/src/routes/articles/[page]/+page.server.ts index 1e5277b..585ba4b 100644 --- a/src/routes/articles/[page]/+page.server.ts +++ b/src/routes/articles/[page]/+page.server.ts @@ -49,10 +49,10 @@ export const load: PageServerLoad = async ({ fetch, params, setHeaders, url }) = locale: 'en_US', images: [ { - url: `${baseUrl}b_shell_nut_favicon.gif`, - alt: 'Bradley Shellnut Website Logo', - width: 512, - height: 512 + url: `${baseUrl}og?header=Articles Page ${page} | bradleyshellnut.com&page=My favorite articles`, + alt: `Bradley Shellnut Articles Page ${page}`, + width: 1200, + height: 630 } ] }, @@ -60,7 +60,7 @@ export const load: PageServerLoad = async ({ fetch, params, setHeaders, url }) = title: 'Favorite Articles', description: 'My favorite articles', card: 'summary_large_image', - image: `${baseUrl}b_shell_nut_favicon.gif`, + image: `${baseUrl}og?header=Articles Page ${page} | bradleyshellnut.com&page=My favorite articles`, imageAlt: 'Bradley Shellnut Website Logo' }, url: currentPageUrl diff --git a/src/routes/og/+server.ts b/src/routes/og/+server.ts new file mode 100644 index 0000000..d9fcd07 --- /dev/null +++ b/src/routes/og/+server.ts @@ -0,0 +1,57 @@ +import type { RequestHandler } from '@sveltejs/kit'; +import satori from 'satori'; +import { Resvg } from '@resvg/resvg-js'; +import { html as toReactNode } from 'satori-html'; +import FiraSansSemiBold from '$lib/fonts/FiraSans-SemiBold.ttf'; +import SocialImageCard from '$lib/components/socialImageCard.svelte'; + +const height = 630; +const width = 1200; + +export const GET: RequestHandler = async ({ url }) => { + try { + const ogImage = `${new URL(url.origin).href}/b_shell_nut_favicon.png`; + const header = url.searchParams.get('header') ?? undefined; + const page = url.searchParams.get('page') ?? undefined; + const content = url.searchParams.get('content') ?? ''; + const result = SocialImageCard.render({ + header, + page, + content, + image: ogImage, + width, + height, + url: new URL(url.origin).href + }); + console.log('result', result); + const element = toReactNode(`${result.html}`); + const svg = await satori(element, { + fonts: [ + { + name: 'Fira Sans', + data: Buffer.from(FiraSansSemiBold), + style: 'normal' + } + ], + height, + width + }); + + const resvg = new Resvg(svg, { + fitTo: { + mode: 'width', + value: width + } + }); + + const image = resvg.render(); + + return new Response(image.asPng(), { + headers: { + 'content-type': 'image/png' + } + }); + } catch (e) { + console.error(e); + } +}; diff --git a/src/routes/portfolio/+page.server.ts b/src/routes/portfolio/+page.server.ts index db27128..b3c21ac 100644 --- a/src/routes/portfolio/+page.server.ts +++ b/src/routes/portfolio/+page.server.ts @@ -20,10 +20,10 @@ export const load: PageServerLoad = async ({ url }) => { locale: 'en_US', images: [ { - url: `${baseUrl}b_shell_nut_favicon.gif`, - alt: 'Bradley Shellnut Website Logo', - width: 512, - height: 512 + url: `${baseUrl}og?header=Portfolio | bradleyshellnut.com&page=My portfolio of sites I have created.`, + alt: 'Bradley Shellnut Portfolio Page', + width: 1200, + height: 630 } ] }, @@ -31,7 +31,7 @@ export const load: PageServerLoad = async ({ url }) => { title: 'Portfolio', description: "Bradley Shellnut's Portfolio", card: 'summary_large_image', - image: `${baseUrl}b_shell_nut_favicon.gif`, + image: `${baseUrl}og?header=Portfolio | bradleyshellnut.com&page=My portfolio of sites I have created.`, imageAlt: 'Bradley Shellnut Website Logo' }, url: currentPageUrl diff --git a/src/routes/portfolio/+page.svelte b/src/routes/portfolio/+page.svelte index bca690f..1caa122 100644 --- a/src/routes/portfolio/+page.svelte +++ b/src/routes/portfolio/+page.svelte @@ -2,10 +2,10 @@ import { createTabs, melt } from '@melt-ui/svelte'; import GitHub from '@iconify-icons/simple-icons/github'; import Portfolio from '$lib/components/Portfolio.svelte'; - import personalSite from "$lib/assets/images/Bradley_Shellnut_New_Site.png?as=run"; - import weddingWebsite from "$lib/assets/images/Wedding_Website.png?as=run"; - import oldSite from '$lib/assets/images/Old_Website_Bradley_Shellnut.png?as=run'; - import shellnutArchitectWebsite from "$lib/assets/images/Mark_Shellnut_Architect.png?as=run"; + import personalSite from "$lib/assets/images/portfolio/Bradley_Shellnut_New_Site.png?as=run"; + import weddingWebsite from "$lib/assets/images/portfolio/Wedding_Website.png?as=run"; + import oldSite from '$lib/assets/images/portfolio/Old_Website_Bradley_Shellnut.png?as=run'; + import shellnutArchitectWebsite from "$lib/assets/images/portfolio/Mark_Shellnut_Architect.png?as=run"; import PersonalWebsiteSvelteKit from "$lib/content/portfolio/personal/personal-website-sveltekit.md"; import WeddingWebsite from '$lib/content/portfolio/personal/wedding-website.md'; import MarkShellnutArchitect from '$lib/content/portfolio/professional/mark-shellnut-architect.md'; diff --git a/src/routes/privacy/+page.ts b/src/routes/privacy/+page.ts index bb01220..428bc19 100644 --- a/src/routes/privacy/+page.ts +++ b/src/routes/privacy/+page.ts @@ -20,10 +20,10 @@ export const load: PageLoad = async ({ url }) => { locale: 'en_US', images: [ { - url: `${baseUrl}b_shell_nut_favicon.gif`, - alt: 'Bradley Shellnut Website Logo', - width: 512, - height: 512 + url: `${baseUrl}og?header=Privacy Blog | bradleyshellnut.com&page=My thoughts on personal internet privacy.`, + alt: 'Bradley Shellnut Privacy Blog', + width: 1200, + height: 630 } ] }, @@ -31,7 +31,7 @@ export const load: PageLoad = async ({ url }) => { title: 'Privacy Blog', description: 'My thoughts on personal internet privacy.', card: 'summary_large_image', - image: `${baseUrl}b_shell_nut_favicon.gif`, + image: `${baseUrl}og?header=Privacy Blog | bradleyshellnut.com&page=My thoughts on personal internet privacy.`, imageAlt: 'Bradley Shellnut Website Logo' }, url: currentPageUrl diff --git a/src/routes/uses/+page.ts b/src/routes/uses/+page.ts index 3897a34..5f5db54 100644 --- a/src/routes/uses/+page.ts +++ b/src/routes/uses/+page.ts @@ -20,10 +20,10 @@ export const load: PageLoad = async ({ url }) => { locale: 'en_US', images: [ { - url: `${baseUrl}b_shell_nut_favicon.gif`, - alt: 'Bradley Shellnut Website Logo', - width: 512, - height: 512 + url: `${baseUrl}og?header=Uses | bradleyshellnut.com&page=What I use!`, + alt: 'Bradley Shellnut Uses Page', + width: 1200, + height: 630 } ] }, @@ -31,7 +31,7 @@ export const load: PageLoad = async ({ url }) => { title: '/Uses', description: "What I use!", card: 'summary_large_image', - image: `${baseUrl}b_shell_nut_favicon.gif`, + image: `${baseUrl}og?header=Uses | bradleyshellnut.com&page=What I use!`, imageAlt: 'Bradley Shellnut Website Logo' }, url: currentPageUrl diff --git a/vite.config.ts b/vite.config.ts index 4cf40fc..b1a76dc 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,4 +1,5 @@ import { sveltekit } from '@sveltejs/kit/vite'; +import fs from 'fs'; import type { UserConfig } from 'vite'; import { imagetools } from '@zerodevx/svelte-img/vite'; @@ -11,11 +12,27 @@ const config: UserConfig = { profiles: { run: new URLSearchParams('?w=300;480;640;1024;1920&format=avif;webp;jpg&as=run:64') } - }) + }), + rawFonts(['.ttf']) ], test: { include: ['src/**/*.{test,spec}.{js,ts}'] } }; +function rawFonts(ext) { + return { + name: 'vite-plugin-raw-fonts', + resolveId(id) { + return ext.some((e) => id.endsWith(e)) ? id : null; + }, + transform(code, id) { + if (ext.some((e) => id.endsWith(e))) { + const buffer = fs.readFileSync(id); + return { code: `export default ${JSON.stringify(buffer)}`, map: null }; + } + } + }; +} + export default config;