diff --git a/website/css/docs.css b/website/css/docs.css new file mode 100644 index 0000000..1d6501a --- /dev/null +++ b/website/css/docs.css @@ -0,0 +1,288 @@ +/* ===== 文档页面 ===== */ +.page-docs { + position: fixed; + inset: 0; + display: flex; + flex-direction: column; + background: var(--bg); + overflow: hidden; +} + +/* 顶部栏 */ +.docs-header { + height: 56px; + flex-shrink: 0; + display: flex; + align-items: center; + gap: 16px; + padding: 0 24px; + background: rgba(3, 7, 18, 0.95); + border-bottom: 1px solid var(--border); + backdrop-filter: blur(12px); + z-index: 100; +} + +.docs-header-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 16px; + font-weight: 700; + color: #fff; +} + +.docs-header-title svg { + color: var(--cyan); +} + +.docs-header-breadcrumb { + flex: 1; + font-size: 14px; + color: var(--text-muted); +} + +.docs-header-actions { + display: flex; + align-items: center; + gap: 12px; +} + +.docs-wiki-link { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + border: 1px solid var(--border); + border-radius: 8px; + background: transparent; + color: var(--text-secondary); + font-size: 13px; + text-decoration: none; + transition: all 0.2s; +} + +.docs-wiki-link:hover { + border-color: var(--border-hover); + color: var(--cyan-light); + background: rgba(6, 182, 212, 0.05); +} + +/* 布局 */ +.docs-layout { + flex: 1; + display: flex; + overflow: hidden; +} + +/* 左侧导航 */ +.docs-sidebar { + width: 260px; + flex-shrink: 0; + background: rgba(2, 5, 16, 0.6); + border-right: 1px solid var(--border); + overflow-y: auto; + padding: 20px 0; +} + +.docs-nav-section { + margin-bottom: 24px; +} + +.docs-nav-section-title { + padding: 8px 24px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + color: var(--text-muted); + letter-spacing: 1px; +} + +.docs-nav-item { + display: block; + padding: 8px 24px 8px 32px; + color: var(--text-secondary); + text-decoration: none; + font-size: 14px; + border-left: 3px solid transparent; + transition: all 0.15s; +} + +.docs-nav-item:hover { + background: rgba(255, 255, 255, 0.03); + color: #fff; +} + +.docs-nav-item.active { + border-left-color: var(--cyan); + color: var(--cyan-light); + background: rgba(6, 182, 212, 0.05); +} + +.docs-nav-item .nav-path { + display: block; + font-size: 11px; + color: var(--text-muted); + margin-top: 2px; + font-family: 'JetBrains Mono', monospace; +} + +/* 左下角固定按钮 */ +.docs-wiki-btn { + position: fixed; + bottom: 24px; + left: 24px; + display: flex; + align-items: center; + gap: 8px; + padding: 12px 20px; + background: linear-gradient(135deg, var(--cyan), var(--blue)); + border: none; + border-radius: 12px; + color: #fff; + font-size: 14px; + font-weight: 600; + text-decoration: none; + z-index: 200; + box-shadow: 0 4px 20px rgba(6, 182, 212, 0.3); + transition: all 0.2s; +} + +.docs-wiki-btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 30px rgba(6, 182, 212, 0.4); +} + +.docs-wiki-btn svg { + width: 16px; + height: 16px; +} + +/* 右侧内容区 */ +.docs-content-wrapper { + flex: 1; + overflow-y: auto; +} + +.docs-content { + max-width: 860px; + margin: 0 auto; + padding: 40px 48px 100px; +} + +.docs-content h1 { + font-size: 32px; + font-weight: 800; + color: #fff; + margin-bottom: 8px; + margin-top: 0; +} + +.docs-content h2 { + font-size: 22px; + font-weight: 700; + color: #fff; + margin-top: 32px; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid var(--border); +} + +.docs-content h3 { + font-size: 18px; + font-weight: 600; + color: var(--cyan-light); + margin-top: 24px; + margin-bottom: 8px; +} + +.docs-content p { + font-size: 15px; + color: var(--text-secondary); + line-height: 1.8; + margin-bottom: 12px; +} + +.docs-content strong { color: #fff; } + +.docs-content code { + font-family: 'JetBrains Mono', monospace; + font-size: 13px; + background: rgba(6, 182, 212, 0.1); + padding: 2px 8px; + border-radius: 4px; + color: var(--cyan-light); +} + +.docs-content pre { + margin: 16px 0; + padding: 16px 20px; + background: rgba(10, 15, 30, 0.8); + border: 1px solid var(--border); + border-radius: 10px; + overflow-x: auto; +} + +.docs-content pre code { + background: transparent; + padding: 0; + color: #e5e7eb; + font-size: 13px; + line-height: 1.7; +} + +.docs-content blockquote { + margin: 16px 0; + padding: 12px 20px; + border-left: 4px solid var(--cyan); + background: rgba(6, 182, 212, 0.05); + border-radius: 0 8px 8px 0; +} + +.docs-content blockquote p { margin-bottom: 0; } + +.docs-content ul, .docs-content ol { + margin: 12px 0; + padding-left: 24px; + color: var(--text-secondary); +} + +.docs-content li { + margin-bottom: 6px; + font-size: 15px; + line-height: 1.6; +} + +.docs-content hr { + border: none; + height: 1px; + background: var(--border); + margin: 24px 0; +} + +.docs-content table { + width: 100%; + border-collapse: collapse; + margin: 16px 0; + font-size: 14px; +} + +.docs-content th { + padding: 10px 16px; + text-align: left; + background: rgba(6, 182, 212, 0.05); + border-bottom: 2px solid rgba(6, 182, 212, 0.2); + color: #fff; + font-weight: 600; +} + +.docs-content td { + padding: 10px 16px; + border-bottom: 1px solid var(--border); + color: var(--text-secondary); +} + +@media (max-width: 768px) { + .docs-sidebar { display: none; } + .docs-content { padding: 24px 20px 100px; } + .docs-wiki-btn span { display: none; } +} diff --git a/website/css/hero.css b/website/css/hero.css index e15003e..2bab651 100644 --- a/website/css/hero.css +++ b/website/css/hero.css @@ -193,24 +193,100 @@ } } -.hero-logo { +/* ===== 3D 交互立方体 ===== */ +.cube-scene { position: relative; + width: 200px; + height: 200px; + perspective: 800px; z-index: 1; - width: 140px; - height: 140px; - filter: drop-shadow(0 0 30px rgba(6, 182, 212, 0.4)); - animation: logo-breathe 4s ease-in-out infinite; } -@keyframes logo-breathe { - 0%, 100% { - transform: scale(1); - filter: drop-shadow(0 0 30px rgba(6, 182, 212, 0.4)); - } - 50% { - transform: scale(1.05); - filter: drop-shadow(0 0 50px rgba(6, 182, 212, 0.6)); - } +.cube { + width: 100%; + height: 100%; + position: relative; + transform-style: preserve-3d; + cursor: pointer; +} + +.cube-face { + position: absolute; + width: 200px; + height: 200px; + background: #ffffff; + border: 2px solid rgba(6, 182, 212, 0.6); + box-shadow: + inset 0 0 30px rgba(0, 0, 0, 0.03), + 0 0 15px rgba(6, 182, 212, 0.3), + 0 0 30px rgba(6, 182, 212, 0.15); + user-select: none; +} + +/* 立方体六个面 */ +.cube-face-front { transform: rotateY(0deg) translateZ(100px); } +.cube-face-back { transform: rotateY(180deg) translateZ(100px); } +.cube-face-right { transform: rotateY(90deg) translateZ(100px); } +.cube-face-left { transform: rotateY(-90deg) translateZ(100px); } +.cube-face-top { transform: rotateX(90deg) translateZ(100px); } +.cube-face-bottom { transform: rotateX(-90deg) translateZ(100px); } + +/* 特性对话框 */ +.cube-tooltip { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) scale(0.8); + background: rgba(3, 7, 18, 0.95); + border: 1px solid rgba(6, 182, 212, 0.4); + border-radius: 12px; + padding: 16px 24px; + min-width: 220px; + max-width: 280px; + text-align: center; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease, transform 0.3s ease; + z-index: 10; + backdrop-filter: blur(10px); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 20px rgba(6, 182, 212, 0.15); +} + +.cube-tooltip.is-visible { + opacity: 1; + transform: translate(-50%, -50%) scale(1); +} + +.cube-tooltip-icon { + font-size: 28px; + margin-bottom: 8px; +} + +.cube-tooltip-title { + font-size: 15px; + font-weight: 700; + color: #fff; + margin-bottom: 4px; +} + +.cube-tooltip-desc { + font-size: 12px; + color: var(--text-muted); + line-height: 1.5; +} + +/* 小三角箭头 */ +.cube-tooltip::after { + content: ''; + position: absolute; + bottom: -8px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid rgba(6, 182, 212, 0.4); } /* 滚动指示器 */ @@ -277,10 +353,21 @@ width: 280px; height: 280px; } - .hero-logo { - width: 120px; - height: 120px; + .cube-scene { + width: 160px; + height: 160px; + margin: 0 auto; } + .cube-face { + width: 160px; + height: 160px; + } + .cube-face-front { transform: rotateY(0deg) translateZ(80px); } + .cube-face-back { transform: rotateY(180deg) translateZ(80px); } + .cube-face-right { transform: rotateY(90deg) translateZ(80px); } + .cube-face-left { transform: rotateY(-90deg) translateZ(80px); } + .cube-face-top { transform: rotateX(90deg) translateZ(80px); } + .cube-face-bottom { transform: rotateX(-90deg) translateZ(80px); } } @media (max-width: 640px) { @@ -289,8 +376,19 @@ width: 240px; height: 240px; } - .hero-logo { - width: 100px; - height: 100px; + .cube-scene { + width: 140px; + height: 140px; + margin: 0 auto; } + .cube-face { + width: 140px; + height: 140px; + } + .cube-face-front { transform: rotateY(0deg) translateZ(70px); } + .cube-face-back { transform: rotateY(180deg) translateZ(70px); } + .cube-face-right { transform: rotateY(90deg) translateZ(70px); } + .cube-face-left { transform: rotateY(-90deg) translateZ(70px); } + .cube-face-top { transform: rotateX(90deg) translateZ(70px); } + .cube-face-bottom { transform: rotateX(-90deg) translateZ(70px); } } diff --git a/website/docs/architecture.html b/website/docs/architecture.html new file mode 100644 index 0000000..51b9674 --- /dev/null +++ b/website/docs/architecture.html @@ -0,0 +1,105 @@ + + + + + + 架构设计 - Future OSS 文档 + + + + + + + + +
+ +
+
+
+ + + + 文档中心 +
+
入门 · 架构设计
+ +
+ +
+ + +
+
+

