refactor: 移除hero水墨动画,优化全局样式与组件细节
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{"reason":"idle timeout","timestamp":1777795204433}
|
||||
@@ -0,0 +1,52 @@
|
||||
<h2>Hero 动画风格偏好</h2>
|
||||
<p class="subtitle">你希望 Hero 区域传达什么样的品牌气质?选择最接近你期望的方向</p>
|
||||
|
||||
<div class="options">
|
||||
<div class="option" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="letter">A</div>
|
||||
<div class="content">
|
||||
<h3>沉稳专业 — 微妙渐变 + 文字动效</h3>
|
||||
<p>暖白/浅灰渐变背景,文字依次淡入上浮,极简装饰元素(如细线圆圈)。类似 Atlassian、Stripe 的克制风格。强调品牌可信度与专业感。</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>优势</h4><ul><li>加载快,性能开销极低</li><li>适配所有设备</li><li>与现有品牌色(朱砂红 #C41E3A)自然融合</li></ul></div>
|
||||
<div class="cons"><h4>注意</h4><ul><li>视觉冲击力相对温和</li><li>需要文字排版功底支撑</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="letter">B</div>
|
||||
<div class="content">
|
||||
<h3>科技动感 — 流光渐变 + 粒子/网格</h3>
|
||||
<p>深色或渐变背景,数据粒子流动、科技网格线、光晕效果。强调数字化转型与科技实力。类似 Vercel、Linear 的科技美学。</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>优势</h4><ul><li>视觉冲击力强</li><li>与"数字化转型"定位高度契合</li><li>项目已有 DataParticleFlow、TechGridFlow 等组件</li></ul></div>
|
||||
<div class="cons"><h4>注意</h4><ul><li>性能开销中等</li><li>需要控制粒子数量避免喧宾夺主</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="letter">C</div>
|
||||
<div class="content">
|
||||
<h3>东方韵味 — 水墨/印章 + 现代排版</h3>
|
||||
<p>融合中国传统元素(水墨晕染、印章动画)与现代极简排版。强调文化底蕴与创新融合。项目已有 InkBackground、SealAnimation 等组件。</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>优势</h4><ul><li>品牌辨识度极高</li><li>差异化明显,同行少有</li><li>与 Novalon(诺华麟)品牌名呼应</li></ul></div>
|
||||
<div class="cons"><h4>注意</h4><ul><li>设计难度高,容易"土味"</li><li>需要精准控制水墨效果的克制度</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="d" onclick="toggleSelect(this)">
|
||||
<div class="letter">D</div>
|
||||
<div class="content">
|
||||
<h3>融合创新 — 克制科技 + 品牌印记</h3>
|
||||
<p>以方案 A 的沉稳为底色,融入方案 B 的微妙科技光效(如渐变光晕),点缀方案 C 的品牌印章元素。三者取精华,克制融合。</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>优势</h4><ul><li>兼顾专业感、科技感、品牌辨识度</li><li>层次丰富但不杂乱</li><li>与之前的设计方向(品牌融合重构)一致</li></ul></div>
|
||||
<div class="cons"><h4>注意</h4><ul><li>实现复杂度最高</li><li>需要精心调校各元素的视觉权重</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,332 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hero 动画风格 Demo</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #111; color: #fff; }
|
||||
|
||||
.nav { position: fixed; top: 0; left: 0; right: 0; z-index: 100; display: flex; gap: 8px; padding: 16px 24px; background: rgba(0,0,0,0.8); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(255,255,255,0.1); }
|
||||
.nav button { padding: 8px 20px; border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; background: transparent; color: #fff; cursor: pointer; font-size: 14px; transition: all 0.3s; }
|
||||
.nav button:hover { background: rgba(255,255,255,0.1); }
|
||||
.nav button.active { background: #C41E3A; border-color: #C41E3A; }
|
||||
|
||||
.demo-section { display: none; position: relative; min-height: 100vh; overflow: hidden; }
|
||||
.demo-section.active { display: flex; }
|
||||
|
||||
.hero-content { position: relative; z-index: 10; max-width: 720px; padding: 0 48px; }
|
||||
.hero-tag { display: inline-flex; align-items: center; gap: 8px; padding: 8px 20px; border-radius: 999px; font-size: 14px; font-weight: 500; margin-bottom: 24px; }
|
||||
.hero-title { font-size: 64px; font-weight: 400; line-height: 1.1; margin-bottom: 20px; letter-spacing: -0.02em; }
|
||||
.hero-subtitle { font-size: 24px; font-weight: 600; margin-bottom: 12px; }
|
||||
.hero-desc { font-size: 18px; line-height: 1.7; margin-bottom: 36px; opacity: 0.7; }
|
||||
.hero-buttons { display: flex; gap: 16px; }
|
||||
.btn-primary { padding: 14px 32px; background: #C41E3A; color: #fff; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; display: flex; align-items: center; gap: 8px; }
|
||||
.btn-outline { padding: 14px 32px; background: transparent; color: #1C1C1C; border: 1px solid rgba(0,0,0,0.15); border-radius: 8px; font-size: 16px; cursor: pointer; }
|
||||
|
||||
.style-label { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); padding: 8px 24px; background: rgba(0,0,0,0.7); backdrop-filter: blur(8px); border-radius: 999px; font-size: 13px; color: rgba(255,255,255,0.6); z-index: 100; }
|
||||
|
||||
/* ========== Style A: 沉稳专业 ========== */
|
||||
#style-a { background: linear-gradient(180deg, #FFFBF5 0%, #FFFFFF 100%); color: #1C1C1C; align-items: center; justify-content: flex-start; padding-top: 20vh; }
|
||||
#style-a .hero-tag { background: #FEF2F4; color: #C41E3A; border: 1px solid rgba(196,30,58,0.1); }
|
||||
#style-a .hero-subtitle { color: #C41E3A; }
|
||||
#style-a .btn-primary { background: #C41E3A; color: #fff; }
|
||||
#style-a .btn-outline { color: #1C1C1C; border-color: rgba(28,28,28,0.2); }
|
||||
|
||||
#style-a .deco-circle-1 { position: absolute; top: 80px; right: 120px; width: 96px; height: 96px; border: 1px solid rgba(196,30,58,0.08); border-radius: 50%; }
|
||||
#style-a .deco-circle-2 { position: absolute; bottom: 120px; left: 80px; width: 72px; height: 72px; border: 1px solid rgba(196,30,58,0.06); border-radius: 50%; }
|
||||
#style-a .deco-line { position: absolute; top: 200px; right: 200px; width: 200px; height: 1px; background: linear-gradient(90deg, transparent, rgba(196,30,58,0.1), transparent); }
|
||||
|
||||
/* A animations */
|
||||
@keyframes a-fade-up { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes a-circle-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
|
||||
@keyframes a-line-shimmer { 0% { opacity: 0.3; } 50% { opacity: 0.8; } 100% { opacity: 0.3; } }
|
||||
|
||||
#style-a .hero-tag { animation: a-fade-up 0.6s ease-out 0.1s both; }
|
||||
#style-a .hero-title { animation: a-fade-up 0.6s ease-out 0.2s both; }
|
||||
#style-a .hero-subtitle { animation: a-fade-up 0.6s ease-out 0.3s both; }
|
||||
#style-a .hero-desc { animation: a-fade-up 0.6s ease-out 0.4s both; }
|
||||
#style-a .hero-buttons { animation: a-fade-up 0.6s ease-out 0.5s both; }
|
||||
#style-a .deco-circle-1 { animation: a-circle-float 6s ease-in-out infinite; }
|
||||
#style-a .deco-circle-2 { animation: a-circle-float 8s ease-in-out 1s infinite; }
|
||||
#style-a .deco-line { animation: a-line-shimmer 4s ease-in-out infinite; }
|
||||
|
||||
/* ========== Style B: 科技动感 ========== */
|
||||
#style-b { background: #0A0A0A; color: #fff; align-items: center; justify-content: flex-start; padding-top: 20vh; }
|
||||
#style-b .hero-tag { background: rgba(196,30,58,0.15); color: #ff6b7a; border: 1px solid rgba(196,30,58,0.3); }
|
||||
#style-b .hero-title { color: #fff; }
|
||||
#style-b .hero-subtitle { color: #ff6b7a; }
|
||||
#style-b .hero-desc { color: rgba(255,255,255,0.6); }
|
||||
#style-b .btn-primary { background: #C41E3A; color: #fff; }
|
||||
#style-b .btn-outline { color: #fff; border-color: rgba(255,255,255,0.2); }
|
||||
|
||||
#style-b .grid-bg { position: absolute; inset: 0; background-image:
|
||||
linear-gradient(rgba(196,30,58,0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(196,30,58,0.05) 1px, transparent 1px);
|
||||
background-size: 60px 60px;
|
||||
mask-image: radial-gradient(ellipse 60% 60% at 50% 40%, black 20%, transparent 70%);
|
||||
}
|
||||
#style-b .glow-1 { position: absolute; top: 10%; right: 15%; width: 400px; height: 400px; background: radial-gradient(circle, rgba(196,30,58,0.15) 0%, transparent 70%); filter: blur(60px); border-radius: 50%; }
|
||||
#style-b .glow-2 { position: absolute; bottom: 20%; left: 10%; width: 300px; height: 300px; background: radial-gradient(circle, rgba(100,100,255,0.1) 0%, transparent 70%); filter: blur(60px); border-radius: 50%; }
|
||||
|
||||
/* B particles */
|
||||
#style-b .particles { position: absolute; inset: 0; }
|
||||
#style-b .particle { position: absolute; width: 3px; height: 3px; background: rgba(196,30,58,0.6); border-radius: 1px; }
|
||||
|
||||
@keyframes b-fade-up { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes b-particle-float {
|
||||
0% { transform: translate(0, 0) scale(1); opacity: 0; }
|
||||
10% { opacity: 1; }
|
||||
90% { opacity: 1; }
|
||||
100% { transform: translate(var(--dx), var(--dy)) scale(0.5); opacity: 0; }
|
||||
}
|
||||
@keyframes b-glow-pulse { 0%, 100% { opacity: 0.6; transform: scale(1); } 50% { opacity: 1; transform: scale(1.1); } }
|
||||
@keyframes b-grid-flow { 0% { background-position: 0 0; } 100% { background-position: 60px 60px; } }
|
||||
|
||||
#style-b .grid-bg { animation: b-grid-flow 8s linear infinite; }
|
||||
#style-b .glow-1 { animation: b-glow-pulse 6s ease-in-out infinite; }
|
||||
#style-b .glow-2 { animation: b-glow-pulse 8s ease-in-out 2s infinite; }
|
||||
#style-b .hero-tag { animation: b-fade-up 0.6s ease-out 0.2s both; }
|
||||
#style-b .hero-title { animation: b-fade-up 0.6s ease-out 0.3s both; }
|
||||
#style-b .hero-subtitle { animation: b-fade-up 0.6s ease-out 0.4s both; }
|
||||
#style-b .hero-desc { animation: b-fade-up 0.6s ease-out 0.5s both; }
|
||||
#style-b .hero-buttons { animation: b-fade-up 0.6s ease-out 0.6s both; }
|
||||
|
||||
/* ========== Style C: 东方韵味 ========== */
|
||||
#style-c { background: #FAFAF5; color: #1C1C1C; align-items: center; justify-content: flex-start; padding-top: 18vh; }
|
||||
#style-c .hero-tag { background: rgba(196,30,58,0.08); color: #C41E3A; border: 1px solid rgba(196,30,58,0.15); }
|
||||
#style-c .hero-title { font-family: 'Noto Serif SC', 'STSong', 'SimSun', serif; font-size: 72px; font-weight: 400; }
|
||||
#style-c .hero-subtitle { color: #C41E3A; }
|
||||
#style-c .btn-primary { background: #C41E3A; color: #fff; }
|
||||
#style-c .btn-outline { color: #1C1C1C; border-color: rgba(28,28,28,0.2); }
|
||||
|
||||
/* C ink effects */
|
||||
#style-c .ink-bg { position: absolute; inset: 0; overflow: hidden; }
|
||||
#style-c .ink-blob { position: absolute; border-radius: 50%; filter: blur(80px); }
|
||||
|
||||
@keyframes c-ink-spread {
|
||||
0% { transform: scale(0); opacity: 0; }
|
||||
30% { opacity: 0.15; }
|
||||
100% { transform: scale(1); opacity: 0.08; }
|
||||
}
|
||||
@keyframes c-ink-drift {
|
||||
0%, 100% { transform: translate(0, 0) rotate(0deg); }
|
||||
25% { transform: translate(10px, -15px) rotate(2deg); }
|
||||
50% { transform: translate(-5px, 10px) rotate(-1deg); }
|
||||
75% { transform: translate(-10px, -5px) rotate(1deg); }
|
||||
}
|
||||
@keyframes c-fade-up { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes c-seal-stamp {
|
||||
0% { transform: scale(2) rotate(-15deg); opacity: 0; }
|
||||
60% { transform: scale(0.9) rotate(2deg); opacity: 1; }
|
||||
80% { transform: scale(1.05) rotate(-1deg); }
|
||||
100% { transform: scale(1) rotate(0deg); opacity: 1; }
|
||||
}
|
||||
|
||||
#style-c .ink-blob-1 { top: 5%; right: 10%; width: 500px; height: 500px; background: radial-gradient(circle, rgba(28,28,28,0.12) 0%, transparent 70%); animation: c-ink-spread 2s ease-out 0.3s both, c-ink-drift 20s ease-in-out 2.3s infinite; }
|
||||
#style-c .ink-blob-2 { bottom: 10%; left: 5%; width: 400px; height: 400px; background: radial-gradient(circle, rgba(196,30,58,0.1) 0%, transparent 70%); animation: c-ink-spread 2.5s ease-out 0.6s both, c-ink-drift 25s ease-in-out 2.6s infinite; }
|
||||
#style-c .ink-blob-3 { top: 40%; right: 30%; width: 300px; height: 300px; background: radial-gradient(circle, rgba(28,28,28,0.06) 0%, transparent 70%); animation: c-ink-spread 1.8s ease-out 0.9s both, c-ink-drift 18s ease-in-out 2s infinite; }
|
||||
|
||||
#style-c .seal { position: absolute; top: 15%; right: 12%; width: 80px; height: 80px; border: 3px solid #C41E3A; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-family: 'Noto Serif SC', serif; font-size: 28px; color: #C41E3A; transform: rotate(-8deg); animation: c-seal-stamp 0.8s ease-out 1.2s both; opacity: 0; }
|
||||
|
||||
#style-c .hero-tag { animation: c-fade-up 0.6s ease-out 0.2s both; }
|
||||
#style-c .hero-title { animation: c-fade-up 0.8s ease-out 0.4s both; }
|
||||
#style-c .hero-subtitle { animation: c-fade-up 0.6s ease-out 0.6s both; }
|
||||
#style-c .hero-desc { animation: c-fade-up 0.6s ease-out 0.7s both; }
|
||||
#style-c .hero-buttons { animation: c-fade-up 0.6s ease-out 0.8s both; }
|
||||
|
||||
/* ========== Style D: 融合创新 ========== */
|
||||
#style-d { background: linear-gradient(170deg, #FFFBF5 0%, #FFF5F6 40%, #FFFFFF 100%); color: #1C1C1C; align-items: center; justify-content: flex-start; padding-top: 20vh; }
|
||||
#style-d .hero-tag { background: #FEF2F4; color: #C41E3A; border: 1px solid rgba(196,30,58,0.1); }
|
||||
#style-d .hero-title { font-size: 64px; }
|
||||
#style-d .hero-subtitle { color: #C41E3A; }
|
||||
#style-d .btn-primary { background: #C41E3A; color: #fff; }
|
||||
#style-d .btn-outline { color: #1C1C1C; border-color: rgba(28,28,28,0.2); }
|
||||
|
||||
/* D fusion effects */
|
||||
#style-d .glow-bg { position: absolute; inset: 0; overflow: hidden; }
|
||||
#style-d .glow-orb { position: absolute; border-radius: 50%; filter: blur(80px); }
|
||||
|
||||
@keyframes d-glow-move {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
33% { transform: translate(30px, -20px) scale(1.1); }
|
||||
66% { transform: translate(-20px, 15px) scale(0.95); }
|
||||
}
|
||||
@keyframes d-fade-up { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes d-line-draw { from { stroke-dashoffset: 200; } to { stroke-dashoffset: 0; } }
|
||||
@keyframes d-seal-appear {
|
||||
0% { transform: scale(1.8) rotate(-12deg); opacity: 0; }
|
||||
70% { transform: scale(0.95) rotate(1deg); opacity: 0.9; }
|
||||
100% { transform: scale(1) rotate(-4deg); opacity: 0.85; }
|
||||
}
|
||||
@keyframes d-dot-pulse { 0%, 100% { opacity: 0.3; transform: scale(1); } 50% { opacity: 0.8; transform: scale(1.5); } }
|
||||
|
||||
#style-d .glow-orb-1 { top: 5%; right: 10%; width: 450px; height: 450px; background: radial-gradient(circle, rgba(196,30,58,0.08) 0%, transparent 70%); animation: d-glow-move 15s ease-in-out infinite; }
|
||||
#style-d .glow-orb-2 { bottom: 15%; left: 8%; width: 350px; height: 350px; background: radial-gradient(circle, rgba(196,30,58,0.05) 0%, transparent 70%); animation: d-glow-move 20s ease-in-out 3s infinite; }
|
||||
#style-d .glow-orb-3 { top: 30%; left: 40%; width: 250px; height: 250px; background: radial-gradient(circle, rgba(100,100,200,0.04) 0%, transparent 70%); animation: d-glow-move 18s ease-in-out 6s infinite; }
|
||||
|
||||
#style-d .seal-small { position: absolute; bottom: 25%; right: 15%; width: 56px; height: 56px; border: 2px solid rgba(196,30,58,0.6); border-radius: 6px; display: flex; align-items: center; justify-content: center; font-family: 'Noto Serif SC', serif; font-size: 20px; color: rgba(196,30,58,0.6); animation: d-seal-appear 0.7s ease-out 1s both; opacity: 0; }
|
||||
|
||||
#style-d .tech-dots { position: absolute; top: 20%; right: 25%; }
|
||||
#style-d .tech-dot { position: absolute; width: 4px; height: 4px; background: rgba(196,30,58,0.3); border-radius: 50%; animation: d-dot-pulse 3s ease-in-out infinite; }
|
||||
|
||||
#style-d .deco-line-svg { position: absolute; top: 15%; right: 8%; width: 200px; height: 200px; }
|
||||
#style-d .deco-line-svg line { stroke: rgba(196,30,58,0.1); stroke-width: 1; stroke-dasharray: 200; animation: d-line-draw 2s ease-out 0.5s both; }
|
||||
|
||||
#style-d .hero-tag { animation: d-fade-up 0.5s ease-out 0.1s both; }
|
||||
#style-d .hero-title { animation: d-fade-up 0.6s ease-out 0.2s both; }
|
||||
#style-d .hero-subtitle { animation: d-fade-up 0.5s ease-out 0.3s both; }
|
||||
#style-d .hero-desc { animation: d-fade-up 0.5s ease-out 0.4s both; }
|
||||
#style-d .hero-buttons { animation: d-fade-up 0.5s ease-out 0.5s both; }
|
||||
|
||||
/* Replay button */
|
||||
.replay-btn { position: fixed; bottom: 24px; right: 24px; z-index: 100; padding: 10px 20px; background: rgba(0,0,0,0.6); color: #fff; border: none; border-radius: 8px; cursor: pointer; font-size: 13px; backdrop-filter: blur(8px); }
|
||||
.replay-btn:hover { background: rgba(0,0,0,0.8); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="nav">
|
||||
<button class="active" onclick="switchStyle('a')">A 沉稳专业</button>
|
||||
<button onclick="switchStyle('b')">B 科技动感</button>
|
||||
<button onclick="switchStyle('c')">C 东方韵味</button>
|
||||
<button onclick="switchStyle('d')">D 融合创新</button>
|
||||
</div>
|
||||
|
||||
<!-- Style A: 沉稳专业 -->
|
||||
<section id="style-a" class="demo-section active">
|
||||
<div class="deco-circle-1"></div>
|
||||
<div class="deco-circle-2"></div>
|
||||
<div class="deco-line"></div>
|
||||
<div class="hero-content">
|
||||
<div class="hero-tag">✦ 智连未来,成长伙伴</div>
|
||||
<h1 class="hero-title">睿新致遠</h1>
|
||||
<p class="hero-subtitle">企业数字化转型服务商</p>
|
||||
<p class="hero-desc">以智慧连接数字趋势,以伙伴身份陪您成长——您的数字化转型同行者</p>
|
||||
<div class="hero-buttons">
|
||||
<button class="btn-primary">立即咨询 →</button>
|
||||
<button class="btn-outline">探索产品</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Style B: 科技动感 -->
|
||||
<section id="style-b" class="demo-section">
|
||||
<div class="grid-bg"></div>
|
||||
<div class="glow-1"></div>
|
||||
<div class="glow-2"></div>
|
||||
<div class="particles" id="particles-b"></div>
|
||||
<div class="hero-content">
|
||||
<div class="hero-tag">✦ 智连未来,成长伙伴</div>
|
||||
<h1 class="hero-title">睿新致遠</h1>
|
||||
<p class="hero-subtitle">企业数字化转型服务商</p>
|
||||
<p class="hero-desc">以智慧连接数字趋势,以伙伴身份陪您成长——您的数字化转型同行者</p>
|
||||
<div class="hero-buttons">
|
||||
<button class="btn-primary">立即咨询 →</button>
|
||||
<button class="btn-outline">探索产品</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Style C: 东方韵味 -->
|
||||
<section id="style-c" class="demo-section">
|
||||
<div class="ink-bg">
|
||||
<div class="ink-blob ink-blob-1"></div>
|
||||
<div class="ink-blob ink-blob-2"></div>
|
||||
<div class="ink-blob ink-blob-3"></div>
|
||||
</div>
|
||||
<div class="seal">睿</div>
|
||||
<div class="hero-content">
|
||||
<div class="hero-tag">✦ 智连未来,成长伙伴</div>
|
||||
<h1 class="hero-title">睿新致遠</h1>
|
||||
<p class="hero-subtitle">企业数字化转型服务商</p>
|
||||
<p class="hero-desc">以智慧连接数字趋势,以伙伴身份陪您成长——您的数字化转型同行者</p>
|
||||
<div class="hero-buttons">
|
||||
<button class="btn-primary">立即咨询 →</button>
|
||||
<button class="btn-outline">探索产品</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Style D: 融合创新 -->
|
||||
<section id="style-d" class="demo-section">
|
||||
<div class="glow-bg">
|
||||
<div class="glow-orb glow-orb-1"></div>
|
||||
<div class="glow-orb glow-orb-2"></div>
|
||||
<div class="glow-orb glow-orb-3"></div>
|
||||
</div>
|
||||
<svg class="deco-line-svg" viewBox="0 0 200 200">
|
||||
<line x1="20" y1="20" x2="180" y2="180" />
|
||||
<line x1="180" y1="20" x2="20" y2="180" />
|
||||
</svg>
|
||||
<div class="tech-dots">
|
||||
<div class="tech-dot" style="top:0;left:0;animation-delay:0s"></div>
|
||||
<div class="tech-dot" style="top:0;left:20px;animation-delay:0.5s"></div>
|
||||
<div class="tech-dot" style="top:20px;left:10px;animation-delay:1s"></div>
|
||||
<div class="tech-dot" style="top:20px;left:30px;animation-delay:1.5s"></div>
|
||||
<div class="tech-dot" style="top:40px;left:5px;animation-delay:0.3s"></div>
|
||||
</div>
|
||||
<div class="seal-small">睿</div>
|
||||
<div class="hero-content">
|
||||
<div class="hero-tag">✦ 智连未来,成长伙伴</div>
|
||||
<h1 class="hero-title">睿新致遠</h1>
|
||||
<p class="hero-subtitle">企业数字化转型服务商</p>
|
||||
<p class="hero-desc">以智慧连接数字趋势,以伙伴身份陪您成长——您的数字化转型同行者</p>
|
||||
<div class="hero-buttons">
|
||||
<button class="btn-primary">立即咨询 →</button>
|
||||
<button class="btn-outline">探索产品</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="style-label" id="style-label">当前:A 沉稳专业 — 微妙渐变 + 文字动效</div>
|
||||
<button class="replay-btn" onclick="replayAnimation()">↻ 重播动画</button>
|
||||
|
||||
<script>
|
||||
const labels = {
|
||||
a: 'A 沉稳专业 — 微妙渐变 + 文字动效',
|
||||
b: 'B 科技动感 — 流光渐变 + 粒子/网格',
|
||||
c: 'C 东方韵味 — 水墨/印章 + 现代排版',
|
||||
d: 'D 融合创新 — 克制科技 + 品牌印记'
|
||||
};
|
||||
|
||||
function switchStyle(style) {
|
||||
document.querySelectorAll('.demo-section').forEach(s => s.classList.remove('active'));
|
||||
document.querySelectorAll('.nav button').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById('style-' + style).classList.add('active');
|
||||
document.querySelectorAll('.nav button')[['a','b','c','d'].indexOf(style)].classList.add('active');
|
||||
document.getElementById('style-label').textContent = '当前:' + labels[style];
|
||||
replayAnimation();
|
||||
}
|
||||
|
||||
function replayAnimation() {
|
||||
const active = document.querySelector('.demo-section.active');
|
||||
const clone = active.cloneNode(true);
|
||||
active.parentNode.replaceChild(clone, active);
|
||||
if (active.id === 'style-b') initParticles();
|
||||
}
|
||||
|
||||
// Particles for Style B
|
||||
function initParticles() {
|
||||
const container = document.getElementById('particles-b');
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const p = document.createElement('div');
|
||||
p.className = 'particle';
|
||||
const x = Math.random() * 100;
|
||||
const y = Math.random() * 100;
|
||||
const dx = (Math.random() - 0.5) * 200;
|
||||
const dy = (Math.random() - 0.5) * 200;
|
||||
const dur = Math.random() * 6 + 4;
|
||||
const delay = Math.random() * 4;
|
||||
p.style.cssText = `left:${x}%;top:${y}%;--dx:${dx}px;--dy:${dy}px;animation:b-particle-float ${dur}s ease-in-out ${delay}s infinite;`;
|
||||
container.appendChild(p);
|
||||
}
|
||||
}
|
||||
|
||||
initParticles();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,447 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>东方韵味 Hero — 水墨扩散 Demo</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0a0a0a; color: #fff; overflow-x: hidden; }
|
||||
|
||||
.nav { position: fixed; top: 0; left: 0; right: 0; z-index: 100; display: flex; gap: 8px; padding: 16px 24px; background: rgba(0,0,0,0.8); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(255,255,255,0.1); }
|
||||
.nav button { padding: 8px 20px; border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; background: transparent; color: #fff; cursor: pointer; font-size: 14px; transition: all 0.3s; }
|
||||
.nav button:hover { background: rgba(255,255,255,0.1); }
|
||||
.nav button.active { background: #C41E3A; border-color: #C41E3A; }
|
||||
|
||||
.demo-section { display: none; position: relative; min-height: 100vh; overflow: hidden; }
|
||||
.demo-section.active { display: flex; }
|
||||
|
||||
.hero-content { position: relative; z-index: 10; max-width: 720px; padding: 0 48px; }
|
||||
.hero-tag { display: inline-flex; align-items: center; gap: 8px; padding: 8px 20px; border-radius: 999px; font-size: 14px; font-weight: 500; margin-bottom: 24px; }
|
||||
.hero-title { font-size: 72px; font-weight: 400; line-height: 1.1; margin-bottom: 20px; letter-spacing: 0.05em; }
|
||||
.hero-subtitle { font-size: 24px; font-weight: 600; margin-bottom: 12px; }
|
||||
.hero-desc { font-size: 18px; line-height: 1.7; margin-bottom: 36px; opacity: 0.6; }
|
||||
.hero-buttons { display: flex; gap: 16px; }
|
||||
.btn-primary { padding: 14px 32px; background: #C41E3A; color: #fff; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; display: flex; align-items: center; gap: 8px; }
|
||||
.btn-outline { padding: 14px 32px; background: transparent; color: #1C1C1C; border: 1px solid rgba(0,0,0,0.15); border-radius: 8px; font-size: 16px; cursor: pointer; }
|
||||
|
||||
.replay-btn { position: fixed; bottom: 24px; right: 24px; z-index: 100; padding: 10px 20px; background: rgba(0,0,0,0.6); color: #fff; border: none; border-radius: 8px; cursor: pointer; font-size: 13px; backdrop-filter: blur(8px); }
|
||||
.replay-btn:hover { background: rgba(0,0,0,0.8); }
|
||||
.style-label { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); padding: 8px 24px; background: rgba(0,0,0,0.7); backdrop-filter: blur(8px); border-radius: 999px; font-size: 13px; color: rgba(255,255,255,0.6); z-index: 100; }
|
||||
|
||||
/* ========== Variant 1: 宣纸底色 + 真实水墨扩散 ========== */
|
||||
#v1 { background: #F7F4EE; color: #1C1C1C; align-items: center; justify-content: flex-start; padding-top: 20vh; }
|
||||
#v1 .hero-tag { background: rgba(196,30,58,0.08); color: #C41E3A; border: 1px solid rgba(196,30,58,0.15); }
|
||||
#v1 .hero-title { font-family: 'Noto Serif SC', 'STSong', 'SimSun', serif; color: #1a1a1a; }
|
||||
#v1 .hero-subtitle { color: #C41E3A; }
|
||||
#v1 .btn-primary { background: #C41E3A; color: #fff; }
|
||||
#v1 .btn-outline { color: #1C1C1C; border-color: rgba(28,28,28,0.2); }
|
||||
|
||||
#v1 .ink-canvas { position: absolute; inset: 0; pointer-events: none; }
|
||||
#v1 .seal { position: absolute; top: 18%; right: 14%; width: 72px; height: 72px; border: 2.5px solid #C41E3A; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-family: 'Noto Serif SC', serif; font-size: 26px; color: #C41E3A; opacity: 0; }
|
||||
#v1 .paper-texture { position: absolute; inset: 0; opacity: 0.03; background-image: url("data:image/svg+xml,%3Csvg width='100' height='100' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence baseFrequency='0.8' numOctaves='4'/%3E%3C/filter%3E%3Crect width='100' height='100' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E"); }
|
||||
|
||||
@keyframes v1-fade-up { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes v1-seal-stamp {
|
||||
0% { transform: scale(2.5) rotate(-15deg); opacity: 0; }
|
||||
50% { transform: scale(0.85) rotate(3deg); opacity: 1; }
|
||||
70% { transform: scale(1.1) rotate(-2deg); }
|
||||
100% { transform: scale(1) rotate(-5deg); opacity: 0.9; }
|
||||
}
|
||||
|
||||
#v1 .hero-tag { animation: v1-fade-up 0.6s ease-out 1.5s both; }
|
||||
#v1 .hero-title { animation: v1-fade-up 0.8s ease-out 1.8s both; }
|
||||
#v1 .hero-subtitle { animation: v1-fade-up 0.6s ease-out 2.1s both; }
|
||||
#v1 .hero-desc { animation: v1-fade-up 0.6s ease-out 2.3s both; }
|
||||
#v1 .hero-buttons { animation: v1-fade-up 0.6s ease-out 2.5s both; }
|
||||
#v1 .seal { animation: v1-seal-stamp 0.8s ease-out 2.8s both; }
|
||||
|
||||
/* ========== Variant 2: 深色 + 金墨扩散 ========== */
|
||||
#v2 { background: #0D0D0D; color: #F5F0E8; align-items: center; justify-content: flex-start; padding-top: 20vh; }
|
||||
#v2 .hero-tag { background: rgba(196,30,58,0.2); color: #ff8a95; border: 1px solid rgba(196,30,58,0.3); }
|
||||
#v2 .hero-title { font-family: 'Noto Serif SC', 'STSong', 'SimSun', serif; color: #F5F0E8; }
|
||||
#v2 .hero-subtitle { color: #C41E3A; }
|
||||
#v2 .hero-desc { color: rgba(245,240,232,0.5); }
|
||||
#v2 .btn-primary { background: #C41E3A; color: #fff; }
|
||||
#v2 .btn-outline { color: #F5F0E8; border-color: rgba(245,240,232,0.2); }
|
||||
|
||||
#v2 .ink-canvas { position: absolute; inset: 0; pointer-events: none; }
|
||||
#v2 .seal { position: absolute; top: 18%; right: 14%; width: 72px; height: 72px; border: 2.5px solid rgba(196,30,58,0.8); border-radius: 6px; display: flex; align-items: center; justify-content: center; font-family: 'Noto Serif SC', serif; font-size: 26px; color: rgba(196,30,58,0.8); opacity: 0; }
|
||||
#v2 .ambient-glow { position: absolute; top: 30%; left: 50%; transform: translate(-50%, -50%); width: 600px; height: 600px; background: radial-gradient(circle, rgba(196,30,58,0.06) 0%, transparent 60%); filter: blur(40px); }
|
||||
|
||||
@keyframes v2-fade-up { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes v2-seal-stamp {
|
||||
0% { transform: scale(2.5) rotate(-15deg); opacity: 0; }
|
||||
50% { transform: scale(0.85) rotate(3deg); opacity: 1; }
|
||||
70% { transform: scale(1.1) rotate(-2deg); }
|
||||
100% { transform: scale(1) rotate(-5deg); opacity: 0.85; }
|
||||
}
|
||||
|
||||
#v2 .hero-tag { animation: v2-fade-up 0.6s ease-out 1.5s both; }
|
||||
#v2 .hero-title { animation: v2-fade-up 0.8s ease-out 1.8s both; }
|
||||
#v2 .hero-subtitle { animation: v2-fade-up 0.6s ease-out 2.1s both; }
|
||||
#v2 .hero-desc { animation: v2-fade-up 0.6s ease-out 2.3s both; }
|
||||
#v2 .hero-buttons { animation: v2-fade-up 0.6s ease-out 2.5s both; }
|
||||
#v2 .seal { animation: v2-seal-stamp 0.8s ease-out 2.8s both; }
|
||||
|
||||
/* ========== Variant 3: 半透明水墨 + 现代极简 ========== */
|
||||
#v3 { background: linear-gradient(170deg, #FAFAF5 0%, #FFF8F0 50%, #FFFFFF 100%); color: #1C1C1C; align-items: center; justify-content: flex-start; padding-top: 20vh; }
|
||||
#v3 .hero-tag { background: #FEF2F4; color: #C41E3A; border: 1px solid rgba(196,30,58,0.1); }
|
||||
#v3 .hero-title { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 64px; letter-spacing: -0.02em; }
|
||||
#v3 .hero-subtitle { color: #C41E3A; }
|
||||
#v3 .btn-primary { background: #C41E3A; color: #fff; }
|
||||
#v3 .btn-outline { color: #1C1C1C; border-color: rgba(28,28,28,0.2); }
|
||||
|
||||
#v3 .ink-canvas { position: absolute; inset: 0; pointer-events: none; }
|
||||
#v3 .seal { position: absolute; bottom: 28%; right: 16%; width: 48px; height: 48px; border: 2px solid rgba(196,30,58,0.5); border-radius: 4px; display: flex; align-items: center; justify-content: center; font-family: 'Noto Serif SC', serif; font-size: 18px; color: rgba(196,30,58,0.5); opacity: 0; }
|
||||
#v3 .deco-dots { position: absolute; top: 22%; right: 20%; display: grid; grid-template-columns: repeat(3, 6px); gap: 12px; }
|
||||
#v3 .deco-dot { width: 6px; height: 6px; border-radius: 50%; background: rgba(196,30,58,0.15); }
|
||||
|
||||
@keyframes v3-fade-up { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes v3-seal-appear {
|
||||
0% { transform: scale(1.8) rotate(-10deg); opacity: 0; }
|
||||
70% { transform: scale(0.95) rotate(1deg); opacity: 0.8; }
|
||||
100% { transform: scale(1) rotate(-3deg); opacity: 0.7; }
|
||||
}
|
||||
@keyframes v3-dot-pulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 0.8; } }
|
||||
|
||||
#v3 .hero-tag { animation: v3-fade-up 0.5s ease-out 1.5s both; }
|
||||
#v3 .hero-title { animation: v3-fade-up 0.6s ease-out 1.7s both; }
|
||||
#v3 .hero-subtitle { animation: v3-fade-up 0.5s ease-out 1.9s both; }
|
||||
#v3 .hero-desc { animation: v3-fade-up 0.5s ease-out 2.1s both; }
|
||||
#v3 .hero-buttons { animation: v3-fade-up 0.5s ease-out 2.3s both; }
|
||||
#v3 .seal { animation: v3-seal-appear 0.6s ease-out 2.5s both; }
|
||||
#v3 .deco-dot { animation: v3-dot-pulse 3s ease-in-out infinite; }
|
||||
#v3 .deco-dot:nth-child(2) { animation-delay: 0.4s; }
|
||||
#v3 .deco-dot:nth-child(3) { animation-delay: 0.8s; }
|
||||
#v3 .deco-dot:nth-child(4) { animation-delay: 0.2s; }
|
||||
#v3 .deco-dot:nth-child(5) { animation-delay: 0.6s; }
|
||||
#v3 .deco-dot:nth-child(6) { animation-delay: 1s; }
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="nav">
|
||||
<button class="active" onclick="switchStyle('v1')">V1 宣纸水墨</button>
|
||||
<button onclick="switchStyle('v2')">V2 深色金墨</button>
|
||||
<button onclick="switchStyle('v3')">V3 水墨极简</button>
|
||||
</div>
|
||||
|
||||
<!-- V1: 宣纸底色 + 真实水墨扩散 -->
|
||||
<section id="v1" class="demo-section active">
|
||||
<div class="paper-texture"></div>
|
||||
<canvas class="ink-canvas" id="ink-canvas-v1"></canvas>
|
||||
<div class="seal">睿</div>
|
||||
<div class="hero-content">
|
||||
<div class="hero-tag">✦ 智连未来,成长伙伴</div>
|
||||
<h1 class="hero-title">睿新致遠</h1>
|
||||
<p class="hero-subtitle">企业数字化转型服务商</p>
|
||||
<p class="hero-desc">以智慧连接数字趋势,以伙伴身份陪您成长——您的数字化转型同行者</p>
|
||||
<div class="hero-buttons">
|
||||
<button class="btn-primary">立即咨询 →</button>
|
||||
<button class="btn-outline">探索产品</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- V2: 深色 + 金墨扩散 -->
|
||||
<section id="v2" class="demo-section">
|
||||
<div class="ambient-glow"></div>
|
||||
<canvas class="ink-canvas" id="ink-canvas-v2"></canvas>
|
||||
<div class="seal">睿</div>
|
||||
<div class="hero-content">
|
||||
<div class="hero-tag">✦ 智连未来,成长伙伴</div>
|
||||
<h1 class="hero-title">睿新致遠</h1>
|
||||
<p class="hero-subtitle">企业数字化转型服务商</p>
|
||||
<p class="hero-desc">以智慧连接数字趋势,以伙伴身份陪您成长——您的数字化转型同行者</p>
|
||||
<div class="hero-buttons">
|
||||
<button class="btn-primary">立即咨询 →</button>
|
||||
<button class="btn-outline">探索产品</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- V3: 水墨极简 -->
|
||||
<section id="v3" class="demo-section">
|
||||
<canvas class="ink-canvas" id="ink-canvas-v3"></canvas>
|
||||
<div class="deco-dots">
|
||||
<div class="deco-dot"></div><div class="deco-dot"></div><div class="deco-dot"></div>
|
||||
<div class="deco-dot"></div><div class="deco-dot"></div><div class="deco-dot"></div>
|
||||
</div>
|
||||
<div class="seal">睿</div>
|
||||
<div class="hero-content">
|
||||
<div class="hero-tag">✦ 智连未来,成长伙伴</div>
|
||||
<h1 class="hero-title">睿新致遠</h1>
|
||||
<p class="hero-subtitle">企业数字化转型服务商</p>
|
||||
<p class="hero-desc">以智慧连接数字趋势,以伙伴身份陪您成长——您的数字化转型同行者</p>
|
||||
<div class="hero-buttons">
|
||||
<button class="btn-primary">立即咨询 →</button>
|
||||
<button class="btn-outline">探索产品</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="style-label" id="style-label">V1 宣纸水墨 — 传统宣纸底色 + 水墨扩散 + 印章</div>
|
||||
<button class="replay-btn" onclick="replayAnimation()">↻ 重播动画</button>
|
||||
|
||||
<script>
|
||||
const labels = {
|
||||
v1: 'V1 宣纸水墨 — 传统宣纸底色 + 水墨扩散 + 印章',
|
||||
v2: 'V2 深色金墨 — 深色背景 + 金色/朱砂水墨 + 印章',
|
||||
v3: 'V3 水墨极简 — 浅色现代 + 半透明水墨 + 微装饰',
|
||||
};
|
||||
|
||||
let currentStyle = 'v1';
|
||||
let animFrames = {};
|
||||
|
||||
function switchStyle(style) {
|
||||
currentStyle = style;
|
||||
document.querySelectorAll('.demo-section').forEach(s => s.classList.remove('active'));
|
||||
document.querySelectorAll('.nav button').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById(style).classList.add('active');
|
||||
document.querySelectorAll('.nav button')[['v1','v2','v3'].indexOf(style)].classList.add('active');
|
||||
document.getElementById('style-label').textContent = labels[style];
|
||||
replayAnimation();
|
||||
}
|
||||
|
||||
function replayAnimation() {
|
||||
Object.values(animFrames).forEach(id => cancelAnimationFrame(id));
|
||||
animFrames = {};
|
||||
|
||||
const active = document.querySelector('.demo-section.active');
|
||||
const clone = active.cloneNode(true);
|
||||
active.parentNode.replaceChild(clone, active);
|
||||
|
||||
const canvasId = 'ink-canvas-' + currentStyle;
|
||||
const canvas = document.getElementById(canvasId);
|
||||
if (canvas) {
|
||||
if (currentStyle === 'v1') startInkAnimationV1(canvas);
|
||||
else if (currentStyle === 'v2') startInkAnimationV2(canvas);
|
||||
else if (currentStyle === 'v3') startInkAnimationV3(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Ink Spread Animation Engine
|
||||
// ==========================================
|
||||
|
||||
class InkDroplet {
|
||||
constructor(x, y, maxRadius, color, speed, canvas) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.maxRadius = maxRadius;
|
||||
this.color = color;
|
||||
this.speed = speed;
|
||||
this.canvas = canvas;
|
||||
this.radius = 0;
|
||||
this.opacity = 0;
|
||||
this.phase = 'spreading'; // spreading -> settling -> done
|
||||
this.wobble = Math.random() * Math.PI * 2;
|
||||
this.wobbleSpeed = 0.02 + Math.random() * 0.02;
|
||||
this.wobbleAmp = 0.15 + Math.random() * 0.1;
|
||||
this.edgeParticles = [];
|
||||
this.settleTimer = 0;
|
||||
this.branches = [];
|
||||
|
||||
const branchCount = 3 + Math.floor(Math.random() * 4);
|
||||
for (let i = 0; i < branchCount; i++) {
|
||||
const angle = (Math.PI * 2 / branchCount) * i + (Math.random() - 0.5) * 0.5;
|
||||
this.branches.push({
|
||||
angle,
|
||||
length: 0,
|
||||
maxLength: maxRadius * (0.3 + Math.random() * 0.5),
|
||||
width: 2 + Math.random() * 4,
|
||||
speed: speed * (0.6 + Math.random() * 0.4),
|
||||
wobble: Math.random() * Math.PI * 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.phase === 'spreading') {
|
||||
this.radius += this.speed;
|
||||
this.wobble += this.wobbleSpeed;
|
||||
this.opacity = Math.min(0.6, this.radius / this.maxRadius * 0.6);
|
||||
|
||||
for (const b of this.branches) {
|
||||
if (b.length < b.maxLength) {
|
||||
b.length += b.speed;
|
||||
b.wobble += 0.03;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.radius >= this.maxRadius) {
|
||||
this.phase = 'settling';
|
||||
}
|
||||
} else if (this.phase === 'settling') {
|
||||
this.settleTimer++;
|
||||
this.opacity = Math.max(0.08, this.opacity - 0.003);
|
||||
this.wobble += this.wobbleSpeed * 0.5;
|
||||
if (this.settleTimer > 120) {
|
||||
this.phase = 'done';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
if (this.opacity <= 0) return;
|
||||
|
||||
// Main ink body with organic edge
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.opacity;
|
||||
|
||||
// Draw organic ink shape
|
||||
ctx.beginPath();
|
||||
const segments = 60;
|
||||
for (let i = 0; i <= segments; i++) {
|
||||
const angle = (Math.PI * 2 / segments) * i;
|
||||
const wobbleOffset = Math.sin(angle * 3 + this.wobble) * this.wobbleAmp
|
||||
+ Math.sin(angle * 5 + this.wobble * 1.3) * this.wobbleAmp * 0.5;
|
||||
const r = this.radius * (1 + wobbleOffset);
|
||||
const px = this.x + Math.cos(angle) * r;
|
||||
const py = this.y + Math.sin(angle) * r;
|
||||
if (i === 0) ctx.moveTo(px, py);
|
||||
else ctx.lineTo(px, py);
|
||||
}
|
||||
ctx.closePath();
|
||||
|
||||
// Radial gradient for ink density
|
||||
const grad = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius * 1.2);
|
||||
grad.addColorStop(0, this.color);
|
||||
grad.addColorStop(0.6, this.color);
|
||||
grad.addColorStop(1, 'transparent');
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fill();
|
||||
|
||||
// Draw branches (毛细扩散)
|
||||
for (const b of this.branches) {
|
||||
if (b.length <= 0) continue;
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = b.width;
|
||||
ctx.lineCap = 'round';
|
||||
const startR = this.radius * 0.6;
|
||||
const sx = this.x + Math.cos(b.angle) * startR;
|
||||
const sy = this.y + Math.sin(b.angle) * startR;
|
||||
const wobbleX = Math.sin(b.wobble) * 8;
|
||||
const wobbleY = Math.cos(b.wobble * 0.7) * 8;
|
||||
const ex = this.x + Math.cos(b.angle) * (startR + b.length) + wobbleX;
|
||||
const ey = this.y + Math.sin(b.angle) * (startR + b.length) + wobbleY;
|
||||
const cpx = (sx + ex) / 2 + wobbleX * 0.5;
|
||||
const cpy = (sy + ey) / 2 + wobbleY * 0.5;
|
||||
ctx.moveTo(sx, sy);
|
||||
ctx.quadraticCurveTo(cpx, cpy, ex, ey);
|
||||
ctx.strokeStyle = this.color;
|
||||
ctx.globalAlpha = this.opacity * 0.5;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
function startInkAnimationV1(canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
|
||||
const droplets = [];
|
||||
const inkColor = 'rgba(28, 28, 28, 0.7)';
|
||||
|
||||
// Main large droplet
|
||||
droplets.push(new InkDroplet(canvas.width * 0.7, canvas.height * 0.35, 250, inkColor, 2.5, canvas));
|
||||
// Secondary droplets
|
||||
setTimeout(() => {
|
||||
droplets.push(new InkDroplet(canvas.width * 0.25, canvas.height * 0.7, 150, inkColor, 1.8, canvas));
|
||||
}, 400);
|
||||
setTimeout(() => {
|
||||
droplets.push(new InkDroplet(canvas.width * 0.8, canvas.height * 0.75, 100, inkColor, 1.5, canvas));
|
||||
}, 700);
|
||||
// Red accent droplet
|
||||
setTimeout(() => {
|
||||
droplets.push(new InkDroplet(canvas.width * 0.72, canvas.height * 0.32, 60, 'rgba(196, 30, 58, 0.5)', 1.2, canvas));
|
||||
}, 600);
|
||||
|
||||
function animate() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (const d of droplets) {
|
||||
d.update();
|
||||
d.draw(ctx);
|
||||
}
|
||||
animFrames['v1'] = requestAnimationFrame(animate);
|
||||
}
|
||||
animate();
|
||||
}
|
||||
|
||||
function startInkAnimationV2(canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
|
||||
const droplets = [];
|
||||
const goldColor = 'rgba(212, 165, 116, 0.5)';
|
||||
const redColor = 'rgba(196, 30, 58, 0.4)';
|
||||
|
||||
// Gold ink spread
|
||||
droplets.push(new InkDroplet(canvas.width * 0.65, canvas.height * 0.3, 300, goldColor, 2, canvas));
|
||||
setTimeout(() => {
|
||||
droplets.push(new InkDroplet(canvas.width * 0.3, canvas.height * 0.65, 180, goldColor, 1.5, canvas));
|
||||
}, 500);
|
||||
// Red accent
|
||||
setTimeout(() => {
|
||||
droplets.push(new InkDroplet(canvas.width * 0.67, canvas.height * 0.28, 80, redColor, 1, canvas));
|
||||
}, 800);
|
||||
// Subtle dark ink
|
||||
setTimeout(() => {
|
||||
droplets.push(new InkDroplet(canvas.width * 0.8, canvas.height * 0.7, 120, 'rgba(245, 240, 232, 0.15)', 1.2, canvas));
|
||||
}, 600);
|
||||
|
||||
function animate() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (const d of droplets) {
|
||||
d.update();
|
||||
d.draw(ctx);
|
||||
}
|
||||
animFrames['v2'] = requestAnimationFrame(animate);
|
||||
}
|
||||
animate();
|
||||
}
|
||||
|
||||
function startInkAnimationV3(canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
|
||||
const droplets = [];
|
||||
const lightInk = 'rgba(28, 28, 28, 0.12)';
|
||||
const redHint = 'rgba(196, 30, 58, 0.15)';
|
||||
|
||||
// Very subtle, translucent ink
|
||||
droplets.push(new InkDroplet(canvas.width * 0.7, canvas.height * 0.3, 200, lightInk, 1.8, canvas));
|
||||
setTimeout(() => {
|
||||
droplets.push(new InkDroplet(canvas.width * 0.2, canvas.height * 0.7, 120, lightInk, 1.2, canvas));
|
||||
}, 600);
|
||||
setTimeout(() => {
|
||||
droplets.push(new InkDroplet(canvas.width * 0.72, canvas.height * 0.28, 50, redHint, 0.8, canvas));
|
||||
}, 900);
|
||||
|
||||
function animate() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (const d of droplets) {
|
||||
d.update();
|
||||
d.draw(ctx);
|
||||
}
|
||||
animFrames['v3'] = requestAnimationFrame(animate);
|
||||
}
|
||||
animate();
|
||||
}
|
||||
|
||||
// Init
|
||||
startInkAnimationV1(document.getElementById('ink-canvas-v1'));
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
replayAnimation();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,779 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hero 动画 — 精选方案</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body { background: #0a0a0a; color: #fff; overflow-x: hidden; }
|
||||
|
||||
.nav {
|
||||
position: fixed; top: 0; left: 0; right: 0; z-index: 1000;
|
||||
display: flex; gap: 6px; padding: 14px 20px;
|
||||
background: rgba(0,0,0,0.85); backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.nav button {
|
||||
padding: 8px 18px; border: 1px solid rgba(255,255,255,0.12); border-radius: 6px;
|
||||
background: transparent; color: rgba(255,255,255,0.6); cursor: pointer;
|
||||
font-size: 13px; font-weight: 500; transition: all 0.3s; letter-spacing: 0.02em;
|
||||
}
|
||||
.nav button:hover { background: rgba(255,255,255,0.06); color: #fff; }
|
||||
.nav button.active { background: #C41E3A; border-color: #C41E3A; color: #fff; }
|
||||
|
||||
.scene { display: none; position: relative; width: 100vw; height: 100vh; overflow: hidden; }
|
||||
.scene.active { display: block; }
|
||||
|
||||
.replay-btn {
|
||||
position: fixed; bottom: 20px; right: 20px; z-index: 1000;
|
||||
padding: 10px 18px; background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.5);
|
||||
border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; cursor: pointer;
|
||||
font-size: 12px; backdrop-filter: blur(12px); transition: all 0.3s;
|
||||
}
|
||||
.replay-btn:hover { background: rgba(255,255,255,0.15); color: #fff; }
|
||||
|
||||
.hint {
|
||||
position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 1000;
|
||||
padding: 8px 20px; background: rgba(0,0,0,0.6); backdrop-filter: blur(12px);
|
||||
border-radius: 999px; font-size: 12px; color: rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* Scene 1: 墨韵流光 — Cinematic Ink Flow */
|
||||
/* ============================================ */
|
||||
#s1 { background: #080808; }
|
||||
#s1 canvas { position: absolute; inset: 0; }
|
||||
|
||||
#s1 .content {
|
||||
position: absolute; inset: 0; z-index: 10;
|
||||
display: flex; flex-direction: column; justify-content: center;
|
||||
padding: 0 8vw;
|
||||
}
|
||||
#s1 .tag {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
padding: 6px 16px; border-radius: 999px;
|
||||
font-size: 13px; font-weight: 500; letter-spacing: 0.08em;
|
||||
background: rgba(196,30,58,0.12); color: #e85d6f;
|
||||
border: 1px solid rgba(196,30,58,0.2);
|
||||
opacity: 0; transform: translateY(16px);
|
||||
}
|
||||
#s1 h1 {
|
||||
font-family: 'Noto Serif SC', serif; font-size: clamp(48px, 7vw, 88px);
|
||||
font-weight: 300; letter-spacing: 0.12em; line-height: 1.15;
|
||||
color: #F5F0E8; margin: 20px 0 16px;
|
||||
opacity: 0; transform: translateY(20px);
|
||||
}
|
||||
#s1 .accent { color: #C41E3A; font-weight: 500; }
|
||||
#s1 .sub {
|
||||
font-size: clamp(16px, 2vw, 22px); color: rgba(245,240,232,0.45);
|
||||
font-weight: 300; letter-spacing: 0.04em; line-height: 1.6;
|
||||
max-width: 520px; margin-bottom: 36px;
|
||||
opacity: 0; transform: translateY(16px);
|
||||
}
|
||||
#s1 .btns { display: flex; gap: 14px; opacity: 0; transform: translateY(16px); }
|
||||
#s1 .btn-p {
|
||||
padding: 13px 28px; background: #C41E3A; color: #fff; border: none;
|
||||
border-radius: 6px; font-size: 15px; font-weight: 500; cursor: pointer;
|
||||
letter-spacing: 0.04em; transition: all 0.3s;
|
||||
}
|
||||
#s1 .btn-p:hover { background: #d4213f; transform: translateY(-1px); box-shadow: 0 8px 24px rgba(196,30,58,0.3); }
|
||||
#s1 .btn-o {
|
||||
padding: 13px 28px; background: transparent; color: rgba(245,240,232,0.7);
|
||||
border: 1px solid rgba(245,240,232,0.15); border-radius: 6px;
|
||||
font-size: 15px; cursor: pointer; letter-spacing: 0.04em; transition: all 0.3s;
|
||||
}
|
||||
#s1 .btn-o:hover { border-color: rgba(245,240,232,0.3); color: #F5F0E8; }
|
||||
|
||||
#s1 .seal {
|
||||
position: absolute; top: 18%; right: 12%; z-index: 10;
|
||||
width: 64px; height: 64px; border: 2px solid rgba(196,30,58,0.7);
|
||||
border-radius: 4px; display: flex; align-items: center; justify-content: center;
|
||||
font-family: 'Noto Serif SC', serif; font-size: 24px; color: rgba(196,30,58,0.7);
|
||||
opacity: 0; transform: scale(2) rotate(-15deg);
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* Scene 2: 山水意境 — Layered Mountain Mist */
|
||||
/* ============================================ */
|
||||
#s2 { background: linear-gradient(180deg, #1a1a2e 0%, #16213e 30%, #0f3460 60%, #1a1a2e 100%); }
|
||||
#s2 canvas { position: absolute; inset: 0; }
|
||||
|
||||
#s2 .content {
|
||||
position: absolute; inset: 0; z-index: 10;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
text-align: center; padding: 0 6vw;
|
||||
}
|
||||
#s2 .tag {
|
||||
display: inline-flex; padding: 6px 16px; border-radius: 999px;
|
||||
font-size: 13px; font-weight: 500; letter-spacing: 0.08em;
|
||||
background: rgba(196,30,58,0.15); color: #ff8a95;
|
||||
border: 1px solid rgba(196,30,58,0.25);
|
||||
opacity: 0; transform: translateY(16px);
|
||||
}
|
||||
#s2 h1 {
|
||||
font-family: 'Noto Serif SC', serif; font-size: clamp(52px, 8vw, 96px);
|
||||
font-weight: 300; letter-spacing: 0.15em; line-height: 1.1;
|
||||
color: #F5F0E8; margin: 24px 0 16px;
|
||||
opacity: 0; transform: translateY(20px);
|
||||
}
|
||||
#s2 .accent { color: #C41E3A; }
|
||||
#s2 .sub {
|
||||
font-size: clamp(15px, 1.8vw, 20px); color: rgba(245,240,232,0.4);
|
||||
font-weight: 300; letter-spacing: 0.06em; line-height: 1.7;
|
||||
max-width: 480px; margin-bottom: 36px;
|
||||
opacity: 0; transform: translateY(16px);
|
||||
}
|
||||
#s2 .btns { display: flex; gap: 14px; opacity: 0; transform: translateY(16px); }
|
||||
#s2 .btn-p {
|
||||
padding: 13px 28px; background: #C41E3A; color: #fff; border: none;
|
||||
border-radius: 6px; font-size: 15px; font-weight: 500; cursor: pointer;
|
||||
letter-spacing: 0.04em; transition: all 0.3s;
|
||||
}
|
||||
#s2 .btn-p:hover { background: #d4213f; transform: translateY(-1px); box-shadow: 0 8px 24px rgba(196,30,58,0.3); }
|
||||
#s2 .btn-o {
|
||||
padding: 13px 28px; background: transparent; color: rgba(245,240,232,0.6);
|
||||
border: 1px solid rgba(245,240,232,0.15); border-radius: 6px;
|
||||
font-size: 15px; cursor: pointer; transition: all 0.3s;
|
||||
}
|
||||
#s2 .btn-o:hover { border-color: rgba(245,240,232,0.3); color: #F5F0E8; }
|
||||
|
||||
/* ============================================ */
|
||||
/* Scene 3: 数字水墨 — Ink-to-Data Morphing */
|
||||
/* ============================================ */
|
||||
#s3 { background: #FAFAF5; }
|
||||
#s3 canvas { position: absolute; inset: 0; }
|
||||
|
||||
#s3 .content {
|
||||
position: absolute; inset: 0; z-index: 10;
|
||||
display: flex; flex-direction: column; justify-content: center;
|
||||
padding: 0 8vw;
|
||||
}
|
||||
#s3 .tag {
|
||||
display: inline-flex; padding: 6px 16px; border-radius: 999px;
|
||||
font-size: 13px; font-weight: 500; letter-spacing: 0.06em;
|
||||
background: #FEF2F4; color: #C41E3A; border: 1px solid rgba(196,30,58,0.1);
|
||||
opacity: 0; transform: translateY(16px);
|
||||
}
|
||||
#s3 h1 {
|
||||
font-family: 'Inter', -apple-system, sans-serif;
|
||||
font-size: clamp(44px, 6.5vw, 80px); font-weight: 600;
|
||||
letter-spacing: -0.03em; line-height: 1.05;
|
||||
color: #1C1C1C; margin: 20px 0 12px;
|
||||
opacity: 0; transform: translateY(20px);
|
||||
}
|
||||
#s3 .accent { color: #C41E3A; font-weight: 600; }
|
||||
#s3 .sub {
|
||||
font-size: clamp(16px, 1.8vw, 20px); color: rgba(28,28,28,0.5);
|
||||
font-weight: 400; line-height: 1.7; max-width: 520px; margin-bottom: 36px;
|
||||
opacity: 0; transform: translateY(16px);
|
||||
}
|
||||
#s3 .btns { display: flex; gap: 14px; opacity: 0; transform: translateY(16px); }
|
||||
#s3 .btn-p {
|
||||
padding: 13px 28px; background: #C41E3A; color: #fff; border: none;
|
||||
border-radius: 8px; font-size: 15px; font-weight: 500; cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
#s3 .btn-p:hover { background: #d4213f; transform: translateY(-1px); box-shadow: 0 8px 24px rgba(196,30,58,0.2); }
|
||||
#s3 .btn-o {
|
||||
padding: 13px 28px; background: transparent; color: #1C1C1C;
|
||||
border: 1px solid rgba(28,28,28,0.15); border-radius: 8px;
|
||||
font-size: 15px; cursor: pointer; transition: all 0.3s;
|
||||
}
|
||||
#s3 .btn-o:hover { border-color: rgba(28,28,28,0.3); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="nav">
|
||||
<button class="active" onclick="go('s1')">壹 · 墨韵流光</button>
|
||||
<button onclick="go('s2')">贰 · 山水意境</button>
|
||||
<button onclick="go('s3')">叁 · 数字水墨</button>
|
||||
</div>
|
||||
|
||||
<!-- Scene 1: 墨韵流光 -->
|
||||
<div id="s1" class="scene active">
|
||||
<canvas id="c1"></canvas>
|
||||
<div class="seal">睿</div>
|
||||
<div class="content">
|
||||
<div class="tag">✦ 智连未来,成长伙伴</div>
|
||||
<h1>睿新<span class="accent">致遠</span></h1>
|
||||
<p class="sub">以智慧连接数字趋势,以伙伴身份陪您成长<br/>——您的数字化转型同行者</p>
|
||||
<div class="btns">
|
||||
<button class="btn-p">立即咨询 →</button>
|
||||
<button class="btn-o">探索产品</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scene 2: 山水意境 -->
|
||||
<div id="s2" class="scene">
|
||||
<canvas id="c2"></canvas>
|
||||
<div class="content">
|
||||
<div class="tag">✦ 智连未来,成长伙伴</div>
|
||||
<h1>睿新<span class="accent">致遠</span></h1>
|
||||
<p class="sub">以智慧连接数字趋势<br/>以伙伴身份陪您成长</p>
|
||||
<div class="btns">
|
||||
<button class="btn-p">立即咨询 →</button>
|
||||
<button class="btn-o">探索产品</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scene 3: 数字水墨 -->
|
||||
<div id="s3" class="scene">
|
||||
<canvas id="c3"></canvas>
|
||||
<div class="content">
|
||||
<div class="tag">✦ 智连未来,成长伙伴</div>
|
||||
<h1>睿新<span class="accent">致遠</span></h1>
|
||||
<p class="sub">以智慧连接数字趋势,以伙伴身份陪您成长——您的数字化转型同行者</p>
|
||||
<div class="btns">
|
||||
<button class="btn-p">立即咨询 →</button>
|
||||
<button class="btn-o">探索产品</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hint" id="hint">壹 · 墨韵流光 — 流动水墨 + 光晕 + 印章</div>
|
||||
<button class="replay-btn" onclick="replay()">↻ 重播</button>
|
||||
|
||||
<script>
|
||||
const hints = {
|
||||
s1: '壹 · 墨韵流光 — 流动水墨 + 光晕 + 印章',
|
||||
s2: '贰 · 山水意境 — 层叠山峦 + 云雾 + 星点',
|
||||
s3: '叁 · 数字水墨 — 墨粒扩散 + 数据化消融',
|
||||
};
|
||||
let current = 's1';
|
||||
let rafs = {};
|
||||
|
||||
function go(id) {
|
||||
current = id;
|
||||
document.querySelectorAll('.scene').forEach(s => s.classList.remove('active'));
|
||||
document.getElementById(id).classList.add('active');
|
||||
document.querySelectorAll('.nav button').forEach((b, i) => {
|
||||
b.classList.toggle('active', ['s1','s2','s3'][i] === id);
|
||||
});
|
||||
document.getElementById('hint').textContent = hints[id];
|
||||
replay();
|
||||
}
|
||||
|
||||
function replay() {
|
||||
Object.values(rafs).forEach(id => cancelAnimationFrame(id));
|
||||
rafs = {};
|
||||
|
||||
const scene = document.getElementById(current);
|
||||
const clone = scene.cloneNode(true);
|
||||
scene.parentNode.replaceChild(clone, scene);
|
||||
|
||||
if (current === 's1') initS1();
|
||||
else if (current === 's2') initS2();
|
||||
else if (current === 's3') initS3();
|
||||
|
||||
animateContent(current);
|
||||
}
|
||||
|
||||
function animateContent(id) {
|
||||
const scene = document.getElementById(id);
|
||||
const els = scene.querySelectorAll('.tag, h1, .sub, .btns, .seal');
|
||||
const delays = [1.6, 1.9, 2.2, 2.5, 2.8];
|
||||
|
||||
els.forEach((el, i) => {
|
||||
if (i >= delays.length) return;
|
||||
setTimeout(() => {
|
||||
el.style.transition = 'all 0.8s cubic-bezier(0.16, 1, 0.3, 1)';
|
||||
el.style.opacity = '1';
|
||||
el.style.transform = el.classList.contains('seal') ? 'scale(1) rotate(-5deg)' : 'translateY(0)';
|
||||
}, delays[i] * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================= */
|
||||
/* Scene 1: 墨韵流光 — Flowing Ink Streams */
|
||||
/* ============================================= */
|
||||
function initS1() {
|
||||
const canvas = document.getElementById('c1');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = window.innerWidth * 2;
|
||||
canvas.height = window.innerHeight * 2;
|
||||
canvas.style.width = '100%';
|
||||
canvas.style.height = '100%';
|
||||
ctx.scale(2, 2);
|
||||
|
||||
const W = window.innerWidth;
|
||||
const H = window.innerHeight;
|
||||
|
||||
// Ink streams
|
||||
class InkStream {
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
reset() {
|
||||
this.x = W * (0.5 + Math.random() * 0.5);
|
||||
this.y = -50;
|
||||
this.vx = (Math.random() - 0.6) * 0.8;
|
||||
this.vy = 0.5 + Math.random() * 1.5;
|
||||
this.width = 1 + Math.random() * 3;
|
||||
this.opacity = 0;
|
||||
this.targetOpacity = 0.08 + Math.random() * 0.15;
|
||||
this.trail = [];
|
||||
this.maxTrail = 80 + Math.floor(Math.random() * 60);
|
||||
this.hue = Math.random() > 0.7 ? 0 : 0; // black or red tint
|
||||
this.sat = Math.random() > 0.7 ? 60 : 0;
|
||||
this.light = this.sat > 0 ? 25 : 10 + Math.random() * 8;
|
||||
this.delay = Math.random() * 120;
|
||||
this.age = 0;
|
||||
this.wobblePhase = Math.random() * Math.PI * 2;
|
||||
this.wobbleSpeed = 0.01 + Math.random() * 0.02;
|
||||
this.wobbleAmp = 0.3 + Math.random() * 0.5;
|
||||
}
|
||||
update() {
|
||||
this.age++;
|
||||
if (this.age < this.delay) return;
|
||||
|
||||
this.wobblePhase += this.wobbleSpeed;
|
||||
this.vx += Math.sin(this.wobblePhase) * this.wobbleAmp * 0.02;
|
||||
this.vy += 0.01;
|
||||
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
|
||||
this.trail.push({ x: this.x, y: this.y, w: this.width });
|
||||
if (this.trail.length > this.maxTrail) this.trail.shift();
|
||||
|
||||
this.opacity = Math.min(this.targetOpacity, this.opacity + 0.002);
|
||||
|
||||
if (this.y > H + 100) this.reset();
|
||||
}
|
||||
draw(ctx) {
|
||||
if (this.age < this.delay || this.trail.length < 3) return;
|
||||
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.opacity;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
for (let i = 1; i < this.trail.length; i++) {
|
||||
const p0 = this.trail[i - 1];
|
||||
const p1 = this.trail[i];
|
||||
const t = i / this.trail.length;
|
||||
const alpha = t * this.opacity;
|
||||
const w = p1.w * (0.3 + t * 0.7);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p0.x, p0.y);
|
||||
ctx.lineTo(p1.x, p1.y);
|
||||
ctx.strokeStyle = `hsla(${this.hue}, ${this.sat}%, ${this.light}%, ${alpha})`;
|
||||
ctx.lineWidth = w;
|
||||
ctx.stroke();
|
||||
|
||||
// Ink bleed effect
|
||||
if (Math.random() < 0.03 && t > 0.3) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(p1.x, p1.y, w * 3 + Math.random() * 8, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `hsla(${this.hue}, ${this.sat}%, ${this.light}%, ${alpha * 0.3})`;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
// Glow orbs
|
||||
class GlowOrb {
|
||||
constructor() {
|
||||
this.x = W * (0.3 + Math.random() * 0.5);
|
||||
this.y = H * (0.2 + Math.random() * 0.4);
|
||||
this.radius = 150 + Math.random() * 200;
|
||||
this.vx = (Math.random() - 0.5) * 0.3;
|
||||
this.vy = (Math.random() - 0.5) * 0.2;
|
||||
this.isRed = Math.random() > 0.6;
|
||||
this.opacity = 0;
|
||||
this.targetOpacity = this.isRed ? 0.06 : 0.04;
|
||||
this.phase = Math.random() * Math.PI * 2;
|
||||
}
|
||||
update() {
|
||||
this.phase += 0.005;
|
||||
this.x += this.vx + Math.sin(this.phase) * 0.2;
|
||||
this.y += this.vy + Math.cos(this.phase * 0.7) * 0.15;
|
||||
this.opacity = Math.min(this.targetOpacity, this.opacity + 0.0005);
|
||||
if (this.x < -200 || this.x > W + 200) this.vx *= -1;
|
||||
if (this.y < -200 || this.y > H + 200) this.vy *= -1;
|
||||
}
|
||||
draw(ctx) {
|
||||
const grad = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius);
|
||||
const color = this.isRed ? '196, 30, 58' : '180, 160, 140';
|
||||
grad.addColorStop(0, `rgba(${color}, ${this.opacity})`);
|
||||
grad.addColorStop(0.5, `rgba(${color}, ${this.opacity * 0.4})`);
|
||||
grad.addColorStop(1, `rgba(${color}, 0)`);
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2);
|
||||
}
|
||||
}
|
||||
|
||||
const streams = Array.from({ length: 8 }, () => new InkStream());
|
||||
const orbs = Array.from({ length: 4 }, () => new GlowOrb());
|
||||
|
||||
// Initial big ink splash
|
||||
let splashProgress = 0;
|
||||
const splashCenter = { x: W * 0.7, y: H * 0.35 };
|
||||
|
||||
function drawSplash(ctx) {
|
||||
if (splashProgress >= 1) return;
|
||||
splashProgress = Math.min(1, splashProgress + 0.008);
|
||||
|
||||
const maxR = 280;
|
||||
const r = maxR * easeOutQuart(splashProgress);
|
||||
const alpha = 0.12 * (1 - splashProgress * 0.5);
|
||||
|
||||
ctx.save();
|
||||
ctx.globalAlpha = alpha;
|
||||
|
||||
// Main splash
|
||||
const grad = ctx.createRadialGradient(splashCenter.x, splashCenter.y, 0, splashCenter.x, splashCenter.y, r);
|
||||
grad.addColorStop(0, 'rgba(28, 28, 28, 0.6)');
|
||||
grad.addColorStop(0.4, 'rgba(28, 28, 28, 0.3)');
|
||||
grad.addColorStop(0.7, 'rgba(28, 28, 28, 0.1)');
|
||||
grad.addColorStop(1, 'transparent');
|
||||
ctx.fillStyle = grad;
|
||||
ctx.beginPath();
|
||||
ctx.arc(splashCenter.x, splashCenter.y, r, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Red accent splash
|
||||
const redR = r * 0.4;
|
||||
const redGrad = ctx.createRadialGradient(splashCenter.x + 20, splashCenter.y - 10, 0, splashCenter.x + 20, splashCenter.y - 10, redR);
|
||||
redGrad.addColorStop(0, 'rgba(196, 30, 58, 0.4)');
|
||||
redGrad.addColorStop(0.5, 'rgba(196, 30, 58, 0.15)');
|
||||
redGrad.addColorStop(1, 'transparent');
|
||||
ctx.fillStyle = redGrad;
|
||||
ctx.beginPath();
|
||||
ctx.arc(splashCenter.x + 20, splashCenter.y - 10, redR, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function animate() {
|
||||
ctx.fillStyle = 'rgba(8, 8, 8, 0.04)';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
drawSplash(ctx);
|
||||
|
||||
for (const orb of orbs) { orb.update(); orb.draw(ctx); }
|
||||
for (const stream of streams) { stream.update(); stream.draw(ctx); }
|
||||
|
||||
rafs['s1'] = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
ctx.fillStyle = '#080808';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
animate();
|
||||
}
|
||||
|
||||
/* ============================================= */
|
||||
/* Scene 2: 山水意境 — Layered Mountains + Mist */
|
||||
/* ============================================= */
|
||||
function initS2() {
|
||||
const canvas = document.getElementById('c2');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = window.innerWidth * 2;
|
||||
canvas.height = window.innerHeight * 2;
|
||||
canvas.style.width = '100%';
|
||||
canvas.style.height = '100%';
|
||||
ctx.scale(2, 2);
|
||||
|
||||
const W = window.innerWidth;
|
||||
const H = window.innerHeight;
|
||||
|
||||
// Mountain layers
|
||||
function drawMountain(ctx, baseY, height, color, offset, segments) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-10, H);
|
||||
for (let i = 0; i <= segments; i++) {
|
||||
const x = (W / segments) * i;
|
||||
const noise = Math.sin(i * 0.3 + offset) * height * 0.4
|
||||
+ Math.sin(i * 0.7 + offset * 1.5) * height * 0.3
|
||||
+ Math.sin(i * 1.3 + offset * 0.8) * height * 0.15;
|
||||
const y = baseY - Math.abs(noise);
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.lineTo(W + 10, H);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Stars
|
||||
const stars = Array.from({ length: 80 }, () => ({
|
||||
x: Math.random() * W,
|
||||
y: Math.random() * H * 0.5,
|
||||
r: 0.5 + Math.random() * 1.5,
|
||||
twinkle: Math.random() * Math.PI * 2,
|
||||
speed: 0.02 + Math.random() * 0.03,
|
||||
}));
|
||||
|
||||
// Mist particles
|
||||
const mist = Array.from({ length: 20 }, () => ({
|
||||
x: Math.random() * W,
|
||||
y: H * (0.4 + Math.random() * 0.3),
|
||||
w: 200 + Math.random() * 400,
|
||||
h: 30 + Math.random() * 60,
|
||||
speed: 0.15 + Math.random() * 0.3,
|
||||
opacity: 0.02 + Math.random() * 0.04,
|
||||
}));
|
||||
|
||||
// Red accent light
|
||||
let redGlowProgress = 0;
|
||||
|
||||
let time = 0;
|
||||
|
||||
function animate() {
|
||||
time += 0.003;
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
// Sky gradient
|
||||
const skyGrad = ctx.createLinearGradient(0, 0, 0, H);
|
||||
skyGrad.addColorStop(0, '#0a0a1a');
|
||||
skyGrad.addColorStop(0.4, '#16213e');
|
||||
skyGrad.addColorStop(0.7, '#1a1a3e');
|
||||
skyGrad.addColorStop(1, '#0a0a1a');
|
||||
ctx.fillStyle = skyGrad;
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
// Stars
|
||||
for (const s of stars) {
|
||||
s.twinkle += s.speed;
|
||||
const alpha = 0.3 + Math.sin(s.twinkle) * 0.3;
|
||||
ctx.beginPath();
|
||||
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(245, 240, 232, ${alpha})`;
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Red glow behind mountains
|
||||
redGlowProgress = Math.min(1, redGlowProgress + 0.005);
|
||||
const glowAlpha = 0.08 * redGlowProgress;
|
||||
const glowGrad = ctx.createRadialGradient(W * 0.5, H * 0.35, 0, W * 0.5, H * 0.35, 400);
|
||||
glowGrad.addColorStop(0, `rgba(196, 30, 58, ${glowAlpha})`);
|
||||
glowGrad.addColorStop(0.5, `rgba(196, 30, 58, ${glowAlpha * 0.3})`);
|
||||
glowGrad.addColorStop(1, 'transparent');
|
||||
ctx.fillStyle = glowGrad;
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
// Mountains (back to front)
|
||||
drawMountain(ctx, H * 0.55, 120, 'rgba(15, 20, 40, 0.9)', time * 0.5, 30);
|
||||
drawMountain(ctx, H * 0.62, 100, 'rgba(20, 25, 50, 0.85)', time * 0.8 + 2, 25);
|
||||
drawMountain(ctx, H * 0.7, 80, 'rgba(25, 30, 55, 0.8)', time * 1.2 + 4, 20);
|
||||
drawMountain(ctx, H * 0.78, 60, 'rgba(30, 35, 60, 0.75)', time * 1.5 + 6, 18);
|
||||
|
||||
// Mist
|
||||
for (const m of mist) {
|
||||
m.x += m.speed;
|
||||
if (m.x > W + m.w) m.x = -m.w;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = m.opacity;
|
||||
const mistGrad = ctx.createRadialGradient(m.x, m.y, 0, m.x, m.y, m.w * 0.5);
|
||||
mistGrad.addColorStop(0, 'rgba(200, 210, 230, 0.3)');
|
||||
mistGrad.addColorStop(1, 'transparent');
|
||||
ctx.fillStyle = mistGrad;
|
||||
ctx.fillRect(m.x - m.w * 0.5, m.y - m.h, m.w, m.h * 2);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Foreground mountain
|
||||
drawMountain(ctx, H * 0.88, 40, 'rgba(10, 12, 25, 0.95)', time * 2 + 8, 15);
|
||||
|
||||
rafs['s2'] = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
animate();
|
||||
}
|
||||
|
||||
/* ============================================= */
|
||||
/* Scene 3: 数字水墨 — Ink Particles → Data */
|
||||
/* ============================================= */
|
||||
function initS3() {
|
||||
const canvas = document.getElementById('c3');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = window.innerWidth * 2;
|
||||
canvas.height = window.innerHeight * 2;
|
||||
canvas.style.width = '100%';
|
||||
canvas.style.height = '100%';
|
||||
ctx.scale(2, 2);
|
||||
|
||||
const W = window.innerWidth;
|
||||
const H = window.innerHeight;
|
||||
|
||||
// Ink particles that spread and then morph into data dots
|
||||
class InkParticle {
|
||||
constructor(cx, cy) {
|
||||
this.cx = cx;
|
||||
this.cy = cy;
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const dist = Math.random() * 10;
|
||||
this.x = cx + Math.cos(angle) * dist;
|
||||
this.y = cy + Math.sin(angle) * dist;
|
||||
|
||||
const speed = 0.3 + Math.random() * 1.5;
|
||||
this.vx = Math.cos(angle) * speed;
|
||||
this.vy = Math.sin(angle) * speed;
|
||||
|
||||
this.radius = 1.5 + Math.random() * 4;
|
||||
this.targetRadius = this.radius;
|
||||
this.opacity = 0.4 + Math.random() * 0.4;
|
||||
this.isRed = Math.random() > 0.75;
|
||||
|
||||
this.phase = 'spreading'; // spreading -> settling -> morphing
|
||||
this.spreadTime = 0;
|
||||
this.maxSpreadTime = 60 + Math.random() * 80;
|
||||
this.settleTime = 0;
|
||||
this.morphProgress = 0;
|
||||
|
||||
// Target position for data morph
|
||||
this.targetX = W * (0.55 + Math.random() * 0.35);
|
||||
this.targetY = H * (0.15 + Math.random() * 0.6);
|
||||
this.dataRadius = 1 + Math.random() * 2;
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.phase === 'spreading') {
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
this.vx *= 0.98;
|
||||
this.vy *= 0.98;
|
||||
this.spreadTime++;
|
||||
if (this.spreadTime >= this.maxSpreadTime) {
|
||||
this.phase = 'settling';
|
||||
}
|
||||
} else if (this.phase === 'settling') {
|
||||
this.settleTime++;
|
||||
this.opacity = Math.max(0.15, this.opacity - 0.003);
|
||||
if (this.settleTime > 60) {
|
||||
this.phase = 'morphing';
|
||||
}
|
||||
} else if (this.phase === 'morphing') {
|
||||
this.morphProgress = Math.min(1, this.morphProgress + 0.008);
|
||||
const t = easeInOutCubic(this.morphProgress);
|
||||
this.x = lerp(this.x, this.targetX, t * 0.02);
|
||||
this.y = lerp(this.y, this.targetY, t * 0.02);
|
||||
this.radius = lerp(this.radius, this.dataRadius, t * 0.02);
|
||||
this.opacity = lerp(this.opacity, 0.2 + Math.random() * 0.1, t * 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, Math.max(0.5, this.radius), 0, Math.PI * 2);
|
||||
if (this.isRed) {
|
||||
ctx.fillStyle = `rgba(196, 30, 58, ${this.opacity})`;
|
||||
} else {
|
||||
ctx.fillStyle = `rgba(28, 28, 28, ${this.opacity})`;
|
||||
}
|
||||
ctx.fill();
|
||||
|
||||
// Glow for larger particles
|
||||
if (this.radius > 3 && this.phase === 'spreading') {
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.radius * 2.5, 0, Math.PI * 2);
|
||||
ctx.fillStyle = this.isRed
|
||||
? `rgba(196, 30, 58, ${this.opacity * 0.1})`
|
||||
: `rgba(28, 28, 28, ${this.opacity * 0.08})`;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connection lines between nearby data particles
|
||||
function drawConnections(ctx, particles) {
|
||||
const morphed = particles.filter(p => p.phase === 'morphing' && p.morphProgress > 0.3);
|
||||
ctx.save();
|
||||
for (let i = 0; i < morphed.length; i++) {
|
||||
for (let j = i + 1; j < morphed.length; j++) {
|
||||
const dx = morphed[i].x - morphed[j].x;
|
||||
const dy = morphed[i].y - morphed[j].y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
if (dist < 80) {
|
||||
const alpha = (1 - dist / 80) * 0.08 * Math.min(morphed[i].morphProgress, morphed[j].morphProgress);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(morphed[i].x, morphed[i].y);
|
||||
ctx.lineTo(morphed[j].x, morphed[j].y);
|
||||
ctx.strokeStyle = `rgba(196, 30, 58, ${alpha})`;
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Create particles in batches
|
||||
const particles = [];
|
||||
const center1 = { x: W * 0.7, y: H * 0.35 };
|
||||
const center2 = { x: W * 0.25, y: H * 0.65 };
|
||||
|
||||
for (let i = 0; i < 120; i++) {
|
||||
particles.push(new InkParticle(center1.x, center1.y));
|
||||
}
|
||||
setTimeout(() => {
|
||||
for (let i = 0; i < 60; i++) {
|
||||
particles.push(new InkParticle(center2.x, center2.y));
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// Subtle background gradient orbs
|
||||
function drawBgOrbs(ctx) {
|
||||
const orbData = [
|
||||
{ x: W * 0.65, y: H * 0.3, r: 300, color: 'rgba(196, 30, 58, 0.03)' },
|
||||
{ x: W * 0.3, y: H * 0.7, r: 250, color: 'rgba(28, 28, 28, 0.02)' },
|
||||
];
|
||||
for (const o of orbData) {
|
||||
const grad = ctx.createRadialGradient(o.x, o.y, 0, o.x, o.y, o.r);
|
||||
grad.addColorStop(0, o.color);
|
||||
grad.addColorStop(1, 'transparent');
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(o.x - o.r, o.y - o.r, o.r * 2, o.r * 2);
|
||||
}
|
||||
}
|
||||
|
||||
function animate() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
ctx.fillStyle = '#FAFAF5';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
drawBgOrbs(ctx);
|
||||
|
||||
for (const p of particles) {
|
||||
p.update();
|
||||
p.draw(ctx);
|
||||
}
|
||||
|
||||
drawConnections(ctx, particles);
|
||||
|
||||
rafs['s3'] = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
animate();
|
||||
}
|
||||
|
||||
/* ============================================= */
|
||||
/* Utilities */
|
||||
/* ============================================= */
|
||||
function easeOutQuart(t) { return 1 - Math.pow(1 - t, 4); }
|
||||
function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }
|
||||
function lerp(a, b, t) { return a + (b - a) * t; }
|
||||
|
||||
// Init first scene
|
||||
initS1();
|
||||
animateContent('s1');
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (current === 's1') initS1();
|
||||
else if (current === 's2') initS2();
|
||||
else if (current === 's3') initS3();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,55 @@
|
||||
# 粒子束视觉升级与移动端适配设计
|
||||
|
||||
日期: 2026-05-03
|
||||
状态: 已批准
|
||||
|
||||
## 目标
|
||||
|
||||
提升 InkDataMorph 粒子动画的视觉美观度,并确保移动端展示体验。
|
||||
|
||||
## 核心改动
|
||||
|
||||
### 1. 多色调系统(5级水墨色调)
|
||||
|
||||
| 色调 | 色值 | 用途 | 分配比例 |
|
||||
|------|------|------|----------|
|
||||
| 浓墨 | #1C1C1C | 核心墨点 | 15% |
|
||||
| 淡墨 | #4A4A4A | 中间层次 | 25% |
|
||||
| 浅墨 | #8C8C8C | 氛围渲染 | 30% |
|
||||
| 朱砂 | #C41E3A | 点缀焦点 | 10% |
|
||||
| 淡朱 | #E8707A | 淡雅扩散 | 20% |
|
||||
|
||||
### 2. 粒子形态升级
|
||||
|
||||
- 椭圆粒子 + 随机旋转角度(ctx.ellipse 替代 ctx.arc)
|
||||
- 浓墨粒子:纵横比 0.8-1.0(近圆)
|
||||
- 淡墨/浅墨粒子:纵横比 0.5-0.9(椭圆)
|
||||
- 墨迹飞溅:10% 粒子为飞溅粒子(更快速度、更小半径、不参与 morphing)
|
||||
|
||||
### 3. 响应式适配
|
||||
|
||||
| 参数 | 移动端 (<768px) | 平板 (768-1023px) | 桌面 (≥1024px) |
|
||||
|------|-----------------|-------------------|----------------|
|
||||
| 粒子数量 | 80 | 140 | 220 |
|
||||
| 墨点起始 | 单墨点(右侧中上) | 双墨点(缩小间距) | 双墨点(当前) |
|
||||
| 目标区域X | W*(0.55-0.9) | W*(0.5-0.9) | W*(0.5-0.9) |
|
||||
| 目标区域Y | H*(0.1-0.5) | H*(0.12-0.6) | H*(0.12-0.65) |
|
||||
| 连接距离 | 60px | 80px | 100px |
|
||||
| 光晕缩放 | 0.5x | 0.75x | 1.0x |
|
||||
| 渐变层数 | 2 | 2 | 3 |
|
||||
| 拖尾效果 | 关闭 | 5帧 | 8帧 |
|
||||
| 飞溅粒子 | 无 | 5% | 10% |
|
||||
|
||||
### 4. 技术实现
|
||||
|
||||
- Particle 接口新增: rotation, scaleX, scaleY, isSplash, toneIndex
|
||||
- 新增 InkTone 枚举和 TONES 常量数组
|
||||
- 新增 getResponsiveConfig(W) 函数根据屏幕宽度返回配置
|
||||
- resize 监听(debounced 300ms)重新初始化粒子系统
|
||||
- DPR 适配: Math.min(window.devicePixelRatio, 2)
|
||||
- 移动端性能优化: 减少渐变层、关闭拖尾、减少粒子数
|
||||
|
||||
## 文件影响
|
||||
|
||||
- src/components/effects/ink-data-morph.tsx — 主要改动文件
|
||||
- src/components/effects/ink-data-morph.test.tsx — 测试更新
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 664 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "四川睿新致远科技有限公司",
|
||||
"short_name": "睿新致远",
|
||||
"description": "专注于企业数字化转型服务",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon-16x16.png",
|
||||
"sizes": "16x16",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-32x32.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#C41E3A",
|
||||
"background_color": "#FFFFFF",
|
||||
"display": "standalone",
|
||||
"start_url": "/"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
const sharp = require('sharp');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const svgPath = path.join(__dirname, '..', 'public', 'favicon.svg');
|
||||
const svg = fs.readFileSync(svgPath);
|
||||
|
||||
async function generate() {
|
||||
await sharp(svg).resize(180, 180).png().toFile(path.join(__dirname, '..', 'public', 'apple-touch-icon.png'));
|
||||
console.log('apple-touch-icon.png (180x180) generated');
|
||||
|
||||
await sharp(svg).resize(32, 32).png().toFile(path.join(__dirname, '..', 'public', 'favicon-32x32.png'));
|
||||
console.log('favicon-32x32.png generated');
|
||||
|
||||
await sharp(svg).resize(16, 16).png().toFile(path.join(__dirname, '..', 'public', 'favicon-16x16.png'));
|
||||
console.log('favicon-16x16.png generated');
|
||||
|
||||
const png32 = await sharp(svg).resize(32, 32).png().toBuffer();
|
||||
|
||||
const header = Buffer.alloc(6);
|
||||
header.writeUInt16LE(0, 0);
|
||||
header.writeUInt16LE(1, 2);
|
||||
header.writeUInt16LE(1, 4);
|
||||
|
||||
const entry = Buffer.alloc(16);
|
||||
entry.writeUInt8(32, 0);
|
||||
entry.writeUInt8(32, 1);
|
||||
entry.writeUInt8(0, 2);
|
||||
entry.writeUInt8(0, 3);
|
||||
entry.writeUInt16LE(1, 4);
|
||||
entry.writeUInt16LE(32, 6);
|
||||
entry.writeUInt32LE(png32.length, 8);
|
||||
entry.writeUInt32LE(22, 12);
|
||||
|
||||
const ico = Buffer.concat([header, entry, png32]);
|
||||
fs.writeFileSync(path.join(__dirname, '..', 'public', 'favicon.ico'), ico);
|
||||
console.log('favicon.ico generated');
|
||||
|
||||
console.log('All favicon files generated!');
|
||||
}
|
||||
|
||||
generate().catch(err => console.error(err));
|
||||
+5
-1
@@ -1152,10 +1152,14 @@ body {
|
||||
}
|
||||
|
||||
/* 优化移动端按钮和链接的触摸目标 */
|
||||
a, button {
|
||||
a:not(nav[aria-label="breadcrumb"] a), button {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
}
|
||||
nav[aria-label="breadcrumb"] a {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 防止长文本溢出 */
|
||||
p, li, span {
|
||||
|
||||
+6
-1
@@ -124,7 +124,12 @@ export default function RootLayout({
|
||||
<html lang="zh-CN" className="scroll-smooth" suppressHydrationWarning>
|
||||
<head>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<meta name="theme-color" content="#C41E3A" />
|
||||
<OrganizationSchema />
|
||||
<WebsiteSchema />
|
||||
</head>
|
||||
|
||||
@@ -14,7 +14,7 @@ interface BreadcrumbProps {
|
||||
|
||||
export function Breadcrumb({ items }: BreadcrumbProps) {
|
||||
return (
|
||||
<nav aria-label="breadcrumb" className="flex items-center space-x-2 text-sm text-[#5C5C5C] py-4">
|
||||
<nav aria-label="breadcrumb" className="flex items-center space-x-1 text-sm text-[#5C5C5C] py-4">
|
||||
<StaticLink href="/" className="flex items-center hover:text-[#C41E3A] transition-colors" aria-label="返回首页">
|
||||
<Home className="w-4 h-4" />
|
||||
</StaticLink>
|
||||
@@ -23,7 +23,7 @@ export function Breadcrumb({ items }: BreadcrumbProps) {
|
||||
<ChevronRight className="w-4 h-4 text-[#E5E5E5]" />
|
||||
<StaticLink
|
||||
href={item.href}
|
||||
className="ml-2 hover:text-[#C41E3A] transition-colors"
|
||||
className="ml-1 hover:text-[#C41E3A] transition-colors"
|
||||
>
|
||||
{item.label}
|
||||
</StaticLink>
|
||||
|
||||
@@ -14,15 +14,15 @@ interface PageNavProps {
|
||||
|
||||
export function PageNav({ items }: PageNavProps) {
|
||||
return (
|
||||
<nav aria-label="breadcrumb" className="flex items-center gap-1.5 text-sm text-[#A3A3A3] mb-8">
|
||||
<StaticLink href="/" className="flex items-center hover:text-[#C41E3A] transition-colors" aria-label="返回首页">
|
||||
<Home className="w-3.5 h-3.5" />
|
||||
<nav aria-label="breadcrumb" className="flex items-center gap-[3px] md:gap-1 text-[11px] md:text-sm text-[#A3A3A3] mb-2 md:mb-8 -ml-0.5">
|
||||
<StaticLink href="/" className="flex items-center w-fit hover:text-[#C41E3A] transition-colors" aria-label="返回首页">
|
||||
<Home className="w-2.5 h-2.5 md:w-3.5 md:h-3.5" />
|
||||
</StaticLink>
|
||||
{items.map((item, index) => {
|
||||
const isLast = index === items.length - 1;
|
||||
return (
|
||||
<div key={index} className="flex items-center gap-1.5">
|
||||
<ChevronRight className="w-3.5 h-3.5 text-[#D4D4D4]" />
|
||||
<div key={index} className="flex items-center gap-[3px] md:gap-1">
|
||||
<ChevronRight className="w-2.5 h-2.5 md:w-3.5 md:h-3.5 text-[#D4D4D4]" />
|
||||
{isLast || !item.href ? (
|
||||
<span className={isLast ? 'text-[#1C1C1C] font-medium' : ''}>
|
||||
{item.label}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { motion } from 'framer-motion';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -9,11 +8,6 @@ import { COMPANY_INFO } from '@/lib/constants';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
||||
|
||||
const InkDataMorph = dynamic(
|
||||
() => import('@/components/effects/ink-data-morph').then(mod => ({ default: mod.InkDataMorph })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export function HeroSectionV2() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
@@ -48,7 +42,6 @@ export function HeroSectionV2() {
|
||||
aria-labelledby="hero-heading"
|
||||
className="relative min-h-screen flex flex-col justify-center overflow-hidden bg-white"
|
||||
>
|
||||
<InkDataMorph />
|
||||
<div className="container-wide py-24 md:py-32 lg:py-40 relative z-10 flex-1 flex items-center">
|
||||
<div className="max-w-3xl">
|
||||
<motion.div
|
||||
|
||||
@@ -35,7 +35,7 @@ export function BackToTop() {
|
||||
exit={shouldReduceMotion ? {} : { opacity: 0, y: 20, scale: 0.8 }}
|
||||
transition={{ duration: 0.2, ease: 'easeOut' }}
|
||||
onClick={scrollToTop}
|
||||
className="fixed bottom-8 right-8 z-50 p-3 bg-[#C41E3A] text-white rounded-full shadow-lg hover:bg-[#A01830] hover:shadow-xl transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-[#C41E3A] focus:ring-offset-2"
|
||||
className="fixed right-4 bottom-20 md:bottom-8 md:right-8 z-40 p-3 bg-[#C41E3A] text-white rounded-full shadow-lg hover:bg-[#A01830] hover:shadow-xl transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-[#C41E3A] focus:ring-offset-2"
|
||||
aria-label="返回顶部"
|
||||
title="返回顶部"
|
||||
style={{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useReducer } from 'react';
|
||||
import { useReducer, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
interface FlipCardProps {
|
||||
@@ -93,7 +93,9 @@ function FlipCard({ value, label, maxDigits = 2 }: FlipCardProps) {
|
||||
},
|
||||
{ current: value, previous: value }
|
||||
);
|
||||
useEffect(() => {
|
||||
dispatch(value);
|
||||
}, [value]);
|
||||
|
||||
const formatNumber = (num: number) => {
|
||||
const str = num.toString().padStart(maxDigits, '0');
|
||||
|
||||
Reference in New Issue
Block a user