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:
@@ -0,0 +1,110 @@
|
||||
export interface JsonLd {
|
||||
"@context": "https://schema.org";
|
||||
"@type": string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export function buildSoftwareApplicationJsonLd(opts: {
|
||||
name: string;
|
||||
url: string;
|
||||
description: string;
|
||||
image: string;
|
||||
operatingSystem: string;
|
||||
applicationCategory: string;
|
||||
offersPrice: number;
|
||||
offersCurrency: string;
|
||||
downloadUrl: string;
|
||||
}): JsonLd {
|
||||
return {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
name: opts.name,
|
||||
url: opts.url,
|
||||
description: opts.description,
|
||||
image: opts.image,
|
||||
operatingSystem: opts.operatingSystem,
|
||||
applicationCategory: opts.applicationCategory,
|
||||
offers: {
|
||||
"@type": "Offer",
|
||||
price: opts.offersPrice,
|
||||
priceCurrency: opts.offersCurrency,
|
||||
},
|
||||
downloadUrl: opts.downloadUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildFaqJsonLd(
|
||||
questions: { question: string; answer: string }[],
|
||||
): JsonLd {
|
||||
return {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
mainEntity: questions.map((q) => ({
|
||||
"@type": "Question",
|
||||
name: q.question,
|
||||
acceptedAnswer: {
|
||||
"@type": "Answer",
|
||||
text: q.answer,
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildOrganizationJsonLd(opts: {
|
||||
name: string;
|
||||
url: string;
|
||||
logo: string;
|
||||
sameAs: string[];
|
||||
}): JsonLd {
|
||||
return {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
name: opts.name,
|
||||
url: opts.url,
|
||||
logo: opts.logo,
|
||||
sameAs: opts.sameAs,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildWebSiteJsonLd(opts: {
|
||||
name: string;
|
||||
url: string;
|
||||
searchUrl?: string;
|
||||
}): JsonLd {
|
||||
const ld: JsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
name: opts.name,
|
||||
url: opts.url,
|
||||
};
|
||||
if (opts.searchUrl) {
|
||||
ld.potentialAction = {
|
||||
"@type": "SearchAction",
|
||||
target: {
|
||||
"@type": "EntryPoint",
|
||||
urlTemplate: opts.searchUrl,
|
||||
},
|
||||
"query-input": "required name=search_term_string",
|
||||
};
|
||||
}
|
||||
return ld;
|
||||
}
|
||||
|
||||
export function buildBreadcrumbJsonLd(
|
||||
items: { name: string; url: string }[],
|
||||
): JsonLd {
|
||||
return {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: items.map((item, i) => ({
|
||||
"@type": "ListItem",
|
||||
position: i + 1,
|
||||
name: item.name,
|
||||
item: item.url,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildTitle(primary: string, brand = "Gridline"): string {
|
||||
return `${primary} | ${brand}`;
|
||||
}
|
||||
Reference in New Issue
Block a user