--- import Page from '../page'; import '../globals.css'; import { createElement } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import FaviconLinks from '../_components/favicon-links.astro'; import GoogleAnalytics from '../_components/google-analytics.astro'; import ResourceHints from '../_components/resource-hints.astro'; import LocaleSwitcherScript from '../_components/locale-switcher-script.astro'; import PreciseLazyload from '../_components/precise-lazyload.astro'; import { heroImage, heroImageSrcset } from '../image-assets'; import { LANDING_LOCALES, alternateLinksForPath, getHomeFaq, getHomeSeo, getLocaleDefinition, localeFromPath, localePath, type LandingLocaleCode, } from '../i18n'; import { getCatalogCounts } from '../_lib/catalog'; import { getGithubRepoMeta } from '../_lib/github'; const locale: LandingLocaleCode = localeFromPath(Astro.url.pathname); const localeDef = getLocaleDefinition(locale); const counts = await getCatalogCounts(); const github = await getGithubRepoMeta(); const { title, description } = getHomeSeo(locale, counts); const canonical = new URL(localePath(locale), Astro.site).toString(); const origin = Astro.site?.toString() ?? 'https://open-design.ai/'; const logoUrl = new URL('/android-chrome-512x512.png', Astro.site).toString(); const alternateLinks = alternateLinksForPath('/').map((entry) => ({ ...entry, href: new URL(entry.hrefPath, Astro.site).toString(), })); const xDefaultHref = new URL('/', Astro.site).toString(); const REPO_URL = 'https://github.com/nexu-io/open-design'; const RELEASES_URL = `${REPO_URL}/releases`; const ISSUES_URL = `${REPO_URL}/issues`; const DOCS_URL = `${REPO_URL}#readme`; const LICENSE_URL = `${REPO_URL}/blob/main/LICENSE`; const DISCORD_URL = 'https://discord.gg/9ptkbbqRu'; const OFFICIAL_URL = `${origin}official/`; // Pass bare hosts (not full URLs) so localized Q2 reads // "lives at open-design.ai" instead of the awkward // "lives at https://open-design.ai/" with a stray protocol/slash. const faq = getHomeFaq(locale, { origin: 'open-design.ai', repo: 'github.com/nexu-io/open-design', }); const websiteSchema = { '@type': 'WebSite', '@id': `${origin}#website`, name: 'Open Design', alternateName: ['OpenDesign', 'open-design', 'opendesign', 'Open Design AI', 'OD'], url: origin, inLanguage: localeDef.htmlLang, availableLanguage: LANDING_LOCALES.map((entry) => entry.htmlLang), publisher: { '@id': `${origin}#organization` }, }; const organizationSchema = { '@type': 'Organization', '@id': `${origin}#organization`, name: 'Open Design', alternateName: ['OpenDesign', 'open-design', 'opendesign', 'Open Design AI', 'OD', 'nexu-io/open-design'], url: origin, logo: { '@type': 'ImageObject', url: logoUrl, width: 512, height: 512, }, // Five canonical pillars — Google uses sameAs to merge entity claims // across sources. Listing the official site, GitHub repo, release // feed, README docs, and Discord here prevents capture sites from // splitting the brand entity. sameAs: [REPO_URL, RELEASES_URL, DOCS_URL, DISCORD_URL, OFFICIAL_URL], }; const softwareSchema = { '@type': 'SoftwareApplication', '@id': `${origin}#software`, name: 'Open Design', alternateName: ['OpenDesign', 'open-design', 'opendesign', 'Open Design AI', 'OD'], description, url: origin, inLanguage: localeDef.htmlLang, applicationCategory: 'DesignApplication', operatingSystem: 'macOS, Windows, Linux', license: 'https://www.apache.org/licenses/LICENSE-2.0', softwareVersion: github.versionLabel, downloadUrl: RELEASES_URL, installUrl: `${origin}quickstart/`, softwareHelp: { '@type': 'CreativeWork', url: DOCS_URL }, releaseNotes: RELEASES_URL, codeRepository: REPO_URL, discussionUrl: DISCORD_URL, issueTracker: ISSUES_URL, sameAs: [REPO_URL, RELEASES_URL, DOCS_URL, DISCORD_URL, OFFICIAL_URL, LICENSE_URL], offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD', }, publisher: { '@id': `${origin}#organization` }, }; const faqSchema = { '@type': 'FAQPage', '@id': `${canonical}#faq`, inLanguage: localeDef.htmlLang, mainEntity: faq.map(({ q, a }) => ({ '@type': 'Question', name: q, acceptedAnswer: { '@type': 'Answer', text: a }, })), }; const homepageGraph = { '@context': 'https://schema.org', '@graph': [websiteSchema, organizationSchema, softwareSchema, faqSchema], }; const pageHtml = renderToStaticMarkup( Page({ counts, github, faq, locale }) as ReturnType, ); --- {title} {alternateLinks.map((entry) => ( ))} {/* * Hero LCP preload. Cloudflare Pages turns this into * a 103 Early Hints response automatically when Early Hints is enabled in * the dashboard, so the browser starts the image fetch before the HTML * body finishes streaming. * * Only emitted on `/` — the rest of the site uses lighter hero treatment. */} {LANDING_LOCALES.filter((entry) => entry.code !== locale).map((entry) => ( ))} {/* * Single @graph JSON-LD block — WebSite + Organization + * SoftwareApplication + FAQPage. The FAQPage entries must mirror * the visible FAQ rendered by `` further down the page. */}