🏗️ 架构设计

+ +

三层架构

+

核心层 (oss/) — 插件管理、事件总线、消息总线、配置系统、日志系统。框架的核心基础设施。

+

插件层 (store/) — 协议插件(HTTP/WS/TCP)、工具插件(依赖解析、存储、桥接)、中间件插件(熔断、热重载、生命周期)。

+

应用层 (data/) — 第三方插件(HTML 渲染、Web 工具包)+ 运行时数据(配置文件、共享存储)。

+ +

启动流程

+
    +
  1. 加载 config.yaml → Config 对象
  2. +
  3. 初始化 Logger
  4. +
  5. 创建 PluginManager,只加载 plugin-loader
  6. +
  7. plugin-loader 扫描 store/ 目录,加载所有插件
  8. +
  9. 使用 dependency 插件进行拓扑排序
  10. +
  11. 按拓扑顺序 init() → start() 所有插件
  12. +
  13. HTTP 服务器启动,注册路由
  14. +
  15. 信号监听 → Ctrl+C 时优雅关闭(逆序 stop)
  16. +
+ +

目录结构

+
FutureOSS/
+├── oss/                    # 核心框架
+│   ├── cli.py              # CLI 入口
+│   ├── config/             # 配置加载
+│   ├── logger/             # 日志系统
+│   ├── plugin/             # 插件核心
+│   │   ├── types.py        # 类型定义
+│   │   ├── loader.py       # 动态加载器
+│   │   ├── manager.py      # 插件管理器
+│   │   └── event_bus.py    # 事件总线
+│   └── server/             # HTTP 服务器
+├── store/                  # 本地插件仓库
+│   └── @{FutureOSS}/       # 官方插件
+└── data/                   # 运行时数据
+
+
+
+
+ + + + + + 获取更多信息 + + + + + + + + + + diff --git a/website/docs/development.html b/website/docs/development.html new file mode 100644 index 0000000..2cbcf1d --- /dev/null +++ b/website/docs/development.html @@ -0,0 +1,124 @@ + + + + + + 插件开发指南 - Future OSS 文档 + + + + + + + + +
+ +
+
+
+ + + + 文档中心 +
+
插件 · 开发指南
+ +
+ +
+ + +
+
+

