diff --git a/astro.config.mjs b/astro.config.mjs index 53e49de..a382b41 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -5,5 +5,6 @@ import react from '@astrojs/react'; // https://astro.build/config export default defineConfig({ + site: 'https://davegilligan.com', integrations: [react()] }); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9abe7cd..4456c31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,10 @@ "react": "^19.2.4", "react-dom": "^19.2.4" }, + "devDependencies": { + "@resvg/resvg-js": "^2.6.2", + "satori": "^0.26.0" + }, "engines": { "node": ">=22.12.0" } @@ -1382,6 +1386,234 @@ "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", "license": "MIT" }, + "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==", + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", @@ -1835,6 +2067,23 @@ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT" }, + "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, + "license": "MIT", + "dependencies": { + "fflate": "^0.7.3", + "string.prototype.codepointat": "^0.2.1" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2121,6 +2370,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.10.15", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.15.tgz", @@ -2172,6 +2431,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "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, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001785", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001785.tgz", @@ -2271,6 +2540,13 @@ "node": ">=6" } }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -2333,6 +2609,40 @@ "uncrypto": "^0.1.3" } }, + "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, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/css-select": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", @@ -2349,6 +2659,18 @@ "url": "https://github.com/sponsors/fb55" } }, + "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, + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -2590,6 +2912,16 @@ "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", "license": "ISC" }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -2658,6 +2990,13 @@ "node": ">=6" } }, + "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==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -2729,6 +3068,13 @@ } } }, + "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, + "license": "MIT" + }, "node_modules/flattie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", @@ -2982,6 +3328,19 @@ "url": "https://opencollective.com/unified" } }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/html-escaper": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", @@ -3115,6 +3474,17 @@ "node": ">=6" } }, + "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, + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -4145,6 +4515,24 @@ "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", "license": "MIT" }, + "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, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^1.1.4", + "hex-rgb": "^4.1.0" + } + }, "node_modules/parse-latin": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", @@ -4227,6 +4615,13 @@ "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==", + "dev": true, + "license": "MIT" + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -4566,6 +4961,29 @@ "fsevents": "~2.3.2" } }, + "node_modules/satori": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/satori/-/satori-0.26.0.tgz", + "integrity": "sha512-tkMFrfIs3l2mQ2JEcyW0ADTy3zGggFRFzi6Ef8YozQSFsFKEqaSO1Y8F9wJg4//PJGQauMalHGTUEkPrFwhVPA==", + "dev": true, + "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/sax": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", @@ -4694,6 +5112,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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, + "license": "MIT" + }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -4838,6 +5263,17 @@ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -5304,6 +5740,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "dev": true, + "license": "MIT" + }, "node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", diff --git a/package.json b/package.json index fc841e4..cd62fb9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "prebuild": "node scripts/sync-family-lab.mjs", "build": "astro build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "og": "node scripts/generate-og-images.mjs" }, "dependencies": { "@astrojs/react": "^5.0.2", @@ -20,5 +21,9 @@ "astro": "^6.1.3", "react": "^19.2.4", "react-dom": "^19.2.4" + }, + "devDependencies": { + "@resvg/resvg-js": "^2.6.2", + "satori": "^0.26.0" } } diff --git a/public/assets/ai-lab/api-flow.svg b/public/assets/ai-lab/api-flow.svg new file mode 100644 index 0000000..9e0aeb3 --- /dev/null +++ b/public/assets/ai-lab/api-flow.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/assets/ai-lab/corpus-grid.svg b/public/assets/ai-lab/corpus-grid.svg new file mode 100644 index 0000000..e0fc037 --- /dev/null +++ b/public/assets/ai-lab/corpus-grid.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/assets/ai-lab/hero-lab.svg b/public/assets/ai-lab/hero-lab.svg new file mode 100644 index 0000000..5d859f3 --- /dev/null +++ b/public/assets/ai-lab/hero-lab.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/assets/dg-seal.svg b/public/assets/dg-seal.svg new file mode 100644 index 0000000..071f4de --- /dev/null +++ b/public/assets/dg-seal.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + DAVE · GILLIGAN + · KONGSBERG · EDITION · + + + + + + DG + + + + + + + + + \ No newline at end of file diff --git a/public/assets/dingbats.svg b/public/assets/dingbats.svg new file mode 100644 index 0000000..f8fb1b9 --- /dev/null +++ b/public/assets/dingbats.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/assets/gidouille.svg b/public/assets/gidouille.svg new file mode 100644 index 0000000..8065477 --- /dev/null +++ b/public/assets/gidouille.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/assets/schools/escp-moore.svg b/public/assets/schools/escp-moore.svg new file mode 100644 index 0000000..9f574f8 --- /dev/null +++ b/public/assets/schools/escp-moore.svg @@ -0,0 +1,10 @@ + + + + ESCP + AND + DARLA MOORE + + DUAL DEGREE PROGRAM + PARIS AND COLUMBIA + \ No newline at end of file diff --git a/public/assets/schools/european-university.svg b/public/assets/schools/european-university.svg new file mode 100644 index 0000000..0d82ca8 --- /dev/null +++ b/public/assets/schools/european-university.svg @@ -0,0 +1,9 @@ + + + + EUROPEAN + UNIVERSITY + + INSTITUT COOREMAN + BRUSSELS + \ No newline at end of file diff --git a/public/assets/schools/jagiellonian.svg b/public/assets/schools/jagiellonian.svg new file mode 100644 index 0000000..8702f42 --- /dev/null +++ b/public/assets/schools/jagiellonian.svg @@ -0,0 +1,9 @@ + + + + UNIWERSYTET + JAGIELLONIAN + + EST. 1364 + KRAKOW + \ No newline at end of file diff --git a/public/assets/schools/usn.svg b/public/assets/schools/usn.svg new file mode 100644 index 0000000..15eee79 --- /dev/null +++ b/public/assets/schools/usn.svg @@ -0,0 +1,8 @@ + + + + USN + + UNIVERSITY OF SOUTH-EASTERN NORWAY + KONGSBERG + \ No newline at end of file diff --git a/public/assets/schools/villanova.svg b/public/assets/schools/villanova.svg new file mode 100644 index 0000000..509d885 --- /dev/null +++ b/public/assets/schools/villanova.svg @@ -0,0 +1,8 @@ + + + + VILLANOVA + + UNIVERSITY + Veritas, Unitas, Caritas + \ No newline at end of file diff --git a/public/assets/wordmark.svg b/public/assets/wordmark.svg new file mode 100644 index 0000000..cfe9652 --- /dev/null +++ b/public/assets/wordmark.svg @@ -0,0 +1,11 @@ + + + Dave Gilligan + DAVEGILLIGAN.COM + + + + + + + \ No newline at end of file diff --git a/public/images/articles/ai-bubble/boyles-flask.png b/public/images/articles/ai-bubble/boyles-flask.png new file mode 100644 index 0000000..cf82c31 Binary files /dev/null and b/public/images/articles/ai-bubble/boyles-flask.png differ diff --git a/public/images/articles/ai-bubble/data-centre.jpg b/public/images/articles/ai-bubble/data-centre.jpg new file mode 100644 index 0000000..a3378fb Binary files /dev/null and b/public/images/articles/ai-bubble/data-centre.jpg differ diff --git a/public/images/articles/ai-bubble/eniac.jpg b/public/images/articles/ai-bubble/eniac.jpg new file mode 100644 index 0000000..cf8dcb3 Binary files /dev/null and b/public/images/articles/ai-bubble/eniac.jpg differ diff --git a/public/images/articles/ai-bubble/semper-augustus.jpg b/public/images/articles/ai-bubble/semper-augustus.jpg new file mode 100644 index 0000000..80a2f99 Binary files /dev/null and b/public/images/articles/ai-bubble/semper-augustus.jpg differ diff --git a/public/images/articles/boris-vian/boris-vian.jpg b/public/images/articles/boris-vian/boris-vian.jpg new file mode 100644 index 0000000..2210f86 Binary files /dev/null and b/public/images/articles/boris-vian/boris-vian.jpg differ diff --git a/public/images/articles/boris-vian/jarry.jpg b/public/images/articles/boris-vian/jarry.jpg new file mode 100644 index 0000000..64369a3 Binary files /dev/null and b/public/images/articles/boris-vian/jarry.jpg differ diff --git a/public/images/articles/boris-vian/le-tabou.jpg b/public/images/articles/boris-vian/le-tabou.jpg new file mode 100644 index 0000000..590a1fc Binary files /dev/null and b/public/images/articles/boris-vian/le-tabou.jpg differ diff --git a/public/images/articles/boris-vian/ubu.png b/public/images/articles/boris-vian/ubu.png new file mode 100644 index 0000000..58c4d49 Binary files /dev/null and b/public/images/articles/boris-vian/ubu.png differ diff --git a/public/images/og/ai-lab.png b/public/images/og/ai-lab.png new file mode 100644 index 0000000..126e8ed Binary files /dev/null and b/public/images/og/ai-lab.png differ diff --git a/public/images/og/business.png b/public/images/og/business.png new file mode 100644 index 0000000..341e909 Binary files /dev/null and b/public/images/og/business.png differ diff --git a/public/images/og/cv.png b/public/images/og/cv.png new file mode 100644 index 0000000..971019c Binary files /dev/null and b/public/images/og/cv.png differ diff --git a/public/images/og/education.png b/public/images/og/education.png new file mode 100644 index 0000000..0d9785f Binary files /dev/null and b/public/images/og/education.png differ diff --git a/public/images/og/family-lab.png b/public/images/og/family-lab.png new file mode 100644 index 0000000..6ff07e1 Binary files /dev/null and b/public/images/og/family-lab.png differ diff --git a/public/images/og/family.png b/public/images/og/family.png new file mode 100644 index 0000000..30f67d9 Binary files /dev/null and b/public/images/og/family.png differ diff --git a/public/images/og/fun-postings.png b/public/images/og/fun-postings.png new file mode 100644 index 0000000..24d58ad Binary files /dev/null and b/public/images/og/fun-postings.png differ diff --git a/public/images/og/home.png b/public/images/og/home.png new file mode 100644 index 0000000..9e6872f Binary files /dev/null and b/public/images/og/home.png differ diff --git a/public/images/og/jazz-music.png b/public/images/og/jazz-music.png new file mode 100644 index 0000000..52ee073 Binary files /dev/null and b/public/images/og/jazz-music.png differ diff --git a/public/images/og/languages.png b/public/images/og/languages.png new file mode 100644 index 0000000..1272d24 Binary files /dev/null and b/public/images/og/languages.png differ diff --git a/public/images/og/norway.png b/public/images/og/norway.png new file mode 100644 index 0000000..ddaee58 Binary files /dev/null and b/public/images/og/norway.png differ diff --git a/public/images/og/projects.png b/public/images/og/projects.png new file mode 100644 index 0000000..7454490 Binary files /dev/null and b/public/images/og/projects.png differ diff --git a/public/images/og/writing.png b/public/images/og/writing.png new file mode 100644 index 0000000..8107169 Binary files /dev/null and b/public/images/og/writing.png differ diff --git a/scripts/fonts/SpecialElite-Regular.ttf b/scripts/fonts/SpecialElite-Regular.ttf new file mode 100644 index 0000000..76a925d Binary files /dev/null and b/scripts/fonts/SpecialElite-Regular.ttf differ diff --git a/scripts/generate-og-images.mjs b/scripts/generate-og-images.mjs new file mode 100644 index 0000000..e10e509 --- /dev/null +++ b/scripts/generate-og-images.mjs @@ -0,0 +1,176 @@ +import { readFileSync, mkdirSync, writeFileSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; +import satori from "satori"; +import { Resvg } from "@resvg/resvg-js"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const outDir = join(__dirname, "../public/images/og"); +const fontPath = join(__dirname, "fonts/SpecialElite-Regular.ttf"); + +mkdirSync(outDir, { recursive: true }); + +const fontData = readFileSync(fontPath); + +const W = 1200; +const H = 630; +const NAVY = "#0a0f1e"; +const GOLD = "#c9a84c"; +const CREAM = "#f6f0e1"; +const MUTED = "#7a8aaa"; + +const sections = [ + { slug: "home", title: "Dave Gilligan", strap: "Private AI. Jazz rooms. Civic weather. Pataphysical field notes." }, + { slug: "business", title: "Business", strap: "Operator studies for adults tired of consultant vapor." }, + { slug: "education", title: "Education", strap: "Degrees as cities, schools as chapters, study as migration." }, + { slug: "writing", title: "Writing", strap: "Boris Vian, jazz syntax, and the science of glorious exception." }, + { slug: "jazz-music", title: "Jazz and Music", strap: "Listening notes, Kongsberg nights, Caveau memories." }, + { slug: "ai-lab", title: "AI Lab", strap: "Forward-looking, grounded, open-source friendly." }, + { slug: "norway", title: "Norway", strap: "Field reports from Norwegian civic and family life." }, + { slug: "cv", title: "CV", strap: "Professional, legible, confident." }, + { slug: "family", title: "Family", strap: "The soft archive, still edited like it matters." }, + { slug: "fun-postings",title: "Fun Postings", strap: "Useful nonsense, neatly set." }, + { slug: "languages", title: "Languages", strap: "Polyglot, sly, welcoming." }, + { slug: "projects", title: "Projects", strap: "Builder energy, clean receipts." }, + { slug: "family-lab", title: "Family Lab", strap: "Private archive, atlas, memory room." }, +]; + +function buildNode({ title, strap }) { + return { + type: "div", + props: { + style: { + display: "flex", + flexDirection: "column", + width: W, + height: H, + background: NAVY, + position: "relative", + }, + children: [ + // top gold rule + { + type: "div", + props: { + style: { + position: "absolute", + top: 0, + left: 0, + width: W, + height: 8, + background: GOLD, + }, + }, + }, + // domain label + { + type: "div", + props: { + style: { + position: "absolute", + top: 28, + left: 64, + fontFamily: "SpecialElite", + fontSize: 16, + color: GOLD, + letterSpacing: "0.18em", + textTransform: "uppercase", + }, + children: "DAVEGILLIGAN.COM", + }, + }, + // left accent line + { + type: "div", + props: { + style: { + position: "absolute", + top: 64, + left: 64, + width: 3, + height: H - 128, + background: GOLD, + opacity: 0.35, + }, + }, + }, + // section title + { + type: "div", + props: { + style: { + position: "absolute", + top: 200, + left: 96, + right: 64, + fontFamily: "SpecialElite", + fontSize: title.length > 12 ? 64 : 80, + color: CREAM, + lineHeight: 1.1, + }, + children: title, + }, + }, + // strap line + { + type: "div", + props: { + style: { + position: "absolute", + top: title.length > 12 ? 330 : 360, + left: 96, + right: 64, + fontFamily: "SpecialElite", + fontSize: 22, + color: MUTED, + lineHeight: 1.5, + }, + children: strap, + }, + }, + // bottom label + { + type: "div", + props: { + style: { + position: "absolute", + bottom: 36, + left: 96, + fontFamily: "SpecialElite", + fontSize: 14, + color: GOLD, + letterSpacing: "0.12em", + textTransform: "uppercase", + }, + children: "Blue Note Logic · Kongsberg", + }, + }, + ], + }, + }; +} + +for (const section of sections) { + const svg = await satori(buildNode(section), { + width: W, + height: H, + fonts: [ + { + name: "SpecialElite", + data: fontData, + weight: 400, + style: "normal", + }, + ], + }); + + const resvg = new Resvg(svg, { + fitTo: { mode: "width", value: W }, + }); + const png = resvg.render().asPng(); + const outPath = join(outDir, `${section.slug}.png`); + writeFileSync(outPath, png); + console.log(`✓ ${section.slug}.png (${(png.length / 1024).toFixed(0)} KB)`); +} + +console.log(`\nAll ${sections.length} OG images written to public/images/og/`); diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index acbb7a8..e2efca5 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -11,12 +11,20 @@ interface Props { title?: string; description?: string; lang?: string; + ogImage?: string; + ogType?: "website" | "article"; + ogArticlePublishedTime?: string; + ogArticleSection?: string; } const { title = "Dave Gilligan | Blue Note Logic", description = "A literary, jazzy, technically serious online magazine for writing, consulting, education, languages, family, and AI.", lang = "en", + ogImage, + ogType, + ogArticlePublishedTime, + ogArticleSection, } = Astro.props; const now = new Date(); @@ -39,6 +47,10 @@ const issueDate = { }; const pathname = Astro.url.pathname.replace(/\/+$/, "") || "/"; +const siteUrl = "https://davegilligan.com"; +const canonicalUrl = siteUrl + (pathname === "/" ? "" : pathname); +const ogImageAbsolute = siteUrl + (ogImage ?? "/images/og/home.png"); +const resolvedOgType = ogType ?? "website"; const activeSlug = pathname === "/" ? "home" : pathname.split("/").filter(Boolean)[0]; const primarySlugs = ["business", "education", "writing", "jazz-music", "ai-lab", "norway"]; const primaryNav = launchSections.filter((section) => primarySlugs.includes(section.slug)); @@ -61,6 +73,30 @@ const chromeCopy = getChromeCopy({ activeSlug, issueDate, articleKey }); + + + + + + + + + + + + + + {resolvedOgType === "article" && ogArticlePublishedTime && ( + + )} + {resolvedOgType === "article" && ogArticleSection && ( + + )} + + + + + diff --git a/src/pages/[slug].astro b/src/pages/[slug].astro index f0d8b28..d56cbf1 100644 --- a/src/pages/[slug].astro +++ b/src/pages/[slug].astro @@ -27,6 +27,7 @@ const relatedSections = launchSections.filter(
diff --git a/src/pages/ai-lab.astro b/src/pages/ai-lab.astro index 7ac7142..9622482 100644 --- a/src/pages/ai-lab.astro +++ b/src/pages/ai-lab.astro @@ -18,6 +18,7 @@ const [gilliganTech, blueNoteLogic] = ventureDesk;
diff --git a/src/pages/articles/ai-bubble-2026.astro b/src/pages/articles/ai-bubble-2026.astro index 125ea54..b04de77 100644 --- a/src/pages/articles/ai-bubble-2026.astro +++ b/src/pages/articles/ai-bubble-2026.astro @@ -34,6 +34,10 @@ const pageCopy = {
diff --git a/src/pages/articles/boris-vian-2026.astro b/src/pages/articles/boris-vian-2026.astro index 3b4ada3..fa3e300 100644 --- a/src/pages/articles/boris-vian-2026.astro +++ b/src/pages/articles/boris-vian-2026.astro @@ -34,6 +34,10 @@ const pageCopy = {
diff --git a/src/pages/articles/kongsberg-jazz-2026.astro b/src/pages/articles/kongsberg-jazz-2026.astro index d146b0b..9d6b138 100644 --- a/src/pages/articles/kongsberg-jazz-2026.astro +++ b/src/pages/articles/kongsberg-jazz-2026.astro @@ -42,6 +42,10 @@ const pageCopy = {
diff --git a/src/pages/articles/norway-april-2026.astro b/src/pages/articles/norway-april-2026.astro index 82253a3..bd6948e 100644 --- a/src/pages/articles/norway-april-2026.astro +++ b/src/pages/articles/norway-april-2026.astro @@ -13,6 +13,10 @@ import LocaleCopy from "../../components/LocaleCopy.astro";
diff --git a/src/pages/articles/trivia-and-tunes-april-2026.astro b/src/pages/articles/trivia-and-tunes-april-2026.astro index 71b7d87..142513b 100644 --- a/src/pages/articles/trivia-and-tunes-april-2026.astro +++ b/src/pages/articles/trivia-and-tunes-april-2026.astro @@ -12,6 +12,10 @@ import LocaleCopy from "../../components/LocaleCopy.astro";
diff --git a/src/pages/business.astro b/src/pages/business.astro index fb53ba3..ba1bd5f 100644 --- a/src/pages/business.astro +++ b/src/pages/business.astro @@ -23,6 +23,7 @@ const copy = {
diff --git a/src/pages/cookies.astro b/src/pages/cookies.astro index 0449382..11e8175 100644 --- a/src/pages/cookies.astro +++ b/src/pages/cookies.astro @@ -6,6 +6,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
diff --git a/src/pages/cv.astro b/src/pages/cv.astro index 4d95468..4c53ef3 100644 --- a/src/pages/cv.astro +++ b/src/pages/cv.astro @@ -18,6 +18,7 @@ const educationSlice = schoolDossiers.slice(0, 5);
diff --git a/src/pages/education.astro b/src/pages/education.astro index 9c6057b..d06c125 100644 --- a/src/pages/education.astro +++ b/src/pages/education.astro @@ -45,6 +45,7 @@ const copy = {
diff --git a/src/pages/family-lab.astro b/src/pages/family-lab.astro index 846af74..3da9c7d 100644 --- a/src/pages/family-lab.astro +++ b/src/pages/family-lab.astro @@ -33,6 +33,7 @@ const namedPhotos = photos.filter((photo) => photo.namedFile).slice(0, 12);
diff --git a/src/pages/index.astro b/src/pages/index.astro index 61a84ae..a89471b 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -28,6 +28,7 @@ const projects = launchSections.find((section) => section.slug === "projects");
diff --git a/src/pages/jazz-music.astro b/src/pages/jazz-music.astro index b8da3bf..70e3fd7 100644 --- a/src/pages/jazz-music.astro +++ b/src/pages/jazz-music.astro @@ -7,6 +7,7 @@ import LocaleCopy from "../components/LocaleCopy.astro";
diff --git a/src/pages/norway.astro b/src/pages/norway.astro index 6a680c7..10eac16 100644 --- a/src/pages/norway.astro +++ b/src/pages/norway.astro @@ -14,6 +14,7 @@ import LocaleCopy from "../components/LocaleCopy.astro";
diff --git a/src/pages/privacy.astro b/src/pages/privacy.astro index 3144cd9..ef97e87 100644 --- a/src/pages/privacy.astro +++ b/src/pages/privacy.astro @@ -6,6 +6,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
diff --git a/src/pages/projects.astro b/src/pages/projects.astro index c23db8a..f4be078 100644 --- a/src/pages/projects.astro +++ b/src/pages/projects.astro @@ -15,6 +15,7 @@ import LocaleCopy from "../components/LocaleCopy.astro";
diff --git a/src/pages/writing.astro b/src/pages/writing.astro index b35792e..594ef7b 100644 --- a/src/pages/writing.astro +++ b/src/pages/writing.astro @@ -24,6 +24,7 @@ const copy = {