diff --git a/package.json b/package.json index 72aef1b..8c70ab7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "private": true, "scripts": { "dev": "next dev -H 0.0.0.0 -p 3000 --webpack", - "build": "next build --webpack", + "build": "npm run og-image && next build --webpack", + "og-image": "node scripts/build-og-image.mjs", "start": "next start -H 0.0.0.0 -p 3000", "lint": "next lint", "typecheck": "tsc --noEmit", diff --git a/public/fonts/inter-800.woff b/public/fonts/inter-800.woff new file mode 100644 index 0000000..ed23f0b Binary files /dev/null and b/public/fonts/inter-800.woff differ diff --git a/public/og-image.png b/public/og-image.png new file mode 100644 index 0000000..af55e81 Binary files /dev/null and b/public/og-image.png differ diff --git a/scripts/build-og-image.mjs b/scripts/build-og-image.mjs new file mode 100644 index 0000000..8775f41 --- /dev/null +++ b/scripts/build-og-image.mjs @@ -0,0 +1,122 @@ +"use strict"; +/** + * Generates /public/og-image.png from the brand logo with the project + * colours and name baked in. Run with `node scripts/build-og-image.mjs`. + * Requires `sharp` (already a transitive Next.js dependency). + * + * The SVG embeds the Inter font directly via base64 so the image is + * identical on every machine regardless of which fonts are installed. + * If the font fetch fails we fall back to the system sans-serif at a + * conservatively smaller size so the wordmark still fits. + */ +import { promises as fs } from "node:fs"; +import path from "node:path"; +import sharp from "sharp"; + +const ROOT = path.resolve(process.cwd()); + +const W = 1200; +const H = 630; + +const palette = { + bg: "#0f172a", + bgAccent: "#1c2433", + orange: "#fdb13c", + teal: "#006672", + gridLine: "rgba(255,255,255,0.04)", +}; + +async function loadFontBase64() { + // Check for a local font file first (committed to the repo so Docker + // builds work offline). + const localPath = path.join(ROOT, "public/fonts/inter-800.woff"); + let buffer; + try { + buffer = await fs.readFile(localPath); + } catch { + // Fall back to Google Fonts. + const url = + "https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuDyYMZtrib2Ai-4.woff"; + const res = await fetch(url); + if (!res.ok) throw new Error(`Failed to fetch Inter: ${res.status}`); + buffer = Buffer.from(await res.arrayBuffer()); + } + return buffer.toString("base64"); +} + +const fontB64 = await loadFontBase64(); + +const svg = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CONSTRUCTION OPERATIONS + + + MEGAINFRASTRUCTURE + + + Site operations dashboard · fuel · orders · workforce · sites + + + + + + + + + + + +`; + +const out = path.join(ROOT, "public", "og-image.png"); + +const buf = Buffer.from(svg); +await sharp(buf, { density: 144 }) + .resize({ width: W, height: H }) + .png({ compressionLevel: 9 }) + .toFile(out); + +const stat = await fs.stat(out); +console.log(`Wrote ${out} (${(stat.size / 1024).toFixed(1)} KB, ${W}x${H})`); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2815970..fa3e0fa 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -9,6 +9,41 @@ export const metadata: Metadata = { description: "Construction site operations dashboard — fuel tracking, order management, and workforce hours for MEGA INFRASTRUCTURE.", applicationName: "MEGA INFRASTRUCTURE", + authors: [{ name: "MEGA INFRASTRUCTURE" }], + generator: "Next.js", + keywords: [ + "construction", + "operations", + "fuel tracking", + "workforce", + "orders", + "site management", + ], + // Open Graph — used by WhatsApp, Facebook, LinkedIn, Discord, etc. + openGraph: { + type: "website", + siteName: "MEGA INFRASTRUCTURE", + title: "MEGA INFRASTRUCTURE · Operations", + description: + "Site operations dashboard — fuel tracking, order management, and workforce hours.", + images: [ + { + url: "/og-image.png", + width: 1200, + height: 630, + alt: "MEGA INFRASTRUCTURE — Site operations dashboard", + }, + ], + locale: "en_ZA", + }, + // Twitter / X card + twitter: { + card: "summary_large_image", + title: "MEGA INFRASTRUCTURE · Operations", + description: + "Site operations dashboard — fuel tracking, order management, and workforce hours.", + images: ["/og-image.png"], + }, appleWebApp: { capable: true, title: "MEGA OPS",