🛠️ 插件开发指南

+ +

插件结构

+
store/@{作者}/插件名/
+├── main.py          # 插件入口(必须实现 Plugin 接口)
+└── manifest.json    # 插件元数据(名称、版本、依赖)
+ +

插件接口

+
from oss.plugin.types import Plugin
+
+class MyPlugin(Plugin):
+    def init(self, deps: dict = None):
+        # 初始化逻辑
+        pass
+
+    def start(self):
+        # 启动逻辑
+        pass
+
+    def stop(self):
+        # 停止逻辑
+        pass
+
+    @property
+    def manifest(self):
+        return {
+            "name": "my-plugin",
+            "version": "1.0.0",
+            "dependencies": [],
+            "description": "我的插件"
+        }
+ +

关键原则

+
    +
  • 插件通过 from oss.plugin.types import Plugin 使用框架定义的接口
  • +
  • 插件的 main.pyimportlib 加载到框架的 Python 进程中
  • +
  • 插件可以直接 from oss.xxx import xxx 引用框架模块
  • +
  • 所有插件通过 config.json 配置,不修改源码
  • +
+ +

依赖声明

+
// manifest.json
+{
+    "name": "html-render",
+    "version": "1.0.0",
+    "dependencies": ["http-api", "plugin-storage"],
+    "description": "HTML 渲染插件"
+}
+ +

