* design: landing page spec + app icon asset * chore(www): add anime.js, fonts, and landing page screenshot assets * feat(www): add design tokens, fonts, shared layout * feat(www): add landing page sections and styling * feat(www): add theme toggle and anime.js viewport animations * feat(www): feedback updates — no hero card, os-aware download, footer email, lowercase colored heading * feat(www): remove screenshot borders, move macos hint below ctas, mobile github fallback * feat(www): keep hero CTAs side by side * feat(www): set initial nav border to transparent * feat(www): add Rybbit prod-only, sitemap, SEO head, OG image, robots.txt, 404 page * docs(AGENTS): include www landing page version in release sync checklist * chore(www): add railpack.json for Dokploy deploy (force node provider, TZ) * chore(www): use bun for Railpack deploy (packageManager + standalone www/bun.lock) * docs(AGENTS): note www/bun.lock sync for Railpack deploys * chore(www): add vitest test tooling * feat(www): add FAQ data source * feat(www): add SEO JSON-LD builders * feat(www): add Seo JSON-LD component * feat(www): wire Seo component and social/meta tags into Layout * feat(www): add homepage JSON-LD, keyword H1, data-driven FAQ * fix(www): point Roadmap footer link to GitHub * feat(www): add lastmod to sitemap * perf(www): self-host anime.js * refactor(www): move nav and footer into shared Layout * feat(www): add changelog page * fix(www): make shared component styles global in Layout * perf(www): optimize screenshots with Astro Image (webp/avif + srcset) * feat(www): add web app manifest * feat(www): add helpful links to 404 page * fix(www): shorten homepage H1 copy * fix(www): OS-aware navbar download + footer layout * feat(www): build full changelog from v0.7.0 to v0.7.10
109 lines
2.9 KiB
JavaScript
109 lines
2.9 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import satori from "satori";
|
|
import sharp from "sharp";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const root = path.resolve(__dirname, "..");
|
|
|
|
const literataPath = path.resolve(__dirname, "fonts/or3PQ6P12-iJxAIgLa78DkrbXsDgk0oVDaDPYLanFLHpPf2TbBG_F_Y.ttf");
|
|
const outfitPath = path.resolve(__dirname, "fonts/QGYyz_MVcBeNP4NjuGObqx1XmO1I4TC1C4E.ttf");
|
|
const iconPath = path.resolve(root, "public/gridline-icon.svg");
|
|
|
|
const fonts = [
|
|
{ name: "Literata", data: fs.readFileSync(literataPath), weight: 400, style: "normal" },
|
|
{ name: "Outfit", data: fs.readFileSync(outfitPath), weight: 400, style: "normal" },
|
|
];
|
|
|
|
const iconSvg = fs.readFileSync(iconPath, "utf-8");
|
|
const iconDataUri = `data:image/svg+xml;base64,${Buffer.from(iconSvg).toString("base64")}`;
|
|
|
|
const element = {
|
|
type: "div",
|
|
props: {
|
|
style: {
|
|
width: 1200,
|
|
height: 630,
|
|
background: "#0a0a0a",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
padding: 80,
|
|
fontFamily: "Outfit",
|
|
color: "#fafafa",
|
|
position: "relative",
|
|
},
|
|
children: [
|
|
{
|
|
type: "div",
|
|
props: {
|
|
style: {
|
|
position: "absolute",
|
|
inset: 0,
|
|
background:
|
|
"radial-gradient(circle at 50% 0%, rgba(59,130,246,0.15), transparent 60%)",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "img",
|
|
props: {
|
|
src: iconDataUri,
|
|
width: 140,
|
|
height: 140,
|
|
style: { marginBottom: 40 },
|
|
},
|
|
},
|
|
{
|
|
type: "h1",
|
|
props: {
|
|
style: {
|
|
fontFamily: "Literata",
|
|
fontSize: 84,
|
|
fontWeight: 400,
|
|
lineHeight: 1.05,
|
|
textAlign: "center",
|
|
margin: 0,
|
|
letterSpacing: "-0.02em",
|
|
},
|
|
children: "Gridline",
|
|
},
|
|
},
|
|
{
|
|
type: "p",
|
|
props: {
|
|
style: {
|
|
fontSize: 32,
|
|
color: "rgba(255,255,255,0.55)",
|
|
marginTop: 24,
|
|
textAlign: "center",
|
|
maxWidth: 800,
|
|
},
|
|
children: "The open-source database GUI for PostgreSQL, MySQL, SQLite, and Redis.",
|
|
},
|
|
},
|
|
{
|
|
type: "div",
|
|
props: {
|
|
style: {
|
|
position: "absolute",
|
|
bottom: 48,
|
|
fontSize: 22,
|
|
color: "rgba(255,255,255,0.35)",
|
|
fontFamily: "Outfit",
|
|
},
|
|
children: "getgridline.app",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const svg = await satori(element, { width: 1200, height: 630, fonts });
|
|
const png = await sharp(Buffer.from(svg)).png().toBuffer();
|
|
const outPath = path.resolve(root, "public/og.png");
|
|
fs.writeFileSync(outPath, png);
|
|
console.log(`OG image written to ${outPath}`);
|