GEO 优化

This commit is contained in:
小潘
2026-06-11 16:53:18 +08:00
parent 40fb2ab22e
commit a33d132289
7 changed files with 183 additions and 101 deletions
+2 -2
View File
@@ -5,7 +5,7 @@
## 核心页面 ## 核心页面
- [首页](https://products.2009xc.com/): 企业概览、核心数据、四大产品展示、资质荣誉 - [首页](https://products.2009xc.com/): 企业概览、核心数据、四大产品展示、资质荣誉
- [产品详情](https://products.2009xc.com/product/smart-campus): 各产品详情页(痛点、功能、优势、FAQ),通过 `/product/{slug}` 动态路由访问 - [产品详情](https://products.2009xc.com/product/smart-campus): 各产品详情页(痛点、功能、优势、FAQ),通过 /product/{slug} 动态路由访问
- [合作咨询](https://products.2009xc.com/contact): 联系方式、代理合作权益、在线咨询表单 - [合作咨询](https://products.2009xc.com/contact): 联系方式、代理合作权益、在线咨询表单
## 核心产品 ## 核心产品
@@ -50,7 +50,7 @@
### 售后服务和技术支持是怎样的? ### 售后服务和技术支持是怎样的?
提供 7×24 小时技术支持,包括远程运维、定期巡检、系统升级、用户培训等。 提供 7x24 小时技术支持,包括远程运维、定期巡检、系统升级、用户培训等。
### 数据安全如何保障? ### 数据安全如何保障?
+65 -36
View File
@@ -1,22 +1,34 @@
import { useState, useEffect, useCallback } from 'react' import { useState, useEffect, useCallback, useRef } from 'react'
import { X } from 'lucide-react' import { X } from 'lucide-react'
export default function Carousel({ images, interval = 3000, themeColor }) { export default function Carousel({ images, interval = 4000, themeColor }) {
const [current, setCurrent] = useState(0) const [current, setCurrent] = useState(0)
const [transitioning, setTransitioning] = useState(false) const [transitioning, setTransitioning] = useState(false)
const [preview, setPreview] = useState(null) // 预览图片的索引 const [preview, setPreview] = useState(null)
const timerRef = useRef(null)
const goTo = useCallback((index) => {
setTransitioning(true)
setTimeout(() => {
setCurrent(index)
setTimeout(() => setTransitioning(false), 50)
}, 350)
}, [])
const next = useCallback(() => { const next = useCallback(() => {
if (transitioning) return if (transitioning) return
setTransitioning(true) goTo((current + 1) % images.length)
setCurrent(prev => (prev + 1) % images.length) }, [current, images.length, transitioning, goTo])
setTimeout(() => setTransitioning(false), 500)
}, [images.length, transitioning]) const prev = useCallback(() => {
if (transitioning) return
goTo((current - 1 + images.length) % images.length)
}, [current, images.length, transitioning, goTo])
useEffect(() => { useEffect(() => {
if (preview !== null) return // 预览时不自动轮播 if (preview !== null) return
const timer = setInterval(next, interval) timerRef.current = setInterval(next, interval)
return () => clearInterval(timer) return () => clearInterval(timerRef.current)
}, [next, interval, preview]) }, [next, interval, preview])
const getIndex = (offset) => { const getIndex = (offset) => {
@@ -39,49 +51,66 @@ export default function Carousel({ images, interval = 3000, themeColor }) {
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
{/* Left - prev */} {/* Left - prev */}
<div <div
className={`relative z-10 w-80 h-44 md:w-96 md:h-52 bg-white rounded-2xl border border-gray-100 overflow-hidden shadow-lg -rotate-6 transition-all duration-500 cursor-pointer hover:opacity-80 ${ className="relative z-10 w-80 h-44 md:w-96 md:h-52 bg-white rounded-2xl border border-gray-100 overflow-hidden shadow-lg -rotate-6 cursor-pointer hover:opacity-80 transition-all duration-700 ease-[cubic-bezier(0.4,0,0.2,1)]"
transitioning ? 'opacity-70' : 'opacity-90' style={{
}`} opacity: transitioning ? 0.4 : 0.9,
onClick={() => handleImageClick(prevIdx)} transform: transitioning ? 'rotate(-6deg) translateX(24px) scale(0.95)' : 'rotate(-6deg)',
}}
onClick={() => { handleImageClick(prevIdx); prev() }}
> >
<img <img src={images[prevIdx]} alt="" className="w-full h-full object-contain" />
src={images[prevIdx]}
alt=""
className="w-full h-full object-contain"
/>
</div> </div>
{/* Center - current */} {/* Center - current */}
<div <div
className={`relative z-30 w-96 h-52 md:w-[500px] md:h-[280px] -mx-10 md:-mx-14 bg-white rounded-2xl border border-gray-100 overflow-hidden shadow-2xl transition-all duration-500 cursor-pointer ${ className="relative z-30 w-96 h-52 md:w-[500px] md:h-[280px] -mx-10 md:-mx-14 bg-white rounded-2xl border border-gray-100 overflow-hidden shadow-2xl cursor-pointer transition-all duration-700 ease-[cubic-bezier(0.4,0,0.2,1)]"
transitioning ? 'scale-95' : 'scale-100' style={{
}`} transform: transitioning ? 'scale(0.92)' : 'scale(1)',
opacity: transitioning ? 0.7 : 1,
}}
onClick={() => handleImageClick(current)} onClick={() => handleImageClick(current)}
> >
<div className="absolute top-0 left-0 right-0 h-[2px]" <div className="absolute top-0 left-0 right-0 h-[2px]"
style={{ background: `linear-gradient(90deg, transparent, ${themeColor}, transparent)` }} /> style={{ background: `linear-gradient(90deg, transparent, ${themeColor}, transparent)` }} />
<img <img src={images[current]} alt="" className="w-full h-full object-contain" />
src={images[current]}
alt=""
className="w-full h-full object-contain"
/>
</div> </div>
{/* Right - next */} {/* Right - next */}
<div <div
className={`relative z-10 w-80 h-44 md:w-96 md:h-52 bg-white rounded-2xl border border-gray-100 overflow-hidden shadow-lg rotate-6 transition-all duration-500 cursor-pointer hover:opacity-80 ${ className="relative z-10 w-80 h-44 md:w-96 md:h-52 bg-white rounded-2xl border border-gray-100 overflow-hidden shadow-lg rotate-6 cursor-pointer hover:opacity-80 transition-all duration-700 ease-[cubic-bezier(0.4,0,0.2,1)]"
transitioning ? 'opacity-70' : 'opacity-90' style={{
}`} opacity: transitioning ? 0.4 : 0.9,
onClick={() => handleImageClick(nextIdx)} transform: transitioning ? 'rotate(6deg) translateX(-24px) scale(0.95)' : 'rotate(6deg)',
}}
onClick={() => { handleImageClick(nextIdx); next() }}
> >
<img <img src={images[nextIdx]} alt="" className="w-full h-full object-contain" />
src={images[nextIdx]}
alt=""
className="w-full h-full object-contain"
/>
</div> </div>
</div> </div>
{/* Dot indicators */}
<div className="flex justify-center gap-2 mt-6">
{images.map((_, i) => (
<button
key={i}
onClick={() => {
if (i === current || transitioning) return
setTransitioning(true)
setTimeout(() => {
setCurrent(i)
setTimeout(() => setTransitioning(false), 50)
}, 350)
}}
className="rounded-full transition-all duration-500"
style={{
width: i === current ? '24px' : '8px',
height: '8px',
backgroundColor: i === current ? themeColor : '#d1d5db',
}}
/>
))}
</div>
{/* Preview Modal */} {/* Preview Modal */}
{preview !== null && ( {preview !== null && (
<div <div
+5 -5
View File
@@ -52,9 +52,9 @@ export default function Navbar() {
left: 10%; left: 10%;
right: 10%; right: 10%;
height: 3px; height: 3px;
background: linear-gradient(90deg, transparent, var(--nav-theme, #3b82f6), transparent); background: linear-gradient(90deg, transparent, rgba(255,255,255,0.8), transparent);
border-radius: 2px; border-radius: 2px;
box-shadow: 0 0 12px rgba(59, 130, 246, 0.5), 0 0 24px rgba(59, 130, 246, 0.2); box-shadow: 0 0 12px rgba(255,255,255,0.5), 0 0 24px rgba(255,255,255,0.2);
} }
.nav-item { .nav-item {
position: relative; position: relative;
@@ -75,8 +75,8 @@ export default function Navbar() {
} }
`}</style> `}</style>
<nav <nav
className={`sticky top-0 z-50 transition-all duration-500 backdrop-blur-xl ${scrolled ? 'bg-[#0f172a]/95 shadow-[0_4px_30px_rgba(0,0,0,0.2)] border-b border-white/[0.06]' : 'bg-gradient-to-b from-black/40 to-transparent'}`} className={`sticky top-0 z-50 transition-all duration-500 backdrop-blur-xl ${scrolled ? 'shadow-[0_4px_30px_rgba(0,0,0,0.2)] border-b border-white/[0.06]' : 'bg-gradient-to-b from-black/40 to-transparent'}`}
style={scrolled && themeColor ? { '--nav-theme': themeColor, backgroundColor: `${themeColor}ee` } : undefined} style={scrolled ? { backgroundColor: themeColor ? `${themeColor}f0` : '#0f172af2' } : undefined}
> >
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center h-[80px] overflow-visible"> <div className="flex items-center h-[80px] overflow-visible">
@@ -139,7 +139,7 @@ export default function Navbar() {
mobileOpen ? 'max-h-[800px]' : 'max-h-0' mobileOpen ? 'max-h-[800px]' : 'max-h-0'
}`} }`}
> >
<div className="bg-[#0a1628]/95 backdrop-blur-2xl border-t border-white/[0.06] px-4 pb-6 pt-3 space-y-1"> <div className="bg-[#0a1628]/95 backdrop-blur-2xl border-t border-white/[0.06] px-4 pb-6 pt-3 space-y-1" style={scrolled && themeColor ? { backgroundColor: `${themeColor}f0` } : undefined}>
{navItems.map((item) => ( {navItems.map((item) => (
<Link <Link
key={item.to} key={item.to}
+16 -7
View File
@@ -8,8 +8,6 @@ const chartData = [
{ value: 25, name: '实训管理平台' }, { value: 25, name: '实训管理平台' },
]; ];
const color = '#043b8f';
const rotationNeeded = [45, -45, -135, -225]; const rotationNeeded = [45, -45, -135, -225];
function normalizeAngle(a) { function normalizeAngle(a) {
@@ -25,12 +23,23 @@ function shortestRotation(from, to) {
return from + diff; return from + diff;
} }
export default function RosePieChart({ onSelect, activeIndex }) { /** 将 hex 颜色转为带透明度的 rgba */
function hexToRgba(hex, alpha) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r},${g},${b},${alpha})`;
}
export default function RosePieChart({ onSelect, activeIndex, colors }) {
const containerRef = useRef(null); const containerRef = useRef(null);
const chartRef = useRef(null); const chartRef = useRef(null);
const rotationRef = useRef(0); const rotationRef = useRef(0);
const [rotation, setRotation] = useState(0); const [rotation, setRotation] = useState(0);
// 默认回退颜色
const safeColors = colors?.length === 4 ? colors : ['#60ce93', '#0c86cf', '#aa3130', '#8b50ee'];
useEffect(() => { useEffect(() => {
if (!containerRef.current) return; if (!containerRef.current) return;
@@ -73,7 +82,7 @@ export default function RosePieChart({ onSelect, activeIndex }) {
}, },
data: chartData.map((d, i) => ({ data: chartData.map((d, i) => ({
...d, ...d,
itemStyle: { color, opacity: 1, borderRadius: 6, borderWidth: 0 }, itemStyle: { color: safeColors[i], opacity: 1, borderRadius: 6, borderWidth: 0 },
})), })),
animationType: 'scale', animationType: 'scale',
animationEasing: 'elasticOut', animationEasing: 'elasticOut',
@@ -106,9 +115,9 @@ export default function RosePieChart({ onSelect, activeIndex }) {
series: [{ series: [{
data: chartData.map((d, i) => ({ data: chartData.map((d, i) => ({
...d, ...d,
label: { color: isActive && activeIndex === i ? '#fff' : '#043b8f' }, label: { color: isActive && activeIndex === i ? '#fff' : safeColors[i] },
itemStyle: { itemStyle: {
color: isActive && activeIndex !== i ? color + '30' : color, color: isActive && activeIndex !== i ? hexToRgba(safeColors[i], 0.2) : safeColors[i],
opacity: 1, opacity: 1,
borderRadius: 6, borderRadius: 6,
borderColor: 'rgba(15,23,42,0.8)', borderColor: 'rgba(15,23,42,0.8)',
@@ -140,7 +149,7 @@ export default function RosePieChart({ onSelect, activeIndex }) {
transition: 'transform 1s cubic-bezier(0.4, 0, 0.2, 1)', transition: 'transform 1s cubic-bezier(0.4, 0, 0.2, 1)',
}} }}
> >
<div ref={containerRef} className="w-full h-[420px] md:h-[480px]" /> <div ref={containerRef} className="w-full h-[520px] md:h-[600px]" />
</div> </div>
); );
} }
+71 -41
View File
@@ -1,3 +1,4 @@
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import HeroSection from '../components/HeroSection' import HeroSection from '../components/HeroSection'
@@ -82,11 +83,10 @@ const customerCards = [
import { useState, useEffect } from 'react'
export default function Home() { export default function Home() {
const [activeCapability, setActiveCapability] = useState(0) const [activeCapability, setActiveCapability] = useState(0)
const [tagRadius, setTagRadius] = useState(68) const [tagRadius, setTagRadius] = useState(68)
const activeColor = products[activeCapability]?.themeColor || '#043b8f'
useEffect(() => { useEffect(() => {
const updateRadius = () => { const updateRadius = () => {
@@ -141,19 +141,25 @@ export default function Home() {
{/* Tab bar */} {/* Tab bar */}
<ScrollReveal> <ScrollReveal>
<div className="flex justify-center gap-1 mb-12 flex-wrap"> <div className="flex justify-center gap-1 mb-12 flex-wrap">
{products.map((p, i) => ( {products.map((p, i) => {
const isActive = activeCapability === i
return (
<button <button
key={p.slug} key={p.slug}
onClick={() => setActiveCapability(i)} onClick={() => setActiveCapability(i)}
className={`flex items-center gap-1.5 xs:gap-2 px-3 xs:px-4 sm:px-6 py-2 xs:py-3 rounded-lg text-xs xs:text-sm font-medium transition-all border ${activeCapability === i className="flex items-center gap-1.5 xs:gap-2 px-3 xs:px-4 sm:px-6 py-2 xs:py-3 rounded-lg text-xs xs:text-sm font-medium transition-all border"
? 'text-[#043b8f] border-[#043b8f]' style={{
: 'text-[#475569] border-gray-200 hover:text-[#043b8f] hover:border-[#043b8f]' color: isActive ? p.themeColor : '#475569',
}`} borderColor: isActive ? p.themeColor : '#e5e7eb',
}}
onMouseEnter={(e) => { if (!isActive) { e.target.style.color = p.themeColor; e.target.style.borderColor = p.themeColor; } }}
onMouseLeave={(e) => { if (!isActive) { e.target.style.color = '#475569'; e.target.style.borderColor = '#e5e7eb'; } }}
> >
<p.icon className="w-4 h-4 xs:w-5 xs:h-5" style={{ color: activeCapability === i ? '#043b8f' : p.themeColor }} /> <p.icon className="w-4 h-4 xs:w-5 xs:h-5" style={{ color: isActive ? p.themeColor : p.themeColor }} />
{p.name} {p.name}
</button> </button>
))} )
})}
</div> </div>
</ScrollReveal> </ScrollReveal>
@@ -162,58 +168,82 @@ export default function Home() {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center mb-12"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center mb-12">
{/* Left: visual */} {/* Left: visual */}
<div className="flex justify-center"> <div className="flex justify-center">
<RosePieChart onSelect={setActiveCapability} activeIndex={activeCapability} /> <RosePieChart onSelect={setActiveCapability} activeIndex={activeCapability} colors={products.map(p => p.themeColor)} />
</div> </div>
{/* Right: text */} {/* Right: text */}
<div className="space-y-4"> <div className="space-y-4">
{(activeCapability !== null ? [capabilities[activeCapability]] : capabilities).map((c, i) => { {(activeCapability !== null ? [capabilities[activeCapability]] : capabilities).map((c, i) => {
const idx = activeCapability !== null ? activeCapability : i const idx = activeCapability !== null ? activeCapability : i
const color = products[idx]?.themeColor || '#043b8f'
const isActive = activeCapability === idx
return ( return (
<div key={idx} className={`relative p-8 rounded-2xl transition-all duration-500 cursor-pointer group overflow-hidden ${activeCapability === idx <div key={idx} className="relative p-8 rounded-2xl transition-all duration-500 cursor-pointer group overflow-hidden"
? 'bg-white border border-[#043b8f]/20 shadow-[0_4px_24px_rgba(4,59,199,0.12)]' style={isActive
: 'hover:bg-gray-50/80 border border-gray-100' ? { backgroundColor: '#fff', borderColor: color + '33', borderWidth: 1, boxShadow: `0 8px 32px ${color}14, 0 2px 8px ${color}08` }
}`} onClick={() => setActiveCapability(idx)}> : {}}
{activeCapability === idx && ( onClick={() => setActiveCapability(idx)}>
<div className="absolute top-0 left-0 right-0 h-[2px]" {/* Left accent line */}
style={{ background: 'linear-gradient(90deg, transparent, #043b8f, transparent)' }} /> {isActive && (
<div className="absolute top-6 left-0 bottom-6 w-[3px] rounded-r-full opacity-60"
style={{ background: `linear-gradient(180deg, transparent, ${color}, transparent)` }} />
)} )}
<div className="absolute top-0 right-0 w-24 h-24 bg-[#043b8f]/[0.03] rounded-bl-full opacity-0 group-hover:opacity-100 transition-opacity duration-500" /> {/* Decorative corner blobs */}
<div className="absolute bottom-0 left-0 w-16 h-16 border border-[#043b8f]/[0.06] rounded-tr-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500" /> <div className="absolute top-0 right-0 w-32 h-32 rounded-bl-full opacity-0 group-hover:opacity-100 transition-all duration-700"
<div className="flex items-center gap-3 mb-3"> style={{ backgroundColor: color + '06' }} />
<span className={`flex items-center justify-center w-7 h-7 rounded-md text-[11px] font-black transition-colors duration-300 ${activeCapability === idx <div className="absolute bottom-0 left-0 w-20 h-20 border rounded-tr-3xl opacity-0 group-hover:opacity-100 transition-all duration-700"
? 'bg-[#043b8f]/[0.08] text-[#043b8f]' style={{ borderColor: color + '0d' }} />
: 'bg-gray-100 text-[#94a3b8]' {/* Number badge row */}
}`}>0{idx + 1}</span> <div className="flex items-center gap-3 mb-4">
<div className={`h-px flex-1 transition-colors duration-300 ${activeCapability === idx <span className="flex items-center justify-center w-8 h-8 rounded-lg text-xs font-black transition-all duration-300"
? 'bg-gradient-to-r from-[#043b8f]/15 to-transparent' style={isActive
: 'bg-gray-100' ? { backgroundColor: color + '14', color: color, boxShadow: `0 2px 8px ${color}14` }
}`} /> : { backgroundColor: '#f1f5f9', color: '#94a3b8' }}>0{idx + 1}</span>
<div className="h-px flex-1 transition-all duration-300"
style={isActive
? { background: `linear-gradient(90deg, ${color}30, ${color}08, transparent)` }
: { backgroundColor: '#f1f5f9' }} />
{/* Product icon */}
{React.createElement(products[idx].icon, {
className: "w-4 h-4 transition-all duration-300",
style: { color: isActive ? color : '#cbd5e1' }
})}
</div> </div>
<h3 className={`text-lg xs:text-xl sm:text-2xl font-bold transition-colors duration-300 ${activeCapability === idx ? 'text-[#043b8f]' : 'text-[#0f172a]' {/* Title */}
}`}>{c.title}</h3> <h3 className="text-xl xs:text-2xl sm:text-3xl font-bold transition-all duration-300 tracking-tight"
<p className={`text-sm xs:text-base mt-1.5 font-medium transition-colors duration-300 ${activeCapability === idx ? 'text-[#043b8f]/60' : 'text-[#94a3b8]' style={{ color: isActive ? color : '#0f172a' }}>{c.title}</h3>
}`}>{c.sub}</p> {/* Subtitle */}
<p className="text-sm xs:text-base text-[#334155] leading-[2] mt-4">{c.desc}</p> <p className="text-sm xs:text-base mt-1.5 font-medium transition-all duration-300"
style={{ color: isActive ? color + '99' : '#94a3b8' }}>{c.sub}</p>
{/* Divider */}
<div className="my-4 h-px w-12 transition-all duration-500"
style={{ background: isActive ? `linear-gradient(90deg, ${color}40, transparent)` : '#f1f5f9' }} />
{/* Description */}
<p className="text-sm xs:text-base text-[#475569] leading-[1.9]">{c.desc}</p>
{/* Tags */}
{c.tip && ( {c.tip && (
<div className="mt-5 flex flex-wrap gap-2"> <div className="mt-5 flex flex-wrap gap-2">
{c.tip.split('、').map((t, ti) => ( {c.tip.split('、').map((t, ti) => (
<span key={ti} className={`px-3 py-1 xs:px-4 xs:py-1.5 text-xs xs:text-sm font-medium rounded-md transition-all duration-200 ${activeCapability === idx <span key={ti} className="px-3 py-1.5 text-xs font-medium rounded-full transition-all duration-300"
? 'text-[#043b8f] bg-[#043b8f]/[0.08]' style={isActive
: 'text-[#94a3b8] bg-gray-50' ? { color: color, backgroundColor: color + '0f', borderColor: color + '1a', borderWidth: 1 }
}`}> : { color: '#94a3b8', backgroundColor: '#f8fafc', borderColor: '#e2e8f0', borderWidth: 1 }}>
{t} {t}
</span> </span>
))} ))}
</div> </div>
)} )}
{activeCapability === idx && ( {/* View more link */}
<div className="mt-5 flex justify-end"> {isActive && (
<div className="mt-6 flex justify-end">
<Link to={`/product/${c.slug}`} <Link to={`/product/${c.slug}`}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="text-sm text-[#043b8f]/60 hover:text-[#043b8f] hover:underline flex items-center gap-1 transition-colors duration-200"> className="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-300"
style={{ color: color, backgroundColor: color + '0a' }}
onMouseEnter={(e) => { e.target.style.backgroundColor = color + '1a'; }}
onMouseLeave={(e) => { e.target.style.backgroundColor = color + '0a'; }}>
查看全部 查看全部
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7" /></svg> <svg className="w-3.5 h-3.5 transition-transform duration-300 group-hover:translate-x-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7" /></svg>
</Link> </Link>
</div> </div>
)} )}
+8 -8
View File
@@ -161,19 +161,19 @@ export default function ProductPage() {
<div className="space-y-5"> <div className="space-y-5">
{product.painPoints.map((item, i) => ( {product.painPoints.map((item, i) => (
<ScrollReveal key={i} delay={i * 0.08}> <ScrollReveal key={i} delay={i * 0.08}>
<div className="relative bg-white rounded-2xl p-10 border border-gray-100 hover:shadow-lg hover:shadow-gray-200/50 transition-all duration-300 group h-full"> <div className="relative bg-white rounded-2xl p-6 xs:p-8 md:p-10 border border-gray-100 hover:shadow-lg hover:shadow-gray-200/50 transition-all duration-300 group h-full">
<div className="absolute top-0 left-0 right-0 h-1 rounded-t-2 bg-[var(--theme)] opacity-0 group-hover:opacity-100 transition-opacity duration-300" /> <div className="absolute top-0 left-0 right-0 h-1 rounded-t-2 bg-[var(--theme)] opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
{/* Pain point */} {/* Pain point */}
<div className="flex items-start gap-3 mb-5 pb-5 border-b border-dashed border-gray-200"> <div className="flex items-start gap-2 xs:gap-3 mb-4 xs:mb-5 pb-4 xs:pb-5 border-b border-dashed border-gray-200">
<div className="shrink-0 w-9 h-9 rounded-full bg-red-50 flex items-center justify-center"> <div className="shrink-0 w-7 h-7 xs:w-9 xs:h-9 rounded-full bg-red-50 flex items-center justify-center">
<AlertCircle className="w-5 h-5 text-red-500" /> <AlertCircle className="w-4 h-4 xs:w-5 xs:h-5 text-red-500" />
</div> </div>
<h3 className="text-lg font-bold text-[#0f172a] leading-relaxed">{item.title}</h3> <h3 className="text-base xs:text-lg font-bold text-[#0f172a] leading-relaxed">{item.title}</h3>
</div> </div>
{/* Solution */} {/* Solution */}
<div className="ml-11 pl-5 border-l-2 border-[var(--theme)] bg-[rgba(var(--theme-r),var(--theme-g),var(--theme-b),0.03)] rounded-r-lg py-4 pr-4"> <div className="ml-0 sm:ml-6 md:ml-11 pl-3 xs:pl-5 border-l-2 border-[var(--theme)] bg-[rgba(var(--theme-r),var(--theme-g),var(--theme-b),0.03)] rounded-r-lg py-3 xs:py-4 pr-3 xs:pr-4">
<span className="inline-block text-sm font-semibold text-white bg-[var(--theme)] rounded px-3 py-1.5 mb-3">解决方案</span> <span className="inline-block text-xs xs:text-sm font-semibold text-white bg-[var(--theme)] rounded px-2 xs:px-3 py-1 xs:py-1.5 mb-2 xs:mb-3">解决方案</span>
<p className="text-[#334155] text-xl leading-[1.9]">{item.desc}</p> <p className="text-[#334155] text-sm xs:text-base md:text-lg xl:text-xl leading-[1.7] xs:leading-[1.9]">{item.desc}</p>
</div> </div>
</div> </div>
</ScrollReveal> </ScrollReveal>
+15 -1
View File
@@ -3,6 +3,20 @@ import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
export default defineConfig({ export default defineConfig({
plugins: [react(), tailwindcss()], plugins: [
react(),
tailwindcss(),
{
name: 'txt-charset',
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url.endsWith('.txt')) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
}
next()
})
},
},
],
base: '/', base: '/',
}) })