diff --git a/.gitignore b/.gitignore index f6ffcd5..4527f48 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist/ # Generated from vendor/wavekat-brand submodule (run `make sync`) /public/logos/ +/public/og.png # Environment .env diff --git a/Makefile b/Makefile index 5b0f33c..5d00333 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ cf-build: # Remove build artifacts and synced assets clean: - rm -rf dist/ .astro/ public/logos/ + rm -rf dist/ .astro/ public/logos/ public/og.png help: @echo "Usage: make " diff --git a/astro.config.mjs b/astro.config.mjs index 926abcb..b5bc8e6 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -5,6 +5,7 @@ import tailwindcss from '@tailwindcss/vite'; // https://astro.build/config export default defineConfig({ + site: 'https://wavekat.com', output: 'static', vite: { plugins: [tailwindcss()], diff --git a/package-lock.json b/package-lock.json index 6c3603b..1025e34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "wavekat-com", "version": "0.0.2", "dependencies": { + "@resvg/resvg-js": "^2.6.2", "@tailwindcss/vite": "^4.2.2", "astro": "^6.1.1", "tailwindcss": "^4.2.2" @@ -1280,6 +1281,221 @@ "dev": true, "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==", + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", diff --git a/package.json b/package.json index 1a0d763..df34c6f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "astro": "astro" }, "dependencies": { + "@resvg/resvg-js": "^2.6.2", "@tailwindcss/vite": "^4.2.2", "astro": "^6.1.1", "tailwindcss": "^4.2.2" diff --git a/scripts/sync-brand.js b/scripts/sync-brand.js index 648d1af..aa145e4 100644 --- a/scripts/sync-brand.js +++ b/scripts/sync-brand.js @@ -5,16 +5,18 @@ // npm run cf:build // as the Pages build command. -import { cpSync, mkdirSync } from "fs"; +import { cpSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import { execSync } from "child_process"; import { join } from "path"; import { fileURLToPath } from "url"; +import { Resvg } from "@resvg/resvg-js"; const root = join(fileURLToPath(import.meta.url), "../.."); -const src = join(root, "vendor/wavekat-brand/assets/logos"); -const dest = join(root, "public/logos"); +const brandDir = join(root, "vendor/wavekat-brand/assets"); +const logoSrc = join(brandDir, "logos"); +const logoDest = join(root, "public/logos"); -const assets = [ +const logos = [ "wavekat-tight-light.svg", "wavekat-tight-dark.svg", "wavekat-icon-light.svg", @@ -30,9 +32,15 @@ try { // Not a fatal error — submodule may already be present } -mkdirSync(dest, { recursive: true }); +mkdirSync(logoDest, { recursive: true }); -for (const file of assets) { - cpSync(join(src, file), join(dest, file)); +for (const file of logos) { + cpSync(join(logoSrc, file), join(logoDest, file)); console.log(`synced ${file}`); } + +// Convert og.svg → og.png (social platforms require raster images) +const ogSvg = readFileSync(join(brandDir, "og.svg"), "utf8"); +const resvg = new Resvg(ogSvg, { fitTo: { mode: "width", value: 1200 } }); +writeFileSync(join(root, "public/og.png"), resvg.render().asPng()); +console.log("synced og.svg → og.png"); diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index e69005d..ed120c2 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -4,12 +4,17 @@ import '../styles/global.css'; interface Props { title?: string; description?: string; + ogImage?: string; } const { title = 'WaveKat', description = 'Give every small business the voice of a big one.', + ogImage = '/og.png', } = Astro.props; + +const canonicalURL = new URL(Astro.url.pathname, Astro.site); +const ogImageURL = new URL(ogImage, Astro.site); --- @@ -18,8 +23,24 @@ const { + {title} + + + + + + + + + + + + + + + diff --git a/vendor/wavekat-brand b/vendor/wavekat-brand index 9ca9515..a0c6ce4 160000 --- a/vendor/wavekat-brand +++ b/vendor/wavekat-brand @@ -1 +1 @@ -Subproject commit 9ca951514ca2ef1b6dea9d0250b6030e6fd94050 +Subproject commit a0c6ce4d9e0b06e46db97012c2e1c9c8747bdf27