feat(docs): enhance documentation site with internationalization and layout updates

- Updated the demo API to support locale-based content rendering.
- Refactored the main layout to accommodate language-specific metadata and routing.
- Introduced new components for blog and documentation pages, ensuring proper language handling.
- Added styles for improved UI consistency across different sections.
- Removed deprecated layout files and streamlined the structure for better maintainability.
This commit is contained in:
leookun
2026-08-28 00:35:31 +08:00
parent 6c758f2b44
commit eca063e424
55 changed files with 6700 additions and 407 deletions
@@ -1,17 +1,36 @@
.stage {
position: relative;
width: 100%;
max-width: 960px;
margin: 64px auto 0;
}
.halo {
position: absolute;
inset: -10% -14%;
background: radial-gradient(
50% 55% at 50% 45%,
color-mix(in srgb, var(--color-fd-primary) 9%, transparent),
transparent 72%
);
pointer-events: none;
}
.viewport {
position: relative;
width: 100%;
aspect-ratio: 960 / 620;
overflow: hidden;
background: #141414;
border: 1px solid rgb(255 255 255 / 10%);
background: #f5f5f5;
border: 1px solid var(--color-fd-border);
border-radius: 14px;
box-shadow:
0 42px 90px -34px rgb(0 0 0 / 35%),
0 12px 32px -16px rgb(0 0 0 / 28%);
}
.viewport[data-dark] {
background: #141414;
box-shadow:
0 42px 90px -34px rgb(0 0 0 / 50%),
0 12px 32px -16px rgb(0 0 0 / 42%);
@@ -23,15 +42,18 @@
display: block;
width: 100%;
height: 100%;
background: #141414;
background: transparent;
border: 0;
}
.stage p {
margin: 14px 0 0;
color: var(--color-fd-muted-foreground);
font-size: 12px;
text-align: center;
.poster {
object-fit: cover;
transition: opacity 0.6s ease;
}
.posterHidden {
opacity: 0;
pointer-events: none;
}
@media (max-width: 680px) {
+103 -7
View File
@@ -1,18 +1,114 @@
'use client';
import Image from 'next/image';
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
import { demoLocale, type Language } from '@/lib/i18n';
import styles from './DesktopDemo.module.css';
export function DesktopDemo() {
// 小屏或触屏设备内嵌交互体验差,只展示 poster 静态图
const embedQuery = '(min-width: 680px) and (hover: hover)';
const alt: Record<Language, string> = {
zh: 'cursor-byok 桌面端数据概览界面',
en: 'cursor-byok desktop overview dashboard',
};
function subscribeEmbedQuery(callback: () => void) {
const media = window.matchMedia(embedQuery);
media.addEventListener('change', callback);
return () => media.removeEventListener('change', callback);
}
function subscribeTheme(callback: () => void) {
const observer = new MutationObserver(callback);
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
return () => observer.disconnect();
}
export function DesktopDemo({ lang }: { lang: Language }) {
const stageRef = useRef<HTMLDivElement>(null);
const [mounted, setMounted] = useState(false);
const embeddable = useSyncExternalStore(
subscribeEmbedQuery,
() => window.matchMedia(embedQuery).matches,
() => true,
);
const dark = useSyncExternalStore(
subscribeTheme,
() => document.documentElement.classList.contains('dark'),
() => true,
);
useEffect(() => {
if (!embeddable) return;
const stage = stageRef.current;
if (!stage) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries.some((entry) => entry.isIntersecting)) {
setMounted(true);
observer.disconnect();
}
},
{ rootMargin: '240px' },
);
observer.observe(stage);
return () => observer.disconnect();
}, [embeddable]);
return (
<div className={styles.stage}>
<div className={styles.viewport}>
<div ref={stageRef} className={styles.stage}>
<div className={styles.halo} aria-hidden />
<DemoViewport
key={`${dark ? 'dark' : 'light'}-${lang}`}
dark={dark}
lang={lang}
mounted={embeddable && mounted}
/>
</div>
);
}
function DemoViewport({
dark,
lang,
mounted,
}: {
dark: boolean;
lang: Language;
mounted: boolean;
}) {
const [frameLoaded, setFrameLoaded] = useState(false);
const [revealed, setRevealed] = useState(false);
const theme = dark ? 'default-dark' : 'default-light';
const demoUrl = `/product-demo/demo/index.html?theme=${theme}&locale=${demoLocale[lang]}`;
const posterUrl = `/images/product-demo-${dark ? 'dark' : 'light'}-${lang}.png`;
useEffect(() => {
if (!frameLoaded) return;
// iframe onLoad 只代表文档加载完成,等内部应用渲染后再揭开 poster
const timer = setTimeout(() => setRevealed(true), 900);
return () => clearTimeout(timer);
}, [frameLoaded]);
return (
<div className={styles.viewport} data-dark={dark || undefined}>
{mounted ? (
<iframe
title="Cursor BYOK 真实产品界面演示"
src="/product-demo/demo/index.html"
title={alt[lang]}
src={demoUrl}
className={styles.demo}
onLoad={() => setFrameLoaded(true)}
/>
</div>
<p>真实桌面端组件 · 使用隔离的 Mock 数据,可直接操作菜单、筛选和设置</p>
) : null}
<Image
src={posterUrl}
alt={alt[lang]}
fill
priority
unoptimized
className={revealed ? `${styles.poster} ${styles.posterHidden}` : styles.poster}
/>
</div>
);
}
@@ -0,0 +1,37 @@
'use client';
import { useSyncExternalStore } from 'react';
import { Download } from 'lucide-react';
import type { Language } from '@/lib/i18n';
import { releaseUrl } from '@/lib/shared';
function detectPlatform(): string | null {
const ua = navigator.userAgent;
if (/Mac/i.test(ua)) return 'macOS';
if (/Win/i.test(ua)) return 'Windows';
if (/Linux|X11/i.test(ua)) return 'Linux';
return null;
}
const labels: Record<Language, { platform: (platform: string) => string; fallback: string }> = {
zh: { platform: (platform) => `下载 ${platform} 版`, fallback: '下载最新版' },
en: { platform: (platform) => `Download for ${platform}`, fallback: 'Download' },
};
const subscribeNoop = () => () => {};
export function DownloadButton({ lang }: { lang: Language }) {
// 平台只在客户端可知;SSR 用通用文案,水合后替换为平台文案
const platform = useSyncExternalStore(subscribeNoop, detectPlatform, () => null);
const label = labels[lang];
return (
<a
href={releaseUrl}
className="inline-flex items-center gap-2 rounded-lg bg-fd-primary px-5 py-3 font-medium text-fd-primary-foreground transition-opacity hover:opacity-90"
>
<Download className="size-4" />
{platform ? label.platform(platform) : label.fallback}
</a>
);
}