框架会自动调用 set_http_api()set_plugin_storage() 注入依赖实例。

+ +

安装格式

+

格式为 @{作者名称}/插件名称,例如 @{Falck}/html-render

+
+
+
+
+ + + + + + 获取更多信息 + + + + + + + + + + diff --git a/website/docs/index.html b/website/docs/index.html new file mode 100644 index 0000000..87672cd --- /dev/null +++ b/website/docs/index.html @@ -0,0 +1,108 @@ + + + + + + 什么是 FutureOSS - Future OSS 文档 + + + + + + + + +
+ +
+
+
+ + + + 文档中心 +
+
入门 · 项目介绍
+ +
+ +
+ + +
+
+

🧩 什么是 FutureOSS?

+ +

FutureOSS 是一个一切皆为插件的开发者工具运行时框架。框架本身是空壳,所有功能均以插件形式加载。

+ +

项目定位

+

协议、中间件、通知渠道……所有功能均以插件形式加载。内置熔断降级、依赖自动解析、事件驱动等企业级稳定性机制。

+ +

核心特性

+
    +
  • 一切皆插件 — 框架本身不提供任何业务功能,所有能力通过插件扩展
  • +
  • 热插拔 — 插件运行时加载与卸载,改完即生效,零编译
  • +
  • 依赖自动解析 — 拓扑排序 (Kahn 算法) + 循环依赖检测
  • +
  • 熔断与降级 — 自动熔断,支持 closed/open/half-open 状态切换
  • +
  • 包管理系统 — 一键安装/卸载/更新插件,支持 @{作者}/插件名 格式
  • +
  • 事件驱动 — 发布/订阅 + 通配符匹配 + RPC 桥接
  • +
  • 统一存储 — plugin-storage 为每个插件提供隔离的文件读写入口
  • +
