diff --git a/--full-page b/--full-page new file mode 100644 index 0000000..cd1e6ab Binary files /dev/null and b/--full-page differ diff --git a/src/app/(marketing)/services/page.tsx b/src/app/(marketing)/services/page.tsx index dc97db4..a6bc49a 100644 --- a/src/app/(marketing)/services/page.tsx +++ b/src/app/(marketing)/services/page.tsx @@ -91,7 +91,7 @@ export default function ServicesPage() { asChild > - 立即咨询 + 获取服务报价 diff --git a/src/app/(marketing)/solutions/[id]/client.tsx b/src/app/(marketing)/solutions/[id]/client.tsx index 444abd4..8e98b89 100644 --- a/src/app/(marketing)/solutions/[id]/client.tsx +++ b/src/app/(marketing)/solutions/[id]/client.tsx @@ -138,7 +138,7 @@ export function SolutionDetailClient({ solution, relatedProducts }: SolutionDeta diff --git a/src/app/globals.css b/src/app/globals.css index 49fb1df..5afeae1 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -65,12 +65,12 @@ --color-accent-cyan-rgb: 8, 145, 178; --color-footer-bg: #1C1C1C; - --color-cta-bg: #FAFAFA; + --color-cta-bg: #1a0a0f; --color-hero-dark-end: #1C1C1C; - --color-footer-text: #A0A0A0; - --color-footer-text-muted: #666666; - --color-footer-text-dim: #999999; - --color-footer-text-link: #E0E0E0; + --color-footer-text: #B0B0B0; + --color-footer-text-muted: #8C8C8C; + --color-footer-text-dim: #A0A0A0; + --color-footer-text-link: #E5E5E5; --color-footer-border: #333333; --color-challenge-isolation: #FEF2F4; @@ -149,8 +149,8 @@ --color-primary-lighter: #262626; --color-primary-rgb: 229, 229, 229; - --color-brand-primary: #E04A68; - --color-brand-primary-hover: #F06880; + --color-brand-primary: #D43650; + --color-brand-primary-hover: #E04A68; --color-brand-primary-light: #C41E3A; --color-brand-primary-bg: rgba(196, 30, 58, 0.15); @@ -185,14 +185,14 @@ --color-warning-bg: rgba(217, 119, 6, 0.15); --color-info: #8C8C8C; --color-info-bg: #1A1A1A; - --color-error: #E04A68; + --color-error: #D43650; --color-error-bg: rgba(196, 30, 58, 0.15); --color-accent-blue: #3B82F6; --color-accent-purple: #8B5CF6; --color-accent-cyan: #06B6D4; - --color-brand-primary-rgb: 224, 74, 104; + --color-brand-primary-rgb: 212, 54, 80; --color-warning-rgb: 245, 158, 11; --color-success-rgb: 34, 197, 94; --color-accent-blue-rgb: 59, 130, 246; @@ -355,7 +355,7 @@ @layer utilities { .container-wide { width: 100%; - max-width: 1200px; + max-width: 1280px; margin-left: auto; margin-right: auto; padding-left: var(--spacing-lg); @@ -629,3 +629,33 @@ body { width: 100%; } } + +@keyframes ripple { + 0% { + transform: scale(0); + opacity: 0.5; + } + 100% { + transform: scale(4); + opacity: 0; + } +} + +.animate-ripple { + animation: ripple 0.6s ease-out forwards; +} + +@keyframes skeletonPulse { + 0%, 100% { + background-color: var(--color-skeleton-bg); + opacity: 1; + } + 50% { + background-color: color-mix(in srgb, var(--color-brand-primary) 8%, var(--color-skeleton-bg)); + opacity: 0.8; + } +} + +.skeleton-brand { + animation: skeletonPulse 2s ease-in-out infinite; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0b6e391..16c4c32 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,16 +4,17 @@ import { Ma_Shan_Zheng, Noto_Sans_SC } from "next/font/google"; import "./globals.css"; import { Suspense } from "react"; import { ThemeProvider } from "@/contexts/theme-context"; -import { GoogleAnalytics } from "@/components/analytics/GoogleAnalytics"; +import { GoogleAnalyticsWrapper } from "@/components/analytics/GoogleAnalyticsWrapper"; import { CookieConsent } from "@/components/analytics/CookieConsent"; import { PerformanceTracker } from "@/components/analytics/PerformanceTracker"; import { OutboundLinkTracker } from "@/components/analytics/OutboundLinkTracker"; import { ScrollDepthTracker } from "@/components/analytics/ScrollDepthTracker"; -import { OrganizationSchema, WebsiteSchema } from "@/components/seo/structured-data"; +import { OrganizationSchema, WebsiteSchema, LocalBusinessSchema } from "@/components/seo/structured-data"; import { MobileTabBar } from "@/components/layout/mobile-tab-bar"; import { ErrorBoundary } from "@/components/ui/error-boundary"; import { ScrollProgress } from "@/components/ui/scroll-progress"; import { BackToTop } from "@/components/ui/back-to-top"; +import { ClientLayout } from "@/components/layout/client-layout"; const geistSans = localFont({ src: "./fonts/geist-sans.woff2", @@ -133,8 +134,11 @@ export default function RootLayout({ + + + - + - {children} + + {children} + diff --git a/src/components/analytics/GoogleAnalytics.tsx b/src/components/analytics/GoogleAnalytics.tsx index 09d9099..73c473a 100644 --- a/src/components/analytics/GoogleAnalytics.tsx +++ b/src/components/analytics/GoogleAnalytics.tsx @@ -2,16 +2,28 @@ import Script from 'next/script'; import { usePathname, useSearchParams } from 'next/navigation'; -import { useEffect, Suspense } from 'react'; +import { useEffect, Suspense, useSyncExternalStore } from 'react'; const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || ''; +function useIsMounted() { + return useSyncExternalStore( + (callback) => { + window.addEventListener('resize', callback); + return () => window.removeEventListener('resize', callback); + }, + () => true, + () => false + ); +} + function GoogleAnalyticsContent() { const pathname = usePathname(); const searchParams = useSearchParams(); + const mounted = useIsMounted(); useEffect(() => { - if (!GA_MEASUREMENT_ID || typeof window === 'undefined') {return;} + if (!GA_MEASUREMENT_ID || !mounted || typeof window === 'undefined') {return;} const url = pathname + (searchParams.toString() ? `?${searchParams.toString()}` : ''); @@ -22,9 +34,9 @@ function GoogleAnalyticsContent() { page_location: window.location.origin + url, }); } - }, [pathname, searchParams]); + }, [pathname, searchParams, mounted]); - if (!GA_MEASUREMENT_ID) {return null;} + if (!GA_MEASUREMENT_ID || !mounted) {return null;} return ( <> @@ -61,11 +73,13 @@ function GoogleAnalyticsContent() { } export function GoogleAnalytics() { - if (!GA_MEASUREMENT_ID) {return null;} + const mounted = useIsMounted(); + + if (!GA_MEASUREMENT_ID || !mounted) {return null;} return ( ); -} +} \ No newline at end of file diff --git a/src/components/analytics/GoogleAnalyticsWrapper.tsx b/src/components/analytics/GoogleAnalyticsWrapper.tsx new file mode 100644 index 0000000..e99489f --- /dev/null +++ b/src/components/analytics/GoogleAnalyticsWrapper.tsx @@ -0,0 +1,12 @@ +'use client'; + +import dynamic from 'next/dynamic'; + +const GoogleAnalytics = dynamic( + () => import('./GoogleAnalytics').then((mod) => mod.GoogleAnalytics), + { ssr: false } +); + +export function GoogleAnalyticsWrapper() { + return ; +} \ No newline at end of file diff --git a/src/components/layout/client-layout.tsx b/src/components/layout/client-layout.tsx new file mode 100644 index 0000000..a2895e1 --- /dev/null +++ b/src/components/layout/client-layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { ReactNode } from 'react'; +import { PageTransition } from '@/components/ui/page-transition'; + +interface ClientLayoutProps { + children: ReactNode; +} + +export function ClientLayout({ children }: ClientLayoutProps) { + return ( + + {children} + + ); +} diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index 36dac06..144e4d0 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -4,7 +4,7 @@ import { Suspense, useState, useEffect, useCallback } from 'react'; import { StaticLink } from '@/components/ui/static-link'; import Image from 'next/image'; import { usePathname } from 'next/navigation'; -import { Menu, X } from 'lucide-react'; +import { Menu, X, MessageCircle } from 'lucide-react'; import { AnimatePresence, motion } from 'framer-motion'; import { Button } from '@/components/ui/button'; import { ThemeToggle } from '@/components/ui/theme-toggle'; @@ -75,25 +75,25 @@ function HeaderContent() { return ( <> -
- - {COMPANY_INFO.name} {item.label} - - + @@ -176,7 +187,7 @@ function HeaderContent() { {isOpen && ( - -
setIsOpen(false)} aria-hidden="true" /> - setIsOpen(false)}> - 立即咨询 + + 咨询专家
diff --git a/src/components/sections/challenge-section.tsx b/src/components/sections/challenge-section.tsx index dd9669d..07ba369 100644 --- a/src/components/sections/challenge-section.tsx +++ b/src/components/sections/challenge-section.tsx @@ -32,7 +32,7 @@ export function ChallengeSection() { const shouldReduceMotion = useReducedMotion(); return ( -
+
{CHALLENGES.map((challenge, index) => ( - + initial={shouldReduceMotion ? {} : { opacity: 0, x: index === 0 ? -32 : index === 1 ? 0 : 32 }} + whileInView={{ opacity: 1, x: 0 }} + viewport={{ once: true, margin: '-80px' }} + transition={{ duration: 0.55, delay: index * 0.12, ease: [0.16, 1, 0.3, 1] }} + > + + ))}
diff --git a/src/components/sections/cta-section.tsx b/src/components/sections/cta-section.tsx index 29d72dc..6cd8e6f 100644 --- a/src/components/sections/cta-section.tsx +++ b/src/components/sections/cta-section.tsx @@ -1,9 +1,10 @@ 'use client'; -import { motion } from 'framer-motion'; +import { useRef, useState } from 'react'; +import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion'; import { StaticLink } from '@/components/ui/static-link'; import { Button } from '@/components/ui/button'; -import { ArrowRight } from 'lucide-react'; +import { ArrowRight, Sparkles } from 'lucide-react'; import { COMPANY_INFO } from '@/lib/constants'; import { useReducedMotion } from '@/hooks/use-reduced-motion'; @@ -19,46 +20,158 @@ interface CTASectionProps { export function CTASection({ title = '开启您的数字化转型之旅', description = `与${COMPANY_INFO.shortName}一起,让技术成为您业务增长的核心引擎`, - primaryLabel = '立即咨询', + primaryLabel = '开启数字化转型之旅', primaryHref = '/contact', secondaryLabel = '了解方案', secondaryHref = '/solutions', }: CTASectionProps) { const shouldReduceMotion = useReducedMotion(); + const containerRef = useRef(null); + const mouseX = useMotionValue(0); + const mouseY = useMotionValue(0); + const [isHovered, setIsHovered] = useState(false); + + const springX = useSpring(mouseX, { stiffness: 80, damping: 20 }); + const springY = useSpring(mouseY, { stiffness: 80, damping: 20 }); + + const glowX = useTransform(springX, [0, 1], ['-10%', '110%']); + const glowY = useTransform(springY, [0, 1], ['-10%', '110%']); + + function handleMouseMove(e: React.MouseEvent) { + if (!containerRef.current || shouldReduceMotion) {return;} + const rect = containerRef.current.getBoundingClientRect(); + mouseX.set((e.clientX - rect.left) / rect.width); + mouseY.set((e.clientY - rect.top) / rect.height); + } return ( -
-
+
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + className="relative py-24 md:py-32 overflow-hidden" + style={{ backgroundColor: 'var(--color-cta-bg)' }} + > +
+ + + {!shouldReduceMotion && ( + <> + + + + + )} + +
+ +
+
+
-

+ + + 开始合作 + + +

{title}

-

+

{description}

- -
diff --git a/src/components/sections/hero-section-v2.tsx b/src/components/sections/hero-section-v2.tsx index 9ac0a60..33f4c88 100644 --- a/src/components/sections/hero-section-v2.tsx +++ b/src/components/sections/hero-section-v2.tsx @@ -4,6 +4,7 @@ import { useCallback, useRef, useState } from 'react'; import { motion } from 'framer-motion'; import { StaticLink } from '@/components/ui/static-link'; import { Button } from '@/components/ui/button'; +import { HeroInkBackground } from '@/components/ui/hero-ink-background'; import { COMPANY_INFO } from '@/lib/constants'; import { ArrowRight, MessageSquare, Search, Rocket, Handshake } from 'lucide-react'; import { useReducedMotion } from '@/hooks/use-reduced-motion'; @@ -51,13 +52,7 @@ export function HeroSectionV2() { aria-labelledby="hero-heading" className="relative min-h-screen flex flex-col justify-center overflow-hidden bg-[var(--color-bg-primary)]" > -
+
@@ -102,7 +97,7 @@ export function HeroSectionV2() { > diff --git a/src/components/sections/product-matrix-section.tsx b/src/components/sections/product-matrix-section.tsx index 542941e..f7f26e3 100644 --- a/src/components/sections/product-matrix-section.tsx +++ b/src/components/sections/product-matrix-section.tsx @@ -9,7 +9,7 @@ export function ProductMatrixSection() { const shouldReduceMotion = useReducedMotion(); return ( -
+
{PRODUCTS.map((product, index) => ( - + initial={shouldReduceMotion ? {} : { opacity: 0, y: 28 }} + whileInView={{ opacity: 1, y: 0 }} + viewport={{ once: true, margin: '-60px' }} + transition={{ duration: 0.5, delay: index * 0.08, ease: [0.16, 1, 0.3, 1] }} + > + + ))}
diff --git a/src/components/sections/social-proof-section.tsx b/src/components/sections/social-proof-section.tsx index cd274ae..3bb3bab 100644 --- a/src/components/sections/social-proof-section.tsx +++ b/src/components/sections/social-proof-section.tsx @@ -31,7 +31,7 @@ export function SocialProofSection() { const shouldReduceMotion = useReducedMotion(); return ( -
+
diff --git a/src/components/seo/structured-data.tsx b/src/components/seo/structured-data.tsx index 8cb391e..9a52c9c 100644 --- a/src/components/seo/structured-data.tsx +++ b/src/components/seo/structured-data.tsx @@ -99,3 +99,66 @@ export function BreadcrumbSchema({ items }: { items: BreadcrumbItem[] }) { /> ); } + +export function LocalBusinessSchema() { + const schema = { + "@context": "https://schema.org", + "@type": "LocalBusiness", + "@id": "https://www.novalon.cn/#business", + "name": COMPANY_INFO.name, + "image": "https://www.novalon.cn/og-image.jpg", + "url": "https://www.novalon.cn", + "email": COMPANY_INFO.email, + "description": "专注于企业数字化转型服务,提供软件开发、云计算、数据分析、信息安全等一站式解决方案", + "address": { + "@type": "PostalAddress", + "streetAddress": "成都市高新区", + "addressLocality": "成都市", + "addressRegion": "四川省", + "postalCode": "610000", + "addressCountry": "CN" + }, + "geo": { + "@type": "GeoCoordinates", + "latitude": 30.5728, + "longitude": 104.0668 + }, + "openingHoursSpecification": [ + { + "@type": "OpeningHoursSpecification", + "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], + "opens": "09:00", + "closes": "18:00" + } + ], + "priceRange": "$$", + "currenciesAccepted": "CNY", + "paymentAccepted": "Cash, Credit Card, Bank Transfer", + "areaServed": [ + { "@type": "City", "name": "成都" }, + { "@type": "State", "name": "四川" }, + { "@type": "Country", "name": "中国" } + ], + "hasOfferCatalog": { + "@type": "OfferCatalog", + "name": "数字化转型服务", + "itemListElement": [ + { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "软件开发服务" } }, + { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "云计算解决方案" } }, + { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "数据分析与BI" } }, + { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "信息安全咨询" } }, + { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "企业IT架构设计" } }, + ] + }, + "sameAs": [ + "https://www.novalon.cn" + ] + }; + + return ( +