diff --git a/README.md b/README.md index 664d389..fa916ef 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,12 @@ npm install npm run start ``` -## Optimizations to try after you are done deploying - -- Customize your JSON+LD for [FAQ pages](https://rodneylab.com/sveltekit-faq-page-seo/), [organization, or products](https://navillus.dev/blog/json-ld-in-sveltekit). There is a schema for blogposts, but it is so dead simple that swyxkit does not include it. - ## Acknowledgements - Homepage design inspired by Rene Stalder: https://renestalder.me/en/ - swyxkit & swyx.io for so many decisions, but especially github as cms + blog filtering +- Geoff Rich for dynamic og images https://geoffrich.net/posts/svelte-social-image/ ## Todos - customize json+ld -- ogimage for unfurls diff --git a/package-lock.json b/package-lock.json index e6317cb..69c8417 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "snarkdown": "^2.0.0" }, "devDependencies": { + "@resvg/resvg-js": "^2.4.1", "@sveltejs/adapter-auto": "1.0.0-next.72", "@sveltejs/kit": "^1.0.0-next.510", "@tailwindcss/typography": "^0.5.0", @@ -48,6 +49,8 @@ "remark-slug": "^6.1.0", "remark-toc": "^7.2.0", "rss": "^1.2.2", + "satori": "^0.4.2", + "satori-html": "^0.3.2", "svelte": "^3.50.1", "svelte-check": "^2.9.1", "svelte-preprocess": "^4.10.7", @@ -323,6 +326,237 @@ "dev": true, "license": "MIT" }, + "node_modules/@resvg/resvg-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.4.1.tgz", + "integrity": "sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==", + "dev": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@resvg/resvg-js-android-arm-eabi": "2.4.1", + "@resvg/resvg-js-android-arm64": "2.4.1", + "@resvg/resvg-js-darwin-arm64": "2.4.1", + "@resvg/resvg-js-darwin-x64": "2.4.1", + "@resvg/resvg-js-linux-arm-gnueabihf": "2.4.1", + "@resvg/resvg-js-linux-arm64-gnu": "2.4.1", + "@resvg/resvg-js-linux-arm64-musl": "2.4.1", + "@resvg/resvg-js-linux-x64-gnu": "2.4.1", + "@resvg/resvg-js-linux-x64-musl": "2.4.1", + "@resvg/resvg-js-win32-arm64-msvc": "2.4.1", + "@resvg/resvg-js-win32-ia32-msvc": "2.4.1", + "@resvg/resvg-js-win32-x64-msvc": "2.4.1" + } + }, + "node_modules/@resvg/resvg-js-android-arm-eabi": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.4.1.tgz", + "integrity": "sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-android-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.4.1.tgz", + "integrity": "sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.4.1.tgz", + "integrity": "sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.4.1.tgz", + "integrity": "sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.4.1.tgz", + "integrity": "sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-gnu": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.4.1.tgz", + "integrity": "sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.4.1.tgz", + "integrity": "sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-gnu": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.4.1.tgz", + "integrity": "sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.4.1.tgz", + "integrity": "sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-arm64-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.4.1.tgz", + "integrity": "sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-ia32-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.4.1.tgz", + "integrity": "sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-x64-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.4.1.tgz", + "integrity": "sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "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==", + "dev": true, + "dependencies": { + "fflate": "^0.7.3", + "string.prototype.codepointat": "^0.2.1" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/@sveltejs/adapter-auto": { "version": "1.0.0-next.72", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-1.0.0-next.72.tgz", @@ -1060,6 +1294,15 @@ "dev": true, "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==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "dev": true, @@ -1158,6 +1401,15 @@ "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==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001431", "dev": true, @@ -1334,6 +1586,38 @@ "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==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true, + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/cssesc": { "version": "3.0.0", "dev": true, @@ -2560,6 +2844,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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==", + "dev": true + }, "node_modules/file-entry-cache": { "version": "6.0.1", "dev": true, @@ -3264,6 +3554,16 @@ "node": ">=10" } }, + "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==", + "dev": true, + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, "node_modules/lodash.castarray": { "version": "4.4.0", "dev": true, @@ -4236,6 +4536,12 @@ "node": ">= 0.8.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==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "dev": true, @@ -5408,6 +5714,40 @@ "rimraf": "bin.js" } }, + "node_modules/satori": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/satori/-/satori-0.4.2.tgz", + "integrity": "sha512-/q5fFlyzi0qD8jvRxdwnty4cNdNSzt7Ar/23iaruHNhPb2Xo26R5ByOGv7p5223Ag/q2jbHuoOm6H37urE2Rqg==", + "dev": true, + "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.0.0", + "emoji-regex": "^10.2.1", + "linebreak": "^1.1.0", + "postcss-value-parser": "^4.2.0", + "yoga-wasm-web": "^0.3.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==", + "dev": true, + "dependencies": { + "ultrahtml": "^1.2.0" + } + }, + "node_modules/satori/node_modules/emoji-regex": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz", + "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==", + "dev": true + }, "node_modules/section-matter": { "version": "1.0.0", "dev": true, @@ -5606,6 +5946,12 @@ "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==", + "dev": true + }, "node_modules/stringify-entities": { "version": "3.1.0", "dev": true, @@ -5925,6 +6271,12 @@ "globrex": "^0.1.2" } }, + "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==", + "dev": true + }, "node_modules/to-regex-range": { "version": "5.0.1", "dev": true, @@ -6015,6 +6367,12 @@ "node": ">=4.2.0" } }, + "node_modules/ultrahtml": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.2.0.tgz", + "integrity": "sha512-vxZM2yNvajRmCj/SknRYGNXk2tqiy6kRNvZjJLaleG3zJbSh/aNkOqD1/CVzypw8tyHyhpzYuwQgMMhUB4ZVNQ==", + "dev": true + }, "node_modules/undici": { "version": "5.12.0", "dev": true, @@ -6026,6 +6384,16 @@ "node": ">=12.18" } }, + "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==", + "dev": true, + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, "node_modules/unified": { "version": "9.2.2", "dev": true, @@ -6577,6 +6945,12 @@ "node": ">= 6" } }, + "node_modules/yoga-wasm-web": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.2.tgz", + "integrity": "sha512-X//8P6RHzkO4E1ba/wXwKOybeArNDQJI2vwzFLJxz5bfT9K8fB798ZVYvq+Z+hjRA0Wy2iUlOUch4n2+ddqbDQ==", + "dev": true + }, "node_modules/zwitch": { "version": "2.0.2", "license": "MIT", @@ -6767,6 +7141,120 @@ "version": "1.0.0-next.21", "dev": true }, + "@resvg/resvg-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.4.1.tgz", + "integrity": "sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==", + "dev": true, + "requires": { + "@resvg/resvg-js-android-arm-eabi": "2.4.1", + "@resvg/resvg-js-android-arm64": "2.4.1", + "@resvg/resvg-js-darwin-arm64": "2.4.1", + "@resvg/resvg-js-darwin-x64": "2.4.1", + "@resvg/resvg-js-linux-arm-gnueabihf": "2.4.1", + "@resvg/resvg-js-linux-arm64-gnu": "2.4.1", + "@resvg/resvg-js-linux-arm64-musl": "2.4.1", + "@resvg/resvg-js-linux-x64-gnu": "2.4.1", + "@resvg/resvg-js-linux-x64-musl": "2.4.1", + "@resvg/resvg-js-win32-arm64-msvc": "2.4.1", + "@resvg/resvg-js-win32-ia32-msvc": "2.4.1", + "@resvg/resvg-js-win32-x64-msvc": "2.4.1" + } + }, + "@resvg/resvg-js-android-arm-eabi": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.4.1.tgz", + "integrity": "sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-android-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.4.1.tgz", + "integrity": "sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-darwin-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.4.1.tgz", + "integrity": "sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-darwin-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.4.1.tgz", + "integrity": "sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-linux-arm-gnueabihf": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.4.1.tgz", + "integrity": "sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-linux-arm64-gnu": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.4.1.tgz", + "integrity": "sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-linux-arm64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.4.1.tgz", + "integrity": "sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-linux-x64-gnu": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.4.1.tgz", + "integrity": "sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-linux-x64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.4.1.tgz", + "integrity": "sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-win32-arm64-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.4.1.tgz", + "integrity": "sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-win32-ia32-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.4.1.tgz", + "integrity": "sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==", + "dev": true, + "optional": true + }, + "@resvg/resvg-js-win32-x64-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.4.1.tgz", + "integrity": "sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==", + "dev": true, + "optional": true + }, + "@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==", + "dev": true, + "requires": { + "fflate": "^0.7.3", + "string.prototype.codepointat": "^0.2.1" + } + }, "@sveltejs/adapter-auto": { "version": "1.0.0-next.72", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-1.0.0-next.72.tgz", @@ -7249,6 +7737,12 @@ "version": "1.0.2", "dev": true }, + "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==", + "dev": true + }, "binary-extensions": { "version": "2.2.0", "dev": true @@ -7306,6 +7800,12 @@ "version": "2.0.1", "dev": true }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "dev": true + }, "caniuse-lite": { "version": "1.0.30001431", "dev": true @@ -7402,6 +7902,35 @@ "which": "^2.0.1" } }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "cssesc": { "version": "3.0.0", "dev": true @@ -8060,6 +8589,12 @@ "format": "^0.2.0" } }, + "fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "dev": true + }, "file-entry-cache": { "version": "6.0.1", "dev": true, @@ -8487,6 +9022,16 @@ "version": "2.0.6", "dev": true }, + "linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "dev": true, + "requires": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, "lodash.castarray": { "version": "4.4.0", "dev": true @@ -9015,6 +9560,12 @@ "word-wrap": "^1.2.3" } }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true + }, "parent-module": { "version": "1.0.1", "dev": true, @@ -9710,6 +10261,39 @@ } } }, + "satori": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/satori/-/satori-0.4.2.tgz", + "integrity": "sha512-/q5fFlyzi0qD8jvRxdwnty4cNdNSzt7Ar/23iaruHNhPb2Xo26R5ByOGv7p5223Ag/q2jbHuoOm6H37urE2Rqg==", + "dev": true, + "requires": { + "@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.0.0", + "emoji-regex": "^10.2.1", + "linebreak": "^1.1.0", + "postcss-value-parser": "^4.2.0", + "yoga-wasm-web": "^0.3.1" + }, + "dependencies": { + "emoji-regex": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz", + "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==", + "dev": true + } + } + }, + "satori-html": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/satori-html/-/satori-html-0.3.2.tgz", + "integrity": "sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==", + "dev": true, + "requires": { + "ultrahtml": "^1.2.0" + } + }, "section-matter": { "version": "1.0.0", "dev": true, @@ -9841,6 +10425,12 @@ "strip-ansi": "^6.0.1" } }, + "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==", + "dev": true + }, "stringify-entities": { "version": "3.1.0", "dev": true, @@ -10026,6 +10616,12 @@ "globrex": "^0.1.2" } }, + "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==", + "dev": true + }, "to-regex-range": { "version": "5.0.1", "dev": true, @@ -10076,6 +10672,12 @@ "version": "4.8.4", "dev": true }, + "ultrahtml": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.2.0.tgz", + "integrity": "sha512-vxZM2yNvajRmCj/SknRYGNXk2tqiy6kRNvZjJLaleG3zJbSh/aNkOqD1/CVzypw8tyHyhpzYuwQgMMhUB4ZVNQ==", + "dev": true + }, "undici": { "version": "5.12.0", "dev": true, @@ -10083,6 +10685,16 @@ "busboy": "^1.6.0" } }, + "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==", + "dev": true, + "requires": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, "unified": { "version": "9.2.2", "dev": true, @@ -10409,6 +11021,12 @@ "version": "1.10.2", "dev": true }, + "yoga-wasm-web": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.2.tgz", + "integrity": "sha512-X//8P6RHzkO4E1ba/wXwKOybeArNDQJI2vwzFLJxz5bfT9K8fB798ZVYvq+Z+hjRA0Wy2iUlOUch4n2+ddqbDQ==", + "dev": true + }, "zwitch": { "version": "2.0.2" } diff --git a/package.json b/package.json index f035732..76781b4 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "format": "prettier --write --plugin-search-dir=. ." }, "devDependencies": { + "@resvg/resvg-js": "^2.4.1", "@sveltejs/adapter-auto": "1.0.0-next.72", "@sveltejs/kit": "^1.0.0-next.510", "@tailwindcss/typography": "^0.5.0", @@ -43,6 +44,8 @@ "remark-slug": "^6.1.0", "remark-toc": "^7.2.0", "rss": "^1.2.2", + "satori": "^0.4.2", + "satori-html": "^0.3.2", "svelte": "^3.50.1", "svelte-check": "^2.9.1", "svelte-preprocess": "^4.10.7", diff --git a/src/components/Og.svelte b/src/components/Og.svelte new file mode 100644 index 0000000..b907515 --- /dev/null +++ b/src/components/Og.svelte @@ -0,0 +1,32 @@ + + +
+
+ {message} +
+
+ + diff --git a/src/components/PostItem.svelte b/src/components/PostItem.svelte index 652d5e5..5c59287 100644 --- a/src/components/PostItem.svelte +++ b/src/components/PostItem.svelte @@ -41,7 +41,7 @@ diff --git a/src/lib/content/work/greenhouse.svx b/src/lib/content/work/greenhouse.svx index d48cc77..4344622 100644 --- a/src/lib/content/work/greenhouse.svx +++ b/src/lib/content/work/greenhouse.svx @@ -1,7 +1,7 @@ --- name: Greenhouse url: https://greenhouse.io -# image: '/assets/greenhouse.png' +# image: '/static/assets/greenhouse.png' slug: greenhouse description: 'component library, content modeling in craft cms, javascript' type: professional diff --git a/src/lib/font/PTSerif-Regular.ttf b/src/lib/font/PTSerif-Regular.ttf new file mode 100644 index 0000000..f87c0f1 Binary files /dev/null and b/src/lib/font/PTSerif-Regular.ttf differ diff --git a/src/lib/font/readme.md b/src/lib/font/readme.md new file mode 100644 index 0000000..a10d2d8 --- /dev/null +++ b/src/lib/font/readme.md @@ -0,0 +1,3 @@ +this folder is because the dynamic og image needs to BYOF. + +see `src/routes/og` + `src/components/og.svelte` for implementation details diff --git a/src/lib/localContent.js b/src/lib/localContent.js index 65991d6..b723261 100644 --- a/src/lib/localContent.js +++ b/src/lib/localContent.js @@ -10,7 +10,7 @@ export const fetchMarkdownPosts = async () => { const allPosts = await Promise.all( iterablePostFiles.map(async ([path, resolver]) => { const post = await resolver() - const { name, url, slug, description, type, date } = post.metadata + const { name, url, slug, description, type, date, image } = post.metadata const project = { content: post.default.render().html, @@ -20,6 +20,7 @@ export const fetchMarkdownPosts = async () => { description, type, date, + image, path } diff --git a/src/lib/siteConfig.js b/src/lib/siteConfig.js index bbfd698..6baa41c 100644 --- a/src/lib/siteConfig.js +++ b/src/lib/siteConfig.js @@ -3,7 +3,7 @@ export const GH_USER = 'tjheffner'; export const GH_USER_REPO = 'tjheffner/heffdotdev'; // used for pulling github issues and offering comments export const SITE_TITLE = 'heffner.dev'; export const SITE_DESCRIPTION = 'personal site of tanner heffner'; -export const DEFAULT_OG_IMAGE = 'https://avatars.githubusercontent.com/u/11279744'; +export const DEFAULT_OG_IMAGE = 'https://heffner.dev/og?message=heffner.dev'; export const MY_TWITTER_HANDLE = 'foodpyramids'; export const APPROVED_POSTERS_GH_USERNAME = ['tjheffner']; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index bb2b879..d3526e3 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -31,45 +31,29 @@

- ~welcome to my page~ + welcome to my page

-

this site is under construction but coming along :)

+

this site is perpetually under construction but coming along :)

-

it's mostly a reminder of past work and a place for me to post recipes I like, with the occasional technical post or personal blog.

+

it's mostly a reminder of past work and a place for me to post recipes I like, with the occasional technical post or personal blog mixed in.

thanks for stopping by, check out the links that work

- -

Listening:

- - -

Reading:

- - -

Watching:

- - -
- - +
+ + +
+ pull the latest 3 posts that have `technical` or `snippet` category here +
+
diff --git a/src/routes/[slug]/+page.svelte b/src/routes/[slug]/+page.svelte index 48c4088..92dbfcc 100644 --- a/src/routes/[slug]/+page.svelte +++ b/src/routes/[slug]/+page.svelte @@ -13,6 +13,7 @@ export let data; $: json = data.json; // warning: if you try to destructure content here, make sure to make it reactive, or your page content will not update when your user navigates $: canonical = SITE_URL + $page.url.pathname; + @@ -25,13 +26,17 @@ $: canonical = SITE_URL + $page.url.pathname; - {#if json.image} + + {:else} + + + {/if} diff --git a/src/routes/og/+server.js b/src/routes/og/+server.js new file mode 100644 index 0000000..74da74f --- /dev/null +++ b/src/routes/og/+server.js @@ -0,0 +1,43 @@ +// see https://geoffrich.net/posts/svelte-social-image/ +import satori from 'satori'; +import { Resvg } from '@resvg/resvg-js'; +import PTSerif from '$lib/font/PTSerif-Regular.ttf'; +import { html as toReactNode } from 'satori-html'; +import Og from '../../components/Og.svelte'; + +const height = 400; +const width = 800; + +/** @type {import('./$types').RequestHandler} */ +export const GET = async ({ url }) => { + const message = url.searchParams.get('message') ?? undefined; + const result = Og.render({ message }); + const element = toReactNode(`${result.html}`); + + const svg = await satori(element, { + fonts: [ + { + name: 'PT Serif', + data: Buffer.from(PTSerif), + 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' + } + }); +}; diff --git a/src/routes/work/[slug]/+page.svelte b/src/routes/work/[slug]/+page.svelte index 8e89cff..cf15d83 100644 --- a/src/routes/work/[slug]/+page.svelte +++ b/src/routes/work/[slug]/+page.svelte @@ -22,6 +22,9 @@ {#if image} + {:else} + + {/if} diff --git a/static/android-chrome-256x256.png b/static/android-chrome-256x256.png deleted file mode 100644 index 3a2d890..0000000 Binary files a/static/android-chrome-256x256.png and /dev/null differ diff --git a/svelte.config.js b/svelte.config.js index d5840b7..6c1aa3a 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -17,7 +17,7 @@ const mdsvexConfig = { remarkGithub, { // Use your own repository - repository: 'https://github.com/mvasigh/sveltekit-mdsvex-blog.git' + repository: 'https://github.com/tjheffner/heffdotdev.git' } ], remarkAbbr diff --git a/vite.config.js b/vite.config.js index 7055427..ba09fdb 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,9 +1,22 @@ // vite.config.js import { sveltekit } from '@sveltejs/kit/vite'; +import fs from 'fs'; /** @type {import('vite').UserConfig} */ const config = { - plugins: [sveltekit()] + plugins: [sveltekit(), rawFonts(['.ttf'])] }; +function rawFonts(ext) { + return { + name: 'vite-plugin-raw-fonts', + 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;