From 35fe51427c3a409030b94869f524d176e48f1b09 Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:04:44 +0800 Subject: [PATCH] feat(website): homepage makeover --- package-lock.json | 30 ++++++ packages/website/package.json | 2 + packages/website/src/components/Footer.tsx | 10 +- packages/website/src/components/Header.tsx | 48 ++++++++-- packages/website/src/components/icons.tsx | 75 --------------- .../website/src/components/ui/bento-grid.tsx | 91 +++++++++++++++++++ .../website/src/components/ui/blur-fade.tsx | 79 ++++++++++++++++ .../src/components/ui/neon-gradient-card.tsx | 4 +- .../src/components/ui/sparkles-text.tsx | 4 +- .../website/src/i18n/locales/en-US/home.ts | 2 +- .../website/src/i18n/locales/zh-CN/home.ts | 2 +- packages/website/src/page.tsx | 91 ++++++++++++------- 12 files changed, 311 insertions(+), 127 deletions(-) delete mode 100644 packages/website/src/components/icons.tsx create mode 100644 packages/website/src/components/ui/bento-grid.tsx create mode 100644 packages/website/src/components/ui/blur-fade.tsx diff --git a/package-lock.json b/package-lock.json index 964f624..1c766f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1782,6 +1782,15 @@ } } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", @@ -7179,6 +7188,25 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-icons": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.2.0.tgz", + "integrity": "sha512-SCV4cAX8O2M2WQ3QJm+3k4QdcUIJiBiwKNViQiOwPjWbFWgypzelviW+Bp0A2ij6r/DIEZpTTDcLgD/BE4aexQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/simple-icons" + }, + { + "type": "github", + "url": "https://github.com/sponsors/simple-icons" + } + ], + "license": "CC0-1.0", + "engines": { + "node": ">=0.12.18" + } + }, "node_modules/slice-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", @@ -8169,6 +8197,7 @@ "name": "@page-agent/website", "version": "0.0.15", "dependencies": { + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", @@ -8179,6 +8208,7 @@ "motion": "^12.23.26", "next-themes": "^0.4.6", "rough-notation": "^0.5.1", + "simple-icons": "^16.2.0", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0" }, diff --git a/packages/website/package.json b/packages/website/package.json index 072c016..117a1aa 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -24,6 +24,7 @@ "wouter": "^3.8.1" }, "dependencies": { + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", @@ -34,6 +35,7 @@ "motion": "^12.23.26", "next-themes": "^0.4.6", "rough-notation": "^0.5.1", + "simple-icons": "^16.2.0", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0" } diff --git a/packages/website/src/components/Footer.tsx b/packages/website/src/components/Footer.tsx index 3b351d8..1b78952 100644 --- a/packages/website/src/components/Footer.tsx +++ b/packages/website/src/components/Footer.tsx @@ -1,4 +1,5 @@ import { useTranslation } from 'react-i18next' +import { siGithub } from 'simple-icons' export default function Footer() { const { t } = useTranslation('common') @@ -19,8 +20,13 @@ export default function Footer() { className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors duration-200" aria-label={t('footer.github_label')} > - page-agent -

+ {t('header.slogan')} -

