feat(me): 全面升级个人主页视觉与交互体验

- 引入昼夜模式下的动态渐变标题与光晕背景效果
- 新增极光动画层,提升页面沉浸感与视觉层次
- 优化粒子系统着色器,支持主题色彩漂移与星云渲染
- 改进叙事文本层样式,增强文字可读性与动效表现
- 调整双手合十解锁逻辑,提高手势识别准确率与稳定性
- 更新多语言文案内容,强化品牌表达与情感连接
- 增加退出冷却机制,避免误触并改善用户体验流畅度
- 统一动画曲线与过渡时长,确保界面响应自然和谐
This commit is contained in:
hehh
2025-12-04 16:36:50 +08:00
parent c21d276f40
commit 67049a126f

162
me.html
View File

@@ -18,9 +18,11 @@
[data-theme="day"] {
--bg-color: linear-gradient(135deg, #e0eafc 0%, #cfdef3 100%);
--text-color: #1a1a2e; /* 极深蓝黑,保证可见度 */
--accent-color: #0044cc;
--accent-color: #3567ff;
--loader-bg: #f0f2f5;
--loader-text: #2c3e50;
--title-gradient: linear-gradient(120deg, #1a1a2e 0%, #2b56ff 50%, #1947ff 100%);
--veil: radial-gradient(closest-side, rgba(255,255,255,0.0), rgba(255,255,255,0.0) 70%);
}
/* === 黑夜模式 (霓虹/深空) === */
@@ -30,6 +32,8 @@
--accent-color: #00FFFF;
--loader-bg: #000000;
--loader-text: #FFFFFF;
--title-gradient: linear-gradient(120deg, #ffffff 0%, #7dfbff 35%, #ff7af3 70%, #ffd36e 100%);
--veil: radial-gradient(closest-side, rgba(0,0,0,0.55), rgba(0,0,0,0.0) 72%);
}
body {
@@ -73,19 +77,34 @@
/* === 2. 叙事文本层 (DOM覆盖) === */
#narrative-layer {
position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%);
text-align: center; width: 90%; pointer-events: none; z-index: 5;
position: absolute; top: 28%; left: 50%; transform: translate(-50%, -50%);
text-align: center; width: 72%; max-width: 1000px; pointer-events: none; z-index: 5;
padding: 16px 24px;
}
#narrative-layer::before {
content: '';
position: absolute; inset: -4% 10% 0 10%;
transform: translateZ(0);
background: var(--veil);
filter: blur(20px) saturate(1.1);
pointer-events: none;
z-index: -1;
}
.n-title {
font-size: 8vw; font-weight: 900; letter-spacing: -2px; margin-bottom: 10px;
font-size: clamp(42px, 8vw, 128px); font-weight: 800; letter-spacing: 0; margin-bottom: 14px;
opacity: 0; transform: translateY(30px); transition: all 0.8s cubic-bezier(0.2, 1, 0.3, 1);
background: linear-gradient(45deg, var(--text-color), var(--accent-color));
background: var(--title-gradient);
background-size: 200% 200%; animation: gradientShift 9s ease-in-out infinite;
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
text-shadow: 0 10px 40px rgba(0,0,0,0.35);
}
[data-theme="day"] .n-title { -webkit-text-stroke: 0.6px rgba(255,255,255,0.10); }
[data-theme="night"] .n-title { -webkit-text-stroke: 0.6px rgba(0,0,0,0.20); }
.n-sub {
font-size: 18px; font-weight: 400; letter-spacing: 6px; opacity: 0;
font-size: 20px; font-weight: 500; letter-spacing: 6px; opacity: 0;
transform: translateY(20px); transition: all 0.8s 0.2s cubic-bezier(0.2, 1, 0.3, 1);
color: var(--text-color);
text-shadow: 0 6px 24px rgba(0,0,0,0.25);
}
/* 激活状态类名 */
.show-text .n-title, .show-text .n-sub { opacity: 1; transform: translateY(0); }
@@ -104,7 +123,7 @@
}
.center-stage {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
position: absolute; top: 78%; left: 50%; transform: translate(-50%, -50%);
text-align: center; width: 100%; pointer-events: auto;
}
@@ -131,6 +150,20 @@
@keyframes breathe { 0%,100%{opacity:0.6} 50%{opacity:1} }
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
#canvas-container { position: fixed; inset: 0; z-index: 1; }
#aurora-layer { position: fixed; inset: 0; z-index: 2; pointer-events: none; }
#aurora-layer::before, #aurora-layer::after { content: ''; position: absolute; inset: -20% -10%; filter: blur(60px); opacity: 0.25; }
@keyframes auroraDrift { 0% { transform: translate3d(-4%, -2%, 0) scale(1); } 50% { transform: translate3d(4%, 2%, 0) scale(1.05); } 100% { transform: translate3d(-4%, 0, 0) scale(1); } }
[data-theme="day"] #aurora-layer::before { background: radial-gradient(60% 40% at 25% 30%, rgba(255,180,200,0.35), transparent 60%); animation: auroraDrift 22s ease-in-out infinite; }
[data-theme="day"] #aurora-layer::after { background: radial-gradient(60% 40% at 70% 60%, rgba(110,195,255,0.35), transparent 60%); animation: auroraDrift 28s ease-in-out infinite reverse; }
[data-theme="night"] #aurora-layer::before { background: radial-gradient(70% 45% at 20% 30%, rgba(0,255,255,0.28), transparent 62%); animation: auroraDrift 24s ease-in-out infinite; }
[data-theme="night"] #aurora-layer::after { background: radial-gradient(70% 45% at 75% 65%, rgba(255,122,243,0.24), transparent 62%); animation: auroraDrift 32s ease-in-out infinite reverse; }
</style>
@@ -183,6 +216,7 @@
<video id="input-video" style="display:none"></video>
<div id="canvas-container"></div>
<div id="aurora-layer"></div>
<script>
/**
@@ -196,8 +230,10 @@
handCount: 0,
handL: new THREE.Vector3(9999,9999,0),
handR: new THREE.Vector3(9999,9999,0),
unlockProgress: 0
unlockProgress: 0,
exitCooldownUntil: 0
};
APP_STATE.namasteStableFrames = 0;
// 安全DOM操作防止报错
function safeUpdateText(id, text) {
@@ -255,46 +291,46 @@
const DICTIONARY = {
zh: {
load: [
"正在构建数字灵魂...",
"正在唤醒灵感...",
"接入神经元网络...",
"校准引力波场...",
"等待视觉信号...",
"系统准备就绪."
],
hints: {
main: "点击 或 双手合十 开启",
sub: "单手·流体牵引 | 双手·力场排斥",
main: "双手合十 · 解锁档案",
sub: "单手·引力牵引|双手·力场排斥",
unlocking: "正在识别..."
},
slides: [
{ t: "HE HOUHUI", s: "JAVA & AI 架构师" },
{ t: "INFJ", s: "1% 的提倡者 // 理想主义" },
{ t: "深度 > 广度", s: "在垂直领域构建壁垒" },
{ t: "长期主义", s: "做时间的朋友" },
{ t: "代码哲学", s: "代码是逻辑的载体" },
{ t: "创造", s: "赋予技术以意义" }
{ t: "", s: "以诚为始,以敬为归" },
{ t: "深度", s: "深入一处,洞见万物" },
{ t: "长期主义", s: "与时间为友,向内修行" },
{ t: "代码诗性", s: "在逻辑中安放灵魂" },
{ t: "创造", s: "让技术拥有意义" },
{ t: "INFJ", s: "提倡者 // 温柔坚定" }
]
},
en: {
load: [
"Constructing Digital Soul...",
"Connecting Neural Network...",
"Calibrating Gravity Field...",
"Waiting for Visual Sensor...",
"Summoning Lucid Dream...",
"Linking Neurons...",
"Tuning Gravity Field...",
"Awaiting Vision...",
"System Ready."
],
hints: {
main: "CLICK OR NAMASTE",
sub: "1 Hand Drag · 2 Hands Repel",
unlocking: "IDENTIFYING..."
main: "Click or Namaste",
sub: "One Hand Drag · Two Hands Repel",
unlocking: "Identifying..."
},
slides: [
{ t: "HE HOUHUI", s: "JAVA & AI ARCHITECT" },
{ t: "INFJ", s: "THE ADVOCATE // 1% UNIVERSE" },
{ t: "DEPTH > BREADTH", s: "BUILDING FORTRESSES" },
{ t: "LONG-TERMISM", s: "FRIEND OF TIME" },
{ t: "CODE PHILOSOPHY", s: "VESSEL OF LOGIC" },
{ t: "CREATE", s: "EMPOWER MEANING" }
{ t: "Honesty", s: "Begin with truth, return with grace" },
{ t: "Depth Over Breadth", s: "One well, a thousand rivers" },
{ t: "Long-Termism", s: "Friend of Time, seeker within" },
{ t: "Code Philosophy", s: "A vessel for the soul of logic" },
{ t: "Creation", s: "Where technique meets meaning" },
{ t: "INFJ", s: "The Advocate // Gentle Resolve" }
]
}
};
@@ -365,6 +401,7 @@
const origin = new Float32Array(CONFIG.particleCount * 3);
const velocities = new Float32Array(CONFIG.particleCount * 3);
const sizes = new Float32Array(CONFIG.particleCount);
const seeds = new Float32Array(CONFIG.particleCount);
const simplex = new SimplexNoise();
@@ -386,34 +423,54 @@
// 白天粒子略大,增强可见度
sizes[i] = (Math.random() * 2.5 + 0.5) * (ENV.theme==='day'?1.3:1.0);
seeds[i] = Math.random();
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
geometry.setAttribute('seed', new THREE.BufferAttribute(seeds, 1));
const paletteA = ENV.theme==='day' ? new THREE.Color(0x6ec3ff) : new THREE.Color(0x00ffff);
const paletteB = ENV.theme==='day' ? new THREE.Color(0xffb4c8) : new THREE.Color(0xff7af3);
const material = new THREE.ShaderMaterial({
uniforms: {
scale: { value: window.innerHeight / 2 },
baseColor: { value: CONFIG.colors.base },
activeColor: { value: CONFIG.colors.active },
mixVal: { value: 0.0 }
mixVal: { value: 0.0 },
time: { value: 0.0 },
paletteA: { value: paletteA },
paletteB: { value: paletteB },
nebulaIntensity: { value: 0.0 }
},
vertexShader: `
attribute float size;
attribute float seed;
varying float vSeed;
void main() {
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( 500.0 / -mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
vSeed = seed;
}
`,
fragmentShader: `
uniform vec3 baseColor;
uniform vec3 activeColor;
uniform vec3 paletteA;
uniform vec3 paletteB;
uniform float mixVal;
uniform float time;
uniform float nebulaIntensity;
varying float vSeed;
void main() {
float r = length(gl_PointCoord - vec2(0.5));
if (r > 0.5) discard;
vec3 finalColor = mix(baseColor, activeColor, mixVal);
vec3 baseMix = mix(baseColor, activeColor, mixVal);
float drift = 0.5 + 0.5 * sin(time * 0.15 + vSeed * 6.28318);
vec3 nebula = mix(paletteA, paletteB, drift);
vec3 finalColor = mix(baseMix, nebula, nebulaIntensity);
gl_FragColor = vec4(finalColor, 1.0);
}
`,
@@ -447,6 +504,12 @@
// 颜色插值
material.uniforms.mixVal.value += (targetMix - material.uniforms.mixVal.value) * 0.1;
material.uniforms.time.value = time;
if (ENV.theme === 'night') {
material.uniforms.nebulaIntensity.value = APP_STATE.mode === 'UNLOCKED' ? 0.55 : 0.35;
} else {
material.uniforms.nebulaIntensity.value = APP_STATE.mode === 'UNLOCKED' ? 0.22 : 0.12;
}
// 1. 形状更新 (锁定模式:呼吸球)
if (APP_STATE.mode === 'LOCKED') {
@@ -559,12 +622,15 @@
window.exitArchive = function() {
APP_STATE.mode = 'LOCKED';
APP_STATE.exitCooldownUntil = Date.now() + 2000;
document.getElementById('main-hint').style.display = 'block';
document.getElementById('sub-hint').style.display = 'block';
safeClass('exit-btn', 'remove', 'visible');
safeClass('narrative-layer', 'remove', 'show-text');
APP_STATE.unlockProgress = 0;
safeUpdateText('main-hint', CONTENT.hints.main);
clearTimeout(narrativeTimer);
explode(50);
}
@@ -621,7 +687,7 @@
* ============================================================================
*/
const hands = new Hands({locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`});
hands.setOptions({ maxNumHands: 2, modelComplexity: 1, minDetectionConfidence: 0.7, minTrackingConfidence: 0.7 });
hands.setOptions({ maxNumHands: 2, modelComplexity: 1, minDetectionConfidence: 0.85, minTrackingConfidence: 0.85 });
hands.onResults(results => {
// Loader logic
@@ -658,22 +724,34 @@
}
// 合十检测
if (landmarks.length === 2 && APP_STATE.mode === 'LOCKED') {
const w1=landmarks[0][0]; const w2=landmarks[1][0];
const t1=landmarks[0][12]; const t2=landmarks[1][12];
if (Math.hypot(w1.x-w2.x, w1.y-w2.y)<0.25 && Math.hypot(t1.x-t2.x, t1.y-t2.y)<0.2) {
APP_STATE.unlockProgress++;
safeUpdateText('main-hint', `${CONTENT.hints.unlocking} ${APP_STATE.unlockProgress}%`);
if(APP_STATE.unlockProgress > 50) enterArchive();
if (landmarks.length === 2 && APP_STATE.mode === 'LOCKED' && Date.now() > APP_STATE.exitCooldownUntil) {
const w1 = landmarks[0][0], w2 = landmarks[1][0];
const i1 = landmarks[0][8], i2 = landmarks[1][8];
const m1 = landmarks[0][12], m2 = landmarks[1][12];
const dW = Math.hypot(w1.x - w2.x, w1.y - w2.y);
const dI = Math.hypot(i1.x - i2.x, i1.y - i2.y);
const dM = Math.hypot(m1.x - m2.x, m1.y - m2.y);
const okW = dW < 0.28;
const okI = dI < 0.20;
const okM = dM < 0.20;
const ok = ((okW?1:0) + (okI?1:0) + (okM?1:0)) >= 2;
if (ok) {
APP_STATE.namasteStableFrames++;
} else {
APP_STATE.unlockProgress = 0;
safeUpdateText('main-hint', CONTENT.hints.main);
APP_STATE.namasteStableFrames = Math.max(0, APP_STATE.namasteStableFrames - 1);
}
const targetFrames = 20;
const pct = Math.min(100, Math.round(APP_STATE.namasteStableFrames / targetFrames * 100));
if (pct > 0) safeUpdateText('main-hint', `${CONTENT.hints.unlocking} ${pct}%`);
if (APP_STATE.namasteStableFrames >= targetFrames) enterArchive();
if (!ok && APP_STATE.namasteStableFrames === 0) safeUpdateText('main-hint', CONTENT.hints.main);
}
} else {
APP_STATE.handCount = 0;
safeUpdateText('sys-status', 'SEARCHING...');
if(sysStatus) sysStatus.style.color = 'inherit';
APP_STATE.unlockProgress = 0;
safeUpdateText('main-hint', CONTENT.hints.main);
}
});