feat(www): landing page (#25)
* 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
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,108 @@
|
||||
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}`);
|
||||
Reference in New Issue
Block a user