feat(me): 全面升级个人主页视觉与交互体验
- 引入昼夜模式下的动态渐变标题与光晕背景效果 - 新增极光动画层,提升页面沉浸感与视觉层次 - 优化粒子系统着色器,支持主题色彩漂移与星云渲染 - 改进叙事文本层样式,增强文字可读性与动效表现 - 调整双手合十解锁逻辑,提高手势识别准确率与稳定性 - 更新多语言文案内容,强化品牌表达与情感连接 - 增加退出冷却机制,避免误触并改善用户体验流畅度 - 统一动画曲线与过渡时长,确保界面响应自然和谐
This commit is contained in:
162
me.html
162
me.html
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user