diff --git a/apps/site/public/favicon.ico b/apps/site/public/favicon.ico new file mode 100644 index 0000000..1383ef3 Binary files /dev/null and b/apps/site/public/favicon.ico differ diff --git a/apps/site/public/icon-192.png b/apps/site/public/icon-192.png new file mode 100644 index 0000000..1947716 Binary files /dev/null and b/apps/site/public/icon-192.png differ diff --git a/apps/site/public/icon-512.png b/apps/site/public/icon-512.png new file mode 100644 index 0000000..db0477d Binary files /dev/null and b/apps/site/public/icon-512.png differ diff --git a/apps/site/public/og-image.png b/apps/site/public/og-image.png new file mode 100644 index 0000000..8ad1aff Binary files /dev/null and b/apps/site/public/og-image.png differ diff --git a/apps/site/src/app/[lang]/(marketing)/page.tsx b/apps/site/src/app/[lang]/(marketing)/page.tsx index 1bbfee9..7e96f32 100644 --- a/apps/site/src/app/[lang]/(marketing)/page.tsx +++ b/apps/site/src/app/[lang]/(marketing)/page.tsx @@ -1,3 +1,4 @@ +import type { Metadata } from 'next'; import { getMarketingContent } from '../../../../content/marketing'; import { ScrollNav } from '@/components/marketing/ScrollNav'; import { HeroSection } from '@/components/marketing/HeroSection'; @@ -6,6 +7,31 @@ import { IntegrationsSection } from '@/components/marketing/IntegrationsSection' import { FAQSection } from '@/components/marketing/FAQAccordion'; import { FinalCTA } from '@/components/marketing/FinalCTA'; import { SiteFooter } from '@/components/marketing/SiteFooter'; +import { siteConfig } from '@/lib/site.config'; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ lang: string }>; +}): Promise { + const { lang } = await params; + const isZh = lang === 'zh'; + return { + title: isZh + ? 'CodePilot — Claude Code 的桌面工作空间' + : 'CodePilot — Desktop Workspace for Claude Code', + description: isZh + ? '将对话、Provider、MCP、Skills 和项目上下文整合到一个桌面界面中。' + : siteConfig.description, + alternates: { + canonical: isZh ? `${siteConfig.url}/zh` : siteConfig.url, + languages: { + en: siteConfig.url, + zh: `${siteConfig.url}/zh`, + }, + }, + }; +} export default async function HomePage({ params, diff --git a/apps/site/src/app/apple-icon.png b/apps/site/src/app/apple-icon.png new file mode 100644 index 0000000..8d545a9 Binary files /dev/null and b/apps/site/src/app/apple-icon.png differ diff --git a/apps/site/src/app/icon.png b/apps/site/src/app/icon.png new file mode 100644 index 0000000..5990273 Binary files /dev/null and b/apps/site/src/app/icon.png differ diff --git a/apps/site/src/app/layout.tsx b/apps/site/src/app/layout.tsx index f4ac7f9..c4e82b5 100644 --- a/apps/site/src/app/layout.tsx +++ b/apps/site/src/app/layout.tsx @@ -1,13 +1,64 @@ import './global.css'; import type { ReactNode } from 'react'; import type { Metadata } from 'next'; +import { siteConfig } from '@/lib/site.config'; + +const title = { + default: 'CodePilot — Desktop Workspace for Claude Code', + template: '%s | CodePilot', +}; + +const description = siteConfig.description; export const metadata: Metadata = { - title: { - default: 'CodePilot', - template: '%s | CodePilot', + title, + description, + metadataBase: new URL(siteConfig.url), + keywords: [ + 'Claude Code', + 'AI coding', + 'desktop app', + 'MCP', + 'Claude', + 'Anthropic', + 'code assistant', + 'AI agent', + ], + authors: [{ name: 'CodePilot' }], + creator: 'CodePilot', + openGraph: { + type: 'website', + locale: 'en_US', + url: siteConfig.url, + siteName: siteConfig.name, + title: title.default, + description, + images: [ + { + url: '/og-image.png', + width: 1200, + height: 630, + alt: 'CodePilot — Desktop Workspace for Claude Code', + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: title.default, + description, + images: ['/og-image.png'], + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, + }, }, - description: 'A native desktop GUI client for Claude Code', }; export default function RootLayout({ children }: { children: ReactNode }) { diff --git a/apps/site/src/app/manifest.ts b/apps/site/src/app/manifest.ts new file mode 100644 index 0000000..07cdfe8 --- /dev/null +++ b/apps/site/src/app/manifest.ts @@ -0,0 +1,17 @@ +import type { MetadataRoute } from 'next'; + +export default function manifest(): MetadataRoute.Manifest { + return { + name: 'CodePilot', + short_name: 'CodePilot', + description: 'Desktop workspace for Claude Code', + start_url: '/', + display: 'browser', + background_color: '#ffffff', + theme_color: '#2563eb', + icons: [ + { src: '/icon-192.png', sizes: '192x192', type: 'image/png' }, + { src: '/icon-512.png', sizes: '512x512', type: 'image/png' }, + ], + }; +} diff --git a/apps/site/src/app/robots.ts b/apps/site/src/app/robots.ts new file mode 100644 index 0000000..4e687a3 --- /dev/null +++ b/apps/site/src/app/robots.ts @@ -0,0 +1,14 @@ +import type { MetadataRoute } from 'next'; +import { siteConfig } from '@/lib/site.config'; + +export default function robots(): MetadataRoute.Robots { + return { + rules: [ + { + userAgent: '*', + allow: '/', + }, + ], + sitemap: `${siteConfig.url}/sitemap.xml`, + }; +} diff --git a/apps/site/src/app/sitemap.ts b/apps/site/src/app/sitemap.ts new file mode 100644 index 0000000..35afccd --- /dev/null +++ b/apps/site/src/app/sitemap.ts @@ -0,0 +1,26 @@ +import type { MetadataRoute } from 'next'; +import { source } from '@/lib/source'; +import { siteConfig } from '@/lib/site.config'; + +export default function sitemap(): MetadataRoute.Sitemap { + const url = siteConfig.url; + + const staticPages: MetadataRoute.Sitemap = [ + { url, lastModified: new Date(), changeFrequency: 'weekly', priority: 1 }, + { url: `${url}/zh`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.9 }, + ]; + + const docPages: MetadataRoute.Sitemap = source.getPages().map((page) => { + const lang = page.locale ?? 'en'; + const slugPath = page.slugs.join('/'); + const prefix = lang === 'en' ? '' : `/${lang}`; + return { + url: `${url}${prefix}/docs/${slugPath}`, + lastModified: new Date(), + changeFrequency: 'weekly' as const, + priority: 0.7, + }; + }); + + return [...staticPages, ...docPages]; +} diff --git a/apps/site/src/components/docs/DownloadButton.tsx b/apps/site/src/components/docs/DownloadButton.tsx index 11107f1..4bdd9e1 100644 --- a/apps/site/src/components/docs/DownloadButton.tsx +++ b/apps/site/src/components/docs/DownloadButton.tsx @@ -6,7 +6,7 @@ export function DownloadButton({ label = 'Download Latest Release' }: { label?: href={`${siteConfig.repo.releases}/latest`} target="_blank" rel="noopener noreferrer" - className="mt-4 mb-6 inline-flex items-center gap-2 rounded-full bg-foreground px-6 py-3 text-sm font-semibold text-background transition-opacity hover:opacity-80" + className="mt-4 mb-6 inline-flex items-center gap-2 rounded-full bg-foreground px-6 py-3 text-sm font-semibold text-background transition-opacity hover:opacity-80 dark:bg-white dark:text-black" > {label} diff --git a/apps/site/src/lib/site.config.ts b/apps/site/src/lib/site.config.ts index 9ce6d41..02ec5ca 100644 --- a/apps/site/src/lib/site.config.ts +++ b/apps/site/src/lib/site.config.ts @@ -5,7 +5,8 @@ */ export const siteConfig = { name: 'CodePilot', - description: 'A native desktop GUI client for Claude Code', + description: 'Desktop workspace for Claude Code — conversations, providers, MCP, and project context in one place.', + url: 'https://www.codepilot.sh', // Canonical repository repo: {