diff --git a/web/src/components/layout/site-footer.tsx b/web/src/components/layout/site-footer.tsx index b91d8be..8566b6e 100644 --- a/web/src/components/layout/site-footer.tsx +++ b/web/src/components/layout/site-footer.tsx @@ -1,26 +1,83 @@ -const FOOTER_LINKS = [ - { label: 'GITHUB', href: 'https://github.com/sundynix' }, - { label: 'DOCS', href: '/docs' }, - { label: 'RELEASES', href: '/download' }, - { label: 'RSS', href: '/rss.xml' }, -] +import { Link } from 'react-router-dom' +import { SITE } from '@/content/site' + +const COLUMNS = [ + { + title: 'PRODUCT', + links: [ + { label: '功能', href: '/#features' }, + { label: '架构', href: '/#architecture' }, + { label: '下载', href: '/download', internal: true }, + ], + }, + { + title: 'RESOURCES', + links: [ + { label: '博客', href: '/blog', internal: true }, + { label: 'GitHub', href: SITE.github }, + { label: 'RSS 订阅', href: '/rss.xml' }, + ], + }, +] as const export function SiteFooter() { return ( ) diff --git a/web/src/components/reveal.tsx b/web/src/components/reveal.tsx new file mode 100644 index 0000000..53ebac5 --- /dev/null +++ b/web/src/components/reveal.tsx @@ -0,0 +1,45 @@ +import { useEffect, useRef, useState, type ReactNode } from 'react' +import { cn } from '@/lib/utils' + +interface RevealProps { + children: ReactNode + className?: string + /** 进场延迟(毫秒),用于同屏元素错峰 */ + delay?: number +} + +/** 滚动进场:进入视口后淡入上移一次;prefers-reduced-motion 时直接显示 */ +export function Reveal({ children, className, delay = 0 }: RevealProps) { + const ref = useRef(null) + const [visible, setVisible] = useState(false) + + useEffect(() => { + const el = ref.current + if (!el) return + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + setVisible(true) + return + } + const io = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setVisible(true) + io.disconnect() + } + }, + { threshold: 0.15, rootMargin: '0px 0px -40px 0px' }, + ) + io.observe(el) + return () => io.disconnect() + }, []) + + return ( +
+ {children} +
+ ) +} diff --git a/web/src/components/terminal-demo.tsx b/web/src/components/terminal-demo.tsx index 022ea93..4bcaa78 100644 --- a/web/src/components/terminal-demo.tsx +++ b/web/src/components/terminal-demo.tsx @@ -13,15 +13,19 @@ export function TerminalDemo() {
-
+
{TERMINAL_DEMO.prompt}
-
+
──────────────────────────────────
{TERMINAL_DEMO.events.map((ev, i) => ( -
+
[{ev.tag}] {' '} diff --git a/web/src/content/site.ts b/web/src/content/site.ts index 0a2d95f..390cbf5 100644 --- a/web/src/content/site.ts +++ b/web/src/content/site.ts @@ -4,6 +4,7 @@ export const SITE = { version: 'v0.1.2', tagline: '事件驱动的 AI Agent 工作台', github: 'https://github.com/sundynix/agentix', + icp: '滇ICP备2025056308号-1', } export const TERMINAL_DEMO = { diff --git a/web/src/hooks/use-page-title.ts b/web/src/hooks/use-page-title.ts new file mode 100644 index 0000000..f696ee8 --- /dev/null +++ b/web/src/hooks/use-page-title.ts @@ -0,0 +1,14 @@ +import { useEffect } from 'react' + +const BASE = 'sundynix agentix' +const DEFAULT = `${BASE} — 事件驱动的 AI Agent 工作台` + +/** 设置页面标题;不传参恢复默认 */ +export function usePageTitle(title?: string) { + useEffect(() => { + document.title = title ? `${title} — ${BASE}` : DEFAULT + return () => { + document.title = DEFAULT + } + }, [title]) +} diff --git a/web/src/index.css b/web/src/index.css index 45a645b..31f7473 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -113,8 +113,47 @@ body { .caret-blink { animation: caret-blink 1.1s steps(1) infinite; } -@media (prefers-reduced-motion: reduce) { - .caret-blink { - animation: none; + +/* 滚动进场(配合 Reveal 组件) */ +.reveal { + opacity: 0; + transform: translateY(16px); + transition: + opacity 0.6s ease, + transform 0.6s ease; +} +.reveal-in { + opacity: 1; + transform: none; +} + +/* 终端事件流:逐行浮现 */ +@keyframes term-line-in { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: none; + } +} +.term-line { + opacity: 0; + animation: term-line-in 0.45s ease forwards; +} + +@media (prefers-reduced-motion: reduce) { + .caret-blink, + .term-line { + animation: none; + } + .term-line { + opacity: 1; + } + .reveal { + opacity: 1; + transform: none; + transition: none; } } diff --git a/web/src/pages/blog-post.tsx b/web/src/pages/blog-post.tsx index 21662e6..fb5cdda 100644 --- a/web/src/pages/blog-post.tsx +++ b/web/src/pages/blog-post.tsx @@ -5,6 +5,7 @@ import remarkGfm from 'remark-gfm' import { ArrowLeft } from 'lucide-react' import { fetchPost, formatDate, isNotFound, type Post } from '@/api/posts' import NotFoundPage from '@/pages/not-found' +import { usePageTitle } from '@/hooks/use-page-title' type State = | { status: 'loading' } @@ -15,6 +16,7 @@ type State = export default function BlogPostPage() { const { slug = '' } = useParams() const [state, setState] = useState({ status: 'loading' }) + usePageTitle(state.status === 'ready' ? state.post.title : undefined) useEffect(() => { let cancelled = false diff --git a/web/src/pages/blog.tsx b/web/src/pages/blog.tsx index ca9c6bd..2dd2534 100644 --- a/web/src/pages/blog.tsx +++ b/web/src/pages/blog.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { fetchPosts, formatDate, type PostListItem } from '@/api/posts' import { SectionLabel } from '@/components/section-label' +import { usePageTitle } from '@/hooks/use-page-title' type State = | { status: 'loading' } @@ -9,6 +10,7 @@ type State = | { status: 'ready'; posts: PostListItem[] } export default function BlogPage() { + usePageTitle('博客') const [state, setState] = useState({ status: 'loading' }) useEffect(() => { diff --git a/web/src/pages/download.tsx b/web/src/pages/download.tsx index faa9891..a1b3e78 100644 --- a/web/src/pages/download.tsx +++ b/web/src/pages/download.tsx @@ -1,7 +1,9 @@ import { SectionLabel } from '@/components/section-label' import { DownloadGrid } from '@/components/download-grid' +import { usePageTitle } from '@/hooks/use-page-title' export default function DownloadPage() { + usePageTitle('下载') return (
diff --git a/web/src/pages/home/architecture.tsx b/web/src/pages/home/architecture.tsx index 5420d12..b5d126c 100644 --- a/web/src/pages/home/architecture.tsx +++ b/web/src/pages/home/architecture.tsx @@ -1,6 +1,7 @@ import { Fragment } from 'react' import { ARCH_LAYERS } from '@/content/site' import { SectionLabel } from '@/components/section-label' +import { Reveal } from '@/components/reveal' export function Architecture() { return ( @@ -9,17 +10,19 @@ export function Architecture() { className="border-t border-hairline px-6 py-16 lg:px-10" >
-
- ARCHITECTURE -

- 五层架构,一条零拷贝总线 -

-

- Monolith First,为微服务演进预留缝隙(Morph B)。 -

-
+ +
+ ARCHITECTURE +

+ 五层架构,一条零拷贝总线 +

+

+ Monolith First,为微服务演进预留缝隙(Morph B)。 +

+
+
-
+ {ARCH_LAYERS.map((layer, i) => ( {/* NATS 总线插在 L2 和 L4 之间 */} @@ -44,7 +47,7 @@ export function Architecture() {
))} -
+
) diff --git a/web/src/pages/home/download-cta.tsx b/web/src/pages/home/download-cta.tsx index 49163ec..b561238 100644 --- a/web/src/pages/home/download-cta.tsx +++ b/web/src/pages/home/download-cta.tsx @@ -1,20 +1,25 @@ import { SectionLabel } from '@/components/section-label' import { DownloadGrid } from '@/components/download-grid' +import { Reveal } from '@/components/reveal' export function DownloadCta() { return (
-
- DOWNLOAD -

- 把 AI 团队装进桌面 -

-

- 内置应用内更新,发布流水线自动出包。 -

-
- + +
+ DOWNLOAD +

+ 把 AI 团队装进桌面 +

+

+ 内置应用内更新,发布流水线自动出包。 +

+
+
+ + +
) diff --git a/web/src/pages/home/features.tsx b/web/src/pages/home/features.tsx index 7f850fb..7bec4f2 100644 --- a/web/src/pages/home/features.tsx +++ b/web/src/pages/home/features.tsx @@ -1,34 +1,39 @@ import { FEATURES } from '@/content/site' import { SectionLabel } from '@/components/section-label' +import { Reveal } from '@/components/reveal' export function Features() { return (
-
- CAPABILITIES -

- 一个工作台,一支 AI 团队 -

-

- 从编排到交付,每个环节都是流式的、可观测的、有守卫的。 -

-
+ +
+ CAPABILITIES +

+ 一个工作台,一支 AI 团队 +

+

+ 从编排到交付,每个环节都是流式的、可观测的、有守卫的。 +

+
+
-
- {FEATURES.map((f) => ( -
- - {f.label} - -

{f.title}

-

{f.desc}

-
- ))} -
+ +
+ {FEATURES.map((f) => ( +
+ + {f.label} + +

{f.title}

+

{f.desc}

+
+ ))} +
+
) diff --git a/web/src/pages/home/hero.tsx b/web/src/pages/home/hero.tsx index b01a23e..12ac9be 100644 --- a/web/src/pages/home/hero.tsx +++ b/web/src/pages/home/hero.tsx @@ -2,45 +2,56 @@ import { Link } from 'react-router-dom' import { Download } from 'lucide-react' import { SITE } from '@/content/site' import { TerminalDemo } from '@/components/terminal-demo' +import { Reveal } from '@/components/reveal' export function Hero() { return (
- - - {SITE.version} 已发布 · macOS / Windows - + + + + {SITE.version} 已发布 · macOS / Windows + + -

- 事件驱动的 -
- AI Agent 工作台 -

+ +

+ 事件驱动的 +
+ AI Agent 工作台 +

+
-

- 在画布上编排智能体,让多 Agent - 团队替你研究、检索、写出真正的 Word - 报告。桌面开箱即用,也可一条命令私有化部署。 -

+ +

+ 在画布上编排智能体,让多 Agent + 团队替你研究、检索、写出真正的 Word + 报告。桌面开箱即用,也可一条命令私有化部署。 +

+
-
- - 下载桌面版 - - - $ make demo - -
+ +
+ + 下载桌面版 + + + $ make demo + +
+
- + + +
) } diff --git a/web/src/pages/home/quickstart.tsx b/web/src/pages/home/quickstart.tsx index f4db57c..e2c9e93 100644 --- a/web/src/pages/home/quickstart.tsx +++ b/web/src/pages/home/quickstart.tsx @@ -1,18 +1,24 @@ import { QUICKSTART_STEPS } from '@/content/site' import { SectionLabel } from '@/components/section-label' +import { Reveal } from '@/components/reveal' export function Quickstart() { return (
-
- QUICKSTART -

- 五分钟跑起来 -

-
+ +
+ QUICKSTART +

+ 五分钟跑起来 +

+
+
-
+
             
               # 内嵌 NATS,零配置体验
@@ -43,7 +49,7 @@ export function Quickstart() {
               
             ))}
           
-        
+
) diff --git a/web/src/pages/not-found.tsx b/web/src/pages/not-found.tsx index 40624a1..41b64a8 100644 --- a/web/src/pages/not-found.tsx +++ b/web/src/pages/not-found.tsx @@ -1,6 +1,8 @@ import { Link } from 'react-router-dom' +import { usePageTitle } from '@/hooks/use-page-title' export default function NotFoundPage() { + usePageTitle('页面不存在') return (