+ +
+ +

关键原则

+ +

类型共享

+

框架在 oss/plugin/types.py 中定义所有数据类型和接口,插件通过 from oss.plugin.types import Plugin 直接使用。插件不应重复定义 Logger、EventBus 等类型。

+ +

配置驱动

+

所有插件通过 config.json 配置,不修改源码。配置文件使用相对路径,相对于 config.json 所在目录。

+ +

插件安装格式

+

格式为 @{作者名称}/插件名称,命令:oss pkg install @{Falck}/http-server

+
+
+
+
+ + + + + + 获取更多信息 + + + + + + + + + + + + + diff --git a/website/docs/plugins.html b/website/docs/plugins.html new file mode 100644 index 0000000..b27f2cd --- /dev/null +++ b/website/docs/plugins.html @@ -0,0 +1,90 @@ + + + + + + 官方插件列表 - Future OSS 文档 + + + + + + + + +
+ +
+
+
+ + + + 文档中心 +
+
插件 · 官方插件列表
+ +
+ +
+ + +
+
+

📦 官方插件列表

+ + + + + + + + + + + + + + + + + +
插件名说明依赖
plugin-loader核心插件,扫描/加载/管理所有插件
dependency拓扑排序 (Kahn 算法) + 循环依赖检测
http-apiHTTP 服务器 (8080) + 路由 + 中间件 + CORS
http-tcpHTTP TCP 服务器 (8082)
ws-apiWebSocket 服务器 (8081) + 事件总线
plugin-storage统一文件读写 + JSON 键值存储 + 目录隔离
plugin-bridge事件总线 + 广播 + RPC + 桥接plugin-storage
circuit-breaker熔断器 (closed/open/half-open)
hot-reload文件监听 + 热加载/卸载/更新插件
lifecycle生命周期状态机 (pending/running/stopped)
json-codecJSON 序列化/反序列化 + Schema 验证
pkg包管理 (搜索/安装/卸载/更新)
+
+
+
+
+ + + + + + 获取更多信息 + + + + + + + + + + diff --git a/website/docs/quickstart.html b/website/docs/quickstart.html new file mode 100644 index 0000000..41ef04f --- /dev/null +++ b/website/docs/quickstart.html @@ -0,0 +1,106 @@ + + + + + + 快速开始 - Future OSS 文档 + + + + + + + + +
+ +
+
+
+ + + + 文档中心 +
+
快速开始 · 三步运行
+ +
+ +
+ + +
+
+

🚀 三步运行

+ +

步骤 1:克隆代码

+
git clone https://gitee.com/starlight-apk/feature-oss.git
+cd feature-oss
+ +

步骤 2:安装依赖

+
pip install -r requirements.txt
+pip install -e .
+ +

步骤 3:启动服务

+
bash start.sh
+# 或 Windows: start.bat
+ +

启动后访问 http://localhost:8080/ 即可看到网站。

+ +

系统要求

+
    +
  • Python 3.8+
  • +
  • Linux / macOS / Windows
  • +
  • 核心依赖:click、pyyaml、websockets
  • +
+ +

常用命令

+ + + + + + + + + + +
命令说明
oss serve启动框架服务
oss init初始化项目配置
oss version查看版本号
oss status查看插件运行状态
oss pkg install @{作者}/插件名安装远程插件
oss pkg list列出已安装插件
+
+
+
+
+ + + + + + 获取更多信息 + + + + + + + + + + diff --git a/website/docs/why-python.html b/website/docs/why-python.html new file mode 100644 index 0000000..cce6a4e --- /dev/null +++ b/website/docs/why-python.html @@ -0,0 +1,98 @@ + + + + + + 为什么选择 Python - Future OSS 文档 + + + + + + + + +
+ +
+
+
+ + + + 文档中心 +
+
入门 · 为什么用Python
+ +
+ +
+ + +
+
+

