2 Commits

Author SHA1 Message Date
hehh
d19837edc2 feat(sw): 升级缓存版本并优化资源缓存策略
- 更新缓存名称从 v1.0.1 到 v1.1.0
- 移除部分图片资源路径配置
- 安装阶段增加 skipWaiting 强制更新
- 优化 fetch 拦截逻辑,区分 HTML、音频、图片及静态资源处理
- 增加对带版本号请求的特殊处理
- 激活阶段主动声明客户端控制权
- 改进缓存匹配忽略查询参数提高命中率
- 增强错误捕获避免中断服务工作线程运行
2025-11-25 20:05:26 +08:00
hehh
5c9e2c4186 style(about): 调整玻璃态模糊值和光斑模糊效果
- 减小玻璃态背景的模糊半径从28px到18px
- 减少光斑元素的模糊值从80px到40px
- 优化标签云计算逻辑,使用缓存宽高避免重复查询
- 改进主题语言监听器,防止重复加载和竞态条件
- 修复标签定位计算,提高渲染性能
- 清理冗余代码和注释
2025-11-25 19:27:25 +08:00
37 changed files with 3611 additions and 4847 deletions

View File

@@ -139,7 +139,7 @@ Home/
## 🚀 快速开始 ## 🚀 快速开始
```shell ``shell
# 克隆项目 # 克隆项目
git clone https://github.com/listener-He/Home.git git clone https://github.com/listener-He/Home.git
@@ -181,7 +181,7 @@ open about.html
- [x] 添加骨架屏加载效果 - [x] 添加骨架屏加载效果
- [x] 实现数据缓存机制,提升页面加载速度 - [x] 实现数据缓存机制,提升页面加载速度
- [x] 添加淡入动画效果,提升用户体验 - [x] 添加淡入动画效果,提升用户体验
- [x] 实现 PWA 支持,支持离线访问
### 待完成 ### 待完成
- [ ] 添加更多数据源(如 Twitter、知乎等 - [ ] 添加更多数据源(如 Twitter、知乎等
@@ -193,7 +193,6 @@ open about.html
- [ ] 添加更多主题选项(如高对比度模式等) - [ ] 添加更多主题选项(如高对比度模式等)
- [ ] 实现深色模式下的图片优化处理 - [ ] 实现深色模式下的图片优化处理
- [ ] 添加无障碍访问支持ARIA 属性完善) - [ ] 添加无障碍访问支持ARIA 属性完善)
- [ ] 实现 PWA 支持,支持离线访问
## 🙏 鸣谢与致敬 ## 🙏 鸣谢与致敬

View File

@@ -1,16 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN" data-lang="zh"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>关于我 - Honesty</title> <title>关于我 - Honesty</title>
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- PWA相关配置 -->
<meta name="theme-color" content="#6c5ce7">
<link rel="manifest" href="./manifest.json">
<!--SEO信息 --> <!--SEO信息 -->
<meta name="description" content="关于Honesty,关于HeHouHui,关于HeHui,关于明厚, About Me Honesty, About Me HeHouHui, About Me HeHui"> <meta name="description" content="关于Honesty,关于HeHouHui,关于HeHui,关于明厚, About Me Honesty, About Me HeHouHui, About Me HeHui">
<meta name="keywords" content="Honesty,HeHouHui,HeHui,明厚"> <meta name="keywords" content="Honesty,HeHouHui,HeHui,明厚">
<link rel="canonical" href="https://www.hehouhui.cn/about.html">
<meta name="author" content="Honesty"> <meta name="author" content="Honesty">
<!-- 社交平台分享优化 --> <!-- 社交平台分享优化 -->
@@ -21,7 +22,6 @@
<meta property="og:description" content="我是一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。来自湖南现在上海工作享受在这座充满活力的城市中追求技术梦想。"> <meta property="og:description" content="我是一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。来自湖南现在上海工作享受在这座充满活力的城市中追求技术梦想。">
<meta property="og:image" content="https://www.hehouhui.cn/images/avatar.jpeg"> <meta property="og:image" content="https://www.hehouhui.cn/images/avatar.jpeg">
<meta property="og:site_name" content="Honesty的个人主页"> <meta property="og:site_name" content="Honesty的个人主页">
<meta property="og:locale" content="zh_CN">
<!-- Twitter --> <!-- Twitter -->
<meta property="twitter:card" content="summary_large_image"> <meta property="twitter:card" content="summary_large_image">
@@ -30,9 +30,6 @@
<meta property="twitter:description" content="我是一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。来自湖南现在上海工作享受在这座充满活力的城市中追求技术梦想。"> <meta property="twitter:description" content="我是一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。来自湖南现在上海工作享受在这座充满活力的城市中追求技术梦想。">
<meta property="twitter:image" content="https://www.hehouhui.cn/images/avatar.jpeg"> <meta property="twitter:image" content="https://www.hehouhui.cn/images/avatar.jpeg">
<meta property="twitter:site" content="@Honesty861024"> <meta property="twitter:site" content="@Honesty861024">
<link rel="alternate" hreflang="zh-cn" href="https://www.hehouhui.cn/about.html">
<link rel="alternate" hreflang="en" href="https://www.hehouhui.cn/about.html?lang=en">
<link rel="alternate" hreflang="x-default" href="https://www.hehouhui.cn/about.html">
<!-- 微信小程序/朋友圈分享 --> <!-- 微信小程序/朋友圈分享 -->
<meta property="wechat:image" content="https://www.hehouhui.cn/images/avatar.jpeg"> <meta property="wechat:image" content="https://www.hehouhui.cn/images/avatar.jpeg">
@@ -40,16 +37,16 @@
<meta property="wechat:description" content="我是一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。"> <meta property="wechat:description" content="我是一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。">
<!-- 核心资源:使用 BootCDN 加速 --> <!-- 核心资源:使用 BootCDN 加速 -->
<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/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="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="stylesheet">
<link rel="preload" href="css/about.css?version=20251125" as="style" onload="this.onload=null;this.rel='stylesheet'"> <link rel="stylesheet" href="css/style.css?version=20251125">
<link rel="stylesheet" href="css/about.css?version=20251125">
<!-- Artalk 评论样式 --> <!-- Artalk 评论样式 -->
<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="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.css">
<link rel="preload" href="css/artalk.css?version=20251125" as="style" onload="this.onload=null;this.rel='stylesheet'"> <link rel="stylesheet" href="css/artalk.css?version=20251125">
<link rel="icon" href="favicon.ico"> <link rel="icon" href="favicon.ico">
<link rel="apple-touch-icon" href="./images/logo.png"> <link rel="apple-touch-icon" href="./images/logo.png">
<!--IE淘汰计划--> <!--IE淘汰计划-->
<script> <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); if (/*@cc_on!@*/false || (!!window.MSInputMethodContext && !!document.documentMode)) window.location.href = "https://imsyy.top/upgrade-your-browser/index.html?referrer=" + encodeURIComponent(window.location.href);
@@ -74,7 +71,7 @@
</a> </a>
<div class="nav-menu"> <div class="nav-menu">
<a href="index.html" class="nav-item" aria-label="Home"> <a href="index.html" class="nav-item">
<i class="ri-home-smile-2-line"></i> <i class="ri-home-smile-2-line"></i>
<span class="nav-label" data-i18n="nav.home">首页</span> <span class="nav-label" data-i18n="nav.home">首页</span>
</a> </a>
@@ -82,7 +79,7 @@
<i class="ri-user-3-line"></i> <i class="ri-user-3-line"></i>
<span class="nav-label" data-i18n="nav.about">关于</span> <span class="nav-label" data-i18n="nav.about">关于</span>
</a> </a>
<a href="https://blog.hehouhui.cn" class="nav-item" aria-label="Honesty Blog"> <a href="https://blog.hehouhui.cn" class="nav-item">
<i class="ri-quill-pen-line"></i> <i class="ri-quill-pen-line"></i>
<span class="nav-label" data-i18n="nav.blog">博客</span> <span class="nav-label" data-i18n="nav.blog">博客</span>
</a> </a>
@@ -90,11 +87,11 @@
<div class="nav-divider"></div> <div class="nav-divider"></div>
<!-- 功能按钮 --> <!-- 功能按钮 -->
<button id="lang-btn" class="action-btn" aria-label="Switch Language" tabindex="0"> <button id="lang-btn" class="action-btn" aria-label="Switch Language">
<i class="ri-translate-2"></i> <i class="ri-translate-2"></i>
<span class="btn-text">CN/EN</span> <span class="btn-text">CN/EN</span>
</button> </button>
<button id="theme-btn" class="action-btn" aria-label="Toggle Theme" tabindex="0"> <button id="theme-btn" class="action-btn" aria-label="Toggle Theme">
<i class="ri-sun-line icon-sun"></i> <i class="ri-sun-line icon-sun"></i>
<i class="ri-moon-line icon-moon"></i> <i class="ri-moon-line icon-moon"></i>
</button> </button>
@@ -111,7 +108,7 @@
<div class="bento-card area-profile"> <div class="bento-card area-profile">
<div class="profile-content"> <div class="profile-content">
<div class="avatar-ring"> <div class="avatar-ring">
<img src="images/avatar.jpeg" alt="Honesty" class="avatar-img" loading="lazy" width="120" height="120"> <img src="images/avatar.jpeg" alt="Honesty" class="avatar-img">
<div class="status-dot" data-i18n="status.online">Online</div> <div class="status-dot" data-i18n="status.online">Online</div>
</div> </div>
<div class="profile-info"> <div class="profile-info">
@@ -123,21 +120,12 @@
</div> </div>
<!-- 6个社交链接 (PC端显示) --> <!-- 6个社交链接 (PC端显示) -->
<div class="social-dock desktop-social"> <div class="social-dock desktop-social">
<a href="https://github.com/listener-He" target="_blank" class="s-icon" aria-label="GitHub profile" tabindex="0"><i class="ri-github-fill"></i></a> <a href="https://github.com/listener-He" target="_blank" class="s-icon"><i class="ri-github-fill"></i></a>
<a href="mailto:hehouhui@foxmail.com" class="s-icon" aria-label="Email contact" tabindex="0"><i class="ri-mail-send-fill"></i></a> <a href="mailto:hehouhui@foxmail.com" class="s-icon"><i class="ri-mail-send-fill"></i></a>
<a href="https://twitter.com/Honesty861024" target="_blank" class="s-icon" aria-label="Twitter profile" tabindex="0"><i class="ri-twitter-line"></i></a> <a href="https://blog.hehouhui.cn" target="_blank" class="s-icon"><i class="ri-pages-line"></i></a>
<a href="https://www.zhihu.com/people/wen-xin-92-2-57" target="_blank" class="s-icon" aria-label="Zhihu profile" tabindex="0"><i class="ri-zhihu-line"></i></a> <a href="https://www.zhihu.com/people/wen-xin-92-2-57" target="_blank" class="s-icon"><i class="ri-zhihu-line"></i></a>
<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="javascript:void(0);" onclick="toggleWechat()" class="s-icon"><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> <a href="https://juejin.cn/user/3659591622878503" target="_blank" class="s-icon"><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> </div>
</div> </div>
@@ -169,11 +157,11 @@
<span class="stat-key" data-i18n="stats.followers">Followers</span> <span class="stat-key" data-i18n="stats.followers">Followers</span>
</div> </div>
<div class="stat-item"> <div class="stat-item">
<span class="stat-val neon-font" id="busuanzi_site_pv">0</span> <span class="stat-val neon-font" id="busuanzi_value_site_pv">0</span>
<span class="stat-key" data-i18n="stats.visitNum">Visit num</span> <span class="stat-key" data-i18n="stats.visitNum">Visit num</span>
</div> </div>
<div class="stat-item"> <div class="stat-item">
<span class="stat-val neon-font" id="busuanzi_site_uv">0</span> <span class="stat-val neon-font" id="busuanzi_value_site_uv">0</span>
<span class="stat-key" data-i18n="stats.visitors">Visitors</span> <span class="stat-key" data-i18n="stats.visitors">Visitors</span>
</div> </div>
</div> </div>
@@ -185,7 +173,7 @@
<span class="mbti-code gradient-text">INFJ</span> <span class="mbti-code gradient-text">INFJ</span>
<span class="mbti-name" data-i18n="mbti.name">Advocate</span> <span class="mbti-name" data-i18n="mbti.name">Advocate</span>
<span class="mbti-icon"> <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" width="32" height="32"/> <img src="images/INFJ.png" alt="INFJ" style="width:32px;height:32px;border-radius:50%;border:2px solid rgba(255,255,255,0.4)"/>
</span> </span>
</div> </div>
<p class="mbti-desc" data-i18n="mbti.desc">"理想主义与道德感,果断决绝的行动力。深度洞察与创意,关怀与同理心。"</p> <p class="mbti-desc" data-i18n="mbti.desc">"理想主义与道德感,果断决绝的行动力。深度洞察与创意,关怀与同理心。"</p>
@@ -250,12 +238,12 @@
<!-- 移动端显示的社交栏 (6个链接) --> <!-- 移动端显示的社交栏 (6个链接) -->
<div class="bento-card area-social-mobile mobile-social"> <div class="bento-card area-social-mobile mobile-social">
<a href="https://github.com/listener-He" class="ms-btn" aria-label="GitHub profile" tabindex="0"><i class="ri-github-fill"></i></a> <a href="https://github.com/listener-He" class="ms-btn"><i class="ri-github-fill"></i></a>
<a href="mailto:hehouhui@foxmail.com" class="ms-btn" aria-label="Email contact" tabindex="0"><i class="ri-mail-send-fill"></i></a> <a href="mailto:hehouhui@foxmail.com" class="ms-btn"><i class="ri-mail-send-fill"></i></a>
<a href="https://twitter.com/Honesty861024" class="ms-btn" aria-label="Twitter profile" tabindex="0"><i class="ri-twitter-line"></i></a> <a href="https://blog.hehouhui.cn" class="ms-btn"><i class="ri-pages-line"></i></a>
<a href="https://www.zhihu.com/people/wen-xin-92-2-57" class="ms-btn" aria-label="Zhihu profile" tabindex="0"><i class="ri-zhihu-line"></i></a> <a href="https://www.zhihu.com/people/wen-xin-92-2-57" class="ms-btn"><i class="ri-zhihu-line"></i></a>
<a href="javascript:void(0);" onclick="toggleWechat()" class="ms-btn" aria-label="WeChat QR code" tabindex="0"><i class="ri-wechat-fill"></i></a> <a href="javascript:void(0);" onclick="toggleWechat()" class="ms-btn"><i class="ri-wechat-fill"></i></a>
<a href="https://juejin.cn/user/3659591622878503" class="ms-btn" aria-label="Juejin profile" tabindex="0"><i class="ri-code-box-line"></i></a> <a href="https://juejin.cn/user/3659591622878503" class="ms-btn"><i class="ri-code-box-line"></i></a>
</div> </div>
</div> </div>
@@ -304,11 +292,11 @@
<!-- 移动端悬浮功能按钮 --> <!-- 移动端悬浮功能按钮 -->
<div class="mobile-fab"> <div class="mobile-fab">
<button id="fab-main" class="fab-main" aria-label="Actions" tabindex="0"><i class="ri-magic-line"></i><span class="fab-label">Tools</span></button> <button id="fab-main" class="fab-main" aria-label="Actions"><i class="ri-magic-line"></i><span class="fab-label">Tools</span></button>
<div class="fab-menu" id="fab-menu"> <div class="fab-menu" id="fab-menu">
<button id="fab-lang" class="fab-item" tabindex="0"><i class="ri-translate-2"></i><span class="fab-text">Lang</span></button> <button id="fab-lang" class="fab-item"><i class="ri-translate-2"></i><span class="fab-text">Lang</span></button>
<button id="fab-theme" class="fab-item" tabindex="0"><i class="ri-moon-line"></i><span class="fab-text">Theme</span></button> <button id="fab-theme" class="fab-item"><i class="ri-moon-line"></i><span class="fab-text">Theme</span></button>
<button id="fab-music" class="fab-item" tabindex="0"><i class="ri-music-2-line"></i><span class="fab-text">Play</span></button> <button id="fab-music" class="fab-item"><i class="ri-music-2-line"></i><span class="fab-text">Play</span></button>
</div> </div>
</div> </div>
<audio id="site-audio" class="site-audio" src="data/至少做一件离谱的事-Kiri T.mp3" autoplay loop preload="auto"></audio> <audio id="site-audio" class="site-audio" src="data/至少做一件离谱的事-Kiri T.mp3" autoplay loop preload="auto"></audio>
@@ -320,14 +308,15 @@
<button class="modal-close" onclick="toggleWechat()"><i class="ri-close-line"></i></button> <button class="modal-close" onclick="toggleWechat()"><i class="ri-close-line"></i></button>
<h3 data-i18n="modal.wechat">Official Account</h3> <h3 data-i18n="modal.wechat">Official Account</h3>
<div class="qr-box"> <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" width="200" height="200"> <img src="https://blog-file.hehouhui.cn/wechat/mp-honesy.jpg" alt="WeChat QR" onerror="this.style.display='none';this.nextElementSibling.style.display='block'">
<!-- <div class="qr-fallback">QR Load Failed</div>-->
</div> </div>
<p data-i18n="modal.desc">Scan to follow Tech Share</p> <p data-i18n="modal.desc">Scan to follow Tech Share</p>
</div> </div>
</div> </div>
<footer class="site-footer"> <footer class="site-footer">
<p>Copyright &copy; 2018 <span id="currentYear"></span> Honesty. All rights reserved. <a class="icp" href="https://beian.miit.gov.cn/" target="_blank">湘ICP备20014902号</a> Powered By <a href="https://pages.edgeone.ai/" target="_blank"> Tencent EdgeOne </a></p> <p>&copy; 2018 <span id="currentYear"></span> Honesty. All rights reserved. <a class="icp" href="https://beian.miit.gov.cn/" target="_blank">湘ICP备20014902号</a> Powered By <a href="https://pages.edgeone.ai/" target="_blank"> Tencent EdgeOne </a></p>
<script> <script>
document.getElementById("currentYear").textContent = ' - ' + new Date().getFullYear(); document.getElementById("currentYear").textContent = ' - ' + new Date().getFullYear();
</script> </script>
@@ -335,25 +324,27 @@
</div> </div>
<!-- 脚本BootCDN jQuery / Artalk --> <!-- 脚本BootCDN jQuery / Artalk -->
<script src="js/jquery.min.js" defer></script> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="js/config.js?version=20251125" defer></script> <script src="js/config.js?version=20251125"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.js" defer></script> <script src="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.js"></script>
<script src="js/about.js?version=20251125" defer></script> <!-- 引入多语言包(按需) -->
<script defer src="https://events.vercount.one/js"></script> <script src="https://cdn.jsdelivr.net/npm/artalk@latest/dist/i18n/zh-cn.js"></script>
<script src="https://cdn.jsdelivr.net/npm/artalk@latest/dist/i18n/en.js"></script>
<script src="js/about.js?version=20251125"></script>
<!-- 不蒜子统计 --> <!-- 不蒜子统计 -->
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// 动态加载不蒜子统计脚本 // 动态加载不蒜子统计脚本
const script = document.createElement('script'); const script = document.createElement('script');
script.src = "//cdn.busuanzi.cc/busuanzi/3.6.9/busuanzi.abbr.min.js"; script.src = SiteConfig.analytics.busuanzi.src;
script.async = true; script.async = true;
document.head.appendChild(script); document.head.appendChild(script);
}); });
</script> </script>
<script> <script>
try { 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:"3OBGjwDdEIRS7XZ1",ck:"3OBGjwDdEIRS7XZ1"}); !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});
} catch (e) { } catch (e) {
console.log("51.la统计错误", e); console.log("51.la统计错误", e);
} }
@@ -365,7 +356,7 @@
(function () { (function () {
try { try {
var hm = document.createElement("script"); var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?ae2a009a75b13c21d5121ee51375ea4e"; hm.src = SiteConfig.analytics.baidu.src;
var s = document.getElementsByTagName("script")[0]; var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s); s.parentNode.insertBefore(hm, s);
} catch (e) { } catch (e) {
@@ -377,24 +368,53 @@
<!-- 自动格式化脚本 --> <!-- 自动格式化脚本 -->
<script> <script>
// 监听不蒜子数据的错误兜底 // 核心格式化函数:支持 K / W / M保留最多两位小数去除尾随零
function formatWithUnit(num) {
num = Number(num);
if (isNaN(num) || num < 0) return '0';
// 小于 1000直接显示
if (num < 1_000) {
return Math.floor(num).toString();
}
// 1K ~ 9.99K
if (num < 10_000) {
let val = (num / 1_000).toFixed(2);
return parseFloat(val) + 'K';
}
// 1W ~ 99.99W 1W = 10,000
if (num < 1_000_000) {
let val = (num / 10_000).toFixed(2);
return parseFloat(val) + 'W';
}
// ≥ 1M
let val = (num / 1_000_000).toFixed(2);
return parseFloat(val) + 'M';
}
// 监听不蒜子数据更新并格式化
function initFormatter() { function initFormatter() {
const pvEl = document.getElementById("busuanzi_site_pv"); const pvEl = document.getElementById(SiteConfig.analytics.busuanzi.site_pv_id);
const uvEl = document.getElementById("busuanzi_site_uv"); const uvEl = document.getElementById(SiteConfig.analytics.busuanzi.site_uv_id);
if (!pvEl && !uvEl) return; if (!pvEl && !uvEl) return;
console.log('[Busuanzi]', 'Formatting... Listener observer');
const observer = new MutationObserver(() => { const observer = new MutationObserver(() => {
if (pvEl?.textContent) { if (pvEl?.textContent) {
if (pvEl.textContent.includes('禁用')) { const raw = pvEl.textContent.trim().replace(/[,]/g, '');
pvEl.textContent = '-'; const num = parseFloat(raw);
return; if (!isNaN(num)) {
pvEl.textContent = formatWithUnit(num);
} }
} }
if (uvEl?.textContent) { if (uvEl?.textContent) {
if (uvEl.textContent.includes('禁用')) { const raw = uvEl.textContent.trim().replace(/[,]/g, '');
uvEl.textContent = '-'; const num = parseFloat(raw);
return; if (!isNaN(num)) {
uvEl.textContent = formatWithUnit(num);
} }
} }
}); });
@@ -410,12 +430,37 @@
initFormatter(); initFormatter();
} }
</script> </script>
<noscript>
<link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet"> <!-- PWA注册 -->
<link href="https://cdn.bootcdn.net/ajax/libs/remixicon/3.5.0/remixicon.min.css" rel="stylesheet"> <script>
<link rel="stylesheet" href="css/about.css?version=20251125"> if ('serviceWorker' in navigator) {
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/artalk/2.9.1/Artalk.css"> window.addEventListener('load', function() {
<link rel="stylesheet" href="css/artalk.css?version=20251125"> setTimeout(() => {
</noscript> navigator.serviceWorker.register('./js/sw.js')
.then(function(registration) {
console.log('SW registered: ', registration);
})
.catch(function(registrationError) {
console.log('SW registration failed: ', registrationError);
});
}, 3000); // 页面稳定后再注册
});
}
</script>
<!-- Apple PWA支持 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Honesty">
<link rel="apple-touch-icon" href="./images/avatar.jpeg">
<!-- Windows PWA支持 -->
<meta name="msapplication-TileImage" content="./images/avatar.jpeg">
<meta name="msapplication-TileColor" content="#6c5ce7">
<meta name="msapplication-tap-highlight" content="no">
<!-- 其他PWA相关meta标签 -->
<meta name="mobile-web-app-capable" content="yes">
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,17 @@
/* Base Artalk container styles */ /* Base Artalk container styles */
#artalk-container { #artalk-container {
border-radius: 12px; border-radius: 12px;
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 { #artalk-container .atk-comment-wrap {
background: rgba(128, 128, 128, 0.03); background: rgba(128, 128, 128, 0.03);
@@ -37,6 +44,7 @@
/* Light theme styles */ /* Light theme styles */
.atk-main-editor { .atk-main-editor {
background: rgba(255, 255, 255, 0.85) !important;
backdrop-filter: blur(28px) saturate(180%) !important; backdrop-filter: blur(28px) saturate(180%) !important;
-webkit-backdrop-filter: blur(28px) saturate(180%) !important; -webkit-backdrop-filter: blur(28px) saturate(180%) !important;
border: 1px solid rgba(108, 92, 231, 0.2) !important; border: 1px solid rgba(108, 92, 231, 0.2) !important;
@@ -200,7 +208,118 @@
color: #6c5ce7 !important; color: #6c5ce7 !important;
} }
/* Night theme styles */
[data-theme="night"] .atk-main-editor {
background: rgba(30, 30, 35, 0.75) !important;
backdrop-filter: blur(28px) saturate(180%) !important;
-webkit-backdrop-filter: blur(28px) saturate(180%) !important;
border: 1px solid rgba(255, 255, 255, 0.12) !important;
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.8) !important;
}
[data-theme="night"] .atk-send-btn {
background: linear-gradient(135deg, #00cec9, #00b3ae) !important;
border: none !important;
border-radius: 20px !important;
padding: 8px 20px !important;
font-weight: 500 !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 15px rgba(0, 206, 201, 0.4);
}
[data-theme="night"] .atk-send-btn:hover {
background: linear-gradient(135deg, #00b3ae, #009690) !important;
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(0, 206, 201, 0.6) !important;
}
[data-theme="night"] .atk-send-btn:active {
transform: translateY(0) !important;
}
[data-theme="night"] .atk-editor,
[data-theme="night"] .atk-editor textarea,
[data-theme="night"] .atk-editor input {
background: rgba(30, 30, 35, 0.75);
color: #dfe6e9;
}
[data-theme="night"] .atk-comment-wrap {
background: rgba(40, 40, 45, 0.85); /* 提高背景不透明度以增强可读性 */
border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
transition: all 0.3s ease;
}
[data-theme="night"] .atk-comment-wrap:hover {
background: rgba(45, 45, 50, 0.95); /* 提高悬停时的背景不透明度 */
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.7);
}
[data-theme="night"] .atk-dialog,
[data-theme="night"] .atk-layer .atk-dialog {
background: rgba(30, 30, 35, 0.85);
color: #dfe6e9;
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.8);
}
[data-theme="night"] .atk-header-item {
color: #dfe6e9 !important;
}
[data-theme="night"] .atk-editor-textarea {
color: #dfe6e9 !important;
background: rgba(40, 40, 45, 0.5) !important;
border: 1px solid rgba(255, 255, 255, 0.08) !important;
}
[data-theme="night"] .atk-editor-bottom .atk-item {
color: #b2bec3 !important;
}
[data-theme="night"] .atk-list-header .atk-comment-count {
color: #dfe6e9 !important;
}
[data-theme="night"] .atk-sort-select {
background: rgba(255, 255, 255, 0.08) !important;
border: 1px solid rgba(255, 255, 255, 0.15) !important;
color: #b2bec3 !important;
}
[data-theme="night"] .atk-comment .atk-nick {
color: #ffffff !important;
font-weight: 600 !important;
}
[data-theme="night"] .atk-comment .atk-date {
color: #a0aec0 !important;
}
[data-theme="night"] .atk-comment .atk-content {
color: #e2e8f0 !important;
}
[data-theme="night"] .atk-comment .atk-content a {
color: #00cec9 !important;
text-decoration: underline !important;
}
[data-theme="night"] .atk-comment .atk-content pre {
background: rgba(20, 20, 25, 0.7) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
color: #e2e8f0 !important;
}
[data-theme="night"] .atk-actions .atk-action {
color: #b2bec3 !important;
}
[data-theme="night"] .atk-actions .atk-action:hover {
color: #00cec9 !important;
}
/* Mobile specific styles */ /* Mobile specific styles */
.atk-mobile .atk-main-editor { .atk-mobile .atk-main-editor {
@@ -337,7 +456,30 @@
box-shadow: 0 6px 15px rgba(108, 92, 231, 0.25) !important; box-shadow: 0 6px 15px rgba(108, 92, 231, 0.25) !important;
} }
[data-theme="night"] .atk-pagination .atk-page-item {
background: rgba(255, 255, 255, 0.08) !important;
color: #b2bec3 !important;
border: 1px solid rgba(255, 255, 255, 0.12) !important;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4) !important;
backdrop-filter: blur(28px) saturate(180%) !important;
-webkit-backdrop-filter: blur(28px) saturate(180%) !important;
transition: all 0.3s ease;
}
[data-theme="night"] .atk-pagination .atk-page-item:hover {
background: linear-gradient(135deg, #00cec9, #00b3ae) !important;
color: white !important;
border: 1px solid rgba(0, 206, 201, 0.3) !important;
box-shadow: 0 8px 25px rgba(0, 206, 201, 0.3) !important;
transform: translateY(-2px);
}
[data-theme="night"] .atk-pagination .atk-page-item.atk-active {
background: linear-gradient(135deg, #00cec9, #00b3ae) !important;
color: white !important;
border: 1px solid rgba(0, 206, 201, 0.4) !important;
box-shadow: 0 8px 25px rgba(0, 206, 201, 0.4) !important;
}
/* Loading spinner */ /* Loading spinner */
.atk-loading { .atk-loading {
@@ -374,11 +516,6 @@
margin: 2px !important; margin: 2px !important;
} }
[data-theme="night"] .atk-editor-toolbar .atk-btn {
color: #b2bec3 !important;
filter: brightness(1.3);
}
/* 暗色模式下聚焦边框 */ /* 暗色模式下聚焦边框 */
.atk-dark .atk-editor-textarea:focus { .atk-dark .atk-editor-textarea:focus {
box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.5) !important; box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.5) !important;
@@ -442,12 +579,6 @@
height: 28px !important; height: 28px !important;
object-fit: cover; /* 修复移动端头像拉伸问题 */ object-fit: cover; /* 修复移动端头像拉伸问题 */
} }
.atk-comment>.atk-avatar img {
object-fit: cover;
object-position: center;
width: 28px !important;
height: 28px !important;
}
.atk-meta { .atk-meta {
font-size: 12px !important; font-size: 12px !important;
@@ -477,290 +608,4 @@
[data-theme="night"] .atk-comment-wrap:hover { [data-theme="night"] .atk-comment-wrap:hover {
background: rgba(45, 45, 50, 0.95) !important; background: rgba(45, 45, 50, 0.95) !important;
} }
/* Night theme styles */
[data-theme="night"] .atk-main-editor {
background: rgba(30, 30, 35, 0.75) !important;
backdrop-filter: blur(28px) saturate(180%) !important;
-webkit-backdrop-filter: blur(28px) saturate(180%) !important;
border: 1px solid rgba(255, 255, 255, 0.12) !important;
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.8) !important;
}
[data-theme="night"] .atk-send-btn {
background: linear-gradient(135deg, #00cec9, #00b3ae) !important;
border: none !important;
border-radius: 20px !important;
padding: 8px 20px !important;
font-weight: 500 !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 15px rgba(0, 206, 201, 0.4);
}
[data-theme="night"] .atk-send-btn:hover {
background: linear-gradient(135deg, #00b3ae, #009690) !important;
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(0, 206, 201, 0.6) !important;
}
[data-theme="night"] .atk-send-btn:active {
transform: translateY(0) !important;
}
[data-theme="night"] .atk-editor,
[data-theme="night"] .atk-editor textarea,
[data-theme="night"] .atk-editor input {
background: rgba(30, 30, 35, 0.75)!important;
color: #dfe6e9 !important;
}
[data-theme="night"] .atk-comment-wrap {
background: rgba(40, 40, 45, 0.85) !important; /* 提高背景不透明度以增强可读性 */
border: 1px solid rgba(255, 255, 255, 0.12) !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5) !important;
transition: all 0.3s ease !important;
}
[data-theme="night"] .atk-comment-wrap:hover {
background: rgba(45, 45, 50, 0.95) !important; /* 提高悬停时的背景不透明度 */
border: 1px solid rgba(255, 255, 255, 0.18) !important;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.7) !important;
}
[data-theme="night"] .atk-dialog,
[data-theme="night"] .atk-layer .atk-dialog {
background: rgba(30, 30, 35, 0.85) !important;
color: #dfe6e9 !important;
border: 1px solid rgba(255, 255, 255, 0.15) !important;
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.8) !important;
}
[data-theme="night"] .atk-header-item {
color: #dfe6e9 !important;
}
[data-theme="night"] .atk-editor-textarea {
color: #1f2937 !important;
background: rgba(40, 40, 45, 0.5) !important;
border: 1px solid rgba(255, 255, 255, 0.08) !important;
}
[data-theme="night"] .atk-editor-bottom .atk-item {
color: #b2bec3 !important;
}
[data-theme="night"] .atk-list-header .atk-comment-count {
color: #dfe6e9 !important;
}
[data-theme="night"] .atk-sort-select {
background: rgba(255, 255, 255, 0.08) !important;
border: 1px solid rgba(255, 255, 255, 0.15) !important;
color: #b2bec3 !important;
}
[data-theme="night"] .atk-comment .atk-nick {
color: #ffffff !important;
font-weight: 600 !important;
}
[data-theme="night"] .atk-comment .atk-date {
color: #a0aec0 !important;
}
[data-theme="night"] .atk-comment .atk-content {
color: #e2e8f0 !important;
}
[data-theme="night"] .atk-comment .atk-content a {
color: #00cec9 !important;
text-decoration: underline !important;
}
[data-theme="night"] .atk-comment .atk-content pre {
background: rgba(20, 20, 25, 0.7) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
color: #e2e8f0 !important;
}
[data-theme="night"] .atk-actions .atk-action {
color: #b2bec3 !important;
}
[data-theme="night"] .atk-actions .atk-action:hover {
color: #00cec9 !important;
}
[data-theme="night"] .atk-pagination .atk-page-item {
background: rgba(255, 255, 255, 0.08) !important;
color: #b2bec3 !important;
border: 1px solid rgba(255, 255, 255, 0.12) !important;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4) !important;
backdrop-filter: blur(28px) saturate(180%) !important;
-webkit-backdrop-filter: blur(28px) saturate(180%) !important;
transition: all 0.3s ease;
}
[data-theme="night"] .atk-pagination .atk-page-item:hover {
background: linear-gradient(135deg, #00cec9, #00b3ae) !important;
color: white !important;
border: 1px solid rgba(0, 206, 201, 0.3) !important;
box-shadow: 0 8px 25px rgba(0, 206, 201, 0.3) !important;
transform: translateY(-2px);
}
[data-theme="night"] .atk-pagination .atk-page-item.atk-active {
background: linear-gradient(135deg, #00cec9, #00b3ae) !important;
color: white !important;
border: 1px solid rgba(0, 206, 201, 0.4) !important;
box-shadow: 0 8px 25px rgba(0, 206, 201, 0.4) !important;
}
}
/* PC端黑夜模式下的输入框颜色和列表样式修正 - 全面优化版 */
[data-theme="night"] .atk-editor-textarea {
background: rgba(30, 30, 35, 0.95) !important; /* 提高背景不透明度增强可读性 */
color: #ffffff !important; /* 使用更亮的文字颜色 */
border: 1px solid rgba(255, 255, 255, 0.2) !important; /* 增强边框可见性 */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
}
[data-theme="night"] .atk-editor-textarea:focus {
border: 1px solid #00cec9 !important;
box-shadow: 0 0 0 3px rgba(0, 206, 201, 0.4) !important;
}
[data-theme="night"] .atk-comment-wrap {
background: rgba(30, 30, 35, 0.95) !important; /* 提高背景不透明度增强可读性 */
border: 1px solid rgba(255, 255, 255, 0.15) !important;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important;
}
[data-theme="night"] .atk-comment-wrap:hover {
background: rgba(40, 40, 45, 0.98) !important; /* 进一步提高背景不透明度 */
border: 1px solid rgba(0, 206, 201, 0.5) !important; /* 增强边框可见性 */
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4) !important;
}
[data-theme="night"] .atk-list-header {
border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
}
[data-theme="night"] .atk-header {
border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
}
/* 修复黑夜模式下插件面板的背景色问题 */
[data-theme="night"] .atk-editor-plug-wrap,
[data-theme="night"] .atk-plug-panel-wrap,
[data-theme="night"] .atk-editor-plug-emoticons,
[data-theme="night"] .atk-editor-plug-preview {
background: rgba(30, 30, 35, 0.95) !important;
color: #ffffff !important;
}
[data-theme="night"] .atk-plug-panel-wrap {
border: 1px solid rgba(255, 255, 255, 0.15) !important;
border-radius: 12px !important;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important;
}
[data-theme="night"] .atk-editor-plug-emoticons {
border-top: 1px solid rgba(255, 255, 255, 0.08) !important;
}
/* 优化评论底部工具栏在黑夜模式下的样式 */
[data-theme="night"] .atk-editor-bottom {
background: rgba(40, 40, 45, 0.7) !important;
border-top: 1px solid rgba(255, 255, 255, 0.08) !important;
padding: 12px 20px !important;
border-radius: 0 0 12px 12px !important;
}
[data-theme="night"] .atk-editor-bottom .atk-item {
color: #b2bec3 !important;
margin-right: 12px !important;
}
[data-theme="night"] .atk-send-btn {
background: linear-gradient(135deg, #00cec9, #00b3ae) !important;
border: none !important;
border-radius: 20px !important;
padding: 8px 20px !important;
font-weight: 500 !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 15px rgba(0, 206, 201, 0.4);
}
[data-theme="night"] .atk-send-btn:hover {
background: linear-gradient(135deg, #00b3ae, #009690) !important;
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(0, 206, 201, 0.6) !important;
}
[data-theme="night"] .atk-send-btn:active {
transform: translateY(0) !important;
}
/* 优化评论列表项在黑夜模式下的样式 */
[data-theme="night"] .atk-comment {
color: #e2e8f0 !important;
}
[data-theme="night"] .atk-comment .atk-nick {
color: #ffffff !important;
font-weight: 600 !important;
}
[data-theme="night"] .atk-comment .atk-date {
color: #a0aec0 !important;
}
[data-theme="night"] .atk-comment .atk-content {
color: #ffffff !important; /* 使用更亮的文字颜色提高可读性 */
line-height: 1.6 !important;
}
[data-theme="night"] .atk-comment .atk-content a {
color: #00cec9 !important;
text-decoration: underline !important;
}
[data-theme="night"] .atk-actions .atk-action {
color: #b2bec3 !important;
transition: all 0.2s ease !important;
margin-right: 15px; /* 增加回复评论组件的边距 */
}
[data-theme="night"] .atk-actions .atk-action:hover {
color: #00cec9 !important;
}
/* 修复黑夜模式下评论计数和下拉菜单的可见性 */
[data-theme="night"] .atk-comment-count {
color: #ffffff !important;
font-weight: 600 !important;
text-shadow: 0 0 8px rgba(255, 255, 255, 0.3) !important;
}
[data-theme="night"] .atk-dropdown-wrap {
background: rgba(30, 30, 35, 0.95) !important;
}
[data-theme="night"] .atk-dropdown-item {
color: #ffffff !important;
}
[data-theme="night"] .atk-dropdown-item:hover {
background: rgba(0, 206, 201, 0.2) !important;
color: #00cec9 !important;
}
[data-theme="night"] .atk-dropdown-item.active {
background: rgba(0, 206, 201, 0.3) !important;
color: #00cec9 !important;
}
[data-theme="night"] .atk-arrow-down-icon {
filter: brightness(2) !important;
} }

View File

@@ -262,7 +262,7 @@ pre code { padding: 0; background: none; border: none; word-wrap: normal; }
font-size: 1.3em; font-size: 1.3em;
font-weight: normal; font-weight: normal;
letter-spacing: 2px; letter-spacing: 2px;
color: #ffffff; color: #f0f0f0;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
margin-bottom: 20px; margin-bottom: 20px;
} }
@@ -275,7 +275,7 @@ pre code { padding: 0; background: none; border: none; word-wrap: normal; }
line-height: 2; line-height: 2;
letter-spacing: 2px; letter-spacing: 2px;
color: #ffffff; color: #ffffff;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) !important; text-shadow: 0 1px 2px rgba(141, 114, 114, 0.2) !important;
position: relative; position: relative;
z-index: 1; z-index: 1;
font-feature-settings: 'kern' 1; font-feature-settings: 'kern' 1;
@@ -872,7 +872,7 @@ nav {
.weixin-qrcode-desc { .weixin-qrcode-desc {
font-size: 14px; font-size: 14px;
color: #555; color: #666;
margin-bottom: 15px; margin-bottom: 15px;
} }
@@ -986,12 +986,12 @@ nav {
display: inline-block; display: inline-block;
margin: 0 5px; margin: 0 5px;
font-size: 0.5em; font-size: 0.5em;
color: rgba(255, 255, 255, 0.85); color: rgba(255, 255, 255, 0.7);
line-height: 1.2; line-height: 1.2;
} }
.power a { .power a {
color: rgba(255, 255, 255, 0.95); color: rgba(255, 255, 255, 0.8);
font-size: 0.5em; font-size: 0.5em;
text-decoration: none; text-decoration: none;
transition: color 0.3s ease; transition: color 0.3s ease;
@@ -1006,7 +1006,8 @@ nav {
.remark .power:not(:last-child):after { .remark .power:not(:last-child):after {
content: "|"; content: "|";
margin-left: 8px; margin-left: 8px;
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.5);
color: rgba(255, 255, 255, 0.5);
} }

Binary file not shown.

View File

@@ -1,72 +1,32 @@
[ [
{ {
"name": "spring-ai-example", "id": 1011713435,
"stargazers_count": 13,
"forks_count": 0,
"description": "本项目是一个基于 Spring AI 框架构建的检索增强生成RAG应用旨在通过向量检索技术增强大模型的生成能力使 AI 回答既智能又有依据。 项目采用现代化 Java 技术栈,核心框架为 Spring Boot 3.3 与 Spring AI 1.0,集成 OpenAI 模型deepseek-r1、doubao-embedding主打白嫖支持 PostgreSQL+pgvector、Elasticsearch 等多种向量存储方案,并利用 Redis 实现缓存加速、Elasticsearch 管理对话记忆。",
"html_url": "https://github.com/listener-He/spring-ai-example"
},
{
"name": "yunxiao-LLM-reviewer", "name": "yunxiao-LLM-reviewer",
"stargazers_count": 10, "html_url": "https://github.com/listener-He/yunxiao-LLM-reviewer",
"description": "一款专为阿里云云效 Flow 平台设计的自动化代码审查工具。通过集成 Qwen、DeepSeek 等先进大模型,该工具能够实时分析 Git 合并请求MR中的代码变更智能识别潜在问题并自动生成结构化评审意见。",
"stargazers_count": 9,
"forks_count": 3, "forks_count": 3,
"description": "一款专为阿里云云效 Flow 平台设计的自动化代码审查工具。通过集成 Qwen、DeepSeek 等先进大模型,该工具能够实时分析 Git 合并请求MR中的代码变更智能识别潜在问题并自动生成结构化评审意见。 工具支持多维度代码检测,包括逻辑错误、安全漏洞,有效提升团队代码质量与开发效率。 作为云效 Flow 的自定义步骤,该工具可无缝集成到 CI/CD 流水线中,实现 MR 提交时的自动化代码审查,大幅减少人工评审工作量,加速代码交付流程。", "language": "TypeScript",
"html_url": "https://github.com/listener-He/yunxiao-LLM-reviewer" "updated_at": "2025-10-10T11:06:34Z"
}, },
{ {
"name": "Home", "id": 1064414600,
"stargazers_count": 3, "name": "hexo-theme-stellar",
"forks_count": 0, "html_url": "https://github.com/listener-He/hexo-theme-stellar",
"description": "现代化个人主页,融合科技感与个性化元素,展示个人技术栈、开源项目和博客文章。", "description": "综合型hexo主题博客+知识库+专栏+笔记,内置海量的标签组件和动态数据组件。",
"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,
"forks_count": 2,
"description": "collection-complete 是一个用于处理集合数据并补充相关信息的Java库。它提供了链式调用的功能可以方便地对集合中的元素进行批量操作和属性补充。全程函数式 均衡了易用性与扩展性的设计",
"html_url": "https://github.com/listener-He/collection-complete"
},
{
"name": "keycloak-services-social-weixin",
"stargazers_count": 2,
"forks_count": 3,
"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,
"forks_count": 0,
"description": "基于NotionNext作为后端的微信小程序",
"html_url": "https://github.com/listener-He/Notion-Wechat-Blog"
},
{
"name": "social-auth",
"stargazers_count": 1,
"forks_count": 0,
"description": "",
"html_url": "https://github.com/listener-He/social-auth"
},
{
"name": "hp-lite",
"stargazers_count": 0, "stargazers_count": 0,
"forks_count": 1, "forks_count": 0,
"description": "内网穿透 轻量版 支持 https http tcp udp 支持云端动态控制穿透配置支持免费SSL证书和续签、支持限流和IP黑白名单多账户、统计、http、socks代理、反向代理、多设备管理等功能。", "language": null,
"html_url": "https://github.com/listener-He/hp-lite" "updated_at": "2025-09-26T02:12:18Z"
},
{
"id": 1060085476,
"name": "Universal-IoT-Java",
"html_url": "https://github.com/listener-He/Universal-IoT-Java",
"description": "通用 IoT Java 平台(示例)",
"stargazers_count": 0,
"forks_count": 0,
"language": null,
"updated_at": "2025-10-13T02:30:28Z"
} }
] ]

View File

@@ -10,7 +10,7 @@
"blog": "https://www.hehouhui.cn", "blog": "https://www.hehouhui.cn",
"hireable": true, "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.", "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": 167, "public_repos": 165,
"public_gists": 0, "public_gists": 0,
"followers": 6, "followers": 6,
"following": 12, "following": 12,

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 KiB

BIN
images/bj/10.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

BIN
images/bj/9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -13,6 +13,9 @@
<meta name="keywords" content="Honesty,HeHouHui,HeHui,明厚"> <meta name="keywords" content="Honesty,HeHouHui,HeHui,明厚">
<meta name="author" content="Honesty"> <meta name="author" content="Honesty">
<!-- PWA相关配置 -->
<meta name="theme-color" content="#6c5ce7">
<link rel="manifest" href="./manifest.json">
<!-- 社交平台分享优化 --> <!-- 社交平台分享优化 -->
<!-- Open Graph / Facebook --> <!-- Open Graph / Facebook -->
@@ -64,7 +67,7 @@
</script> </script>
</head> </head>
<body> <body oncontextmenu=self.event.returnValue=false onselectstart="return false">
<header id="panel" class="panel-cover"> <header id="panel" class="panel-cover">
<script> <script>
WIDGET = { WIDGET = {
@@ -104,7 +107,7 @@
<div class="info iUp"> <div class="info iUp">
<div class="info-back"> <div class="info-back">
<img alt="img" src="images/kl.gif" <img alt="img" src="images/kl.gif"
class="js-avatar profilepic" loading="lazy"> class="js-avatar profilepic">
</div> </div>
</div> </div>
</a> </a>
@@ -263,9 +266,9 @@
<div class="weixin-qrcode-container"> <div class="weixin-qrcode-container">
<div class="weixin-qrcode-title">扫描二维码</div> <div class="weixin-qrcode-title">扫描二维码</div>
<div class="weixin-qrcode-desc">请使用微信扫一扫添加关注</div> <div class="weixin-qrcode-desc">请使用微信扫一扫添加关注</div>
<img src="./images/mp-honesy.jpg" alt="微信二维码" <img src="https://blog-file.hehouhui.cn/wechat/mp-honesy.jpg" alt="微信二维码"
class="weixin-qrcode-image" class="weixin-qrcode-image"
onerror="this.src='https://cdn.jsdmirror.com/gh/listener-He/Home/images/logo.png'; this.alt='二维码加载失败'" loading="lazy"> onerror="this.src='https://cdn.jsdmirror.com/gh/listener-He/Home/images/logo.png'; this.alt='二维码加载失败'">
<button class="weixin-qrcode-close" onclick="closeWeixin()">关闭</button> <button class="weixin-qrcode-close" onclick="closeWeixin()">关闭</button>
</div> </div>
</div> </div>
@@ -277,7 +280,7 @@
// 防止滚动穿透 // 防止滚动穿透
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
// 添加ESC键关闭 // 添加ESC键关闭
document.addEventListener('keydown', closeOnEsc, { passive: false }); document.addEventListener('keydown', closeOnEsc);
} }
function closeWeixin() { function closeWeixin() {
@@ -286,7 +289,7 @@
// 恢复滚动 // 恢复滚动
document.body.style.overflow = ''; document.body.style.overflow = '';
// 移除ESC键监听 // 移除ESC键监听
document.removeEventListener('keydown', closeOnEsc, { passive: false }); document.removeEventListener('keydown', closeOnEsc);
} }
function closeOnEsc(e) { function closeOnEsc(e) {
@@ -300,12 +303,12 @@
if (e.target === this) { if (e.target === this) {
closeWeixin(); closeWeixin();
} }
}, { passive: false }); });
// 阻止点击弹框内容时关闭 // 阻止点击弹框内容时关闭
document.querySelector('.weixin-qrcode-container').addEventListener('click', function (e) { document.querySelector('.weixin-qrcode-container').addEventListener('click', function (e) {
e.stopPropagation(); e.stopPropagation();
}, { passive: false }); });
</script> </script>
</div> </div>
<div class="remark iUp"> <div class="remark iUp">
@@ -335,7 +338,10 @@
<script type="text/javascript" src="js/fetch.min.js"></script> <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/config.js"></script>
<script type="text/javascript" src="js/main.js?version=3"></script> <script type="text/javascript" src="js/main.js?version=3"></script>
<script defer src="https://events.vercount.one/js"></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> <script>
@@ -389,7 +395,7 @@
<!-- 可选:继续调用 config --> <!-- 可选:继续调用 config -->
<script type="text/javascript"> <script type="text/javascript">
try { try {
gtag('config', SiteConfig.analytics.google.id); gtag('config', 'G-DYWDEVKDP0');
} catch (e) { } catch (e) {
console.error("Google Analytics config 失败", e); console.error("Google Analytics config 失败", e);
} }
@@ -408,7 +414,38 @@
}; };
s.LA ? s.LA.ids && o() : (s.LA = p, s.LA.ids = [], o()), r.parentNode.insertBefore(n, r) 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}); }({id: "3OBGjwDdEIRS7XZ1", ck: "3OBGjwDdEIRS7XZ1"});
</script> </script>
<!-- PWA注册 -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
setTimeout(() => {
navigator.serviceWorker.register('./js/sw.js')
.then(function(registration) {
console.log('SW registered: ', registration);
})
.catch(function(registrationError) {
console.log('SW registration failed: ', registrationError);
});
}, 3000); // 页面稳定后再注册
});
}
</script>
<!-- Apple PWA支持 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Honesty">
<link rel="apple-touch-icon" href="./images/logo.png">
<!-- Windows PWA支持 -->
<meta name="msapplication-TileImage" content="./images/avatar.jpeg">
<meta name="msapplication-TileColor" content="#6c5ce7">
<meta name="msapplication-tap-highlight" content="no">
<!-- 其他PWA相关meta标签 -->
<meta name="mobile-web-app-capable" content="yes">
</body> </body>
</html> </html>

215
js/StarrySky.js Normal file
View File

@@ -0,0 +1,215 @@
/**
* 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));
} 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 = 10;
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) { function setStoredTheme(theme) {
const cacheKey = window.SiteConfig?.cacheKeys?.theme?.key || 'theme'; const cacheKey = window.SiteConfig?.cacheKeys?.theme?.key || 'theme-v2';
localStorage.setItem(cacheKey, JSON.stringify({ localStorage.setItem(cacheKey, JSON.stringify({
value: theme, time: new Date().getTime() value: theme, time: new Date().getTime()
})); }));
@@ -53,8 +53,8 @@ class AppCore {
constructor() { constructor() {
this.i18n = new I18nManager(); this.i18n = new I18nManager();
this.theme = new ThemeManager(); this.theme = new ThemeManager();
this.ui = new UIManager();
this.data = new DataManager(); this.data = new DataManager();
this.ui = new UIManager();
} }
} }
@@ -63,23 +63,7 @@ class AppCore {
=========================== */ =========================== */
class I18nManager { class I18nManager {
constructor() { constructor() {
// 获取当前请求参数有无 lang 参数 this.lang = getStoredLanguage();
try {
this.query = new URLSearchParams(window.location.search);
if (this.query.has('lang')) {
let lang = this.query.get('lang');
if (lang === 'zh' || lang === 'en') {
this.lang = lang;
setStoredLanguage(lang);
}
}
} catch (e) {
console.error(e);
}
if (!this.lang) {
this.lang = getStoredLanguage();
}
this.dict = { this.dict = {
zh: { zh: {
"nav.home": "首页", "nav.home": "首页",
@@ -95,8 +79,8 @@ class I18nManager {
"stats.exp": "编程年限", "stats.exp": "编程年限",
"stats.repos": "开源项目", "stats.repos": "开源项目",
"stats.followers": "关注者", "stats.followers": "关注者",
"stats.visitors": "访客数", "stats.visitors": "访问量",
"stats.visitNum": "访问量", "stats.visitNum": "访客数",
"mbti.name": "提倡者", "mbti.name": "提倡者",
"mbti.desc": "提倡者人格类型的人非常稀少只有不到1%的人口属于这种类型,但他们对世界的贡献不容忽视。", "mbti.desc": "提倡者人格类型的人非常稀少只有不到1%的人口属于这种类型,但他们对世界的贡献不容忽视。",
"mbti.tag1": "理想主义与道德感", "mbti.tag1": "理想主义与道德感",
@@ -136,8 +120,8 @@ class I18nManager {
"stats.exp": "Years Exp", "stats.exp": "Years Exp",
"stats.repos": "Projects", "stats.repos": "Projects",
"stats.followers": "Followers", "stats.followers": "Followers",
"stats.visitors": "Access User", "stats.visitors": "Visitors",
"stats.visitNum": "Visitors", "stats.visitNum": "Visiting guests",
"mbti.name": "Advocate", "mbti.name": "Advocate",
"mbti.desc": "Advocates of this personality type are very rare, with less than 1% of the population belonging to this type, but their contributions to the world cannot be ignored.", "mbti.desc": "Advocates of this personality type are very rare, with less than 1% of the population belonging to this type, but their contributions to the world cannot be ignored.",
"mbti.tag1": "Idealism & Morality", "mbti.tag1": "Idealism & Morality",
@@ -284,60 +268,28 @@ class DataManager {
try { try {
// Parallel Fetch with timeout // Parallel Fetch with timeout
let userData, repoData; const uRes = await this.fetchWithTimeout(`https://api.github.com/users/${user}`, { timeout: 5000 });
const userData = uRes.ok ? await uRes.json() : (window.SiteConfig?.defaults?.user);
try { let allRepos = [];
const uRes = await this.fetchWithTimeout(`https://api.github.com/users/${user}`, { timeout: 1000 }); let page = 1;
if (uRes.ok) { const perPage = 100;
userData = await uRes.json(); while (page <= 10) { // 最多抓取1000条直到满足条件或为空
} else { const rRes = await this.fetchWithTimeout(`https://api.github.com/users/${user}/repos?sort=stars&per_page=${perPage}&page=${page}`, { timeout: 5000 });
const fallbackUser = await this.fetchWithTimeout("./data/github_user.json", { timeout: 200 }); if (!rRes.ok) break;
userData = await fallbackUser.json(); const repos = await rRes.json();
} if (!Array.isArray(repos) || repos.length === 0) break;
} catch (err) { allRepos = allRepos.concat(repos);
// Handle abort errors and other fetch errors if (repos.length < perPage || allRepos.length >= 1000) break; // 足量
if (err.name === 'AbortError') { page++;
console.warn("GitHub user fetch aborted, using fallback data");
}
const fallbackUser = await this.fetchWithTimeout("./data/github_user.json", { timeout: 200 });
userData = await fallbackUser.json();
}
try {
let allRepos = [];
let page = 1;
const perPage = 100;
while (page <= 50) { // 最多抓取500条直到满足条件或为空
const rRes = await this.fetchWithTimeout(`https://api.github.com/users/${user}/repos?sort=pushed&direction=desc&per_page=${perPage}&page=${page}`, { timeout: 3000 });
if (!rRes.ok) break;
const repos = await rRes.json();
if (!Array.isArray(repos) || repos.length === 0) break;
allRepos = allRepos.concat(repos);
if (repos.length < perPage || allRepos.length >= 500) break; // 足量
page++;
}
if (allRepos.length) {
repoData = allRepos;
} else {
const fallbackRepos = await this.fetchWithTimeout("./data/github_repos.json", { timeout: 300 });
repoData = await fallbackRepos.json();
}
} catch (err) {
// Handle abort errors and other fetch errors
if (err.name === 'AbortError') {
console.warn("GitHub repos fetch aborted, using fallback data");
}
const fallbackRepos = await this.fetchWithTimeout("./data/github_repos.json", { timeout: 300 });
repoData = await fallbackRepos.json();
} }
let repoData = allRepos.length ? allRepos : (window.SiteConfig?.defaults?.repos);
// 过滤掉fork项目并按星数排序 // 过滤掉fork项目并按星数排序
if (Array.isArray(repoData)) { if (Array.isArray(repoData)) {
repoData = repoData repoData = repoData
.filter(repo => !repo.fork && (repo.stargazers_count > 0 || repo.forks_count > 0)) .filter(repo => !repo.fork && (repo.stargazers_count > 0 || repo.forks_count > 0))
.sort((a, b) => (b.stargazers_count || 0) - (a.stargazers_count || 0)) .sort((a, b) => (b.stargazers_count || 0) - (a.stargazers_count || 0))
.slice(0, 16); // 只取前16 .slice(0, 12); // 只取前12
} }
const slimUser = { const slimUser = {
@@ -376,10 +328,22 @@ class DataManager {
}); });
} }
formatVisitors() {
const siteVisitorDom = window.document.getElementById('busuanzi_value_site_pv');
if (siteVisitorDom) {
let count = siteVisitorDom.innerText;
if (count && count.length > 3) {
// 格式化 k,w m等单位 支持两位小数点
count = count.replace(/(\d)(?=(\d{3})+$)/g, '$1,');
count = count.replace(/(\d+)(?=(\d{3})+(?:\.\d+)?$)/g, '$0 ');
siteVisitorDom.innerText = count;
}
}
}
renderRepos(list) { renderRepos(list) {
if (!Array.isArray(list)) list = window.SiteConfig?.defaults?.repos; if (!Array.isArray(list)) list = window.SiteConfig?.defaults?.repos;
let html = ''; let html = '';
list.forEach(repo => { list.slice(0, 12).forEach(repo => {
// Fix: API field compatibility // Fix: API field compatibility
const stars = repo.stargazers_count !== undefined ? repo.stargazers_count : (repo.stars || 0); const stars = repo.stargazers_count !== undefined ? repo.stargazers_count : (repo.stars || 0);
const forks = repo.forks_count !== undefined ? repo.forks_count : (repo.forks || 0); const forks = repo.forks_count !== undefined ? repo.forks_count : (repo.forks || 0);
@@ -596,11 +560,10 @@ class UIManager {
server: window.SiteConfig.artalk.server, server: window.SiteConfig.artalk.server,
site: window.SiteConfig.artalk.site, site: window.SiteConfig.artalk.site,
// 多语言支持 // 多语言支持
locale: isZh ? 'zh-CN' : 'en', locale: isZh ? 'zh-CN' : 'en-US',
// 自定义占位符(支持多语言) // 自定义占位符(支持多语言)
placeholder: isZh ? '说点什么吧...支持 Markdown 语法,可 @用户、发送表情' : 'Leave a comment... Supports Markdown, @mentions, and Send 😊', placeholder: isZh ? '说点什么吧...支持 Markdown 语法,可 @用户、发送表情' : 'Leave a comment... Supports Markdown, @mentions, and Send 😊',
// 无评论时显示
noComment: isZh ? '快来成为第一个评论的人吧~' : 'Be the first to leave a comment~😍',
// 发送按钮文字(多语言) // 发送按钮文字(多语言)
sendBtn: isZh ? '发送' : 'Send', sendBtn: isZh ? '发送' : 'Send',
loginBtn: isZh ? '发送' : 'Send', loginBtn: isZh ? '发送' : 'Send',
@@ -613,7 +576,11 @@ class UIManager {
// 启用 Markdown // 启用 Markdown
markdown: true, markdown: true,
emoticons: "https://emoticons.hzchu.top/json/artalk/zaoandandandeyouyongquan.json", // 表情面板
emoji: {
// 使用默认表情包
preset: 'twemoji'
},
// 启用 @ 用户提醒功能 // 启用 @ 用户提醒功能
mention: true, mention: true,
@@ -655,7 +622,7 @@ class UIManager {
`昨天 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` : `昨天 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` :
`Yesterday ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; `Yesterday ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
return date.toLocaleString(isZh ? 'zh-CN' : 'en', { return date.toLocaleString(isZh ? 'zh-CN' : 'en-US', {
year: 'numeric', year: 'numeric',
month: '2-digit', month: '2-digit',
day: '2-digit', day: '2-digit',
@@ -676,8 +643,8 @@ class UIManager {
} }
}; };
let artalkRef = Artalk.init(artalkConfig); Artalk.init(artalkConfig);
this.enhanceArtalkUI(artalkRef); this.enhanceArtalkUI();
} catch (e) { } catch (e) {
console.error("Artalk Error", e); console.error("Artalk Error", e);
const msg = isZh ? '当前评论区已关闭' : 'Comments are closed'; const msg = isZh ? '当前评论区已关闭' : 'Comments are closed';
@@ -711,7 +678,7 @@ class UIManager {
this.initArtalk(); this.initArtalk();
} }
enhanceArtalkUI(artalkRef) { enhanceArtalkUI() {
const container = document.getElementById('artalk-container'); const container = document.getElementById('artalk-container');
if (!container) return; if (!container) return;
@@ -732,31 +699,29 @@ class UIManager {
} }
// 监听主题/语言变化 // 监听主题/语言变化
const themeObserver = new MutationObserver(() => { if (this._themeLangObserver) {
try { this._themeLangObserver.disconnect(); } catch (_) {}
}
const prevTheme = document.documentElement.getAttribute('data-theme');
const prevLang = document.documentElement.getAttribute('data-lang');
this._lastTheme = prevTheme;
this._lastLang = prevLang;
this._reloading = false;
this._themeLangObserver = new MutationObserver(() => {
const newTheme = document.documentElement.getAttribute('data-theme'); const newTheme = document.documentElement.getAttribute('data-theme');
const newLang = document.documentElement.getAttribute('data-lang'); const newLang = document.documentElement.getAttribute('data-lang');
console.log('Theme/Language changed:', newTheme, newLang); if (this._reloading) return;
if (newLang && newLang !== lang) { if (newTheme === this._lastTheme && newLang === this._lastLang) return;
// 延迟执行 this._lastTheme = newTheme;
setTimeout(() => { this._lastLang = newLang;
// 重新加载整个评论组件 setTimeout(() => {
this.reloadArtalk(); if (this._reloading) return;
}, 300); this._reloading = true;
} else if (newTheme && newTheme !== currentTheme) { this.reloadArtalk();
try { this._reloading = false;
artalkRef.ui.setDarkMode(newTheme === 'night') }, 300);
} catch (e) {
setTimeout(() => {
// 重新加载整个评论组件
this.reloadArtalk();
}, 300);
}
}
}); });
this._themeLangObserver.observe(document.documentElement, {
themeObserver.observe(document.documentElement, {
attributes: true, attributes: true,
attributeFilter: ['data-theme', 'data-lang'] attributeFilter: ['data-theme', 'data-lang']
}); });
@@ -834,55 +799,15 @@ class UIManager {
//const name = item.name || ''; //const name = item.name || '';
//const hash = Array.from(name).reduce((a, c) => a + c.charCodeAt(0), 0); //const hash = Array.from(name).reduce((a, c) => a + c.charCodeAt(0), 0);
const gid = Number(item.gradientId) && Number.isFinite(Number(item.gradientId)) const gid = Number(item.gradientId) && Number.isFinite(Number(item.gradientId))
? Math.max(1, Math.min(25, Number(item.gradientId))) ? Math.max(1, Math.min(10, Number(item.gradientId)))
: (idx % 25) + 1; : (idx % 10) + 1;
return {...item, gradientId: gid}; return {...item, gradientId: gid};
}); });
const currentState = window.matchMedia('(max-width: 768px)').matches ? 'mobile' : 'desktop'; const isMobile = window.matchMedia('(max-width: 768px)').matches;
// 检查是否已保存状态到 sessionStorage
const savedState = sessionStorage.getItem('techCloudState_' + currentState);
if (savedState) {
const parsedState = JSON.parse(savedState);
// 如果当前状态与保存的状态一致,直接使用保存的内容
if (parsedState.type === currentState) {
container.innerHTML = parsedState.html;
if (currentState === 'mobile') {
container.classList.add('mobile-scroll');
}
if (currentState === 'desktop') {
container.classList.remove('mobile-scroll');
// 重新初始化3D球体动画
this.init3DSphereAnimation(container, techStack);
}
// 监听窗口大小变化
this.setupResizeListener(container, techStack, currentState);
return;
}
}
// 清空容器并重新生成
this.generateTechCloud(container, techStack, currentState);
// 监听窗口大小变化
this.setupResizeListener(container, techStack, currentState);
}
// 保存技术标签云状态到 sessionStorage
saveTechCloudState(container, type) {
const state = {
type: type,
html: container.innerHTML
};
sessionStorage.setItem('techCloudState_' + type, JSON.stringify(state));
}
// 生成技术标签云
generateTechCloud(container, techStack, type) {
container.innerHTML = ''; container.innerHTML = '';
if (type === 'mobile') {
if (isMobile) {
// Mobile: 3-row seamless marquee // Mobile: 3-row seamless marquee
container.classList.add('mobile-scroll'); container.classList.add('mobile-scroll');
const rows = 3; const rows = 3;
@@ -894,7 +819,7 @@ class UIManager {
const appendItem = (rowEl, item, idx) => { const appendItem = (rowEl, item, idx) => {
const el = document.createElement('span'); const el = document.createElement('span');
el.className = 'tech-tag-mobile'; el.className = 'tech-tag-mobile';
const colorClass = `tag-color-${item.gradientId || ((idx % 25) + 1)}`; const colorClass = `tag-color-${item.gradientId || ((idx % 10) + 1)}`;
el.classList.add(colorClass); el.classList.add(colorClass);
el.innerText = item.name; el.innerText = item.name;
el.style.border = 'none'; el.style.border = 'none';
@@ -912,157 +837,131 @@ class UIManager {
} else { } else {
// PC: 3D Sphere // PC: 3D Sphere
container.classList.remove('mobile-scroll'); container.classList.remove('mobile-scroll');
this.init3DSphereAnimation(container, techStack);
}
// 保存当前状态 // 使用防抖优化尺寸计算
this.saveTechCloudState(container, type); let resizeTimeout;
} const updateContainerSize = () => {
if (resizeTimeout) {
clearTimeout(resizeTimeout);
}
resizeTimeout = setTimeout(() => {
init3DSphere();
}, 100);
};
// 初始化3D球体动画 // 初始化3D球体
init3DSphereAnimation(container, techStack) { const init3DSphere = () => {
// 清除之前的动画 // 清除之前的动画
if (container.__animToken) { if (container.__animToken) {
cancelAnimationFrame(container.__animToken); cancelAnimationFrame(container.__animToken);
} }
// 清空容器 // 清空容器
container.innerHTML = ''; container.innerHTML = '';
const tags = []; const tags = [];
techStack.forEach((item, index) => { techStack.forEach((item, index) => {
const el = document.createElement('a'); const el = document.createElement('a');
el.className = 'tech-tag-3d'; el.className = 'tech-tag-3d';
const colorClass = `tag-color-${item.gradientId || ((index % 25) + 1)}`; const colorClass = `tag-color-${item.gradientId || ((index % 10) + 1)}`;
el.classList.add(colorClass); el.classList.add(colorClass);
el.innerText = item.name; el.innerText = item.name;
el.style.border = 'none'; el.style.border = 'none';
container.appendChild(el); container.appendChild(el);
tags.push({el, x: 0, y: 0, z: 0}); tags.push({el, x: 0, y: 0, z: 0, bw: el.offsetWidth, bh: el.offsetHeight});
});
// 动态半径,避免容器溢出,使用防抖优化
let radius = Math.max(160, Math.min(container.offsetWidth, container.offsetHeight) / 2 - 24);
const dtr = Math.PI / 180;
let lasta = 1, lastb = 1;
let active = false, mouseX = 0, mouseY = 0;
// 初始化位置
tags.forEach((tag, i) => {
let phi = Math.acos(-1 + (2 * i + 1) / tags.length);
let theta = Math.sqrt(tags.length * Math.PI) * phi;
tag.x = radius * Math.cos(theta) * Math.sin(phi);
tag.y = radius * Math.sin(theta) * Math.sin(phi);
tag.z = radius * Math.cos(phi);
});
container.onmouseover = () => active = true;
container.onmouseout = () => active = false;
container.onmousemove = (e) => {
// 使用requestAnimationFrame处理鼠标移动事件避免强制重排
requestAnimationFrame(() => {
let rect = container.getBoundingClientRect();
mouseX = (e.clientX - (rect.left + rect.width / 2)) / 5;
mouseY = (e.clientY - (rect.top + rect.height / 2)) / 5;
});
};
const update = () => {
let a, b;
if (active) {
a = (-Math.min(Math.max(-mouseY, -200), 200) / radius) * 2;
b = (Math.min(Math.max(-mouseX, -200), 200) / radius) * 2;
} else {
a = lasta * 0.98; // Auto rotate
b = lastb * 0.98;
}
lasta = a;
lastb = b;
if (Math.abs(a) <= 0.01 && Math.abs(b) <= 0.01 && !active) a = 0.5; // Keep spinning slowly
let sa = Math.sin(a * dtr), ca = Math.cos(a * dtr);
let sb = Math.sin(b * dtr), cb = Math.cos(b * dtr);
// 批量更新样式以减少重排
// 先收集所有需要更新的样式信息
const updates = [];
tags.forEach(tag => {
let rx1 = tag.x, ry1 = tag.y * ca - tag.z * sa, rz1 = tag.y * sa + tag.z * ca;
let ry2 = ry1, rz2 = rx1 * -sb + rz1 * cb;
tag.x = rx1 * cb + rz1 * sb;
tag.y = ry2;
tag.z = rz2;
let scale = (tag.z + radius) / (2 * radius) + 0.45;
scale = Math.min(Math.max(scale, 0.7), 1.15);
const opacity = (tag.z + radius) / (2 * radius) + 0.2;
const zIndex = parseInt(scale * 100);
const left = tag.x + container.offsetWidth / 2 - tag.el.offsetWidth / 2;
const top = tag.y + container.offsetHeight / 2 - tag.el.offsetHeight / 2;
updates.push({
el: tag.el,
transform: `translate(${left}px, ${top}px) scale(${scale})`,
opacity: opacity,
zIndex: zIndex
}); });
});
updates.forEach(update => { // 动态半径,避免容器溢出,使用防抖优化
update.el.style.transform = update.transform; const cw = container.clientWidth;
update.el.style.opacity = update.opacity; const ch = container.clientHeight;
update.el.style.zIndex = update.zIndex; let radius = Math.max(160, Math.min(cw, ch) / 2 - 24);
}); const dtr = Math.PI / 180;
let lasta = 1, lastb = 1;
let active = false, mouseX = 0, mouseY = 0;
container.__animToken = requestAnimationFrame(update); // 初始化位置
}; tags.forEach((tag, i) => {
let phi = Math.acos(-1 + (2 * i + 1) / tags.length);
let theta = Math.sqrt(tags.length * Math.PI) * phi;
tag.x = radius * Math.cos(theta) * Math.sin(phi);
tag.y = radius * Math.sin(theta) * Math.sin(phi);
tag.z = radius * Math.cos(phi);
});
container.__animToken = requestAnimationFrame(update); container.onmouseover = () => active = true;
container.onmouseout = () => active = false;
container.onmousemove = (e) => {
// 使用requestAnimationFrame处理鼠标移动事件避免强制重排
requestAnimationFrame(() => {
let rect = container.getBoundingClientRect();
mouseX = (e.clientX - (rect.left + rect.width / 2)) / 5;
mouseY = (e.clientY - (rect.top + rect.height / 2)) / 5;
});
};
const update = () => {
let a, b;
if (active) {
a = (-Math.min(Math.max(-mouseY, -200), 200) / radius) * 2;
b = (Math.min(Math.max(-mouseX, -200), 200) / radius) * 2;
} else {
a = lasta * 0.98; // Auto rotate
b = lastb * 0.98;
}
lasta = a;
lastb = b;
if (Math.abs(a) <= 0.01 && Math.abs(b) <= 0.01 && !active) a = 0.5; // Keep spinning slowly
let sa = Math.sin(a * dtr), ca = Math.cos(a * dtr);
let sb = Math.sin(b * dtr), cb = Math.cos(b * dtr);
// 批量更新样式以减少重排
// 先收集所有需要更新的样式信息
const updates = [];
tags.forEach(tag => {
let rx1 = tag.x, ry1 = tag.y * ca - tag.z * sa, rz1 = tag.y * sa + tag.z * ca;
let ry2 = ry1, rz2 = rx1 * -sb + rz1 * cb;
tag.x = rx1 * cb + rz1 * sb;
tag.y = ry2;
tag.z = rz2;
let scale = (tag.z + radius) / (2 * radius) + 0.45;
scale = Math.min(Math.max(scale, 0.7), 1.15);
const opacity = (tag.z + radius) / (2 * radius) + 0.2;
const zIndex = parseInt(scale * 100);
const left = tag.x + cw / 2 - tag.bw / 2;
const top = tag.y + ch / 2 - tag.bh / 2;
updates.push({
el: tag.el,
transform: `translate(${left}px, ${top}px) scale(${scale})`,
opacity: opacity,
zIndex: zIndex
});
});
updates.forEach(update => {
update.el.style.transform = update.transform;
update.el.style.opacity = update.opacity;
update.el.style.zIndex = update.zIndex;
});
container.__animToken = requestAnimationFrame(update);
};
container.__animToken = requestAnimationFrame(update);
};
// 初始化3D球体
init3DSphere();
// 监听窗口大小变化
window.addEventListener('resize', updateContainerSize);
}
} }
// 设置窗口大小变化监听器
setupResizeListener(container, techStack, currentType) {
// 使用防抖优化尺寸计算
let resizeTimeout;
let windowRef = currentType;
const handleResize = () => {
if (resizeTimeout) {
clearTimeout(resizeTimeout);
}
resizeTimeout = setTimeout(() => {
const isMobile = window.matchMedia('(max-width: 768px)').matches;
const currentState = isMobile ? 'mobile' : 'desktop';
if (windowRef === currentState) {
return
}
windowRef = currentState;
// 检查 sessionStorage 中是否有对应状态的内容
const savedState = sessionStorage.getItem('techCloudState_' + currentState);
if (savedState) {
// 直接使用保存的内容
const parsedState = JSON.parse(savedState);
container.innerHTML = parsedState.html;
if (currentState === 'mobile') {
container.classList.add('mobile-scroll');
}
if (currentState === 'desktop') {
container.classList.remove('mobile-scroll');
this.init3DSphereAnimation(container, techStack);
}
} else {
// 重新生成
container.innerHTML = '';
this.generateTechCloud(container, techStack, currentState);
}
}, 100);
};
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
}
initFab() { initFab() {
const main = document.getElementById('fab-main'); const main = document.getElementById('fab-main');
@@ -1072,6 +971,8 @@ class UIManager {
const fMusic = document.getElementById('fab-music'); const fMusic = document.getElementById('fab-music');
if (!main || !menu || !fLang || !fTheme || !fMusic) return; if (!main || !menu || !fLang || !fTheme || !fMusic) return;
// 添加拖拽功能
this.initDraggableFab();
const updateLabels = () => { const updateLabels = () => {
// 使用requestAnimationFrame避免强制重排 // 使用requestAnimationFrame避免强制重排
@@ -1120,6 +1021,73 @@ class UIManager {
requestAnimationFrame(updateLabels); requestAnimationFrame(updateLabels);
} }
// 初始化拖拽功能
initDraggableFab() {
const fab = document.querySelector('.mobile-fab');
if (!fab) return;
let isDragging = false;
let initialX, initialY, currentX, currentY, xOffset = 0, yOffset = 0;
fab.style.willChange = 'transform';
// 拖拽相关方法
const setTranslate = (xPos, yPos, el) => {
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
};
const dragStart = (e) => {
if (e.type === 'touchstart') {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
isDragging = true;
};
const dragEnd = () => {
initialX = currentX;
initialY = currentY;
isDragging = false;
};
const drag = (e) => {
if (isDragging) {
e.preventDefault();
if (e.type === 'touchmove') {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
// 使用requestAnimationFrame优化拖拽动画
requestAnimationFrame(() => {
const ww = window.innerWidth;
const wh = window.innerHeight;
const rect = fab.getBoundingClientRect();
const fw = rect.width;
const fh = rect.height;
currentX = Math.max(0, Math.min(currentX, ww - fw));
currentY = Math.max(0, Math.min(currentY, wh - fh));
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, fab);
});
}
};
// 绑定事件
fab.addEventListener('touchstart', dragStart, { passive: false });
fab.addEventListener('touchend', dragEnd, { passive: false });
fab.addEventListener('touchmove', drag, { passive: false });
fab.addEventListener('mousedown', dragStart, { passive: false });
fab.addEventListener('mouseup', dragEnd, { passive: false });
fab.addEventListener('mousemove', drag, { passive: false });
}
initAudio() { initAudio() {
const el = document.getElementById('site-audio'); const el = document.getElementById('site-audio');
@@ -1127,43 +1095,16 @@ class UIManager {
this.audio = el; this.audio = el;
this.audio.loop = true; this.audio.loop = true;
// 页面加载完成后根据条件决定是否播放 // 检查是否在24小时内用户暂停过音乐
window.addEventListener('load', () => { const shouldRemainPaused = this.shouldMusicRemainPaused();
// 检查是否在24小时内用户暂停过音乐
const shouldRemainPaused = this.shouldMusicRemainPaused();
// 如果不应该保持暂停状态,则尝试播放
if (!shouldRemainPaused) {
let userInteracted = true;
this.audio.play().catch(() => {
// 静默处理播放失败
userInteracted = false;
});
// 添加用户交互检查,避免浏览器阻止自动播放
const attemptAutoplay = () => {
// 检查是否已有用户交互
if (this.userInteracted === false) {
// 添加一次性用户交互监听器
const enableAudio = () => {
this.userInteracted = true;
setTimeout(() => {
this.audio.play().catch(() => {
});
}, 1000);
document.removeEventListener('click', enableAudio);
document.removeEventListener('touchstart', enableAudio);
document.removeEventListener('keydown', enableAudio);
document.removeEventListener('mousemove', enableAudio);
};
document.addEventListener('click', enableAudio, { once: true }); const tryPlay = () => {
document.addEventListener('touchstart', enableAudio, { once: true }); if (!shouldRemainPaused) {
document.addEventListener('keydown', enableAudio, { once: true }); this.audio.play().catch(() => {
document.addEventListener('mousemove', enableAudio, { once: true }); });
}
};
requestAnimationFrame(attemptAutoplay);
} }
}); };
tryPlay();
} }
updateCustomStyles(container, theme) { updateCustomStyles(container, theme) {

View File

@@ -1,181 +1,202 @@
// 配置文件 - 提取自各个JavaScript文件的关键配置 // 配置文件 - 提取自各个JavaScript文件的关键配置
// 创建日期: 2025-11-20 // 创建日期: 2025-11-20
(function() { const SiteConfig = {
const SiteConfig = { // bj.js 配置
// bj.js 配置 stars: {
stars: { count: 300,
count: 300, refreshInterval: 50
refreshInterval: 50 },
},
// main.js 配置 // main.js 配置
animation: { animation: {
elementUp: { elementUp: {
delay: 0, delay: 0,
increment: 150 increment: 150
}
},
background: {
imagePaths: [
"/images/bj/1.webp",
"/images/bj/2.webp",
"/images/bj/3.webp",
"/images/bj/4.webp",
"/images/bj/5.webp",
"/images/bj/6.webp",
"/images/bj/7.webp"
]
},
hitokoto: {
apiUrl: 'https://v1.hitokoto.cn?c=c&c=d&c=i&c=k'
},
// about.js 配置
github: {
username: 'listener-He'
},
blog: {
rssUrl: 'https://blog.hehouhui.cn/api/rss'
},
// 通用缓存键与TTL毫秒
cacheKeys: {
github: { key: 'gh_data', ttlMs: 36000000 },
blog: { key: 'blog_data', ttlMs: 3600000 },
theme: { key: 'theme', ttlMs: 3600000 }
},
techStack: [
{ name: 'Java', category: 'core', weight: 5 },
{ name: 'Spring Boot', category: 'backend', weight: 5 },
{ name: 'JavaScript', category: 'frontend', weight: 5 },
{ name: 'Python', category: 'core', weight: 4 },
{ name: 'WebFlux', category: 'backend', weight: 5 },
{ name: 'Reactor', category: 'backend', weight: 5 },
{ name: 'TypeScript', category: 'frontend', weight: 4 },
{ name: 'Spring Cloud', category: 'backend', weight: 4 },
{ name: 'Go', category: 'core', weight: 3 },
{ name: 'MySQL', category: 'data', weight: 4 },
{ name: 'Redis', category: 'data', weight: 4 },
{ name: 'MongoDB', category: 'data', weight: 3 },
{ name: 'Docker', category: 'ops', weight: 4 },
{ name: 'Kubernetes', category: 'ops', weight: 3 },
{ name: 'OpenAI API', category: 'ai', weight: 3 },
{ name: 'LangChain', category: 'ai', weight: 3 },
{ name: 'TensorFlow', category: 'ai', weight: 2 },
{ name: 'PyTorch', category: 'ai', weight: 2 },
{ name: 'Elasticsearch', category: 'data', weight: 3 },
{ name: 'RabbitMQ', category: 'data', weight: 2 },
{ name: 'RocketMQ', category: 'data', weight: 2 },
{ name: 'Kafka', category: 'data', weight: 2 },
{ name: 'Jenkins', category: 'ops', weight: 3 },
{ name: 'Git', category: 'ops', weight: 4 },
{ name: 'Linux', category: 'ops', weight: 3 },
{ name: 'AWS', category: 'ops', weight: 2 },
{ name: 'Nginx', category: 'ops', weight: 2 },
{ name: 'Spring Security', category: 'backend', weight: 3 },
{ name: 'MyBatis', category: 'backend', weight: 3 },
{ name: 'JPA', category: 'backend', weight: 2 },
{ name: 'Dubbo', category: 'backend', weight: 2 },
{ name: 'Netty', category: 'backend', weight: 2 },
{ name: 'Transformers', category: 'ai', weight: 2 },
{ name: 'Scikit-learn', category: 'ai', weight: 2 },
{ name: 'Ollama', category: 'ai', weight: 1 },
{ name: 'Dify', category: 'ai', weight: 1 },
{ name: 'Spring AI', category: 'ai', weight: 1 },
{ name: 'ClickHouse', category: 'data', weight: 1 },
{ name: 'Postgresql', category: 'data', weight: 1 },
{ name: "Hexo", category: "frontend", weight: 5},
{ name: "NextJs", category: "frontend", weight: 1},
{ name: "HuggingFace", category: "ai", weight: 1},
{ name: "Vue", category: "frontend", weight: 3},
{ name: "React", category: "frontend", weight: 1},
{ name: "R2dbc", category: "data", weight: 1},
{ name: "Proto", category: "core", weight: 1},
{ name: "Mqtt", category: "core", weight: 2},
{ name: "Grpc", category: "core", weight: 1},
{ name: "Figma", category: "frontend", weight: 1}
],
// 默认数据当API或RSS不可用时使用
defaults: {
repos: [
{name: "yunxiao-LLM-reviewer", desc: "AI Code Reviewer based on LLM", stars: 9, url: "#"},
{name: "hexo-theme-stellar", desc: "Comprehensive Hexo theme", stars: 5, url: "#"},
{name: "Universal-IoT-Java", desc: "IoT Platform Demo", stars: 2, url: "#"}
],
posts: [
{title: "Vector Database Guide", date: "2025-01-02", cat: "Tech", url: "#"},
{title: "Spring Boot 3.0 Features", date: "2024-12-30", cat: "Java", url: "#"},
{title: "Microservices Patterns", date: "2024-12-28", cat: "Arch", url: "#"}
],
user: { repos: 165, followers: 6, created: "2018-05-14" }
},
socialCards: {
rings: [130, 180, 230],
goldenAngle: 137.5,
baseSpeed: 16
},
artalk: {
server: 'https://artalk.hehouhui.cn',
site: 'Honesty的主页',
placeholder: '来说点什么吧...',
noComment: '暂无评论',
sendBtn: '发送'
},
// 站点统计配置
analytics: {
busuanzi: {
src: '//cdn.busuanzi.cc/busuanzi/3.6.9/busuanzi.abbr.min.js',
site_pv_id: 'busuanzi_site_pv',
site_uv_id: 'busuanzi_site_uv',
formatter: true
},
baidu: {
src: 'https://hm.baidu.com/hm.js?ae2a009a75b13c21d5121ee51375ea4e',
id: 'ae2a009a75b13c21d5121ee51375ea4e'
},
google: {
src: 'https://www.googletagmanager.com/gtag/js',
id: 'G-DYWDEVKDP0'
},
tencent: {
src: 'https://sdk.51.la/js-sdk-pro.min.js',
id: '3OBGjwDdEIRS7XZ1',
ck: '3OBGjwDdEIRS7XZ1'
}
},
animationSettings: {
observerOptions: {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
},
itemObserverOptions: {
threshold: 0.15,
rootMargin: '0px 0px -20px 0px'
}
},
// 开发环境配置
dev: {
isLocal: (typeof location !== 'undefined') ? (location.hostname.indexOf('localhost') > -1 || location.hostname.indexOf('127.0.0.1') > -1) : false
} }
}; },
background: {
imagePaths: [
"/images/bj/1.jpg",
"/images/bj/2.jpg",
"/images/bj/3.jpg",
"/images/bj/4.jpg",
"/images/bj/5.jpg",
"/images/bj/6.jpg",
"/images/bj/7.jpg"
]
},
hitokoto: {
apiUrl: 'https://v1.hitokoto.cn?c=c&c=d&c=i&c=k'
},
// about.js 配置
github: {
username: 'listener-He'
},
blog: {
rssUrl: 'https://blog.hehouhui.cn/api/rss'
},
// 通用缓存键与TTL毫秒
cacheKeys: {
github: { key: 'gh_data_v2', ttlMs: 36000000 },
blog: { key: 'blog_data_v2', ttlMs: 3600000 },
theme: { key: 'theme_v2', ttlMs: 3600000 }
},
techStack: [
{ name: 'Java', category: 'core', weight: 5 },
{ name: 'Spring Boot', category: 'backend', weight: 5 },
{ name: 'JavaScript', category: 'frontend', weight: 5 },
{ name: 'Python', category: 'core', weight: 4 },
{ name: 'WebFlux', category: 'backend', weight: 5 },
{ name: 'Reactor', category: 'backend', weight: 5 },
{ name: 'TypeScript', category: 'frontend', weight: 4 },
{ name: 'Spring Cloud', category: 'backend', weight: 4 },
{ name: 'Go', category: 'core', weight: 3 },
{ name: 'MySQL', category: 'data', weight: 4 },
{ name: 'Redis', category: 'data', weight: 4 },
{ name: 'MongoDB', category: 'data', weight: 3 },
{ name: 'Docker', category: 'ops', weight: 4 },
{ name: 'Kubernetes', category: 'ops', weight: 3 },
{ name: 'OpenAI API', category: 'ai', weight: 3 },
{ name: 'LangChain', category: 'ai', weight: 3 },
{ name: 'TensorFlow', category: 'ai', weight: 2 },
{ name: 'PyTorch', category: 'ai', weight: 2 },
{ name: 'Elasticsearch', category: 'data', weight: 3 },
{ name: 'RabbitMQ', category: 'data', weight: 2 },
{ name: 'RocketMQ', category: 'data', weight: 2 },
{ name: 'Kafka', category: 'data', weight: 2 },
{ name: 'Jenkins', category: 'ops', weight: 3 },
{ name: 'Git', category: 'ops', weight: 4 },
{ name: 'Linux', category: 'ops', weight: 3 },
{ name: 'AWS', category: 'ops', weight: 2 },
{ name: 'Nginx', category: 'ops', weight: 2 },
{ name: 'Spring Security', category: 'backend', weight: 3 },
{ name: 'MyBatis', category: 'backend', weight: 3 },
{ name: 'JPA', category: 'backend', weight: 2 },
{ name: 'Dubbo', category: 'backend', weight: 2 },
{ name: 'Netty', category: 'backend', weight: 2 },
{ name: 'Transformers', category: 'ai', weight: 2 },
{ name: 'Scikit-learn', category: 'ai', weight: 2 },
{ name: 'Ollama', category: 'ai', weight: 1 },
{ name: 'Dify', category: 'ai', weight: 1 },
{ name: 'Spring AI', category: 'ai', weight: 1 },
{ name: 'ClickHouse', category: 'data', weight: 1 },
{ name: 'Postgresql', category: 'data', weight: 1 },
{ name: "Hexo", category: "frontend", weight: 5},
{ name: "NextJs", category: "frontend", weight: 1},
{ name: "HuggingFace", category: "ai", weight: 1},
{ name: "Vue", category: "frontend", weight: 3},
{ name: "React", category: "frontend", weight: 1},
{ name: "R2dbc", category: "data", weight: 1},
{ name: "Proto", category: "core", weight: 1},
{ name: "Mqtt", category: "core", weight: 2},
{ name: "Grpc", category: "core", weight: 1},
{ name: "Figma", category: "frontend", weight: 1}
],
// 导出配置 // 默认数据当API或RSS不可用时使用
if (typeof module !== 'undefined' && module.exports) { defaults: {
module.exports = SiteConfig; repos: [
} else if (typeof window !== 'undefined') { {name: "yunxiao-LLM-reviewer", desc: "AI Code Reviewer based on LLM", stars: 9, url: "#"},
window.SiteConfig = SiteConfig; {name: "hexo-theme-stellar", desc: "Comprehensive Hexo theme", stars: 5, url: "#"},
{name: "Universal-IoT-Java", desc: "IoT Platform Demo", stars: 2, url: "#"}
],
posts: [
{title: "Vector Database Guide", date: "2025-01-02", cat: "Tech", url: "#"},
{title: "Spring Boot 3.0 Features", date: "2024-12-30", cat: "Java", url: "#"},
{title: "Microservices Patterns", date: "2024-12-28", cat: "Arch", url: "#"}
],
user: { repos: 165, followers: 6, created: "2018-05-14" }
},
socialCards: {
rings: [130, 180, 230],
goldenAngle: 137.5,
baseSpeed: 16
},
artalk: {
server: 'https://artalk.hehouhui.cn',
site: 'Honesty的主页',
placeholder: '来说点什么吧...',
noComment: '暂无评论',
sendBtn: '发送'
},
// 站点统计配置
analytics: {
busuanzi: {
src: 'https://events.vercount.one/js',
site_pv_id: 'busuanzi_value_site_pv',
site_uv_id: 'busuanzi_value_site_uv',
formatter: true
},
baidu: {
src: 'https://hm.baidu.com/hm.js?ae2a009a75b13c21d5121ee51375ea4e',
id: 'ae2a009a75b13c21d5121ee51375ea4e'
},
google: {
src: 'https://www.googletagmanager.com/gtag/js',
id: 'G-DYWDEVKDP0'
},
tencent: {
src: 'https://sdk.51.la/js-sdk-pro.min.js',
id: '3OBGjwDdEIRS7XZ1',
ck: '3OBGjwDdEIRS7XZ1'
}
},
animationSettings: {
observerOptions: {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
},
itemObserverOptions: {
threshold: 0.15,
rootMargin: '0px 0px -20px 0px'
}
},
// 开发环境配置
dev: {
isLocal: (typeof location !== 'undefined') ? (location.hostname.indexOf('localhost') > -1 || location.hostname.indexOf('127.0.0.1') > -1) : false
} }
})(); };
if (Array.isArray(SiteConfig.techStack)) {
const categoryGradientMap = {
core: 7,
backend: 4,
data: 9,
ops: 10,
ai: 3
};
const vividSet = [1, 4, 7, 8];
SiteConfig.techStack = SiteConfig.techStack.map((item) => {
const name = item.name || '';
const hash = Array.from(name).reduce((a, c) => a + c.charCodeAt(0), 0);
if (item.gradientId && Number.isFinite(Number(item.gradientId))) {
return { ...item, gradientId: Math.max(1, Math.min(10, Number(item.gradientId))) };
}
let base = categoryGradientMap[item.category] || ((hash % 10) + 1);
if (Number(item.weight) >= 5) {
base = vividSet[hash % vividSet.length];
}
return { ...item, gradientId: base };
});
}
// 导出配置
if (typeof module !== 'undefined' && module.exports) {
module.exports = SiteConfig;
} else if (typeof window !== 'undefined') {
window.SiteConfig = SiteConfig;
}

View File

@@ -49,16 +49,18 @@ $(document).ready(function () {
/** /**
* 自定义壁纸 * 自定义壁纸
*/ */
var imgUrls = null; //JSON.parse(sessionStorage.getItem("imgUrls")); var imgUrls = JSON.parse(sessionStorage.getItem("imgUrls"));
var index = sessionStorage.getItem("index");
var $panel = $('#panel'); var $panel = $('#panel');
var date = new Date(); var date = new Date();
var dayOfWeek = date.getDay(); var dayOfWeek = date.getDay();
if (imgUrls == null) { if (imgUrls == null) {
imgUrls = []; imgUrls = [];
index = 0;
SiteConfig.background.imagePaths.forEach(path => { SiteConfig.background.imagePaths.forEach(path => {
imgUrls.push(path); imgUrls.push(path);
}); });
//sessionStorage.setItem("imgUrls", JSON.stringify(imgUrls)); sessionStorage.setItem("imgUrls", JSON.stringify(imgUrls));
// sessionStorage.setItem("index", index); // sessionStorage.setItem("index", index);
} else { } else {
// if (index == imgUrls.length) // if (index == imgUrls.length)

58
js/moments.js Normal file
View File

@@ -0,0 +1,58 @@
$(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); // 等待动画结束后隐藏
});
});

View File

@@ -0,0 +1,87 @@
const CACHE_NAME = 'honesty-home-v1.1.0';
const urlsToCache = [
'./index.html',
'./about.html',
'./css/style.css',
'./css/about.css',
'./css/artalk.css',
'./js/config.js',
'./js/about.js',
'./images/avatar.jpeg',
'./images/favicon.ico',
'./images/favicon.png',
'./images/logo.png',
'./images/INFJ.png'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
return cache.addAll(urlsToCache);
})
.catch(() => Promise.resolve())
);
self.skipWaiting();
});
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET' || !event.request.url.startsWith(self.location.origin)) {
return;
}
const url = new URL(event.request.url);
const isHTML = event.request.mode === 'navigate' || url.pathname.endsWith('.html');
const isVersioned = url.search && /version=/.test(url.search);
const isAudio = url.pathname.endsWith('.mp3') || url.pathname.endsWith('.wav') || url.pathname.endsWith('.ogg');
const isImage = /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(url.pathname);
const isStaticAsset = /\.(css|js|json)$/i.test(url.pathname);
if (isHTML || isVersioned || isAudio) {
event.respondWith(
fetch(event.request)
.then(resp => {
if (resp && resp.status === 200 && resp.type === 'basic' && !isAudio) {
const copy = resp.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, copy)).catch(() => {});
}
return resp;
})
.catch(() => {
return caches.match(event.request, { ignoreSearch: true })
.then(m => m || caches.match(event.request))
})
);
return;
}
event.respondWith(
caches.match(event.request, { ignoreSearch: true })
.then(cached => {
if (cached) return cached;
return fetch(event.request).then(resp => {
if (resp && resp.status === 200 && resp.type === 'basic' && (isImage || isStaticAsset)) {
const copy = resp.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, copy)).catch(() => {});
}
return resp;
});
})
);
});
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (!cacheWhitelist.includes(cacheName)) {
return caches.delete(cacheName);
}
})
);
})
);
self.clients.claim();
});

620
me.html
View File

@@ -1,620 +0,0 @@
<!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>