Add complete Open Graph metadata layer across all 20 pages
- Add og:title, og:description, og:image, og:url, og:type, og:site_name to BaseLayout - Add og:locale (en_GB) + og:locale:alternate (fr_FR, nb_NO) multilingual signals - Add article:published_time, article:section, article:author on all 5 article pages - Add Twitter/X summary_large_image card and canonical link on every page - Generate 13 Jazz Noir branded 1200x630 PNG OG images (satori + resvg-js) - Add scripts/generate-og-images.mjs + Special Elite font for future regeneration - Add public/images/articles/ and public/assets/ which were previously untracked Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@@ -5,5 +5,6 @@ import react from '@astrojs/react';
|
|||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
site: 'https://davegilligan.com',
|
||||||
integrations: [react()]
|
integrations: [react()]
|
||||||
});
|
});
|
||||||
@@ -15,6 +15,10 @@
|
|||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4"
|
"react-dom": "^19.2.4"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
|
"satori": "^0.26.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.12.0"
|
"node": ">=22.12.0"
|
||||||
}
|
}
|
||||||
@@ -1382,6 +1386,234 @@
|
|||||||
"integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==",
|
"integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-rc.3",
|
"version": "1.0.0-rc.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
|
||||||
@@ -1835,6 +2067,23 @@
|
|||||||
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
|
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -2121,6 +2370,16 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"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": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.10.15",
|
"version": "2.10.15",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.15.tgz",
|
"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": "^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": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001785",
|
"version": "1.0.30001785",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001785.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001785.tgz",
|
||||||
@@ -2271,6 +2540,13 @@
|
|||||||
"node": ">=6"
|
"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": {
|
"node_modules/comma-separated-tokens": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
||||||
@@ -2333,6 +2609,40 @@
|
|||||||
"uncrypto": "^0.1.3"
|
"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": {
|
"node_modules/css-select": {
|
||||||
"version": "5.2.2",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||||
@@ -2349,6 +2659,18 @@
|
|||||||
"url": "https://github.com/sponsors/fb55"
|
"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": {
|
"node_modules/css-tree": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
||||||
@@ -2590,6 +2912,16 @@
|
|||||||
"integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==",
|
"integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==",
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/entities": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||||
@@ -2658,6 +2990,13 @@
|
|||||||
"node": ">=6"
|
"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": {
|
"node_modules/escape-string-regexp": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
"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": {
|
"node_modules/flattie": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz",
|
||||||
@@ -2982,6 +3328,19 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"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": {
|
"node_modules/html-escaper": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
||||||
@@ -3115,6 +3474,17 @@
|
|||||||
"node": ">=6"
|
"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": {
|
"node_modules/longest-streak": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
|
||||||
@@ -4145,6 +4515,24 @@
|
|||||||
"integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
|
"integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/parse-latin": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz",
|
||||||
@@ -4227,6 +4615,13 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/prismjs": {
|
"node_modules/prismjs": {
|
||||||
"version": "1.30.0",
|
"version": "1.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
||||||
@@ -4566,6 +4961,29 @@
|
|||||||
"fsevents": "~2.3.2"
|
"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": {
|
"node_modules/sax": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
|
||||||
@@ -4694,6 +5112,13 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"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": {
|
"node_modules/stringify-entities": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
|
||||||
@@ -4838,6 +5263,17 @@
|
|||||||
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==",
|
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/unicode-trie": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pako": "^0.2.5",
|
||||||
|
"tiny-inflate": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unified": {
|
"node_modules/unified": {
|
||||||
"version": "11.0.5",
|
"version": "11.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
||||||
@@ -5304,6 +5740,13 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/zod": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
"prebuild": "node scripts/sync-family-lab.mjs",
|
"prebuild": "node scripts/sync-family-lab.mjs",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro"
|
"astro": "astro",
|
||||||
|
"og": "node scripts/generate-og-images.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/react": "^5.0.2",
|
"@astrojs/react": "^5.0.2",
|
||||||
@@ -20,5 +21,9 @@
|
|||||||
"astro": "^6.1.3",
|
"astro": "^6.1.3",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4"
|
"react-dom": "^19.2.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
|
"satori": "^0.26.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<svg width="1200" height="760" viewBox="0 0 1200 760" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="1200" height="760" fill="#F7F0E0"></rect>
|
||||||
|
<rect x="44" y="44" width="1112" height="672" rx="28" fill="#FFF9EF" stroke="#201914" stroke-width="2"></rect>
|
||||||
|
<rect x="84" y="92" width="312" height="230" rx="24" fill="#151A21"></rect>
|
||||||
|
<rect x="424" y="92" width="312" height="230" rx="24" fill="#F0E3C9" stroke="#201914" stroke-width="2"></rect>
|
||||||
|
<rect x="764" y="92" width="312" height="230" rx="24" fill="#D8E5DF" stroke="#201914" stroke-width="2"></rect>
|
||||||
|
<rect x="116" y="126" width="136" height="14" rx="7" fill="#C28A47"></rect>
|
||||||
|
<rect x="116" y="164" width="236" height="10" rx="5" fill="#46505A"></rect>
|
||||||
|
<rect x="116" y="188" width="210" height="10" rx="5" fill="#46505A"></rect>
|
||||||
|
<rect x="116" y="212" width="228" height="10" rx="5" fill="#46505A"></rect>
|
||||||
|
<rect x="456" y="126" width="116" height="14" rx="7" fill="#1A5960"></rect>
|
||||||
|
<rect x="456" y="164" width="238" height="10" rx="5" fill="#B3A68B"></rect>
|
||||||
|
<rect x="456" y="188" width="214" height="10" rx="5" fill="#B3A68B"></rect>
|
||||||
|
<rect x="456" y="212" width="198" height="10" rx="5" fill="#B3A68B"></rect>
|
||||||
|
<rect x="796" y="126" width="134" height="14" rx="7" fill="#5D2F2B"></rect>
|
||||||
|
<rect x="796" y="164" width="236" height="10" rx="5" fill="#A7BBB5"></rect>
|
||||||
|
<rect x="796" y="188" width="214" height="10" rx="5" fill="#A7BBB5"></rect>
|
||||||
|
<rect x="796" y="212" width="198" height="10" rx="5" fill="#A7BBB5"></rect>
|
||||||
|
<path d="M396 206H424" stroke="#201914" stroke-width="4" stroke-linecap="round"></path>
|
||||||
|
<path d="M736 206H764" stroke="#201914" stroke-width="4" stroke-linecap="round"></path>
|
||||||
|
<path d="M412 192L424 206L412 220" stroke="#201914" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
<path d="M752 192L764 206L752 220" stroke="#201914" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
<rect x="84" y="384" width="992" height="276" rx="24" fill="#161B22"></rect>
|
||||||
|
<rect x="118" y="420" width="184" height="18" rx="9" fill="#C28A47"></rect>
|
||||||
|
<rect x="118" y="468" width="384" height="10" rx="5" fill="#46505A"></rect>
|
||||||
|
<rect x="118" y="492" width="338" height="10" rx="5" fill="#46505A"></rect>
|
||||||
|
<rect x="118" y="516" width="404" height="10" rx="5" fill="#46505A"></rect>
|
||||||
|
<rect x="118" y="552" width="220" height="76" rx="16" fill="#1A5960"></rect>
|
||||||
|
<rect x="362" y="552" width="220" height="76" rx="16" fill="#5D2F2B"></rect>
|
||||||
|
<rect x="606" y="552" width="220" height="76" rx="16" fill="#2B323C"></rect>
|
||||||
|
<rect x="850" y="552" width="192" height="76" rx="16" fill="#F0E3C9"></rect>
|
||||||
|
<rect x="878" y="582" width="136" height="12" rx="6" fill="#B39B77"></rect>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,35 @@
|
|||||||
|
<svg width="1200" height="800" viewBox="0 0 1200 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="1200" height="800" fill="#FBF4E6"></rect>
|
||||||
|
<rect x="40" y="40" width="1120" height="720" rx="28" fill="#FFF9EF" stroke="#201914" stroke-width="2"></rect>
|
||||||
|
<rect x="86" y="92" width="192" height="130" rx="22" fill="#F0E3C9" stroke="#201914" stroke-width="2"></rect>
|
||||||
|
<rect x="324" y="92" width="192" height="130" rx="22" fill="#D8E5DF" stroke="#201914" stroke-width="2"></rect>
|
||||||
|
<rect x="562" y="92" width="192" height="130" rx="22" fill="#F3D9D2" stroke="#201914" stroke-width="2"></rect>
|
||||||
|
<rect x="800" y="92" width="312" height="130" rx="22" fill="#161B22" stroke="#201914" stroke-width="2"></rect>
|
||||||
|
<rect x="112" y="122" width="78" height="12" rx="6" fill="#1A5960"></rect>
|
||||||
|
<rect x="112" y="154" width="140" height="10" rx="5" fill="#B8AA8E"></rect>
|
||||||
|
<rect x="112" y="176" width="120" height="10" rx="5" fill="#B8AA8E"></rect>
|
||||||
|
<rect x="350" y="122" width="82" height="12" rx="6" fill="#5D2F2B"></rect>
|
||||||
|
<rect x="350" y="154" width="130" height="10" rx="5" fill="#9EAFAB"></rect>
|
||||||
|
<rect x="350" y="176" width="112" height="10" rx="5" fill="#9EAFAB"></rect>
|
||||||
|
<rect x="588" y="122" width="96" height="12" rx="6" fill="#C28A47"></rect>
|
||||||
|
<rect x="588" y="154" width="132" height="10" rx="5" fill="#B8A19D"></rect>
|
||||||
|
<rect x="588" y="176" width="112" height="10" rx="5" fill="#B8A19D"></rect>
|
||||||
|
<rect x="828" y="122" width="124" height="12" rx="6" fill="#C28A47"></rect>
|
||||||
|
<rect x="828" y="154" width="246" height="10" rx="5" fill="#46505A"></rect>
|
||||||
|
<rect x="828" y="176" width="214" height="10" rx="5" fill="#46505A"></rect>
|
||||||
|
<circle cx="182" cy="420" r="82" fill="#1A5960"></circle>
|
||||||
|
<circle cx="418" cy="420" r="82" fill="#C28A47"></circle>
|
||||||
|
<circle cx="654" cy="420" r="82" fill="#5D2F2B"></circle>
|
||||||
|
<circle cx="890" cy="420" r="82" fill="#11151C"></circle>
|
||||||
|
<circle cx="1018" cy="420" r="54" fill="#E7DBC1" stroke="#201914" stroke-width="2"></circle>
|
||||||
|
<path d="M264 420H336" stroke="#201914" stroke-width="4" stroke-linecap="round"></path>
|
||||||
|
<path d="M500 420H572" stroke="#201914" stroke-width="4" stroke-linecap="round"></path>
|
||||||
|
<path d="M736 420H808" stroke="#201914" stroke-width="4" stroke-linecap="round"></path>
|
||||||
|
<path d="M972 420H964" stroke="#201914" stroke-width="4" stroke-linecap="round"></path>
|
||||||
|
<rect x="110" y="607" width="980" height="92" rx="22" fill="#201914"></rect>
|
||||||
|
<rect x="144" y="640" width="168" height="12" rx="6" fill="#C28A47"></rect>
|
||||||
|
<rect x="344" y="640" width="168" height="12" rx="6" fill="#F7F0E0"></rect>
|
||||||
|
<rect x="544" y="640" width="168" height="12" rx="6" fill="#1A5960"></rect>
|
||||||
|
<rect x="744" y="640" width="168" height="12" rx="6" fill="#F7F0E0"></rect>
|
||||||
|
<rect x="944" y="640" width="112" height="12" rx="6" fill="#C28A47"></rect>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,32 @@
|
|||||||
|
<svg width="1200" height="900" viewBox="0 0 1200 900" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="1200" height="900" fill="#F7F0E0"></rect>
|
||||||
|
<rect x="36" y="36" width="1128" height="828" rx="28" fill="#FFF9EF" stroke="#201914" stroke-width="2"></rect>
|
||||||
|
<rect x="72" y="80" width="540" height="420" rx="22" fill="#11151C"></rect>
|
||||||
|
<rect x="96" y="104" width="492" height="38" rx="10" fill="#202A34"></rect>
|
||||||
|
<circle cx="118" cy="123" r="6" fill="#D86D56"></circle>
|
||||||
|
<circle cx="140" cy="123" r="6" fill="#D8B156"></circle>
|
||||||
|
<circle cx="162" cy="123" r="6" fill="#4E8F8C"></circle>
|
||||||
|
<rect x="96" y="170" width="186" height="108" rx="18" fill="#1B2F3A"></rect>
|
||||||
|
<rect x="302" y="170" width="286" height="108" rx="18" fill="#5D2F2B"></rect>
|
||||||
|
<rect x="96" y="298" width="492" height="178" rx="18" fill="#151A21"></rect>
|
||||||
|
<path d="M120 400C178 336 223 380 281 329C339 278 393 326 451 274C491 238 530 240 564 262V452H120V400Z" fill="#5A7E76"></path>
|
||||||
|
<path d="M120 430C168 389 223 413 272 372C321 331 380 370 434 327C486 285 526 292 564 316V452H120V430Z" fill="#C28A47"></path>
|
||||||
|
<circle cx="736" cy="178" r="102" fill="#EFE2C5" stroke="#201914" stroke-width="2"></circle>
|
||||||
|
<path d="M694 217C694 181.654 722.654 153 758 153V153C793.346 153 822 181.654 822 217V288H694V217Z" fill="#1A5960"></path>
|
||||||
|
<rect x="676" y="288" width="164" height="58" rx="16" fill="#201914"></rect>
|
||||||
|
<rect x="706" y="314" width="104" height="6" rx="3" fill="#F7F0E0"></rect>
|
||||||
|
<rect x="676" y="404" width="418" height="154" rx="24" fill="#F2E3C7" stroke="#201914" stroke-width="2"></rect>
|
||||||
|
<rect x="706" y="434" width="124" height="14" rx="7" fill="#1A5960"></rect>
|
||||||
|
<rect x="706" y="470" width="338" height="14" rx="7" fill="#D3C2A1"></rect>
|
||||||
|
<rect x="706" y="502" width="296" height="14" rx="7" fill="#D3C2A1"></rect>
|
||||||
|
<rect x="706" y="534" width="198" height="14" rx="7" fill="#D3C2A1"></rect>
|
||||||
|
<rect x="676" y="598" width="418" height="190" rx="24" fill="#151A21"></rect>
|
||||||
|
<rect x="706" y="628" width="160" height="14" rx="7" fill="#C28A47"></rect>
|
||||||
|
<rect x="706" y="662" width="114" height="84" rx="14" fill="#1A5960"></rect>
|
||||||
|
<rect x="838" y="662" width="114" height="84" rx="14" fill="#5D2F2B"></rect>
|
||||||
|
<rect x="970" y="662" width="94" height="84" rx="14" fill="#2C333B"></rect>
|
||||||
|
<path d="M748 116L780 149" stroke="#201914" stroke-width="3" stroke-linecap="round"></path>
|
||||||
|
<path d="M780 149L805 125" stroke="#201914" stroke-width="3" stroke-linecap="round"></path>
|
||||||
|
<path d="M916 122L958 122" stroke="#201914" stroke-width="3" stroke-linecap="round"></path>
|
||||||
|
<path d="M937 101L937 143" stroke="#201914" stroke-width="3" stroke-linecap="round"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,27 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
|
||||||
|
<defs>
|
||||||
|
<path id="rim-top" d="M 100 100 m -82 0 a 82 82 0 0 1 164 0"></path>
|
||||||
|
<path id="rim-bot" d="M 100 100 m 82 0 a 82 82 0 0 1 -164 0"></path>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<circle cx="100" cy="100" r="92" stroke="#8f2218" stroke-width="1.4"></circle>
|
||||||
|
<circle cx="100" cy="100" r="84" stroke="#8f2218" stroke-width="0.6"></circle>
|
||||||
|
|
||||||
|
<g fill="#8f2218" font-family="Special Elite, Courier, monospace" font-size="11" letter-spacing="3">
|
||||||
|
<text><textPath href="#rim-top" startOffset="50%" text-anchor="middle">DAVE · GILLIGAN</textPath></text>
|
||||||
|
<text><textPath href="#rim-bot" startOffset="50%" text-anchor="middle">· KONGSBERG · EDITION ·</textPath></text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<path d="M 100 70
 C 120 70 130 88 120 102
 C 112 113 96 113 91 102
 C 87 92 96 84 105 86
 C 113 89 113 99 106 102
 C 100 105 95 100 97 95" stroke="#8f2218" stroke-width="1.6" stroke-linecap="round"></path>
|
||||||
|
|
||||||
|
<g font-family="Instrument Serif, serif" fill="#0e0c08">
|
||||||
|
<text x="100" y="142" font-size="32" text-anchor="middle" letter-spacing="-1">DG</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g fill="#8f2218">
|
||||||
|
<circle cx="100" cy="20" r="1.8"></circle>
|
||||||
|
<circle cx="180" cy="100" r="1.8"></circle>
|
||||||
|
<circle cx="100" cy="180" r="1.8"></circle>
|
||||||
|
<circle cx="20" cy="100" r="1.8"></circle>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,38 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 64" fill="none">
|
||||||
|
|
||||||
|
<g stroke="#0e0c08" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" fill="none">
|
||||||
|
|
||||||
|
<g transform="translate(32, 32)">
|
||||||
|
<circle cx="0" cy="-8" r="1.6" fill="#0e0c08"></circle>
|
||||||
|
<circle cx="-7" cy="5" r="1.6" fill="#0e0c08"></circle>
|
||||||
|
<circle cx="7" cy="5" r="1.6" fill="#0e0c08"></circle>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(96, 32)">
|
||||||
|
<path d="M 0 -10 c -6 0 -10 4 -10 10 c 0 -6 4 -10 10 -10 c 6 0 10 4 10 10 c 0 -6 -4 -10 -10 -10 z"></path>
|
||||||
|
<path d="M -10 0 c 0 6 4 10 10 10 c -6 0 -10 -4 -10 -10 c 0 6 4 10 10 10 c 6 0 10 -4 10 -10"></path>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(160, 32)" stroke-width="1.8">
|
||||||
|
<path d="M 4 -8 c -6 0 -8 4 -8 6 c 0 4 4 4 4 8 c 0 4 -4 4 -4 4 c 0 0 4 2 8 2"></path>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(224, 32)">
|
||||||
|
<path d="M -10 -4 l 14 -2 l 8 6 l -8 6 l -14 -2 z"></path>
|
||||||
|
<circle cx="-10" cy="0" r="2" fill="#0e0c08"></circle>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(288, 32)">
|
||||||
|
<path d="M -2 -10 c -5 0 -8 3 -8 6 c 0 3 3 6 8 6"></path>
|
||||||
|
<path d="M 0 -10 v 22 M 5 -10 v 22"></path>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(352, 32)">
|
||||||
|
<path d="M 0 -10 l 3 7 l 7 3 l -7 3 l -3 7 l -3 -7 l -7 -3 l 7 -3 z" fill="#0e0c08"></path>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(416, 32)" stroke="#8f2218">
|
||||||
|
<path d="M 0 -8 c 7 0 11 5 9 11 c -2 5 -8 6 -11 3 c -3 -3 -1 -7 2 -7 c 2 0 3 2 2 4"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" stroke="#8f2218" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
|
||||||
|
<path d="M 32 18
 C 46 18 54 30 50 42
 C 47 51 36 54 28 49
 C 21 44 22 35 28 31
 C 34 28 41 32 41 38
 C 41 43 37 46 33 45
 C 30 44 29 41 31 39"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 403 B |
@@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 280">
|
||||||
|
<rect width="480" height="280" rx="28" fill="#68131a"></rect>
|
||||||
|
<rect x="24" y="24" width="432" height="232" rx="22" fill="none" stroke="#d8bb74" stroke-width="2"></rect>
|
||||||
|
<text x="240" y="94" fill="#ffffff" font-family="Georgia, serif" font-size="46" font-weight="700" text-anchor="middle">ESCP</text>
|
||||||
|
<text x="240" y="124" fill="#d8bb74" font-family="Arial, sans-serif" font-size="18" letter-spacing="6" text-anchor="middle">AND</text>
|
||||||
|
<text x="240" y="170" fill="#ffffff" font-family="Georgia, serif" font-size="36" font-weight="700" text-anchor="middle">DARLA MOORE</text>
|
||||||
|
<line x1="116" y1="188" x2="364" y2="188" stroke="#d8bb74" stroke-width="1.5"></line>
|
||||||
|
<text x="240" y="214" fill="#d8bb74" font-family="Arial, sans-serif" font-size="15" letter-spacing="4" text-anchor="middle">DUAL DEGREE PROGRAM</text>
|
||||||
|
<text x="240" y="236" fill="#d2a1a1" font-family="Arial, sans-serif" font-size="13" letter-spacing="3.5" text-anchor="middle">PARIS AND COLUMBIA</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 280">
|
||||||
|
<rect width="480" height="280" rx="28" fill="#1c3453"></rect>
|
||||||
|
<rect x="24" y="24" width="432" height="232" rx="22" fill="none" stroke="#6dc1d0" stroke-width="2"></rect>
|
||||||
|
<text x="240" y="94" fill="#81d6e1" font-family="Georgia, serif" font-size="24" letter-spacing="5" text-anchor="middle">EUROPEAN</text>
|
||||||
|
<text x="240" y="144" fill="#ffffff" font-family="Georgia, serif" font-size="44" font-weight="700" text-anchor="middle">UNIVERSITY</text>
|
||||||
|
<line x1="126" y1="164" x2="354" y2="164" stroke="#6dc1d0" stroke-width="1.5"></line>
|
||||||
|
<text x="240" y="198" fill="#81d6e1" font-family="Arial, sans-serif" font-size="17" letter-spacing="4" text-anchor="middle">INSTITUT COOREMAN</text>
|
||||||
|
<text x="240" y="224" fill="#93b7d3" font-family="Arial, sans-serif" font-size="14" letter-spacing="4" text-anchor="middle">BRUSSELS</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 901 B |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 280">
|
||||||
|
<rect width="480" height="280" rx="28" fill="#7a1718"></rect>
|
||||||
|
<rect x="24" y="24" width="432" height="232" rx="22" fill="none" stroke="#d8bb74" stroke-width="2"></rect>
|
||||||
|
<text x="240" y="96" fill="#f1d08d" font-family="Georgia, serif" font-size="28" letter-spacing="5" text-anchor="middle">UNIWERSYTET</text>
|
||||||
|
<text x="240" y="146" fill="#ffffff" font-family="Georgia, serif" font-size="42" font-weight="700" text-anchor="middle">JAGIELLONIAN</text>
|
||||||
|
<line x1="122" y1="164" x2="358" y2="164" stroke="#d8bb74" stroke-width="1.5"></line>
|
||||||
|
<text x="240" y="198" fill="#f1d08d" font-family="Arial, sans-serif" font-size="17" letter-spacing="5" text-anchor="middle">EST. 1364</text>
|
||||||
|
<text x="240" y="224" fill="#dcb0a8" font-family="Arial, sans-serif" font-size="14" letter-spacing="4" text-anchor="middle">KRAKOW</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 896 B |
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 280">
|
||||||
|
<rect width="480" height="280" rx="28" fill="#08284b"></rect>
|
||||||
|
<rect x="24" y="24" width="432" height="232" rx="22" fill="none" stroke="#c8a85b" stroke-width="2"></rect>
|
||||||
|
<text x="240" y="118" fill="#f7e7bf" font-family="Georgia, serif" font-size="72" font-weight="700" text-anchor="middle">USN</text>
|
||||||
|
<line x1="132" y1="138" x2="348" y2="138" stroke="#c8a85b" stroke-width="2"></line>
|
||||||
|
<text x="240" y="174" fill="#ffffff" font-family="Arial, sans-serif" font-size="18" letter-spacing="2.5" text-anchor="middle">UNIVERSITY OF SOUTH-EASTERN NORWAY</text>
|
||||||
|
<text x="240" y="208" fill="#9eb4d8" font-family="Arial, sans-serif" font-size="15" letter-spacing="5" text-anchor="middle">KONGSBERG</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 774 B |
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 280">
|
||||||
|
<rect width="480" height="280" rx="28" fill="#123c65"></rect>
|
||||||
|
<rect x="24" y="24" width="432" height="232" rx="22" fill="none" stroke="#ffffff" stroke-width="2"></rect>
|
||||||
|
<text x="240" y="114" fill="#ffffff" font-family="Georgia, serif" font-size="52" font-weight="700" text-anchor="middle">VILLANOVA</text>
|
||||||
|
<line x1="102" y1="136" x2="378" y2="136" stroke="#93c1dd" stroke-width="2"></line>
|
||||||
|
<text x="240" y="176" fill="#93c1dd" font-family="Arial, sans-serif" font-size="21" letter-spacing="6" text-anchor="middle">UNIVERSITY</text>
|
||||||
|
<text x="240" y="212" fill="#c1dded" font-family="Georgia, serif" font-size="16" font-style="italic" text-anchor="middle">Veritas, Unitas, Caritas</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 767 B |
@@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 120" fill="none">
|
||||||
|
|
||||||
|
<text x="0" y="74" font-family="Instrument Serif, serif" font-size="84" fill="#120f0a" letter-spacing="-3">Dave Gilligan</text>
|
||||||
|
<text x="2" y="100" font-family="Special Elite, monospace" font-size="13" fill="#1f5d5f" letter-spacing="5">DAVEGILLIGAN.COM</text>
|
||||||
|
|
||||||
|
<g transform="translate(456, 60)" stroke="#8f2218" stroke-width="1.4" fill="none">
|
||||||
|
<path d="M 0 0 c -6 -6 -6 -14 0 -20 c 6 6 6 14 0 20 z"></path>
|
||||||
|
<path d="M -10 -10 c 6 -6 14 -6 20 0 c -6 6 -14 6 -20 0 z"></path>
|
||||||
|
<circle cx="0" cy="-10" r="2.5" fill="#8f2218"></circle>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 642 B |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 502 KiB |
|
After Width: | Height: | Size: 284 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 28 KiB |
@@ -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/`);
|
||||||
@@ -11,12 +11,20 @@ interface Props {
|
|||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
lang?: string;
|
lang?: string;
|
||||||
|
ogImage?: string;
|
||||||
|
ogType?: "website" | "article";
|
||||||
|
ogArticlePublishedTime?: string;
|
||||||
|
ogArticleSection?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = "Dave Gilligan | Blue Note Logic",
|
title = "Dave Gilligan | Blue Note Logic",
|
||||||
description = "A literary, jazzy, technically serious online magazine for writing, consulting, education, languages, family, and AI.",
|
description = "A literary, jazzy, technically serious online magazine for writing, consulting, education, languages, family, and AI.",
|
||||||
lang = "en",
|
lang = "en",
|
||||||
|
ogImage,
|
||||||
|
ogType,
|
||||||
|
ogArticlePublishedTime,
|
||||||
|
ogArticleSection,
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -39,6 +47,10 @@ const issueDate = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const pathname = Astro.url.pathname.replace(/\/+$/, "") || "/";
|
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 activeSlug = pathname === "/" ? "home" : pathname.split("/").filter(Boolean)[0];
|
||||||
const primarySlugs = ["business", "education", "writing", "jazz-music", "ai-lab", "norway"];
|
const primarySlugs = ["business", "education", "writing", "jazz-music", "ai-lab", "norway"];
|
||||||
const primaryNav = launchSections.filter((section) => primarySlugs.includes(section.slug));
|
const primaryNav = launchSections.filter((section) => primarySlugs.includes(section.slug));
|
||||||
@@ -61,6 +73,30 @@ const chromeCopy = getChromeCopy({ activeSlug, issueDate, articleKey });
|
|||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
<meta name="theme-color" content="#f6f0e1" />
|
<meta name="theme-color" content="#f6f0e1" />
|
||||||
|
<link rel="canonical" href={canonicalUrl} />
|
||||||
|
<meta property="og:site_name" content="Dave Gilligan" />
|
||||||
|
<meta property="og:title" content={title} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:url" content={canonicalUrl} />
|
||||||
|
<meta property="og:type" content={resolvedOgType} />
|
||||||
|
<meta property="og:image" content={ogImageAbsolute} />
|
||||||
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="630" />
|
||||||
|
<meta property="og:image:alt" content={title} />
|
||||||
|
<meta property="og:locale" content="en_GB" />
|
||||||
|
<meta property="og:locale:alternate" content="fr_FR" />
|
||||||
|
<meta property="og:locale:alternate" content="nb_NO" />
|
||||||
|
{resolvedOgType === "article" && ogArticlePublishedTime && (
|
||||||
|
<meta property="article:published_time" content={ogArticlePublishedTime} />
|
||||||
|
)}
|
||||||
|
{resolvedOgType === "article" && ogArticleSection && (
|
||||||
|
<meta property="article:section" content={ogArticleSection} />
|
||||||
|
)}
|
||||||
|
<meta property="article:author" content="https://davegilligan.com" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content={title} />
|
||||||
|
<meta name="twitter:description" content={description} />
|
||||||
|
<meta name="twitter:image" content={ogImageAbsolute} />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const relatedSections = launchSections.filter(
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title={`${section.title} | Dave Gilligan`}
|
title={`${section.title} | Dave Gilligan`}
|
||||||
description={section.summary.en}
|
description={section.summary.en}
|
||||||
|
ogImage={`/images/og/${section.slug}.png`}
|
||||||
>
|
>
|
||||||
<main class="section-page">
|
<main class="section-page">
|
||||||
<section class="container section-page__hero">
|
<section class="container section-page__hero">
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const [gilliganTech, blueNoteLogic] = ventureDesk;
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="AI Lab | Dave Gilligan"
|
title="AI Lab | Dave Gilligan"
|
||||||
description="A fully developed AI Lab issue covering CorpusAI, live use cases, European infrastructure, API examples, and Blue Note Logic implementation work."
|
description="A fully developed AI Lab issue covering CorpusAI, live use cases, European infrastructure, API examples, and Blue Note Logic implementation work."
|
||||||
|
ogImage="/images/og/ai-lab.png"
|
||||||
>
|
>
|
||||||
<main class="ai-lab-page">
|
<main class="ai-lab-page">
|
||||||
<section class="container ai-lab-hero">
|
<section class="container ai-lab-hero">
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ const pageCopy = {
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title={`${aiBubbleArticle.title} | Dave Gilligan`}
|
title={`${aiBubbleArticle.title} | Dave Gilligan`}
|
||||||
description={aiBubbleArticle.excerpt.en}
|
description={aiBubbleArticle.excerpt.en}
|
||||||
|
ogType="article"
|
||||||
|
ogImage="/images/articles/ai-bubble/data-centre.jpg"
|
||||||
|
ogArticlePublishedTime={aiBubbleArticle.publishedAt.replace(' ', 'T') + 'Z'}
|
||||||
|
ogArticleSection="Business"
|
||||||
>
|
>
|
||||||
<main class="jazz-page">
|
<main class="jazz-page">
|
||||||
<section class="container jazz-hero">
|
<section class="container jazz-hero">
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ const pageCopy = {
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title={`${borisVianArticle.title} | Dave Gilligan`}
|
title={`${borisVianArticle.title} | Dave Gilligan`}
|
||||||
description={borisVianArticle.excerpt.en}
|
description={borisVianArticle.excerpt.en}
|
||||||
|
ogType="article"
|
||||||
|
ogImage="/images/articles/boris-vian/boris-vian.jpg"
|
||||||
|
ogArticlePublishedTime={borisVianArticle.publishedAt.replace(' ', 'T') + 'Z'}
|
||||||
|
ogArticleSection="Writing"
|
||||||
>
|
>
|
||||||
<main class="jazz-page">
|
<main class="jazz-page">
|
||||||
<section class="container jazz-hero">
|
<section class="container jazz-hero">
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ const pageCopy = {
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title={`${jazzArticle.title} | Dave Gilligan`}
|
title={`${jazzArticle.title} | Dave Gilligan`}
|
||||||
description={jazzArticle.excerpt.en}
|
description={jazzArticle.excerpt.en}
|
||||||
|
ogType="article"
|
||||||
|
ogImage="/images/og/jazz-music.png"
|
||||||
|
ogArticlePublishedTime={jazzArticle.publishedAt.replace(' ', 'T') + 'Z'}
|
||||||
|
ogArticleSection="Jazz and Music"
|
||||||
>
|
>
|
||||||
<main class="jazz-page">
|
<main class="jazz-page">
|
||||||
<section class="container jazz-hero">
|
<section class="container jazz-hero">
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import LocaleCopy from "../../components/LocaleCopy.astro";
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title={`${norwayArticle.title} | Dave Gilligan`}
|
title={`${norwayArticle.title} | Dave Gilligan`}
|
||||||
description={norwayArticle.excerpt.en}
|
description={norwayArticle.excerpt.en}
|
||||||
|
ogType="article"
|
||||||
|
ogImage="/images/og/norway.png"
|
||||||
|
ogArticlePublishedTime={norwayArticle.publishedAt.replace(' ', 'T') + 'Z'}
|
||||||
|
ogArticleSection="Norway"
|
||||||
>
|
>
|
||||||
<main class="norway-page">
|
<main class="norway-page">
|
||||||
<section class="container norway-hero norway-hero--article">
|
<section class="container norway-hero norway-hero--article">
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import LocaleCopy from "../../components/LocaleCopy.astro";
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title={`${projectsArticle.title} | Dave Gilligan`}
|
title={`${projectsArticle.title} | Dave Gilligan`}
|
||||||
description={projectsArticle.excerpt.en}
|
description={projectsArticle.excerpt.en}
|
||||||
|
ogType="article"
|
||||||
|
ogImage="/images/og/projects.png"
|
||||||
|
ogArticlePublishedTime={projectsArticle.publishedAt.replace(' ', 'T') + 'Z'}
|
||||||
|
ogArticleSection="Projects"
|
||||||
>
|
>
|
||||||
<main class="projects-page">
|
<main class="projects-page">
|
||||||
<section class="container projects-hero">
|
<section class="container projects-hero">
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const copy = {
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Business Desk | Dave Gilligan"
|
title="Business Desk | Dave Gilligan"
|
||||||
description="A live business desk fed from the CMS: agentic AI, labour, Queneau, Ionesco, and the anti-buzzword management of imaginary solutions."
|
description="A live business desk fed from the CMS: agentic AI, labour, Queneau, Ionesco, and the anti-buzzword management of imaginary solutions."
|
||||||
|
ogImage="/images/og/business.png"
|
||||||
>
|
>
|
||||||
<main class="business-page">
|
<main class="business-page">
|
||||||
<section class="container jazz-layout" style="margin-bottom: 0; padding-bottom: 0;">
|
<section class="container jazz-layout" style="margin-bottom: 0; padding-bottom: 0;">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Cookie Desk | Dave Gilligan"
|
title="Cookie Desk | Dave Gilligan"
|
||||||
description="Draft cookie notice for the localhost multilingual build, covering essential cookies, optional analytics, and future embedded media consent."
|
description="Draft cookie notice for the localhost multilingual build, covering essential cookies, optional analytics, and future embedded media consent."
|
||||||
|
ogImage="/images/og/home.png"
|
||||||
>
|
>
|
||||||
<main class="policy-page">
|
<main class="policy-page">
|
||||||
<section class="policy-hero container">
|
<section class="policy-hero container">
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const educationSlice = schoolDossiers.slice(0, 5);
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="CV | Dave Gilligan"
|
title="CV | Dave Gilligan"
|
||||||
description="A custom CV desk covering Dave Gilligan's current ventures, earlier finance and reinsurance work, education route, and the operating themes tying them together."
|
description="A custom CV desk covering Dave Gilligan's current ventures, earlier finance and reinsurance work, education route, and the operating themes tying them together."
|
||||||
|
ogImage="/images/og/cv.png"
|
||||||
>
|
>
|
||||||
<main class="cv-page">
|
<main class="cv-page">
|
||||||
<section class="container cv-hero">
|
<section class="container cv-hero">
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const copy = {
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Education Dossiers | Dave Gilligan"
|
title="Education Dossiers | Dave Gilligan"
|
||||||
description="Five school dossiers written as a literary magazine issue, with attributed imagery and clearly labeled faux Boris Vian and Vernon Sullivan signatures."
|
description="Five school dossiers written as a literary magazine issue, with attributed imagery and clearly labeled faux Boris Vian and Vernon Sullivan signatures."
|
||||||
|
ogImage="/images/og/education.png"
|
||||||
>
|
>
|
||||||
<main class="education-page">
|
<main class="education-page">
|
||||||
<section class="education-hero container">
|
<section class="education-hero container">
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const namedPhotos = photos.filter((photo) => photo.namedFile).slice(0, 12);
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Family Laboratory | Dave Gilligan"
|
title="Family Laboratory | Dave Gilligan"
|
||||||
description="A bright pataphysical family-atlas test page built from the Facebook archive, with a custom editorial collage and a full lightbox for the cleaned photo set."
|
description="A bright pataphysical family-atlas test page built from the Facebook archive, with a custom editorial collage and a full lightbox for the cleaned photo set."
|
||||||
|
ogImage="/images/og/family-lab.png"
|
||||||
>
|
>
|
||||||
<main class="family-lab-page">
|
<main class="family-lab-page">
|
||||||
<section class="container family-lab-hero">
|
<section class="container family-lab-hero">
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const projects = launchSections.find((section) => section.slug === "projects");
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Dave Gilligan | Hybrid IT, AI, Jazz, and Literary Magazine"
|
title="Dave Gilligan | Hybrid IT, AI, Jazz, and Literary Magazine"
|
||||||
description="A hybrid hi-tech and retro editorial homepage for Dave Gilligan, weaving AI systems, jazz, writing, languages, advocacy, and lived geography into a magazine-like personal site."
|
description="A hybrid hi-tech and retro editorial homepage for Dave Gilligan, weaving AI systems, jazz, writing, languages, advocacy, and lived geography into a magazine-like personal site."
|
||||||
|
ogImage="/images/og/home.png"
|
||||||
>
|
>
|
||||||
<main class="page-shell page-shell--cover">
|
<main class="page-shell page-shell--cover">
|
||||||
<section class="cover-hero">
|
<section class="cover-hero">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import LocaleCopy from "../components/LocaleCopy.astro";
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Jazz and Music | Dave Gilligan"
|
title="Jazz and Music | Dave Gilligan"
|
||||||
description={jazzArticle.excerpt.en}
|
description={jazzArticle.excerpt.en}
|
||||||
|
ogImage="/images/og/jazz-music.png"
|
||||||
>
|
>
|
||||||
<main class="jazz-page">
|
<main class="jazz-page">
|
||||||
<section class="container jazz-hero">
|
<section class="container jazz-hero">
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import LocaleCopy from "../components/LocaleCopy.astro";
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Norway Desk | Dave Gilligan"
|
title="Norway Desk | Dave Gilligan"
|
||||||
description="A Norway desk feature on family life, fathers, immigration, and the Article 8 case law surrounding Norway."
|
description="A Norway desk feature on family life, fathers, immigration, and the Article 8 case law surrounding Norway."
|
||||||
|
ogImage="/images/og/norway.png"
|
||||||
>
|
>
|
||||||
<main class="norway-page">
|
<main class="norway-page">
|
||||||
<section class="container norway-hero">
|
<section class="container norway-hero">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Privacy Desk | Dave Gilligan"
|
title="Privacy Desk | Dave Gilligan"
|
||||||
description="Draft privacy notice for the local multilingual edition, covering accounts, family access, contact handling, and editorial infrastructure."
|
description="Draft privacy notice for the local multilingual edition, covering accounts, family access, contact handling, and editorial infrastructure."
|
||||||
|
ogImage="/images/og/home.png"
|
||||||
>
|
>
|
||||||
<main class="policy-page">
|
<main class="policy-page">
|
||||||
<section class="policy-hero container">
|
<section class="policy-hero container">
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import LocaleCopy from "../components/LocaleCopy.astro";
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Projects Desk | Dave Gilligan"
|
title="Projects Desk | Dave Gilligan"
|
||||||
description="An April 2026 projects desk feature on Trivia & Tunes, Blue Note Rhino, room-scale music trivia, and the first full vibe-coded product in the portfolio."
|
description="An April 2026 projects desk feature on Trivia & Tunes, Blue Note Rhino, room-scale music trivia, and the first full vibe-coded product in the portfolio."
|
||||||
|
ogImage="/images/og/projects.png"
|
||||||
>
|
>
|
||||||
<main class="projects-page">
|
<main class="projects-page">
|
||||||
<section class="container projects-hero">
|
<section class="container projects-hero">
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const copy = {
|
|||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Writing Desk | Dave Gilligan"
|
title="Writing Desk | Dave Gilligan"
|
||||||
description="A live writing desk fed from the PHP archive: Boris Vian, Vernon Sullivan weather, jazz syntax, and pataphysical essays."
|
description="A live writing desk fed from the PHP archive: Boris Vian, Vernon Sullivan weather, jazz syntax, and pataphysical essays."
|
||||||
|
ogImage="/images/og/writing.png"
|
||||||
>
|
>
|
||||||
<main class="writing-page">
|
<main class="writing-page">
|
||||||
<section class="container writing-feature" style="margin-bottom: 0; padding-bottom: 0;">
|
<section class="container writing-feature" style="margin-bottom: 0; padding-bottom: 0;">
|
||||||
|
|||||||