🐍 为什么从 Go 重构为 Python?

+ +

Go 的 plugin 包不适合热插拔场景。.so 文件与主程序是独立编译单元,类型不共享,依赖注入后接口不匹配会导致 panic。Python 的动态特性天然适合插件化架构。

+ +

技术对比

+ + + + + + + + + + +
问题Go 方案Python 方案
插件热插拔plugin.Open(".so")importlib 动态加载 .py
依赖注入反射注入,类型不匹配 → panic直接传 dict/对象,无编译隔离
开发效率每次改插件需重新编译改完即生效,零编译
配置类型map[string]string 限制dict[str, Any] 原生支持任意类型
+ +

Go 插件系统的致命问题

+

Go 的插件机制依赖 plugin.Open() 加载编译好的 .so 文件。每个 .so 是独立的编译单元,无法共享类型定义。这意味着插件中定义的接口与框架中的接口即使签名相同,也被视为不同类型,导致依赖注入后接口为 nil,运行时直接 panic

+ +

Python 的优势

+
    +
  • 动态类型 — 没有编译时类型检查,插件直接使用框架定义的类型
  • +
  • importlib — 运行时动态加载 .py 文件,零编译开销
  • +
  • 类型共享 — 插件通过 from oss.plugin.types import Plugin 直接引用框架类型
  • +
  • 开发迭代快 — 改完代码即生效,无需重新编译
  • +
