Compare commits
2 Commits
main
...
version-2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d19837edc2 | ||
|
|
5c9e2c4186 |
@@ -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 支持,支持离线访问
|
|
||||||
|
|
||||||
## 🙏 鸣谢与致敬
|
## 🙏 鸣谢与致敬
|
||||||
|
|
||||||
|
|||||||
207
about.html
@@ -1,18 +1,19 @@
|
|||||||
<!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">
|
||||||
|
|
||||||
<!-- 社交平台分享优化 -->
|
<!-- 社交平台分享优化 -->
|
||||||
<!-- Open Graph / Facebook -->
|
<!-- Open Graph / Facebook -->
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
@@ -21,8 +22,7 @@
|
|||||||
<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">
|
||||||
<meta property="twitter:url" content="https://www.hehouhui.cn/about.html">
|
<meta property="twitter:url" content="https://www.hehouhui.cn/about.html">
|
||||||
@@ -30,26 +30,23 @@
|
|||||||
<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">
|
||||||
<meta property="wechat:title" content="关于我 - Honesty的个人主页">
|
<meta property="wechat:title" content="关于我 - Honesty的个人主页">
|
||||||
<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>
|
||||||
|
|
||||||
@@ -276,7 +264,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第三部分:最新文章 -->
|
<!-- 第三部分:最新文章 -->
|
||||||
<div class="content-section">
|
<div class="content-section">
|
||||||
<div class="content-col">
|
<div class="content-col">
|
||||||
@@ -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 © 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>© 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>
|
||||||
1497
christmas.html
4024
css/about.css
477
css/artalk.css
@@ -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);
|
||||||
@@ -24,7 +31,7 @@
|
|||||||
background: rgba(128, 128, 128, 0.08);
|
background: rgba(128, 128, 128, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
#artalk-container .atk-dialog,
|
#artalk-container .atk-dialog,
|
||||||
#artalk-container .atk-layer .atk-dialog {
|
#artalk-container .atk-layer .atk-dialog {
|
||||||
background: var(--glass-bg);
|
background: var(--glass-bg);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
@@ -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;
|
||||||
@@ -389,42 +526,42 @@
|
|||||||
.atk-header {
|
.atk-header {
|
||||||
padding: 15px !important;
|
padding: 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.atk-editor-plug-wrap {
|
.atk-editor-plug-wrap {
|
||||||
padding: 0 15px 15px !important;
|
padding: 0 15px 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.atk-editor-textarea {
|
.atk-editor-textarea {
|
||||||
padding: 12px !important;
|
padding: 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.atk-editor-bottom {
|
.atk-editor-bottom {
|
||||||
padding: 0 15px 15px !important;
|
padding: 0 15px 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.atk-list-header {
|
.atk-list-header {
|
||||||
padding: 15px !important;
|
padding: 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.atk-comment-wrap {
|
.atk-comment-wrap {
|
||||||
padding: 15px !important;
|
padding: 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.atk-pagination {
|
.atk-pagination {
|
||||||
padding: 15px !important;
|
padding: 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.atk-pagination .atk-page-item {
|
.atk-pagination .atk-page-item {
|
||||||
padding: 6px 12px !important;
|
padding: 6px 12px !important;
|
||||||
margin: 0 3px !important;
|
margin: 0 3px !important;
|
||||||
font-size: 0.9rem !important;
|
font-size: 0.9rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 移动端增强样式 */
|
/* 移动端增强样式 */
|
||||||
.atk-main-editor {
|
.atk-main-editor {
|
||||||
border-radius: 16px !important;
|
border-radius: 16px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.atk-comment-wrap {
|
.atk-comment-wrap {
|
||||||
padding: 12px 0 !important;
|
padding: 12px 0 !important;
|
||||||
font-size: 0.95rem !important;
|
font-size: 0.95rem !important;
|
||||||
@@ -436,30 +573,24 @@
|
|||||||
border-top: none !important;
|
border-top: none !important;
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.atk-avatar {
|
.atk-avatar {
|
||||||
width: 28px !important;
|
width: 28px !important;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 修复移动端输入框底色问题 */
|
/* 修复移动端输入框底色问题 */
|
||||||
[data-theme="night"] .atk-editor-textarea {
|
[data-theme="night"] .atk-editor-textarea {
|
||||||
background: rgba(40, 40, 45, 0.5) !important;
|
background: rgba(40, 40, 45, 0.5) !important;
|
||||||
color: #dfe6e9 !important;
|
color: #dfe6e9 !important;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08) !important;
|
border: 1px solid rgba(255, 255, 255, 0.08) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 完全移除评论区域的容器样式以节省宽度 */
|
/* 完全移除评论区域的容器样式以节省宽度 */
|
||||||
#artalk-container {
|
#artalk-container {
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
@@ -468,299 +599,13 @@
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 黑夜模式下移动端评论背景色优化 */
|
/* 黑夜模式下移动端评论背景色优化 */
|
||||||
[data-theme="night"] .atk-comment-wrap {
|
[data-theme="night"] .atk-comment-wrap {
|
||||||
background: rgba(40, 40, 45, 0.9) !important; /* 提高移动端黑夜模式下的背景不透明度 */
|
background: rgba(40, 40, 45, 0.9) !important; /* 提高移动端黑夜模式下的背景不透明度 */
|
||||||
}
|
}
|
||||||
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -10,10 +10,10 @@
|
|||||||
"blog": "https://www.hehouhui.cn",
|
"blog": "https://www.hehouhui.cn",
|
||||||
"hireable": true,
|
"hireable": true,
|
||||||
"bio": "Hi, I’m Honesty—Shanghai Java/AI Dev (7+ yrs). Spring AI, LLM, TensorFlow, Faiss. Write tech content, cycle for inspiration. AI-obsessed.",
|
"bio": "Hi, I’m 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,
|
||||||
"created_at": "2018-05-14T02:57:55Z",
|
"created_at": "2018-05-14T02:57:55Z",
|
||||||
"updated_at": "2025-11-09T05:45:15Z"
|
"updated_at": "2025-11-09T05:45:15Z"
|
||||||
}
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"rewrites": [
|
|
||||||
{
|
|
||||||
"source": "/.well-known/*.txt",
|
|
||||||
"destination": "/well-known/:splat.txt"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
BIN
images/INFJ.webp
|
Before Width: | Height: | Size: 17 KiB |
BIN
images/bj/1.webp
|
Before Width: | Height: | Size: 691 KiB |
BIN
images/bj/10.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
images/bj/2.webp
|
Before Width: | Height: | Size: 415 KiB |
BIN
images/bj/3.webp
|
Before Width: | Height: | Size: 488 KiB |
BIN
images/bj/4.webp
|
Before Width: | Height: | Size: 1.8 MiB |
BIN
images/bj/5.webp
|
Before Width: | Height: | Size: 168 KiB |
BIN
images/bj/6.webp
|
Before Width: | Height: | Size: 1.2 MiB |
BIN
images/bj/7.webp
|
Before Width: | Height: | Size: 786 KiB |
BIN
images/bj/8.webp
|
Before Width: | Height: | Size: 1.8 MiB |
BIN
images/bj/9.jpg
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 27 KiB |
61
index.html
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}();
|
||||||
677
js/about.js
@@ -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",
|
||||||
@@ -255,10 +239,10 @@ class DataManager {
|
|||||||
// 创建带超时的fetch函数
|
// 创建带超时的fetch函数
|
||||||
async fetchWithTimeout(url, options = {}) {
|
async fetchWithTimeout(url, options = {}) {
|
||||||
const { timeout = 5000 } = options;
|
const { timeout = 5000 } = options;
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const id = setTimeout(() => controller.abort(), timeout);
|
const id = setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
...options,
|
...options,
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
@@ -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);
|
||||||
@@ -399,7 +363,7 @@ class DataManager {
|
|||||||
<div class="repo-desc">${escapeHtml(dShort)}</div>
|
<div class="repo-desc">${escapeHtml(dShort)}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 使用requestAnimationFrame避免强制重排
|
// 使用requestAnimationFrame避免强制重排
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const pc = $('#projects-container');
|
const pc = $('#projects-container');
|
||||||
@@ -516,7 +480,7 @@ class DataManager {
|
|||||||
<div class="b-cat">${escapeHtml(cat)}</div>
|
<div class="b-cat">${escapeHtml(cat)}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 使用requestAnimationFrame避免强制重排
|
// 使用requestAnimationFrame避免强制重排
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const bc = $('#blog-container');
|
const bc = $('#blog-container');
|
||||||
@@ -596,34 +560,37 @@ 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',
|
||||||
|
|
||||||
// 主题支持
|
// 主题支持
|
||||||
darkMode: document.documentElement.getAttribute('data-theme') === 'night',
|
darkMode: document.documentElement.getAttribute('data-theme') === 'night',
|
||||||
|
|
||||||
// 编辑器增强配置
|
// 编辑器增强配置
|
||||||
editor: {
|
editor: {
|
||||||
// 启用 Markdown
|
// 启用 Markdown
|
||||||
markdown: true,
|
markdown: true,
|
||||||
|
|
||||||
emoticons: "https://emoticons.hzchu.top/json/artalk/zaoandandandeyouyongquan.json",
|
// 表情面板
|
||||||
|
emoji: {
|
||||||
|
// 使用默认表情包
|
||||||
|
preset: 'twemoji'
|
||||||
|
},
|
||||||
|
|
||||||
// 启用 @ 用户提醒功能
|
// 启用 @ 用户提醒功能
|
||||||
mention: true,
|
mention: true,
|
||||||
|
|
||||||
// 自动聚焦(仅桌面端)
|
// 自动聚焦(仅桌面端)
|
||||||
autoFocus: !('ontouchstart' in window),
|
autoFocus: !('ontouchstart' in window),
|
||||||
|
|
||||||
// 限制编辑器高度
|
// 限制编辑器高度
|
||||||
maxHeight: 200,
|
maxHeight: 200,
|
||||||
|
|
||||||
// 工具栏
|
// 工具栏
|
||||||
toolbar: [
|
toolbar: [
|
||||||
'bold', 'italic', 'strike', 'link',
|
'bold', 'italic', 'strike', 'link',
|
||||||
@@ -632,7 +599,7 @@ class UIManager {
|
|||||||
'emoji', 'mention'
|
'emoji', 'mention'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// 评论格式化函数
|
// 评论格式化函数
|
||||||
commentFormatter: (comment) => {
|
commentFormatter: (comment) => {
|
||||||
// 美化时间显示
|
// 美化时间显示
|
||||||
@@ -640,22 +607,22 @@ class UIManager {
|
|||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const diffSec = Math.floor((now - date) / 1000);
|
const diffSec = Math.floor((now - date) / 1000);
|
||||||
|
|
||||||
if (diffSec < 60) return isZh ? '刚刚' : 'Just now';
|
if (diffSec < 60) return isZh ? '刚刚' : 'Just now';
|
||||||
if (diffSec < 3600) return isZh ? `${Math.floor(diffSec / 60)}分钟前` : `${Math.floor(diffSec / 60)} minutes ago`;
|
if (diffSec < 3600) return isZh ? `${Math.floor(diffSec / 60)}分钟前` : `${Math.floor(diffSec / 60)} minutes ago`;
|
||||||
if (diffSec < 86400) return isZh ? `${Math.floor(diffSec / 3600)}小时前` : `${Math.floor(diffSec / 3600)} hours ago`;
|
if (diffSec < 86400) return isZh ? `${Math.floor(diffSec / 3600)}小时前` : `${Math.floor(diffSec / 3600)} hours ago`;
|
||||||
|
|
||||||
const isToday = date.toDateString() === now.toDateString();
|
const isToday = date.toDateString() === now.toDateString();
|
||||||
if (isToday) return isZh ?
|
if (isToday) return isZh ?
|
||||||
`今天 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` :
|
`今天 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` :
|
||||||
`Today ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
`Today ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
const isYesterday = new Date(now - 86400000).toDateString() === date.toDateString();
|
const isYesterday = new Date(now - 86400000).toDateString() === date.toDateString();
|
||||||
if (isYesterday) return isZh ?
|
if (isYesterday) return isZh ?
|
||||||
`昨天 ${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',
|
||||||
@@ -663,21 +630,21 @@ class UIManager {
|
|||||||
minute: '2-digit'
|
minute: '2-digit'
|
||||||
}).replace(/\//g, '-');
|
}).replace(/\//g, '-');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果是管理员,添加徽章
|
// 如果是管理员,添加徽章
|
||||||
if (comment.is_admin) {
|
if (comment.is_admin) {
|
||||||
comment.nick = `👑${comment.nick}`;
|
comment.nick = `👑${comment.nick}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新显示时间
|
// 更新显示时间
|
||||||
comment.create_date_formatted = formatTime(comment.date || comment.created_at || comment.create_date);
|
comment.create_date_formatted = formatTime(comment.date || comment.created_at || comment.create_date);
|
||||||
|
|
||||||
return comment;
|
return comment;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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';
|
||||||
@@ -700,29 +667,29 @@ class UIManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空容器
|
// 清空容器
|
||||||
const container = document.getElementById('artalk-container');
|
const container = document.getElementById('artalk-container');
|
||||||
if (container) {
|
if (container) {
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新初始化
|
// 重新初始化
|
||||||
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;
|
||||||
|
|
||||||
// 检测是否为移动端
|
// 检测是否为移动端
|
||||||
const isMobile = window.matchMedia('(max-width: 768px)').matches;
|
const isMobile = window.matchMedia('(max-width: 768px)').matches;
|
||||||
container.classList.toggle('atk-mobile', isMobile);
|
container.classList.toggle('atk-mobile', isMobile);
|
||||||
container.classList.toggle('atk-desktop', !isMobile);
|
container.classList.toggle('atk-desktop', !isMobile);
|
||||||
|
|
||||||
// 获取当前语言
|
// 获取当前语言
|
||||||
const lang = getStoredLanguage();
|
const lang = getStoredLanguage();
|
||||||
|
|
||||||
// 获取当前主题
|
// 获取当前主题
|
||||||
const currentTheme = document.documentElement.getAttribute('data-theme');
|
const currentTheme = document.documentElement.getAttribute('data-theme');
|
||||||
|
|
||||||
@@ -730,33 +697,31 @@ class UIManager {
|
|||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
this.enhanceMobileArtalk(container, lang);
|
this.enhanceMobileArtalk(container, lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听主题/语言变化
|
// 监听主题/语言变化
|
||||||
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']
|
||||||
});
|
});
|
||||||
@@ -769,32 +734,32 @@ class UIManager {
|
|||||||
container.querySelectorAll('.atk-comment-wrap .atk-content').forEach(el => {
|
container.querySelectorAll('.atk-comment-wrap .atk-content').forEach(el => {
|
||||||
// 检查是否已经处理过
|
// 检查是否已经处理过
|
||||||
if (el.dataset.mobileProcessed) return;
|
if (el.dataset.mobileProcessed) return;
|
||||||
|
|
||||||
// 检查内容是否超过3行才添加展开收起功能
|
// 检查内容是否超过3行才添加展开收起功能
|
||||||
const lineHeight = parseInt(window.getComputedStyle(el).lineHeight);
|
const lineHeight = parseInt(window.getComputedStyle(el).lineHeight);
|
||||||
const paddingTop = parseInt(window.getComputedStyle(el).paddingTop);
|
const paddingTop = parseInt(window.getComputedStyle(el).paddingTop);
|
||||||
const paddingBottom = parseInt(window.getComputedStyle(el).paddingBottom);
|
const paddingBottom = parseInt(window.getComputedStyle(el).paddingBottom);
|
||||||
const actualHeight = el.clientHeight - paddingTop - paddingBottom;
|
const actualHeight = el.clientHeight - paddingTop - paddingBottom;
|
||||||
|
|
||||||
// 如果内容高度超过3倍行高,则添加展开收起功能
|
// 如果内容高度超过3倍行高,则添加展开收起功能
|
||||||
if (actualHeight > lineHeight * 3) {
|
if (actualHeight > lineHeight * 3) {
|
||||||
// 添加移动端内容截断
|
// 添加移动端内容截断
|
||||||
el.classList.add('clamped');
|
el.classList.add('clamped');
|
||||||
el.style.setProperty('--max-lines', '3');
|
el.style.setProperty('--max-lines', '3');
|
||||||
|
|
||||||
// 创建展开/收起按钮
|
// 创建展开/收起按钮
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.className = 'atk-expand-btn';
|
btn.className = 'atk-expand-btn';
|
||||||
const expandText = lang === 'zh' ? '展开' : 'Expand';
|
const expandText = lang === 'zh' ? '展开' : 'Expand';
|
||||||
const collapseText = lang === 'zh' ? '收起' : 'Collapse';
|
const collapseText = lang === 'zh' ? '收起' : 'Collapse';
|
||||||
btn.textContent = expandText;
|
btn.textContent = expandText;
|
||||||
|
|
||||||
btn.addEventListener('click', (e) => {
|
btn.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const isClamped = el.classList.toggle('clamped');
|
const isClamped = el.classList.toggle('clamped');
|
||||||
btn.textContent = isClamped ? expandText : collapseText;
|
btn.textContent = isClamped ? expandText : collapseText;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 将按钮插入到适当位置
|
// 将按钮插入到适当位置
|
||||||
const actionsElement = el.closest('.atk-comment').querySelector('.atk-actions');
|
const actionsElement = el.closest('.atk-comment').querySelector('.atk-actions');
|
||||||
if (actionsElement) {
|
if (actionsElement) {
|
||||||
@@ -803,23 +768,23 @@ class UIManager {
|
|||||||
el.parentNode.appendChild(btn);
|
el.parentNode.appendChild(btn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标记为已处理
|
// 标记为已处理
|
||||||
el.dataset.mobileProcessed = '1';
|
el.dataset.mobileProcessed = '1';
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始应用
|
// 初始应用
|
||||||
applyMobileStyles();
|
applyMobileStyles();
|
||||||
|
|
||||||
// 创建观察器以处理动态添加的评论
|
// 创建观察器以处理动态添加的评论
|
||||||
const observer = new MutationObserver(applyMobileStyles);
|
const observer = new MutationObserver(applyMobileStyles);
|
||||||
observer.observe(container, {
|
observer.observe(container, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
attributes: false
|
attributes: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加移动端特定的样式类
|
// 添加移动端特定的样式类
|
||||||
const theme = document.documentElement.getAttribute('data-theme');
|
const theme = document.documentElement.getAttribute('data-theme');
|
||||||
container.classList.add(`atk-theme-${theme || 'day'}`);
|
container.classList.add(`atk-theme-${theme || 'day'}`);
|
||||||
@@ -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);
|
|
||||||
}
|
// 使用防抖优化尺寸计算
|
||||||
|
let resizeTimeout;
|
||||||
// 保存当前状态
|
const updateContainerSize = () => {
|
||||||
this.saveTechCloudState(container, type);
|
if (resizeTimeout) {
|
||||||
}
|
clearTimeout(resizeTimeout);
|
||||||
|
}
|
||||||
// 初始化3D球体动画
|
resizeTimeout = setTimeout(() => {
|
||||||
init3DSphereAnimation(container, techStack) {
|
init3DSphere();
|
||||||
// 清除之前的动画
|
}, 100);
|
||||||
if (container.__animToken) {
|
};
|
||||||
cancelAnimationFrame(container.__animToken);
|
|
||||||
}
|
// 初始化3D球体
|
||||||
|
const init3DSphere = () => {
|
||||||
// 清空容器
|
// 清除之前的动画
|
||||||
container.innerHTML = '';
|
if (container.__animToken) {
|
||||||
|
cancelAnimationFrame(container.__animToken);
|
||||||
const tags = [];
|
}
|
||||||
|
|
||||||
techStack.forEach((item, index) => {
|
// 清空容器
|
||||||
const el = document.createElement('a');
|
container.innerHTML = '';
|
||||||
el.className = 'tech-tag-3d';
|
|
||||||
const colorClass = `tag-color-${item.gradientId || ((index % 25) + 1)}`;
|
const tags = [];
|
||||||
el.classList.add(colorClass);
|
|
||||||
el.innerText = item.name;
|
techStack.forEach((item, index) => {
|
||||||
el.style.border = 'none';
|
const el = document.createElement('a');
|
||||||
container.appendChild(el);
|
el.className = 'tech-tag-3d';
|
||||||
tags.push({el, x: 0, y: 0, z: 0});
|
const colorClass = `tag-color-${item.gradientId || ((index % 10) + 1)}`;
|
||||||
});
|
el.classList.add(colorClass);
|
||||||
|
el.innerText = item.name;
|
||||||
// 动态半径,避免容器溢出,使用防抖优化
|
el.style.border = 'none';
|
||||||
let radius = Math.max(160, Math.min(container.offsetWidth, container.offsetHeight) / 2 - 24);
|
container.appendChild(el);
|
||||||
const dtr = Math.PI / 180;
|
tags.push({el, x: 0, y: 0, z: 0, bw: el.offsetWidth, bh: el.offsetHeight});
|
||||||
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
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
// 动态半径,避免容器溢出,使用防抖优化
|
||||||
|
const cw = container.clientWidth;
|
||||||
|
const ch = container.clientHeight;
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 初始化位置
|
||||||
|
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;
|
||||||
|
|
||||||
updates.forEach(update => {
|
let scale = (tag.z + radius) / (2 * radius) + 0.45;
|
||||||
update.el.style.transform = update.transform;
|
scale = Math.min(Math.max(scale, 0.7), 1.15);
|
||||||
update.el.style.opacity = update.opacity;
|
const opacity = (tag.z + radius) / (2 * radius) + 0.2;
|
||||||
update.el.style.zIndex = update.zIndex;
|
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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
container.__animToken = requestAnimationFrame(update);
|
updates.forEach(update => {
|
||||||
};
|
update.el.style.transform = update.transform;
|
||||||
|
update.el.style.opacity = update.opacity;
|
||||||
container.__animToken = requestAnimationFrame(update);
|
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');
|
||||||
@@ -1071,8 +970,10 @@ class UIManager {
|
|||||||
const fTheme = document.getElementById('fab-theme');
|
const fTheme = document.getElementById('fab-theme');
|
||||||
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避免强制重排
|
||||||
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,50 +1095,23 @@ 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) {
|
||||||
// 确保容器具有正确的主题类
|
// 确保容器具有正确的主题类
|
||||||
container.classList.remove('atk-theme-day', 'atk-theme-night');
|
container.classList.remove('atk-theme-day', 'atk-theme-night');
|
||||||
container.classList.add(`atk-theme-${theme || 'day'}`);
|
container.classList.add(`atk-theme-${theme || 'day'}`);
|
||||||
|
|
||||||
// 更新自定义元素的主题样式
|
// 更新自定义元素的主题样式
|
||||||
const customElements = container.querySelectorAll('.atk-expand-btn, .atk-pagination .atk-page-item');
|
const customElements = container.querySelectorAll('.atk-expand-btn, .atk-pagination .atk-page-item');
|
||||||
customElements.forEach(el => {
|
customElements.forEach(el => {
|
||||||
|
|||||||
367
js/config.js
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
@@ -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); // 等待动画结束后隐藏
|
||||||
|
});
|
||||||
|
});
|
||||||
87
js/sw.js
@@ -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
@@ -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>
|
|
||||||