mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-06 13:44:21 +08:00
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:
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user