diff --git a/.gitignore b/.gitignore index 3927ebc8..51e66c21 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ yarn-error.log* .claude .taskmaster .playwright-mcp +.superpowers .agent /.shrimp /openspec diff --git a/app/[lang]/(home)/(new-home)/components/AnimatedCarouselContainer.tsx b/app/[lang]/(home)/(new-home)/components/AnimatedCarouselContainer.tsx deleted file mode 100644 index d7b3fc58..00000000 --- a/app/[lang]/(home)/(new-home)/components/AnimatedCarouselContainer.tsx +++ /dev/null @@ -1,38 +0,0 @@ -'use client'; - -import { ReactNode, useRef, memo } from 'react'; -import { useInView } from 'motion/react'; - -interface AnimatedCarouselContainerProps { - activeIndex: number; - children: ReactNode; -} - -export const AnimatedCarouselContainer = memo( - function AnimatedCarouselContainer({ - activeIndex, - children, - }: AnimatedCarouselContainerProps) { - const containerRef = useRef(null); - - // 使用 useInView 检测组件是否在视口内 - const isInView = useInView(containerRef, { - margin: '0px 0px 0px 0px', - amount: 0, - }); - - return ( -
- {/* 只在视口内时渲染内容 */} - {isInView &&
{children}
} - {/* 不在视口时显示静态内容 */} - {!isInView && ( -
{children}
- )} -
- ); - }, -); diff --git a/app/[lang]/(home)/(new-home)/components/BorderBeam.tsx b/app/[lang]/(home)/(new-home)/components/BorderBeam.tsx deleted file mode 100644 index 64fe8c21..00000000 --- a/app/[lang]/(home)/(new-home)/components/BorderBeam.tsx +++ /dev/null @@ -1,91 +0,0 @@ -'use client'; - -import { cn } from '@/lib/utils'; - -interface BorderBeamProps { - /** - * The size of the border beam. - */ - size?: number; - /** - * The duration of the border beam. - */ - duration?: number; - /** - * The delay of the border beam. - */ - delay?: number; - /** - * The color of the border beam from. - */ - colorFrom?: string; - /** - * The color of the border beam to. - */ - colorTo?: string; - /** - * The class name of the border beam. - */ - className?: string; - /** - * The style of the border beam. - */ - style?: React.CSSProperties; - /** - * Whether to reverse the animation direction. - */ - reverse?: boolean; - /** - * The border width of the beam. - */ - borderWidth?: number; -} - -export const BorderBeam = ({ - className, - size = 50, - delay = 0, - duration = 6, - colorFrom = '#ffaa40', - colorTo = '#9c40ff', - style, - reverse = false, - borderWidth = 1, -}: BorderBeamProps) => { - return ( -
-
- -
- ); -}; diff --git a/app/[lang]/(home)/(new-home)/components/CarouselCard.tsx b/app/[lang]/(home)/(new-home)/components/CarouselCard.tsx deleted file mode 100644 index 8310366a..00000000 --- a/app/[lang]/(home)/(new-home)/components/CarouselCard.tsx +++ /dev/null @@ -1,64 +0,0 @@ -'use client'; - -import { ArrowRight } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { ReactNode, memo } from 'react'; -import { useGTM } from '@/hooks/use-gtm'; - -interface CarouselCardProps { - children: ReactNode; - title: string; - description: string; - buttonText: string; - buttonLink: string; -} - -export const CarouselCard = memo(function CarouselCard({ - children, - title, - description, - buttonText, - buttonLink, -}: CarouselCardProps) { - const { trackButton } = useGTM(); - - return ( -
- - ); -}); diff --git a/app/[lang]/(home)/(new-home)/components/CountUp.tsx b/app/[lang]/(home)/(new-home)/components/CountUp.tsx deleted file mode 100644 index 754298ac..00000000 --- a/app/[lang]/(home)/(new-home)/components/CountUp.tsx +++ /dev/null @@ -1,104 +0,0 @@ -'use client'; - -import { useCallback, useEffect, useRef } from 'react'; -import { useInView } from 'react-intersection-observer'; - -interface CountUpProps { - to: number; - from?: number; - direction?: 'up' | 'down'; - delay?: number; - duration?: number; - className?: string; - startWhen?: boolean; - separator?: string; - onStart?: () => void; - onEnd?: () => void; -} - -export default function CountUp({ - to, - from = 0, - direction = 'up', - delay = 0, - duration = 2, - className = '', - startWhen = true, - separator = '', - onStart, - onEnd, -}: CountUpProps) { - const spanRef = useRef(null); - const hasAnimatedRef = useRef(false); - - const { ref: inViewRef, inView } = useInView({ - threshold: 0.1, - triggerOnce: true, - }); - - // Combine refs - const setRefs = useCallback( - (element: HTMLSpanElement | null) => { - // Update our ref - spanRef.current = element; - // Call the inView ref callback - inViewRef(element); - }, - [inViewRef], - ); - - const formatNumber = (num: number): string => { - const options: Intl.NumberFormatOptions = { - useGrouping: !!separator, - }; - - const formattedNumber = Intl.NumberFormat('en-US', options).format(num); - return separator - ? formattedNumber.replace(/,/g, separator) - : formattedNumber; - }; - - useEffect(() => { - if (inView && startWhen && !hasAnimatedRef.current && spanRef.current) { - hasAnimatedRef.current = true; - - onStart?.(); - - const timer = setTimeout(() => { - const steps = 60; // 60 FPS - const increment = (to - from) / (steps * duration); - let currentCount = from; - - const counter = setInterval(() => { - currentCount += increment; - - if (direction === 'up' ? currentCount >= to : currentCount <= to) { - // Final update - if (spanRef.current) { - spanRef.current.textContent = formatNumber(to); - } - clearInterval(counter); - onEnd?.(); - } else { - // Update DOM directly without triggering re-render - if (spanRef.current) { - spanRef.current.textContent = formatNumber( - Math.floor(currentCount), - ); - } - } - }, 1000 / steps); - - return () => clearInterval(counter); - }, delay * 1000); - - return () => clearTimeout(timer); - } - }, [inView, startWhen, to, from, direction, delay, duration, separator]); - - return ( - - {formatNumber(from)} - - ); -} diff --git a/app/[lang]/(home)/(new-home)/components/FallingTags.tsx b/app/[lang]/(home)/(new-home)/components/FallingTags.tsx deleted file mode 100644 index b0691c3b..00000000 --- a/app/[lang]/(home)/(new-home)/components/FallingTags.tsx +++ /dev/null @@ -1,443 +0,0 @@ -'use client'; - -import { useEffect, useRef } from 'react'; -import { useInView } from 'motion/react'; -import * as Matter from 'matter-js'; - -interface Tag { - text: string; -} - -const tags: Tag[] = [ - { text: '① Have an idea' }, - { text: '② Provision a VM' }, - { text: '③ Install dependencies' }, - { text: '④ Realize dependencies conflict' }, - { text: '⑤ Start over with Docker' }, - { text: '⑥ Write a complex Dockerfile' }, - { text: '⑦ Configure network ports' }, - { text: '⑧ Set up a separate database' }, - { text: '⑨ Manually configure DB connection strings' }, - { text: '⑩ Finally, deploy...' }, - { text: '...and it works on your machine, but not on the server' }, -]; - -type Phase = 'dropping' | 'staying' | 'draining'; - -interface BodyItem { - body: Matter.Body | null; - element: HTMLElement; - landed: boolean; - index: number; - rect?: DOMRect; -} - -const CONFIG = { - physics: { - gravity: 1, - restitution: 0.4, - friction: 0.7, - density: 0.001, - frictionAir: 0.02, - }, - timing: { - dropDelay: 500, - stayDuration: 3000, - drainDuration: 2500, - resetDelay: 500, - }, - rendering: { - // 不再需要 updateInterval,每帧都更新 - }, -}; - -export function FallingTags({ - resetAfterAllLanded = false, - stopEngineAfterLanded = true, -}: { - resetAfterAllLanded?: boolean; - stopEngineAfterLanded?: boolean; -}) { - const containerRef = useRef(null); - const engineRef = useRef(null); - const floorRef = useRef(null); - const bodiesRef = useRef([]); - const animationIdRef = useRef(0); - const phaseRef = useRef('dropping'); - const containerHeightRef = useRef(0); - const lastTimeRef = useRef(0); - - // 使用 motion/react 的 useInView 检测组件是否进入视口 - const inView = useInView(containerRef, { - once: true, - amount: 0.1, - }); - - useEffect(() => { - if (!inView || !containerRef.current) return; - - const container = containerRef.current; - const containerWidth = container.clientWidth; - const containerHeight = container.clientHeight; - containerHeightRef.current = containerHeight; - - // 创建引擎 - const engine = Matter.Engine.create({ - gravity: { x: 0, y: CONFIG.physics.gravity }, - enableSleeping: true, - positionIterations: 10, - velocityIterations: 8, - }); - engineRef.current = engine; - - // 创建边界 - const createBoundaries = () => { - const wallThickness = 10; - const wallStyle = { - isStatic: true, - render: { fillStyle: 'transparent' }, - }; - - const floor = Matter.Bodies.rectangle( - containerWidth / 2, - containerHeight + 5 - 96, - containerWidth, - wallThickness, - { ...wallStyle, label: 'floor' }, - ); - floorRef.current = floor; - - const leftWall = Matter.Bodies.rectangle( - -5, - containerHeight / 2, - wallThickness, - containerHeight, - { ...wallStyle, label: 'leftWall' }, - ); - - const rightWall = Matter.Bodies.rectangle( - containerWidth + 5, - containerHeight / 2, - wallThickness, - containerHeight, - { ...wallStyle, label: 'rightWall' }, - ); - - Matter.Composite.add(engine.world, [floor, leftWall, rightWall]); - }; - - createBoundaries(); - - // 不使用 Runner,改用手动 rAF 驱动物理引擎 - - // 创建 DOM 元素并初始化 bodies - const initializeBodies = () => { - bodiesRef.current = tags.map((tag, index) => { - const elem = document.createElement('span'); - // 移除 backdrop-blur-md,改用轻量样式 - elem.className = - 'absolute whitespace-nowrap rounded-full px-3 py-2 xl:px-4 xl:py-3 font-light text-base inset-shadow-bubble'; - elem.textContent = tag.text; - - // 使用内联样式设置轻量外观(渐变背景替代内阴影) - elem.style.cssText = ` - background: linear-gradient(to bottom, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.05) 15%, #141414 40%, #141414 60%, rgba(255, 255, 255, 0.05) 85%, rgba(255, 255, 255, 0.15)); - border: 1px solid rgba(255, 255, 255, 0.08); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); - will-change: transform; - contain: layout style paint; - transform: translate3d(0px, -200px, 0) rotate(0deg); - opacity: 1; - `; - - container.appendChild(elem); - const rect = elem.getBoundingClientRect(); - - return { - body: null, - element: elem, - landed: false, - index, - rect, - }; - }); - }; - - initializeBodies(); - - // 掉落元素 - const dropElements = () => { - tags.forEach((_, index) => { - setTimeout(() => { - const item = bodiesRef.current[index]; - if (!item || !engine || !item.rect) return; - - const rect = item.rect; - const initialAngle = (Math.random() - 0.5) * 0.5; - const dropX = - containerWidth / 2 + (Math.random() - 0.5) * (containerWidth * 0.6); - - const body = Matter.Bodies.rectangle( - dropX, - -80 - index * 70, - rect.width, - rect.height, - { - restitution: CONFIG.physics.restitution, - friction: CONFIG.physics.friction, - density: CONFIG.physics.density, - frictionAir: CONFIG.physics.frictionAir, - render: { fillStyle: 'transparent' }, - angle: initialAngle, - angularVelocity: (Math.random() - 0.5) * 0.1, - // 添加 chamfer 使物理形状匹配圆角胶囊外观 - chamfer: { radius: rect.height / 2 }, - }, - ); - - Matter.Composite.add(engine.world, body); - item.body = body; - }, index * CONFIG.timing.dropDelay); - }); - }; - - dropElements(); - - const startUpdateLoop = () => { - let localLandedCount = 0; - lastTimeRef.current = performance.now(); - - const update = (now: number) => { - // 计算时间差并限制最大步长,避免大跳跃 - const delta = now - lastTimeRef.current; - lastTimeRef.current = now; - const clampedDelta = Math.min(delta, 33); // 最大 33ms (约 30fps) - - // 手动推进物理引擎 - if (engineRef.current) { - Matter.Engine.update(engineRef.current, clampedDelta); - } - - // 同步 DOM - 批量更新减少重排 - bodiesRef.current.forEach((item) => { - if (!item || !item.body || !item.element) return; - - const { x, y } = item.body.position; - const angle = item.body.angle; - - // 物理引擎的 x 坐标就是相对于容器的绝对位置 - // 需要减去元素自身宽度的一半来居中 - const offsetX = x - (item.rect?.width || 0) / 2; - const offsetY = y; - - // 添加淡出效果 - if (phaseRef.current === 'draining') { - const fadeStart = containerHeightRef.current * 0.8; - if (y > fadeStart) { - const opacity = Math.max( - 0, - 1 - (y - fadeStart) / (containerHeightRef.current * 0.3), - ); - // 使用 cssText 一次性更新多个属性,减少样式重算 - item.element.style.cssText = ` - background: linear-gradient(to bottom, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.05) 15%, #141414 40%, #141414 60%, rgba(255, 255, 255, 0.05) 85%, rgba(255, 255, 255, 0.15)); - border: 1px solid rgba(255, 255, 255, 0.08); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); - will-change: transform; - contain: layout style paint; - transform: translate3d(${offsetX}px, ${offsetY}px, 0) rotate(${angle}rad); - opacity: ${opacity}; - `; - } else { - item.element.style.transform = `translate3d(${offsetX}px, ${offsetY}px, 0) rotate(${angle}rad)`; - } - } else { - item.element.style.transform = `translate3d(${offsetX}px, ${offsetY}px, 0) rotate(${angle}rad)`; - } - - // 检测着陆 - if (!item.landed && item.body.isSleeping) { - item.landed = true; - localLandedCount++; - - if (localLandedCount === tags.length) { - // 所有元素都已着陆 - if (stopEngineAfterLanded) { - // 停止更新循环以节省性能 - if (animationIdRef.current) { - cancelAnimationFrame(animationIdRef.current); - animationIdRef.current = 0; - } - } - - if (resetAfterAllLanded) { - onAllLanded(); - } - } - } - }); - - // 继续下一帧(除非已停止) - if (animationIdRef.current !== 0) { - animationIdRef.current = requestAnimationFrame(update); - } - }; - - animationIdRef.current = requestAnimationFrame(update); - }; - - startUpdateLoop(); - - // 全部着陆后 - const onAllLanded = () => { - phaseRef.current = 'staying'; - setTimeout(() => { - removeFloor(); - }, CONFIG.timing.stayDuration); - }; - - // 移除地板 - const removeFloor = () => { - if (!engine || !floorRef.current) return; - phaseRef.current = 'draining'; - - // 重新启动更新循环(如果之前停止了) - if (animationIdRef.current === 0) { - lastTimeRef.current = performance.now(); - const update = (now: number) => { - const delta = now - lastTimeRef.current; - lastTimeRef.current = now; - const clampedDelta = Math.min(delta, 33); - - if (engineRef.current) { - Matter.Engine.update(engineRef.current, clampedDelta); - } - - bodiesRef.current.forEach((item) => { - if (!item || !item.body || !item.element) return; - - const { x, y } = item.body.position; - const angle = item.body.angle; - const offsetX = x - (item.rect?.width || 0) / 2; - const offsetY = y; - - if (phaseRef.current === 'draining') { - const fadeStart = containerHeightRef.current * 0.8; - if (y > fadeStart) { - const opacity = Math.max( - 0, - 1 - (y - fadeStart) / (containerHeightRef.current * 0.3), - ); - item.element.style.cssText = ` - background: linear-gradient(to bottom, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.05) 15%, #141414 40%, #141414 60%, rgba(255, 255, 255, 0.05) 85%, rgba(255, 255, 255, 0.15)); - border: 1px solid rgba(255, 255, 255, 0.08); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); - will-change: transform; - contain: layout style paint; - transform: translate3d(${offsetX}px, ${offsetY}px, 0) rotate(${angle}rad); - opacity: ${opacity}; - `; - } else { - item.element.style.transform = `translate3d(${offsetX}px, ${offsetY}px, 0) rotate(${angle}rad)`; - } - } else { - item.element.style.transform = `translate3d(${offsetX}px, ${offsetY}px, 0) rotate(${angle}rad)`; - } - }); - - if (animationIdRef.current !== 0) { - animationIdRef.current = requestAnimationFrame(update); - } - }; - animationIdRef.current = requestAnimationFrame(update); - } - - Matter.Composite.remove(engine.world, floorRef.current); - const leftWall = engine.world.bodies.find( - (body) => body.label === 'leftWall', - ); - const rightWall = engine.world.bodies.find( - (body) => body.label === 'rightWall', - ); - if (leftWall) Matter.Composite.remove(engine.world, leftWall); - if (rightWall) Matter.Composite.remove(engine.world, rightWall); - - bodiesRef.current.forEach((item) => { - if (item && item.body) { - Matter.Body.setStatic(item.body, false); - Matter.Body.setAngularVelocity( - item.body, - (Math.random() - 0.5) * 0.2, - ); - Matter.Sleeping.set(item.body, false); - } - }); - - setTimeout(() => { - resetCycle(); - }, CONFIG.timing.drainDuration); - }; - - // 重置循环 - const resetCycle = () => { - if (animationIdRef.current) { - cancelAnimationFrame(animationIdRef.current); - animationIdRef.current = 0; - } - - const currentBodies = Matter.Composite.allBodies(engine.world).filter( - (body) => !body.isStatic, - ); - Matter.Composite.remove(engine.world, currentBodies); - - phaseRef.current = 'dropping'; - - // 重置元素位置和状态 - bodiesRef.current.forEach((item) => { - item.body = null; - item.landed = false; - if (item.element) { - item.element.style.cssText = ` - background: linear-gradient(to bottom, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.05) 15%, #141414 40%, #141414 60%, rgba(255, 255, 255, 0.05) 85%, rgba(255, 255, 255, 0.15)); - border: 1px solid rgba(255, 255, 255, 0.08); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); - will-change: transform; - contain: layout style paint; - transform: translate3d(0px, -200px, 0) rotate(0deg); - opacity: 1; - `; - } - }); - - setTimeout(() => { - createBoundaries(); - dropElements(); - startUpdateLoop(); - }, CONFIG.timing.resetDelay); - }; - - // 清理函数 - return () => { - if (animationIdRef.current) { - cancelAnimationFrame(animationIdRef.current); - } - if (engineRef.current) { - Matter.World.clear(engineRef.current.world, false); - Matter.Engine.clear(engineRef.current); - } - // 清理 DOM 元素 - bodiesRef.current.forEach((item) => { - if (item.element && item.element.parentNode) { - item.element.parentNode.removeChild(item.element); - } - }); - }; - }, [inView, resetAfterAllLanded, stopEngineAfterLanded]); - - return ( -
- ); -} diff --git a/app/[lang]/(home)/(new-home)/components/Glare.tsx b/app/[lang]/(home)/(new-home)/components/Glare.tsx deleted file mode 100644 index 3a2d2d0f..00000000 --- a/app/[lang]/(home)/(new-home)/components/Glare.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; - -export function Glare( - props: React.DetailedHTMLProps< - React.SVGAttributes, - SVGSVGElement - >, -) { - const gradientIdA = React.useId(); - const gradientIdB = React.useId(); - - return ( - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/app/[lang]/(home)/(new-home)/components/GradientWave.tsx b/app/[lang]/(home)/(new-home)/components/GradientWave.tsx deleted file mode 100644 index eaadef2a..00000000 --- a/app/[lang]/(home)/(new-home)/components/GradientWave.tsx +++ /dev/null @@ -1,141 +0,0 @@ -'use client'; - -import { useRef, useEffect } from 'react'; -import { MotionValue } from 'framer-motion'; - -interface GradientWaveProps { - progress: MotionValue; // 0-1 -} - -export function GradientWave({ progress }: GradientWaveProps) { - const canvasRef = useRef(null); - const rafRef = useRef(); - const isInViewRef = useRef(true); - const widthRef = useRef(0); - const heightRef = useRef(0); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext('2d', { alpha: true }); - if (!ctx) return; - - const lineCount = 64; - const dpr = Math.min(window.devicePixelRatio || 1, 2); // 限制最大 DPR 为 2 - - // 设置 canvas 尺寸 - const updateSize = () => { - const rect = canvas.getBoundingClientRect(); - widthRef.current = rect.width; - heightRef.current = rect.height; - canvas.width = rect.width * dpr; - canvas.height = rect.height * dpr; - // 重置变换矩阵后按 DPR 设定缩放,避免重复 scale 累积 - // 使用 setTransform 确保每次 resize 后状态正确 - ctx.setTransform(dpr, 0, 0, dpr, 0, 0); - }; - - updateSize(); - - // 创建渐变缓存 - const createGradient = (x: number, height: number) => { - const gradient = ctx.createLinearGradient(x, height, x, 0); - gradient.addColorStop(0, '#146DFF'); - gradient.addColorStop(1, '#ffffff'); - return gradient; - }; - - // 渲染函数 - const render = () => { - if (!isInViewRef.current) return; - - const width = widthRef.current; - const height = heightRef.current; - const progressValue = progress.get(); - - ctx.clearRect(0, 0, width, height); - - for (let i = 0; i < lineCount; i++) { - const lineProgress = i / lineCount; - const x = (i / (lineCount - 1)) * width; - - // 计算高度 - const distanceFromProgress = Math.abs(lineProgress - progressValue); - const heightFactor = Math.exp(-distanceFromProgress * 12); - const lineHeight = (20 + heightFactor * 70) * (height / 100); - - // 计算透明度 - const progressLineIndex = Math.round(progressValue * (lineCount - 1)); - const distanceInLines = Math.abs(i - progressLineIndex); - let opacity: number; - - if (distanceInLines <= 5) { - const fadeFactor = 1 - (distanceInLines / 5) * 0.4; - opacity = (0.6 + heightFactor * 0.3) * fadeFactor; - } else { - opacity = (0.3 + heightFactor * 0.3) * 0.7; - } - - // 绘制线条 - ctx.beginPath(); - ctx.moveTo(x, height); - ctx.lineTo(x, height - lineHeight); - ctx.strokeStyle = createGradient(x, lineHeight); - ctx.lineWidth = 2; - ctx.lineCap = 'round'; - ctx.globalAlpha = opacity; - ctx.stroke(); - } - - ctx.globalAlpha = 1; - rafRef.current = requestAnimationFrame(render); - }; - - // 监听 progress 变化 - const unsubscribe = progress.on('change', () => { - if (!rafRef.current && isInViewRef.current) { - rafRef.current = requestAnimationFrame(render); - } - }); - - // Intersection Observer - const observer = new IntersectionObserver( - ([entry]) => { - isInViewRef.current = entry.isIntersecting; - if (entry.isIntersecting) { - rafRef.current = requestAnimationFrame(render); - } else { - if (rafRef.current) { - cancelAnimationFrame(rafRef.current); - rafRef.current = undefined; - } - } - }, - { threshold: 0 }, - ); - - observer.observe(canvas); - - // 初始渲染 - render(); - - // Resize observer - const resizeObserver = new ResizeObserver(() => { - updateSize(); - render(); - }); - resizeObserver.observe(canvas); - - return () => { - unsubscribe(); - observer.disconnect(); - resizeObserver.disconnect(); - if (rafRef.current) { - cancelAnimationFrame(rafRef.current); - } - }; - }, [progress]); - - return ; -} diff --git a/app/[lang]/(home)/(new-home)/components/HeroBackground.tsx b/app/[lang]/(home)/(new-home)/components/HeroBackground.tsx deleted file mode 100644 index a3b024c3..00000000 --- a/app/[lang]/(home)/(new-home)/components/HeroBackground.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import Image from 'next/image'; -import HeroGrid from '@/assets/hero-grid.svg'; -import HeroGridAnimation from '@/assets/hero-grid-animation.svg'; - -export function HeroBackground() { - return ( - <> - {/* 背景网格 - 放在section底部 */} - - -
- - ); -} diff --git a/app/[lang]/(home)/(new-home)/components/HeroTitle.tsx b/app/[lang]/(home)/(new-home)/components/HeroTitle.tsx deleted file mode 100644 index 727b11e1..00000000 --- a/app/[lang]/(home)/(new-home)/components/HeroTitle.tsx +++ /dev/null @@ -1,69 +0,0 @@ -'use client'; - -import { GradientText } from '@/new-components/GradientText'; -import { FramedText } from '../../../../../new-components/FramedText'; -import { RotatingWords } from './RotatingWords'; -import { Star, ShieldCheck } from 'lucide-react'; -import { GithubIcon } from '@/components/ui/icons'; - -export function HeroTitle({ isInView }: { isInView: boolean }) { - return ( -
- {/* 顶部标签 - 社会证明区 */} -
- {/* GitHub Stars */} - - - - - 16K+ Stars - - - -
- - {/* Source Available Badge */} -
- - 100% Source Available -
-
- - {/* SEO H1 - Hidden visually but readable by search engines and screen readers */} -

- Sealos Cloud Platform: Deploy AI Agents, Dev Runtimes, Web Apps, and - Databases with Just a Prompt -

- - {/* Visual Title - Decorative, hidden from assistive tech */} - - -

- No YAML. No Dockerfile. No CI/CD.{' '} - Describe what you need in plain English and deploy to production in - seconds—powered by Kubernetes, without the complexity. -

-
- ); -} diff --git a/app/[lang]/(home)/(new-home)/components/ProgressIndicator.tsx b/app/[lang]/(home)/(new-home)/components/ProgressIndicator.tsx deleted file mode 100644 index c4120f4e..00000000 --- a/app/[lang]/(home)/(new-home)/components/ProgressIndicator.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; - -export function ProgressIndicator( - props: React.DetailedHTMLProps< - React.SVGAttributes, - SVGSVGElement - >, -) { - const gradientId = React.useId(); - - return ( - - - - - - - - - - ); -} diff --git a/app/[lang]/(home)/(new-home)/components/PromptInput.tsx b/app/[lang]/(home)/(new-home)/components/PromptInput.tsx deleted file mode 100644 index 9fb1d7b3..00000000 --- a/app/[lang]/(home)/(new-home)/components/PromptInput.tsx +++ /dev/null @@ -1,482 +0,0 @@ -'use client'; - -import { Button } from '@/components/ui/button'; -import { Textarea } from '@/components/ui/textarea'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { ArrowUp, ChevronRight, Bot, Database, Code } from 'lucide-react'; -import { Glare } from './Glare'; -import React, { - ReactNode, - useState, - useEffect, - memo, - useCallback, -} from 'react'; -import Image from 'next/image'; -import { useInView } from 'framer-motion'; -import { useTypewriterEffect } from '@/hooks/useTypewriterEffect'; -// AI Agent icons -import DifyIcon from '@/assets/aiagent-appicons/dify.svg'; -import FastGPTIcon from '@/assets/aiagent-appicons/fastgpt.svg'; -import LobechatIcon from '@/assets/aiagent-appicons/lobechat.svg'; -import N8NIcon from '@/assets/aiagent-appicons/n8n.svg'; -// Database icons -import KafkaIcon from '@/assets/db-appicons/kafkaicon.svg'; -import MilvusIcon from '@/assets/db-appicons/milvus.svg'; -import MongoIcon from '@/assets/db-appicons/mongoicon.svg'; -import MysqlIcon from '@/assets/db-appicons/mysqlicon.svg'; -import PgIcon from '@/assets/db-appicons/pgicon.svg'; -import RedisIcon from '@/assets/db-appicons/redisicon.svg'; -// Dev Runtime icons -import AstroIcon from '@/assets/stacks-appicons/astro.svg'; -import DjangoIcon from '@/assets/stacks-appicons/django.svg'; -import FlaskIcon from '@/assets/stacks-appicons/flask.svg'; -import GolangIcon from '@/assets/stacks-appicons/golang.svg'; -import JavaIcon from '@/assets/stacks-appicons/java.svg'; -import NextjsIcon from '@/assets/stacks-appicons/nextjs.svg'; -import PhpIcon from '@/assets/stacks-appicons/php.svg'; -import PythonIcon from '@/assets/stacks-appicons/python.svg'; -import ReactIcon from '@/assets/stacks-appicons/react.svg'; -import RustIcon from '@/assets/stacks-appicons/rust.svg'; -import SpringbootIcon from '@/assets/stacks-appicons/springboot.svg'; - -import { useGTM } from '@/hooks/use-gtm'; -import { useAuthRedirect } from '@/hooks/use-auth-redirect'; -import { getOpenBrainParam } from '@/lib/utils/brain'; - -interface PromptOption { - icon: ReactNode; - name: string; - prompt: string; -} - -type CategoryConfig = { - name: string; - icon?: ReactNode; -} & ( - | { - type: 'list'; - prompts: PromptOption[]; - } - | { - type: 'single'; - prompt: string; - } -); - -// 预制的 prompt 数据配置 -const PROMPT_CATEGORIES: CategoryConfig[] = [ - { - type: 'single', - name: 'OpenClaw', - prompt: 'Give me OpenClaw', - }, - { - type: 'single', - name: 'Claude Code', - prompt: 'Give me a cloud dev environment with claude code.', - }, - { - type: 'single', - name: 'Build full-stack application', - prompt: - 'I want to create a full-stack application using Next.js and database.', - }, - { - type: 'single', - name: 'Deploy N8N', - prompt: 'I need to deploy n8n from an app store with queue mode.', - }, - { - type: 'single', - name: 'Build Django application', - prompt: 'I want to build a Python Django web application.', - }, - { - type: 'list', - name: 'AI Agent', - icon: , - prompts: [ - { - icon: N8N, - name: 'N8N', - prompt: 'I want to deploy N8N from app store.', - }, - { - icon: Dify, - name: 'Dify', - prompt: 'I want to deploy Dify from app store.', - }, - { - icon: FastGPT, - name: 'FastGPT', - prompt: 'I want to deploy FastGPT from app store.', - }, - { - icon: ( - Lobe Chat - ), - name: 'Lobe Chat', - prompt: 'I want to deploy Lobe Chat from app store.', - }, - ], - }, - { - type: 'list', - name: 'Database', - icon: , - prompts: [ - { - icon: PostgreSQL, - name: 'PostgreSQL', - prompt: 'I want to deploy only PostgreSQL.', - }, - { - icon: MongoDB, - name: 'MongoDB', - prompt: 'I want to deploy only MongoDB.', - }, - { - icon: MySQL, - name: 'MySQL', - prompt: 'I want to deploy only MySQL.', - }, - { - icon: Redis, - name: 'Redis', - prompt: 'I want to deploy only Redis.', - }, - { - icon: Kafka, - name: 'Kafka', - prompt: 'I want to deploy only Kafka.', - }, - { - icon: Milvus, - name: 'Milvus', - prompt: 'I want to deploy only Milvus.', - }, - ], - }, - { - type: 'list', - name: 'Dev Runtime', - icon: , - prompts: [ - { - icon: Next.js, - name: 'Next.js', - prompt: 'I want to build an app using Next.js devbox runtime.', - }, - { - icon: React, - name: 'React', - prompt: 'I want to build an app using React devbox runtime.', - }, - { - icon: Astro, - name: 'Astro', - prompt: 'I want to build an app using Astro devbox runtime.', - }, - { - icon: Django, - name: 'Django', - prompt: 'I want to build an app using Django devbox runtime.', - }, - { - icon: Flask, - name: 'Flask', - prompt: 'I want to build an app using Flask devbox runtime.', - }, - { - icon: ( - Spring Boot - ), - name: 'Spring Boot', - prompt: 'I want to build an app using Spring Boot devbox runtime.', - }, - { - icon: Python, - name: 'Python', - prompt: 'I want to build an app using python devbox runtime.', - }, - { - icon: Go, - name: 'Go', - prompt: 'I want to build an app using golang devbox runtime.', - }, - { - icon: PHP, - name: 'PHP', - prompt: 'I want to build an app using PHP devbox runtime.', - }, - { - icon: Java, - name: 'Java', - prompt: 'I want to build an app using Java devbox runtime.', - }, - { - icon: Rust, - name: 'Rust', - prompt: 'I want to build an app using Rust devbox runtime.', - }, - ], - }, -]; - -// Isolated typewriter component with its own hook -// This component manages its own state and only re-renders itself -const TypewriterOverlay = memo( - ({ - isActive, - language, - isInView, - }: { - isActive: boolean; - language: string; - isInView: boolean; - }) => { - const { currentText } = useTypewriterEffect(isActive && isInView, language); - - if (!isActive) return null; - - return ( - - ); - }, -); -TypewriterOverlay.displayName = 'TypewriterOverlay'; - -// Memoized prompt categories component -const PromptCategories = memo( - ({ onPromptSelect }: { onPromptSelect: (prompt: string) => void }) => ( -
-
- Try instant deploys — click to - auto-fill: -
- -
- {PROMPT_CATEGORIES.map((category) => ( - - {category.type === 'list' && ( - - - - - - {category.prompts.map((prompt) => ( - onPromptSelect(prompt.prompt)} - className="cursor-pointer" - aria-label={'Predefined prompt: ' + category.name} - aria-description="Click to fill this prompt" - > - {prompt.icon} - {prompt.name} - - ))} - - - )} - - {category.type === 'single' && ( - - )} - - ))} -
-
- ), -); -PromptCategories.displayName = 'PromptCategories'; - -// Memoized glare effect component -const GlareEffect = memo(() => { - return ( - <> -