+
+
+
+
+ + + + + + 获取更多信息 + + + + + + + + + + diff --git a/website/index.html b/website/index.html index 2304a97..a9f2696 100644 --- a/website/index.html +++ b/website/index.html @@ -24,9 +24,9 @@
-
+
-
+
2026 · 插件驱动 · 一切皆可扩展 @@ -59,7 +59,21 @@
- +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -71,5 +85,6 @@ + diff --git a/website/js/cube.js b/website/js/cube.js new file mode 100644 index 0000000..004fa81 --- /dev/null +++ b/website/js/cube.js @@ -0,0 +1,180 @@ +/** + * 3D 交互立方体(requestAnimationFrame 驱动) + * - 纯白正方体 + 青绿色发光边框,自动旋转 + * - 鼠标悬停某面时平滑过渡到该面正对用户,弹出对话框 + * - 鼠标离开 2 秒后平滑恢复旋转 + */ +(function () { + 'use strict'; + + const cube = document.getElementById('feature-cube'); + const tooltip = document.getElementById('cube-tooltip'); + if (!cube || !tooltip) return; + + const tooltipIcon = tooltip.querySelector('.cube-tooltip-icon'); + const tooltipTitle = tooltip.querySelector('.cube-tooltip-title'); + const tooltipDesc = tooltip.querySelector('.cube-tooltip-desc'); + const faces = cube.querySelectorAll('.cube-face'); + + // 旋转状态 + let angleX = -20; + let angleY = 0; + let isSpinning = true; + let resumeTimer = null; + let currentFace = null; + + // 目标角度(悬停时设置) + let targetX = null; + let targetY = null; + + const SPEED_Y = 0.5; // Y 轴每帧旋转速度 + const SPEED_X = 0.15; // X 轴微动速度 + const LERP = 0.05; // 平滑插值系数 + + // 六个面的特性数据 + const faceData = { + front: { icon: '🧩', title: '一切皆为插件', desc: '协议、中间件、通知渠道,所有功能均以插件形式加载' }, + back: { icon: '🔄', title: '热插拔', desc: '插件运行时加载与卸载,改完即生效,零编译' }, + right: { icon: '🔗', title: '依赖自动解析', desc: '拓扑排序 + 循环依赖检测,自动处理加载顺序' }, + left: { icon: '🛡️', title: '熔断降级', desc: '内置熔断器,支持 closed/open/half-open 状态切换' }, + top: { icon: '📡', title: '事件驱动', desc: '发布/订阅 + 通配符匹配 + RPC 桥接' }, + bottom: { icon: '📦', title: '统一存储', desc: 'plugin-storage 提供隔离的文件读写入口' } + }; + + /** + * 角度差(处理 360° 环绕,返回最短方向) + */ + function angleDiff(from, to) { + let diff = ((to - from) % 360 + 540) % 360 - 180; + return diff; + } + + /** + * 动画循环 + */ + function animate() { + if (targetX !== null && targetY !== null) { + // 平滑过渡到目标角度 + const diffX = angleDiff(angleX, targetX); + const diffY = angleDiff(angleY, targetY); + + if (Math.abs(diffX) < 0.2 && Math.abs(diffY) < 0.2) { + angleX = targetX; + angleY = targetY; + // 到达目标后停止插值,保持静止 + targetX = null; + targetY = null; + } else { + angleX += diffX * LERP; + angleY += diffY * LERP; + } + } else if (isSpinning) { + // 自动旋转:Y 轴匀速 + X 轴微动 + angleY += SPEED_Y; + angleX = -20 + Math.sin(Date.now() * 0.001) * 8; + } + + cube.style.transform = `rotateX(${angleX}deg) rotateY(${angleY}deg)`; + requestAnimationFrame(animate); + } + + /** + * 计算某个面的目标角度(Y 轴自动选最近的对齐角度,避免跳帧) + */ + function calcFaceTarget(baseX, baseY) { + // Y 轴:找到距离当前 angleY 最近的对齐角度(baseY + n*360) + const n = Math.round((angleY - baseY) / 360); + const snappedY = baseY + n * 360; + return { x: baseX, y: snappedY }; + } + + /** + * 暂停旋转,显示对话框 + */ + function pauseAndShow(faceName) { + if (currentFace === faceName) return; + currentFace = faceName; + + // 清除恢复定时器 + if (resumeTimer) { + clearTimeout(resumeTimer); + resumeTimer = null; + } + + // 计算目标角度(Y 轴自动对齐最近角度,避免跳帧) + const faceBaseTargets = { + front: { x: -20, y: 0 }, + back: { x: -20, y: 180 }, + right: { x: -20, y: -90 }, + left: { x: -20, y: 90 }, + top: { x: -90, y: 0 }, + bottom: { x: 90, y: 0 } + }; + + const base = faceBaseTargets[faceName]; + if (base) { + const snapped = calcFaceTarget(base.x, base.y); + targetX = snapped.x; + targetY = snapped.y; + } + + isSpinning = false; + + // 填充并显示对话框 + const data = faceData[faceName]; + if (!data) return; + + tooltipIcon.textContent = data.icon; + tooltipTitle.textContent = data.title; + tooltipDesc.textContent = data.desc; + void tooltip.offsetWidth; + tooltip.classList.add('is-visible'); + } + + /** + * 隐藏对话框 + */ + function hideTooltip() { + currentFace = null; + tooltip.classList.remove('is-visible'); + } + + /** + * 恢复旋转 + */ + function resumeSpin() { + hideTooltip(); + targetX = null; + targetY = null; + isSpinning = true; + } + + // 为每个面绑定 mouseenter + faces.forEach(function (face) { + face.addEventListener('mouseenter', function () { + const faceName = face.getAttribute('data-face'); + pauseAndShow(faceName); + }); + + face.addEventListener('touchstart', function () { + const faceName = face.getAttribute('data-face'); + pauseAndShow(faceName); + }, { passive: true }); + }); + + // 整个场景 mouseleave → 2 秒后恢复(用 scene 而非 cube,避免子元素事件干扰) + const scene = cube.parentElement; + if (scene) { + scene.addEventListener('mouseleave', function () { + hideTooltip(); + if (resumeTimer) clearTimeout(resumeTimer); + resumeTimer = setTimeout(function () { + resumeSpin(); + }, 2000); + }); + } + + // 启动动画 + animate(); + +})(); diff --git a/website/js/dock.js b/website/js/dock.js index 96547b6..df2f3a0 100644 --- a/website/js/dock.js +++ b/website/js/dock.js @@ -6,16 +6,30 @@ (function () { 'use strict'; + // 获取当前页面相对于网站根目录的深度 + // 返回 '../' 或 '../../' 等,根目录页面返回 '' + function getPathPrefix() { + const path = window.location.pathname; + // 去掉文件名,得到目录部分 + const dir = path.substring(0, path.lastIndexOf('/') + 1); + // 计算目录层级数(排除开头的 /) + const segments = dir.split('/').filter(s => s.length > 0); + // 每个层级需要一个 '../' + return segments.map(() => '../').join(''); + } + // Dock HTML 模板 // current: 当前页面文件名,用于设置 active 状态 function renderDock(current) { + const prefix = getPathPrefix(); const items = [ - { href: 'index.html', tooltip: '首页', svg: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' }, - { href: 'features.html', tooltip: '特性', svg: 'M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z' }, - { href: 'plugins.html', tooltip: '插件', svg: 'M11 4a2 2 0 114 0v1a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-1a2 2 0 100 4h1a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-1a2 2 0 10-4 0v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-3a1 1 0 00-1-1H4a2 2 0 110-4h1a1 1 0 001-1V7a1 1 0 011-1h3a1 1 0 001-1V4z' }, - { href: 'architecture.html', tooltip: '架构', svg: 'M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z' }, + { href: prefix + 'index.html', tooltip: '首页', svg: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' }, + { href: prefix + 'features.html', tooltip: '特性', svg: 'M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z' }, + { href: prefix + 'docs/index.html', tooltip: '文档', svg: 'M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253' }, + { href: prefix + 'plugins.html', tooltip: '插件', svg: 'M11 4a2 2 0 114 0v1a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-1a2 2 0 100 4h1a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-1a2 2 0 10-4 0v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-3a1 1 0 00-1-1H4a2 2 0 110-4h1a1 1 0 001-1V7a1 1 0 011-1h3a1 1 0 001-1V4z' }, + { href: prefix + 'architecture.html', tooltip: '架构', svg: 'M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z' }, { separator: true }, - { href: 'quickstart.html', tooltip: '快速开始', svg: 'M13 10V3L4 14h7v7l9-11h-7z' }, + { href: prefix + 'quickstart.html', tooltip: '快速开始', svg: 'M13 10V3L4 14h7v7l9-11h-7z' }, { separator: true }, { href: 'https://gitee.com/starlight-apk/feature-oss', tooltip: '源码', svg: 'M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4', target: '_blank' } ]; diff --git a/website/js/parallax.js b/website/js/parallax.js index 2638c6f..b13d73f 100644 --- a/website/js/parallax.js +++ b/website/js/parallax.js @@ -24,6 +24,23 @@ class ParallaxTracker { } _init() { + // 自动检测文档页面 + const docsContent = document.querySelector('.docs-content'); + if (docsContent && !docsContent.hasAttribute('data-parallax-speed')) { + docsContent.setAttribute('data-parallax-speed', '1.0'); + } + + // 自动检测首页 + const heroContent = document.querySelector('.hero-content'); + if (heroContent && !heroContent.hasAttribute('data-parallax-speed')) { + heroContent.setAttribute('data-parallax-speed', '1.0'); + } + + const heroSection = document.querySelector('.page-hero'); + if (heroSection && !heroSection.hasAttribute('data-parallax-speed')) { + heroSection.setAttribute('data-parallax-speed', '1.0'); + } + // 查找视差元素 document.querySelectorAll('[data-parallax-speed]').forEach(el => { const speed = parseFloat(el.dataset.parallaxSpeed) || 0.1; diff --git a/website/logo.svg b/website/logo.svg deleted file mode 100644 index 1e94414..0000000 --- a/website/logo.svg +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -