45 Commits

Author SHA1 Message Date
hehh
a0e911421f feat(tracking): 集成新的统计追踪服务
- 在 about.html 中添加 vercount 统计脚本
- 在 christmas.html 中添加 vercount 统计脚本
- 在 index.html 中替换原有的分析服务为 vercount 统计脚本
- 在 me.html 中添加 vercount 统计脚本
- 移除旧的 analysee 统计服务相关代码
- 统一使用 defer 属性优化脚本加载性能
2026-01-10 18:28:17 +08:00
hehh
d0091fc713 config(edgeone): 添加重写规则配置
- 配置 .well-known 目录下 txt 文件的路径重写规则
- 将 /.well-known/*.txt 重定向到 /well-known/:splat.txt
- 解决 .well-known 目录文件访问问题
2026-01-10 18:00:07 +08:00
hehh
dac607472f feat(ui): 优化音频控制和提示文字交互体验
- 将音频控制按钮改为固定定位并增强悬停效果
- 重新设计提示文字样式,支持响应式和居中显示
- 添加提示文字点击事件以切换控制面板
- 设置 canvas 元素不拦截鼠标事件
- 更新提示文字内容,增强用户引导
- 增加控制面板显示状态的日志输出
2025-12-13 01:15:50 +08:00
hehh
10d73b33e1 feat(christmas): 增强粒子效果和音频状态管理
- 将粒子数量从1000增加到2000,尘埃粒子从2000增加到3000
- 修改 MediaPipe WASM 路径为本地 data/wasm 目录
- 实现音频状态的持久化存储和恢复功能
- 添加定时保存音频状态逻辑,每5秒保存一次
- 增强音频播放控制,确保循环播放和自动恢复
- 优化音频事件监听器,实时同步播放状态
2025-12-13 01:01:09 +08:00
hehh
19e9801318 feat(audio): 优化背景音乐播放控制
- 实现音频状态的本地存储与恢复功能
- 添加定期自动保存播放状态机制
- 支持页面加载时恢复上次播放位置
- 增强自动播放逻辑,提升用户体验
- 监听多种用户交互事件触发音频播放
- 添加音频播放结束后的自动重播功能
- 优化静音与播放状态切换逻辑
- 提升移动端触摸事件对音频控制的支持
2025-12-12 20:16:38 +08:00
hehh
b424f898db feat(gesture): 优化移动端手势识别与控制交互
- 调整移动端手势识别阈值,提升识别准确率
- 增加移动端专属手势指令:左右滑动控制前后移动
- 优化手势影响范围计算逻辑,增强触摸屏设备体验
- 完善摄像头权限检查机制,改善用户授权流程
- 支持通过点击提示文字唤起控制台界面
- 统一控制台显示/隐藏逻辑,提高代码可维护性
- 调整响应式布局参数,适配更多设备屏幕尺寸
2025-12-12 20:15:56 +08:00
hehh
ad744a0690 feat(christmas): 优化粒子动画以适配不同屏幕尺寸
- 根据屏幕宽度动态调整散射模式下粒子的分布范围
- 调整焦点照片的位置和距离以适应小屏幕设备
- 根据屏幕尺寸改变噪声强度,使动画更自然
- 优化手势识别的影响范围和灵敏度,提升触摸体验
- 调整插值速度和缩放比例,确保动画在各种设备上流畅运行
- 重构窗口大小变化处理函数,提高代码可维护性
2025-12-12 18:20:42 +08:00
hehh
78329785a7 feat(responsive): 优化多设备响应式设计
- 添加超小设备 (小于 480px) 的样式适配
- 完善小设备 (480px 到 768px) 的布局调整
- 新增平板设备 (769px 到 1024px) 的显示优化
- 补充大屏幕设备 (大于 1024px) 的视觉增强
- 统一各设备尺寸下的控件尺寸与间距标准
- 移除加载文字中的英文提示 (Loading Holiday Magic)
2025-12-12 18:17:51 +08:00
hehh
3fb3739666 feat(christmas.html): 增强页面 SEO 与社交分享功能
- 添加 favicon 和苹果触屏图标支持
- 补充 Open Graph 与 Twitter Card 元数据
- 增加微信分享标题、描述与图片配置
- 替换 Google Fonts 为国内加速镜像链接
- 优化背景色至更舒适的极深午夜蓝色
- 新增手势操作引导说明区域
- 实现音频自动播放及用户交互触发逻辑
- 添加点击与键盘事件以改善播放体验
2025-12-12 18:12:32 +08:00
hehh
28013c438f feat(christmas): 实现3D圣诞树互动页面
- 添加上传照片功能,用户可添加个人回忆到3D场景
- 集成MediaPipe手势识别,支持多种手势控制粒子动画
- 优化粒子系统,实现更自然的运动和交互反馈
- 设计奢华金色主题UI,增强节日氛围视觉效果
- 实现响应式布局,适配移动端和桌面端体验
- 添加背景音乐和音量控制功能
- 重构动画逻辑,提高手势响应速度和流畅度
- 优化Three.js渲染性能,提升整体运行效率
2025-12-12 18:03:50 +08:00
hehh
785bf0fe61 perf(christmas): 优化圣诞树页面性能
- 减少CSS代码行数,合并重复样式声明
- 降低粒子系统中几何体的分段数以减少内存占用
- 减少粒子和尘埃粒子的数量以提升渲染性能
- 添加硬件加速样式以提高动画流畅度
- 优化WebGL渲染器配置,限制像素比率
- 调整后期处理效果参数以平衡视觉效果与性能
- 优化动画插值计算,防止过度计算
- 在窗口大小调整事件中添加防抖动处理
- 限制动画帧时间差最大值以稳定动画表现
- 添加手势状态变化检测以减少不必要的状态切换
2025-12-12 17:05:25 +08:00
hehh
cada354dbe feat(christmas.html): 添加背景音乐和不蒜子统计功能
- 格式化HTML代码,提升可读性
- 添加背景音乐播放功能,支持自动播放和用户交互播放
- 集成不蒜子网站统计脚本
- 优化粒子系统和3D渲染效果
- 改进手部识别交互逻辑
- 更新默认图片文字内容
- 添加音频播放错误处理机制
- 优化CSS样式结构和动画效果
2025-12-12 16:56:38 +08:00
hehh
b5974a4407 feat(christmas): 添加圣诞主题3D互动页面
- 创建了基于Three.js的3D圣诞树场景
- 实现了粒子系统,包括装饰球、礼盒和彩带
- 添加了照片上传功能,支持用户自定义装饰
- 集成了手势识别,可通过手势控制视角和模式
- 设计了多种显示模式:散落、树形和聚焦视图
- 添加了后期处理效果,增强视觉体验
- 实现了响应式设计,适配不同屏幕尺寸
- 添加了节日加载动画和UI界面
- 配置了SEO和社交平台分享元数据
2025-12-12 16:47:58 +08:00
hehh
a7cc299695 feat(about): 添加导航项的无障碍标签并优化二维码加载逻辑
- 为首页和博客导航链接增加 aria-label 属性以提升无障碍访问性
- 移除微信公众号模态框中失效的二维码降级提示注释
- 删除冗余的 moments.js 和粒子系统相关脚本文件内容
2025-12-12 16:28:29 +08:00
hehh
6b38d01dd3 refactor(about): 移除内联关键CSS并优化页面结构
- 删除了 about.html 中的内联关键 CSS 样式代码
- 保留了主题变量和基础样式的定义逻辑
- 简化了主容器的媒体查询配置
- 调整了页面底部链接与脚本加载顺序
- 优化了整体HTML结构以提升可维护性
2025-12-12 16:22:32 +08:00
hehh
503ae0c273 feat(physics): 重构粒子物理系统并增强手势交互
- 重写物理核心为基于力的系统,支持更自然的粒子流动
- 引入旋度噪声(Curl Noise)实现流体感运动
- 添加弹性回归力使粒子可自动飘回原位
- 实现高级手势交互场,支持任意数量手部追踪
- 增加掌心力场与指尖轨迹交互逻辑
- 优化粒子着色器,提升视觉表现与性能
- 改进主题系统与颜色配置结构
- 更新UI布局与加载动画效果
-
2025-12-12 16:19:05 +08:00
hehh
75231ee73a refactor(ui): 重构界面与粒子系统提升视觉体验
- 更新标题为 FLUID ARCHIVE 并调整字体与背景色
- 简化 UI 布局,增强沉浸感并隐藏鼠标指针
- 优化加载动画为旋转圆环,改善过渡效果
- 粒子系统重写:减少数量、引入着色器材质与辉光效果
- 改进粒子物理行为,支持跟随、聚合与自由漂浮模式
- 更新主题配置逻辑,简化节气与节日判断规则
- 修复手势控制响应延迟与丢失问题,增强交互反馈
- 调整摄像机视角与后处理参数以增强空间氛围感
2025-12-12 12:27:36 +08:00
hehh
31b7d72123 refactor(ui): 重构界面布局与主题系统
- 移除旧版加载屏与叙事层DOM结构
- 新增基于Three.js的粒子系统引擎
- 实现动态主题检测与切换功能
- 更新CSS变量系统以支持RGB颜色模式
- 优化UI层布局结构提升响应式体验
- 添加节庆主题自动识别逻辑
- 简化HTML结构并增强语义化程度
- 调整字体与色彩配置提高可读性
- 引入新的动画呼吸效果与脉冲效果
- 重构JavaScript模块提升代码组织性
2025-12-12 12:27:20 +08:00
hehh
2b6d363aba refactor(config): 重构站点配置加载逻辑
- 将SiteConfig封装为IIFE避免全局污染
- 移除about.html中的内联SiteConfig定义
- 统一通过js/config.js管理所有配置项
- 修复统计脚本加载时对SiteConfig的依赖问题
- 确保配置在模块化和浏览器环境中正常导出
- 优化不蒜子统计ID获取方式提升容错性
2025-12-12 12:06:49 +08:00
hehh
e47c5c2803 perf(about): 优化页面加载性能与结构
- 将外部CSS资源改为preload方式异步加载
- 内联关键首屏CSS样式减少渲染阻塞
- 添加图片尺寸属性提升渲染性能
- 内联关键JS配置提升初始化速度
- 为脚本标签添加defer属性优化加载顺序
- 增加noscript回退方案确保无JS兼容性
2025-12-12 11:57:03 +08:00
hehh
a98496c232 feat(particles): 添加粒子系统类以支持土星动画和交互控制
- 创建 ParticleSystem 类,支持多种粒子动画效果
- 实现土星主体和光环的粒子分布初始化逻辑
- 添加粒子材质和着色器定义,支持日夜主题切换
- 实现粒子位置更新逻辑,包括噪声扰动和手势交互
- 添加粒子爆炸、散开和聚合等动画控制方法
- 支持通过手势命令动态调整动画参数
- 提供粒子系统资源释放接口
2025-12-12 11:42:54 +08:00
hehh
1ed730a3d2 feat(me): 重构粒子系统与动画加载逻辑
- 引入独立的ParticleSystem类管理粒子系统
- 添加土星动画作为默认加载动画
- 实现动态动画切换机制,避免资源冲突
- 优化粒子爆炸与散射效果调用方式
- 移除旧版粒子初始化与物理计算代码
- 更新手势交互与UI状态同步逻辑
- 修复动画模式下按钮显示时机问题
2025-12-10 00:04:26 +08:00
hehh
cff4db87af feat(me): 调整倒计时逻辑和界面显示时机
- 将倒计时初始值从变量替换为固定字符串 '5'
- 移除 explode 函数调用
- 延迟显示进入选项和启动倒计时逻辑
- 调整倒计时间隔从 980ms 到 1000ms
- 将倒计时相关代码包裹在 setTimeout 中延迟执行
2025-12-04 22:17:08 +08:00
hehh
dd837153b8 fix(animation): 调整动画延迟时间
- 将动画延迟从1000毫秒调整为980毫秒
- 优化动画启动时机以提升用户体验
2025-12-04 21:50:58 +08:00
hehh
b9461c5741 feat(me): 动态化倒计时提示文本
- 将倒计时提示文本中的固定时间替换为动态占位符
- 使用 `{second}` 占位符实现多语言时间显示
- 更新中文和英文场景下的倒计时文本格式
- 优化倒计时逻辑以支持动态文本更新
- 确保语言切换时提示文本正确渲染
2025-12-04 21:50:01 +08:00
hehh
fef7ca2288 feat(me): 优化页面加载和摄像头权限处理逻辑
- 移除enterAnimationMode函数中的APP_STATE.isLoaded检查,确保动画模式始终可进入
- 改进加载屏幕隐藏逻辑,增加DOM存在性检查防止潜在错误
- 根据摄像头状态动态更新UI提示信息
- 增加摄像头权限检查机制,在页面加载时自动检测并启用摄像头
- 实现延迟显示交互模式按钮逻辑,确保页面过渡完成后再显示
- 调整资源加载完成后的进入按钮显示逻辑,移除不必要的条件判断
- 优化倒计时文本显示,支持中英文切换
- 统一页面加载完成后的提示信息更新逻辑
2025-12-04 21:41:26 +08:00
hehh
5268f35af1 feat(ui): 优化加载界面动画效果
- 将原有的旋转圆环加载动画替换为三点脉冲动画
- 调整加载文字字号从14px增大到16px,提高可读性
- 重构HTML结构,新增loading-container包裹元素
- 修改状态文本样式,增加字体粗细和间距调整
- 删除旧的spin关键帧动画定义
- 更新JavaScript轮播文案逻辑,使用取模运算循环显示提示语
- 增加APP_STATE.namasteStableFrames变量重置逻辑
- 调整加载文案切换间隔时间从600ms延长至800ms
2025-12-04 21:19:28 +08:00
hehh
978b618df2 refactor(config): 统一缓存键命名并优化主题设置逻辑
- 将缓存键名从带版本后缀的形式统一为无版本后缀
- 更新主题设置与读取逻辑,使用配置中心的缓存键定义
- 修正语言检测时对中文标识的判断逻辑
- 优化页面脚本加载顺序,确保配置文件优先加载
- 调整主题显示文本的中英文切换条件判断表达式
2025-12-04 19:30:57 +08:00
hehh
0174d29bde feat(ui): 重构进入界面与交互模式按钮
- 重新设计进入界面的按钮和倒计时提示样式
- 新增交互模式按钮,支持动态显示和隐藏
- 修改默认进入方式为动画模式而非叙事模式
- 调整摄像头权限申请逻辑,改为用户主动点击触发
- 优化加载文案循环播放逻辑
- 更新多语言字典中的相关提示文本
- 改进页面加载完成后不自动启动摄像头的处理方式
2025-12-04 19:25:20 +08:00
hehh
7b9d343cf9 fix(me): 调整无摄像头进入逻辑与定时器设置
- 将自动进入延迟从5秒缩短至3秒
- 修改进入条件检查,使用 isLoaded 替代 mode 判断
- 移除粒子爆炸效果和叙事模式启动
- 清除加载文案定时器以避免冲突
- 更新点击事件中的状态判断逻辑
2025-12-04 18:55:17 +08:00
hehh
a2b627b4ed feat(me): 添加无摄像头权限时的直接进入功能
- 新增直接进入按钮和倒计时提示界面
- 实现5秒后自动进入档案馆逻辑
- 添加摄像头权限状态管理和重试机制
- 支持点击按钮立即跳过摄像头授权
- 更新多语言字典中的相关提示文本
- 调整粒子爆炸效果参数提升视觉体验
- 优化手势识别逻辑仅在摄像头启用时运行
- 添加重新申请摄像头权限的交互入口
2025-12-04 18:52:09 +08:00
hehh
0f6504a46b perf(me.html): 优化DOM访问以提升页面性能
- 引入DOM_CACHE对象缓存频繁访问的DOM元素
- 修改safeUpdateText和safeClass函数以接受DOM元素而非ID
- 替换所有document.getElementById调用为DOM_CACHE中的缓存引用
- 减少重复DOM查询,提高渲染和交互性能
2025-12-04 18:27:47 +08:00
hehh
5ced418d1a style(me.html): 优化CSS样式和布局结构
- 统一CSS属性书写格式,增加空格分隔rgba值
- 重构多行CSS属性,提高可读性
- 调整HTML元素间距和布局结构
- 优化JavaScript函数格式和变量命名
- 更新加载文案和界面提示文本
- 改进粒子系统动画参数配置
- 优化手势识别逻辑和UI交互反馈
2025-12-04 18:22:28 +08:00
hehh
67049a126f feat(me): 全面升级个人主页视觉与交互体验
- 引入昼夜模式下的动态渐变标题与光晕背景效果
- 新增极光动画层,提升页面沉浸感与视觉层次
- 优化粒子系统着色器,支持主题色彩漂移与星云渲染
- 改进叙事文本层样式,增强文字可读性与动效表现
- 调整双手合十解锁逻辑,提高手势识别准确率与稳定性
- 更新多语言文案内容,强化品牌表达与情感连接
- 增加退出冷却机制,避免误触并改善用户体验流畅度
- 统一动画曲线与过渡时长,确保界面响应自然和谐
2025-12-04 16:36:50 +08:00
hehh
c21d276f40 feat(ui): 重构加载与叙事层,优化手势交互体验
- 重构智能加载层结构与样式,提升初始化体验
- 优化白天/黑夜主题配色与粒子渲染效果
- 增强手势识别逻辑与视觉反馈
- 改进UI层DOM结构与类名语义化
- 更新内容字典与加载文案
- 修复安全DOM操作与空指针问题
- 调整物理引擎参数,增强交互手感
- 优化粒子爆炸与散开动画效果
- 统一状态管理对象,提高代码可维护性
- 增加权限提示与超时处理机制
2025-12-04 16:02:55 +08:00
hehh
e183f8bf63 feat(me): 实现昼夜主题切换与智能叙事系统
- 新增昼夜模式自动切换功能,根据时间和系统偏好设置主题
- 优化粒子系统,支持不同主题下的视觉效果差异
- 重构UI层结构,增强交互提示与视觉反馈
- 添加智能AI加载动画与科技感加载页
- 实现手势交互物理引擎,支持单手磁流体牵引与双手斥力场
- 增加多语言支持(中/英),动态内容切换
- 优化Three.js渲染配置,提升性能与视觉表现
- 添加叙事文本动画与过渡效果
- 改进着色器材质,支持颜色渐变插值
- 实现主题缓存机制,提高用户体验一致性
2025-12-04 14:26:51 +08:00
hehh
3031355836 feat(webgl): 重构粒子系统与手势交互引擎
- 重写粒子物理系统,支持速度与目标点分离的Verlet积分
- 新增辉光后期处理(UnrealBloomPass)增强视觉表现
- 优化手势识别引擎,提高捏合与滑动手势精度
- 修复摄像头画面镜像问题,改善交互准确性
- 增加叙事模式自动播放与手动切换功能
- 改进UI布局与加载动画,提升用户体验
- 调整粒子形态生成算法,新增黑洞、螺旋等预设
- 优化移动端渲染性能,限制高密度屏幕采样率
2025-12-04 00:32:11 +08:00
hehh
0de81219bb feat(me): 初始化个人介绍页面并集成手势交互粒子系统
- 创建基于Three.js的全屏粒子动画背景
- 集成MediaPipe手势识别实现手部追踪
- 实现多种手势控制:Namaste解锁、Pinch扭曲、Swipe旋转
- 设计默认动画循环(云朵、晶格、流动等六种形态)
- 添加叙事模式展示个人信息与理念
- 内置Web Audio API生成环境音效与交互音效
- 构建HUD显示系统监控帧率、手势状态与实体数量
- 支持响应式布局适配移动端与桌面端体验
- 使用着色器材质确保粒子渲染清晰度与性能
- 程序化生成文本点阵用于信息可视化呈现
2025-12-04 00:28:28 +08:00
hehh
aca4d5a0de fix(css): 调整英文版统计键和标签的字体大小响应式样式
- 删除了针对英文版移动端统计键字体大小的旧样式规则
- 新增了针对平板尺寸范围内英文版统计键字体大小的媒体查询
- 添加了针对特定屏幕宽度内英文版 MBTI 标签字体大小的媒体查询
- 优化了多语言环境下响应式布局的表现一致性
2025-12-02 23:59:55 +08:00
hehh
2d52212ad1 fix(audio): 更新音频文件路径以移除压缩后缀
- 移除了音频文件名中的 "_compressed" 后缀
- 确保音频文件能被正确加载和播放
- 保持了原有的自动播放和循环属性
2025-12-01 13:02:06 +08:00
hehh
88e1d9e3d9 style(css): 更新技术标签颜色值
- 将 tech-tag-3d 和 tech-tag-mobile 的颜色从白色更改为紫色 (#d57eeb)
2025-12-01 13:01:34 +08:00
hehh
4fe9034fb9 feat(about): 添加推荐分享模块和社交链接样式优化
- 新增推荐分享模块,包含动画效果和夜间模式适配
- 优化社交图标样式,统一设计风格
- 调整移动端社交布局,提升用户体验
- 修复夜间模式下文字阴影显示问题
- 移除重复的桌面端社交样式定义
2025-11-30 22:05:46 +08:00
hehh
6272941f3e style(artalk): 移除评论区编辑器的玻璃态样式
- 删除 atk-main-editor 的背景和边框玻璃态效果
- 移除相关的 backdrop-filter 和模糊样式
- 简化评论区域的样式定义
2025-11-30 18:34:04 +08:00
hehh
67b7c0e5f9 feat(audio): 改进音频自动播放逻辑
- 在HTML中将音频preload属性从none改为auto
- 添加用户交互检测以提高自动播放成功率
- 使用setTimeout延迟播放尝试以绕过浏览器限制
- 移除不必要的console.error输出
- 优化CSS以确保评论头像正确显示
2025-11-30 18:27:31 +08:00
hehh
a871a734ee chore(about): 更新页脚版权信息显示
- 将页脚中的版权符号从"©"更改为"Copyright ©"
- 保持其他页脚内容和链接不变
- 确保年份动态更新功能继续正常工作
2025-11-30 16:42:45 +08:00
22 changed files with 2592 additions and 916 deletions

View File

@@ -40,15 +40,16 @@
<meta property="wechat:description" content="我是一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。">
<!-- 核心资源:使用 BootCDN 加速 -->
<link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/remixicon/3.5.0/remixicon.min.css" rel="stylesheet">
<link rel="stylesheet" href="css/about.css?version=20251125">
<link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link href="https://cdn.bootcdn.net/ajax/libs/remixicon/3.5.0/remixicon.min.css" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link rel="preload" href="css/about.css?version=20251125" as="style" onload="this.onload=null;this.rel='stylesheet'">
<!-- Artalk 评论样式 -->
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.css">
<link rel="stylesheet" href="css/artalk.css?version=20251125">
<link rel="preload" href="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link rel="preload" href="css/artalk.css?version=20251125" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link rel="icon" href="favicon.ico">
<link rel="apple-touch-icon" href="./images/logo.png">
<!--IE淘汰计划-->
<script>
if (/*@cc_on!@*/false || (!!window.MSInputMethodContext && !!document.documentMode)) window.location.href = "https://imsyy.top/upgrade-your-browser/index.html?referrer=" + encodeURIComponent(window.location.href);
@@ -73,7 +74,7 @@
</a>
<div class="nav-menu">
<a href="index.html" class="nav-item">
<a href="index.html" class="nav-item" aria-label="Home">
<i class="ri-home-smile-2-line"></i>
<span class="nav-label" data-i18n="nav.home">首页</span>
</a>
@@ -81,7 +82,7 @@
<i class="ri-user-3-line"></i>
<span class="nav-label" data-i18n="nav.about">关于</span>
</a>
<a href="https://blog.hehouhui.cn" class="nav-item">
<a href="https://blog.hehouhui.cn" class="nav-item" aria-label="Honesty Blog">
<i class="ri-quill-pen-line"></i>
<span class="nav-label" data-i18n="nav.blog">博客</span>
</a>
@@ -110,7 +111,7 @@
<div class="bento-card area-profile">
<div class="profile-content">
<div class="avatar-ring">
<img src="images/avatar.jpeg" alt="Honesty" class="avatar-img" loading="lazy">
<img src="images/avatar.jpeg" alt="Honesty" class="avatar-img" loading="lazy" width="120" height="120">
<div class="status-dot" data-i18n="status.online">Online</div>
</div>
<div class="profile-info">
@@ -129,6 +130,15 @@
<a href="javascript:void(0);" onclick="toggleWechat()" class="s-icon" aria-label="WeChat QR code" tabindex="0"><i class="ri-wechat-fill"></i></a>
<a href="https://juejin.cn/user/3659591622878503" target="_blank" class="s-icon" aria-label="Juejin profile" tabindex="0"><i class="ri-code-box-line"></i></a>
</div>
<!-- 推荐分享模块 TRAE 链接https://www.trae.ai/s/8HSXCa -->
<div class="recommend-share-module">
<a href="https://www.trae.ai/s/8HSXCa" target="_blank" class="share-link" aria-label="推荐分享">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 18 18" class="share-icon">
<path stroke="#32F08C" stroke-width="1.35" d="M3.75 9H3V6h12v3h-.75M3.75 9v6h10.5V9M3.75 9h10.5M9 6V4.75M9 6H7.75a2.5 2.5 0 0 1-2.5-2.5c0-.69.56-1.25 1.25-1.25A2.5 2.5 0 0 1 9 4.75M9 6h1.25a2.5 2.5 0 0 0 2.5-2.5c0-.69-.56-1.25-1.25-1.25A2.5 2.5 0 0 0 9 4.75M9 6v9"></path>
</svg>
<span class="badge-dot"></span>
</a>
</div>
</div>
</div>
@@ -175,7 +185,7 @@
<span class="mbti-code gradient-text">INFJ</span>
<span class="mbti-name" data-i18n="mbti.name">Advocate</span>
<span class="mbti-icon">
<img src="images/INFJ.webp" alt="INFJ" style="width:32px;height:32px;border-radius:50%;border:2px solid rgba(255,255,255,0.4)" loading="lazy"/>
<img src="images/INFJ.webp" alt="INFJ" style="width:32px;height:32px;border-radius:50%;border:2px solid rgba(255,255,255,0.4)" loading="lazy" width="32" height="32"/>
</span>
</div>
<p class="mbti-desc" data-i18n="mbti.desc">"理想主义与道德感,果断决绝的行动力。深度洞察与创意,关怀与同理心。"</p>
@@ -301,8 +311,7 @@
<button id="fab-music" class="fab-item" tabindex="0"><i class="ri-music-2-line"></i><span class="fab-text">Play</span></button>
</div>
</div>
<!-- 隐藏的音频播放iframe -->
<iframe id="audio-player-iframe" src="audio-player.html" style="display: none;"></iframe>
<audio id="site-audio" class="site-audio" src="data/至少做一件离谱的事-Kiri T.mp3" autoplay loop preload="auto"></audio>
</main>
<!-- 微信弹窗 -->
@@ -311,8 +320,7 @@
<button class="modal-close" onclick="toggleWechat()"><i class="ri-close-line"></i></button>
<h3 data-i18n="modal.wechat">Official Account</h3>
<div class="qr-box">
<img src="./images/mp-honesy.jpg" alt="WeChat QR" onerror="this.style.display='none';this.nextElementSibling.style.display='block'" loading="lazy">
<!-- <div class="qr-fallback">QR Load Failed</div>-->
<img src="./images/mp-honesy.jpg" alt="WeChat QR" onerror="this.style.display='none';this.nextElementSibling.style.display='block'" loading="lazy" width="200" height="200">
</div>
<p data-i18n="modal.desc">Scan to follow Tech Share</p>
</div>
@@ -327,24 +335,25 @@
</div>
<!-- 脚本BootCDN jQuery / Artalk -->
<script src="js/jquery.min.js"></script>
<script src="js/config.js?version=20251125"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.js"></script>
<script src="js/about.js?version=20251125"></script>
<script src="js/jquery.min.js" defer></script>
<script src="js/config.js?version=20251125" defer></script>
<script src="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.js" defer></script>
<script src="js/about.js?version=20251125" defer></script>
<script defer src="https://events.vercount.one/js"></script>
<!-- 不蒜子统计 -->
<script>
document.addEventListener('DOMContentLoaded', function () {
// 动态加载不蒜子统计脚本
const script = document.createElement('script');
script.src = SiteConfig.analytics.busuanzi.src;
script.src = "//cdn.busuanzi.cc/busuanzi/3.6.9/busuanzi.abbr.min.js";
script.async = true;
document.head.appendChild(script);
});
</script>
<script>
try {
!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id: SiteConfig.analytics.tencent.id, ck: SiteConfig.analytics.tencent.ck});
!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"3OBGjwDdEIRS7XZ1",ck:"3OBGjwDdEIRS7XZ1"});
} catch (e) {
console.log("51.la统计错误", e);
}
@@ -356,7 +365,7 @@
(function () {
try {
var hm = document.createElement("script");
hm.src = SiteConfig.analytics.baidu.src;
hm.src = "https://hm.baidu.com/hm.js?ae2a009a75b13c21d5121ee51375ea4e";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
} catch (e) {
@@ -370,8 +379,8 @@
<script>
// 监听不蒜子数据的错误兜底
function initFormatter() {
const pvEl = document.getElementById(SiteConfig.analytics.busuanzi.site_pv_id);
const uvEl = document.getElementById(SiteConfig.analytics.busuanzi.site_uv_id);
const pvEl = document.getElementById("busuanzi_site_pv");
const uvEl = document.getElementById("busuanzi_site_uv");
if (!pvEl && !uvEl) return;
console.log('[Busuanzi]', 'Formatting... Listener observer');
@@ -401,5 +410,12 @@
initFormatter();
}
</script>
<noscript>
<link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/remixicon/3.5.0/remixicon.min.css" rel="stylesheet">
<link rel="stylesheet" href="css/about.css?version=20251125">
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.css">
<link rel="stylesheet" href="css/artalk.css?version=20251125">
</noscript>
</body>
</html>

View File

@@ -1,208 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audio Player</title>
<style>
body {
margin: 0;
padding: 0;
background: transparent;
}
</style>
</head>
<body>
<audio id="audio-player" controls autoplay style="display: none;"></audio>
<script>
const audio = document.getElementById('audio-player');
let currentSrc = '';
let isStateBroadcasting = false;
// 监听来自主页面的消息
window.addEventListener('message', function(event) {
// 允许来自同域的所有窗口的消息
if (event.origin !== window.location.origin) return;
const data = event.data;
switch (data.action) {
case 'play':
// 如果当前正在播放,则暂停;否则播放
if (audio.paused) {
audio.play().catch(e => console.error('播放失败:', e));
} else {
audio.pause();
}
// 发送状态更新回所有可能的窗口
broadcastState();
break;
case 'pause':
if (!audio.paused) {
audio.pause();
}
broadcastState();
break;
case 'setTrack':
if (currentSrc !== data.src) {
currentSrc = data.src;
audio.src = data.src;
audio.load();
// 自动播放新曲目
setTimeout(() => {
audio.play().catch(e => console.error('播放失败:', e));
broadcastState();
}, 100);
}
break;
case 'setVolume':
audio.volume = data.volume;
broadcastState();
break;
case 'getCurrentState':
// 向请求方发送当前状态
event.source.postMessage({
action: 'currentState',
playing: !audio.paused,
src: audio.src,
volume: audio.volume,
currentTime: audio.currentTime,
duration: audio.duration || 0
}, event.origin);
break;
}
});
// 广播状态到所有可能监听的窗口
function broadcastState() {
if (isStateBroadcasting) return; // 防止状态广播循环
isStateBroadcasting = true;
window.parent.postMessage({
action: 'stateChange',
playing: !audio.paused,
src: audio.src,
volume: audio.volume,
currentTime: audio.currentTime,
duration: audio.duration || 0
}, '*');
// 保存播放状态
savePlaybackState();
setTimeout(() => {
isStateBroadcasting = false;
}, 10);
}
// 监听音频事件并通知主页面
audio.addEventListener('play', function() {
broadcastState();
});
audio.addEventListener('pause', function() {
broadcastState();
});
audio.addEventListener('ended', function() {
window.parent.postMessage({
action: 'trackEnded'
}, '*');
});
// 页面加载完成后通知主页面
window.addEventListener('load', function() {
// 恢复之前保存的播放状态
restorePlaybackState();
window.parent.postMessage({
action: 'playerReady'
}, '*');
});
// 页面可见性变化时处理
document.addEventListener('visibilitychange', function() {
if (!document.hidden) {
// 页面变为可见时,广播当前状态
setTimeout(broadcastState, 100);
}
});
// 页面即将卸载时保存播放状态
window.addEventListener('beforeunload', function() {
savePlaybackState();
});
// 保存播放状态到 sessionStorage
function savePlaybackState() {
try {
sessionStorage.setItem('audioState', JSON.stringify({
src: audio.src,
currentTime: audio.currentTime,
playing: !audio.paused,
volume: audio.volume,
timestamp: Date.now()
}));
} catch (e) {
console.error('保存音频状态失败:', e);
}
}
// 从 sessionStorage 恢复播放状态
function restorePlaybackState() {
try {
const savedState = sessionStorage.getItem('audioState');
if (savedState) {
const state = JSON.parse(savedState);
// 如果状态保存时间不超过1小时则恢复播放
if (Date.now() - state.timestamp < 3600000) {
currentSrc = state.src;
audio.src = state.src;
audio.volume = state.volume !== undefined ? state.volume : 1.0;
audio.currentTime = state.currentTime || 0;
// 确保在用户交互后才尝试播放
if (state.playing) {
// 检查是否已经有用户交互
if (document.hasFocus()) {
// 稍微延迟播放,确保一切准备就绪
setTimeout(() => {
audio.play().catch(e => {
console.error('恢复播放失败:', e);
// 如果自动播放失败,等待用户交互后再播放
const tryPlayOnInteraction = () => {
audio.play().catch(console.error);
document.removeEventListener('click', tryPlayOnInteraction);
document.removeEventListener('touchstart', tryPlayOnInteraction);
};
document.addEventListener('click', tryPlayOnInteraction, { once: true });
document.addEventListener('touchstart', tryPlayOnInteraction, { once: true });
});
}, 300);
} else {
// 等待用户交互后再播放
const tryPlayOnInteraction = () => {
audio.play().catch(console.error);
document.removeEventListener('click', tryPlayOnInteraction);
document.removeEventListener('touchstart', tryPlayOnInteraction);
};
document.addEventListener('click', tryPlayOnInteraction, { once: true });
document.addEventListener('touchstart', tryPlayOnInteraction, { once: true });
}
}
}
}
} catch (e) {
console.error('恢复音频状态失败:', e);
}
}
</script>
</body>
</html>

1497
christmas.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -584,8 +584,6 @@ body {
contain: layout style;
}
.avatar-ring {
position: relative;
width: 120px;
@@ -638,6 +636,48 @@ body {
justify-content: center;
gap: 4px;
}
/* Social Links */
.social-dock {
display: flex;
gap: 12px;
margin-top: 20px;
}
.s-icon {
width: 46px;
height: 46px;
border-radius: 50%;
background: rgba(128, 128, 128, 0.1);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
transition: all 0.3s;
text-decoration: none;
}
.s-icon:hover {
background: var(--accent);
color: #fff;
transform: translateY(-3px);
}
.s-icon:hover i {
color: transparent !important;
-webkit-text-fill-color: transparent;
}
.s-icon i {
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
}
.s-icon[href*="github.com"] i { background: var(--gradient-7); }
.s-icon[href^="mailto:"] i { background: var(--gradient-6); }
.s-icon[href*="blog.hehouhui.cn"] i { background: var(--gradient-3); }
.s-icon[href*="zhihu.com"] i { background: var(--gradient-4); }
.s-icon[href*="juejin.cn"] i { background: var(--gradient-1); }
.s-icon[onclick*="toggleWechat"] i { background: var(--gradient-5); }
/* Tablet Profile Layout */
@media (min-width: 769px) and (max-width: 1024px) {
.area-profile {
@@ -689,6 +729,40 @@ body {
.desktop-social {
display: none;
}
/* 移动端推荐分享模块调整 */
.recommend-share-module {
right: 15px;
bottom: 15px;
opacity: 0;
transform: translateY(10px);
animation: shareModuleFadeIn 0.5s ease-out 0.5s forwards;
}
@keyframes shareModuleFadeIn {
to {
opacity: 1;
transform: translateY(0);
}
}
.share-link {
width: 36px;
height: 36px;
}
.share-icon {
width: 16px;
height: 16px;
}
.badge-dot {
width: 6px;
height: 6px;
top: 1px;
right: 1px;
border-width: 1px;
}
}
/* Night Theme Profile Styles */
@@ -704,10 +778,10 @@ body {
background-clip: text !important;
color: transparent !important;
text-shadow:
0 0 10px rgba(255, 126, 179, 0.8),
0 0 20px rgba(255, 117, 140, 0.7),
0 0 30px rgba(255, 107, 107, 0.6),
0 0 40px rgba(255, 154, 139, 0.5);
0 0 2px rgba(255, 126, 179, 0.3),
0 0 6px rgba(255, 117, 140, 0.1),
0 0 32px rgba(255, 107, 107, 0.4),
0 0 10px rgba(255, 154, 139, 0.5);
transform: translateZ(0);
-webkit-text-stroke: 0.3px rgba(0, 0, 0, 0.3);
}
@@ -725,16 +799,29 @@ body {
-webkit-text-stroke: 0.2px rgba(0, 0, 0, 0.2);
}
/* Social Links */
.social-dock {
display: flex;
gap: 12px;
margin-top: 20px;
/* 推荐分享模块 */
.recommend-share-module {
position: absolute;
right: 20px;
bottom: 20px;
opacity: 0;
transform: translateY(10px);
animation: shareModuleFadeIn 0.5s ease-out 0.5s forwards;
}
.s-icon {
width: 46px;
height: 46px;
@keyframes shareModuleFadeIn {
to {
opacity: 1;
transform: translateY(0);
}
}
.share-link {
position: relative;
width: 42px;
height: 42px;
border-radius: 50%;
background: rgba(128, 128, 128, 0.1);
display: flex;
@@ -743,29 +830,51 @@ body {
color: var(--text-secondary);
transition: all 0.3s;
text-decoration: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.s-icon:hover {
.share-link:hover {
background: var(--accent);
color: #fff;
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.s-icon:hover i {
color: transparent !important;
-webkit-text-fill-color: transparent;
.share-icon {
width: 18px;
height: 18px;
}
.s-icon i {
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
.share-icon path {
stroke: #32F08C;
}
.badge-dot {
position: absolute;
top: 2px;
right: 2px;
width: 8px;
height: 8px;
background: #ff4757;
border-radius: 50%;
border: 2px solid var(--bg-base);
}
/* 夜间模式下的推荐分享模块 */
[data-theme="night"] .share-link {
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
[data-theme="night"] .share-link:hover {
box-shadow: 0 4px 15px rgba(108, 92, 231, 0.4);
}
/* 夜间模式下的SVG图标 */
[data-theme="night"] .share-icon path {
stroke: #32F08C;
filter: drop-shadow(0 0 4px rgba(50, 240, 140, 0.4));
}
.s-icon[href*="github.com"] i { background: var(--gradient-7); }
.s-icon[href^="mailto:"] i { background: var(--gradient-6); }
.s-icon[href*="blog.hehouhui.cn"] i { background: var(--gradient-3); }
.s-icon[href*="zhihu.com"] i { background: var(--gradient-4); }
.s-icon[href*="juejin.cn"] i { background: var(--gradient-1); }
.s-icon[onclick*="toggleWechat"] i { background: var(--gradient-5); }
/* Night Theme Social Styles */
[data-theme="night"] .s-icon {
@@ -785,9 +894,19 @@ body {
display: none !important;
}
.social-dock { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; justify-items: center; }
.s-icon { width: 42px; height: 42px; }
.s-icon i { font-size: 1.6rem; }
.social-dock {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
justify-items: center;
}
.s-icon {
width: 42px;
height: 42px;
}
.s-icon i {
font-size: 1.6rem;
}
}
/* Mobile Social Layout */
@@ -801,9 +920,8 @@ body {
justify-content: space-around;
padding: 20px;
}
.desktop-social {
display: none;
}
.ms-btn {
width: 40px;
height: 40px;
@@ -1005,6 +1123,18 @@ body {
text-shadow: 0 0 5px rgba(255, 255, 255, 0.1);
}
@media (min-width: 769px) and (max-width: 1445px){
[data-lang="en"] .stat-key {
font-size: 0.5rem !important;
}
}
@media (min-width: 1030px) and (max-width: 1445px) {
[data-lang="en"] .mbti-tags .tag {
font-size: 0.5rem !important;
}
}
/* Responsive Stats Layout */
@media (min-width: 1025px) and (max-width: 1445px), (min-width: 1201px) {
.area-stats {
@@ -1395,8 +1525,8 @@ body {
}
[data-theme="day"] .tech-tag-3d.tag-color-23,
[data-theme="day"] .tech-tag-mobile.tag-color-23 {
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
color: #d57eeb !important;
-webkit-text-fill-color: #d57eeb !important;
}
[data-theme="day"] .tech-tag-3d.tag-color-24,
[data-theme="day"] .tech-tag-mobile.tag-color-24 {
@@ -2295,12 +2425,7 @@ html, body {
overflow-x: hidden;
}
/* Fix Stat Key Font Size on English Mobile */
@media (max-width: 768px) {
[data-lang="en"] .stat-key {
font-size: 6px !important;
}
}
/* Final Theme Consistency Checks */
[data-theme="night"] {

View File

@@ -6,15 +6,7 @@
contain: layout style;
}
/* 确保评论区域适配白天/黑夜模式 */
#artalk-container .atk-main-editor {
background: var(--glass-bg);
border: var(--glass-border);
box-shadow: var(--glass-shadow);
backdrop-filter: blur(var(--glass-blur)) saturate(130%);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(130%);
border-radius: 24px;
}
#artalk-container .atk-comment-wrap {
background: rgba(128, 128, 128, 0.03);
@@ -450,6 +442,12 @@
height: 28px !important;
object-fit: cover; /* 修复移动端头像拉伸问题 */
}
.atk-comment>.atk-avatar img {
object-fit: cover;
object-position: center;
width: 28px !important;
height: 28px !important;
}
.atk-meta {
font-size: 12px !important;

BIN
data/christmas.mp3 Normal file

Binary file not shown.

View File

@@ -15,11 +15,18 @@
},
{
"name": "Home",
"stargazers_count": 2,
"stargazers_count": 3,
"forks_count": 0,
"description": "现代化个人主页,融合科技感与个性化元素,展示个人技术栈、开源项目和博客文章。",
"html_url": "https://github.com/listener-He/Home"
},
{
"name": "auto-ip2region",
"stargazers_count": 2,
"forks_count": 0,
"description": "Auto IP2Region 是一个智能化的IP地址地理信息解析库它结合了本地数据库和多个免费在线API服务通过智能负载均衡和自动故障转移机制为您提供准确、可靠的IP地理位置信息查询服务。",
"html_url": "https://github.com/listener-He/auto-ip2region"
},
{
"name": "collection-complete",
"stargazers_count": 2,
@@ -34,6 +41,13 @@
"description": "",
"html_url": "https://github.com/listener-He/keycloak-services-social-weixin"
},
{
"name": "fast-blur",
"stargazers_count": 1,
"forks_count": 0,
"description": "High-performance, lightweight data obfuscation library for Java. Offers fast, reversible data transformation without traditional encryption overhead. Ideal for caching, logging, and performance-critical applications. / 专为Java设计的高性能轻量级数据混淆库。提供快速、可逆的数据转换无需传统加密开销。适用于缓存、日志和性能关键型应用。",
"html_url": "https://github.com/listener-He/fast-blur"
},
{
"name": "Notion-Wechat-Blog",
"stargazers_count": 1,

View File

@@ -10,7 +10,7 @@
"blog": "https://www.hehouhui.cn",
"hireable": true,
"bio": "Hi, Im Honesty—Shanghai Java/AI Dev (7+ yrs). Spring AI, LLM, TensorFlow, Faiss. Write tech content, cycle for inspiration. AI-obsessed.",
"public_repos": 165,
"public_repos": 167,
"public_gists": 0,
"followers": 6,
"following": 12,

BIN
data/hand_landmarker.task Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

8
edgeone.json Normal file
View File

@@ -0,0 +1,8 @@
{
"rewrites": [
{
"source": "/.well-known/*.txt",
"destination": "/well-known/:splat.txt"
}
]
}

View File

@@ -335,10 +335,7 @@
<script type="text/javascript" src="js/fetch.min.js"></script>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript" src="js/main.js?version=3"></script>
<!--<script type="text/javascript" src="js/bj.js"></script>-->
<!--<script type="text/javascript" src="https://cdn.jsdmirror.com/gh/listener-He/Home/js/moments.js"></script>-->
<script async src="https://analyse.hehouhui.cn/tracker.js" data-ackee-server="https://analyse.hehouhui.cn"
data-ackee-domain-id="7887135f-a413-46e2-a98c-52d4f18d9973"></script>
<script defer src="https://events.vercount.one/js"></script>
<!-- 不蒜子统计 -->
<script>
@@ -413,26 +410,5 @@
}()
}({id: SiteConfig.analytics.tencent.id, ck: SiteConfig.analytics.tencent.ck});
</script>
<!-- 隐藏的音频播放iframe -->
<iframe id="audio-player-iframe" src="audio-player.html" style="display: none;"></iframe>
<script>
// 音频控制逻辑
let audioIframe = document.getElementById('audio-player-iframe');
// 监听来自iframe的消息
window.addEventListener('message', function(event) {
// 确保消息来自我们的iframe
if (event.source !== audioIframe.contentWindow) return;
const data = event.data;
if (data.playing && data.action === 'playerReady') {
// iframe准备就绪设置初始音频
audioIframe.contentWindow.postMessage({
action: 'setTrack',
src: 'data/至少做一件离谱的事-Kiri T_compressed.mp3'
}, '*');
}
});
</script>
</body>
</html>

View File

@@ -1,215 +0,0 @@
/**
* Starry Sky
*
* 作者: DoWake
* 描述使用Canvas绘制星空
* 地址https://github.com/DoWake/StarrySky
* 日期2023/03/02
*/
const StarrySky = function () {
//Canvas元素
let canvasElement;
//Canvas 2D对象
let canvasContext;
//Canvas 宽度
let canvasWidth;
//Canvas 高度
let canvasHeight;
//星星列表
let starList;
//星星颜色列表rgb格式"255, 255, 255"
let starColorList;
//星星半径大小
let starRadius;
//焦距等级与canvasWidth相乘必须大于0
let focalDistanceLevel;
//星星数量等级与canvasWidth相乘必须大于0
let starCountLevel;
//星星速度等级与焦距相乘必须大于0
let starSpeedLevel;
//焦距
let focalDistance;
//星星数量
let starCount;
//执行动画
let rAF;
return {
//初始化
init: function (canvas_element) {
if (canvas_element && canvas_element.nodeName === "CANVAS") {
canvasElement = canvas_element;
canvasElement.width = canvasElement.clientWidth;
canvasElement.height = canvasElement.clientHeight;
canvasElement.style.backgroundColor = "black";
canvasContext = canvasElement.getContext("2d");
canvasWidth = canvasElement.clientWidth;
canvasHeight = canvasElement.clientHeight;
starColorList = ["255, 255, 255"];
starRadius = 1;
focalDistanceLevel = 0.4;
starCountLevel = 0.2;
starSpeedLevel = 0.0005;
focalDistance = canvasWidth * focalDistanceLevel;
starCount = Math.ceil(canvasWidth * starCountLevel);
starList = [];
for (let i = 0; i < starCount; i++) {
starList[i] = {
x: canvasWidth * (0.1 + 0.8 * Math.random()),
y: canvasHeight * (0.1 + 0.8 * Math.random()),
z: focalDistance * Math.random(),
color: starColorList[Math.ceil(Math.random() * 1000) % starColorList.length]
}
}
const self = this;
window.addEventListener("resize", self.throttle(function () {
canvasElement.width = canvasElement.clientWidth;
canvasElement.height = canvasElement.clientHeight;
canvasWidth = canvasElement.clientWidth;
canvasHeight = canvasElement.clientHeight;
focalDistance = canvasWidth * focalDistanceLevel;
const starCount2 = Math.ceil(canvasWidth * starCountLevel);
if (starCount > starCount2) {
starList.splice(starCount2);
} else {
let num = starCount2 - starCount;
while (num--) {
starList.push({
x: canvasWidth * (0.1 + 0.8 * Math.random()),
y: canvasHeight * (0.1 + 0.8 * Math.random()),
z: focalDistance * Math.random(),
color: starColorList[Math.ceil(Math.random() * 1000) % starColorList.length]
});
}
}
starCount = Math.ceil(canvasWidth * starCountLevel);
}, 200), { passive: true });
} else {
console.error('初始化失败必须传入Canvas元素');
}
},
//设置星空背景颜色
setSkyColor: function (sky_color = "black") {
canvasElement.style.backgroundColor = sky_color;
},
//设置星星半径大小
setStarRadius: function (star_radius = 1) {
starRadius = star_radius;
},
//设置焦距等级
setFocalDistanceLevel: function (focal_distance_level = 0.4) {
focalDistanceLevel = focal_distance_level;
focalDistance = canvasWidth * focalDistanceLevel
},
//设置星星数量等级
setStarCountLevel: function (star_count_level = 0.2) {
starCountLevel = star_count_level;
const starCount2 = Math.ceil(canvasWidth * starCountLevel);
if (starCount > starCount2) {
starList.splice(starCount2);
} else {
let num = starCount2 - starCount;
while (num--) {
starList.push({
x: canvasWidth * (0.1 + 0.8 * Math.random()),
y: canvasHeight * (0.1 + 0.8 * Math.random()),
z: focalDistance * Math.random(),
color: starColorList[Math.ceil(Math.random() * 1000) % starColorList.length]
});
}
}
starCount = Math.ceil(canvasWidth * starCountLevel);
},
//设置星星速度等级
setStarSpeedLevel: function (star_speed_level = 0.0005) {
starSpeedLevel = star_speed_level
},
/**
* 设置星星颜色
* @param {Array|String} color 星星颜色
* @param {Boolean} mode 是否立刻同步颜色
*/
setStarColorList: function (color, mode = false) {
if (typeof color === 'object') {
starColorList = color;
} else if (typeof color === 'string') {
starColorList.push(color);
}
if (mode) {
for (let i = 0; i < starList.length; i++) {
starList[i]["color"] = starColorList[Math.ceil(Math.random() * 1000) % starColorList.length];
}
}
},
//渲染
render: function () {
const starSpeed = canvasWidth * focalDistanceLevel * starSpeedLevel;
//清空画布
canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
//计算位置
for (let i = 0; i < starList.length; i++) {
const star = starList[i];
const star_x = (star["x"] - canvasWidth / 2) * (focalDistance / star["z"]) + canvasWidth / 2;
const star_y = (star["y"] - canvasHeight / 2) * (focalDistance / star["z"]) + canvasHeight / 2;
star["z"] -= starSpeed;
if (star["z"] > 0 && star["z"] <= focalDistance && star_x >= -20 && star_x <= canvasWidth + 20 && star_y >= -20 && star_y <= canvasHeight + 20) {
const star_radius = starRadius * (focalDistance / star["z"] * 0.8);
const star_opacity = 1 - 0.8 * (star["z"] / focalDistance);
canvasContext.fillStyle = "rgba(" + star["color"] + ", " + star_opacity + ")";
canvasContext.shadowOffsetX = 0;
canvasContext.shadowOffsetY = 0;
canvasContext.shadowColor = "rgb(" + star["color"] + ")";
canvasContext.shadowBlur = 5;
canvasContext.beginPath();
canvasContext.arc(star_x, star_y, star_radius, 0, 2 * Math.PI);
canvasContext.fill();
} else {
const z = focalDistance * Math.random();
star["x"] = canvasWidth * (0.1 + 0.8 * Math.random());
star["y"] = canvasHeight * (0.1 + 0.8 * Math.random());
star["z"] = z;
star["color"] = starColorList[Math.ceil(Math.random() * 1000) % starColorList.length];
}
}
const self = this;
rAF = window.requestAnimationFrame(function () {
self.render();
});
},
//销毁
destroy: function () {
window.cancelAnimationFrame(rAF);
starList = [];
canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
canvasElement.width = 0;
canvasElement.height = 0;
},
//防抖
debounce: function (func, time = 200) {
let timeId;
return function () {
if (timeId) {
clearTimeout(timeId);
}
timeId = setTimeout(function () {
func();
}, time);
}
},
//节流
throttle: function (func, time = 200) {
let timeId = null;
let pre = 0;
return function () {
if (Date.now() - pre > time) {
clearTimeout(timeId);
pre = Date.now();
func();
} else {
timeId = setTimeout(func, time);
}
};
}
}
}();

View File

@@ -13,7 +13,7 @@ function getStoredLanguage() {
// 公共方法:设置本地存储的主题设置
function setStoredTheme(theme) {
const cacheKey = window.SiteConfig?.cacheKeys?.theme?.key || 'theme-v2';
const cacheKey = window.SiteConfig?.cacheKeys?.theme?.key || 'theme';
localStorage.setItem(cacheKey, JSON.stringify({
value: theme, time: new Date().getTime()
}));
@@ -534,9 +534,6 @@ class DataManager {
=========================== */
class UIManager {
constructor() {
this.userInteracted = false;
this.audioPlayer = null;
this.audioIframe = null;
this.initTechCloud();
this.initModal();
this.initArtalk();
@@ -1083,33 +1080,10 @@ class UIManager {
const theme = getStoredTheme();
fLang.querySelector('.fab-text').textContent = lang === 'zh' ? 'English' : '中文';
fTheme.querySelector('.fab-text').textContent = theme === 'night' ? 'Day' : 'Night';
// 音频播放状态需要通过iframe通信获取
const audioIframe = document.getElementById('audio-player-iframe');
if (audioIframe && audioIframe.contentWindow) {
// 请求当前播放状态
audioIframe.contentWindow.postMessage({
action: 'getCurrentState'
}, '*');
}
const playing = (this.audio && !this.audio.paused);
fMusic.querySelector('.fab-text').textContent = lang === 'zh' ? (playing ? '暂停' : '播放') : (playing ? 'Pause' : 'Play');
});
};
// 监听来自iframe的音频状态更新
window.addEventListener('message', (event) => {
const audioIframe = document.getElementById('audio-player-iframe');
if (!audioIframe || event.source !== audioIframe.contentWindow) return;
const data = event.data;
if (data.action === 'currentState') {
const fMusic = document.getElementById('fab-music');
if (fMusic) {
const lang = getStoredLanguage();
fMusic.querySelector('.fab-text').textContent =
lang === 'zh' ? (data.playing ? '暂停' : '播放') : (data.playing ? 'Pause' : 'Play');
}
}
});
main.addEventListener('click', () => {
menu.classList.toggle('open');
main.setAttribute('aria-expanded', menu.classList.contains('open') ? 'true' : 'false');
@@ -1127,15 +1101,17 @@ class UIManager {
requestAnimationFrame(updateLabels);
});
fMusic.addEventListener('click', () => {
const audioIframe = document.getElementById('audio-player-iframe');
if (audioIframe && audioIframe.contentWindow) {
// 直接发送播放指令让iframe内部处理播放/暂停切换
audioIframe.contentWindow.postMessage({
action: 'play'
}, '*');
// 记录用户操作
this.setMusicPauseTime(); // 记录暂停时间
if (this.audio) {
if (this.audio.paused) {
this.audio.play().catch(() => {
});
// 清除暂停时间记录,允许下次自动播放
this.clearMusicPauseTime();
} else {
this.audio.pause();
// 记录暂停时间
this.setMusicPauseTime();
}
}
// 延迟更新标签以避免阻塞
requestAnimationFrame(updateLabels);
@@ -1146,33 +1122,33 @@ class UIManager {
initAudio() {
// 获取已存在的iframe或创建新的
let audioIframe = document.getElementById('audio-player-iframe');
if (!audioIframe) {
audioIframe = document.createElement('iframe');
audioIframe.src = 'audio-player.html';
audioIframe.style.display = 'none';
audioIframe.id = 'audio-player-iframe';
document.body.appendChild(audioIframe);
}
this.audioIframe = audioIframe;
const el = document.getElementById('site-audio');
if (!el) return;
this.audio = el;
this.audio.loop = true;
const autoPlayer = () => {
// 页面加载完成后根据条件决定是否播放
window.addEventListener('load', () => {
// 检查是否在24小时内用户暂停过音乐
const shouldRemainPaused = this.shouldMusicRemainPaused();
// 如果不应该保持暂停状态,则尝试播放
if (!shouldRemainPaused) {
let userInteracted = true;
this.audio.play().catch(() => {
// 静默处理播放失败
userInteracted = false;
});
// 添加用户交互检查,避免浏览器阻止自动播放
const attemptAutoplay = () => {
// 检查是否已有用户交互
if (this.userInteracted) {
this.playAudio();
} else {
if (this.userInteracted === false) {
// 添加一次性用户交互监听器
const enableAudio = () => {
this.userInteracted = true;
this.playAudio();
setTimeout(() => {
this.audio.play().catch(() => {
});
}, 1000);
document.removeEventListener('click', enableAudio);
document.removeEventListener('touchstart', enableAudio);
document.removeEventListener('keydown', enableAudio);
@@ -1185,86 +1161,9 @@ class UIManager {
document.addEventListener('mousemove', enableAudio, { once: true });
}
};
setTimeout(attemptAutoplay, 500); // 稍微延迟以确保iframe加载完成
}
}
// 监听iframe发来的消息
const handleMessage = (event) => {
// 确保消息来自我们的iframe
if (event.source !== this.audioIframe.contentWindow) return;
const data = event.data;
switch (data.action) {
case 'playerReady':
// iframe准备就绪设置初始音频
this.setAudioTrack('data/至少做一件离谱的事-Kiri T_compressed.mp3');
autoPlayer();
break;
case 'stateChange':
// 音频状态改变更新UI
this.updateAudioUI(data.playing);
break;
case 'trackEnded':
// 曲目结束
this.updateAudioUI(false);
break;
case 'currentState':
// 当前状态响应
this.updateAudioUI(data.playing);
if (!data.playing) {
autoPlayer();
}
break;
}
};
window.addEventListener('message', handleMessage);
}
// 播放音频
playAudio() {
const audioIframe = document.getElementById('audio-player-iframe');
if (audioIframe && audioIframe.contentWindow) {
audioIframe.contentWindow.postMessage({
action: 'play'
}, '*');
}
}
// 暂停音频
pauseAudio() {
const audioIframe = document.getElementById('audio-player-iframe');
if (audioIframe && audioIframe.contentWindow) {
audioIframe.contentWindow.postMessage({
action: 'pause'
}, '*');
}
}
// 设置音频曲目
setAudioTrack(src) {
const audioIframe = document.getElementById('audio-player-iframe');
if (audioIframe && audioIframe.contentWindow) {
audioIframe.contentWindow.postMessage({
action: 'setTrack',
src: src
}, '*');
}
}
// 更新音频UI状态
updateAudioUI(playing) {
// 更新移动端fab按钮的文本
const fMusic = document.getElementById('fab-music');
if (fMusic) {
const lang = getStoredLanguage();
fMusic.querySelector('.fab-text').textContent =
lang === 'zh' ? (playing ? '暂停' : '播放') : (playing ? 'Pause' : 'Play');
requestAnimationFrame(attemptAutoplay);
}
});
}
updateCustomStyles(container, theme) {

View File

@@ -1,42 +0,0 @@
// audio-service-worker.js
// Service Worker for background audio playback
self.addEventListener('install', event => {
self.skipWaiting();
});
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim());
});
// 监听来自主页面的消息
self.addEventListener('message', async event => {
const client = event.source;
const data = event.data;
switch (data.action) {
case 'play':
// 这里只是示例实际上Service Worker无法直接播放音频
// 我们需要采用另一种方式实现
break;
case 'pause':
break;
case 'setTrack':
break;
}
});
// 使用 Broadcast Channel API 在页面间通信
const broadcastChannel = new BroadcastChannel('audio-control');
broadcastChannel.addEventListener('message', event => {
const data = event.data;
// 将消息转发给所有客户端
self.clients.matchAll().then(clients => {
clients.forEach(client => {
client.postMessage(data);
});
});
});

View File

@@ -1,7 +1,8 @@
// 配置文件 - 提取自各个JavaScript文件的关键配置
// 创建日期: 2025-11-20
const SiteConfig = {
(function() {
const SiteConfig = {
// bj.js 配置
stars: {
count: 300,
@@ -43,9 +44,9 @@ const SiteConfig = {
// 通用缓存键与TTL毫秒
cacheKeys: {
github: { key: 'gh_data_v2', ttlMs: 36000000 },
blog: { key: 'blog_data_v2', ttlMs: 3600000 },
theme: { key: 'theme_v2', ttlMs: 3600000 }
github: { key: 'gh_data', ttlMs: 36000000 },
blog: { key: 'blog_data', ttlMs: 3600000 },
theme: { key: 'theme', ttlMs: 3600000 }
},
techStack: [
@@ -168,12 +169,13 @@ const SiteConfig = {
dev: {
isLocal: (typeof location !== 'undefined') ? (location.hostname.indexOf('localhost') > -1 || location.hostname.indexOf('127.0.0.1') > -1) : false
}
};
};
// 导出配置
if (typeof module !== 'undefined' && module.exports) {
// 导出配置
if (typeof module !== 'undefined' && module.exports) {
module.exports = SiteConfig;
} else if (typeof window !== 'undefined') {
} else if (typeof window !== 'undefined') {
window.SiteConfig = SiteConfig;
}
}
})();

View File

@@ -1,58 +0,0 @@
$(document).ready(function () {
const iframe = document.getElementById('moment-frame');
const momentsContainer = document.getElementById('moments-container');
function animateIframe() {
momentsContainer.style.display = 'block';
momentsContainer.classList.add('visible');
}
function openMoments(url) {
if (iframe.src == null || iframe.src === '' || iframe.src !== url) {
iframe.src = url;
iframe.onload = () => {
setTimeout(() => {
animateIframe();
}, 300); // 延迟更自然
};
} else {
animateIframe();
}
}
// 处理瞬间链接点击事件
$('.moments-link').on('click', function (e) {
e.preventDefault(); // 阻止默认跳转
// 获取链接地址
const url = "https://moments.hehouhui.cn";
// 判断是否是移动端
const isMobile = /iPhone|Android/i.test(navigator.userAgent);
if (isMobile) {
// 移动端:直接跳转
window.location.href = url;
} else {
// PC端在iframe中显示
openMoments(url);
}
});
// 关闭按钮点击事件
$('.close-btn').on('click', function () {
momentsContainer.classList.remove('visible');
setTimeout(() => {
momentsContainer.style.display = 'none';
}, 500); // 等待动画结束后隐藏
});
// 遮罩层点击事件 点击空白处关闭模拟器
$('.overlay').on('click', function () {
momentsContainer.classList.remove('visible');
setTimeout(() => {
momentsContainer.style.display = 'none';
}, 500); // 等待动画结束后隐藏
});
});

620
me.html Normal file
View File

@@ -0,0 +1,620 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>ETHEREAL | HAND PHYSICS</title>
<style>
:root {
--bg-color: #020202;
--ui-font: 'Helvetica Neue', 'Arial', sans-serif;
--serif-font: 'Times New Roman', serif;
}
body {
margin: 0;
overflow: hidden;
background-color: var(--bg-color);
font-family: var(--ui-font);
cursor: none; /* 隐藏鼠标 */
}
#canvas-container {
position: fixed;
inset: 0;
z-index: 1;
}
#input-video { display: none; }
/* 极简主义 UI */
#ui-layer {
position: fixed;
inset: 0;
z-index: 10;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 40px;
mix-blend-mode: exclusion; /* 高级混合模式 */
}
.top-bar {
display: flex;
justify-content: space-between;
text-transform: uppercase;
font-size: 10px;
letter-spacing: 2px;
color: #fff;
opacity: 0.7;
}
.center-stage {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
width: 100%;
}
.art-title {
font-family: var(--serif-font);
font-size: 48px;
font-weight: 100;
letter-spacing: 12px;
color: #fff;
opacity: 0;
transition: opacity 2s ease;
}
.art-sub {
font-size: 10px;
letter-spacing: 6px;
color: #fff;
margin-top: 15px;
opacity: 0;
transition: opacity 2s 0.5s ease;
}
.visible { opacity: 1 !important; }
.debug-info {
position: absolute;
bottom: 40px;
left: 40px;
font-family: monospace;
font-size: 10px;
color: rgba(255,255,255,0.4);
line-height: 1.5;
}
/* 加载器 */
#loader {
position: fixed;
inset: 0;
background: #000;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 1s;
}
.loader-line {
width: 0%;
height: 1px;
background: #fff;
transition: width 0.5s;
}
</style>
<!-- 核心库 -->
<script src="https://cdn.bootcdn.net/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/EffectComposer.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/RenderPass.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/ShaderPass.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/postprocessing/UnrealBloomPass.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/shaders/CopyShader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/shaders/LuminosityHighPassShader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/simplex-noise@2.4.0/simplex-noise.min.js"></script>
<!-- AI 视觉库 -->
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
</head>
<body>
<div id="loader"><div class="loader-line" id="loader-bar"></div></div>
<div id="canvas-container"></div>
<video id="input-video" playsinline></video>
<div id="ui-layer">
<div class="top-bar">
<span id="fps-display">FPS: --</span>
<span id="theme-display">CALIBRATING REALITY...</span>
</div>
<div class="center-stage">
<div class="art-title" id="title">VOID</div>
<div class="art-sub" id="subtitle">INTERACTIVE FLUID DYNAMICS</div>
</div>
<div class="debug-info" id="hand-state">
HANDS: SEARCHING<br>
FORCE: 0.00
</div>
</div>
<script>
/**
* ============================================================================
* 1. 数学工具与配置 (Math & Config)
* ============================================================================
*/
const CONFIG = {
particleCount: 8000, // 粒子数量,适中以保证物理计算性能
particleSize: 1.8, // 基础大小
bloomStrength: 1.2, // 辉光强度
handForceRadius: 15.0, // 手掌影响半径
friction: 0.96, // 物理摩擦力 (越小停得越快)
returnSpeed: 0.008, // 回归原位的速度 (弹性)
noiseScale: 0.02, // 噪声纹理缩放
curlStrength: 0.5 // 旋度强度 (流体感)
};
const Simplex = new SimplexNoise();
// 颜色主题定义 (Palette)
const THEMES = {
DEFAULT: { name: 'VOID / 虚空', colors: [0x888888, 0xffffff, 0x444444] },
SPRING: { name: 'FLORA / 生机', colors: [0xff3366, 0xffdd00, 0x00ff88] },
SUMMER: { name: 'OCEAN / 碧海', colors: [0x00ffff, 0x0066ff, 0xffffff] },
AUTUMN: { name: 'EMBER / 余烬', colors: [0xff4400, 0xffaa00, 0x330000] },
WINTER: { name: 'FROST / 霜雪', colors: [0xaaccff, 0xffffff, 0x8899aa] },
LOVE: { name: 'PULSE / 悸动', colors: [0xff0044, 0xff88aa, 0x440011] },
SPIRIT: { name: 'SOUL / 灵光', colors: [0x00ffcc, 0xaa00ff, 0x0000ff] }
};
// 自动主题选择器
function getTheme() {
const m = new Date().getMonth() + 1;
const d = new Date().getDate();
if (m===2 && d===14) return THEMES.LOVE;
if (m===10 && d===31) return THEMES.SPIRIT;
if (m>=3 && m<=5) return THEMES.SPRING;
if (m>=6 && m<=8) return THEMES.SUMMER;
if (m>=9 && m<=11) return THEMES.AUTUMN;
return THEMES.WINTER;
}
const ACTIVE_THEME = getTheme();
/**
* ============================================================================
* 2. 物理核心 (Physics Core) - 重写为基于力的系统
* ============================================================================
*/
class PhysicsSystem {
constructor(scene) {
this.count = CONFIG.particleCount;
this.geometry = new THREE.BufferGeometry();
// 双重缓冲数据Current(当前), Target(回归目标), Velocity(速度)
this.positions = new Float32Array(this.count * 3);
this.origins = new Float32Array(this.count * 3); // 原始位置(用于回归)
this.velocities = new Float32Array(this.count * 3);
this.colors = new Float32Array(this.count * 3);
this.sizes = new Float32Array(this.count);
this.life = new Float32Array(this.count); // 粒子生命周期/闪烁偏移
this.initParticles();
// ShaderMaterial 提供高性能渲染和柔和的光点
this.material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
pixelRatio: { value: window.devicePixelRatio },
baseSize: { value: CONFIG.particleSize }
},
vertexShader: `
attribute float size;
attribute vec3 color;
attribute float life;
varying vec3 vColor;
varying float vAlpha;
uniform float time;
uniform float pixelRatio;
uniform float baseSize;
void main() {
vColor = color;
// 粒子呼吸效果
float breath = 0.6 + 0.4 * sin(time * 2.0 + life * 10.0);
vAlpha = 0.5 + 0.5 * breath;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = baseSize * size * breath * pixelRatio * (300.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
varying vec3 vColor;
varying float vAlpha;
void main() {
// 圆形绘制,边缘羽化
vec2 coord = gl_PointCoord - vec2(0.5);
float r = length(coord);
if (r > 0.5) discard;
// 核心亮,边缘暗
float glow = 1.0 - (r * 2.0);
glow = pow(glow, 1.5);
gl_FragColor = vec4(vColor, glow * vAlpha);
}
`,
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending
});
this.mesh = new THREE.Points(this.geometry, this.material);
scene.add(this.mesh);
}
initParticles() {
// 创建一个无序但均匀的云团
const c1 = new THREE.Color(ACTIVE_THEME.colors[0]);
const c2 = new THREE.Color(ACTIVE_THEME.colors[1]);
const c3 = new THREE.Color(ACTIVE_THEME.colors[2]);
for(let i=0; i<this.count; i++) {
const i3 = i * 3;
// 随机分布在一个宽阔的区域
const x = (Math.random() - 0.5) * 200;
const y = (Math.random() - 0.5) * 120;
const z = (Math.random() - 0.5) * 80;
this.positions[i3] = x; this.positions[i3+1] = y; this.positions[i3+2] = z;
this.origins[i3] = x; this.origins[i3+1] = y; this.origins[i3+2] = z;
// 颜色混合
const rand = Math.random();
let c;
if(rand < 0.33) c = c1;
else if(rand < 0.66) c = c2;
else c = c3;
this.colors[i3] = c.r; this.colors[i3+1] = c.g; this.colors[i3+2] = c.b;
this.sizes[i] = Math.random() * 1.5 + 0.5;
this.life[i] = Math.random();
}
this.geometry.setAttribute('position', new THREE.BufferAttribute(this.positions, 3));
this.geometry.setAttribute('color', new THREE.BufferAttribute(this.colors, 3));
this.geometry.setAttribute('size', new THREE.BufferAttribute(this.sizes, 1));
this.geometry.setAttribute('life', new THREE.BufferAttribute(this.life, 1));
}
// ★★★ 核心物理更新逻辑 ★★★
update(time, handData) {
this.material.uniforms.time.value = time;
// 获取手部数据 (如果没有手,数组为空)
const hands = handData.hands || [];
for(let i=0; i<this.count; i++) {
const i3 = i * 3;
const px = this.positions[i3];
const py = this.positions[i3+1];
const pz = this.positions[i3+2];
// 1. Curl Noise (旋度噪声) - 让粒子自然流动的关键
// 计算噪声场对速度的影响,模拟空气流动
const noiseScale = 0.015;
const n1 = Simplex.noise3D(px*noiseScale, py*noiseScale, time*0.2);
const n2 = Simplex.noise3D(px*noiseScale + 100, py*noiseScale, time*0.2);
const n3 = Simplex.noise3D(px*noiseScale + 200, py*noiseScale, time*0.2);
// 施加环境流动力
this.velocities[i3] += n1 * 0.02;
this.velocities[i3+1] += n2 * 0.02;
this.velocities[i3+2] += n3 * 0.02;
// 2. 弹性回归力 (Elasticity) - 让粒子即使被扰动也能慢慢飘回原位
// 除非被手“抓”住,否则它们有自己的家
const ox = this.origins[i3];
const oy = this.origins[i3+1];
const oz = this.origins[i3+2];
this.velocities[i3] += (ox - px) * CONFIG.returnSpeed;
this.velocities[i3+1] += (oy - py) * CONFIG.returnSpeed;
this.velocities[i3+2] += (oz - pz) * CONFIG.returnSpeed;
// 3. ★★★ 高级手势交互场 (Hand Interaction Field) ★★★
// 支持任意手势、任意数量的手
for (let h = 0; h < hands.length; h++) {
const hand = hands[h];
// 3.1 掌心力场 (Palm Force)
// 如果手张开:产生基于法线的推力 (Push)
// 如果手握拳:产生引力 (Gravity Well)
const dPx = px - hand.palm.x;
const dPy = py - hand.palm.y;
const dPz = pz - hand.palm.z;
const distSq = dPx*dPx + dPy*dPy + dPz*dPz;
// 交互半径
if (distSq < 1500) {
const dist = Math.sqrt(distSq);
const forceFactor = (1500 - distSq) / 1500; // 0 (边缘) -> 1 (中心)
if (hand.isFist) {
// 握拳:黑洞引力,且带有强烈的旋转
// 吸力
this.velocities[i3] -= dPx * 0.1 * forceFactor;
this.velocities[i3+1] -= dPy * 0.1 * forceFactor;
this.velocities[i3+2] -= dPz * 0.1 * forceFactor;
// 旋转力 (Cross Product with Up vector)
this.velocities[i3] += -dPy * 0.2 * forceFactor;
this.velocities[i3+1] += dPx * 0.2 * forceFactor;
} else {
// 张开:基于手掌法线方向的推力 (空气炮)
// 计算点乘,判断粒子是否在手掌前方
// 简单模拟:径向推开 + 手掌朝向的定向风
const pushStrength = 2.0 * hand.velocity; // 动得越快,风越大
this.velocities[i3] += hand.normal.x * pushStrength * forceFactor;
this.velocities[i3+1] += hand.normal.y * pushStrength * forceFactor;
this.velocities[i3+2] += hand.normal.z * pushStrength * forceFactor;
// 基础斥力 (防止穿模)
if (dist < 10) {
this.velocities[i3] += dPx * 0.5 * forceFactor;
this.velocities[i3+1] += dPy * 0.5 * forceFactor;
this.velocities[i3+2] += dPz * 0.5 * forceFactor;
}
}
}
// 3.2 指尖轨迹 (Finger Trails)
// 每一个指尖都是一个微小的扰动源,允许精细作画
for (let f = 0; f < 5; f++) {
const tip = hand.fingertips[f];
const dtx = px - tip.x;
const dty = py - tip.y;
const dtz = pz - tip.z;
const tipDistSq = dtx*dtx + dty*dty + dtz*dtz;
if (tipDistSq < 100) {
// 指尖产生湍流
this.velocities[i3] += (Math.random()-0.5) * 1.5;
this.velocities[i3+1] += (Math.random()-0.5) * 1.5;
this.velocities[i3+2] += (Math.random()-0.5) * 1.5;
}
}
}
// 4. 积分与阻尼
this.velocities[i3] *= CONFIG.friction;
this.velocities[i3+1] *= CONFIG.friction;
this.velocities[i3+2] *= CONFIG.friction;
this.positions[i3] += this.velocities[i3];
this.positions[i3+1] += this.velocities[i3+1];
this.positions[i3+2] += this.velocities[i3+2];
}
this.geometry.attributes.position.needsUpdate = true;
}
}
/**
* ============================================================================
* 3. 智能感知层 (Intelligent Perception)
* ============================================================================
*/
class HandInterface {
constructor() {
this.handData = { hands: [] };
// MediaPipe 初始化
this.hands = new Hands({locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`});
this.hands.setOptions({
maxNumHands: 2,
modelComplexity: 1,
minDetectionConfidence: 0.7,
minTrackingConfidence: 0.7
});
this.hands.onResults(this.processResults.bind(this));
// 摄像头启动
const video = document.getElementById('input-video');
const camera = new Camera(video, {
onFrame: async () => { await this.hands.send({image: video}); },
width: 640, height: 480
});
camera.start().then(() => {
// UI 更新
document.getElementById('loader-bar').style.width = '100%';
setTimeout(() => document.getElementById('loader').style.opacity = 0, 500);
setTimeout(() => {
document.getElementById('title').classList.add('visible');
document.getElementById('subtitle').classList.add('visible');
document.getElementById('loader').style.display = 'none';
}, 1000);
});
// 上一帧数据用于计算速度
this.prevHands = {};
}
processResults(results) {
const landmarks = results.multiHandLandmarks;
const worldLandmarks = results.multiHandWorldLandmarks; // 使用真实世界坐标计算法线
const currentHands = [];
const debugEl = document.getElementById('hand-state');
if (landmarks) {
landmarks.forEach((lm, index) => {
// 1. 坐标映射 (2D -> 3D Scene Space)
// 屏幕中心为 (0,0), 范围约 -60 到 60
const palmCenter = lm[9]; // 中指根部作为手掌中心
const pos = new THREE.Vector3(
(1.0 - palmCenter.x) * 140 - 70, // X 镜像翻转
-(palmCenter.y * 100 - 50), // Y 翻转
0 // Z 暂定为0后续可根据 lm.z 优化深度
);
// 2. 计算速度 (Velocity)
let velocity = 0;
if (this.prevHands[index]) {
const dist = pos.distanceTo(this.prevHands[index]);
velocity = Math.min(dist, 5.0); // 限制最大速度
}
// 3. 计算手势状态 (State Analysis)
// 3.1 握拳检测: 比较指尖(tip)到掌心(wrist/center)的距离
const wrist = lm[0];
let foldedFingers = 0;
const tips = [8, 12, 16, 20]; // 4 fingers (excluding thumb)
tips.forEach(t => {
// 简单判断: 如果指尖y坐标 低于 指根y坐标 (屏幕空间),或距离手腕太近
// 更稳健的方法是3D距离
const dTip = Math.hypot(lm[t].x - wrist.x, lm[t].y - wrist.y);
const dPip = Math.hypot(lm[t-2].x - wrist.x, lm[t-2].y - wrist.y);
if (dTip < dPip) foldedFingers++;
});
const isFist = foldedFingers >= 3;
// 3.2 手掌法线 (Normal Vector)
// 利用 P0(Wrist), P5(IndexBase), P17(PinkyBase) 计算平面法线
// 这里做一个简化估算:根据手掌移动方向和手腕-中指向量
const pWrist = new THREE.Vector3((1-lm[0].x), -lm[0].y, 0);
const pMiddle = new THREE.Vector3((1-lm[9].x), -lm[9].y, 0);
const dir = new THREE.Vector3().subVectors(pMiddle, pWrist).normalize();
// 默认法线朝向屏幕外 (0,0,1),结合方向旋转
const normal = new THREE.Vector3(0, 0, 1).add(dir.multiplyScalar(0.5)).normalize();
// 4. 收集指尖位置 (用于精细交互)
const fingertips = [4, 8, 12, 16, 20].map(i => {
return new THREE.Vector3(
(1.0 - lm[i].x) * 140 - 70,
-(lm[i].y * 100 - 50),
(lm[i].z || 0) * 50 // 尝试引入深度
);
});
currentHands.push({
id: index,
palm: pos,
velocity: velocity,
isFist: isFist,
normal: normal,
fingertips: fingertips
});
// 更新上一帧
this.prevHands[index] = pos.clone();
});
}
this.handData.hands = currentHands;
// 更新 UI Debug
if (currentHands.length > 0) {
const h = currentHands[0];
const stateStr = h.isFist ? "GRAVITY WELL (FIST)" : "WIND FORCE (OPEN)";
debugEl.innerHTML = `HANDS: DETECTED (${currentHands.length})<br>MODE: ${stateStr}<br>VEL: ${h.velocity.toFixed(2)}`;
} else {
debugEl.innerHTML = `HANDS: SEARCHING...<br>Please show your hands`;
}
}
}
/**
* ============================================================================
* 4. 渲染循环 (Main Loop)
* ============================================================================
*/
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000000, 0.01);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 60; // 摄像机拉远,看到更多粒子
const renderer = new THREE.WebGLRenderer({ powerPreference: "high-performance", antialias: false });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.getElementById('canvas-container').appendChild(renderer.domElement);
// Post Processing (Bloom)
const composer = new THREE.EffectComposer(renderer);
composer.addPass(new THREE.RenderPass(scene, camera));
const bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
bloomPass.strength = CONFIG.bloomStrength;
bloomPass.radius = 0.5;
bloomPass.threshold = 0.1;
composer.addPass(bloomPass);
// Init Systems
const physics = new PhysicsSystem(scene);
const perception = new HandInterface();
// Update UI Text
document.getElementById('theme-display').innerText = `THEME: ${ACTIVE_THEME.name}`;
document.getElementById('title').innerText = ACTIVE_THEME.name.split('/')[0];
// Animation Loop
const clock = new THREE.Clock();
let frames = 0;
let lastTime = 0;
function animate() {
requestAnimationFrame(animate);
const time = clock.getElapsedTime();
const delta = clock.getDelta();
// FPS Counter
frames++;
if (time - lastTime >= 1) {
document.getElementById('fps-display').innerText = `FPS: ${frames}`;
frames = 0;
lastTime = time;
}
// Update Physics
physics.update(time, perception.handData);
// Subtle Camera Move
camera.position.x = Math.sin(time * 0.1) * 2;
camera.position.y = Math.cos(time * 0.1) * 2;
camera.lookAt(0,0,0);
composer.render();
}
// Resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
<script defer src="https://events.vercount.one/js"></script>
</body>
</html>