+ @@ -51,7 +58,7 @@ export default function Header() { className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 shrink-0" aria-label={t('header.nav_docs')} > - + - + @@ -74,7 +88,7 @@ export default function Header() { href="/docs/introduction/overview" className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200" > - + {t('header.nav_docs')} - + {t('header.nav_source')} @@ -100,7 +121,7 @@ export default function Header() { aria-controls="mobile-menu" onClick={() => setMobileMenuOpen(!mobileMenuOpen)} > - {mobileMenuOpen ? : } + {mobileMenuOpen ? : } @@ -116,7 +137,7 @@ export default function Header() { className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200" onClick={() => setMobileMenuOpen(false)} > - + {t('header.nav_docs')} - + {t('header.nav_source')}
diff --git a/packages/website/src/components/icons.tsx b/packages/website/src/components/icons.tsx deleted file mode 100644 index 3848e4a..0000000 --- a/packages/website/src/components/icons.tsx +++ /dev/null @@ -1,75 +0,0 @@ -// SVG图标组件集合,用于Header等地方复用 - -interface IconProps { - className?: string - 'aria-hidden'?: boolean -} - -export function BookIcon({ className = 'w-4 h-4', ...props }: IconProps) { - return ( - - ) -} - -export function GithubIcon({ className = 'w-4 h-4', ...props }: IconProps) { - return ( - - ) -} - -export function MenuIcon({ className = 'w-6 h-6', ...props }: IconProps) { - return ( - - ) -} - -export function CloseIcon({ className = 'w-6 h-6', ...props }: IconProps) { - return ( - - ) -} diff --git a/packages/website/src/components/ui/bento-grid.tsx b/packages/website/src/components/ui/bento-grid.tsx new file mode 100644 index 0000000..9289074 --- /dev/null +++ b/packages/website/src/components/ui/bento-grid.tsx @@ -0,0 +1,91 @@ +import { ArrowRightIcon } from '@radix-ui/react-icons' +import { ComponentPropsWithoutRef, ReactNode } from 'react' + +import { Button } from '@/components/ui/button' +import { cn } from '@/lib/utils' + +interface BentoGridProps extends ComponentPropsWithoutRef<'div'> { + children: ReactNode + className?: string +} + +interface BentoCardProps extends ComponentPropsWithoutRef<'div'> { + name: string + className: string + background: ReactNode + Icon: React.ElementType + description: string + href: string + cta: string +} + +const BentoGrid = ({ children, className, ...props }: BentoGridProps) => { + return ( +
+ {children} +
+ ) +} + +const BentoCard = ({ + name, + className, + background, + Icon, + description, + href, + cta, + ...props +}: BentoCardProps) => ( +
+
{background}
+
+
+ +

{name}

+

{description}

+
+ + +
+ + + +
+
+) + +export { BentoCard, BentoGrid } diff --git a/packages/website/src/components/ui/blur-fade.tsx b/packages/website/src/components/ui/blur-fade.tsx new file mode 100644 index 0000000..13d109f --- /dev/null +++ b/packages/website/src/components/ui/blur-fade.tsx @@ -0,0 +1,79 @@ +import { + AnimatePresence, + MotionProps, + UseInViewOptions, + Variants, + motion, + useInView, +} from 'motion/react' +import { useRef } from 'react' + +type MarginType = UseInViewOptions['margin'] + +interface BlurFadeProps extends MotionProps { + children: React.ReactNode + className?: string + variant?: { + hidden: { y: number } + visible: { y: number } + } + duration?: number + delay?: number + offset?: number + direction?: 'up' | 'down' | 'left' | 'right' + inView?: boolean + inViewMargin?: MarginType + blur?: string +} + +export function BlurFade({ + children, + className, + variant, + duration = 0.4, + delay = 0, + offset = 6, + direction = 'down', + inView = false, + inViewMargin = '-50px', + blur = '6px', + ...props +}: BlurFadeProps) { + const ref = useRef(null) + const inViewResult = useInView(ref, { once: true, margin: inViewMargin }) + const isInView = !inView || inViewResult + const defaultVariants: Variants = { + hidden: { + [direction === 'left' || direction === 'right' ? 'x' : 'y']: + direction === 'right' || direction === 'down' ? -offset : offset, + opacity: 0, + filter: `blur(${blur})`, + }, + visible: { + [direction === 'left' || direction === 'right' ? 'x' : 'y']: 0, + opacity: 1, + filter: `blur(0px)`, + }, + } + const combinedVariants = variant || defaultVariants + return ( + + + {children} + + + ) +} diff --git a/packages/website/src/components/ui/neon-gradient-card.tsx b/packages/website/src/components/ui/neon-gradient-card.tsx index cf62fd7..b372761 100644 --- a/packages/website/src/components/ui/neon-gradient-card.tsx +++ b/packages/website/src/components/ui/neon-gradient-card.tsx @@ -108,7 +108,7 @@ export const NeonGradientCard: React.FC = ({ '--pseudo-element-background-image': `linear-gradient(0deg, ${neonColors.firstColor}, ${neonColors.secondColor})`, '--pseudo-element-width': `${dimensions.width + borderSize * 2}px`, '--pseudo-element-height': `${dimensions.height + borderSize * 2}px`, - '--after-blur': `${dimensions.width / 3}px`, + '--after-blur': `${dimensions.width / 6}px`, } as CSSProperties } className={cn('relative z-10 size-full rounded-[var(--border-radius)]', className)} @@ -116,7 +116,7 @@ export const NeonGradientCard: React.FC = ({ >
= ({ {sparkles.map((sparkle) => ( ))} - {children} + + {children} +
) diff --git a/packages/website/src/i18n/locales/en-US/home.ts b/packages/website/src/i18n/locales/en-US/home.ts index 3a89aeb..a5abdde 100644 --- a/packages/website/src/i18n/locales/en-US/home.ts +++ b/packages/website/src/i18n/locales/en-US/home.ts @@ -3,7 +3,7 @@ export default { badge: 'GUI Agent in your webpage', title_line1: 'The AI Operator', title_line2: 'Living in Your Web App', - subtitle_emoji: '🪄 One line of CDN', + subtitle_emoji: '🪄One line of code', subtitle_main: ' adds intelligent GUI Agents to your website.', subtitle_detail: 'Users give natural language commands, AI handles the rest.', tab_try: '🚀 Try It Now', diff --git a/packages/website/src/i18n/locales/zh-CN/home.ts b/packages/website/src/i18n/locales/zh-CN/home.ts index a94cc5a..0740c58 100644 --- a/packages/website/src/i18n/locales/zh-CN/home.ts +++ b/packages/website/src/i18n/locales/zh-CN/home.ts @@ -3,7 +3,7 @@ export default { badge: 'GUI Agent in your webpage', title_line1: '让你的 Web 应用', title_line2: '拥有 AI 操作员', - subtitle_emoji: '🪄 一行 CDN 引入', + subtitle_emoji: '🪄一行代码', subtitle_main: ',为你的网站添加 GUI Agent。', subtitle_detail: '用户/答疑机器人给出文字指示,AI 帮你操作页面。', tab_try: '🚀 立即尝试', diff --git a/packages/website/src/page.tsx b/packages/website/src/page.tsx index b232129..d48f4f1 100644 --- a/packages/website/src/page.tsx +++ b/packages/website/src/page.tsx @@ -1,4 +1,5 @@ /* eslint-disable react-dom/no-dangerously-set-innerhtml */ +import { Bot, Box, MessageSquare, PlayCircle, Shield, Sparkles, Users, Zap } from 'lucide-react' import { PageAgent } from 'page-agent' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -6,6 +7,11 @@ import { Link, useSearchParams } from 'wouter' import Footer from './components/Footer' import Header from './components/Header' +import { AnimatedGradientText } from './components/ui/animated-gradient-text' +import { Highlighter } from './components/ui/highlighter' +import { NeonGradientCard } from './components/ui/neon-gradient-card' +import { Particles } from './components/ui/particles' +import { SparklesText } from './components/ui/sparkles-text' import { CDN_CN_URL, CDN_URL } from './constants' function getInjection(useCN?: boolean) { @@ -82,19 +88,28 @@ export default function HomePage() {
- {/* Background Pattern */} + {/* Background Pattern + Particles */} +
-
+
- {t('home:hero.badge')} + + {t('home:hero.badge')} +

- - {t('home:hero.subtitle_emoji')} - + + + {t('home:hero.subtitle_emoji')} + + {t('home:hero.subtitle_main')}
{t('home:hero.subtitle_detail')}

{/* Try It Now Section - Tab Card */} -
+
-
+ {/* Tab Headers */}
)}
-
+
@@ -326,10 +347,10 @@ export default function HomePage() { role="listitem" >

{t('home:features.in_page.title')} @@ -345,10 +366,10 @@ export default function HomePage() { role="listitem" >

{t('home:features.zero_backend.title')} @@ -364,10 +385,10 @@ export default function HomePage() { role="listitem" >

{t('home:features.accessible.title')} @@ -383,10 +404,10 @@ export default function HomePage() { role="listitem" >

{t('home:features.secure_integration.title')} @@ -403,12 +424,12 @@ export default function HomePage() {
-

{t('home:use_cases.section_title')} -

+

{t('home:use_cases.section_subtitle')}

@@ -416,10 +437,10 @@ export default function HomePage() {
{/* Use Case 1 */} -
+
-
- 1 +
+
@@ -434,10 +455,10 @@ export default function HomePage() {
{/* Use Case 2 */} -
+
-
- 2 +
+

@@ -451,10 +472,10 @@ export default function HomePage() {

{/* Use Case 3 */} -
+
-
- 3 +
+

@@ -468,10 +489,10 @@ export default function HomePage() {

{/* Use Case 4 */} -
+
-
- 4 +
+