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 @@
+
\ 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 @@
+
\ 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 @@
+
\ 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 @@
+
\ 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 @@
+
\ 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 @@
+
\ 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 @@
+
\ 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 = {