diff --git a/.gitignore b/.gitignore index 62c8935..637d92c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.idea/ \ No newline at end of file +.idea/ +node_modules/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8e2d756 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,103 @@ +# CLAUDE.md - Document WordPress Theme + +## 项目概述 + +这是一个 WordPress 博客主题(Document),基于文档类型设计,方便记录和查询学习笔记。 + +- **类型**: WordPress 主题 +- **版本**: 1.5.1 +- **版本号位置**: `style.css` (第8行) 和 `include/config.php` (`DOCUMENT_VERSION` 常量),两处必须同步修改 + +## 项目结构 + +``` +├── style.css / style.scss # 主样式(WordPress 主题入口) +├── functions.php # 主题初始化,加载所有 include +├── header.php / footer.php # 全局头尾模板 +├── index.php / single.php / page.php / 404.php # 页面模板 +├── include/ +│ ├── config.php # 主题版本号、后台设置表单定义、所有配置项默认值 +│ ├── themes/ +│ │ ├── load.php # ★ 前端资源加载(JS/CSS enqueue)核心文件 +│ │ ├── theme.php # 主题钩子入口,include 所有子模块 +│ │ ├── shortcode.php # 短标签 +│ │ └── ... +│ ├── admin/ # 后台相关(设置页面、编辑器插件) +│ ├── functions/ # 通用函数、SMTP、初始化 +│ ├── widget/ # 侧边栏小部件 +│ ├── todo/ clipboard/ snake/ doom/ # 各功能模块的数据库和API +│ └── ... +├── common/ +│ ├── main.js # 前端主逻辑 +│ ├── inline/ # 各页面内联脚本(emoji, index, monitor, view 等) +│ ├── prism/ # 代码高亮(Prism.js) +│ ├── viewer/ # 图片灯箱(Viewer.js) +│ ├── plugins/ # TinyMCE 编辑器插件 +│ ├── todo/ clipboard/ snake/ doom/ friend/ swiper/ widget/ # 各功能模块前端 +│ └── admin/ # 后台前端(Vue + Ant Design) +├── assets/theme/ # 第三方库(jQuery, Swiper, Vue, Ant Design, Moment, Axios) +├── template/ # 模板片段(文章列表、分页、页面模板等) +├── build.js # ★ 前端构建脚本(esbuild 压缩) +└── package.json # Node.js 依赖(仅 esbuild) +``` + +## 构建流程 + +### 重要:修改 JS 或 CSS 后必须重新构建 + +本主题使用 esbuild 将所有前端 JS/CSS 压缩为 `.min.js` / `.min.css` 文件。WordPress 生产环境加载的是压缩版。 + +```bash +# 首次使用需安装依赖 +npm install + +# 构建(压缩所有 JS/CSS,约1秒完成) +node build.js +``` + +### 构建机制说明 + +- `build.js` 读取所有前端 JS/CSS 源文件,用 esbuild 压缩后输出为同目录下的 `.min.js` / `.min.css` +- `include/themes/load.php` 中的 `nicen_theme_min_path()` 函数自动检测 `.min` 文件是否存在: + - 存在 → 加载压缩版(生产环境) + - 不存在 → 回退到原版(开发调试) +- **只修改源文件**(如 `common/main.js`),**不要手动编辑** `.min.js` 文件 + +### 什么时候需要构建 + +- 修改了 `common/` 下的任何 `.js` 文件 → 需要 `node build.js` +- 修改了 `style.css` → 需要 `node build.js` +- 修改了 `.php` 文件 → **不需要**构建 +- 新增了 JS/CSS 文件 → 需要在 `build.js` 的文件列表中添加该文件,然后构建 + +## 开发规范 + +### 文件修改后的完整流程 + +1. 修改源文件(`.js` / `.css` / `.php`) +2. 如果改了 JS 或 CSS:运行 `node build.js` +3. 确认 `.min` 文件已更新 +4. 提交所有文件(包括源文件和 `.min` 文件) + +### 版本号更新 + +需要同时修改两处: +- `style.css` 头部注释中的 `Version:` 字段 +- `include/config.php` 中的 `DOCUMENT_VERSION` 常量 + +### 新增页面模板 + +在 `include/config.php` 的 `PAGES` 常量中添加配置,包括模板文件路径和依赖的样式/脚本。 + +### 资源加载 + +- 前端资源加载集中在 `include/themes/load.php` +- 后台资源加载在 `include/admin/load.php` +- 页面模板的资源通过 `PAGES` 配置自动加载 + +### 注意事项 + +- jQuery 是全局依赖,`window.$ = jQuery` 已在 inline script 中设置 +- 后台使用 Vue 2 + Ant Design,前端不使用 +- 主题配置通过 `nicen_theme_config('key', false)` 读取 +- 主题色等样式变量通过 CSS 变量注入(`include/themes/load.php` 底部的 inline style) diff --git a/DOOM_DESIGN.md b/DOOM_DESIGN.md new file mode 100644 index 0000000..7767a9b --- /dev/null +++ b/DOOM_DESIGN.md @@ -0,0 +1,201 @@ +# Doom-Style FPS Game (Raycasting) + +## Context +Add a Doom/Wolfenstein-style first-person shooter to the blog as a "My Corner" game. Built entirely with Canvas 2D raycasting — no WebGL, no external libraries. One map, 3 weapons, level-based (find exit to win). Flat color walls with distance shading. Leaderboard reuses the same pattern as Snake Game. + +## Game Design + +### Gameplay +- **Objective**: Navigate a maze, kill all enemies, find the exit door +- **Perspective**: First-person, Wolfenstein 3D / Doom style +- **Map**: 1 hand-crafted map (grid-based, ~24x24), walls are solid colors with distance-based shading +- **Win condition**: Reach exit door (all enemies killed or exit found) +- **Score**: Kills × 100 + health remaining × 10 + time bonus (faster = more points) + +### Weapons +| # | Weapon | Damage | Fire Rate | Ammo | Notes | +|---|--------|--------|-----------|------|-------| +| 1 | Pistol | 25 | Medium | Unlimited | Starting weapon | +| 2 | Shotgun | 80 (spread) | Slow | 20 | Pickup in map, wide spread | +| 3 | Machine Gun | 15 | Fast | 100 | Pickup in map, rapid fire | + +Weapon switching: keys 1/2/3 or scroll wheel. Weapon sprite drawn at bottom-center of screen (simple geometric shapes, not image assets). + +### Enemies +- **Type 1: Guard** — walks a patrol route, detects player by line-of-sight, shoots when in range (hitscan), low HP (50) +- **Type 2: Soldier** — faster, more HP (100), chases aggressively once alerted +- Enemies rendered as colored billboard sprites (circles/rectangles, not images), scaled by distance +- Basic state machine: IDLE → ALERT (heard gunshot / saw player) → CHASE → ATTACK → DEAD +- Collision: enemies block player movement + +### Player +- HP: 100, displayed as health bar in HUD +- Movement: WASD (forward/back/strafe), mouse or arrow keys to rotate +- Interaction: approach exit door to trigger win + +### HUD +- Health bar (bottom-left) +- Ammo count (bottom-right) +- Current weapon indicator +- Minimap (top-right corner, toggleable with M key) — shows walls, player position/direction, enemy dots +- Crosshair at screen center +- Kill count + +## Technical Architecture + +### Raycasting Engine +- Cast one ray per screen column (e.g., 640 rays for 640px wide canvas) +- DDA algorithm (Digital Differential Analyzer) for fast grid-based wall intersection +- Wall height = `screenHeight / perpDistance` (fish-eye corrected) +- Wall color based on wall type value in map grid, darkened by distance (`color * (1 - distance/maxDist)`) +- North/South walls slightly darker than East/West for depth perception +- Floor/ceiling: solid colors (no texture mapping needed) + +### Sprite Rendering +- After walls are drawn, render sprites (enemies, pickups, exit door) +- Sort sprites by distance (far to near), draw back-to-front +- Each column checks against the wall depth buffer — only draw sprite pixels closer than the wall +- Sprites scale with distance: `spriteHeight = screenHeight / distance` + +### Map Format +```javascript +// 0 = empty, 1-4 = wall types (different colors), 9 = exit door +const MAP = [ + [1,1,1,1,1,1,...], + [1,0,0,0,0,1,...], + [1,0,2,0,0,1,...], + ... +]; +// Separate arrays for entity placement +const ENTITIES = [ + { type: 'guard', x: 3.5, y: 5.5 }, + { type: 'soldier', x: 10.5, y: 8.5 }, + { type: 'shotgun', x: 7.5, y: 3.5 }, + { type: 'machinegun', x: 15.5, y: 12.5 }, + { type: 'health', x: 12.5, y: 6.5 }, +]; +``` + +### Game Loop +``` +requestAnimationFrame loop: + 1. Process input (movement, rotation, shooting) + 2. Update entities (enemy AI, projectiles, animations) + 3. Cast rays → build wall depth buffer + 4. Draw ceiling + floor + 5. Draw walls (column by column) + 6. Draw sprites (sorted by distance) + 7. Draw weapon sprite (bottom of screen) + 8. Draw HUD (health, ammo, minimap, crosshair) + 9. Check win/lose conditions +``` + +### Controls +| Input | Action | +|-------|--------| +| W / ArrowUp | Move forward | +| S / ArrowDown | Move backward | +| A | Strafe left | +| D | Strafe right | +| ArrowLeft | Rotate left | +| ArrowRight | Rotate right | +| Mouse move (pointer lock) | Rotate view | +| Left click / Space | Shoot | +| 1, 2, 3 | Switch weapon | +| Scroll wheel | Cycle weapon | +| M | Toggle minimap | + +Mobile: virtual joystick (left side) + virtual buttons (right side: shoot, weapon switch). + +## Files to Create + +### Frontend +1. **`common/doom/doom.js`** (~800-1200 lines) — Complete game engine: + - Raycasting renderer + - Player movement + collision + - Enemy AI + sprite rendering + - Weapon system + - HUD rendering + - Map data + - Game state management (MENU → PLAYING → WIN → GAMEOVER) + - Score submission + leaderboard display + - Built-in md5 for anti-cheat token + +2. **`common/doom/doom.css`** — Page layout, canvas styling, leaderboard panel, responsive + +### Page Template +3. **`template/page/doom.php`** — WordPress page template "Doom FPS" + - Canvas element (640×480 or responsive) + - Start overlay (enter name + start) + - Win/GameOver overlay (score, rank, play again) + - Leaderboard panel (reuse snake's layout pattern) + +### Backend +4. **`include/doom/install.php`** — DB table `wp_document_doom_scores` + ```sql + id BIGINT AUTO_INCREMENT PRIMARY KEY + player_name VARCHAR(50) NOT NULL + score INT NOT NULL DEFAULT 0 + kills INT NOT NULL DEFAULT 0 + duration INT NOT NULL DEFAULT 0 + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + INDEX idx_score (score DESC) + INDEX idx_created (created_at) + ``` + +5. **`include/doom/api.php`** — AJAX endpoints + - `doom_submit` — submit score with anti-cheat token, rate limit + - `doom_leaderboard` — alltime (top 20) / weekly (top 10) + - Cleanup: top 20 retained, 3-week expiry (same as snake) + +## Files to Modify + +1. **`include/config.php`** — Add "Doom FPS" to PAGES array: + ```php + 'Doom FPS' => [ + 'template' => 'template/page/doom.php', + 'dependent' => [ + 'styles' => ['/common/doom/doom.css'], + 'scripts' => ['/common/doom/doom.js'], + ] + ] + ``` + +2. **`include/themes/theme.php`** — Add includes: + ```php + include_once $root . '/include/doom/install.php'; + include_once $root . '/include/doom/api.php'; + ``` + +3. **`include/themes/load.php`** — Add inline var injection for Doom: + ```php + if ( $key === 'Doom FPS' ) { + $doom_salt = md5('doom_' . date('Y-m-d') . '_' . wp_salt('auth')); + wp_add_inline_script($key, 'window.DOOM_DAY_SALT="'.$doom_salt.'";window.DOOM_AJAX="'.admin_url('admin-ajax.php').'";', 'before'); + } + ``` + +4. **`README.md`** — Add Doom FPS entry to changelog + +## Implementation Order + +1. **Raycasting engine** — walls rendering, player movement, collision detection +2. **Map design** — create the 24x24 map layout +3. **Weapon system** — 3 weapons, shooting, ammo +4. **Enemy system** — sprites, AI state machine, combat +5. **HUD** — health, ammo, minimap, crosshair +6. **Game states** — menu, playing, win, gameover overlays +7. **Backend** — DB, API, leaderboard (copy-paste from snake, adjust fields) +8. **Integration** — config.php, theme.php, load.php, template +9. **Mobile controls** — virtual joystick + buttons +10. **Polish** — sound effects (optional), README update + +## Verification +1. Create WP page with "Doom FPS" template → visit as guest → game loads +2. Enter name → start → WASD movement works, walls render correctly +3. Find and pick up weapons → ammo shows in HUD +4. Enemies patrol, detect player, combat works +5. Kill enemies → find exit → win screen → score submitted → appears in leaderboard +6. Test alltime/weekly leaderboard tabs +7. Test mouse pointer lock for smooth rotation +8. Verify on mobile with virtual controls diff --git a/FIX-JQUERY-CONFLICT.md b/FIX-JQUERY-CONFLICT.md new file mode 100644 index 0000000..0c0550c --- /dev/null +++ b/FIX-JQUERY-CONFLICT.md @@ -0,0 +1,154 @@ +# jQuery 冲突修复指南 + +## 问题描述 + +你的 WordPress 主题同时加载了: +- WordPress 自带的 jQuery 3.7.1(正确) +- 某个插件/主题引入的 jQuery 1.6.2(错误,导致 `.on()` 方法不可用) + +## 已完成的修复 + +### 1. ✅ 修复了 `main.js` 404 错误 + +**问题:** `functions.php` 中路径错误 +- 错误路径:`/js/main.js` +- 正确路径:`/common/main.js` + +**修复:** 已在 `functions.php` 中更正路径 + +### 2. ✅ 添加了强制移除旧版 jQuery 的函数 + +**位置:** `functions.php` 中的 `remove_old_jquery_conflicts()` 函数 + +**功能:** +- 在所有脚本加载前(优先级 1)移除旧版 jQuery +- 确保只使用 WordPress 自带的 jQuery + +## 下一步操作 + +### 方法一:使用诊断工具定位问题源(推荐) + +1. **访问诊断工具:** + ``` + 你的网站地址/wp-content/themes/theme-document/diagnose-jquery.php + ``` + +2. **查看结果:** + - 检查哪些脚本标记为"可疑" + - 查看 jQuery 版本检测结果 + +3. **定位问题源:** + - 打开浏览器 F12 → Network 标签 + - 搜索 `jquery-1.6.2` + - 点击该请求,查看 "Initiator"(发起者) + - 找到是哪个插件/主题文件引入的 + +4. **删除诊断工具:** + ```bash + rm diagnose-jquery.php + ``` + +### 方法二:手动排查插件 + +1. **禁用所有插件:** + - WordPress 后台 → 插件 → 已安装的插件 + - 逐个禁用插件,刷新页面 + - 检查 F12 Console 是否还有 jQuery 1.6.2 + +2. **找到问题插件后:** + - 更新该插件到最新版本 + - 或寻找替代插件 + - 或联系插件开发者 + +### 方法三:强制移除(已在 functions.php 中实现) + +`functions.php` 中已添加 `remove_old_jquery_conflicts()` 函数,它会: +- 在所有脚本加载前移除旧版 jQuery +- 确保只使用 WordPress 自带的 jQuery + +**如果问题仍然存在:** + +可能需要检查是否有插件通过 `wp_head` 或 `wp_footer` 直接输出 ` + +点击展开查看原作者对本仓库介绍 + 😁作为一个程序员,在日常的工作、生活、学习的过程中基本都有很多需要做笔记的地方;做笔记的主要目的之一是为了“温故而知新”,另一个则是为了在下一次遇到的时候,不需要再次耗费精力去找解决方法; 回顾自己之前写的那个主题,不管是写还是查都不怎么方便,最终下定决心重写一个主题;以 “方便记、方便查、简约”作为设计核心; @@ -14,26 +21,7 @@ Gitee:[https://gitee.com/friend-nicen/theme-document](https://gitee.com/friend 使用帮助看这里:[https://nicen.cn/1552.html](https://nicen.cn/1552.html) -> 😁,加我微信【good7341】或机器人微信【nicen_friend】拉你进交流群! - -# 打赏记录 - -不定时更新..........,感谢: - -| 时间 | 昵称 | 金额 | -|------------|-------------|-------| -| 2024-04-08 | 黯星丶 | 36.6 | -| 2024-04-18 | 桩白墨 plough | 25 | -| 2024-11-13 | Laumy | 66.0 | -| 2025-01-13 | Jason zhang | 100.0 | -| 2025-02-13 | 法律人@海 | 66.0 | -| 2025-04-25 | Laumy | 60.0 | -# 打赏 - -乐意的话,打赏一下也行,不打赏也没关系,如果打赏记得备注一下大名哦,~.~ - -![img.png](https://weixin.nicen.cn/images/weixin.jpg) # 插件推荐 @@ -51,6 +39,149 @@ Github: Gitee: + + +# v1.5.1 (2026-04-18) - 前端资源压缩优化 + +Haibin + +**这是一次重大架构变更,引入了前端构建流程。** + +### 变更概述 + +引入 esbuild 构建工具,对主题所有前端 JS/CSS 文件进行压缩(minify),减少页面加载体积约 206 KB(约 40-50%)。 + +### 具体变更 + +1. **新增构建系统** + - 新增 `build.js` 构建脚本,基于 [esbuild](https://esbuild.github.io/),一键压缩所有前端 JS 和 CSS + - 新增 `package.json`,声明 esbuild 开发依赖 + - 运行 `node build.js` 即可生成所有 `.min.js` / `.min.css` 压缩文件 + +2. **修改资源加载逻辑 (`include/themes/load.php`)** + - 新增 `nicen_theme_min_path()` 辅助函数:自动检测压缩版文件是否存在,存在则加载 `.min.js`/`.min.css`,否则回退到原版文件 + - 所有前端 `wp_enqueue_script` / `wp_enqueue_style` 调用均改为通过该函数加载 + - **向下兼容**:如果 `.min` 文件不存在(比如未执行构建),主题仍然正常加载原版文件,不会报错 + +3. **压缩效果** + + | 文件 | 压缩前 | 压缩后 | 缩减 | + |------|--------|--------|------| + | `common/main.js` | 24.5 KB | 5.0 KB | **-80%** | + | `common/inline/*.js` (6个) | 31 KB | 10 KB | **-68%** | + | `common/prism/prism.js` | 119 KB | 86 KB | -28% | + | `style.css` | 109 KB | 75 KB | -31% | + | 小游戏/工具 JS | 139 KB | 68 KB | -51% | + | 各页面 CSS | 41 KB | 29 KB | -29% | + | **总计节省** | | | **206 KB** | + +4. **新增 `CLAUDE.md`** + - 为 Claude Code AI 助手提供项目上下文和开发工作流说明 + - 包含构建、测试、提交的标准流程 + +### 开发工作流变更 + +从此版本开始,修改 JS 或 CSS 后需要重新构建压缩文件: + +```bash +node build.js # 重新压缩所有文件(约1秒) +``` + +详见 `CLAUDE.md`。 + +### 注意事项 + +- `node_modules/` 目录已在 `.gitignore` 中忽略,首次 clone 后需运行 `npm install` +- 原版未压缩的源文件全部保留,`.min` 文件是额外生成的 +- 后台管理页面的资源(vue.min.js、antd.min.js 等)未做改动,仅优化前端访客页面 + +# 2026-04-18 + +Haibin + +1. 新增 **Doom FPS** 第一人称射击小游戏 + - Raycasting 引擎,Wolfenstein 3D / Doom 风格,纯 Canvas 2D 无外部依赖 + - 24×24 迷宫地图,4 种颜色墙面 + 距离着色 + 侧面明暗 + - 3 把武器:手枪(无限弹药)、霰弹枪(散射)、机关枪(连射),地图内拾取 + - 2 类敌人:Guard(巡逻)和 Soldier(追击),AI 状态机(IDLE → ALERT → CHASE → ATTACK) + - HUD:血量条、弹药、击杀数、准星、小地图(M 键切换) + - 操作:WASD 移动 + 鼠标瞄准射击 + 滚轮/数字键切武器 + - 通关条件:找到出口金色门,评分 = 击杀×100 + 剩余血量×10 + 时间奖励 + - 永久排行榜:All Time(前 20)和 This Week(前 10),3 周自动清理 + - Anti-cheat token + IP 速率限制 + - 使用方式:后台新建页面 → 选择"Doom FPS"模板 → 发布 + +# 2026-04-17 + +Haibin + +1. 新增待办事项功能,仅管理员可见,支持增删改查、完成状态切换、高/中/低优先级、截止日期(过期标红)、拖拽排序、筛选 +2. 采用 LocalStorage 缓存 + MySQL 异步同步架构,页面瞬间渲染,多端数据自动同步 +3. 使用方式:后台新建页面 → 选择"待办事项"模板 → 发布即可访问 +4. 新增 **My Corner(小角落)** 导航页面,卡片网格布局,管理员可添加/编辑/删除/拖拽排序卡片 + - 每个卡片可单独设置"仅管理员可见"或"所有人可见" + - 支持 Emoji、图片 URL、WordPress 媒体库上传作为图标 + - 链接支持站内相对路径和完整外部 URL(外部链接自动新标签页打开) + - 右键卡片可编辑,使用方式:后台新建页面 → 选择"My Corner"模板 → 发布 +5. 新增 **Clipboard(复制助手)** 工具 + - 全站自动监听:已登录用户在任意页面按 Ctrl+C 复制文字时,自动静默保存并弹出轻量 toast 提示 + - 管理页面:手动粘贴文本、拖拽上传文件(<10MB)、搜索筛选、一键复制回剪贴板、下载文件 + - 所有登录用户可用,各自独立数据互不可见 + - LocalStorage + MySQL 双存储,每用户自动保留最近 200 条 + - 使用方式:后台新建页面 → 选择"Clipboard"模板 → 发布 +6. 新增 **Snake Game(贪吃蛇)** 小游戏 + - 现代风格:渐变蛇身、发光食物、粒子爆炸特效、深蓝背景 + - 支持键盘(方向键/WASD)和手机滑动操作,穿墙不死 + - 永久排行榜:All Time(全部历史)和 This Week(本周)两个榜单,前三名金银铜奖牌 + - 任何访客无需登录即可游戏,输入名字即可永久记录战绩 + - 使用方式:后台新建页面 → 选择"Snake Game"模板 → 发布 +7. 安全加固:XSS 防护、javascript: 协议拦截、anti-cheat token、速率限制 + +# 2026-02-27 + +Haibin + +1. 优化了图片懒加载,博客访问更快捷且减少带宽损耗 +2. 修复firefox浏览器无法查看侧边栏的问题 +3. 优化各种安全及防爬虫问题 + + +# 2026-02-08 + +Haibin + +1. 在文章开头添加了字数统计功能,统一显示总词数(中文字符数 + 英文单词数)。 +2. 新增支持 HTML `
` 和 `` 可折叠专栏功能,支持在 Markdown 中使用,包含完整的样式支持: + - 美观的容器样式(背景色、边框、圆角、阴影) + - 自定义的折叠/展开箭头图标 + - 完整的标题样式继承(h1-h6 均支持,h2 包含左侧彩色条和底部边框) + - 内容区域的内边距和背景色 + - 悬停和展开状态的交互效果 + - 支持内部的所有 Markdown 元素(段落、列表、代码块、引用等) + + +# 2026-02-01 + +Haibin + +修复了 main.js 404 错误。添加了强制移除旧版 jQuery 的代码。 +创建了诊断工具 diagnose-jquery.php,创建了诊断工具 + + +# 2026-01-14 + +Haibin + +1. 修复远程图片时两侧边栏没有动态更新的bug,修复导航栏消失bug +2. 全面英文化主题。 +3. 修复导航栏与正文内容不同步的bug + +## 历史更新(原作者) + +
+点击展开查看原作者的全部更新记录 + + # 2025-04-25 1. 修复前台存在多个jq时会导致部分功能不可用的问题 @@ -159,6 +290,8 @@ Gitee: ![首页设置](https://nicen.cn/wp-content/uploads/2022/06/1665419976060.png) ![页脚设置](https://nicen.cn/wp-content/uploads/2022/06/1665419953959.png) ![导航栏设置](https://nicen.cn/wp-content/uploads/2022/06/1665419985715.png) + +
# 安装之前 @@ -227,6 +360,12 @@ wordpress自带的邮件发送服务不太友好,你可以在主题选项开 2. 评论回复通知被回复评论的用户 3. 评论审核通知通知发布评论的用户 + +## 历史开发(原作者) + +
+点击展开查看原作者的开发记录 + # 2022-10-14 1. 新增首页文章导航 @@ -375,3 +514,4 @@ Tags: 文档,自适应,主题切换,阅读进度跟随 1. 新增主题选项:设置首页显示单、双栏 +
\ No newline at end of file diff --git a/TECHNICAL.md b/TECHNICAL.md new file mode 100644 index 0000000..17eff70 --- /dev/null +++ b/TECHNICAL.md @@ -0,0 +1,341 @@ +# Technical Documentation + +This document covers the technical details of custom features added to the Document WordPress theme. + +--- + +## Table of Contents + +1. [My Corner (Navigation Page)](#1-my-corner-navigation-page) +2. [Clipboard Helper](#2-clipboard-helper) +3. [Snake Game](#3-snake-game) +4. [Architecture Overview](#4-architecture-overview) + +--- + +## 1. My Corner (Navigation Page) + +A card-grid navigation page where administrators can manage links to various tools and pages. + +### Files + +| File | Purpose | +|------|---------| +| `template/page/toys.php` | Page template (Template Name: "My Corner") | +| `include/functions/toys.php` | AJAX backend for CRUD + reorder | + +### Data Storage + +Stored in `wp_options` table as a serialized array under the key `nicen_theme_toys`. + +Each item structure: +```php +[ + 'name' => 'Todo List', // Display name + 'icon' => '🐧', // Emoji, image URL, or media library URL + 'desc' => 'Description', // Card description + 'url' => '/index.php/todo-list/', // Relative path or full URL + 'admin_only' => true // Visibility: admin-only or public +] +``` + +### AJAX Endpoint + +**Action**: `toys_manage` (admin-only, requires `administrator` capability) + +| Operation | Parameters | Description | +|-----------|-----------|-------------| +| `add` | name, icon, desc, url, admin_only | Add a new card | +| `update` | index, name, icon, desc, url, admin_only | Update existing card | +| `delete` | index | Remove a card | +| `reorder` | order (array of indices) | Reorder cards via drag-and-drop | + +### Security + +- **Authentication**: `current_user_can('administrator')` check +- **CSRF**: WordPress nonce verification (`toys_nonce`) +- **Input sanitization**: All fields pass through `sanitize_text_field()` +- **URL injection prevention**: `javascript:` protocol explicitly blocked via regex +- **XSS prevention**: Icon output uses `esc_html()` for non-image icons, `esc_url()` for image sources + +### Frontend Features + +- **Card grid**: CSS Grid with `auto-fill`, minimum 180px per card +- **Icon types**: Emoji text (rendered directly), image URL or media library (rendered as ``) +- **Link handling**: Relative paths use site URL prefix; external URLs (starting with `http`) open in `target="_blank"` +- **Admin UI**: Right-click card to edit, "+" card to add, drag-and-drop reorder (HTML5 DnD API) +- **Modal**: Add/Edit/Delete dialog, closeable only via buttons (not overlay click) +- **Media library**: WordPress `wp.media` integration for icon image upload +- **Admin-only badge**: Lock icon displayed on cards with `admin_only: true` +- **URL storage**: Original URL stored in `data-url` attribute to avoid extraction issues with `href` +- **Fallback**: Non-admin users with no visible cards receive a 404 response + +--- + +## 2. Clipboard Helper + +A two-part system: a global silent copy listener across all pages, plus a dedicated management page. + +### Files + +| File | Purpose | +|------|---------| +| `template/page/clipboard.php` | Management page template (Template Name: "Clipboard") | +| `include/clipboard/install.php` | Database table creation | +| `include/clipboard/api.php` | AJAX endpoints | +| `common/clipboard/clipboard-listener.js` | Global copy event listener (all pages) | +| `common/clipboard/clipboard.js` | Management page logic | +| `common/clipboard/clipboard.css` | Management page styles | + +### Database Schema + +**Table**: `{prefix}_document_clipboard` + +| Column | Type | Description | +|--------|------|-------------| +| `id` | BIGINT (PK, auto-increment) | Record ID | +| `user_id` | BIGINT | WordPress user ID | +| `type` | VARCHAR(10), default `'text'` | Entry type: `text` or `file` | +| `content` | LONGTEXT | Text content or file description | +| `filename` | VARCHAR(255) | Original filename (files only) | +| `filepath` | VARCHAR(500) | Server path to uploaded file | +| `filesize` | BIGINT | File size in bytes | +| `created_at` | DATETIME | Timestamp | + +**Indexes**: `idx_user_id`, `idx_created`, `idx_type` + +Table is created on theme activation (`after_switch_theme`) and verified on every `init` hook. + +### AJAX Endpoints + +All endpoints require login (`wp_ajax_` only, no `wp_ajax_nopriv_`). + +| Action | Description | +|--------|-------------| +| `clipboard_save` | Save text (max 1MB), with 3-second deduplication window | +| `clipboard_upload` | Upload file (max 10MB), stored in `wp-uploads/clipboard/{user_id}/` | +| `clipboard_list` | Paginated list (50/page) with search and type filter | +| `clipboard_delete` | Delete single entry (own only) + remove file from disk | +| `clipboard_clear` | Delete all entries for current user + all user files | + +### Global Copy Listener (`clipboard-listener.js`) + +Loaded site-wide for logged-in users via `wp_enqueue_script` in `include/themes/load.php`. + +**Behavior**: +1. Listens to `document` `copy` event +2. Reads selected text via `window.getSelection()` +3. Ignores selections shorter than 3 characters +4. Debounced at 100ms to prevent rapid-fire saves +5. Saves to both `localStorage` (max 100 entries, key: `clipboard_history`) and server via AJAX +6. Displays floating toast notification ("Saved: preview...") that auto-dismisses after 2 seconds + +**Configuration**: `window.CLIPBOARD_AJAX` is injected via `wp_add_inline_script` pointing to `admin-ajax.php`. + +### Management Page (`clipboard.js`) + +- **Text input**: Textarea with Ctrl+Enter to save +- **File upload**: Drag-and-drop zone + click to browse, XHR upload with progress bar +- **List rendering**: Chronological list with type badges (green=text, blue=file) +- **Search**: Real-time search with 300ms debounce +- **Type filter**: All / Text / Files tabs +- **Pagination**: Smart ellipsis when pages > 7 +- **Copy to clipboard**: `navigator.clipboard.writeText()` with `document.execCommand('copy')` fallback +- **Expandable text**: Content >300 chars is truncated with expand/collapse toggle +- **File icons**: Maps ~30 file extensions to emoji icons +- **Relative time**: "just now", "Xm ago", "Xh ago", etc. + +### Data Retention + +- **Auto-cleanup**: Max 200 entries per user; oldest entries beyond 200 are deleted (including associated files) +- **Upload directory**: `wp-content/uploads/clipboard/{user_id}/`, protected with `.htaccess` (`Options -Indexes`) +- **File size limit**: 10MB per file, enforced server-side + +--- + +## 3. Snake Game + +A canvas-based snake game with permanent leaderboard, open to all visitors without login. + +### Files + +| File | Purpose | +|------|---------| +| `template/page/snake.php` | Page template (Template Name: "Snake Game") | +| `include/snake/install.php` | Database table creation | +| `include/snake/api.php` | AJAX endpoints + cleanup logic | +| `common/snake/snake.js` | Game engine + UI logic (~550 lines) | +| `common/snake/snake.css` | Layout + leaderboard styles | + +### Database Schema + +**Table**: `{prefix}_document_snake_scores` + +| Column | Type | Description | +|--------|------|-------------| +| `id` | BIGINT (PK, auto-increment) | Record ID | +| `player_name` | VARCHAR(50) | Player's display name | +| `score` | INT | Number of food items eaten | +| `duration` | INT | Game duration in seconds | +| `created_at` | DATETIME | Timestamp | + +**Indexes**: `idx_score` (score DESC), `idx_created` (created_at) + +### AJAX Endpoints + +Both endpoints are public (`wp_ajax_` + `wp_ajax_nopriv_`), no login required. + +| Action | Parameters | Description | +|--------|-----------|-------------| +| `snake_submit` | name, score, duration, token | Submit a score with anti-cheat validation | +| `snake_leaderboard` | type (`alltime` / `weekly`) | Get top scores (alltime=20, weekly=10) | + +### Anti-Cheat System + +A token-based validation prevents trivial score forgery: + +**Token generation flow**: +``` +Daily salt (PHP): 'snake_' + date('Y-m-d') + '_' + wp_salt('auth') +Hashed salt: md5(daily_salt) → 32-char hex string (safe for JS injection) +Token: md5(score + '_' + duration + '_' + hashed_salt) +``` + +**Client side**: `window.SNAKE_DAY_SALT` is injected as the pre-hashed salt via `wp_add_inline_script()` in `include/themes/load.php`. The JS computes `md5(score + '_' + duration + '_' + SNAKE_DAY_SALT)` using a built-in MD5 implementation. + +**Server side**: `api.php` recomputes `md5($score . '_' . $duration . '_' . md5(document_snake_salt()))` and compares. + +**Why pre-hash the salt**: `wp_salt('auth')` contains special characters (quotes, backslashes) that get corrupted by `esc_js()` when injected into JavaScript. By hashing the salt first with MD5, only safe hex characters (`0-9a-f`) are passed to the client. + +**Rate limiting**: 1 submission per 5 seconds per IP, enforced via WordPress transients. + +### Data Retention & Cleanup + +Cleanup runs automatically after each score submission (`document_snake_cleanup()`): + +| Rule | Description | +|------|-------------| +| **All-time top 20** | Only the top 20 scores (by score DESC, duration ASC) are kept. All others are deleted. | +| **3-week expiry** | Records older than 3 weeks are automatically deleted. | +| **Weekly display** | The weekly leaderboard shows only the top 10 scores from the last 7 days. | + +Cleanup logic handles ties correctly by selecting the top 20 row IDs explicitly and deleting everything not in that set. + +### Game Engine (`snake.js`) + +**Architecture**: Single IIFE, vanilla JavaScript (ES5 compatible), no dependencies. + +**Grid**: 20×20 cells on a 400×400 canvas. + +**Game states**: `START` → `PLAYING` → `GAMEOVER` + +**Rendering** (Canvas 2D): +- Dark background (`#1a1a2e`) with subtle grid lines +- Snake body: circles with HSL gradient (head = bright green, fades toward tail) +- Snake head: directional eyes + glow effect (shadow blur) +- Food: pulsating red-orange radial gradient with glow animation +- Particle system: 12 particles burst on food consumption, red-orange hue, fade out over time + +**Movement**: +- Game loop via `requestAnimationFrame` with tick-based updates +- Speed starts at 150ms/tick, decreases by 3ms per food eaten, minimum 60ms +- **Wall wrap-around**: snake passes through walls and appears on the opposite side (no wall death) +- Self-collision: game over + +**Controls**: +- Keyboard: Arrow keys + WASD +- Mobile: Touch swipe with 20px dead zone +- Direction guard: cannot reverse into opposite direction (e.g., moving right, can't press left) + +**Player name**: Persisted in `localStorage` across sessions. + +**Leaderboard UI**: +- Two tabs: "All Time" and "This Week" +- Top 3 entries get medal emojis (gold/silver/bronze) +- Current player's entries highlighted +- Auto-refreshed after each game over + on tab switch + +**AJAX configuration**: `window.SNAKE_AJAX` injected via `wp_add_inline_script`. Fallback: `window.HOME + '/wp-admin/admin-ajax.php'` or `/wp-admin/admin-ajax.php`. + +### CSS Layout + +- Flex layout: game area (left) + leaderboard panel (right), max-width 1000px +- Score bar: theme color background, flex row with score + time +- Canvas: dark background, rounded corners, box shadow +- Overlays: semi-transparent black backdrop, centered content with blur effect +- Leaderboard: scrollable list (max-height 420px), themed tab buttons +- Medal row colors: gold (#fff8e1), silver (#f5f5f5), bronze (#fff3e0) +- Responsive: stacks vertically at ≤720px + +--- + +## 4. Architecture Overview + +### Template Registration + +Templates are **not** auto-scanned by WordPress. They are registered in the `PAGES` constant in `include/config.php`: + +```php +'Snake Game' => [ + 'template' => 'template/page/snake.php', + 'dependent' => [ + 'styles' => ['/common/snake/snake.css'], + 'scripts' => ['/common/snake/snake.js'], + ] +] +``` + +The `nicen_theme_load_source()` function in `include/themes/load.php` iterates over `PAGES`, detects the current page template via `get_page_template_slug()`, and enqueues the matching CSS/JS dependencies. + +### Resource Loading (`include/themes/load.php`) + +| Context | Resources Loaded | +|---------|-----------------| +| All pages | jQuery, enquire.js, main-sub.js, main.js, style.css | +| Single post | viewer.js, prism.js (code highlighting), prism.css, viewer.css | +| Homepage | swiper bundle, homepage carousel CSS/JS | +| Logged-in users | `clipboard-listener.js` + `CLIPBOARD_AJAX` inline var | +| Snake Game template | `SNAKE_DAY_SALT` + `SNAKE_AJAX` inline vars (injected before game script) | +| All page templates | Dependent CSS/JS from `PAGES` config | + +### Inline JavaScript Injection + +Several features inject configuration via `wp_add_inline_script()`: + +| Variable | Scope | Purpose | +|----------|-------|---------| +| `window.ROOT` | Global | Theme directory URL | +| `window.HOME` | Global | Site home URL | +| `window.CLIPBOARD_AJAX` | Logged-in users | `admin-ajax.php` URL for clipboard | +| `window.SNAKE_DAY_SALT` | Snake Game page | Pre-hashed daily salt for anti-cheat token | +| `window.SNAKE_AJAX` | Snake Game page | `admin-ajax.php` URL for snake endpoints | +| `window.Current` | Single posts/pages | Current post ID | +| `window._ts` | Single posts/pages | Server timestamp | + +### Database Tables + +All custom tables are created on theme activation and verified on `init`: + +| Table | Module | Public Access | +|-------|--------|--------------| +| `wp_document_clipboard` | Clipboard | No (login required) | +| `wp_document_snake_scores` | Snake Game | Yes (guests can submit/view) | + +### Security Summary + +| Feature | Auth | CSRF | Input Sanitization | Anti-Abuse | +|---------|------|------|--------------------|------------| +| My Corner | Admin only | WP nonce | `sanitize_text_field()`, `javascript:` block | N/A | +| Clipboard | Login required | None (login-gated) | `sanitize_text_field()`, `sanitize_file_name()` | 200 entry limit, 10MB file limit, dedup | +| Snake Game | Public | Anti-cheat token (MD5) | Name: 20 char limit; Score: 0-9999 | Rate limit: 1 submit/5s per IP, top 20 retention | + +### Theme Inclusion Chain + +``` +functions.php + └── include/themes/theme.php + ├── include/clipboard/install.php (DB setup) + ├── include/clipboard/api.php (AJAX handlers) + ├── include/snake/install.php (DB setup) + └── include/snake/api.php (AJAX handlers) +``` diff --git a/assets/extra/variant.css b/assets/extra/variant.css index acd0ca1..e076882 100644 --- a/assets/extra/variant.css +++ b/assets/extra/variant.css @@ -8,7 +8,7 @@ --theme-text-mini: 0.76rem; --theme-text-more-mini: 0.69rem; --theme-text-more-mini-2: 0.62rem; - --theme-text: 0.84rem; + --theme-text: 1rem; --theme-header-size: 1.1rem; --theme-secondary: 0.78rem; --theme-first-level-title: 1.35rem; diff --git a/assets/extra/variant.scss b/assets/extra/variant.scss index 5dfe2e0..47419fa 100644 --- a/assets/extra/variant.scss +++ b/assets/extra/variant.scss @@ -8,7 +8,7 @@ --theme-text-mini: 0.76rem; --theme-text-more-mini: 0.69rem; --theme-text-more-mini-2: 0.62rem; - --theme-text: 0.84rem; + --theme-text: 1rem; --theme-header-size: 1.1rem; --theme-secondary: 0.78rem; --theme-first-level-title: 1.35rem; diff --git a/assets/theme/enquire.min.js b/assets/theme/enquire.min.js new file mode 100644 index 0000000..6745d43 --- /dev/null +++ b/assets/theme/enquire.min.js @@ -0,0 +1,4 @@ +/*! + * enquire.js v2.1.6 - Awesome Media Queries in JavaScript + * Copyright (c) 2017 Nick Williams - http://wicky.nillia.ms/enquire.js + * License: MIT */(function(p){if(typeof exports=="object"&&typeof module<"u")module.exports=p();else if(typeof define=="function"&&define.amd)define([],p);else{var d;typeof window<"u"?d=window:typeof global<"u"?d=global:typeof self<"u"?d=self:d=this,d.enquire=p()}})(function(){var p,d,v;return(function u(a,f,o){function i(t,n){if(!f[t]){if(!a[t]){var r=typeof require=="function"&&require;if(!n&&r)return r(t,!0);if(c)return c(t,!0);var s=new Error("Cannot find module '"+t+"'");throw s.code="MODULE_NOT_FOUND",s}var h=f[t]={exports:{}};a[t][0].call(h.exports,function(l){var y=a[t][1][l];return i(y||l)},h,h.exports,u,a,f,o)}return f[t].exports}for(var c=typeof require=="function"&&require,e=0;e { console.error(e); process.exit(1); }); diff --git a/comments.php b/comments.php index 656e6e5..0616676 100644 --- a/comments.php +++ b/comments.php @@ -41,7 +41,7 @@
    -
  • 评论区
  • +
  • Comments
@@ -55,15 +55,15 @@
-
comment_author . '的评论'; ?>
+
comment_author . '\'s comment'; ?>
@@ -74,7 +74,7 @@ id="comment_post_ID">
@@ -84,19 +84,19 @@ class="comment-content">
- - -
@@ -126,7 +126,7 @@ class="comment-content"> } else { ?>
- 评论区未打开,无法接收留言! + Comments are not open, cannot receive messages!
30 ? text.substring(0, 30) + '...' : text; + showToast('Saved: ' + preview); + }, 100); + }); +})(); diff --git a/common/clipboard/clipboard-listener.min.js b/common/clipboard/clipboard-listener.min.js new file mode 100644 index 0000000..4b6849a --- /dev/null +++ b/common/clipboard/clipboard-listener.min.js @@ -0,0 +1 @@ +(function(){"use strict";var n=window.CLIPBOARD_AJAX||"",o="document_clipboard",i=null;if(!n)return;function a(){try{return JSON.parse(localStorage.getItem(o)||"[]")}catch{return[]}}function c(e){try{localStorage.setItem(o,JSON.stringify(e.slice(0,100)))}catch{}}function d(e){var t=document.getElementById("clipboard-toast");t||(t=document.createElement("div"),t.id="clipboard-toast",t.style.cssText="position:fixed;bottom:24px;right:24px;background:rgba(62,175,124,0.95);color:#fff;padding:8px 18px;border-radius:8px;font-size:13px;z-index:99999;opacity:0;transition:opacity 0.3s;pointer-events:none;box-shadow:0 4px 12px rgba(0,0,0,0.15);",document.body.appendChild(t)),t.textContent=e,t.style.opacity="1",clearTimeout(t._timer),t._timer=setTimeout(function(){t.style.opacity="0"},2e3)}function s(e){var t=new FormData;t.append("action","clipboard_save"),t.append("content",e),fetch(n,{method:"POST",body:t,credentials:"same-origin"}).catch(function(){})}document.addEventListener("copy",function(){clearTimeout(i),i=setTimeout(function(){var e=window.getSelection(),t=e?e.toString():"";if(!(!t||t.trim().length===0)&&!(t.trim().length<3)){var r=a();r.unshift({type:"text",content:t,created_at:new Date().toISOString()}),c(r),s(t);var l=t.length>30?t.substring(0,30)+"...":t;d("Saved: "+l)}},100)})})(); diff --git a/common/clipboard/clipboard.css b/common/clipboard/clipboard.css new file mode 100644 index 0000000..796f7fd --- /dev/null +++ b/common/clipboard/clipboard.css @@ -0,0 +1,348 @@ +/* ===== Clipboard Helper Styles ===== */ +.cb-container { + max-width: 900px; + margin: 0 auto; + padding: 30px 20px 60px; +} +.cb-header { + text-align: center; + margin-bottom: 32px; +} +.cb-header h2 { + font-size: 1.8rem; + margin-bottom: 8px; + color: var(--theme-text-color); +} +.cb-header p { + color: var(--theme-text-secondary); + font-size: var(--theme-secondary); +} + +/* Input area */ +.cb-input-area { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin-bottom: 24px; +} +.cb-paste-area { + display: flex; + flex-direction: column; + gap: 10px; +} +.cb-textarea { + width: 100%; + padding: 12px; + border: 1px solid #ddd; + border-radius: 8px; + font-size: 0.9rem; + resize: vertical; + outline: none; + transition: border-color 0.2s; + font-family: inherit; + box-sizing: border-box; +} +.cb-textarea:focus { + border-color: var(--theme-color); +} + +/* Upload area */ +.cb-upload-area { + border: 2px dashed #ccc; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; + text-align: center; + transition: border-color 0.2s, background 0.2s; + cursor: pointer; + position: relative; +} +.cb-upload-area:hover, +.cb-upload-area.drag-over { + border-color: var(--theme-color); + background: rgba(66, 185, 131, 0.04); +} +.cb-upload-icon { + font-size: 2rem; + margin-bottom: 8px; + opacity: 0.5; +} +.cb-upload-text { + font-size: 0.9rem; + color: var(--theme-text-secondary); +} +.cb-upload-link { + color: var(--theme-color); + cursor: pointer; + text-decoration: underline; +} +.cb-upload-hint { + font-size: 0.75rem; + color: #aaa; + margin-top: 4px; +} +.cb-upload-progress { + width: 80%; + height: 4px; + background: #eee; + border-radius: 2px; + margin-top: 10px; + overflow: hidden; +} +.cb-progress-bar { + height: 100%; + background: var(--theme-color); + border-radius: 2px; + width: 0%; + transition: width 0.3s; +} + +/* Toolbar */ +.cb-toolbar { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 16px; + flex-wrap: wrap; +} +.cb-search-wrap { + flex: 1; + min-width: 160px; +} +.cb-search { + width: 100%; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 0.9rem; + outline: none; + box-sizing: border-box; +} +.cb-search:focus { + border-color: var(--theme-color); +} +.cb-filters { + display: flex; + gap: 4px; +} +.cb-filter-btn { + padding: 6px 14px; + border: 1px solid #ddd; + border-radius: 6px; + background: transparent; + cursor: pointer; + font-size: 0.82rem; + transition: all 0.2s; +} +.cb-filter-btn.active { + background: var(--theme-color); + color: #fff; + border-color: var(--theme-color); +} +.cb-toolbar-right { + display: flex; + align-items: center; + gap: 10px; +} +.cb-stats { + font-size: 0.8rem; + color: var(--theme-text-secondary); +} + +/* Buttons */ +.cb-btn { + padding: 8px 18px; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 0.85rem; + transition: opacity 0.2s; + white-space: nowrap; +} +.cb-btn:hover { opacity: 0.85; } +.cb-btn-primary { + background: var(--theme-color); + color: #fff; +} +.cb-btn-danger { + background: #ff4d4f; + color: #fff; + font-size: 0.78rem; + padding: 6px 12px; +} + +/* List */ +.cb-list { + display: flex; + flex-direction: column; + gap: 10px; +} +.cb-loading, .cb-empty { + text-align: center; + padding: 40px; + color: var(--theme-text-secondary); +} +.cb-empty-icon { + font-size: 3rem; + margin-bottom: 10px; + opacity: 0.3; +} + +/* Entry item */ +.cb-item { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 14px 16px; + background: var(--theme-front-main-color); + border-radius: 10px; + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05); + transition: box-shadow 0.2s; +} +.cb-item:hover { + box-shadow: 0 3px 14px rgba(0, 0, 0, 0.1); +} +.cb-item-icon { + font-size: 1.5rem; + flex-shrink: 0; + width: 36px; + text-align: center; + padding-top: 2px; +} +.cb-item-body { + flex: 1; + min-width: 0; + overflow: hidden; +} +.cb-item-content { + font-size: 0.88rem; + color: var(--theme-text-color); + line-height: 1.5; + white-space: pre-wrap; + word-break: break-all; + max-height: 120px; + overflow: hidden; + position: relative; +} +.cb-item-content.expanded { + max-height: none; +} +.cb-item-expand { + font-size: 0.75rem; + color: var(--theme-color); + cursor: pointer; + margin-top: 4px; + display: inline-block; +} +.cb-item-meta { + display: flex; + align-items: center; + gap: 10px; + margin-top: 6px; + font-size: 0.75rem; + color: #aaa; +} +.cb-type-badge { + display: inline-block; + padding: 1px 8px; + border-radius: 10px; + font-size: 0.7rem; + font-weight: 600; +} +.cb-type-text { + background: rgba(66, 185, 131, 0.15); + color: var(--theme-color); +} +.cb-type-file { + background: rgba(59, 130, 246, 0.15); + color: #3b82f6; +} +.cb-file-size { + color: #999; +} +.cb-item-actions { + display: flex; + gap: 6px; + flex-shrink: 0; + padding-top: 2px; +} +.cb-action-btn { + background: none; + border: 1px solid #ddd; + border-radius: 6px; + padding: 5px 10px; + cursor: pointer; + font-size: 0.78rem; + transition: all 0.2s; + white-space: nowrap; +} +.cb-action-btn:hover { + border-color: var(--theme-color); + color: var(--theme-color); +} +.cb-action-btn.delete:hover { + border-color: #ff4d4f; + color: #ff4d4f; +} +.cb-action-btn.download { + text-decoration: none; + color: inherit; + display: inline-flex; + align-items: center; +} + +/* Pagination */ +.cb-pagination { + display: flex; + justify-content: center; + gap: 6px; + margin-top: 24px; +} +.cb-page-btn { + padding: 6px 12px; + border: 1px solid #ddd; + border-radius: 6px; + background: transparent; + cursor: pointer; + font-size: 0.82rem; + transition: all 0.2s; +} +.cb-page-btn.active { + background: var(--theme-color); + color: #fff; + border-color: var(--theme-color); +} +.cb-page-btn:hover:not(.active) { + border-color: var(--theme-color); +} + +/* Copy success feedback */ +.cb-copied { + background: var(--theme-color) !important; + color: #fff !important; + border-color: var(--theme-color) !important; +} + +/* Responsive */ +@media (max-width: 640px) { + .cb-input-area { + grid-template-columns: 1fr; + } + .cb-toolbar { + flex-direction: column; + align-items: stretch; + } + .cb-toolbar-right { + justify-content: space-between; + } + .cb-item { + flex-direction: column; + gap: 8px; + } + .cb-item-actions { + align-self: flex-end; + } +} diff --git a/common/clipboard/clipboard.js b/common/clipboard/clipboard.js new file mode 100644 index 0000000..2108409 --- /dev/null +++ b/common/clipboard/clipboard.js @@ -0,0 +1,388 @@ +/** + * Clipboard Helper - Page logic + * @author Haibin + * @date 2026-04-17 + */ +(function () { + 'use strict'; + + var AJAX_URL = window.HOME + '/wp-admin/admin-ajax.php'; + var currentPage = 1; + var currentType = ''; + var searchTimer = null; + + /* ========== AJAX helper ========== */ + function ajax(action, data, isFile) { + var fd = new FormData(); + fd.append('action', action); + if (data) { + if (isFile) { + Object.keys(data).forEach(function (k) { fd.append(k, data[k]); }); + } else { + Object.keys(data).forEach(function (k) { fd.append(k, data[k]); }); + } + } + return fetch(AJAX_URL, { method: 'POST', body: fd, credentials: 'same-origin' }) + .then(function (r) { return r.json(); }) + .then(function (res) { + if (res.success) return res.data; + throw new Error(res.data || 'Error'); + }); + } + + /* ========== Load & Render ========== */ + function loadList() { + var search = document.getElementById('cb-search').value.trim(); + ajax('clipboard_list', { + page: currentPage, + search: search, + type: currentType + }).then(function (data) { + renderList(data.items, data.total); + renderPagination(data.total, data.page, data.per_page); + document.getElementById('cb-stats').textContent = data.total + ' items'; + }).catch(function () { + document.getElementById('cb-list').innerHTML = '
Failed to load
'; + }); + } + + function renderList(items, total) { + var el = document.getElementById('cb-list'); + if (!items || items.length === 0) { + el.innerHTML = '
📋
' + + (total === 0 ? 'No clipboard entries yet. Copy some text or upload a file!' : 'No results found') + '
'; + return; + } + + var html = ''; + items.forEach(function (item) { + html += '
'; + + // Icon + if (item.type === 'file') { + html += '
' + getFileIcon(item.filename) + '
'; + } else { + html += '
📄
'; + } + + // Body + html += '
'; + if (item.type === 'text') { + var content = escapeHtml(item.content || ''); + var isLong = content.length > 300; + html += '
' + content + '
'; + if (isLong) { + html += 'Show more'; + } + } else { + html += '
'; + html += '' + escapeHtml(item.filename || 'file') + ''; + html += '
'; + } + + // Meta + html += '
'; + html += '' + + (item.type === 'text' ? 'TEXT' : 'FILE') + ''; + if (item.type === 'file' && item.filesize) { + html += '' + formatSize(item.filesize) + ''; + } + if (item.type === 'text' && item.content) { + html += '' + item.content.length + ' chars'; + } + html += '' + formatTime(item.created_at) + ''; + html += '
'; + html += '
'; + + // Actions + html += '
'; + if (item.type === 'text') { + html += ''; + } else if (item.download_url) { + html += '⬇ Download'; + } + html += ''; + html += '
'; + + html += '
'; + }); + + el.innerHTML = html; + } + + function renderPagination(total, page, perPage) { + var el = document.getElementById('cb-pagination'); + var pages = Math.ceil(total / perPage); + if (pages <= 1) { el.innerHTML = ''; return; } + + var html = ''; + if (page > 1) { + html += ''; + } + for (var i = 1; i <= pages; i++) { + if (pages > 7 && i > 3 && i < pages - 2 && Math.abs(i - page) > 1) { + if (i === 4 || i === pages - 3) html += '...'; + continue; + } + html += ''; + } + if (page < pages) { + html += ''; + } + el.innerHTML = html; + } + + /* ========== Actions ========== */ + function saveText() { + var input = document.getElementById('cb-paste-input'); + var text = input.value.trim(); + if (!text) return; + input.disabled = true; + ajax('clipboard_save', { content: text }).then(function () { + input.value = ''; + input.disabled = false; + currentPage = 1; + loadList(); + }).catch(function (e) { + alert(e.message || 'Save failed'); + input.disabled = false; + }); + } + + function uploadFile(file) { + if (file.size > 10 * 1024 * 1024) { + alert('File too large (max 10MB)'); + return; + } + + var progress = document.getElementById('cb-upload-progress'); + var bar = document.getElementById('cb-progress-bar'); + progress.style.display = ''; + bar.style.width = '0%'; + + var fd = new FormData(); + fd.append('action', 'clipboard_upload'); + fd.append('file', file); + + var xhr = new XMLHttpRequest(); + xhr.open('POST', AJAX_URL); + xhr.withCredentials = true; + + xhr.upload.onprogress = function (e) { + if (e.lengthComputable) { + bar.style.width = Math.round(e.loaded / e.total * 100) + '%'; + } + }; + xhr.onload = function () { + progress.style.display = 'none'; + bar.style.width = '0%'; + try { + var res = JSON.parse(xhr.responseText); + if (res.success) { + currentPage = 1; + loadList(); + } else { + alert(res.data || 'Upload failed'); + } + } catch (e) { + alert('Upload failed'); + } + }; + xhr.onerror = function () { + progress.style.display = 'none'; + alert('Upload failed'); + }; + xhr.send(fd); + } + + function copyText(id) { + var item = document.querySelector('.cb-item[data-id="' + id + '"]'); + if (!item) return; + var content = item.querySelector('.cb-item-content'); + if (!content) return; + + var text = content.textContent; + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then(function () { + showCopyFeedback(id); + }); + } else { + // Fallback + var ta = document.createElement('textarea'); + ta.value = text; + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + showCopyFeedback(id); + } + } + + function showCopyFeedback(id) { + var btn = document.querySelector('.cb-action-btn[data-id="' + id + '"]'); + if (!btn) return; + btn.classList.add('cb-copied'); + btn.innerHTML = '✓ Copied!'; + setTimeout(function () { + btn.classList.remove('cb-copied'); + btn.innerHTML = '📋 Copy'; + }, 1500); + } + + function deleteItem(id) { + if (!confirm('Delete this entry?')) return; + ajax('clipboard_delete', { id: id }).then(function () { + loadList(); + }).catch(function (e) { + alert(e.message || 'Delete failed'); + }); + } + + function clearAll() { + if (!confirm('Clear ALL clipboard entries? This cannot be undone.')) return; + ajax('clipboard_clear').then(function () { + currentPage = 1; + loadList(); + }); + } + + function toggleExpand(el) { + var content = el.previousElementSibling; + if (content.classList.contains('expanded')) { + content.classList.remove('expanded'); + el.textContent = 'Show more'; + } else { + content.classList.add('expanded'); + el.textContent = 'Show less'; + } + } + + function goPage(p) { + currentPage = p; + loadList(); + document.querySelector('.cb-container').scrollIntoView({ behavior: 'smooth' }); + } + + /* ========== Helpers ========== */ + function escapeHtml(str) { + var d = document.createElement('div'); + d.textContent = str; + return d.innerHTML; + } + + function formatSize(bytes) { + if (bytes < 1024) return bytes + ' B'; + if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; + return (bytes / 1048576).toFixed(1) + ' MB'; + } + + function formatTime(dateStr) { + if (!dateStr) return ''; + var d = new Date(dateStr.replace(' ', 'T')); + var now = new Date(); + var diff = (now - d) / 1000; + + if (diff < 60) return 'Just now'; + if (diff < 3600) return Math.floor(diff / 60) + 'm ago'; + if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'; + if (diff < 604800) return Math.floor(diff / 86400) + 'd ago'; + + return (d.getMonth() + 1) + '/' + d.getDate() + ' ' + + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); + } + + function getFileIcon(filename) { + if (!filename) return '📄'; + var ext = filename.split('.').pop().toLowerCase(); + var icons = { + pdf: '📕', doc: '📄', docx: '📄', + xls: '📊', xlsx: '📊', csv: '📊', + ppt: '📊', pptx: '📊', + txt: '📄', log: '📄', md: '📄', + zip: '📦', rar: '📦', gz: '📦', tar: '📦', '7z': '📦', + jpg: '🎨', jpeg: '🎨', png: '🎨', gif: '🎨', svg: '🎨', webp: '🎨', + mp3: '🎵', wav: '🎵', flac: '🎵', + mp4: '🎥', avi: '🎥', mkv: '🎥', + js: '💻', py: '💻', php: '💻', java: '💻', c: '💻', cpp: '💻', + html: '🌐', css: '🌐', + json: '📄', xml: '📄', yaml: '📄', yml: '📄', + sh: '💻', bat: '💻', + }; + return icons[ext] || '📄'; + } + + /* ========== Init ========== */ + function init() { + loadList(); + + // Save text button + document.getElementById('cb-paste-btn').addEventListener('click', saveText); + document.getElementById('cb-paste-input').addEventListener('keydown', function (e) { + if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) saveText(); + }); + + // File upload + var uploadArea = document.getElementById('cb-upload-area'); + var fileInput = document.getElementById('cb-file-input'); + + uploadArea.addEventListener('click', function (e) { + if (e.target.tagName !== 'LABEL' && e.target.tagName !== 'INPUT') { + fileInput.click(); + } + }); + fileInput.addEventListener('change', function () { + if (this.files[0]) uploadFile(this.files[0]); + this.value = ''; + }); + + // Drag & drop + uploadArea.addEventListener('dragover', function (e) { + e.preventDefault(); + uploadArea.classList.add('drag-over'); + }); + uploadArea.addEventListener('dragleave', function () { + uploadArea.classList.remove('drag-over'); + }); + uploadArea.addEventListener('drop', function (e) { + e.preventDefault(); + uploadArea.classList.remove('drag-over'); + if (e.dataTransfer.files[0]) uploadFile(e.dataTransfer.files[0]); + }); + + // Search + document.getElementById('cb-search').addEventListener('input', function () { + clearTimeout(searchTimer); + searchTimer = setTimeout(function () { + currentPage = 1; + loadList(); + }, 300); + }); + + // Filter buttons + document.querySelectorAll('.cb-filter-btn').forEach(function (btn) { + btn.addEventListener('click', function () { + document.querySelectorAll('.cb-filter-btn').forEach(function (b) { b.classList.remove('active'); }); + btn.classList.add('active'); + currentType = btn.dataset.type; + currentPage = 1; + loadList(); + }); + }); + + // Clear all + document.getElementById('cb-clear-btn').addEventListener('click', clearAll); + } + + // Expose functions + window._cb = { + copyText: copyText, + deleteItem: deleteItem, + toggleExpand: toggleExpand, + goPage: goPage + }; + + if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); + else init(); +})(); diff --git a/common/clipboard/clipboard.min.css b/common/clipboard/clipboard.min.css new file mode 100644 index 0000000..c4d60dd --- /dev/null +++ b/common/clipboard/clipboard.min.css @@ -0,0 +1 @@ +.cb-container{max-width:900px;margin:0 auto;padding:30px 20px 60px}.cb-header{text-align:center;margin-bottom:32px}.cb-header h2{font-size:1.8rem;margin-bottom:8px;color:var(--theme-text-color)}.cb-header p{color:var(--theme-text-secondary);font-size:var(--theme-secondary)}.cb-input-area{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:24px}.cb-paste-area{display:flex;flex-direction:column;gap:10px}.cb-textarea{width:100%;padding:12px;border:1px solid #ddd;border-radius:8px;font-size:.9rem;resize:vertical;outline:none;transition:border-color .2s;font-family:inherit;box-sizing:border-box}.cb-textarea:focus{border-color:var(--theme-color)}.cb-upload-area{border:2px dashed #ccc;border-radius:10px;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center;transition:border-color .2s,background .2s;cursor:pointer;position:relative}.cb-upload-area:hover,.cb-upload-area.drag-over{border-color:var(--theme-color);background:#42b9830a}.cb-upload-icon{font-size:2rem;margin-bottom:8px;opacity:.5}.cb-upload-text{font-size:.9rem;color:var(--theme-text-secondary)}.cb-upload-link{color:var(--theme-color);cursor:pointer;text-decoration:underline}.cb-upload-hint{font-size:.75rem;color:#aaa;margin-top:4px}.cb-upload-progress{width:80%;height:4px;background:#eee;border-radius:2px;margin-top:10px;overflow:hidden}.cb-progress-bar{height:100%;background:var(--theme-color);border-radius:2px;width:0%;transition:width .3s}.cb-toolbar{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap}.cb-search-wrap{flex:1;min-width:160px}.cb-search{width:100%;padding:8px 12px;border:1px solid #ddd;border-radius:6px;font-size:.9rem;outline:none;box-sizing:border-box}.cb-search:focus{border-color:var(--theme-color)}.cb-filters{display:flex;gap:4px}.cb-filter-btn{padding:6px 14px;border:1px solid #ddd;border-radius:6px;background:transparent;cursor:pointer;font-size:.82rem;transition:all .2s}.cb-filter-btn.active{background:var(--theme-color);color:#fff;border-color:var(--theme-color)}.cb-toolbar-right{display:flex;align-items:center;gap:10px}.cb-stats{font-size:.8rem;color:var(--theme-text-secondary)}.cb-btn{padding:8px 18px;border:none;border-radius:6px;cursor:pointer;font-size:.85rem;transition:opacity .2s;white-space:nowrap}.cb-btn:hover{opacity:.85}.cb-btn-primary{background:var(--theme-color);color:#fff}.cb-btn-danger{background:#ff4d4f;color:#fff;font-size:.78rem;padding:6px 12px}.cb-list{display:flex;flex-direction:column;gap:10px}.cb-loading,.cb-empty{text-align:center;padding:40px;color:var(--theme-text-secondary)}.cb-empty-icon{font-size:3rem;margin-bottom:10px;opacity:.3}.cb-item{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;background:var(--theme-front-main-color);border-radius:10px;box-shadow:0 1px 6px #0000000d;transition:box-shadow .2s}.cb-item:hover{box-shadow:0 3px 14px #0000001a}.cb-item-icon{font-size:1.5rem;flex-shrink:0;width:36px;text-align:center;padding-top:2px}.cb-item-body{flex:1;min-width:0;overflow:hidden}.cb-item-content{font-size:.88rem;color:var(--theme-text-color);line-height:1.5;white-space:pre-wrap;word-break:break-all;max-height:120px;overflow:hidden;position:relative}.cb-item-content.expanded{max-height:none}.cb-item-expand{font-size:.75rem;color:var(--theme-color);cursor:pointer;margin-top:4px;display:inline-block}.cb-item-meta{display:flex;align-items:center;gap:10px;margin-top:6px;font-size:.75rem;color:#aaa}.cb-type-badge{display:inline-block;padding:1px 8px;border-radius:10px;font-size:.7rem;font-weight:600}.cb-type-text{background:#42b98326;color:var(--theme-color)}.cb-type-file{background:#3b82f626;color:#3b82f6}.cb-file-size{color:#999}.cb-item-actions{display:flex;gap:6px;flex-shrink:0;padding-top:2px}.cb-action-btn{background:none;border:1px solid #ddd;border-radius:6px;padding:5px 10px;cursor:pointer;font-size:.78rem;transition:all .2s;white-space:nowrap}.cb-action-btn:hover{border-color:var(--theme-color);color:var(--theme-color)}.cb-action-btn.delete:hover{border-color:#ff4d4f;color:#ff4d4f}.cb-action-btn.download{text-decoration:none;color:inherit;display:inline-flex;align-items:center}.cb-pagination{display:flex;justify-content:center;gap:6px;margin-top:24px}.cb-page-btn{padding:6px 12px;border:1px solid #ddd;border-radius:6px;background:transparent;cursor:pointer;font-size:.82rem;transition:all .2s}.cb-page-btn.active{background:var(--theme-color);color:#fff;border-color:var(--theme-color)}.cb-page-btn:hover:not(.active){border-color:var(--theme-color)}.cb-copied{background:var(--theme-color)!important;color:#fff!important;border-color:var(--theme-color)!important}@media(max-width:640px){.cb-input-area{grid-template-columns:1fr}.cb-toolbar{flex-direction:column;align-items:stretch}.cb-toolbar-right{justify-content:space-between}.cb-item{flex-direction:column;gap:8px}.cb-item-actions{align-self:flex-end}} diff --git a/common/clipboard/clipboard.min.js b/common/clipboard/clipboard.min.js new file mode 100644 index 0000000..1f3d1e1 --- /dev/null +++ b/common/clipboard/clipboard.min.js @@ -0,0 +1 @@ +(function(){"use strict";var u=window.HOME+"/wp-admin/admin-ajax.php",d=1,p="",f=null;function l(n,e,o){var a=new FormData;return a.append("action",n),e&&(o?Object.keys(e).forEach(function(t){a.append(t,e[t])}):Object.keys(e).forEach(function(t){a.append(t,e[t])})),fetch(u,{method:"POST",body:a,credentials:"same-origin"}).then(function(t){return t.json()}).then(function(t){if(t.success)return t.data;throw new Error(t.data||"Error")})}function r(){var n=document.getElementById("cb-search").value.trim();l("clipboard_list",{page:d,search:n,type:p}).then(function(e){y(e.items,e.total),h(e.total,e.page,e.per_page),document.getElementById("cb-stats").textContent=e.total+" items"}).catch(function(){document.getElementById("cb-list").innerHTML='
Failed to load
'})}function y(n,e){var o=document.getElementById("cb-list");if(!n||n.length===0){o.innerHTML='
📋
'+(e===0?"No clipboard entries yet. Copy some text or upload a file!":"No results found")+"
";return}var a="";n.forEach(function(t){if(a+='
',t.type==="file"?a+='
'+B(t.filename)+"
":a+='
📄
',a+='
',t.type==="text"){var i=s(t.content||""),c=i.length>300;a+='
'+i+"
",c&&(a+='Show more')}else a+='
',a+=""+s(t.filename||"file")+"",a+="
";a+='
',a+=''+(t.type==="text"?"TEXT":"FILE")+"",t.type==="file"&&t.filesize&&(a+=''+I(t.filesize)+""),t.type==="text"&&t.content&&(a+=""+t.content.length+" chars"),a+=""+M(t.created_at)+"",a+="
",a+="
",a+='
',t.type==="text"?a+='':t.download_url&&(a+='⬇ Download'),a+='',a+="
",a+="
"}),o.innerHTML=a}function h(n,e,o){var a=document.getElementById("cb-pagination"),t=Math.ceil(n/o);if(t<=1){a.innerHTML="";return}var i="";e>1&&(i+='');for(var c=1;c<=t;c++){if(t>7&&c>3&&c1){(c===4||c===t-3)&&(i+='...');continue}i+='"}e→'),a.innerHTML=i}function v(){var n=document.getElementById("cb-paste-input"),e=n.value.trim();e&&(n.disabled=!0,l("clipboard_save",{content:e}).then(function(){n.value="",n.disabled=!1,d=1,r()}).catch(function(o){alert(o.message||"Save failed"),n.disabled=!1}))}function b(n){if(n.size>10*1024*1024){alert("File too large (max 10MB)");return}var e=document.getElementById("cb-upload-progress"),o=document.getElementById("cb-progress-bar");e.style.display="",o.style.width="0%";var a=new FormData;a.append("action","clipboard_upload"),a.append("file",n);var t=new XMLHttpRequest;t.open("POST",u),t.withCredentials=!0,t.upload.onprogress=function(i){i.lengthComputable&&(o.style.width=Math.round(i.loaded/i.total*100)+"%")},t.onload=function(){e.style.display="none",o.style.width="0%";try{var i=JSON.parse(t.responseText);i.success?(d=1,r()):alert(i.data||"Upload failed")}catch{alert("Upload failed")}},t.onerror=function(){e.style.display="none",alert("Upload failed")},t.send(a)}function x(n){var e=document.querySelector('.cb-item[data-id="'+n+'"]');if(e){var o=e.querySelector(".cb-item-content");if(o){var a=o.textContent;if(navigator.clipboard&&navigator.clipboard.writeText)navigator.clipboard.writeText(a).then(function(){m(n)});else{var t=document.createElement("textarea");t.value=a,t.style.position="fixed",t.style.opacity="0",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t),m(n)}}}}function m(n){var e=document.querySelector('.cb-action-btn[data-id="'+n+'"]');e&&(e.classList.add("cb-copied"),e.innerHTML="✓ Copied!",setTimeout(function(){e.classList.remove("cb-copied"),e.innerHTML="📋 Copy"},1500))}function w(n){confirm("Delete this entry?")&&l("clipboard_delete",{id:n}).then(function(){r()}).catch(function(e){alert(e.message||"Delete failed")})}function E(){confirm("Clear ALL clipboard entries? This cannot be undone.")&&l("clipboard_clear").then(function(){d=1,r()})}function L(n){var e=n.previousElementSibling;e.classList.contains("expanded")?(e.classList.remove("expanded"),n.textContent="Show more"):(e.classList.add("expanded"),n.textContent="Show less")}function T(n){d=n,r(),document.querySelector(".cb-container").scrollIntoView({behavior:"smooth"})}function s(n){var e=document.createElement("div");return e.textContent=n,e.innerHTML}function I(n){return n<1024?n+" B":n<1048576?(n/1024).toFixed(1)+" KB":(n/1048576).toFixed(1)+" MB"}function M(n){if(!n)return"";var e=new Date(n.replace(" ","T")),o=new Date,a=(o-e)/1e3;return a<60?"Just now":a<3600?Math.floor(a/60)+"m ago":a<86400?Math.floor(a/3600)+"h ago":a<604800?Math.floor(a/86400)+"d ago":e.getMonth()+1+"/"+e.getDate()+" "+String(e.getHours()).padStart(2,"0")+":"+String(e.getMinutes()).padStart(2,"0")}function B(n){if(!n)return"📄";var e=n.split(".").pop().toLowerCase(),o={pdf:"📕",doc:"📄",docx:"📄",xls:"📊",xlsx:"📊",csv:"📊",ppt:"📊",pptx:"📊",txt:"📄",log:"📄",md:"📄",zip:"📦",rar:"📦",gz:"📦",tar:"📦","7z":"📦",jpg:"🎨",jpeg:"🎨",png:"🎨",gif:"🎨",svg:"🎨",webp:"🎨",mp3:"🎵",wav:"🎵",flac:"🎵",mp4:"🎥",avi:"🎥",mkv:"🎥",js:"💻",py:"💻",php:"💻",java:"💻",c:"💻",cpp:"💻",html:"🌐",css:"🌐",json:"📄",xml:"📄",yaml:"📄",yml:"📄",sh:"💻",bat:"💻"};return o[e]||"📄"}function g(){r(),document.getElementById("cb-paste-btn").addEventListener("click",v),document.getElementById("cb-paste-input").addEventListener("keydown",function(o){o.key==="Enter"&&(o.ctrlKey||o.metaKey)&&v()});var n=document.getElementById("cb-upload-area"),e=document.getElementById("cb-file-input");n.addEventListener("click",function(o){o.target.tagName!=="LABEL"&&o.target.tagName!=="INPUT"&&e.click()}),e.addEventListener("change",function(){this.files[0]&&b(this.files[0]),this.value=""}),n.addEventListener("dragover",function(o){o.preventDefault(),n.classList.add("drag-over")}),n.addEventListener("dragleave",function(){n.classList.remove("drag-over")}),n.addEventListener("drop",function(o){o.preventDefault(),n.classList.remove("drag-over"),o.dataTransfer.files[0]&&b(o.dataTransfer.files[0])}),document.getElementById("cb-search").addEventListener("input",function(){clearTimeout(f),f=setTimeout(function(){d=1,r()},300)}),document.querySelectorAll(".cb-filter-btn").forEach(function(o){o.addEventListener("click",function(){document.querySelectorAll(".cb-filter-btn").forEach(function(a){a.classList.remove("active")}),o.classList.add("active"),p=o.dataset.type,d=1,r()})}),document.getElementById("cb-clear-btn").addEventListener("click",E)}window._cb={copyText:x,deleteItem:w,toggleExpand:L,goPage:T},document.readyState==="loading"?document.addEventListener("DOMContentLoaded",g):g()})(); diff --git a/common/doom/doom.css b/common/doom/doom.css new file mode 100644 index 0000000..2d65015 --- /dev/null +++ b/common/doom/doom.css @@ -0,0 +1,420 @@ +/* ===== Doom FPS Page Styles ===== */ + +/* Override theme content area for Doom page */ +.doom-page-wrapper .main-content { + background: transparent !important; + padding: 0 !important; + max-width: 100%; +} + +.doom-container { + max-width: 1000px; + margin: 0 auto; + padding: 20px; + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; +} + +/* Game Area */ +.doom-game { + position: relative; + width: 100%; + max-width: 960px; +} + +.doom-game canvas { + display: block; + width: 100%; + height: auto; + background: #111; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0,0,0,0.4); + cursor: crosshair; +} + +.doom-score-bar { + display: flex; + justify-content: center; + gap: 20px; + padding: 8px 16px; + background: rgba(0,0,0,0.7); + border-radius: 0 0 8px 8px; + color: #ccc; + font: 13px monospace; +} + +.doom-controls-hint { + text-align: center; + padding: 6px; + color: #888; + font-size: 12px; +} + +/* Overlays */ +.doom-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + /* Cover only the canvas area, not the hint text below */ + aspect-ratio: 16/9; + background: rgba(0,0,0,0.85); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + border-radius: 8px; + z-index: 10; + color: #fff; +} + +.doom-overlay h2 { + font: bold 48px monospace; + color: #c33; + margin-bottom: 10px; + text-shadow: 0 0 20px rgba(200,50,50,0.6); +} + +.doom-overlay .doom-subtitle { + font: 16px monospace; + color: #aaa; + margin-bottom: 24px; +} + +.doom-overlay input[type="text"] { + padding: 10px 16px; + border: 2px solid #555; + border-radius: 6px; + background: #222; + color: #fff; + font: 16px monospace; + width: 220px; + text-align: center; + margin-bottom: 16px; + outline: none; +} + +.doom-overlay input[type="text"]:focus { + border-color: #c33; +} + +.doom-btn { + padding: 12px 36px; + background: #c33; + color: #fff; + border: none; + border-radius: 6px; + font: bold 18px monospace; + cursor: pointer; + transition: background 0.2s; + margin: 6px; +} + +.doom-btn:hover { + background: #e44; +} + +.doom-result-stats { + display: flex; + gap: 24px; + margin: 16px 0; + font: 14px monospace; + color: #ccc; +} + +.doom-result-stats span { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +} + +.doom-result-stats .doom-stat-val { + font-size: 28px; + font-weight: bold; + color: #fff; +} + +.doom-result-stats .doom-stat-label { + font-size: 11px; + color: #888; + text-transform: uppercase; +} + +.doom-rank-display { + font: bold 24px monospace; + color: #fc0; + margin: 8px 0; +} + +/* Leaderboard — full width below game */ +.doom-leaderboard { + width: 100%; + max-width: 960px; + background: var(--theme-front-main-color, #fff); + border-radius: 12px; + box-shadow: 0 2px 12px rgba(0,0,0,0.08); + overflow: hidden; +} + +.doom-lb-header { + padding: 16px 18px 12px; + border-bottom: 1px solid #eee; + display: flex; + align-items: center; + justify-content: space-between; +} + +.doom-lb-header h3 { + margin: 0; + font-size: 1.1rem; + color: var(--theme-text-color, #333); +} + +.doom-lb-tabs { + display: flex; + gap: 6px; +} + +.doom-tab-btn { + padding: 5px 14px; + border: 1px solid #ddd; + border-radius: 20px; + background: transparent; + font-size: 0.8rem; + cursor: pointer; + color: #666; + transition: all 0.2s; +} + +.doom-tab-btn.active { + background: var(--theme-color, #c33); + color: #fff; + border-color: var(--theme-color, #c33); +} + +.doom-lb-body { + max-height: 400px; + overflow-y: auto; + padding: 8px 0; +} + +.doom-lb-row { + display: flex; + align-items: center; + padding: 8px 16px; + gap: 8px; + font-size: 0.85rem; + transition: background 0.15s; +} + +.doom-lb-row:hover { + background: rgba(0,0,0,0.03); +} + +.doom-lb-mine { + background: rgba(200,50,50,0.08) !important; + font-weight: 600; +} + +.doom-lb-medal-1 { background: #fff8e1; } +.doom-lb-medal-2 { background: #f5f5f5; } +.doom-lb-medal-3 { background: #fff3e0; } + +.doom-lb-rank { + width: 28px; + text-align: center; + font-size: 1.1rem; +} + +.doom-lb-num { + font-size: 0.8rem; + color: #999; +} + +.doom-lb-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--theme-text-color, #333); +} + +.doom-lb-score { + font-weight: 600; + color: var(--theme-color, #c33); + min-width: 45px; + text-align: right; +} + +.doom-lb-kills { + color: #888; + font-size: 0.75rem; + min-width: 30px; + text-align: right; +} + +.doom-lb-time { + color: #aaa; + font-size: 0.75rem; + min-width: 40px; + text-align: right; +} + +.doom-lb-empty { + text-align: center; + padding: 40px 20px; + color: #999; + font-size: 0.9rem; +} + +/* Responsive */ +@media (max-width: 480px) { + .doom-container { + padding: 10px; + } + .doom-overlay h2 { + font-size: 32px; + } + .doom-blog { + padding: 24px 16px; + } +} + +/* Blog article section */ +.doom-blog { + width: 100%; + max-width: 960px; + padding: 40px 30px; + margin-top: 8px; + background: var(--theme-front-main-color, #fff); + border-radius: 12px; + box-shadow: 0 2px 12px rgba(0,0,0,0.08); +} + +.doom-blog h2 { + margin-top: 2rem; +} + +.doom-blog h2:first-child { + margin-top: 0; +} + +.doom-blog p { + line-height: 1.8; + margin-bottom: 1rem; +} + +.doom-blog pre { + background: #1e1e2e; + color: #cdd6f4; + padding: 1rem; + border-radius: 8px; + overflow-x: auto; + margin: 1rem 0; + font-size: 0.85rem; + line-height: 1.6; +} + +.doom-blog code { + font-family: 'Cascadia Code', 'Fira Code', Consolas, monospace; +} + +.doom-blog p code { + background: var(--theme-code-bg, #f0f0f0); + padding: 0.15em 0.4em; + border-radius: 4px; + font-size: 0.88em; +} + +.doom-blog blockquote { + margin: 1rem 0; + padding: 0.75rem 1rem; + border-left: 4px solid var(--theme-color, #c33); + background: rgba(0,0,0,0.03); + border-radius: 0 6px 6px 0; +} + +.doom-blog blockquote p { + margin: 0.4rem 0; +} + +.doom-blog table { + border-collapse: collapse; + width: 100%; + margin: 1rem 0; +} + +.doom-blog table th, +.doom-blog table td { + padding: 0.5rem 0.75rem; + border: 1px solid #d0d7de; + text-align: left; +} + +.doom-blog table th { + background: rgba(0,0,0,0.04); + font-weight: 600; +} + +.doom-blog table tr:hover { + background: rgba(0,0,0,0.02); +} + +.doom-blog img { + max-width: 100%; + border-radius: 8px; + margin: 1rem 0; +} + +.doom-blog .doom-blog-header { + text-align: center; + margin-bottom: 2rem; + padding-bottom: 1.5rem; + border-bottom: 1px solid #eee; + position: relative; +} + +.doom-lang-btn { + position: absolute; + top: 0; + right: 0; + padding: 4px 14px; + border: 1px solid #ddd; + border-radius: 20px; + background: transparent; + font: bold 13px monospace; + cursor: pointer; + color: #666; + transition: all 0.2s; +} + +.doom-lang-btn:hover { + background: var(--theme-color, #c33); + color: #fff; + border-color: var(--theme-color, #c33); +} + +.doom-blog .doom-blog-header h1 { + font-size: 1.8rem; + margin: 0 0 0.5rem 0; + color: var(--theme-text-color, #333); +} + +.doom-blog .doom-blog-header .doom-blog-meta { + color: #999; + font-size: 0.85rem; +} + +.doom-blog .doom-section-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 1.3rem; + color: var(--theme-text-color, #333); + padding-bottom: 0.5rem; + border-bottom: 2px solid var(--theme-color, #c33); + margin-bottom: 1rem; +} diff --git a/common/doom/doom.js b/common/doom/doom.js new file mode 100644 index 0000000..11a80ee --- /dev/null +++ b/common/doom/doom.js @@ -0,0 +1,2073 @@ +/** + * Doom-Style FPS Game - Raycasting Engine + * Pure Canvas 2D, no external dependencies + * @author Haibin + * @date 2026-04-18 + */ +(function () { + 'use strict'; + + // ======================== CONFIG ======================== + var SCREEN_W = 960, SCREEN_H = 540; + var FOV = Math.PI / 3; // 60 degrees + var HALF_FOV = FOV / 2; + var BASE_FOV = FOV; + var MOVE_SPEED = 3.0; + var ROT_SPEED = 2.5; + var MOUSE_SENS = 0.003; + var PLAYER_RADIUS = 0.25; + var MAX_DEPTH = 24; + var TILE = 1; + + // ======================== LEVELS ======================== + // Each level: { map, entities, spawn: {x,y,angle} } + // Map tiles: 0=empty, 1-4=wall types, 5=locked door, 8=exit door, 9=thin wall + var LEVELS = [ + // === LEVEL 1 === + { + name: 'Level 1 - The Bunker', + spawn: { x: 1.5, y: 1.5, angle: 0 }, + map: [ + [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], + [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], + [1,0,0,2,2,2,0,0,0,0,3,0,3,0,0,0,0,0,4,4,4,0,0,1], + [1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,4,0,0,1], + [1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,1], + [1,0,0,0,0,0,0,0,0,0,3,0,3,0,0,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1], + [1,1,1,0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1], + [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,1], + [1,1,1,0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1], + [1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], + [1,0,0,3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,1], + [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,1], + [1,0,0,3,0,3,0,0,0,0,4,4,4,0,0,0,0,0,2,0,0,0,0,1], + [1,0,0,0,0,0,0,0,0,0,4,0,4,0,0,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,0,0,4,4,4,0,0,0,0,0,0,0,0,0,0,1], + [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], + ], + entities: [ + { type: 'guard', x: 5.5, y: 5.5 }, + { type: 'guard', x: 10.5, y: 2.5 }, + { type: 'guard', x: 18.5, y: 5.5 }, + { type: 'guard', x: 3.5, y: 17.5 }, + { type: 'guard', x: 15.5, y: 18.5 }, + { type: 'soldier', x: 10.5, y: 10.5 }, + { type: 'soldier', x: 20.5, y: 20.5 }, + { type: 'soldier', x: 5.5, y: 14.5 }, + { type: 'shotgun', x: 7.5, y: 3.5 }, + { type: 'machinegun', x: 19.5, y: 19.5 }, + { type: 'health', x: 11.5, y: 11.5 }, + { type: 'health', x: 3.5, y: 21.5 }, + { type: 'ammo', x: 15.5, y: 3.5 }, + { type: 'ammo', x: 20.5, y: 14.5 }, + ] + }, + // === LEVEL 2 === The Fortress: complex corridors, locked doors, more enemies + { + name: 'Level 2 - The Fortress', + spawn: { x: 1.5, y: 22.5, angle: 0 }, + map: [ + [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], + [1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,8,1], + [1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,3,0,2,2,2,2,0,3,0,4,4,4,4,0,0,0,0,0,1], + [1,0,0,0,0,0,0,2,0,0,2,0,0,0,4,0,0,4,0,0,0,0,0,1], + [1,3,3,3,3,3,0,2,0,0,2,0,3,3,4,0,0,4,0,3,3,3,3,1], + [1,0,0,0,0,0,0,2,0,0,0,0,0,0,4,0,0,4,0,0,0,0,0,1], + [1,0,0,0,0,0,0,2,2,5,2,2,0,0,4,4,5,4,0,0,0,0,0,1], + [1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,1], + [1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,1], + [1,1,1,5,1,1,0,0,1,1,1,1,1,1,1,1,0,0,1,1,5,1,1,1], + [1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1], + [1,1,1,0,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1], + [1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1], + [1,0,2,2,2,0,0,0,1,1,1,0,0,1,1,1,0,0,0,4,4,4,0,1], + [1,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,4,0,1], + [1,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,4,0,1], + [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], + [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], + [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], + ], + entities: [ + // Guards — more numerous + { type: 'guard', x: 2.5, y: 2.5 }, + { type: 'guard', x: 10.5, y: 2.5 }, + { type: 'guard', x: 16.5, y: 4.5 }, + { type: 'guard', x: 2.5, y: 8.5 }, + { type: 'guard', x: 21.5, y: 8.5 }, + { type: 'guard', x: 5.5, y: 15.5 }, + { type: 'guard', x: 18.5, y: 15.5 }, + // Soldiers — tougher + { type: 'soldier', x: 9.5, y: 5.5 }, + { type: 'soldier', x: 15.5, y: 5.5 }, + { type: 'soldier', x: 11.5, y: 12.5 }, + { type: 'soldier', x: 3.5, y: 18.5 }, + { type: 'soldier', x: 20.5, y: 18.5 }, + { type: 'soldier', x: 20.5, y: 2.5 }, + // Pickups + { type: 'health', x: 1.5, y: 6.5 }, + { type: 'health', x: 22.5, y: 20.5 }, + { type: 'health', x: 11.5, y: 15.5 }, + { type: 'ammo', x: 9.5, y: 9.5 }, + { type: 'ammo', x: 18.5, y: 9.5 }, + { type: 'ammo', x: 12.5, y: 18.5 }, + { type: 'shotgun', x: 3.5, y: 11.5 }, + { type: 'machinegun', x: 20.5, y: 11.5 }, + { type: 'sniper', x: 9.5, y: 12.5 }, + { type: 'm4a1', x: 14.5, y: 12.5 }, + ] + } + ]; + + // Active map data (loaded from current level) + var MAP, MAP_H, MAP_W, ENTITIES_DEF; + var currentLevel = 0; + var levelScore = 0; // accumulated score from previous levels + + function loadLevel(levelIdx) { + var lvl = LEVELS[levelIdx]; + MAP = lvl.map; + MAP_H = MAP.length; + MAP_W = MAP[0].length; + ENTITIES_DEF = lvl.entities; + } + + // Wall colors by type + var WALL_COLORS = { + 1: { r: 120, g: 120, b: 120 }, // grey concrete + 2: { r: 140, g: 60, b: 60 }, // dark red brick + 3: { r: 60, g: 100, b: 140 }, // blue steel + 4: { r: 80, g: 130, b: 60 }, // green moss + 5: { r: 100, g: 70, b: 40 }, // locked door (brown) + 8: { r: 180, g: 140, b: 40 }, // gold exit door + 9: { r: 100, g: 100, b: 100 }, // thin wall + }; + + var CEIL_COLOR = '#222233'; + var FLOOR_COLOR = '#444433'; + + // ======================== WEAPONS ======================== + var WEAPONS = { + knife: { name: 'Knife', damage: 50, fireRate: 500, ammo: Infinity, spread: 0, color: '#ccc', auto: false, melee: true, range: 1.0 }, + pistol: { name: 'Pistol', damage: 15, fireRate: 400, ammo: Infinity, spread: 0, color: '#aaa', auto: false }, + shotgun: { name: 'Shotgun', damage: 80, fireRate: 800, ammo: 20, spread: 0.15, color: '#c84', auto: false }, + machinegun: { name: 'Machine Gun', damage: 15, fireRate: 100, ammo: 100, spread: 0.05, color: '#4a4', auto: true }, + sniper: { name: 'Sniper', damage: 150, fireRate: 1200, ammo: 10, spread: 0, color: '#448', auto: false }, + m4a1: { name: 'M4A1', damage: 25, fireRate: 120, ammo: 120, spread: 0.04, color: '#886', auto: true }, + }; + + // ======================== STATE ======================== + var canvas, ctx, miniCanvas, miniCtx; + var audioCtx = null; // Web Audio context, created on first user interaction + var gameState = 'MENU'; // MENU, PLAYING, WIN, GAMEOVER + var player, entities, depthBuf; + var keys = {}; + var mouseMovX = 0; + var pointerLocked = false; + var lastTime = 0, dt = 0, gameTime = 0; + var kills = 0, totalEnemies = 0, coins = 0; + var currentWeapon = 'pistol'; + var weaponAmmo = {}; + var hasWeapon = {}; + var lastFireTime = 0; + var weaponAnim = 0; // 0-1 fire animation progress + var showMinimap = true; + var playerName = ''; + var damageFlash = 0; + var isFiring = false; + var projectiles = []; // enemy bullet projectiles + var floatingTexts = []; // damage numbers {x, y, z, text, life, color} + var shakeIntensity = 0; // screen shake + var grenadeCount = 0; + var grenades = []; // active grenade projectiles {x, y, dx, dy, life} + var sniperZoom = false; + + // Leaderboard + var leaderboardData = []; + var leaderboardType = 'alltime'; + + // ======================== MD5 (for anti-cheat) ======================== + function md5(s){function L(k,d){return(k<>>(32-d))}function K(G,k){var I,d,F,H,x;F=(G&2147483648);H=(k&2147483648);I=(G&1073741824);d=(k&1073741824);x=(G&1073741823)+(k&1073741823);if(I&d)return(x^2147483648^F^H);if(I|d){if(x&1073741824)return(x^3221225472^F^H);else return(x^1073741824^F^H)}else return(x^F^H)}function w(a,b,c){return(a&b)|((~a)&c)}function v(a,b,c){return(a&c)|(b&(~c))}function u(a,b,c){return(a^b^c)}function t(a,b,c){return(b^(a|(~c)))}function C(a,b,c,d,e,f,g){a=K(a,K(K(w(b,c,d),e),g));return K(L(a,f),b)}function B(a,b,c,d,e,f,g){a=K(a,K(K(v(b,c,d),e),g));return K(L(a,f),b)}function A(a,b,c,d,e,f,g){a=K(a,K(K(u(b,c,d),e),g));return K(L(a,f),b)}function z(a,b,c,d,e,f,g){a=K(a,K(K(t(b,c,d),e),g));return K(L(a,f),b)}function D(str){var r='',i,c;for(i=0;i>8)&0xFF)}return r}function E(str){var arr=[],len=str.length*8,i;for(i=0;i>5]|=(str.charCodeAt(i/8)&0xFF)<<(i%32);return arr}function F(arr,len){arr[len>>5]|=0x80<<(len%32);arr[(((len+64)>>>9)<<4)+14]=len;var a=1732584193,b=-271733879,c=-1732584194,d=271733878,i;for(i=0;i>(j*8+4))&0x0F)+hex.charAt((n>>(j*8))&0x0F)}return out}var utf8=[];for(var i=0;i>6));utf8.push(0x80|(code&0x3F))}else{utf8.push(0xE0|(code>>12));utf8.push(0x80|((code>>6)&0x3F));utf8.push(0x80|(code&0x3F))}}var str='';for(var i=0;i -200 && mx < 200) { + mouseMovX += mx; + } + } + }); + document.addEventListener('mousedown', function (e) { + if (pointerLocked && e.button === 0) isFiring = true; + if (pointerLocked && e.button === 2 && gameState === 'PLAYING') { + if (currentWeapon === 'sniper') { + sniperZoom = !sniperZoom; + } + } + }); + document.addEventListener('mouseup', function (e) { + if (e.button === 0) isFiring = false; + }); + // Prevent context menu + canvas.addEventListener('contextmenu', function (e) { e.preventDefault(); }); + + // Scroll wheel weapon cycle + canvas.addEventListener('wheel', function (e) { + if (gameState !== 'PLAYING') return; + e.preventDefault(); + var order = ['knife', 'pistol', 'shotgun', 'machinegun', 'sniper', 'm4a1']; + var owned = order.filter(function (w) { return hasWeapon[w]; }); + var idx = owned.indexOf(currentWeapon); + if (e.deltaY > 0) idx = (idx + 1) % owned.length; + else idx = (idx - 1 + owned.length) % owned.length; + switchWeapon(owned[idx]); + }, { passive: false }); + + // UI buttons + var startBtn = document.getElementById('doom-start-btn'); + if (startBtn) startBtn.addEventListener('click', startGame); + + var replayBtn = document.getElementById('doom-replay-btn'); + if (replayBtn) replayBtn.addEventListener('click', startGame); + + var replayBtn2 = document.getElementById('doom-replay-btn2'); + if (replayBtn2) replayBtn2.addEventListener('click', startGame); + + // Leaderboard tabs + document.querySelectorAll('.doom-tab-btn').forEach(function (btn) { + btn.addEventListener('click', function () { + leaderboardType = btn.dataset.type; + document.querySelectorAll('.doom-tab-btn').forEach(function (b) { b.classList.remove('active'); }); + btn.classList.add('active'); + loadLeaderboard(); + }); + }); + + loadLeaderboard(); + requestAnimationFrame(gameLoop); + } + + function startGame() { + var nameInput = document.getElementById('doom-name-input'); + if (nameInput) { + playerName = nameInput.value.trim().substring(0, 20); + if (!playerName) { alert('Please enter your name'); return; } + localStorage.setItem('doom_player_name', playerName); + } + + currentLevel = 0; + levelScore = 0; + initLevel(currentLevel, true); + + initAudio(); + gameState = 'PLAYING'; + hideOverlays(); + canvas.requestPointerLock(); + } + + // Initialize a level. fullReset=true resets weapons/ammo (new game), false=keep (level transition) + function initLevel(levelIdx, fullReset) { + loadLevel(levelIdx); + var lvl = LEVELS[levelIdx]; + + // Reset player position + player = { x: lvl.spawn.x, y: lvl.spawn.y, angle: lvl.spawn.angle, hp: fullReset ? 100 : player.hp }; + + if (fullReset) { + currentWeapon = 'pistol'; + hasWeapon = { knife: true, pistol: true, shotgun: false, machinegun: false, sniper: false, m4a1: false }; + weaponAmmo = { knife: Infinity, pistol: Infinity, shotgun: 0, machinegun: 0, sniper: 0, m4a1: 0 }; + kills = 0; + coins = 0; + gameTime = 0; + } + lastFireTime = 0; + weaponAnim = 0; + damageFlash = 0; + isFiring = false; + projectiles = []; + floatingTexts = []; + grenades = []; + // Give grenades on level 2+ + if (fullReset) grenadeCount = 0; + if (levelIdx >= 1) grenadeCount += 3; + + // Clone entities + entities = []; + totalEnemies = 0; + ENTITIES_DEF.forEach(function (e) { + // Skip entities placed inside walls + var gx = Math.floor(e.x), gy = Math.floor(e.y); + if (gx < 0 || gy < 0 || gx >= MAP_W || gy >= MAP_H || MAP[gy][gx] !== 0) return; + var ent = { type: e.type, x: e.x, y: e.y, alive: true }; + if (e.type === 'guard') { + ent.hp = 50; ent.speed = 1.5; ent.damage = 8; ent.range = 8; + ent.fireRate = 1000; ent.lastFire = 0; + ent.color = '#c44'; ent.alertColor = '#f66'; + ent.state = 'IDLE'; ent.alertTimer = 0; + ent.patrolAngle = Math.random() * Math.PI * 2; + totalEnemies++; + } else if (e.type === 'soldier') { + ent.hp = 100; ent.speed = 2.2; ent.damage = 12; ent.range = 12; + ent.fireRate = 700; ent.lastFire = 0; + ent.color = '#66c'; ent.alertColor = '#88f'; + ent.state = 'IDLE'; ent.alertTimer = 0; + ent.patrolAngle = Math.random() * Math.PI * 2; + totalEnemies++; + } else if (e.type === 'shotgun') { + ent.pickupType = 'shotgun'; ent.color = '#c84'; + } else if (e.type === 'machinegun') { + ent.pickupType = 'machinegun'; ent.color = '#4a4'; + } else if (e.type === 'sniper') { + ent.pickupType = 'sniper'; ent.color = '#448'; + } else if (e.type === 'm4a1') { + ent.pickupType = 'm4a1'; ent.color = '#886'; + } else if (e.type === 'health') { + ent.pickupType = 'health'; ent.color = '#f44'; + } else if (e.type === 'ammo') { + ent.pickupType = 'ammo'; ent.color = '#fa0'; + } + entities.push(ent); + }); + } + + function switchWeapon(w) { + if (hasWeapon[w] && currentWeapon !== w) { + currentWeapon = w; + weaponAnim = 0; + if (w !== 'sniper') sniperZoom = false; + } + } + + function cycleWeapon() { + var order = ['knife', 'pistol', 'shotgun', 'machinegun', 'sniper', 'm4a1']; + var owned = order.filter(function (w) { return hasWeapon[w]; }); + if (owned.length <= 1) return; + var idx = (owned.indexOf(currentWeapon) + 1) % owned.length; + switchWeapon(owned[idx]); + } + + function hideOverlays() { + var ids = ['doom-start-overlay', 'doom-gameover-overlay', 'doom-win-overlay']; + ids.forEach(function (id) { + var el = document.getElementById(id); + if (el) el.style.display = 'none'; + }); + } + + function showOverlay(id) { + var el = document.getElementById(id); + if (el) el.style.display = 'flex'; + } + + // ======================== GAME LOOP ======================== + function gameLoop(timestamp) { + dt = Math.min((timestamp - lastTime) / 1000, 0.05); + lastTime = timestamp; + + if (gameState === 'PLAYING') { + gameTime += dt; + processInput(); + updateEntities(); + updateProjectiles(); + checkPickups(); + checkWin(); + updateFloatingTexts(dt); + updateGrenades(); + } + + render(); + requestAnimationFrame(gameLoop); + } + + // ======================== INPUT ======================== + function processInput() { + var moveX = 0, moveY = 0; + var cos = Math.cos(player.angle), sin = Math.sin(player.angle); + + // Forward / back + if (keys['KeyW'] || keys['ArrowUp']) { moveX += cos * MOVE_SPEED * dt; moveY += sin * MOVE_SPEED * dt; } + if (keys['KeyS'] || keys['ArrowDown']) { moveX -= cos * MOVE_SPEED * dt; moveY -= sin * MOVE_SPEED * dt; } + // Strafe + if (keys['KeyA']) { moveX += sin * MOVE_SPEED * dt; moveY -= cos * MOVE_SPEED * dt; } + if (keys['KeyD']) { moveX -= sin * MOVE_SPEED * dt; moveY += cos * MOVE_SPEED * dt; } + + // Rotation via keyboard + if (keys['ArrowLeft']) player.angle -= ROT_SPEED * dt; + if (keys['ArrowRight']) player.angle += ROT_SPEED * dt; + + // Mouse rotation — direct apply, outliers already filtered in mousemove + if (pointerLocked) { + player.angle += mouseMovX * MOUSE_SENS; + mouseMovX = 0; + } + + // Normalize angle to prevent jumps + player.angle = player.angle % (2 * Math.PI); + if (player.angle < 0) player.angle += 2 * Math.PI; + + // Collision-checked movement + if (moveX !== 0 || moveY !== 0) { + var newX = player.x + moveX; + var newY = player.y + moveY; + // Slide along walls + if (!isWall(newX, player.y)) player.x = newX; + if (!isWall(player.x, newY)) player.y = newY; + + // Footstep sound every 0.35s while moving + var now2 = performance.now(); + if (now2 - lastStepTime > 350) { + playSound('step'); + lastStepTime = now2; + } + } + + // Shooting + var now = performance.now(); + var wp = WEAPONS[currentWeapon]; + var wantShoot = isFiring || keys['Space']; + if (wantShoot && now - lastFireTime > wp.fireRate && weaponAmmo[currentWeapon] > 0) { + shoot(); + lastFireTime = now; + if (weaponAmmo[currentWeapon] !== Infinity) weaponAmmo[currentWeapon]--; + } + + // Weapon animation decay + if (weaponAnim > 0) weaponAnim = Math.max(0, weaponAnim - dt * 6); + if (damageFlash > 0) damageFlash = Math.max(0, damageFlash - dt * 4); + } + + function isWall(x, y) { + // Check with player radius + var checks = [ + [x - PLAYER_RADIUS, y - PLAYER_RADIUS], + [x + PLAYER_RADIUS, y - PLAYER_RADIUS], + [x - PLAYER_RADIUS, y + PLAYER_RADIUS], + [x + PLAYER_RADIUS, y + PLAYER_RADIUS], + ]; + for (var i = 0; i < checks.length; i++) { + var mx = Math.floor(checks[i][0]), my = Math.floor(checks[i][1]); + if (mx < 0 || my < 0 || mx >= MAP_W || my >= MAP_H) return true; + if (MAP[my][mx] !== 0) return true; + } + // Check enemy collision + for (var i = 0; i < entities.length; i++) { + var e = entities[i]; + if (!e.alive || e.pickupType) continue; + var dx = x - e.x, dy = y - e.y; + if (Math.sqrt(dx * dx + dy * dy) < 0.7) return true; + } + return false; + } + + // ======================== SHOOTING ======================== + function shoot() { + weaponAnim = 1.0; + var wp = WEAPONS[currentWeapon]; + playSound(currentWeapon); + if (currentWeapon === 'shotgun') shakeIntensity = Math.max(shakeIntensity, 0.3); + // Alert nearby enemies + alertNearbyEnemies(player.x, player.y, wp.melee ? 5 : 10); + + if (wp.melee) { + // Melee attack: hit closest enemy within range and in front of player + knifeStab(wp.damage, wp.range); + } else if (currentWeapon === 'shotgun') { + // Shotgun: multiple pellets + for (var p = 0; p < 5; p++) { + var spread = (Math.random() - 0.5) * wp.spread * 2; + castBullet(player.angle + spread, wp.damage / 5); + } + } else { + var spread = (Math.random() - 0.5) * wp.spread * 2; + castBullet(player.angle + spread, wp.damage); + } + } + + // Spawn a floating damage number at world position + function spawnFloatText(x, y, text, color) { + floatingTexts.push({ x: x, y: y, z: 0.5, text: String(text), life: 1.0, color: color || '#ff0' }); + } + + function updateFloatingTexts(dt) { + for (var i = floatingTexts.length - 1; i >= 0; i--) { + var ft = floatingTexts[i]; + ft.z += dt * 1.2; // float upward + ft.life -= dt; + if (ft.life <= 0) floatingTexts.splice(i, 1); + } + } + + function knifeStab(damage, range) { + var best = null, bestDist = range + 0.1; + var cos = Math.cos(player.angle), sin = Math.sin(player.angle); + for (var i = 0; i < entities.length; i++) { + var e = entities[i]; + if (!e.alive || e.pickupType || e.hp <= 0) continue; + var dx = e.x - player.x, dy = e.y - player.y; + var dist = Math.sqrt(dx * dx + dy * dy); + if (dist > range) continue; + // Must be roughly in front (within ~90 degrees) + var dot = dx * cos + dy * sin; + if (dot < 0) continue; + if (dist < bestDist) { bestDist = dist; best = e; } + } + if (best) { + best.hp -= damage; + spawnFloatText(best.x, best.y, damage, '#ff0'); + best.state = 'CHASE'; + best.alertTimer = 5; + if (best.hp <= 0) { + best.alive = false; + best.state = 'DEAD'; + kills++; + playSound('kill'); + // Drop a coin + entities.push({ type: 'coin', x: best.x, y: best.y, alive: true, pickupType: 'coin', color: '#fc0' }); + } else { + playSound('hit'); + } + } + } + + function castBullet(angle, damage) { + // Raycast to find first enemy hit + var rayX = Math.cos(angle), rayY = Math.sin(angle); + var best = null, bestDist = MAX_DEPTH; + + for (var i = 0; i < entities.length; i++) { + var e = entities[i]; + if (!e.alive || e.pickupType || e.hp <= 0) continue; + + var dx = e.x - player.x, dy = e.y - player.y; + var dist = Math.sqrt(dx * dx + dy * dy); + if (dist > bestDist) continue; + + // Project enemy onto ray + var dot = dx * rayX + dy * rayY; + if (dot < 0.1) continue; // behind player + + // Perpendicular distance from ray to enemy center + var perpDist = Math.abs(dx * rayY - dy * rayX); + var hitRadius = dist < 1.5 ? 0.6 : 0.4; // larger hitbox at close range + + if (perpDist < hitRadius) { + // Check wall between player and enemy + if (!wallBetween(player.x, player.y, e.x, e.y, dot)) { + bestDist = dot; + best = e; + } + } + } + + if (best) { + best.hp -= damage; + spawnFloatText(best.x, best.y, damage, '#ff0'); + best.state = 'CHASE'; + best.alertTimer = 5; + if (best.hp <= 0) { + best.alive = false; + best.state = 'DEAD'; + kills++; + playSound('kill'); + // Drop a coin + entities.push({ type: 'coin', x: best.x, y: best.y, alive: true, pickupType: 'coin', color: '#fc0' }); + } else { + playSound('hit'); + } + } + } + + function wallBetween(x1, y1, x2, y2, maxDist) { + var dx = x2 - x1, dy = y2 - y1; + var dist = Math.sqrt(dx * dx + dy * dy); + var steps = Math.ceil(dist * 3); + for (var i = 1; i < steps; i++) { + var t = i / steps; + var cx = x1 + dx * t, cy = y1 + dy * t; + var mx = Math.floor(cx), my = Math.floor(cy); + if (mx >= 0 && my >= 0 && mx < MAP_W && my < MAP_H && MAP[my][mx] !== 0 && MAP[my][mx] !== 8) return true; + } + return false; + } + + function alertNearbyEnemies(x, y, radius) { + entities.forEach(function (e) { + if (!e.alive || e.pickupType) return; + var dx = e.x - x, dy = e.y - y; + if (Math.sqrt(dx * dx + dy * dy) < radius) { + if (e.state === 'IDLE') { + e.state = 'ALERT'; + e.alertTimer = 3; + } + } + }); + } + + // ======================== ENTITIES ======================== + function updateEntities() { + entities.forEach(function (e) { + if (!e.alive || e.pickupType) return; + + var dx = player.x - e.x, dy = player.y - e.y; + var dist = Math.sqrt(dx * dx + dy * dy); + var angleToPlayer = Math.atan2(dy, dx); + var now = performance.now(); + + // Line of sight check + var canSee = dist < e.range && !wallBetween(e.x, e.y, player.x, player.y, dist); + + switch (e.state) { + case 'IDLE': + // Slow patrol movement + e.patrolAngle += (Math.random() - 0.5) * dt; + var px = e.x + Math.cos(e.patrolAngle) * e.speed * 0.3 * dt; + var py = e.y + Math.sin(e.patrolAngle) * e.speed * 0.3 * dt; + if (!isWallForEnemy(px, py, e)) { e.x = px; e.y = py; } + else e.patrolAngle += Math.PI / 2; + + if (canSee) { e.state = 'CHASE'; e.alertTimer = 5; } + break; + + case 'ALERT': + e.alertTimer -= dt; + if (canSee) { e.state = 'CHASE'; e.alertTimer = 5; } + else if (e.alertTimer <= 0) e.state = 'IDLE'; + break; + + case 'CHASE': + e.alertTimer -= dt; + // Move toward player + var mx = e.x + Math.cos(angleToPlayer) * e.speed * dt; + var my = e.y + Math.sin(angleToPlayer) * e.speed * dt; + if (!isWallForEnemy(mx, my, e)) { e.x = mx; e.y = my; } + + // Melee attack when very close + if (dist < 0.8 && now - e.lastFire > e.fireRate) { + player.hp -= e.damage * 1.5; + damageFlash = 1.0; + playSound('hurt'); + shakeIntensity = Math.max(shakeIntensity, 0.5); + if (player.hp <= 0) { + player.hp = 0; + gameState = 'GAMEOVER'; + var goScore = document.getElementById('doom-go-score'); + if (goScore) goScore.textContent = levelScore + calcScore(); + var goKills = document.getElementById('doom-go-kills'); + if (goKills) goKills.textContent = kills + '/' + totalEnemies; + var goTime = document.getElementById('doom-go-time'); + if (goTime) goTime.textContent = formatTime(gameTime); + showOverlay('doom-gameover-overlay'); + document.exitPointerLock(); + submitScore(); + } + } + // Ranged attack if in range and can see + else if (canSee && dist < e.range && now - e.lastFire > e.fireRate) { + // Spawn a projectile toward player + var spread = (Math.random() - 0.5) * 0.15; + projectiles.push({ + x: e.x, y: e.y, + dx: Math.cos(angleToPlayer + spread) * 6, + dy: Math.sin(angleToPlayer + spread) * 6, + damage: e.damage, + life: 3, // seconds before despawn + color: e.type === 'soldier' ? '#88f' : '#fa0', + }); + e.lastFire = now; + } + + if (!canSee && e.alertTimer <= 0) e.state = 'IDLE'; + else if (canSee) e.alertTimer = 5; + break; + } + }); + } + + function isWallForEnemy(x, y, self) { + // Check with radius (0.3) to prevent clipping into walls + var r = 0.3; + var checks = [[x-r,y-r],[x+r,y-r],[x-r,y+r],[x+r,y+r],[x,y]]; + for (var c = 0; c < checks.length; c++) { + var mx = Math.floor(checks[c][0]), my = Math.floor(checks[c][1]); + if (mx < 0 || my < 0 || mx >= MAP_W || my >= MAP_H) return true; + if (MAP[my][mx] !== 0 && MAP[my][mx] !== 8) return true; + } + // Don't overlap other enemies + for (var i = 0; i < entities.length; i++) { + var e = entities[i]; + if (e === self || !e.alive || e.pickupType) continue; + var dx = x - e.x, dy = y - e.y; + if (Math.sqrt(dx * dx + dy * dy) < 0.6) return true; + } + // Don't walk into player + var pdx = x - player.x, pdy = y - player.y; + if (Math.sqrt(pdx * pdx + pdy * pdy) < 0.7) return true; + return false; + } + + // ======================== PROJECTILES ======================== + function updateProjectiles() { + for (var i = projectiles.length - 1; i >= 0; i--) { + var p = projectiles[i]; + p.life -= dt; + if (p.life <= 0) { projectiles.splice(i, 1); continue; } + + p.x += p.dx * dt; + p.y += p.dy * dt; + + // Hit wall? + var mx = Math.floor(p.x), my = Math.floor(p.y); + if (mx < 0 || my < 0 || mx >= MAP_W || my >= MAP_H || MAP[my][mx] !== 0) { + projectiles.splice(i, 1); + continue; + } + + // Hit player? + var dx = p.x - player.x, dy = p.y - player.y; + if (Math.sqrt(dx * dx + dy * dy) < 0.4) { + player.hp -= p.damage; + damageFlash = 1.0; + playSound('hurt'); + shakeIntensity = Math.max(shakeIntensity, 0.5); + projectiles.splice(i, 1); + if (player.hp <= 0) { + player.hp = 0; + gameState = 'GAMEOVER'; + var goScore = document.getElementById('doom-go-score'); + if (goScore) goScore.textContent = levelScore + calcScore(); + var goKills = document.getElementById('doom-go-kills'); + if (goKills) goKills.textContent = kills + '/' + totalEnemies; + var goTime = document.getElementById('doom-go-time'); + if (goTime) goTime.textContent = formatTime(gameTime); + showOverlay('doom-gameover-overlay'); + document.exitPointerLock(); + submitScore(); + return; + } + } + } + } + + function checkPickups() { + entities.forEach(function (e) { + if (!e.alive || !e.pickupType) return; + var dx = player.x - e.x, dy = player.y - e.y; + if (Math.sqrt(dx * dx + dy * dy) < 0.6) { + var picked = false; + if (e.pickupType === 'shotgun') { + hasWeapon.shotgun = true; + weaponAmmo.shotgun += 20; + switchWeapon('shotgun'); + e.alive = false; picked = true; + } else if (e.pickupType === 'machinegun') { + hasWeapon.machinegun = true; + weaponAmmo.machinegun += 100; + switchWeapon('machinegun'); + e.alive = false; picked = true; + } else if (e.pickupType === 'health') { + if (player.hp < 100) { + player.hp = Math.min(100, player.hp + 30); + e.alive = false; picked = true; + } + } else if (e.pickupType === 'ammo') { + if (hasWeapon.shotgun) weaponAmmo.shotgun += 10; + if (hasWeapon.machinegun) weaponAmmo.machinegun += 50; + if (hasWeapon.sniper) weaponAmmo.sniper += 5; + if (hasWeapon.m4a1) weaponAmmo.m4a1 += 60; + e.alive = false; picked = true; + } else if (e.pickupType === 'sniper') { + hasWeapon.sniper = true; + weaponAmmo.sniper += 10; + switchWeapon('sniper'); + e.alive = false; picked = true; + } else if (e.pickupType === 'm4a1') { + hasWeapon.m4a1 = true; + weaponAmmo.m4a1 += 120; + switchWeapon('m4a1'); + e.alive = false; picked = true; + } else if (e.pickupType === 'coin') { + coins++; + levelScore += 50; + spawnFloatText(e.x, e.y, '+50 🪙', '#fc0'); + e.alive = false; picked = true; + } + if (picked) playSound('pickup'); + } + }); + } + + // Try to open a locked door (tile 5) near the player + function tryOpenDoor() { + var px = Math.floor(player.x), py = Math.floor(player.y); + var checks = [[px,py],[px+1,py],[px-1,py],[px,py+1],[px,py-1], + [px+1,py+1],[px-1,py-1],[px+1,py-1],[px-1,py+1]]; + for (var i = 0; i < checks.length; i++) { + var cx = checks[i][0], cy = checks[i][1]; + if (cx >= 0 && cy >= 0 && cx < MAP_W && cy < MAP_H && MAP[cy][cx] === 5) { + var dx = (cx + 0.5) - player.x, dy = (cy + 0.5) - player.y; + if (Math.sqrt(dx * dx + dy * dy) < 1.5) { + MAP[cy][cx] = 0; // open the door + playSound('door'); + return; + } + } + } + } + + // Throw a grenade + function throwGrenade() { + if (grenadeCount <= 0) return; + grenadeCount--; + var cos = Math.cos(player.angle), sin = Math.sin(player.angle); + // Player velocity from current movement keys + var pvx = 0, pvy = 0; + if (keys['KeyW'] || keys['ArrowUp']) { pvx += cos * MOVE_SPEED; pvy += sin * MOVE_SPEED; } + if (keys['KeyS'] || keys['ArrowDown']) { pvx -= cos * MOVE_SPEED; pvy -= sin * MOVE_SPEED; } + if (keys['KeyA']) { pvx += sin * MOVE_SPEED; pvy -= cos * MOVE_SPEED; } + if (keys['KeyD']) { pvx -= sin * MOVE_SPEED; pvy += cos * MOVE_SPEED; } + grenades.push({ + x: player.x + cos * 0.5, + y: player.y + sin * 0.5, + dx: cos * 6 + pvx, + dy: sin * 6 + pvy, + z: 0.5, + dz: 3.0, + life: 3.0, + bounces: 0 + }); + playSound('door'); + } + + function updateGrenades() { + var GRAVITY = 9.8; + var BOUNCE_DAMPEN = 0.45; // energy retained on bounce + var FRICTION = 0.97; // ground friction per frame + var WALL_BOUNCE = 0.5; // wall bounce energy + + for (var i = grenades.length - 1; i >= 0; i--) { + var g = grenades[i]; + g.life -= dt; + + // Apply gravity to vertical velocity + g.dz -= GRAVITY * dt; + g.z += g.dz * dt; + + // Ground bounce + if (g.z <= 0) { + g.z = 0; + if (Math.abs(g.dz) > 0.5) { + // Bounce! + g.dz = -g.dz * BOUNCE_DAMPEN; + g.bounces++; + // Reduce horizontal speed on bounce + g.dx *= 0.7; + g.dy *= 0.7; + // Bounce sound (quieter each time) + if (g.bounces <= 3) playSound('step'); + } else { + // Settled on ground + g.dz = 0; + g.z = 0; + // Rolling friction + g.dx *= 0.92; + g.dy *= 0.92; + } + } + + // Horizontal movement with wall collision/bounce + var nx = g.x + g.dx * dt; + var ny = g.y + g.dy * dt; + + var mxN = Math.floor(nx), myN = Math.floor(ny); + var mxO = Math.floor(g.x), myO = Math.floor(g.y); + + // Check X axis wall collision + var hitX = false, hitY = false; + if (mxN < 0 || mxN >= MAP_W || MAP[myO][mxN] !== 0) { + g.dx = -g.dx * WALL_BOUNCE; + nx = g.x; + hitX = true; + } + // Check Y axis wall collision + if (myN < 0 || myN >= MAP_H || MAP[myN][Math.floor(nx)] !== 0) { + g.dy = -g.dy * WALL_BOUNCE; + ny = g.y; + hitY = true; + } + if (hitX || hitY) { + g.bounces++; + if (g.bounces <= 3) playSound('step'); + } + + g.x = nx; + g.y = ny; + + // Air friction + if (g.z > 0.01) { + g.dx *= FRICTION; + g.dy *= FRICTION; + } + + if (g.life <= 0) { + explodeGrenade(g.x, g.y); + grenades.splice(i, 1); + } + } + } + + function explodeGrenade(ex, ey) { + var radius = 2.5; + shakeIntensity = Math.max(shakeIntensity, 0.8); + damageFlash = 0.3; + playSound('explosion'); + // Damage all enemies in radius + for (var i = 0; i < entities.length; i++) { + var e = entities[i]; + if (!e.alive || e.pickupType || e.hp === undefined) continue; + var dx = e.x - ex, dy = e.y - ey; + var dist = Math.sqrt(dx * dx + dy * dy); + if (dist < radius) { + var dmg = Math.floor(120 * (1 - dist / radius)); + e.hp -= dmg; + spawnFloatText(e.x, e.y, dmg, '#f80'); + e.state = 'CHASE'; + e.alertTimer = 5; + if (e.hp <= 0) { + e.alive = false; + e.state = 'DEAD'; + kills++; + playSound('kill'); + entities.push({ type: 'coin', x: e.x, y: e.y, alive: true, pickupType: 'coin', color: '#fc0' }); + } + } + } + // Damage player if close + var pd = Math.sqrt((player.x - ex) * (player.x - ex) + (player.y - ey) * (player.y - ey)); + if (pd < radius) { + var pdmg = Math.floor(60 * (1 - pd / radius)); + player.hp -= pdmg; + damageFlash = 1.0; + } + } + + function checkWin() { + // Check if player is near exit door (tile 8) + var mx = Math.floor(player.x), my = Math.floor(player.y); + // Check adjacent tiles for exit door + var checks = [[mx,my],[mx+1,my],[mx-1,my],[mx,my+1],[mx,my-1]]; + for (var i = 0; i < checks.length; i++) { + var cx = checks[i][0], cy = checks[i][1]; + if (cx >= 0 && cy >= 0 && cx < MAP_W && cy < MAP_H && MAP[cy][cx] === 8) { + var dx = (cx + 0.5) - player.x, dy = (cy + 0.5) - player.y; + if (Math.sqrt(dx * dx + dy * dy) < 0.8) { + // Check if there are more levels + if (currentLevel < LEVELS.length - 1) { + // Transition to next level — keep weapons, hp, accumulate score + levelScore += calcScore(); + currentLevel++; + initLevel(currentLevel, false); + return; + } + // Final level — win! + gameState = 'WIN'; + var score = levelScore + calcScore(); + var scoreEl = document.getElementById('doom-win-score'); + if (scoreEl) scoreEl.textContent = score; + var killsEl = document.getElementById('doom-win-kills'); + if (killsEl) killsEl.textContent = kills + '/' + totalEnemies; + var timeEl = document.getElementById('doom-win-time'); + if (timeEl) timeEl.textContent = formatTime(gameTime); + showOverlay('doom-win-overlay'); + document.exitPointerLock(); + submitScore(); + } + } + } + } + + function calcScore() { + var timeBonus = Math.max(0, 3000 - Math.floor(gameTime) * 10); + return kills * 100 + Math.floor(player.hp) * 10 + timeBonus; + } + + function formatTime(t) { + var m = Math.floor(t / 60), s = Math.floor(t % 60); + return m + ':' + (s < 10 ? '0' : '') + s; + } + + // ======================== RENDERING ======================== + function render() { + // Sniper zoom FOV + FOV = (sniperZoom && currentWeapon === 'sniper') ? BASE_FOV / 2 : BASE_FOV; + HALF_FOV = FOV / 2; + + // Screen shake + var shakeX = 0, shakeY = 0; + if (shakeIntensity > 0) { + shakeX = (Math.random() - 0.5) * shakeIntensity * 20; + shakeY = (Math.random() - 0.5) * shakeIntensity * 20; + shakeIntensity = Math.max(0, shakeIntensity - dt * 5); + ctx.save(); + ctx.translate(shakeX, shakeY); + } + + // Clear + ctx.fillStyle = CEIL_COLOR; + ctx.fillRect(0, 0, SCREEN_W, SCREEN_H / 2); + ctx.fillStyle = FLOOR_COLOR; + ctx.fillRect(0, SCREEN_H / 2, SCREEN_W, SCREEN_H / 2); + + if (gameState === 'MENU') { + drawMenuScreen(); + return; + } + + // Raycasting + castRays(); + // Sprites + drawSprites(); + // Projectiles + drawProjectiles(); + // Floating damage numbers + drawFloatingTexts(); + // Weapon + drawWeapon(); + // HUD + drawHUD(); + // Damage flash + if (damageFlash > 0) { + ctx.fillStyle = 'rgba(255,0,0,' + (damageFlash * 0.35) + ')'; + ctx.fillRect(0, 0, SCREEN_W, SCREEN_H); + } + // Sniper scope overlay + if (sniperZoom && currentWeapon === 'sniper') { + var cx = SCREEN_W / 2, cy = SCREEN_H / 2; + var scopeR = Math.min(SCREEN_W, SCREEN_H) * 0.4; + ctx.fillStyle = 'rgba(0,0,0,0.85)'; + ctx.beginPath(); + ctx.rect(0, 0, SCREEN_W, SCREEN_H); + ctx.arc(cx, cy, scopeR, 0, Math.PI * 2, true); + ctx.fill(); + // Crosshair lines + ctx.strokeStyle = 'rgba(0,255,0,0.5)'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(cx - scopeR, cy); ctx.lineTo(cx + scopeR, cy); + ctx.moveTo(cx, cy - scopeR); ctx.lineTo(cx, cy + scopeR); + ctx.stroke(); + // Scope ring + ctx.strokeStyle = '#333'; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.arc(cx, cy, scopeR, 0, Math.PI * 2); + ctx.stroke(); + } + // Minimap + if (showMinimap) drawMinimap(); + + // Restore shake transform + if (shakeX !== 0 || shakeY !== 0) ctx.restore(); + } + + function drawMenuScreen() { + ctx.fillStyle = '#111'; + ctx.fillRect(0, 0, SCREEN_W, SCREEN_H); + ctx.fillStyle = '#c33'; + ctx.font = 'bold 60px monospace'; + ctx.textAlign = 'center'; + ctx.fillText('DOOM FPS', SCREEN_W / 2, SCREEN_H / 2 - 60); + ctx.fillStyle = '#888'; + ctx.font = '16px monospace'; + ctx.fillText('Enter your name and click START', SCREEN_W / 2, SCREEN_H / 2); + ctx.textAlign = 'left'; + } + + // ======================== RAYCASTING ======================== + function castRays() { + for (var col = 0; col < SCREEN_W; col++) { + var rayAngle = player.angle - HALF_FOV + (col / SCREEN_W) * FOV; + var rayCos = Math.cos(rayAngle), raySin = Math.sin(rayAngle); + + // DDA setup + var mapX = Math.floor(player.x), mapY = Math.floor(player.y); + var deltaDistX = Math.abs(1 / rayCos), deltaDistY = Math.abs(1 / raySin); + var stepX, stepY, sideDistX, sideDistY; + + if (rayCos < 0) { stepX = -1; sideDistX = (player.x - mapX) * deltaDistX; } + else { stepX = 1; sideDistX = (mapX + 1 - player.x) * deltaDistX; } + if (raySin < 0) { stepY = -1; sideDistY = (player.y - mapY) * deltaDistY; } + else { stepY = 1; sideDistY = (mapY + 1 - player.y) * deltaDistY; } + + // DDA loop + var hit = false, side = 0, wallType = 1; + for (var d = 0; d < MAX_DEPTH * 2; d++) { + if (sideDistX < sideDistY) { + sideDistX += deltaDistX; + mapX += stepX; + side = 0; + } else { + sideDistY += deltaDistY; + mapY += stepY; + side = 1; + } + if (mapX < 0 || mapY < 0 || mapX >= MAP_W || mapY >= MAP_H) break; + if (MAP[mapY][mapX] > 0) { + hit = true; + wallType = MAP[mapY][mapX]; + break; + } + } + + if (!hit) { depthBuf[col] = MAX_DEPTH; continue; } + + // Perpendicular distance (fish-eye correction) + var perpDist; + if (side === 0) perpDist = sideDistX - deltaDistX; + else perpDist = sideDistY - deltaDistY; + if (perpDist < 0.01) perpDist = 0.01; + + depthBuf[col] = perpDist; + + // Wall height + var wallH = Math.floor(SCREEN_H / perpDist); + var drawStart = Math.floor((SCREEN_H - wallH) / 2); + var drawEnd = drawStart + wallH; + + // Wall color with distance shading + var wc = WALL_COLORS[wallType] || WALL_COLORS[1]; + var shade = Math.max(0.15, 1 - perpDist / MAX_DEPTH); + // Side shading (N/S darker) + if (side === 1) shade *= 0.7; + + var r = Math.floor(wc.r * shade); + var g = Math.floor(wc.g * shade); + var b = Math.floor(wc.b * shade); + + ctx.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')'; + ctx.fillRect(col, Math.max(0, drawStart), 1, Math.min(SCREEN_H, drawEnd) - Math.max(0, drawStart)); + } + } + + // ======================== SPRITES ======================== + function drawSprites() { + // Collect visible sprites with distance + var sprites = []; + entities.forEach(function (e) { + if (!e.alive) return; + var dx = e.x - player.x, dy = e.y - player.y; + var dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 0.3 || dist > MAX_DEPTH) return; + sprites.push({ e: e, dist: dist, dx: dx, dy: dy }); + }); + + // Sort far to near + sprites.sort(function (a, b) { return b.dist - a.dist; }); + + sprites.forEach(function (s) { + var e = s.e, dx = s.dx, dy = s.dy, dist = s.dist; + + // Angle relative to player + var spriteAngle = Math.atan2(dy, dx) - player.angle; + // Normalize to -PI..PI + while (spriteAngle > Math.PI) spriteAngle -= 2 * Math.PI; + while (spriteAngle < -Math.PI) spriteAngle += 2 * Math.PI; + + // Check if in FOV (with some margin) + if (Math.abs(spriteAngle) > HALF_FOV + 0.2) return; + + // Screen position + var screenX = Math.floor(SCREEN_W / 2 * (1 + spriteAngle / HALF_FOV)); + var spriteH = Math.floor(SCREEN_H / dist); + var spriteW = spriteH * 0.6; + var drawStartY = Math.floor((SCREEN_H - spriteH) / 2); + + // Determine sprite color and shape + var color, isEnemy = !e.pickupType; + if (isEnemy) { + color = e.state === 'CHASE' || e.state === 'ALERT' ? e.alertColor : e.color; + } else { + color = e.color; + } + + // Draw sprite column by column (respecting depth buffer) + var startX = Math.floor(screenX - spriteW / 2); + var endX = Math.floor(screenX + spriteW / 2); + + for (var col = startX; col < endX; col++) { + if (col < 0 || col >= SCREEN_W) continue; + if (dist >= depthBuf[col]) continue; // behind wall + + var tx = (col - startX) / spriteW; // 0..1 across sprite + + if (isEnemy) { + // Draw humanoid shape + drawEnemyColumn(col, drawStartY, spriteH, tx, color, e); + } else { + // Draw pickup as diamond/circle + drawPickupColumn(col, drawStartY, spriteH, tx, color, e); + } + } + }); + } + + function drawEnemyColumn(col, startY, height, tx, color, enemy) { + // Simple humanoid: head (top 20%), body (middle 50%), legs (bottom 30%) + var headEnd = startY + height * 0.2; + var bodyEnd = startY + height * 0.7; + var legEnd = startY + height; + + // Only draw within a rough silhouette + var centerDist = Math.abs(tx - 0.5) * 2; // 0 at center, 1 at edges + + // Head (narrower) + if (centerDist < 0.5) { + ctx.fillStyle = '#fda'; // skin tone + ctx.fillRect(col, Math.max(0, startY), 1, Math.max(0, headEnd - startY)); + } + + // Body + if (centerDist < 0.8) { + ctx.fillStyle = color; + ctx.fillRect(col, Math.max(0, headEnd), 1, Math.max(0, bodyEnd - headEnd)); + } + + // Legs (two separate) + if ((tx < 0.4 && tx > 0.15) || (tx > 0.6 && tx < 0.85)) { + ctx.fillStyle = '#555'; + ctx.fillRect(col, Math.max(0, bodyEnd), 1, Math.max(0, legEnd - bodyEnd)); + } + } + + function drawPickupColumn(col, startY, height, tx, color, entity) { + var centerDist = Math.abs(tx - 0.5) * 2; + // Diamond shape + var top = startY + height * 0.3; + var bot = startY + height * 0.7; + var mid = (top + bot) / 2; + var halfH = (bot - top) / 2; + + // Only draw if within diamond + var maxWidth = 1 - centerDist; + if (maxWidth > 0) { + var drawTop = mid - halfH * maxWidth; + var drawBot = mid + halfH * maxWidth; + // Glow effect + ctx.fillStyle = color; + ctx.fillRect(col, Math.max(0, drawTop), 1, Math.max(0, drawBot - drawTop)); + } + } + + // ======================== PROJECTILE RENDERING ======================== + function drawProjectiles() { + projectiles.forEach(function (p) { + var dx = p.x - player.x, dy = p.y - player.y; + var dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 0.2 || dist > MAX_DEPTH) return; + + var spriteAngle = Math.atan2(dy, dx) - player.angle; + while (spriteAngle > Math.PI) spriteAngle -= 2 * Math.PI; + while (spriteAngle < -Math.PI) spriteAngle += 2 * Math.PI; + if (Math.abs(spriteAngle) > HALF_FOV + 0.1) return; + + var screenX = Math.floor(SCREEN_W / 2 * (1 + spriteAngle / HALF_FOV)); + var size = Math.floor(SCREEN_H / dist * 0.15); + if (size < 2) size = 2; + var screenY = Math.floor(SCREEN_H / 2); + + // Check depth buffer at center column + if (screenX >= 0 && screenX < SCREEN_W && dist < depthBuf[screenX]) { + // Glowing orb + ctx.fillStyle = p.color; + ctx.beginPath(); + ctx.arc(screenX, screenY, size, 0, Math.PI * 2); + ctx.fill(); + // Glow + ctx.fillStyle = 'rgba(255,255,200,0.4)'; + ctx.beginPath(); + ctx.arc(screenX, screenY, size * 2, 0, Math.PI * 2); + ctx.fill(); + } + }); + + // Draw grenades + grenades.forEach(function (g) { + var dx = g.x - player.x, dy = g.y - player.y; + var dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 0.2 || dist > MAX_DEPTH) return; + var spriteAngle = Math.atan2(dy, dx) - player.angle; + while (spriteAngle > Math.PI) spriteAngle -= 2 * Math.PI; + while (spriteAngle < -Math.PI) spriteAngle += 2 * Math.PI; + if (Math.abs(spriteAngle) > HALF_FOV + 0.1) return; + var screenX = Math.floor(SCREEN_W / 2 * (1 + spriteAngle / HALF_FOV)); + var scale = SCREEN_H / dist; + var size = Math.max(2, Math.floor(scale * 0.08)); + // g.z controls vertical position: 0=ground, higher=up + var screenY = Math.floor(SCREEN_H / 2 + scale * 0.5 - g.z * scale); + if (screenX >= 0 && screenX < SCREEN_W && dist < depthBuf[screenX]) { + // Grenade body + ctx.fillStyle = '#4a4'; + ctx.beginPath(); + ctx.arc(screenX, screenY, size, 0, Math.PI * 2); + ctx.fill(); + // Blinking fuse + if (Math.floor(g.life * 6) % 2 === 0) { + ctx.fillStyle = '#f80'; + ctx.beginPath(); + ctx.arc(screenX, screenY - size, size * 0.4, 0, Math.PI * 2); + ctx.fill(); + } + // Shadow on ground + var groundY = Math.floor(SCREEN_H / 2 + scale * 0.5); + ctx.fillStyle = 'rgba(0,0,0,0.3)'; + ctx.beginPath(); + ctx.ellipse(screenX, groundY, size * 0.8, size * 0.3, 0, 0, Math.PI * 2); + ctx.fill(); + } + }); + } + + // ======================== WEAPON ======================== + function drawWeapon() { + var wp = WEAPONS[currentWeapon]; + var bobX = 0, bobY = 0; + + // Walking bob + if (keys['KeyW'] || keys['KeyS'] || keys['KeyA'] || keys['KeyD']) { + bobX = Math.sin(gameTime * 8) * 5; + bobY = Math.abs(Math.cos(gameTime * 8)) * 8; + } + + // Fire recoil + var recoilY = weaponAnim * 20; + + var cx = SCREEN_W / 2 + bobX; + var cy = SCREEN_H - 80 + bobY - recoilY; + + ctx.save(); + + if (currentWeapon === 'knife') { + // Knife — blade + handle, slash animation + var slashAngle = weaponAnim * 0.8; + ctx.save(); + ctx.translate(cx, cy + 20); + ctx.rotate(-0.3 + slashAngle); + // Handle + ctx.fillStyle = '#8B6914'; + ctx.fillRect(-5, 0, 10, 30); + // Guard + ctx.fillStyle = '#888'; + ctx.fillRect(-10, -2, 20, 5); + // Blade + ctx.fillStyle = '#ccc'; + ctx.beginPath(); + ctx.moveTo(-4, -2); + ctx.lineTo(0, -50); + ctx.lineTo(4, -2); + ctx.fill(); + // Edge highlight + ctx.fillStyle = '#fff'; + ctx.beginPath(); + ctx.moveTo(0, -2); + ctx.lineTo(1, -45); + ctx.lineTo(2, -2); + ctx.fill(); + ctx.restore(); + } else if (currentWeapon === 'pistol') { + // Simple pistol shape + ctx.fillStyle = '#888'; + ctx.fillRect(cx - 6, cy, 12, 50); // barrel + ctx.fillStyle = '#666'; + ctx.fillRect(cx - 10, cy + 30, 20, 30); // grip + ctx.fillStyle = '#555'; + ctx.fillRect(cx - 4, cy - 5, 8, 10); // sight + } else if (currentWeapon === 'shotgun') { + // Double barrel shotgun + ctx.fillStyle = '#8B6914'; + ctx.fillRect(cx - 8, cy + 30, 16, 40); // stock + ctx.fillStyle = '#777'; + ctx.fillRect(cx - 10, cy - 10, 8, 45); // barrel 1 + ctx.fillRect(cx + 2, cy - 10, 8, 45); // barrel 2 + ctx.fillStyle = '#555'; + ctx.fillRect(cx - 12, cy + 20, 24, 8); // pump + } else if (currentWeapon === 'machinegun') { + // Machine gun + ctx.fillStyle = '#555'; + ctx.fillRect(cx - 5, cy - 15, 10, 60); // long barrel + ctx.fillStyle = '#666'; + ctx.fillRect(cx - 12, cy + 25, 24, 25); // body + ctx.fillStyle = '#8B6914'; + ctx.fillRect(cx - 8, cy + 40, 16, 25); // grip + ctx.fillStyle = '#444'; + ctx.fillRect(cx + 8, cy + 10, 6, 20); // magazine + } else if (currentWeapon === 'sniper') { + // Sniper rifle — long barrel, scope, stock + ctx.fillStyle = '#4a4a5a'; + ctx.fillRect(cx - 4, cy - 30, 8, 75); // long barrel + ctx.fillStyle = '#3a3a48'; + ctx.fillRect(cx - 8, cy + 25, 16, 12); // receiver + ctx.fillStyle = '#8B6914'; + ctx.fillRect(cx - 6, cy + 37, 12, 30); // stock + ctx.fillRect(cx + 5, cy + 42, 8, 22); // cheek rest + // Scope + ctx.fillStyle = '#222'; + ctx.fillRect(cx - 3, cy - 10, 6, 18); // scope mount + ctx.fillStyle = '#333'; + ctx.beginPath(); + ctx.arc(cx, cy - 15, 6, 0, Math.PI * 2); // scope front lens + ctx.fill(); + ctx.beginPath(); + ctx.arc(cx, cy + 5, 5, 0, Math.PI * 2); // scope rear lens + ctx.fill(); + ctx.fillStyle = '#2a2a38'; + ctx.fillRect(cx - 2, cy - 15, 4, 20); // scope tube + // Bipod legs + ctx.strokeStyle = '#555'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(cx - 3, cy + 20); ctx.lineTo(cx - 14, cy + 50); + ctx.moveTo(cx + 3, cy + 20); ctx.lineTo(cx + 14, cy + 50); + ctx.stroke(); + // Bolt handle + ctx.fillStyle = '#666'; + ctx.fillRect(cx + 6, cy + 15, 10, 4); + } else if (currentWeapon === 'm4a1') { + // M4A1 assault rifle — barrel, handguard, stock, magazine + ctx.fillStyle = '#555'; + ctx.fillRect(cx - 4, cy - 20, 8, 65); // barrel + handguard + // Handguard rails + ctx.fillStyle = '#4a4a4a'; + ctx.fillRect(cx - 7, cy - 10, 3, 30); + ctx.fillRect(cx + 4, cy - 10, 3, 30); + // Upper receiver + ctx.fillStyle = '#606060'; + ctx.fillRect(cx - 9, cy + 20, 18, 15); + // Carry handle / sight + ctx.fillStyle = '#444'; + ctx.fillRect(cx - 3, cy - 8, 6, 5); + ctx.fillRect(cx - 2, cy - 13, 4, 5); // front sight post + // Magazine (curved) + ctx.fillStyle = '#3a3a3a'; + ctx.save(); + ctx.translate(cx + 2, cy + 35); + ctx.rotate(0.1); + ctx.fillRect(-5, 0, 10, 22); + ctx.restore(); + // Stock (collapsible) + ctx.fillStyle = '#666'; + ctx.fillRect(cx - 5, cy + 35, 10, 8); + ctx.fillStyle = '#8B6914'; + ctx.fillRect(cx - 6, cy + 43, 12, 22); + // Buffer tube + ctx.fillStyle = '#555'; + ctx.fillRect(cx - 3, cy + 33, 6, 12); + // Trigger guard + ctx.strokeStyle = '#555'; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.arc(cx, cy + 38, 6, 0, Math.PI); + ctx.stroke(); + } + + // Muzzle flash (not for knife) + if (weaponAnim > 0.6 && currentWeapon !== 'knife') { + var flashSize = weaponAnim * 30; + ctx.fillStyle = 'rgba(255,200,50,' + weaponAnim + ')'; + ctx.beginPath(); + ctx.arc(cx, cy - 10, flashSize, 0, Math.PI * 2); + ctx.fill(); + ctx.fillStyle = 'rgba(255,255,200,' + weaponAnim * 0.5 + ')'; + ctx.beginPath(); + ctx.arc(cx, cy - 10, flashSize * 0.5, 0, Math.PI * 2); + ctx.fill(); + } + + ctx.restore(); + } + + // ======================== FLOATING DAMAGE NUMBERS ======================== + function drawFloatingTexts() { + for (var i = 0; i < floatingTexts.length; i++) { + var ft = floatingTexts[i]; + // Project world position to screen + var dx = ft.x - player.x, dy = ft.y - player.y; + var dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 0.1) continue; + var angle = Math.atan2(dy, dx) - player.angle; + // Normalize angle + while (angle < -Math.PI) angle += Math.PI * 2; + while (angle > Math.PI) angle -= Math.PI * 2; + if (Math.abs(angle) > HALF_FOV + 0.1) continue; + + var screenX = Math.floor((1 + angle / HALF_FOV) * SCREEN_W / 2); + var scale = SCREEN_H / dist; + // ft.z is the height offset (floats upward from 0.5) + var screenY = Math.floor(SCREEN_H / 2 - ft.z * scale + scale * 0.3); + + var alpha = Math.min(1, ft.life * 2); // fade out in last 0.5s + var fontSize = Math.max(12, Math.min(32, Math.floor(scale * 0.25))); + ctx.font = 'bold ' + fontSize + 'px monospace'; + ctx.textAlign = 'center'; + ctx.fillStyle = ft.color.replace(')', ',' + alpha + ')').replace('rgb', 'rgba'); + // Simple color with alpha + ctx.globalAlpha = alpha; + ctx.fillStyle = ft.color; + ctx.fillText(ft.text, screenX, screenY); + ctx.globalAlpha = 1; + } + } + + // ======================== HUD ======================== + function drawHUD() { + ctx.save(); + + // Crosshair + ctx.strokeStyle = '#fff'; + ctx.lineWidth = 1.5; + var cx = SCREEN_W / 2, cy = SCREEN_H / 2; + ctx.beginPath(); + ctx.moveTo(cx - 10, cy); ctx.lineTo(cx - 4, cy); + ctx.moveTo(cx + 4, cy); ctx.lineTo(cx + 10, cy); + ctx.moveTo(cx, cy - 10); ctx.lineTo(cx, cy - 4); + ctx.moveTo(cx, cy + 4); ctx.lineTo(cx, cy + 10); + ctx.stroke(); + + // Health bar (bottom left) + var barX = 20, barY = SCREEN_H - 40, barW = 150, barH = 20; + ctx.fillStyle = 'rgba(0,0,0,0.6)'; + ctx.fillRect(barX - 2, barY - 2, barW + 4, barH + 4); + var hpPct = Math.max(0, player.hp / 100); + var hpColor = hpPct > 0.5 ? '#4c4' : hpPct > 0.25 ? '#cc4' : '#c44'; + ctx.fillStyle = hpColor; + ctx.fillRect(barX, barY, barW * hpPct, barH); + ctx.fillStyle = '#fff'; + ctx.font = 'bold 14px monospace'; + ctx.textAlign = 'left'; + ctx.fillText('HP: ' + Math.ceil(player.hp), barX + 4, barY + 15); + + // Ammo (bottom right) + var ammoStr = weaponAmmo[currentWeapon] === Infinity ? 'INF' : weaponAmmo[currentWeapon]; + ctx.textAlign = 'right'; + ctx.fillStyle = '#fff'; + ctx.font = 'bold 16px monospace'; + ctx.fillText(WEAPONS[currentWeapon].name, SCREEN_W - 20, SCREEN_H - 50); + ctx.font = 'bold 22px monospace'; + ctx.fillText(ammoStr, SCREEN_W - 20, SCREEN_H - 25); + + // Kill count (top left) + ctx.textAlign = 'left'; + ctx.font = 'bold 14px monospace'; + ctx.fillStyle = '#fff'; + ctx.fillText('KILLS: ' + kills + '/' + totalEnemies, 20, 30); + + // Time (top center) + ctx.textAlign = 'center'; + ctx.fillText(formatTime(gameTime), SCREEN_W / 2, 30); + + // Score + ctx.textAlign = 'right'; + ctx.fillText('SCORE: ' + (levelScore + calcScore()), SCREEN_W - 20, 30); + + // Level indicator (top left, below kills) + ctx.textAlign = 'left'; + ctx.fillStyle = '#aaa'; + ctx.fillText('LEVEL ' + (currentLevel + 1), 20, 50); + + // Coins (top left, below level) + if (coins > 0) { + ctx.fillStyle = '#fc0'; + ctx.fillText('COINS: ' + coins, 20, 70); + } + + // Grenade count (above ammo, bottom right) + if (grenadeCount > 0) { + ctx.textAlign = 'right'; + ctx.fillStyle = '#4a4'; + ctx.font = 'bold 12px monospace'; + ctx.fillText('GRENADES: ' + grenadeCount, SCREEN_W - 20, SCREEN_H - 70); + } + + ctx.restore(); + } + + // ======================== MINIMAP ======================== + function drawMinimap() { + var mw = miniCanvas.width, mh = miniCanvas.height; + var scale = mw / 12; // Show ~12 tiles around player + var ox = player.x - 6, oy = player.y - 6; + + miniCtx.fillStyle = 'rgba(0,0,0,0.7)'; + miniCtx.fillRect(0, 0, mw, mh); + + // Draw walls + for (var y = 0; y < MAP_H; y++) { + for (var x = 0; x < MAP_W; x++) { + if (MAP[y][x] === 0) continue; + var sx = (x - ox) * scale, sy = (y - oy) * scale; + if (sx < -scale || sy < -scale || sx > mw || sy > mh) continue; + var wc = WALL_COLORS[MAP[y][x]] || WALL_COLORS[1]; + miniCtx.fillStyle = 'rgb(' + wc.r + ',' + wc.g + ',' + wc.b + ')'; + miniCtx.fillRect(sx, sy, scale, scale); + } + } + + // Draw entities + entities.forEach(function (e) { + if (!e.alive) return; + var sx = (e.x - ox) * scale, sy = (e.y - oy) * scale; + if (sx < 0 || sy < 0 || sx > mw || sy > mh) return; + miniCtx.fillStyle = e.pickupType ? '#ff0' : (e.state === 'CHASE' ? '#f00' : '#f88'); + miniCtx.beginPath(); + miniCtx.arc(sx, sy, e.pickupType ? 2 : 3, 0, Math.PI * 2); + miniCtx.fill(); + }); + + // Draw player + var px = (player.x - ox) * scale, py = (player.y - oy) * scale; + miniCtx.fillStyle = '#0f0'; + miniCtx.beginPath(); + miniCtx.arc(px, py, 4, 0, Math.PI * 2); + miniCtx.fill(); + + // Player direction + miniCtx.strokeStyle = '#0f0'; + miniCtx.lineWidth = 2; + miniCtx.beginPath(); + miniCtx.moveTo(px, py); + miniCtx.lineTo(px + Math.cos(player.angle) * 15, py + Math.sin(player.angle) * 15); + miniCtx.stroke(); + + // Draw minimap on main canvas + ctx.drawImage(miniCanvas, SCREEN_W - mw - 10, 10); + ctx.strokeStyle = 'rgba(255,255,255,0.3)'; + ctx.strokeRect(SCREEN_W - mw - 10, 10, mw, mh); + } + + // ======================== LEADERBOARD & AJAX ======================== + function ajax(action, data, cb) { + var url = window.DOOM_AJAX || (window.HOME ? window.HOME + '/wp-admin/admin-ajax.php' : '/wp-admin/admin-ajax.php'); + var fd = new FormData(); + fd.append('action', action); + for (var k in data) fd.append(k, data[k]); + fetch(url, { method: 'POST', body: fd, credentials: 'same-origin' }) + .then(function (r) { return r.json(); }) + .then(function (res) { if (cb) cb(res); }) + .catch(function () {}); + } + + function submitScore() { + var score = levelScore + calcScore(); + var duration = Math.floor(gameTime); + var salt = window.DOOM_DAY_SALT || ''; + var token = md5(score + '_' + duration + '_' + salt); + ajax('doom_submit', { name: playerName, score: score, kills: kills, duration: duration, token: token }, function (res) { + if (res.success && res.data) { + var rankEl = document.getElementById('doom-rank'); + var rankEl2 = document.getElementById('doom-rank2'); + if (rankEl) rankEl.textContent = '#' + res.data.rank; + if (rankEl2) rankEl2.textContent = '#' + res.data.rank; + } + loadLeaderboard(); + }); + } + + function loadLeaderboard() { + ajax('doom_leaderboard', { type: leaderboardType }, function (res) { + if (!res.success) return; + leaderboardData = res.data || []; + renderLeaderboard(); + }); + } + + function renderLeaderboard() { + var list = document.getElementById('doom-lb-list'); + if (!list) return; + if (leaderboardData.length === 0) { + list.innerHTML = '
No scores yet. Be the first!
'; + return; + } + var medals = ['🥇', '🥈', '🥉']; // gold, silver, bronze + var html = ''; + leaderboardData.forEach(function (row, i) { + var isMine = row.player_name === playerName; + var medal = i < 3 ? medals[i] : '' + (i + 1) + ''; + var m = Math.floor(row.duration / 60), s = row.duration % 60; + var timeStr = m + ':' + (s < 10 ? '0' : '') + s; + html += '
' + + '' + medal + '' + + '' + escHtml(row.player_name) + '' + + '' + row.score + '' + + '' + row.kills + 'K' + + '' + timeStr + '' + + '
'; + }); + list.innerHTML = html; + } + + function escHtml(s) { + var d = document.createElement('div'); + d.textContent = s; + return d.innerHTML; + } + + // ======================== BOOT ======================== + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/common/doom/doom.min.css b/common/doom/doom.min.css new file mode 100644 index 0000000..b70cce4 --- /dev/null +++ b/common/doom/doom.min.css @@ -0,0 +1 @@ +.doom-page-wrapper .main-content{background:transparent!important;padding:0!important;max-width:100%}.doom-container{max-width:1000px;margin:0 auto;padding:20px;display:flex;flex-direction:column;align-items:center;gap:24px}.doom-game{position:relative;width:100%;max-width:960px}.doom-game canvas{display:block;width:100%;height:auto;background:#111;border-radius:8px;box-shadow:0 4px 20px #0006;cursor:crosshair}.doom-score-bar{display:flex;justify-content:center;gap:20px;padding:8px 16px;background:#000000b3;border-radius:0 0 8px 8px;color:#ccc;font:13px monospace}.doom-controls-hint{text-align:center;padding:6px;color:#888;font-size:12px}.doom-overlay{position:absolute;top:0;left:0;right:0;aspect-ratio:16/9;background:#000000d9;display:flex;flex-direction:column;align-items:center;justify-content:center;border-radius:8px;z-index:10;color:#fff}.doom-overlay h2{font:700 48px monospace;color:#c33;margin-bottom:10px;text-shadow:0 0 20px rgba(200,50,50,.6)}.doom-overlay .doom-subtitle{font:16px monospace;color:#aaa;margin-bottom:24px}.doom-overlay input[type=text]{padding:10px 16px;border:2px solid #555;border-radius:6px;background:#222;color:#fff;font:16px monospace;width:220px;text-align:center;margin-bottom:16px;outline:none}.doom-overlay input[type=text]:focus{border-color:#c33}.doom-btn{padding:12px 36px;background:#c33;color:#fff;border:none;border-radius:6px;font:700 18px monospace;cursor:pointer;transition:background .2s;margin:6px}.doom-btn:hover{background:#e44}.doom-result-stats{display:flex;gap:24px;margin:16px 0;font:14px monospace;color:#ccc}.doom-result-stats span{display:flex;flex-direction:column;align-items:center;gap:4px}.doom-result-stats .doom-stat-val{font-size:28px;font-weight:700;color:#fff}.doom-result-stats .doom-stat-label{font-size:11px;color:#888;text-transform:uppercase}.doom-rank-display{font:700 24px monospace;color:#fc0;margin:8px 0}.doom-leaderboard{width:100%;max-width:960px;background:var(--theme-front-main-color, #fff);border-radius:12px;box-shadow:0 2px 12px #00000014;overflow:hidden}.doom-lb-header{padding:16px 18px 12px;border-bottom:1px solid #eee;display:flex;align-items:center;justify-content:space-between}.doom-lb-header h3{margin:0;font-size:1.1rem;color:var(--theme-text-color, #333)}.doom-lb-tabs{display:flex;gap:6px}.doom-tab-btn{padding:5px 14px;border:1px solid #ddd;border-radius:20px;background:transparent;font-size:.8rem;cursor:pointer;color:#666;transition:all .2s}.doom-tab-btn.active{background:var(--theme-color, #c33);color:#fff;border-color:var(--theme-color, #c33)}.doom-lb-body{max-height:400px;overflow-y:auto;padding:8px 0}.doom-lb-row{display:flex;align-items:center;padding:8px 16px;gap:8px;font-size:.85rem;transition:background .15s}.doom-lb-row:hover{background:#00000008}.doom-lb-mine{background:#c8323214!important;font-weight:600}.doom-lb-medal-1{background:#fff8e1}.doom-lb-medal-2{background:#f5f5f5}.doom-lb-medal-3{background:#fff3e0}.doom-lb-rank{width:28px;text-align:center;font-size:1.1rem}.doom-lb-num{font-size:.8rem;color:#999}.doom-lb-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--theme-text-color, #333)}.doom-lb-score{font-weight:600;color:var(--theme-color, #c33);min-width:45px;text-align:right}.doom-lb-kills{color:#888;font-size:.75rem;min-width:30px;text-align:right}.doom-lb-time{color:#aaa;font-size:.75rem;min-width:40px;text-align:right}.doom-lb-empty{text-align:center;padding:40px 20px;color:#999;font-size:.9rem}@media(max-width:480px){.doom-container{padding:10px}.doom-overlay h2{font-size:32px}.doom-blog{padding:24px 16px}}.doom-blog{width:100%;max-width:960px;padding:40px 30px;margin-top:8px;background:var(--theme-front-main-color, #fff);border-radius:12px;box-shadow:0 2px 12px #00000014}.doom-blog h2{margin-top:2rem}.doom-blog h2:first-child{margin-top:0}.doom-blog p{line-height:1.8;margin-bottom:1rem}.doom-blog pre{background:#1e1e2e;color:#cdd6f4;padding:1rem;border-radius:8px;overflow-x:auto;margin:1rem 0;font-size:.85rem;line-height:1.6}.doom-blog code{font-family:Cascadia Code,Fira Code,Consolas,monospace}.doom-blog p code{background:var(--theme-code-bg, #f0f0f0);padding:.15em .4em;border-radius:4px;font-size:.88em}.doom-blog blockquote{margin:1rem 0;padding:.75rem 1rem;border-left:4px solid var(--theme-color, #c33);background:#00000008;border-radius:0 6px 6px 0}.doom-blog blockquote p{margin:.4rem 0}.doom-blog table{border-collapse:collapse;width:100%;margin:1rem 0}.doom-blog table th,.doom-blog table td{padding:.5rem .75rem;border:1px solid #d0d7de;text-align:left}.doom-blog table th{background:#0000000a;font-weight:600}.doom-blog table tr:hover{background:#00000005}.doom-blog img{max-width:100%;border-radius:8px;margin:1rem 0}.doom-blog .doom-blog-header{text-align:center;margin-bottom:2rem;padding-bottom:1.5rem;border-bottom:1px solid #eee;position:relative}.doom-lang-btn{position:absolute;top:0;right:0;padding:4px 14px;border:1px solid #ddd;border-radius:20px;background:transparent;font:700 13px monospace;cursor:pointer;color:#666;transition:all .2s}.doom-lang-btn:hover{background:var(--theme-color, #c33);color:#fff;border-color:var(--theme-color, #c33)}.doom-blog .doom-blog-header h1{font-size:1.8rem;margin:0 0 .5rem;color:var(--theme-text-color, #333)}.doom-blog .doom-blog-header .doom-blog-meta{color:#999;font-size:.85rem}.doom-blog .doom-section-title{display:flex;align-items:center;gap:8px;font-size:1.3rem;color:var(--theme-text-color, #333);padding-bottom:.5rem;border-bottom:2px solid var(--theme-color, #c33);margin-bottom:1rem} diff --git a/common/doom/doom.min.js b/common/doom/doom.min.js new file mode 100644 index 0000000..b863590 --- /dev/null +++ b/common/doom/doom.min.js @@ -0,0 +1 @@ +(function(){"use strict";var E=960,R=540,ue=Math.PI/3,j=ue/2,we=ue,F=3,Le=2.5,Je=.003,$=.25,te=24,L0=1,Te=[{name:"Level 1 - The Bunker",spawn:{x:1.5,y:1.5,angle:0},map:[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],[1,0,0,2,2,2,0,0,0,0,3,0,3,0,0,0,0,0,4,4,4,0,0,1],[1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,4,0,0,1],[1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,1],[1,0,0,0,0,0,0,0,0,0,3,0,3,0,0,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1],[1,1,1,0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,1],[1,1,1,0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1],[1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],[1,0,0,3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,1],[1,0,0,3,0,3,0,0,0,0,4,4,4,0,0,0,0,0,2,0,0,0,0,1],[1,0,0,0,0,0,0,0,0,0,4,0,4,0,0,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,0,0,4,4,4,0,0,0,0,0,0,0,0,0,0,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],entities:[{type:"guard",x:5.5,y:5.5},{type:"guard",x:10.5,y:2.5},{type:"guard",x:18.5,y:5.5},{type:"guard",x:3.5,y:17.5},{type:"guard",x:15.5,y:18.5},{type:"soldier",x:10.5,y:10.5},{type:"soldier",x:20.5,y:20.5},{type:"soldier",x:5.5,y:14.5},{type:"shotgun",x:7.5,y:3.5},{type:"machinegun",x:19.5,y:19.5},{type:"health",x:11.5,y:11.5},{type:"health",x:3.5,y:21.5},{type:"ammo",x:15.5,y:3.5},{type:"ammo",x:20.5,y:14.5}]},{name:"Level 2 - The Fortress",spawn:{x:1.5,y:22.5,angle:0},map:[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,8,1],[1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,3,0,2,2,2,2,0,3,0,4,4,4,4,0,0,0,0,0,1],[1,0,0,0,0,0,0,2,0,0,2,0,0,0,4,0,0,4,0,0,0,0,0,1],[1,3,3,3,3,3,0,2,0,0,2,0,3,3,4,0,0,4,0,3,3,3,3,1],[1,0,0,0,0,0,0,2,0,0,0,0,0,0,4,0,0,4,0,0,0,0,0,1],[1,0,0,0,0,0,0,2,2,5,2,2,0,0,4,4,5,4,0,0,0,0,0,1],[1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,1],[1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,1],[1,1,1,5,1,1,0,0,1,1,1,1,1,1,1,1,0,0,1,1,5,1,1,1],[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1],[1,1,1,0,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1],[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1],[1,0,2,2,2,0,0,0,1,1,1,0,0,1,1,1,0,0,0,4,4,4,0,1],[1,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,4,0,1],[1,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,4,0,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],entities:[{type:"guard",x:2.5,y:2.5},{type:"guard",x:10.5,y:2.5},{type:"guard",x:16.5,y:4.5},{type:"guard",x:2.5,y:8.5},{type:"guard",x:21.5,y:8.5},{type:"guard",x:5.5,y:15.5},{type:"guard",x:18.5,y:15.5},{type:"soldier",x:9.5,y:5.5},{type:"soldier",x:15.5,y:5.5},{type:"soldier",x:11.5,y:12.5},{type:"soldier",x:3.5,y:18.5},{type:"soldier",x:20.5,y:18.5},{type:"soldier",x:20.5,y:2.5},{type:"health",x:1.5,y:6.5},{type:"health",x:22.5,y:20.5},{type:"health",x:11.5,y:15.5},{type:"ammo",x:9.5,y:9.5},{type:"ammo",x:18.5,y:9.5},{type:"ammo",x:12.5,y:18.5},{type:"shotgun",x:3.5,y:11.5},{type:"machinegun",x:20.5,y:11.5},{type:"sniper",x:9.5,y:12.5},{type:"m4a1",x:14.5,y:12.5}]}],L,z,K,qe,re=0,Y=0;function Ve(t){var e=Te[t];L=e.map,z=L.length,K=L[0].length,qe=e.entities}var ve={1:{r:120,g:120,b:120},2:{r:140,g:60,b:60},3:{r:60,g:100,b:140},4:{r:80,g:130,b:60},5:{r:100,g:70,b:40},8:{r:180,g:140,b:40},9:{r:100,g:100,b:100}},Qe="#222233",Ze="#444433",ye={knife:{name:"Knife",damage:50,fireRate:500,ammo:1/0,spread:0,color:"#ccc",auto:!1,melee:!0,range:1},pistol:{name:"Pistol",damage:15,fireRate:400,ammo:1/0,spread:0,color:"#aaa",auto:!1},shotgun:{name:"Shotgun",damage:80,fireRate:800,ammo:20,spread:.15,color:"#c84",auto:!1},machinegun:{name:"Machine Gun",damage:15,fireRate:100,ammo:100,spread:.05,color:"#4a4",auto:!0},sniper:{name:"Sniper",damage:150,fireRate:1200,ammo:10,spread:0,color:"#448",auto:!1},m4a1:{name:"M4A1",damage:25,fireRate:120,ammo:120,spread:.04,color:"#886",auto:!0}},U,n,ae,q,g=null,W="MENU",y,w,le,P={},Ae=0,oe=!1,Ce=0,A=0,J=0,Q=0,ne=0,de=0,k="pistol",G={},B={},be=0,H=0,Se=!0,X="",Z=0,ge=!1,ee=[],ie=[],_=0,fe=0,ce=[],he=!1,Ee=[],Be="alltime";function $e(t){function e(h,T){return h<>>32-T}function a(h,T){var s,u,p,m,O;return p=h&2147483648,m=T&2147483648,s=h&1073741824,u=T&1073741824,O=(h&1073741823)+(T&1073741823),s&u?O^2147483648^p^m:s|u?O&1073741824?O^3221225472^p^m:O^1073741824^p^m:O^p^m}function i(h,T,s){return h&T|~h&s}function r(h,T,s){return h&s|T&~s}function l(h,T,s){return h^T^s}function o(h,T,s){return T^(h|~s)}function f(h,T,s,u,p,m,O){return h=a(h,a(a(i(T,s,u),p),O)),a(e(h,m),T)}function v(h,T,s,u,p,m,O){return h=a(h,a(a(r(T,s,u),p),O)),a(e(h,m),T)}function c(h,T,s,u,p,m,O){return h=a(h,a(a(l(T,s,u),p),O)),a(e(h,m),T)}function d(h,T,s,u,p,m,O){return h=a(h,a(a(o(T,s,u),p),O)),a(e(h,m),T)}function x(h){var T="",s,u;for(s=0;s>8&255);return T}function S(h){var T=[],s=h.length*8,u;for(u=0;u>5]|=(h.charCodeAt(u/8)&255)<>5]|=128<>>9<<4)+14]=T;var s=1732584193,u=-271733879,p=-1732584194,m=271733878,M;for(M=0;M>me*8+4&15)+Ke.charAt(je>>me*8&15)}return Ue}for(var I=[],V=0;V>6),I.push(128|C&63)):(I.push(224|C>>12),I.push(128|C>>6&63),I.push(128|C&63))}for(var pe="",V=0;V-200&&l<200&&(Ae+=l)}}),document.addEventListener("mousedown",function(r){oe&&r.button===0&&(ge=!0),oe&&r.button===2&&W==="PLAYING"&&k==="sniper"&&(he=!he)}),document.addEventListener("mouseup",function(r){r.button===0&&(ge=!1)}),U.addEventListener("contextmenu",function(r){r.preventDefault()}),U.addEventListener("wheel",function(r){if(W==="PLAYING"){r.preventDefault();var l=["knife","pistol","shotgun","machinegun","sniper","m4a1"],o=l.filter(function(v){return B[v]}),f=o.indexOf(k);r.deltaY>0?f=(f+1)%o.length:f=(f-1+o.length)%o.length,N(o[f])}},{passive:!1});var e=document.getElementById("doom-start-btn");e&&e.addEventListener("click",Re);var a=document.getElementById("doom-replay-btn");a&&a.addEventListener("click",Re);var i=document.getElementById("doom-replay-btn2");i&&i.addEventListener("click",Re),document.querySelectorAll(".doom-tab-btn").forEach(function(r){r.addEventListener("click",function(){Be=r.dataset.type,document.querySelectorAll(".doom-tab-btn").forEach(function(l){l.classList.remove("active")}),r.classList.add("active"),Pe()})}),Pe(),requestAnimationFrame(Ge)}}function Re(){var t=document.getElementById("doom-name-input");if(t){if(X=t.value.trim().substring(0,20),!X){alert("Please enter your name");return}localStorage.setItem("doom_player_name",X)}re=0,Y=0,De(re,!0),Ye(),W="PLAYING",e0(),U.requestPointerLock()}function De(t,e){Ve(t);var a=Te[t];y={x:a.spawn.x,y:a.spawn.y,angle:a.spawn.angle,hp:e?100:y.hp},e&&(k="pistol",B={knife:!0,pistol:!0,shotgun:!1,machinegun:!1,sniper:!1,m4a1:!1},G={knife:1/0,pistol:1/0,shotgun:0,machinegun:0,sniper:0,m4a1:0},Q=0,de=0,J=0),be=0,H=0,Z=0,ge=!1,ee=[],ie=[],ce=[],e&&(fe=0),t>=1&&(fe+=3),w=[],ne=0,qe.forEach(function(i){var r=Math.floor(i.x),l=Math.floor(i.y);if(!(r<0||l<0||r>=K||l>=z||L[l][r]!==0)){var o={type:i.type,x:i.x,y:i.y,alive:!0};i.type==="guard"?(o.hp=50,o.speed=1.5,o.damage=8,o.range=8,o.fireRate=1e3,o.lastFire=0,o.color="#c44",o.alertColor="#f66",o.state="IDLE",o.alertTimer=0,o.patrolAngle=Math.random()*Math.PI*2,ne++):i.type==="soldier"?(o.hp=100,o.speed=2.2,o.damage=12,o.range=12,o.fireRate=700,o.lastFire=0,o.color="#66c",o.alertColor="#88f",o.state="IDLE",o.alertTimer=0,o.patrolAngle=Math.random()*Math.PI*2,ne++):i.type==="shotgun"?(o.pickupType="shotgun",o.color="#c84"):i.type==="machinegun"?(o.pickupType="machinegun",o.color="#4a4"):i.type==="sniper"?(o.pickupType="sniper",o.color="#448"):i.type==="m4a1"?(o.pickupType="m4a1",o.color="#886"):i.type==="health"?(o.pickupType="health",o.color="#f44"):i.type==="ammo"&&(o.pickupType="ammo",o.color="#fa0"),w.push(o)}})}function N(t){B[t]&&k!==t&&(k=t,H=0,t!=="sniper"&&(he=!1))}function Xe(){var t=["knife","pistol","shotgun","machinegun","sniper","m4a1"],e=t.filter(function(i){return B[i]});if(!(e.length<=1)){var a=(e.indexOf(k)+1)%e.length;N(e[a])}}function e0(){var t=["doom-start-overlay","doom-gameover-overlay","doom-win-overlay"];t.forEach(function(e){var a=document.getElementById(e);a&&(a.style.display="none")})}function ke(t){var e=document.getElementById(t);e&&(e.style.display="flex")}function Ge(t){A=Math.min((t-Ce)/1e3,.05),Ce=t,W==="PLAYING"&&(J+=A,t0(),l0(),o0(),f0(),p0(),n0(A),u0()),m0(),requestAnimationFrame(Ge)}function t0(){var t=0,e=0,a=Math.cos(y.angle),i=Math.sin(y.angle);if((P.KeyW||P.ArrowUp)&&(t+=a*F*A,e+=i*F*A),(P.KeyS||P.ArrowDown)&&(t-=a*F*A,e-=i*F*A),P.KeyA&&(t+=i*F*A,e-=a*F*A),P.KeyD&&(t-=i*F*A,e+=a*F*A),P.ArrowLeft&&(y.angle-=Le*A),P.ArrowRight&&(y.angle+=Le*A),oe&&(y.angle+=Ae*Je,Ae=0),y.angle=y.angle%(2*Math.PI),y.angle<0&&(y.angle+=2*Math.PI),t!==0||e!==0){var r=y.x+t,l=y.y+e;_e(r,y.y)||(y.x=r),_e(y.x,l)||(y.y=l);var o=performance.now();o-Oe>350&&(D("step"),Oe=o)}var f=performance.now(),v=ye[k],c=ge||P.Space;c&&f-be>v.fireRate&&G[k]>0&&(a0(),be=f,G[k]!==1/0&&G[k]--),H>0&&(H=Math.max(0,H-A*6)),Z>0&&(Z=Math.max(0,Z-A*4))}function _e(t,e){for(var a=[[t-$,e-$],[t+$,e-$],[t-$,e+$],[t+$,e+$]],i=0;i=K||l>=z||L[l][r]!==0)return!0}for(var i=0;i=0;e--){var a=ie[e];a.z+=t*1.2,a.life-=t,a.life<=0&&ie.splice(e,1)}}function i0(t,e){for(var a=null,i=e+.1,r=Math.cos(y.angle),l=Math.sin(y.angle),o=0;oe)){var x=v*r+c*l;x<0||dl)){var x=v*a+c*i;if(!(x<.1)){var S=Math.abs(v*i-c*a),b=d<1.5?.6:.4;S=0&&I>=0&&bt.fireRate){if(y.hp-=t.damage*1.5,Z=1,D("hurt"),_=Math.max(_,.5),y.hp<=0){y.hp=0,W="GAMEOVER";var x=document.getElementById("doom-go-score");x&&(x.textContent=Y+se());var S=document.getElementById("doom-go-kills");S&&(S.textContent=Q+"/"+ne);var b=document.getElementById("doom-go-time");b&&(b.textContent=xe(J)),ke("doom-gameover-overlay"),document.exitPointerLock(),Ie()}}else if(o&&it.fireRate){var I=(Math.random()-.5)*.15;ee.push({x:t.x,y:t.y,dx:Math.cos(r+I)*6,dy:Math.sin(r+I)*6,damage:t.damage,life:3,color:t.type==="soldier"?"#88f":"#fa0"}),t.lastFire=l}!o&&t.alertTimer<=0?t.state="IDLE":o&&(t.alertTimer=5);break}}})}function Ne(t,e,a){for(var i=.3,r=[[t-i,e-i],[t+i,e-i],[t-i,e+i],[t+i,e+i],[t,e]],l=0;l=K||f>=z||L[f][o]!==0&&L[f][o]!==8)return!0}for(var v=0;v=0;t--){var e=ee[t];if(e.life-=A,e.life<=0){ee.splice(t,1);continue}e.x+=e.dx*A,e.y+=e.dy*A;var a=Math.floor(e.x),i=Math.floor(e.y);if(a<0||i<0||a>=K||i>=z||L[i][a]!==0){ee.splice(t,1);continue}var r=e.x-y.x,l=e.y-y.y;if(Math.sqrt(r*r+l*l)<.4&&(y.hp-=e.damage,Z=1,D("hurt"),_=Math.max(_,.5),ee.splice(t,1),y.hp<=0)){y.hp=0,W="GAMEOVER";var o=document.getElementById("doom-go-score");o&&(o.textContent=Y+se());var f=document.getElementById("doom-go-kills");f&&(f.textContent=Q+"/"+ne);var v=document.getElementById("doom-go-time");v&&(v.textContent=xe(J)),ke("doom-gameover-overlay"),document.exitPointerLock(),Ie();return}}}function f0(){w.forEach(function(t){if(!(!t.alive||!t.pickupType)){var e=y.x-t.x,a=y.y-t.y;if(Math.sqrt(e*e+a*a)<.6){var i=!1;t.pickupType==="shotgun"?(B.shotgun=!0,G.shotgun+=20,N("shotgun"),t.alive=!1,i=!0):t.pickupType==="machinegun"?(B.machinegun=!0,G.machinegun+=100,N("machinegun"),t.alive=!1,i=!0):t.pickupType==="health"?y.hp<100&&(y.hp=Math.min(100,y.hp+30),t.alive=!1,i=!0):t.pickupType==="ammo"?(B.shotgun&&(G.shotgun+=10),B.machinegun&&(G.machinegun+=50),B.sniper&&(G.sniper+=5),B.m4a1&&(G.m4a1+=60),t.alive=!1,i=!0):t.pickupType==="sniper"?(B.sniper=!0,G.sniper+=10,N("sniper"),t.alive=!1,i=!0):t.pickupType==="m4a1"?(B.m4a1=!0,G.m4a1+=120,N("m4a1"),t.alive=!1,i=!0):t.pickupType==="coin"&&(de++,Y+=50,Me(t.x,t.y,"+50 \u{1FA99}","#fc0"),t.alive=!1,i=!0),i&&D("pickup")}}})}function c0(){for(var t=Math.floor(y.x),e=Math.floor(y.y),a=[[t,e],[t+1,e],[t-1,e],[t,e+1],[t,e-1],[t+1,e+1],[t-1,e-1],[t+1,e-1],[t-1,e+1]],i=0;i=0&&l>=0&&r=0;r--){var l=ce[r];l.life-=A,l.dz-=t*A,l.z+=l.dz*A,l.z<=0&&(l.z=0,Math.abs(l.dz)>.5?(l.dz=-l.dz*e,l.bounces++,l.dx*=.7,l.dy*=.7,l.bounces<=3&&D("step")):(l.dz=0,l.z=0,l.dx*=.92,l.dy*=.92));var o=l.x+l.dx*A,f=l.y+l.dy*A,v=Math.floor(o),c=Math.floor(f),d=Math.floor(l.x),x=Math.floor(l.y),S=!1,b=!1;(v<0||v>=K||L[x][v]!==0)&&(l.dx=-l.dx*i,o=l.x,S=!0),(c<0||c>=z||L[c][Math.floor(o)]!==0)&&(l.dy=-l.dy*i,f=l.y,b=!0),(S||b)&&(l.bounces++,l.bounces<=3&&D("step")),l.x=o,l.y=f,l.z>.01&&(l.dx*=a,l.dy*=a),l.life<=0&&(h0(l.x,l.y),ce.splice(r,1))}}function h0(t,e){var a=2.5;_=Math.max(_,.8),Z=.3,D("explosion");for(var i=0;i=0&&l>=0&&r0&&(t=(Math.random()-.5)*_*20,e=(Math.random()-.5)*_*20,_=Math.max(0,_-A*5),n.save(),n.translate(t,e)),n.fillStyle=Qe,n.fillRect(0,0,E,R/2),n.fillStyle=Ze,n.fillRect(0,R/2,E,R/2),W==="MENU"){v0();return}if(y0(),d0(),x0(),A0(),T0(),b0(),Z>0&&(n.fillStyle="rgba(255,0,0,"+Z*.35+")",n.fillRect(0,0,E,R)),he&&k==="sniper"){var a=E/2,i=R/2,r=Math.min(E,R)*.4;n.fillStyle="rgba(0,0,0,0.85)",n.beginPath(),n.rect(0,0,E,R),n.arc(a,i,r,0,Math.PI*2,!0),n.fill(),n.strokeStyle="rgba(0,255,0,0.5)",n.lineWidth=1,n.beginPath(),n.moveTo(a-r,i),n.lineTo(a+r,i),n.moveTo(a,i-r),n.lineTo(a,i+r),n.stroke(),n.strokeStyle="#333",n.lineWidth=3,n.beginPath(),n.arc(a,i,r,0,Math.PI*2),n.stroke()}Se&&S0(),(t!==0||e!==0)&&n.restore()}function v0(){n.fillStyle="#111",n.fillRect(0,0,E,R),n.fillStyle="#c33",n.font="bold 60px monospace",n.textAlign="center",n.fillText("DOOM FPS",E/2,R/2-60),n.fillStyle="#888",n.font="16px monospace",n.fillText("Enter your name and click START",E/2,R/2),n.textAlign="left"}function y0(){for(var t=0;t=K||l>=z));V++)if(L[l][r]>0){S=!0,I=L[l][r];break}if(!S){le[t]=te;continue}var C;b===0?C=d-o:C=x-f,C<.01&&(C=.01),le[t]=C;var pe=Math.floor(R/C),h=Math.floor((R-pe)/2),T=h+pe,s=ve[I]||ve[1],u=Math.max(.15,1-C/te);b===1&&(u*=.7);var p=Math.floor(s.r*u),m=Math.floor(s.g*u),O=Math.floor(s.b*u);n.fillStyle="rgb("+p+","+m+","+O+")",n.fillRect(t,Math.max(0,h),1,Math.min(R,T)-Math.max(0,h))}}function d0(){var t=[];w.forEach(function(e){if(e.alive){var a=e.x-y.x,i=e.y-y.y,r=Math.sqrt(a*a+i*i);r<.3||r>te||t.push({e,dist:r,dx:a,dy:i})}}),t.sort(function(e,a){return a.dist-e.dist}),t.forEach(function(e){for(var a=e.e,i=e.dx,r=e.dy,l=e.dist,o=Math.atan2(r,i)-y.angle;o>Math.PI;)o-=2*Math.PI;for(;o<-Math.PI;)o+=2*Math.PI;if(!(Math.abs(o)>j+.2)){var f=Math.floor(E/2*(1+o/j)),v=Math.floor(R/l),c=v*.6,d=Math.floor((R-v)/2),x,S=!a.pickupType;S?x=a.state==="CHASE"||a.state==="ALERT"?a.alertColor:a.color:x=a.color;for(var b=Math.floor(f-c/2),I=Math.floor(f+c/2),V=b;V=E)&&!(l>=le[V])){var C=(V-b)/c;S?g0(V,d,v,C,x,a):M0(V,d,v,C,x,a)}}})}function g0(t,e,a,i,r,l){var o=e+a*.2,f=e+a*.7,v=e+a,c=Math.abs(i-.5)*2;c<.5&&(n.fillStyle="#fda",n.fillRect(t,Math.max(0,e),1,Math.max(0,o-e))),c<.8&&(n.fillStyle=r,n.fillRect(t,Math.max(0,o),1,Math.max(0,f-o))),(i<.4&&i>.15||i>.6&&i<.85)&&(n.fillStyle="#555",n.fillRect(t,Math.max(0,f),1,Math.max(0,v-f)))}function M0(t,e,a,i,r,l){var o=Math.abs(i-.5)*2,f=e+a*.3,v=e+a*.7,c=(f+v)/2,d=(v-f)/2,x=1-o;if(x>0){var S=c-d*x,b=c+d*x;n.fillStyle=r,n.fillRect(t,Math.max(0,S),1,Math.max(0,b-S))}}function x0(){ee.forEach(function(t){var e=t.x-y.x,a=t.y-y.y,i=Math.sqrt(e*e+a*a);if(!(i<.2||i>te)){for(var r=Math.atan2(a,e)-y.angle;r>Math.PI;)r-=2*Math.PI;for(;r<-Math.PI;)r+=2*Math.PI;if(!(Math.abs(r)>j+.1)){var l=Math.floor(E/2*(1+r/j)),o=Math.floor(R/i*.15);o<2&&(o=2);var f=Math.floor(R/2);l>=0&&lte)){for(var r=Math.atan2(a,e)-y.angle;r>Math.PI;)r-=2*Math.PI;for(;r<-Math.PI;)r+=2*Math.PI;if(!(Math.abs(r)>j+.1)){var l=Math.floor(E/2*(1+r/j)),o=R/i,f=Math.max(2,Math.floor(o*.08)),v=Math.floor(R/2+o*.5-t.z*o);if(l>=0&&l.6&&k!=="knife"){var f=H*30;n.fillStyle="rgba(255,200,50,"+H+")",n.beginPath(),n.arc(r,l-10,f,0,Math.PI*2),n.fill(),n.fillStyle="rgba(255,255,200,"+H*.5+")",n.beginPath(),n.arc(r,l-10,f*.5,0,Math.PI*2),n.fill()}n.restore()}function A0(){for(var t=0;tMath.PI;)l-=Math.PI*2;if(!(Math.abs(l)>j+.1)){var o=Math.floor((1+l/j)*E/2),f=R/r,v=Math.floor(R/2-e.z*f+f*.3),c=Math.min(1,e.life*2),d=Math.max(12,Math.min(32,Math.floor(f*.25)));n.font="bold "+d+"px monospace",n.textAlign="center",n.fillStyle=e.color.replace(")",","+c+")").replace("rgb","rgba"),n.globalAlpha=c,n.fillStyle=e.color,n.fillText(e.text,o,v),n.globalAlpha=1}}}}function b0(){n.save(),n.strokeStyle="#fff",n.lineWidth=1.5;var t=E/2,e=R/2;n.beginPath(),n.moveTo(t-10,e),n.lineTo(t-4,e),n.moveTo(t+4,e),n.lineTo(t+10,e),n.moveTo(t,e-10),n.lineTo(t,e-4),n.moveTo(t,e+4),n.lineTo(t,e+10),n.stroke();var a=20,i=R-40,r=150,l=20;n.fillStyle="rgba(0,0,0,0.6)",n.fillRect(a-2,i-2,r+4,l+4);var o=Math.max(0,y.hp/100),f=o>.5?"#4c4":o>.25?"#cc4":"#c44";n.fillStyle=f,n.fillRect(a,i,r*o,l),n.fillStyle="#fff",n.font="bold 14px monospace",n.textAlign="left",n.fillText("HP: "+Math.ceil(y.hp),a+4,i+15);var v=G[k]===1/0?"INF":G[k];n.textAlign="right",n.fillStyle="#fff",n.font="bold 16px monospace",n.fillText(ye[k].name,E-20,R-50),n.font="bold 22px monospace",n.fillText(v,E-20,R-25),n.textAlign="left",n.font="bold 14px monospace",n.fillStyle="#fff",n.fillText("KILLS: "+Q+"/"+ne,20,30),n.textAlign="center",n.fillText(xe(J),E/2,30),n.textAlign="right",n.fillText("SCORE: "+(Y+se()),E-20,30),n.textAlign="left",n.fillStyle="#aaa",n.fillText("LEVEL "+(re+1),20,50),de>0&&(n.fillStyle="#fc0",n.fillText("COINS: "+de,20,70)),fe>0&&(n.textAlign="right",n.fillStyle="#4a4",n.font="bold 12px monospace",n.fillText("GRENADES: "+fe,E-20,R-70)),n.restore()}function S0(){var t=ae.width,e=ae.height,a=t/12,i=y.x-6,r=y.y-6;q.fillStyle="rgba(0,0,0,0.7)",q.fillRect(0,0,t,e);for(var l=0;lt||v>e)){var c=ve[L[l][o]]||ve[1];q.fillStyle="rgb("+c.r+","+c.g+","+c.b+")",q.fillRect(f,v,a,a)}}w.forEach(function(S){if(S.alive){var b=(S.x-i)*a,I=(S.y-r)*a;b<0||I<0||b>t||I>e||(q.fillStyle=S.pickupType?"#ff0":S.state==="CHASE"?"#f00":"#f88",q.beginPath(),q.arc(b,I,S.pickupType?2:3,0,Math.PI*2),q.fill())}});var d=(y.x-i)*a,x=(y.y-r)*a;q.fillStyle="#0f0",q.beginPath(),q.arc(d,x,4,0,Math.PI*2),q.fill(),q.strokeStyle="#0f0",q.lineWidth=2,q.beginPath(),q.moveTo(d,x),q.lineTo(d+Math.cos(y.angle)*15,x+Math.sin(y.angle)*15),q.stroke(),n.drawImage(ae,E-t-10,10),n.strokeStyle="rgba(255,255,255,0.3)",n.strokeRect(E-t-10,10,t,e)}function ze(t,e,a){var i=window.DOOM_AJAX||(window.HOME?window.HOME+"/wp-admin/admin-ajax.php":"/wp-admin/admin-ajax.php"),r=new FormData;r.append("action",t);for(var l in e)r.append(l,e[l]);fetch(i,{method:"POST",body:r,credentials:"same-origin"}).then(function(o){return o.json()}).then(function(o){a&&a(o)}).catch(function(){})}function Ie(){var t=Y+se(),e=Math.floor(J),a=window.DOOM_DAY_SALT||"",i=$e(t+"_"+e+"_"+a);ze("doom_submit",{name:X,score:t,kills:Q,duration:e,token:i},function(r){if(r.success&&r.data){var l=document.getElementById("doom-rank"),o=document.getElementById("doom-rank2");l&&(l.textContent="#"+r.data.rank),o&&(o.textContent="#"+r.data.rank)}Pe()})}function Pe(){ze("doom_leaderboard",{type:Be},function(t){t.success&&(Ee=t.data||[],E0())})}function E0(){var t=document.getElementById("doom-lb-list");if(t){if(Ee.length===0){t.innerHTML='
No scores yet. Be the first!
';return}var e=["🥇","🥈","🥉"],a="";Ee.forEach(function(i,r){var l=i.player_name===X,o=r<3?e[r]:''+(r+1)+"",f=Math.floor(i.duration/60),v=i.duration%60,c=f+":"+(v<10?"0":"")+v;a+='
'+o+''+R0(i.player_name)+''+i.score+''+i.kills+'K'+c+"
"}),t.innerHTML=a}}function R0(t){var e=document.createElement("div");return e.textContent=t,e.innerHTML}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Fe):Fe()})(); diff --git a/common/friend/friend.min.css b/common/friend/friend.min.css new file mode 100644 index 0000000..bee2cf7 --- /dev/null +++ b/common/friend/friend.min.css @@ -0,0 +1 @@ +.friend-graph{position:relative;width:100%;user-select:none;min-height:calc(100vh - 100px);border-radius:8px;padding:60px 0 0;overflow:hidden}#friend-edges{position:absolute;top:0;left:0;pointer-events:none;z-index:1}.friend-edge{stroke:#e0e0e0;stroke-width:1;transition:stroke .3s}.friend-node{position:absolute;width:3.8rem;cursor:move;z-index:2;transition:transform .3s}.friend-node.dragging{z-index:3;transform:scale(1.05)}.node-icon{display:flex;justify-content:center;align-items:center;width:3.8rem;height:3.8rem;border-radius:50%;overflow:hidden;border:3px solid #fff;box-shadow:0 4px 6px #0000001a;background:#fff;margin-bottom:8px}.node-icon img{width:75%;height:75%;pointer-events:none;user-select:none;object-fit:cover}.node-info{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;text-align:center;background:#ffffffe6;padding:15px;border-radius:8px;box-shadow:0 2px 4px #0000000d;opacity:0;transition:opacity .3s;pointer-events:none;position:absolute;width:240px;left:50%;z-index:1001;transform:translate(-50%)}.friend-node:hover{z-index:1001}.friend-node:hover .node-info{opacity:1}.node-info h3{margin:0 0 4px;font-size:14px;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.node-info p{margin:0;font-size:12px;color:#666;display:-webkit-box;overflow:hidden}@media(max-width:767px){.friend-node{position:absolute;width:2.5rem}.node-icon{width:2.5rem;height:2.5rem}} diff --git a/common/friend/friend.min.js b/common/friend/friend.min.js new file mode 100644 index 0000000..9a79e27 --- /dev/null +++ b/common/friend/friend.min.js @@ -0,0 +1,9 @@ +$(function(){let m=window.innerWidth<767?2:3.6;const r=document.getElementById("friend-graph");if(!r)return;const d=r.querySelector(".current-site"),u=r.querySelectorAll(".friend-link"),l={name:d.dataset.name||"\u5F53\u524D\u7AD9\u70B9",image:d.dataset.image||"",description:d.dataset.description||""},f=Array.from(u).map(t=>({name:t.dataset.name||"",image:t.dataset.image||"",description:t.dataset.description||""})),c=[],h=100,b=50;function y(t,e,n){for(const a of n)if(Math.sqrt(Math.pow(t-a.x,2)+Math.pow(e-a.y,2))720?100:50;do e=Math.random()*(r.clientWidth-2*i)+i,n=Math.random()*(r.clientHeight-2*i)+i,a++;while(!y(e,n,t)&&a{const n=w(c);c.push({id:e+1,label:t.name,image:t.image||"",description:t.description||"",x:n.x,y:n.y})});const x=c.map((t,e)=>({from:0,to:e})).filter(t=>t.from!==t.to);function v(){c.forEach(t=>{const e=document.createElement("div");e.className="friend-node",e.style.left=`${t.x}px`,e.style.top=`${t.y}px`,e.innerHTML=` +
+ ${t.label} +
+
+

${t.label}

+

${t.description}

+
+ `;let n=!1,a,o,i,p;e.addEventListener("mousedown",function(s){n=!1,i=s.clientX,p=s.clientY,a=s.clientX-t.x,o=s.clientY-t.y,e.classList.add("dragging")}),document.addEventListener("mousemove",function(s){if(!e.classList.contains("dragging"))return;Math.sqrt(Math.pow(s.clientX-i,2)+Math.pow(s.clientY-p,2))>5&&(n=!0),t.x=s.clientX-a,t.y=s.clientY-o,e.style.left=`${t.x}px`,e.style.top=`${t.y}px`,g()}),e.addEventListener("mouseup",function(){if(!n&&t.id!==0){const s=u[t.id-1].dataset.url;s&&window.open(s,"_blank")}e.classList.remove("dragging")}),r.appendChild(e)})}function g(){const t=document.getElementById("friend-edges");if(!t)return;t.innerHTML="";const e=m*rem/2;x.forEach(n=>{const a=c[n.from],o=c[n.to],i=document.createElementNS("http://www.w3.org/2000/svg","line");i.setAttribute("x1",a.x+e),i.setAttribute("y1",a.y+e),i.setAttribute("x2",o.x+e),i.setAttribute("y2",o.y+e),i.setAttribute("class","friend-edge"),t.appendChild(i)})}function E(){const t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.id="friend-edges",t.style.width="100%",t.style.height="100%",r.appendChild(t),v(),g()}E()}); diff --git a/common/inline/emoji.min.js b/common/inline/emoji.min.js new file mode 100644 index 0000000..005eaee --- /dev/null +++ b/common/inline/emoji.min.js @@ -0,0 +1 @@ +$(function(e){(function(s){s.fn.extend({insertContent:function(n,o){var t=s(this)[0];if(document.selection){this.focus();var c=document.selection.createRange();c.text=n,this.focus(),c.moveStart("character",-f);var r=c.text.length;if(arguments.length==2){var f=t.value.length;c.moveEnd("character",r+o),o<=0?c.moveStart("character",r-2*o-n.length):c.moveStart("character",r-o-n.length),c.select()}}else if(t.selectionStart||t.selectionStart=="0"){var l=t.selectionStart,h=t.selectionEnd,u=t.scrollTop;t.value=t.value.substring(0,l)+n+t.value.substring(h,t.value.length),this.focus(),t.selectionStart=l+n.length,t.selectionEnd=l+n.length,t.scrollTop=u,arguments.length==2&&(t.setSelectionRange(l-o,t.selectionEnd+o),this.focus())}else this.value+=n,this.focus()}})})(jQuery);let i=e(".emoji-list"),a=e("#comment_content");e(".icon-biaoqing").click(function(s){i.css("display")==="none"?(i.css("display","flex"),a.addClass("focus")):(i.css("display","none"),a.removeClass("focus")),s.stopPropagation()}),e("body").click(function(s){i.length>0&&!e.contains(i.get(0),s.target)&&(i.css("display","none"),a.removeClass("focus"))}),e(".smilees").click(function(){a.insertContent(`:${e(this).data("emoji")}:`)})}); diff --git a/common/inline/index.min.js b/common/inline/index.min.js new file mode 100644 index 0000000..bcf7edd --- /dev/null +++ b/common/inline/index.min.js @@ -0,0 +1,17 @@ +$(function(t){function v(a,i=3e3){let l=0;return(...c)=>{const e=+new Date;e-l>i&&(l=e,a.apply(this,c))}}(function(){let a=!1;const i=t("html");function l(e,o=!0){let n=e.find(".iconfont");o?(n.css("display","inline-block"),n.addClass("animate-rotate"),e.find("span").text("\u52A0\u8F7D\u4E2D...")):(n.hide(),n.removeClass("animate-rotate"),e.data("next")?e.find("span").text("\u70B9\u51FB\u67E5\u770B\u66F4\u591A"):e.find("span").text("\u6CA1\u6709\u66F4\u591A\u4E86"))}if(t(".main-content").on("click",".loadnext",function(){if(a)return;a=!0;const e=t(this);e.data("next")!==""&&(l(e),t.post(e.data("next"),"pagination=yes",function(o){let n=t(o).find(".i-article");if(e.parent().before(n),e.data("next",t(o).find(".loadnext").data("next")),computed(),toFixed(),l(e,!1),!t("#space").is(":visible"))return;const d=t("#navigator .scroll ul");let s=d.find("li").length;n.each(function(r,f){let p=t(f).find("h2").attr("id"),u=t(f).find("h2 a").text();d.append(`
  • + +
  • `)})}).always(function(){a=!1}))}),IN_HOME){if(!Auto_load_index)return}else if(!Auto_load_else)return;const c=v(()=>{t(".main-content .loadnext").click()},500);t(window).on("scroll",()=>{i.scrollTop()+window.innerHeight+1>=i.innerHeight()&&c()})})(),window.onload=()=>{const a=document.referrer;if(DYNAMIC&&a.indexOf(location.host)>-1){let i=localStorage.getItem("ActiveTab");t(".tab[data-key="+i+"]").click()}else localStorage.removeItem("ActiveTab")},(function(){if(DYNAMIC){let a=0;window.activeTab||Object.defineProperty(window,"activeTab",{set(i){if(t("#space").is(":visible")&&a!=i){t("#navigator .scroll ul").removeWithLeakage();let c=t("#navigator .scroll"),e="
      ";t(".main-main .article-list:visible .i-article").each(function(n,d){let s=t(d).find("h2").attr("id"),r=t(d).find("h2 a").text();e+=`
    • + +
    • `}),c.append(e+"
    "),a=i}},get(){return a}}),t(".dynamic .tab").on("click",function(){let i=t(this),l=t(".dynamic .tab"),c=l.index(i);if(i.hasClass("active-tab")){activeTab=c;return}l.removeClass("active-tab"),i.addClass("active-tab");let e=t("#"+i.data("key"));if(localStorage.setItem("ActiveTab",i.data("key")),t("div.article-list").hide(),e.length>0){e.show(),computed(),toFixed(),activeTab=c;return}t("#dynamic").css("display","flex");let o=t(`
    `),n=t(".hasDynamic");t.post(i.data("url"),"pagination=yes",function(d){let s=t(d).find(".i-article");o.append(s),o.append(t(d).find(".pagination")),n.append(o),t("#dynamic").hide(),o.show(),computed(),toFixed(),activeTab=c})})}})()}); diff --git a/common/inline/main.min.js b/common/inline/main.min.js new file mode 100644 index 0000000..ec502a3 --- /dev/null +++ b/common/inline/main.min.js @@ -0,0 +1 @@ +function toggleTheme(t=!0){t?($("html").addClass("dark").removeClass("personal"),localStorage.setItem("night",1),$(function(){$(".read-mode i").removeClass("icon-baitian-qing").addClass("icon-yueliang")})):($("html").removeClass("dark").addClass("personal"),localStorage.removeItem("night"),$(function(){$(".read-mode i").removeClass("icon-yueliang").addClass("icon-baitian-qing")}))}let l=()=>{let t=document.documentElement,e=t.offsetWidth/100;e<17&&(e=17),t.style.fontSize=e+"px",window.rem=e};window.onresize=l,l();let theme=localStorage.getItem("theme-color");theme&&$("html").addClass(theme);let night=localStorage.getItem("night");night&&toggleTheme(!0),$.fn.getTop=function(){let t=this.position();if(t.top!==0)return t.top;{let e=$("html").get(0);return this.get(0).getBoundingClientRect().top+e.scrollTop}},$.fn.removeWithLeakage=function(){this.each(function(t,e){$("*",e).add([e]).each(function(){$.event.remove(this),$.removeData(this)}),e.parentNode&&e.parentNode.removeChild(e)})}; diff --git a/common/inline/monitor.js b/common/inline/monitor.js index b211146..b6c1127 100644 --- a/common/inline/monitor.js +++ b/common/inline/monitor.js @@ -7,30 +7,50 @@ /* * 防止子容器触发父容器的滚动 +* 兼容旧版本 jQuery(例如 1.6.2 不支持 .on)和现代浏览器 +* 不再依赖 $(elem).on,而是直接使用原生事件绑定 * */ $.fn.scrollUnique = function () { return $(this).each(function () { - var eventType = 'mousewheel'; - // 火狐是DOMMouseScroll事件 - if (document.mozHidden !== undefined) { - eventType = 'DOMMouseScroll'; - } - $(this).on(eventType, function (event) { + var elem = this; + // 统一使用 wheel / DOMMouseScroll / mousewheel 事件,兼容各浏览器 + var handler = function (event) { + var e = event || window.event; + var original = e.originalEvent || e; // 一些数据 - var scrollTop = this.scrollTop, - scrollHeight = this.scrollHeight, - height = this.clientHeight; + var scrollTop = elem.scrollTop, + scrollHeight = elem.scrollHeight, + height = elem.clientHeight; - var delta = (event.originalEvent.wheelDelta) ? event.originalEvent.wheelDelta : -(event.originalEvent.detail || 0); + var delta = original.wheelDelta ? original.wheelDelta : -(original.detail || 0); if ((delta > 0 && scrollTop <= delta) || (delta < 0 && scrollHeight - height - scrollTop <= -1 * delta)) { - // IE浏览器下滚动会跨越边界直接影响父级滚动,因此,临界时候手动边界滚动定位 - this.scrollTop = delta > 0 ? 0 : scrollHeight; + // IE 浏览器下滚动会跨越边界直接影响父级滚动,因此,临界时候手动边界滚动定位 + elem.scrollTop = delta > 0 ? 0 : scrollHeight; // 向上滚 || 向下滚 - event.preventDefault(); + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } } - }); + }; + + // 优先使用标准 wheel 事件 + if (elem.addEventListener) { + elem.addEventListener('wheel', handler, { passive: false }); + // 兼容旧版 Firefox + elem.addEventListener('DOMMouseScroll', handler, { passive: false }); + // 兼容部分旧版浏览器 + elem.addEventListener('mousewheel', handler, { passive: false }); + } else if (elem.attachEvent) { + // 旧版 IE + elem.attachEvent('onmousewheel', handler); + } else { + // 最后兜底 + elem.onmousewheel = handler; + } }); }; @@ -57,12 +77,15 @@ $(function ($) { /* * 屏蔽文章导航滚动事件向上传递 + * 兼容老版本 jQuery(使用 .scroll 而不是 .on) * */ (function () { - $('.scroll').scrollUnique() - .on('scroll', function (e) { - e.stopPropagation(); //停止向上传递事件 - }); + // 初始化自定义滚轮处理,防止滚动穿透到父容器 + $('.scroll').scrollUnique(); + // 使用 jQuery.scroll 绑定 scroll 事件,在所有 jQuery 版本中都可用 + $('.scroll').scroll(function (e) { + e.stopPropagation(); //停止向上传递事件 + }); })(); //防止重复触发目录滚动 @@ -138,7 +161,8 @@ $(function ($) { } - let anchor = main.find(Index.find('div a').attr("href")); + // 直接根据锚点选择器查找目标节点,避免依赖 html 作为根节点 + let anchor = $(Index.find('div a').attr("href")); if (anchor.length !== 0) { @@ -152,11 +176,22 @@ $(function ($) { let top = anchor.getTop() - ($(".isIndex").length > 0 ? 55 : 15); /* - * 滚动锚点标记 + * 使用 window.scrollTo 进行滚动,兼容 Firefox / Chromium * */ - main.animate({scrollTop: top}, 200, function () { + try { + window.scrollTo({ + top: top, + behavior: 'smooth' + }); + // 简单延时后恢复监听(平滑滚动大约 200ms) + setTimeout(function () { + watchScroll = true; + }, 300); + } catch (e) { + // 不支持平滑滚动配置的旧浏览器降级处理 + window.scrollTo(0, top); watchScroll = true; - }); + } } @@ -227,9 +262,10 @@ $(function ($) { if (!watchScroll) return; - let thats = main; - let top = thats.get(0).scrollTop; - let collects = thats.find(".main-content").find("h1,h2,h3,h4"); + // 获取当前已滚动的距离(优先使用 window.scrollY,兼容 Firefox) + let top = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0; + // 直接从文章内容区域查找标题,避免依赖 html 作为根节点 + let collects = $(".main-content").find("h1,h2,h3,h4"); let position = null; /* diff --git a/common/inline/monitor.min.js b/common/inline/monitor.min.js new file mode 100644 index 0000000..87b72b2 --- /dev/null +++ b/common/inline/monitor.min.js @@ -0,0 +1 @@ +$.fn.scrollUnique=function(){return $(this).each(function(){var e=this,s=function(t){var n=t||window.event,o=n.originalEvent||n,i=e.scrollTop,c=e.scrollHeight,p=e.clientHeight,r=o.wheelDelta?o.wheelDelta:-(o.detail||0);(r>0&&i<=r||r<0&&c-p-i<=-1*r)&&(e.scrollTop=r>0?0:c,n.preventDefault?n.preventDefault():n.returnValue=!1)};e.addEventListener?(e.addEventListener("wheel",s,{passive:!1}),e.addEventListener("DOMMouseScroll",s,{passive:!1}),e.addEventListener("mousewheel",s,{passive:!1})):e.attachEvent?e.attachEvent("onmousewheel",s):e.onmousewheel=s})},$(function(e){(function(){e("[data-pid]").each(function(){const t=e(this),n=t.data("pid");e(`[data-parent=${n}]`).length&&t.children("div").prepend('')})})(),(function(){e(".scroll").scrollUnique(),e(".scroll").scroll(function(t){t.stopPropagation()})})();let s=!1;(function(){function t(f){let a=f[0].offsetTop;c.height(f.height()),c.css("top",`${a}px`);let l=e(".scroll").get(0);if(a>=l.scrollTop+l.offsetHeight){if(s)return;s=!0,e(l).animate({scrollTop:a-100},200,function(){s=!1})}if(a0?55:15);try{window.scrollTo({top:h,behavior:"smooth"}),setTimeout(function(){i=!0},300)}catch{window.scrollTo(0,h),i=!0}}}function n(){e(".main-container .main-left .first-index").css("color",""),e(".main-container .main-left .secondary-index").css("color",""),e(".main-container .main-left .third-index").css("color","")}e("body").on("click",".first-index",function(){n(),t(e(this)),e(this).css("color","var(--theme-color)")}).on("click",".secondary-index",function(){n(),t(e(this)),e(this).css("color","var(--theme-color)"),e(this).parent().parent().prev(".first-index").css("color","var(--theme-color)")}).on("click",".third-index",function(){n(),t(e(this)),e(this).css("color","var(--theme-color)"),e(this).parent().parent().prev(".secondary-index").css("color","var(--theme-color)"),e(this).parent().parent().parent().parent().prev(".first-index").css("color","var(--theme-color)")});let o=!0,i=!0,c=e(".main-container .line"),p=e("html"),r=e(".main-container .main-left li div:first");o=!1,r.trigger("click"),e(".main-left").css("opacity",1),e(window).on("scroll",function(){if(!i)return;let f=window.scrollY||document.documentElement.scrollTop||document.body.scrollTop||0,a=e(".main-content").find("h1,h2,h3,h4"),l=null;for(let h=0;hf){h!=0?l=h-1:l=h;break}}l===null&&(l=a.length-1);let d=e("a[href='#"+a.eq(l).attr("id")+"']").parent();o=!1,d.parent().parent().css("display")!=="none"&&d.trigger("click")})})(),(function(){let t=e("#navigator"),n=e("#space");if(t.length>0){let o=n.position();t.css("left",o.left),t.css("top",n.getTop()),t.show(),enquire.register("screen and (min-width:1024px)",{match(){e(window).on("resize",function(){t.css("left",n.position().left)})},unmatch(){e(window).off("resize",function(){t.css("left",n.position().left)})}})}})(),(function(){e(".icp-beian div").click(function(){if(e(".icp-beian div").index(this)==0){let t=e(this);e.post("/?document_nice="+Current,function(){t.find("span").text(parseInt(t.find("span").text())+1)})}else{let t=e(this);e.post("/?document_bad="+Current,function(){t.find("span").text(parseInt(t.find("span").text())+1)})}})})(),(function(){const t=(n,o=null)=>{const i=e(n),c=i.parent().parent().data("pid"),p=e(`[data-parent=${c}]`);i.hasClass("collapse")&&o===null||o?(i.removeClass("collapse"),p.show(100).find(".iconfont").each((r,f)=>{t(f,!0)})):(i.addClass("collapse"),p.hide(100).find(".iconfont").each((r,f)=>{t(f,!1)}))};e("#space .scroll .iconfont").click(function(n){t(this),n.stopPropagation()}),e(".main-top .icon-daohang-caidan").click(function(){const n=e(this),o=n.hasClass("collapse");e(".first-index > .iconfont").each((i,c)=>{t(c,o)}),o?n.removeClass("collapse"):n.addClass("collapse")})})()}); diff --git a/common/inline/swiper.min.js b/common/inline/swiper.min.js new file mode 100644 index 0000000..5e0d31c --- /dev/null +++ b/common/inline/swiper.min.js @@ -0,0 +1 @@ +$(function(){if($("#swiper").length>0){let i=new Swiper("#swiper",{slidesPerView:2,spaceBetween:12,navigation:{nextEl:".swiper-button-next",prevEl:".swiper-button-prev"},breakpoints:{480:{slidesPerView:3},768:{slidesPerView:4},1024:{slidesPerView:4},1580:{slidesPerView:5}}})}}); diff --git a/common/inline/view.min.js b/common/inline/view.min.js new file mode 100644 index 0000000..cded6f0 --- /dev/null +++ b/common/inline/view.min.js @@ -0,0 +1 @@ +window._ts&&Math.floor(Date.now()/1e3)-window._ts>60&&$.post(location.pathname+"?document_view="+Current,function(i){const t=JSON.parse(i);$(()=>{$("div.article-info > ul > li:nth-child(4)").html(`${t.view}\u70ED\u5EA6`)})}); diff --git a/common/main.js b/common/main.js index 6127edd..e4c4e48 100644 --- a/common/main.js +++ b/common/main.js @@ -266,14 +266,23 @@ $(function ($) { * 左侧边栏位置检测 * */ function fixedLeft() { + // 每次调用时都重新获取最新的位置和高度值,确保计算准确 + if (space.length > 0) { + topTop = $('#space').getTop(); + topHeight = navigator.outerHeight(); + topOffset = innerHeight - topHeight - topTop; + } - let html_scrollTop = html.scrollTop(); + // 优先使用 window.scrollY,兼容某些浏览器 html.scrollTop 始终为 0 的情况 + let html_scrollTop = window.scrollY || html.scrollTop(); if (html_scrollTop >= _absolute) { isfixedLeft = true; + // 重新获取最新高度(可能因为图片加载而变化) topHeight = navigator.outerHeight(); + topTop = $('#space').getTop(); topOffset = innerHeight - topHeight - topTop; @@ -314,8 +323,21 @@ $(function ($) { top = min; } + // 确保 top 值不会超出可视区域(防止侧栏移出屏幕) + // top 值应该在 min 和 (min + navigator.height()) 之间 + if (top < min) { + top = min; + } + // 确保侧栏底部不会超出屏幕底部 + let maxTop = innerHeight - navigator.outerHeight() - 20; // 留20px边距 + if (top > maxTop && maxTop > min) { + top = maxTop; + } navigator.css('top', top); + } else { + // 如果还没到需要偏移的位置,确保侧栏在初始位置 + navigator.css('top', topTop); } } else { @@ -366,6 +388,8 @@ $(function ($) { * 滚动、屏幕大小变化时,动态更新右侧侧边栏的位置 * */ function toFixed() { + // 每次滚动时都重新计算高度值,因为页面高度可能在滚动过程中发生变化(如图片加载) + computed(); /* * 如果有左侧边栏 @@ -388,8 +412,8 @@ $(function ($) { isFixed = true;//标记正在滚动 - // 已经滚动的距离 - let html_scrollTop = html.scrollTop(); + // 已经滚动的距离(优先使用 window.scrollY,兼容性更好) + let html_scrollTop = window.scrollY || html.scrollTop(); /* * 大于保持静态,小于保持绝对 @@ -475,6 +499,139 @@ $(function ($) { } } }); + + /* + * 图片加载完成后重新计算导航栏位置 + * 解决图片懒加载导致的高度计算不准确问题 + * */ + (function() { + let imageLoadTimer = null; //防抖计时器 + let loadedImages = 0; //已加载图片计数 + let totalImages = 0; //总图片数 + + // 重新计算导航栏位置的函数(带防抖) + function recalculateOnImageLoad() { + if (imageLoadTimer) { + clearTimeout(imageLoadTimer); + } + imageLoadTimer = setTimeout(function() { + if (typeof computed === 'function') { + computed(); //重新计算高度 + } + if (typeof toFixed === 'function') { + toFixed(); //重新计算位置 + } + }, 100); //防抖延迟100ms + } + + // 监听所有图片的加载事件 + function setupImageLoadListeners() { + const images = document.querySelectorAll('img'); + totalImages = images.length; + + if (totalImages === 0) { + // 如果没有图片,直接执行一次计算 + recalculateOnImageLoad(); + return; + } + + images.forEach(function(img) { + // 如果图片已经加载完成 + if (img.complete && img.naturalHeight !== 0) { + loadedImages++; + if (loadedImages === totalImages) { + recalculateOnImageLoad(); + } + } else { + // 监听图片加载完成事件 + img.addEventListener('load', function() { + loadedImages++; + if (loadedImages === totalImages) { + recalculateOnImageLoad(); + } else { + // 每张图片加载完成都重新计算一次(防抖会合并多次调用) + recalculateOnImageLoad(); + } + }, { once: true }); + + // 监听图片加载错误事件(也要重新计算) + img.addEventListener('error', function() { + loadedImages++; + recalculateOnImageLoad(); + }, { once: true }); + } + }); + } + + // 页面加载完成后也重新计算一次 + if (document.readyState === 'complete') { + setupImageLoadListeners(); + } else { + window.addEventListener('load', function() { + setupImageLoadListeners(); + }); + } + + // 监听动态添加的图片(使用 MutationObserver) + if (typeof MutationObserver !== 'undefined') { + const observer = new MutationObserver(function(mutations) { + let hasNewImages = false; + mutations.forEach(function(mutation) { + mutation.addedNodes.forEach(function(node) { + if (node.nodeType === 1) { // Element node + if (node.tagName === 'IMG') { + hasNewImages = true; + totalImages++; + if (node.complete && node.naturalHeight !== 0) { + loadedImages++; + recalculateOnImageLoad(); + } else { + node.addEventListener('load', function() { + loadedImages++; + recalculateOnImageLoad(); + }, { once: true }); + node.addEventListener('error', function() { + loadedImages++; + recalculateOnImageLoad(); + }, { once: true }); + } + } else if (node.querySelectorAll) { + const imgs = node.querySelectorAll('img'); + if (imgs.length > 0) { + hasNewImages = true; + totalImages += imgs.length; + imgs.forEach(function(img) { + if (img.complete && img.naturalHeight !== 0) { + loadedImages++; + recalculateOnImageLoad(); + } else { + img.addEventListener('load', function() { + loadedImages++; + recalculateOnImageLoad(); + }, { once: true }); + img.addEventListener('error', function() { + loadedImages++; + recalculateOnImageLoad(); + }, { once: true }); + } + }); + } + } + } + }); + }); + if (hasNewImages) { + recalculateOnImageLoad(); + } + }); + + // 开始观察DOM变化 + observer.observe(document.body, { + childList: true, + subtree: true + }); + } + })(); } })(); diff --git a/common/main.min.js b/common/main.min.js new file mode 100644 index 0000000..e68fcad --- /dev/null +++ b/common/main.min.js @@ -0,0 +1 @@ +$(function(e){if(location.search.indexOf("replytocom")!=-1){let t=e(".comment-form").getTop()-100;e("html").get(0).scrollTop=t}(function(){let t=e("html"),n=e(".toTop"),l=e("#progress");l.text(((window.scrollY+t.get(0).clientHeight)/t.get(0).scrollHeight*100).toFixed(0)+"%"),e(".toTop").click(function(){t.animate({scrollTop:0},200,"linear",function(){window.scrollTo(0,0)})}),e(window).on("scroll",function(){let i=t.get(0);window.scrollY>0?n.css("visibility","visible"):n.css("visibility","hidden"),l.text(((window.scrollY+i.clientHeight)/i.scrollHeight*100).toFixed(0)+"%")}),e(".read-mode").click(function(){e(this).find("i").hasClass("icon-yueliang")?toggleTheme(!1):toggleTheme(!0)})})(),(function(){typeof Viewer>"u"||document.querySelectorAll(".viewerLightBox").forEach(t=>{new Viewer(t,{navbar:!1})})})(),(function(){e(".icon-sousuo").on("click",function(t){let n=e("#search");n.val()!=""&&(location.href=location.origin+"?s="+n.val())}),e("#search").on("keypress",function(t){let n=e(this);n.val()!=""&&t.keyCode=="13"&&(location.href=location.origin+"?s="+n.val())})})(),(function(){let t="60%";function n(){let l=e(".right"),i=e(".daohang");i.hasClass("icon-cha")?(l.css("left","-"+t),i.removeClass("icon-cha"),i.addClass("icon-daohangmoren")):(l.css("left",0),i.addClass("icon-cha"),i.removeClass("icon-daohangmoren"))}enquire.register("screen and (max-width: 1024px)",{match(){e(".daohang").on("click",n)},unmatch(){e(".daohang").off("click",n)}})})(),(function(){window.computed=()=>{},window.toFixed=()=>{};let t=!1,n=e("html"),l=e("#fixed"),i=e("#right"),y=e(".main-main"),C=e(".main-bottom").outerHeight(),v=e("#space");if(v.length>0)var f=e("#navigator"),d=f.outerHeight(),a=e("#space").getTop(),H=innerHeight-d-a;if(i.length!=0){let w=function(){v.length>0&&(a=e("#space").getTop(),d=f.outerHeight(),H=innerHeight-d-a);let s=y.get(0).getBoundingClientRect();u=y.getTop()+s.height+rem-innerHeight,u0&&(a=e("#space").getTop(),d=f.outerHeight(),H=innerHeight-d-a);let s=window.scrollY||n.scrollTop();if(s>=u)if(L=!0,d=f.outerHeight(),a=e("#space").getTop(),H=innerHeight-d-a,a+d+s>=u+innerHeight){let o=a+H-(s-u)-rem,c=v.getTop();c>o&&f.height()>y.height()&&(o=c),C+60+f.height()+2*remp&&p>c&&(o=p),f.css("top",o)}else f.css("top",a);else L&&(f.animate({top:a},"fast"),L=!1)},h=function(){if(w(),v.length>0&&Y(),i.length==0||t||g>u)return;t=!0;let s=window.scrollY||n.scrollTop();s>=g&&s<=u?(i.css("left",l.position().left+rem),g+innerHeight>innerHeight?(i.css("top",""),i.css("bottom","0")):(i.css("bottom",""),i.css("top",T)),i.css("position","fixed")):C+i.height()+70<=innerHeight?(i.css("bottom",""),i.css("top",T),i.css("position","fixed")):s>=u?u!=g&&(i.css("position","absolute"),i.css("top",""),i.css("bottom",q)):i.css("position","static"),t=!1};var z=w,F=Y,A=h;let q=0,g,u,I=l.get(0).getBoundingClientRect(),T=l.getTop();g=T+I.height-innerHeight,w(),window.computed=w;let L=!1;window.toFixed=h,enquire.register("screen and (min-width:1024px)",{match(){e(window).on("scroll",h),e(window).on("resize",h)},unmatch(){e(window).off("scroll",h),e(window).off("resize",h)},setup(){window.innerWidth>1024&&h()}}),(function(){let s=null,o=0,c=0;function r(){s&&clearTimeout(s),s=setTimeout(function(){typeof w=="function"&&w(),typeof h=="function"&&h()},100)}function p(){const E=document.querySelectorAll("img");if(c=E.length,c===0){r();return}E.forEach(function(x){x.complete&&x.naturalHeight!==0?(o++,o===c&&r()):(x.addEventListener("load",function(){o++,r()},{once:!0}),x.addEventListener("error",function(){o++,r()},{once:!0}))})}document.readyState==="complete"?p():window.addEventListener("load",function(){p()}),typeof MutationObserver<"u"&&new MutationObserver(function(x){let _=!1;x.forEach(function(S){S.addedNodes.forEach(function(m){if(m.nodeType===1){if(m.tagName==="IMG")_=!0,c++,m.complete&&m.naturalHeight!==0?(o++,r()):(m.addEventListener("load",function(){o++,r()},{once:!0}),m.addEventListener("error",function(){o++,r()},{once:!0}));else if(m.querySelectorAll){const k=m.querySelectorAll("img");k.length>0&&(_=!0,c+=k.length,k.forEach(function(b){b.complete&&b.naturalHeight!==0?(o++,r()):(b.addEventListener("load",function(){o++,r()},{once:!0}),b.addEventListener("error",function(){o++,r()},{once:!0}))}))}}})}),_&&r()}).observe(document.body,{childList:!0,subtree:!0})})()}})(),(function(){let t=null,n=e(".main-header"),l=function(){if(window.scrollY<64){n.css("opacity","1"),clearTimeout(t),t=null;return}if(t)return;let i=window.scrollY;t=setTimeout(()=>{window.scrollYcode[class*=language-],pre[class*=language-]{background:var(--theme-pre-code-bg)}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:#ffffff80}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}pre[data-line]{position:relative;padding:1em 0 1em 3em}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:#997a6614;background:linear-gradient(to right,#997a661a 70%,#997a6600);pointer-events:none;line-height:inherit;white-space:pre}@media print{.line-highlight{-webkit-print-color-adjust:exact;color-adjust:exact}}.line-highlight:before,.line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:#997a6666;color:#f4f1ef;font:700 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}.line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}.line-numbers .line-highlight:after,.line-numbers .line-highlight:before{content:none}pre[id].linkable-line-numbers span.line-numbers-rows{pointer-events:all}pre[id].linkable-line-numbers span.line-numbers-rows>span:before{cursor:pointer}pre[id].linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:#80808033}pre[class*=language-].line-numbers{position:relative;padding-left:3em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}span.inline-color-wrapper{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyIDIiPjxwYXRoIGZpbGw9ImdyYXkiIGQ9Ik0wIDBoMnYySDB6Ii8+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0wIDBoMXYxSDB6TTEgMWgxdjFIMXoiLz48L3N2Zz4=);background-position:center;background-size:110%;display:inline-block;height:1.333ch;width:1.333ch;margin:0 .333ch;box-sizing:border-box;border:1px solid #fff;outline:1px solid rgba(0,0,0,.5);overflow:hidden}span.inline-color{display:block;height:120%;width:120%}.copy-to-clipboard-button{cursor:pointer}.copy-to-clipboard-button span{color:#690!important;font-size:.6rem}div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:1}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em 0 0;background:transparent;border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none} diff --git a/common/prism/prism.min.js b/common/prism/prism.min.js new file mode 100644 index 0000000..bd11d29 --- /dev/null +++ b/common/prism/prism.min.js @@ -0,0 +1,40 @@ +var _self=typeof window<"u"?window:typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope?self:{},Prism=(function(e){var i=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,n=0,d={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function r(t){return t instanceof p?new p(t.type,r(t.content),t.alias):Array.isArray(t)?t.map(r):t.replace(/&/g,"&").replace(/"u")return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(u){var r=(/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(u.stack)||[])[1];if(r){var t=document.getElementsByTagName("script");for(var f in t)if(t[f].src==r)return t[f]}return null}},isActive:function(r,t,f){for(var u="no-"+t;r;){var g=r.classList;if(g.contains(t))return!0;if(g.contains(u))return!1;r=r.parentElement}return!!f}},languages:{plain:d,plaintext:d,text:d,txt:d,extend:function(r,t){var f=a.util.clone(a.languages[r]);for(var u in t)f[u]=t[u];return f},insertBefore:function(r,t,f,u){var g=(u=u||a.languages)[r],y={};for(var m in g)if(g.hasOwnProperty(m)){if(m==t)for(var A in f)f.hasOwnProperty(A)&&(y[A]=f[A]);f.hasOwnProperty(m)||(y[m]=g[m])}var I=u[r];return u[r]=y,a.languages.DFS(a.languages,(function(k,x){x===I&&k!=r&&(this[k]=y)})),y},DFS:function r(t,f,u,g){g=g||{};var y=a.util.objId;for(var m in t)if(t.hasOwnProperty(m)){f.call(t,m,t[m],u||m);var A=t[m],I=a.util.type(A);I!=="Object"||g[y(A)]?I!=="Array"||g[y(A)]||(g[y(A)]=!0,r(A,f,m,g)):(g[y(A)]=!0,r(A,f,null,g))}}},plugins:{},highlightAll:function(r,t){a.highlightAllUnder(document,r,t)},highlightAllUnder:function(r,t,f){var u={callback:f,container:r,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};a.hooks.run("before-highlightall",u),u.elements=Array.prototype.slice.apply(u.container.querySelectorAll(u.selector)),a.hooks.run("before-all-elements-highlight",u);for(var g,y=0;g=u.elements[y++];)a.highlightElement(g,t===!0,u.callback)},highlightElement:function(r,t,f){var u=a.util.getLanguage(r),g=a.languages[u];a.util.setLanguage(r,u);var y=r.parentElement;y&&y.nodeName.toLowerCase()==="pre"&&a.util.setLanguage(y,u);var m={element:r,language:u,grammar:g,code:r.textContent};function A(k){m.highlightedCode=k,a.hooks.run("before-insert",m),m.element.innerHTML=m.highlightedCode,a.hooks.run("after-highlight",m),a.hooks.run("complete",m),f&&f.call(m.element)}if(a.hooks.run("before-sanity-check",m),(y=m.element.parentElement)&&y.nodeName.toLowerCase()==="pre"&&!y.hasAttribute("tabindex")&&y.setAttribute("tabindex","0"),!m.code)return a.hooks.run("complete",m),void(f&&f.call(m.element));if(a.hooks.run("before-highlight",m),m.grammar)if(t&&e.Worker){var I=new Worker(a.filename);I.onmessage=function(k){A(k.data)},I.postMessage(JSON.stringify({language:m.language,code:m.code,immediateClose:!0}))}else A(a.highlight(m.code,m.grammar,m.language));else A(a.util.encode(m.code))},highlight:function(r,t,f){var u={code:r,grammar:t,language:f};if(a.hooks.run("before-tokenize",u),!u.grammar)throw new Error('The language "'+u.language+'" has no grammar.');return u.tokens=a.tokenize(u.code,u.grammar),a.hooks.run("after-tokenize",u),p.stringify(a.util.encode(u.tokens),u.language)},tokenize:function(r,t){var f=t.rest;if(f){for(var u in f)t[u]=f[u];delete t.rest}var g=new o;return s(g,g.head,r),l(r,g,t,g.head,0),(function(y){for(var m=[],A=y.head.next;A!==y.tail;)m.push(A.value),A=A.next;return m})(g)},hooks:{all:{},add:function(r,t){var f=a.hooks.all;f[r]=f[r]||[],f[r].push(t)},run:function(r,t){var f=a.hooks.all[r];if(f&&f.length)for(var u,g=0;u=f[g++];)u(t)}},Token:p};function p(r,t,f,u){this.type=r,this.content=t,this.alias=f,this.length=0|(u||"").length}function b(r,t,f,u){r.lastIndex=t;var g=r.exec(f);if(g&&u&&g[1]){var y=g[1].length;g.index+=y,g[0]=g[0].slice(y)}return g}function l(r,t,f,u,g,y){for(var m in f)if(f.hasOwnProperty(m)&&f[m]){var A=f[m];A=Array.isArray(A)?A:[A];for(var I=0;I=y.reach);v+=T.value.length,T=T.next){var C=T.value;if(t.length>r.length)return;if(!(C instanceof p)){var L,P=1;if(R){if(!(L=b(O,v,r,B))||L.index>=r.length)break;var M=L.index,$=L.index+L[0].length,N=v;for(N+=T.value.length;M>=N;)N+=(T=T.next).value.length;if(v=N-=T.value.length,T.value instanceof p)continue;for(var _=T;_!==t.tail&&(N<$||typeof _.value=="string");_=_.next)P++,N+=_.value.length;P--,C=r.slice(v,N),L.index-=v}else if(!(L=b(O,0,C,B)))continue;M=L.index;var D=L[0],U=C.slice(0,M),j=C.slice(M+D.length),H=v+C.length;y&&H>y.reach&&(y.reach=H);var G=T.prev;if(U&&(G=s(t,G,U),v+=U.length),h(t,G,P),T=s(t,G,new p(m,x?a.tokenize(D,x):D,F,D)),j&&s(t,T,j),P>1){var z={cause:m+","+I,reach:H};l(r,t,f,T.prev,v,z),y&&z.reach>y.reach&&(y.reach=z.reach)}}}}}}function o(){var r={value:null,prev:null,next:null},t={value:null,prev:r,next:null};r.next=t,this.head=r,this.tail=t,this.length=0}function s(r,t,f){var u=t.next,g={value:f,prev:t,next:u};return t.next=g,u.prev=g,r.length++,g}function h(r,t,f){for(var u=t.next,g=0;g"+g.content+""},!e.document)return e.addEventListener&&(a.disableWorkerMessageHandler||e.addEventListener("message",(function(r){var t=JSON.parse(r.data),f=t.language,u=t.code,g=t.immediateClose;e.postMessage(a.highlight(u,a.languages[f],f)),g&&e.close()}),!1)),a;var c=a.util.currentScript();function E(){a.manual||a.highlightAll()}if(c&&(a.filename=c.src,c.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var S=document.readyState;S==="loading"||S==="interactive"&&c&&c.defer?document.addEventListener("DOMContentLoaded",E):window.requestAnimationFrame?window.requestAnimationFrame(E):window.setTimeout(E,16)}return a})(_self);typeof module<"u"&&module.exports&&(module.exports=Prism),typeof global<"u"&&(global.Prism=Prism),Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(e,i){var n={};n["language-"+i]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[i]},n.cdata=/^$/i;var d={"included-cdata":{pattern://i,inside:n}};d["language-"+i]={pattern:/[\s\S]+/,inside:Prism.languages[i]};var a={};a[e]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:d},Prism.languages.insertBefore("markup","cdata",a)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(e,i){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(`(^|["'\\s])(?:`+e+`)\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s'">=]+(?=[\\s>]))`,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[i,"language-"+i],inside:Prism.languages[i]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml,(function(e){var i=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp(`@[\\w-](?:[^;{\\s"']|\\s+(?!\\s)|`+i.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+i.source+`|(?:[^\\\\\r +()"']|\\\\[^])*)\\)`,"i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+i.source+"$"),alias:"url"}}},selector:{pattern:RegExp(`(^|[{}\\s])[^{}\\s](?:[^{};"'\\s]|\\s+(?![\\s{])|`+i.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:i,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),n.tag.addAttribute("style","css"))})(Prism),Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(`((?:^|[^$\\w\\xA0-\\uFFFF."'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r +]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r +])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r +]|\\\\.|\\[(?:[^[\\]\\\\\r +]|\\\\.|\\[(?:[^[\\]\\\\\r +]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r +])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r +,.;:})\\]]|//))`),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript,(function(e){var i="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},d={bash:n,environment:{pattern:RegExp("\\$"+i),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+i),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+i),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:d},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:d},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:d.entity}}],environment:{pattern:RegExp("\\$?"+i),alias:"constant"},variable:d.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var a=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],p=d.variable[1].inside,b=0;b>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),Prism.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},Prism.languages.c.string],char:Prism.languages.c.char,comment:Prism.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:Prism.languages.c}}}}),Prism.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete Prism.languages.c.boolean,(function(e){function i(N,_){return N.replace(/<<(\d+)>>/g,(function(D,U){return"(?:"+_[+U]+")"}))}function n(N,_,D){return RegExp(i(N,_),D||"")}function d(N,_){for(var D=0;D<_;D++)N=N.replace(/<>/g,(function(){return"(?:"+N+")"}));return N.replace(/<>/g,"[^\\s\\S]")}var a="bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",p="class enum interface record struct",b="add alias and ascending async await by descending from(?=\\s*(?:\\w|$)) get global group into init(?=\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\s*{)",l="abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield";function o(N){return"\\b(?:"+N.trim().replace(/ /g,"|")+")\\b"}var s=o(p),h=RegExp(o(a+" "+p+" "+b+" "+l)),c=o(p+" "+b+" "+l),E=o(a+" "+p+" "+l),S=d("<(?:[^<>;=+\\-*/%&|^]|<>)*>",2),r=d("\\((?:[^()]|<>)*\\)",2),t="@?\\b[A-Za-z_]\\w*\\b",f=i("<<0>>(?:\\s*<<1>>)?",[t,S]),u=i("(?!<<0>>)<<1>>(?:\\s*\\.\\s*<<1>>)*",[c,f]),g="\\[\\s*(?:,\\s*)*\\]",y=i("<<0>>(?:\\s*(?:\\?\\s*)?<<1>>)*(?:\\s*\\?)?",[u,g]),m=i("[^,()<>[\\];=+\\-*/%&|^]|<<0>>|<<1>>|<<2>>",[S,r,g]),A=i("\\(<<0>>+(?:,<<0>>+)+\\)",[m]),I=i("(?:<<0>>|<<1>>)(?:\\s*(?:\\?\\s*)?<<2>>)*(?:\\s*\\?)?",[A,u,g]),k={keyword:h,punctuation:/[<>()?,.:[\]]/},x=`'(?:[^\r +'\\\\]|\\\\.|\\\\[Uux][\\da-fA-F]{1,8})'`,B=`"(?:\\\\.|[^\\\\"\r +])*"`;e.languages.csharp=e.languages.extend("clike",{string:[{pattern:n("(^|[^$\\\\])<<0>>",['@"(?:""|\\\\[^]|[^\\\\"])*"(?!")']),lookbehind:!0,greedy:!0},{pattern:n("(^|[^@$\\\\])<<0>>",[B]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:n("(\\busing\\s+static\\s+)<<0>>(?=\\s*;)",[u]),lookbehind:!0,inside:k},{pattern:n("(\\busing\\s+<<0>>\\s*=\\s*)<<1>>(?=\\s*;)",[t,I]),lookbehind:!0,inside:k},{pattern:n("(\\busing\\s+)<<0>>(?=\\s*=)",[t]),lookbehind:!0},{pattern:n("(\\b<<0>>\\s+)<<1>>",[s,f]),lookbehind:!0,inside:k},{pattern:n("(\\bcatch\\s*\\(\\s*)<<0>>",[u]),lookbehind:!0,inside:k},{pattern:n("(\\bwhere\\s+)<<0>>",[t]),lookbehind:!0},{pattern:n("(\\b(?:is(?:\\s+not)?|as)\\s+)<<0>>",[y]),lookbehind:!0,inside:k},{pattern:n("\\b<<0>>(?=\\s+(?!<<1>>|with\\s*\\{)<<2>>(?:\\s*[=,;:{)\\]]|\\s+(?:in|when)\\b))",[I,E,t]),inside:k}],keyword:h,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:[dflmu]|lu|ul)?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),e.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),e.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:n("([(,]\\s*)<<0>>(?=\\s*:)",[t]),lookbehind:!0,alias:"punctuation"}}),e.languages.insertBefore("csharp","class-name",{namespace:{pattern:n("(\\b(?:namespace|using)\\s+)<<0>>(?:\\s*\\.\\s*<<0>>)*(?=\\s*[;{])",[t]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:n("(\\b(?:default|sizeof|typeof)\\s*\\(\\s*(?!\\s))(?:[^()\\s]|\\s(?!\\s)|<<0>>)*(?=\\s*\\))",[r]),lookbehind:!0,alias:"class-name",inside:k},"return-type":{pattern:n("<<0>>(?=\\s+(?:<<1>>\\s*(?:=>|[({]|\\.\\s*this\\s*\\[)|this\\s*\\[))",[I,u]),inside:k,alias:"class-name"},"constructor-invocation":{pattern:n("(\\bnew\\s+)<<0>>(?=\\s*[[({])",[I]),lookbehind:!0,inside:k,alias:"class-name"},"generic-method":{pattern:n("<<0>>\\s*<<1>>(?=\\s*\\()",[t,S]),inside:{function:n("^<<0>>",[t]),generic:{pattern:RegExp(S),alias:"class-name",inside:k}}},"type-list":{pattern:n("\\b((?:<<0>>\\s+<<1>>|record\\s+<<1>>\\s*<<5>>|where\\s+<<2>>)\\s*:\\s*)(?:<<3>>|<<4>>|<<1>>\\s*<<5>>|<<6>>)(?:\\s*,\\s*(?:<<3>>|<<4>>|<<6>>))*(?=\\s*(?:where|[{;]|=>|$))",[s,f,t,I,h.source,r,"\\bnew\\s*\\(\\s*\\)"]),lookbehind:!0,inside:{"record-arguments":{pattern:n("(^(?!new\\s*\\()<<0>>\\s*)<<1>>",[f,r]),lookbehind:!0,greedy:!0,inside:e.languages.csharp},keyword:h,"class-name":{pattern:RegExp(I),greedy:!0,inside:k},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var R=B+"|"+x,F=i(`/(?![*/])|//[^\r +]*[\r +]|/\\*(?:[^*]|\\*(?!/))*\\*/|<<0>>`,[R]),w=d(i(`[^"'/()]|<<0>>|\\(<>*\\)`,[F]),2),O="\\b(?:assembly|event|field|method|module|param|property|return|type)\\b",T=i("<<0>>(?:\\s*\\(<<1>>*\\))?",[u,w]);e.languages.insertBefore("csharp","class-name",{attribute:{pattern:n("((?:^|[^\\s\\w>)?])\\s*\\[\\s*)(?:<<0>>\\s*:\\s*)?<<1>>(?:\\s*,\\s*<<1>>)*(?=\\s*\\])",[O,T]),lookbehind:!0,greedy:!0,inside:{target:{pattern:n("^<<0>>(?=\\s*:)",[O]),alias:"keyword"},"attribute-arguments":{pattern:n("\\(<<0>>*\\)",[w]),inside:e.languages.csharp},"class-name":{pattern:RegExp(u),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var v=`:[^}\r +]+`,C=d(i(`[^"'/()]|<<0>>|\\(<>*\\)`,[F]),2),L=i("\\{(?!\\{)(?:(?![}:])<<0>>)*<<1>>?\\}",[C,v]),P=d(i(`[^"'/()]|/(?!\\*)|/\\*(?:[^*]|\\*(?!/))*\\*/|<<0>>|\\(<>*\\)`,[R]),2),M=i("\\{(?!\\{)(?:(?![}:])<<0>>)*<<1>>?\\}",[P,v]);function $(N,_){return{interpolation:{pattern:n("((?:^|[^{])(?:\\{\\{)*)<<0>>",[N]),lookbehind:!0,inside:{"format-string":{pattern:n("(^\\{(?:(?![}:])<<0>>)*)<<1>>(?=\\}$)",[_,v]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:e.languages.csharp}}},string:/[\s\S]+/}}e.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:n('(^|[^\\\\])(?:\\$@|@\\$)"(?:""|\\\\[^]|\\{\\{|<<0>>|[^\\\\{"])*"',[L]),lookbehind:!0,greedy:!0,inside:$(L,C)},{pattern:n('(^|[^@\\\\])\\$"(?:\\\\.|\\{\\{|<<0>>|[^\\\\"{])*"',[M]),lookbehind:!0,greedy:!0,inside:$(M,P)}],char:{pattern:RegExp(x),greedy:!0}}),e.languages.dotnet=e.languages.cs=e.languages.csharp})(Prism),(function(e){var i=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n="\\b(?!)\\w+(?:\\s*\\.\\s*\\w+)*\\b".replace(//g,(function(){return i.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp("(\\b(?:class|concept|enum|struct|typename)\\s+)(?!)\\w+".replace(//g,(function(){return i.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:i,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(`(\\b(?:import|module)\\s+)(?:"(?:\\\\(?:\r +|[^])|[^"\\\\\r +])*"|<[^<>\r +]*>|`+"(?:\\s*:\\s*)?|:\\s*".replace(//g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])})(Prism),(function(e){var i,n=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css.selector={pattern:e.languages.css.selector.pattern,lookbehind:!0,inside:i={"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+/,class:/\.[-\w]+/,id:/#[-\w]+/,attribute:{pattern:RegExp(`\\[(?:[^[\\]"']|`+n.source+")*\\]"),greedy:!0,inside:{punctuation:/^\[|\]$/,"case-sensitivity":{pattern:/(\s)[si]$/i,lookbehind:!0,alias:"keyword"},namespace:{pattern:/^(\s*)(?:(?!\s)[-*\w\xA0-\uFFFF])*\|(?!=)/,lookbehind:!0,inside:{punctuation:/\|$/}},"attr-name":{pattern:/^(\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+/,lookbehind:!0},"attr-value":[n,{pattern:/(=\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+(?=\s*$)/,lookbehind:!0}],operator:/[|~*^$]?=/}},"n-th":[{pattern:/(\(\s*)[+-]?\d*[\dn](?:\s*[+-]\s*\d+)?(?=\s*\))/,lookbehind:!0,inside:{number:/[\dn]+/,operator:/[+-]/}},{pattern:/(\(\s*)(?:even|odd)(?=\s*\))/i,lookbehind:!0}],combinator:/>|\+|~|\|\|/,punctuation:/[(),]/}},e.languages.css.atrule.inside["selector-function-argument"].inside=i,e.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}});var d={pattern:/(\b\d+)(?:%|[a-z]+(?![\w-]))/,lookbehind:!0},a={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0};e.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[{pattern:/(^|[^\w-])(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|RebeccaPurple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)(?![\w-])/i,lookbehind:!0},{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:d,number:a,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:d,number:a})})(Prism),(function(e){var i="(?:[ ]+(?![ ])(?:)?|)".replace(//g,(function(){return`\\\\[\r +](?:\\s|\\\\[\r +]|#.*(?!.))*(?![\\s#]|\\\\[\r +])`})),n=`"(?:[^"\\\\\r +]|\\\\(?:\r +|[^]))*"|'(?:[^'\\\\\r +]|\\\\(?:\r +|[^]))*'`,d=`--[\\w-]+=(?:|(?!["'])(?:[^\\s\\\\]|\\\\.)+)`.replace(//g,(function(){return n})),a={pattern:RegExp(n),greedy:!0},p={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function b(l,o){return l=l.replace(//g,(function(){return d})).replace(//g,(function(){return i})),RegExp(l,o)}e.languages.docker={instruction:{pattern:/(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/im,lookbehind:!0,greedy:!0,inside:{options:{pattern:b("(^(?:ONBUILD)?\\w+)(?:)*","i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[a,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:b("(^(?:ONBUILD)?HEALTHCHECK(?:)*)(?:CMD|NONE)\\b","i"),lookbehind:!0,greedy:!0},{pattern:b("(^(?:ONBUILD)?FROM(?:)*(?!--)[^ \\\\]+)AS","i"),lookbehind:!0,greedy:!0},{pattern:b("(^ONBUILD)\\w+","i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:p,string:a,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,operator:/\\$/m}},comment:p},e.languages.dockerfile=e.languages.docker})(Prism),Prism.languages.git={comment:/^#.*/m,deleted:/^[-–].*/m,inserted:/^\+.*/m,string:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s--?\w+/}},coord:/^@@.*@@$/m,"commit-sha1":/^commit \w{40}$/m},Prism.languages.go=Prism.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),Prism.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete Prism.languages.go["class-name"],(function(e){function i(s){return RegExp("(^(?:"+s+"):[ ]*(?![ ]))[^]+","i")}e.languages.http={"request-line":{pattern:/^(?:CONNECT|DELETE|GET|HEAD|OPTIONS|PATCH|POST|PRI|PUT|SEARCH|TRACE)\s(?:https?:\/\/|\/)\S*\sHTTP\/[\d.]+/m,inside:{method:{pattern:/^[A-Z]+\b/,alias:"property"},"request-target":{pattern:/^(\s)(?:https?:\/\/|\/)\S*(?=\s)/,lookbehind:!0,alias:"url",inside:e.languages.uri},"http-version":{pattern:/^(\s)HTTP\/[\d.]+/,lookbehind:!0,alias:"property"}}},"response-status":{pattern:/^HTTP\/[\d.]+ \d+ .+/m,inside:{"http-version":{pattern:/^HTTP\/[\d.]+/,alias:"property"},"status-code":{pattern:/^(\s)\d+(?=\s)/,lookbehind:!0,alias:"number"},"reason-phrase":{pattern:/^(\s).+/,lookbehind:!0,alias:"string"}}},header:{pattern:/^[\w-]+:.+(?:(?:\r\n?|\n)[ \t].+)*/m,inside:{"header-value":[{pattern:i("Content-Security-Policy"),lookbehind:!0,alias:["csp","languages-csp"],inside:e.languages.csp},{pattern:i("Public-Key-Pins(?:-Report-Only)?"),lookbehind:!0,alias:["hpkp","languages-hpkp"],inside:e.languages.hpkp},{pattern:i("Strict-Transport-Security"),lookbehind:!0,alias:["hsts","languages-hsts"],inside:e.languages.hsts},{pattern:i("[^:]+"),lookbehind:!0}],"header-name":{pattern:/^[^:]+/,alias:"keyword"},punctuation:/^:/}}};var n,d=e.languages,a={"application/javascript":d.javascript,"application/json":d.json||d.javascript,"application/xml":d.xml,"text/xml":d.xml,"text/html":d.html,"text/css":d.css,"text/plain":d.plain},p={"application/json":!0,"application/xml":!0};function b(s){var h=s.replace(/^[a-z]+\//,"");return"(?:"+s+"|\\w+/(?:[\\w.-]+\\+)+"+h+"(?![+\\w.-]))"}for(var l in a)if(a[l]){n=n||{};var o=p[l]?b(l):l;n[l.replace(/\//g,"-")]={pattern:RegExp("(content-type:\\s*"+o+`(?:(?:\r +?| +)[\\w-].*)*(?:\r(?: +|(?! +))| +))[^ \\w-][^]*`,"i"),lookbehind:!0,inside:a[l]}}n&&e.languages.insertBefore("http","header",n)})(Prism),Prism.languages.icon={comment:/#.*/,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n_]|\\.|_(?!\1)(?:\r\n|[\s\S]))*\1/,greedy:!0},number:/\b(?:\d+r[a-z\d]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)\b|\.\d+\b/i,"builtin-keyword":{pattern:/&(?:allocated|ascii|clock|collections|cset|current|date|dateline|digits|dump|e|error(?:number|text|value)?|errout|fail|features|file|host|input|lcase|letters|level|line|main|null|output|phi|pi|pos|progname|random|regions|source|storage|subject|time|trace|ucase|version)\b/,alias:"variable"},directive:{pattern:/\$\w+/,alias:"builtin"},keyword:/\b(?:break|by|case|create|default|do|else|end|every|fail|global|if|initial|invocable|link|local|next|not|of|procedure|record|repeat|return|static|suspend|then|to|until|while)\b/,function:/\b(?!\d)\w+(?=\s*[({]|\s*!\s*\[)/,operator:/[+-]:(?!=)|(?:[\/?@^%&]|\+\+?|--?|==?=?|~==?=?|\*\*?|\|\|\|?|<(?:->?|>?=?)(?::=)?|:(?:=:?)?|[!.\\|~]/,punctuation:/[\[\](){},;]/},Prism.languages.ini={comment:{pattern:/(^[ \f\t\v]*)[#;][^\n\r]*/m,lookbehind:!0},section:{pattern:/(^[ \f\t\v]*)\[[^\n\r\]]*\]?/m,lookbehind:!0,inside:{"section-name":{pattern:/(^\[[ \f\t\v]*)[^ \f\t\v\]]+(?:[ \f\t\v]+[^ \f\t\v\]]+)*/,lookbehind:!0,alias:"selector"},punctuation:/\[|\]/}},key:{pattern:/(^[ \f\t\v]*)[^ \f\n\r\t\v=]+(?:[ \f\t\v]+[^ \f\n\r\t\v=]+)*(?=[ \f\t\v]*=)/m,lookbehind:!0,alias:"attr-name"},value:{pattern:/(=[ \f\t\v]*)[^ \f\n\r\t\v]+(?:[ \f\t\v]+[^ \f\n\r\t\v]+)*/,lookbehind:!0,alias:"attr-value",inside:{"inner-value":{pattern:/^("|').+(?=\1$)/,lookbehind:!0}}},punctuation:/=/},(function(e){var i=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,n="(?:[a-z]\\w*\\s*\\.\\s*)*(?:[A-Z]\\w*\\s*\\.\\s*)*",d={pattern:RegExp("(^|[^\\w.])"+n+"[A-Z](?:[\\d_A-Z]*[a-z]\\w*)?\\b"),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};e.languages.java=e.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[d,{pattern:RegExp("(^|[^\\w.])"+n+"[A-Z]\\w*(?=\\s+\\w+\\s*[;,=()]|\\s*(?:\\[[\\s,]*\\]\\s*)?::\\s*new\\b)"),lookbehind:!0,inside:d.inside},{pattern:RegExp("(\\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\\s+)"+n+"[A-Z]\\w*\\b"),lookbehind:!0,inside:d.inside}],keyword:i,function:[e.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),e.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":d,keyword:i,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp("(\\bimport\\s+)"+n+"(?:[A-Z]\\w*|\\*)(?=\\s*;)"),lookbehind:!0,inside:{namespace:d.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp("(\\bimport\\s+static\\s+)"+n+"(?:\\w+|\\*)(?=\\s*;)"),lookbehind:!0,alias:"static",inside:{namespace:d.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp("(\\b(?:exports|import(?:\\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\\s+)(?!)[a-z]\\w*(?:\\.[a-z]\\w*)*\\.?".replace(//g,(function(){return i.source}))),lookbehind:!0,inside:{punctuation:/\./}}})})(Prism),(function(e){function i(n,d){return"___"+n.toUpperCase()+d+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,d,a,p){if(n.language===d){var b=n.tokenStack=[];n.code=n.code.replace(a,(function(l){if(typeof p=="function"&&!p(l))return l;for(var o,s=b.length;n.code.indexOf(o=i(d,s))!==-1;)++s;return b[s]=l,o})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,d){if(n.language===d&&n.tokenStack){n.grammar=e.languages[d];var a=0,p=Object.keys(n.tokenStack);(function b(l){for(var o=0;o=p.length);o++){var s=l[o];if(typeof s=="string"||s.content&&typeof s.content=="string"){var h=p[a],c=n.tokenStack[h],E=typeof s=="string"?s:s.content,S=i(d,h),r=E.indexOf(S);if(r>-1){++a;var t=E.substring(0,r),f=new e.Token(d,e.tokenize(c,n.grammar),"language-"+d,c),u=E.substring(r+S.length),g=[];t&&g.push.apply(g,b([t])),g.push(f),u&&g.push.apply(g,b([u])),typeof s=="string"?l.splice.apply(l,[o,1].concat(g)):s.content=g}}else s.content&&b(s.content)}return l})(n.tokens)}}}})})(Prism),(function(e){var i=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,n=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],d=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,a=/|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,p=/[{}\[\](),:;]/;e.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:i,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:d,operator:a,punctuation:p};var b={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:e.languages.php},l=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:b}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:b}}];e.languages.insertBefore("php","variable",{string:l,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:i,string:l,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,number:d,operator:a,punctuation:p}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),e.hooks.add("before-tokenize",(function(o){/<\?/.test(o.code)&&e.languages["markup-templating"].buildPlaceholders(o,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g)})),e.hooks.add("after-tokenize",(function(o){e.languages["markup-templating"].tokenizePlaceholders(o,"php")}))})(Prism),(function(e){var i=e.languages.javadoclike={parameter:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*@(?:arg|arguments|param)\s+)\w+/m,lookbehind:!0},keyword:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*|\{)@[a-z][a-zA-Z-]+\b/m,lookbehind:!0},punctuation:/[{}]/};Object.defineProperty(i,"addSupport",{value:function(n,d){typeof n=="string"&&(n=[n]),n.forEach((function(a){(function(p,b){var l="doc-comment",o=e.languages[p];if(o){var s=o[l];if(s||(s=(o=e.languages.insertBefore(p,"comment",{"doc-comment":{pattern:/(^|[^\\])\/\*\*[^/][\s\S]*?(?:\*\/|$)/,lookbehind:!0,alias:"comment"}}))[l]),s instanceof RegExp&&(s=o[l]={pattern:s}),Array.isArray(s))for(var h=0,c=s.length;h]?=|\?\/\/|\/\/=?|[-+*/%]=?|[<>?]|\b(?:and|not|or)\b/],"c-style-function":{pattern:/\b[a-z_]\w*(?=\s*\()/i,alias:"function"},punctuation:/::|[()\[\]{},:;]|\.(?=\s*[\[\w$])/,dot:{pattern:/\./,alias:"important"}};d.interpolation.inside.content.inside=a})(Prism),(function(e){function i(l,o){return RegExp(l.replace(//g,(function(){return"(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*"})),o)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|(?:Weak)?(?:Map|Set)|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:i("(\\bimport\\b\\s*)(?:(?:\\s*,\\s*(?:\\*\\s*as\\s+|\\{[^{}]*\\}))?|\\*\\s*as\\s+|\\{[^{}]*\\})(?=\\s*\\bfrom\\b)"),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:i("(\\bexport\\b\\s*)(?:\\*(?:\\s*as\\s+)?(?=\\s*\\bfrom\\b)|\\{[^{}]*\\})"),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|finally|for|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:i("(\\.\\s*)#?"),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|(?:local|session)Storage|location|navigator|performance|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var n=["function","function-variable","method","method-variable","property-access"],d=0;d/g,(function(){return`(?:\\\\.|[^\\\\ +\r]|(?: +|\r +?)(?![\r +]))`})),RegExp("((?:^|[^\\\\])(?:\\\\{2})*)(?:"+o+")")}var n="(?:\\\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\\\|\r\n`])+",d=`\\|?__(?:\\|__)+\\|?(?:(?: +|\r +?)|(?![^]))`.replace(/__/g,(function(){return n})),a=`\\|?[ ]*:?-{3,}:?[ ]*(?:\\|[ ]*:?-{3,}:?[ ]*)+\\|?(?: +|\r +?)`;e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+d+a+"(?:"+d+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+d+a+")(?:"+d+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(n),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+d+")"+a+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+d+"$"),inside:{"table-header":{pattern:RegExp(n),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:i("\\b__(?:(?!_)|_(?:(?!_))+_)+__\\b|\\*\\*(?:(?!\\*)|\\*(?:(?!\\*))+\\*)+\\*\\*"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:i("\\b_(?:(?!_)|__(?:(?!_))+__)+_\\b|\\*(?:(?!\\*)|\\*\\*(?:(?!\\*))+\\*\\*)+\\*"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:i("(~~?)(?:(?!~))+\\2"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:i('!?\\[(?:(?!\\]))+\\](?:\\([^\\s)]+(?:[ ]+"(?:\\\\.|[^"\\\\])*")?\\)|[ ]?\\[(?:(?!\\]))+\\])'),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach((function(o){["url","bold","italic","strike","code-snippet"].forEach((function(s){o!==s&&(e.languages.markdown[o].inside.content.inside[s]=e.languages.markdown[s])}))})),e.hooks.add("after-tokenize",(function(o){o.language!=="markdown"&&o.language!=="md"||(function s(h){if(h&&typeof h!="string")for(var c=0,E=h.length;c",quot:'"'},l=String.fromCodePoint||String.fromCharCode;e.languages.md=e.languages.markdown})(Prism),(function(e){var i=/\$(?:\w[a-z\d]*(?:_[^\x00-\x1F\s"'\\()$]*)?|\{[^}\s"'\\]+\})/i;e.languages.nginx={comment:{pattern:/(^|[\s{};])#.*/,lookbehind:!0,greedy:!0},directive:{pattern:/(^|\s)\w(?:[^;{}"'\\\s]|\\.|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\s+(?:#.*(?!.)|(?![#\s])))*?(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:{string:{pattern:/((?:^|[^\\])(?:\\\\)*)(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/,lookbehind:!0,greedy:!0,inside:{escape:{pattern:/\\["'\\nrt]/,alias:"entity"},variable:i}},comment:{pattern:/(\s)#.*/,lookbehind:!0,greedy:!0},keyword:{pattern:/^\S+/,greedy:!0},boolean:{pattern:/(\s)(?:off|on)(?!\S)/,lookbehind:!0},number:{pattern:/(\s)\d+[a-z]*(?!\S)/i,lookbehind:!0},variable:i}},punctuation:/[{};]/}})(Prism),(function(e){var i="(?:\\b[a-zA-Z]\\w*|[|\\\\[\\]])+";e.languages.phpdoc=e.languages.extend("javadoclike",{parameter:{pattern:RegExp("(@(?:global|param|property(?:-read|-write)?|var)\\s+(?:"+i+"\\s+)?)\\$\\w+"),lookbehind:!0}}),e.languages.insertBefore("phpdoc","keyword",{"class-name":[{pattern:RegExp("(@(?:global|package|param|property(?:-read|-write)?|return|subpackage|throws|var)\\s+)"+i),lookbehind:!0,inside:{keyword:/\b(?:array|bool|boolean|callback|double|false|float|int|integer|mixed|null|object|resource|self|string|true|void)\b/,punctuation:/[|\\[\]()]/}}]}),e.languages.javadoclike.addSupport("php",e.languages.phpdoc)})(Prism),Prism.languages.insertBefore("php","variable",{this:{pattern:/\$this\b/,alias:"keyword"},global:/\$(?:GLOBALS|HTTP_RAW_POST_DATA|_(?:COOKIE|ENV|FILES|GET|POST|REQUEST|SERVER|SESSION)|argc|argv|http_response_header|php_errormsg)\b/,scope:{pattern:/\b[\w\\]+::/,inside:{keyword:/\b(?:parent|self|static)\b/,punctuation:/::|\\/}}}),Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/},Prism.languages.plsql=Prism.languages.extend("sql",{comment:{pattern:/\/\*[\s\S]*?\*\/|--.*/,greedy:!0},keyword:/\b(?:A|ACCESSIBLE|ADD|AGENT|AGGREGATE|ALL|ALTER|AND|ANY|ARRAY|AS|ASC|AT|ATTRIBUTE|AUTHID|AVG|BEGIN|BETWEEN|BFILE_BASE|BINARY|BLOB_BASE|BLOCK|BODY|BOTH|BOUND|BULK|BY|BYTE|C|CALL|CALLING|CASCADE|CASE|CHAR|CHARACTER|CHARSET|CHARSETFORM|CHARSETID|CHAR_BASE|CHECK|CLOB_BASE|CLONE|CLOSE|CLUSTER|CLUSTERS|COLAUTH|COLLECT|COLUMNS|COMMENT|COMMIT|COMMITTED|COMPILED|COMPRESS|CONNECT|CONSTANT|CONSTRUCTOR|CONTEXT|CONTINUE|CONVERT|COUNT|CRASH|CREATE|CREDENTIAL|CURRENT|CURSOR|CUSTOMDATUM|DANGLING|DATA|DATE|DATE_BASE|DAY|DECLARE|DEFAULT|DEFINE|DELETE|DESC|DETERMINISTIC|DIRECTORY|DISTINCT|DOUBLE|DROP|DURATION|ELEMENT|ELSE|ELSIF|EMPTY|END|ESCAPE|EXCEPT|EXCEPTION|EXCEPTIONS|EXCLUSIVE|EXECUTE|EXISTS|EXIT|EXTERNAL|FETCH|FINAL|FIRST|FIXED|FLOAT|FOR|FORALL|FORCE|FROM|FUNCTION|GENERAL|GOTO|GRANT|GROUP|HASH|HAVING|HEAP|HIDDEN|HOUR|IDENTIFIED|IF|IMMEDIATE|IMMUTABLE|IN|INCLUDING|INDEX|INDEXES|INDICATOR|INDICES|INFINITE|INSERT|INSTANTIABLE|INT|INTERFACE|INTERSECT|INTERVAL|INTO|INVALIDATE|IS|ISOLATION|JAVA|LANGUAGE|LARGE|LEADING|LENGTH|LEVEL|LIBRARY|LIKE|LIKE2|LIKE4|LIKEC|LIMIT|LIMITED|LOCAL|LOCK|LONG|LOOP|MAP|MAX|MAXLEN|MEMBER|MERGE|MIN|MINUS|MINUTE|MOD|MODE|MODIFY|MONTH|MULTISET|MUTABLE|NAME|NAN|NATIONAL|NATIVE|NCHAR|NEW|NOCOMPRESS|NOCOPY|NOT|NOWAIT|NULL|NUMBER_BASE|OBJECT|OCICOLL|OCIDATE|OCIDATETIME|OCIDURATION|OCIINTERVAL|OCILOBLOCATOR|OCINUMBER|OCIRAW|OCIREF|OCIREFCURSOR|OCIROWID|OCISTRING|OCITYPE|OF|OLD|ON|ONLY|OPAQUE|OPEN|OPERATOR|OPTION|OR|ORACLE|ORADATA|ORDER|ORGANIZATION|ORLANY|ORLVARY|OTHERS|OUT|OVERLAPS|OVERRIDING|PACKAGE|PARALLEL_ENABLE|PARAMETER|PARAMETERS|PARENT|PARTITION|PASCAL|PERSISTABLE|PIPE|PIPELINED|PLUGGABLE|POLYMORPHIC|PRAGMA|PRECISION|PRIOR|PRIVATE|PROCEDURE|PUBLIC|RAISE|RANGE|RAW|READ|RECORD|REF|REFERENCE|RELIES_ON|REM|REMAINDER|RENAME|RESOURCE|RESULT|RESULT_CACHE|RETURN|RETURNING|REVERSE|REVOKE|ROLLBACK|ROW|SAMPLE|SAVE|SAVEPOINT|SB1|SB2|SB4|SECOND|SEGMENT|SELECT|SELF|SEPARATE|SEQUENCE|SERIALIZABLE|SET|SHARE|SHORT|SIZE|SIZE_T|SOME|SPARSE|SQL|SQLCODE|SQLDATA|SQLNAME|SQLSTATE|STANDARD|START|STATIC|STDDEV|STORED|STRING|STRUCT|STYLE|SUBMULTISET|SUBPARTITION|SUBSTITUTABLE|SUBTYPE|SUM|SYNONYM|TABAUTH|TABLE|TDO|THE|THEN|TIME|TIMESTAMP|TIMEZONE_ABBR|TIMEZONE_HOUR|TIMEZONE_MINUTE|TIMEZONE_REGION|TO|TRAILING|TRANSACTION|TRANSACTIONAL|TRUSTED|TYPE|UB1|UB2|UB4|UNDER|UNION|UNIQUE|UNPLUG|UNSIGNED|UNTRUSTED|UPDATE|USE|USING|VALIST|VALUE|VALUES|VARIABLE|VARIANCE|VARRAY|VARYING|VIEW|VIEWS|VOID|WHEN|WHERE|WHILE|WITH|WORK|WRAPPED|WRITE|YEAR|ZONE)\b/i,operator:/:=?|=>|[<>^~!]=|\.\.|\|\||\*\*|[-+*/%<>=@]/}),Prism.languages.insertBefore("plsql","operator",{label:{pattern:/<<\s*\w+\s*>>/,alias:"symbol"}}),Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python,(function(e){e.languages.typescript=e.languages.extend("javascript",{"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var i=e.languages.extend("typescript",{});delete i["class-name"],e.languages.typescript["class-name"].inside=i,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:i}}}}),e.languages.ts=e.languages.typescript})(Prism),Prism.languages["visual-basic"]={comment:{pattern:/(?:['‘’]|REM\b)(?:[^\r\n_]|_(?:\r\n?|\n)?)*/i,inside:{keyword:/^REM/i}},directive:{pattern:/#(?:Const|Else|ElseIf|End|ExternalChecksum|ExternalSource|If|Region)(?:\b_[ \t]*(?:\r\n?|\n)|.)+/i,alias:"property",greedy:!0},string:{pattern:/\$?["“”](?:["“”]{2}|[^"“”])*["“”]C?/i,greedy:!0},date:{pattern:/#[ \t]*(?:\d+([/-])\d+\1\d+(?:[ \t]+(?:\d+[ \t]*(?:AM|PM)|\d+:\d+(?::\d+)?(?:[ \t]*(?:AM|PM))?))?|\d+[ \t]*(?:AM|PM)|\d+:\d+(?::\d+)?(?:[ \t]*(?:AM|PM))?)[ \t]*#/i,alias:"number"},number:/(?:(?:\b\d+(?:\.\d+)?|\.\d+)(?:E[+-]?\d+)?|&[HO][\dA-F]+)(?:[FRD]|U?[ILS])?/i,boolean:/\b(?:False|Nothing|True)\b/i,keyword:/\b(?:AddHandler|AddressOf|Alias|And(?:Also)?|As|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|C(?:Bool|Byte|Char|Date|Dbl|Dec|Int|Lng|Obj|SByte|Short|Sng|Str|Type|UInt|ULng|UShort)|Char|Class|Const|Continue|Currency|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else(?:If)?|End(?:If)?|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get(?:Type|XMLNamespace)?|Global|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|IsNot|Let|Lib|Like|Long|Loop|Me|Mod|Module|Must(?:Inherit|Override)|My(?:Base|Class)|Namespace|Narrowing|New|Next|Not(?:Inheritable|Overridable)?|Object|Of|On|Operator|Option(?:al)?|Or(?:Else)?|Out|Overloads|Overridable|Overrides|ParamArray|Partial|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|SByte|Select|Set|Shadows|Shared|short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TryCast|Type|TypeOf|U(?:Integer|Long|Short)|Until|Using|Variant|Wend|When|While|Widening|With(?:Events)?|WriteOnly|Xor)\b/i,operator:/[+\-*/\\^<=>&#@$%!]|\b_(?=[ \t]*[\r\n])/,punctuation:/[{}().,:?]/},Prism.languages.vb=Prism.languages["visual-basic"],Prism.languages.vba=Prism.languages["visual-basic"],(function(){if(typeof Prism<"u"&&typeof document<"u"&&document.querySelector){var e,i="line-numbers",n="linkable-line-numbers",d=/\n(?!$)/g,a=!0;Prism.plugins.lineHighlight={highlightLines:function(c,E,S){var r=(E=typeof E=="string"?E:c.getAttribute("data-line")||"").replace(/\s+/g,"").split(",").filter(Boolean),t=+c.getAttribute("data-line-offset")||0,f=((function(){if(e===void 0){var R=document.createElement("div");R.style.fontSize="13px",R.style.lineHeight="1.5",R.style.padding="0",R.style.border="0",R.innerHTML=" 
     ",document.body.appendChild(R),e=R.offsetHeight===38,document.body.removeChild(R)}return e})()?parseInt:parseFloat)(getComputedStyle(c).lineHeight),u=Prism.util.isActive(c,i),g=c.querySelector("code"),y=u?c:g||c,m=[],A=g.textContent.match(d),I=A?A.length+1:1,k=g&&y!=g?(function(R,F){var w=getComputedStyle(R),O=getComputedStyle(F);function T(v){return+v.substr(0,v.length-2)}return F.offsetTop+T(O.borderTopWidth)+T(O.paddingTop)-T(w.paddingTop)})(c,g):0;r.forEach((function(R){var F=R.split("-"),w=+F[0],O=+F[1]||w;if(!((O=Math.min(I+t,O))w&&T.setAttribute("data-end",String(O)),T.style.top=(w-t-1)*f+k+"px",T.textContent=new Array(O-w+2).join(` +`)}));m.push((function(){T.style.width=c.scrollWidth+"px"})),m.push((function(){y.appendChild(T)}))}}));var x=c.id;if(u&&Prism.util.isActive(c,n)&&x){l(c,n)||m.push((function(){c.classList.add(n)}));var B=parseInt(c.getAttribute("data-start")||"1");b(".line-numbers-rows > span",c).forEach((function(R,F){var w=F+B;R.onclick=function(){var O=x+"."+w;a=!1,location.hash=O,setTimeout((function(){a=!0}),1)}}))}return function(){m.forEach(o)}}};var p=0;Prism.hooks.add("before-sanity-check",(function(c){var E=c.element.parentElement;if(s(E)){var S=0;b(".line-highlight",E).forEach((function(r){S+=r.textContent.length,r.parentNode.removeChild(r)})),S&&/^(?: \n)+$/.test(c.code.slice(-S))&&(c.code=c.code.slice(0,-S))}})),Prism.hooks.add("complete",(function c(E){var S=E.element.parentElement;if(s(S)){clearTimeout(p);var r=Prism.plugins.lineNumbers,t=E.plugins&&E.plugins.lineNumbers;l(S,i)&&r&&!t?Prism.hooks.add("line-numbers",c):(Prism.plugins.lineHighlight.highlightLines(S)(),p=setTimeout(h,1))}})),window.addEventListener("hashchange",h),window.addEventListener("resize",(function(){b("pre").filter(s).map((function(c){return Prism.plugins.lineHighlight.highlightLines(c)})).forEach(o)}))}function b(c,E){return Array.prototype.slice.call((E||document).querySelectorAll(c))}function l(c,E){return c.classList.contains(E)}function o(c){c()}function s(c){return!!(c&&/pre/i.test(c.nodeName)&&(c.hasAttribute("data-line")||c.id&&Prism.util.isActive(c,n)))}function h(){var c=location.hash.slice(1);b(".temporary.line-highlight").forEach((function(t){t.parentNode.removeChild(t)}));var E=(c.match(/\.([\d,-]+)$/)||[,""])[1];if(E&&!document.getElementById(c)){var S=c.slice(0,c.lastIndexOf(".")),r=document.getElementById(S);r&&(r.hasAttribute("data-line")||r.setAttribute("data-line",""),Prism.plugins.lineHighlight.highlightLines(r,E,"temporary ")(),a&&document.querySelector(".temporary.line-highlight").scrollIntoView())}}})(),(function(){if(typeof Prism<"u"&&typeof document<"u"){var e="line-numbers",i=/\n(?!$)/g,n=Prism.plugins.lineNumbers={getLine:function(p,b){if(p.tagName==="PRE"&&p.classList.contains(e)){var l=p.querySelector(".line-numbers-rows");if(l){var o=parseInt(p.getAttribute("data-start"),10)||1,s=o+(l.children.length-1);bs&&(b=s);var h=b-o;return l.children[h]}}},resize:function(p){a([p])},assumeViewportIndependence:!0},d=void 0;window.addEventListener("resize",(function(){n.assumeViewportIndependence&&d===window.innerWidth||(d=window.innerWidth,a(Array.prototype.slice.call(document.querySelectorAll("pre.line-numbers"))))})),Prism.hooks.add("complete",(function(p){if(p.code){var b=p.element,l=b.parentNode;if(l&&/pre/i.test(l.nodeName)&&!b.querySelector(".line-numbers-rows")&&Prism.util.isActive(b,e)){b.classList.remove(e),l.classList.add(e);var o,s=p.code.match(i),h=s?s.length+1:1,c=new Array(h+1).join("");(o=document.createElement("span")).setAttribute("aria-hidden","true"),o.className="line-numbers-rows",o.innerHTML=c,l.hasAttribute("data-start")&&(l.style.counterReset="linenumber "+(parseInt(l.getAttribute("data-start"),10)-1)),p.element.appendChild(o),a([l]),Prism.hooks.run("line-numbers",p)}}})),Prism.hooks.add("line-numbers",(function(p){p.plugins=p.plugins||{},p.plugins.lineNumbers=!0}))}function a(p){if((p=p.filter((function(l){var o,s=(o=l,o?window.getComputedStyle?getComputedStyle(o):o.currentStyle||null:null)["white-space"];return s==="pre-wrap"||s==="pre-line"}))).length!=0){var b=p.map((function(l){var o=l.querySelector("code"),s=l.querySelector(".line-numbers-rows");if(o&&s){var h=l.querySelector(".line-numbers-sizer"),c=o.textContent.split(i);h||((h=document.createElement("span")).className="line-numbers-sizer",o.appendChild(h)),h.innerHTML="0",h.style.display="block";var E=h.getBoundingClientRect().height;return h.innerHTML="",{element:l,lines:c,lineHeights:[],oneLinerHeight:E,sizer:h}}})).filter(Boolean);b.forEach((function(l){var o=l.sizer,s=l.lines,h=l.lineHeights,c=l.oneLinerHeight;h[s.length-1]=void 0,s.forEach((function(E,S){if(E&&E.length>1){var r=o.appendChild(document.createElement("span"));r.style.display="block",r.textContent=E}else h[S]=c}))})),b.forEach((function(l){for(var o=l.sizer,s=l.lineHeights,h=0,c=0;c\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/g,i=/^#?((?:[\da-f]){3,4}|(?:[\da-f]{2}){3,4})$/i,n=[function(d){var a=i.exec(d);if(a){for(var p=(d=a[1]).length>=6?2:1,b=d.length/p,l=p==1?.06666666666666667:.00392156862745098,o=[],s=0;s=0){for(var a,p=d.content,b=p.split(e).join(""),l=0,o=n.length;l';d.content=s+p}}))}})(),(function(){if(typeof Prism<"u"&&typeof document<"u"){var e=[],i={},n=function(){};Prism.plugins.toolbar={};var d=Prism.plugins.toolbar.registerButton=function(p,b){var l;l=typeof b=="function"?b:function(o){var s;return typeof b.onClick=="function"?((s=document.createElement("button")).type="button",s.addEventListener("click",(function(){b.onClick.call(this,o)}))):typeof b.url=="string"?(s=document.createElement("a")).href=b.url:s=document.createElement("span"),b.className&&s.classList.add(b.className),s.textContent=b.text,s},p in i?console.warn('There is a button with the key "'+p+'" registered already.'):e.push(i[p]=l)},a=Prism.plugins.toolbar.hook=function(p){var b=p.element.parentNode;if(b&&/pre/i.test(b.nodeName)&&!b.parentNode.classList.contains("code-toolbar")){var l=document.createElement("div");l.classList.add("code-toolbar"),b.parentNode.insertBefore(l,b),l.appendChild(b);var o=document.createElement("div");o.classList.add("toolbar");var s=e,h=(function(c){for(;c;){var E=c.getAttribute("data-toolbar-order");if(E!=null)return(E=E.trim()).length?E.split(/\s*,\s*/g):[];c=c.parentElement}})(p.element);h&&(s=h.map((function(c){return i[c]||n}))),s.forEach((function(c){var E=c(p);if(E){var S=document.createElement("div");S.classList.add("toolbar-item"),S.appendChild(E),o.appendChild(S)}})),l.appendChild(o)}};d("label",(function(p){var b=p.element.parentNode;if(b&&/pre/i.test(b.nodeName)&&b.hasAttribute("data-label")){var l,o,s=b.getAttribute("data-label");try{o=document.querySelector("template#"+s)}catch{}return o?l=o.content:(b.hasAttribute("data-url")?(l=document.createElement("a")).href=b.getAttribute("data-url"):l=document.createElement("span"),l.textContent=s),l}})),Prism.hooks.add("complete",a)}})(),(function(){function e(i){var n=document.createElement("textarea");n.value=i.getText(),n.style.top="0",n.style.left="0",n.style.position="fixed",document.body.appendChild(n),n.focus(),n.select();try{var d=document.execCommand("copy");setTimeout((function(){d?i.success():i.error()}),1)}catch(a){setTimeout((function(){i.error(a)}),1)}document.body.removeChild(n)}typeof Prism<"u"&&typeof document<"u"&&(Prism.plugins.toolbar?Prism.plugins.toolbar.registerButton("copy-to-clipboard",(function(i){var n=i.element,d=(function(o){var s={copy:"","copy-error":"Press Ctrl+C to copy","copy-success":"","copy-timeout":5e3};for(var h in s){for(var c="data-prismjs-"+h,E=o;E&&!E.hasAttribute(c);)E=E.parentElement;E&&(s[h]=E.getAttribute(c))}return s})(n),a=document.createElement("button");a.className="copy-to-clipboard-button",a.setAttribute("type","button");var p=document.createElement("span");return a.appendChild(p),l("copy"),(function(o,s){o.addEventListener("click",(function(){(function(h){navigator.clipboard?navigator.clipboard.writeText(h.getText()).then(h.success,(function(){e(h)})):e(h)})(s)}))})(a,{getText:function(){return n.textContent},success:function(){l("copy-success"),b()},error:function(){l("copy-error"),setTimeout((function(){(function(o){window.getSelection().selectAllChildren(o)})(n)}),1),b()}}),a;function b(){setTimeout((function(){l("copy")}),d["copy-timeout"])}function l(o){p.innerHTML=d[o],a.setAttribute("data-copy-state",o)}})):console.warn("Copy to Clipboard plugin loaded before Toolbar plugin."))})(); diff --git a/common/snake/snake.css b/common/snake/snake.css new file mode 100644 index 0000000..85e889e --- /dev/null +++ b/common/snake/snake.css @@ -0,0 +1,226 @@ +/* ===== Snake Game Styles ===== */ +.snake-container { + max-width: 1000px; + margin: 0 auto; + padding: 20px 20px 60px; +} +.snake-layout { + display: flex; + gap: 30px; + align-items: flex-start; +} + +/* Game area */ +.snake-game-area { + position: relative; + flex-shrink: 0; +} +.snake-score-bar { + display: flex; + justify-content: space-between; + padding: 10px 16px; + background: var(--theme-front-main-color); + border-radius: 10px 10px 0 0; + font-size: 0.95rem; + font-weight: 600; + color: var(--theme-text-color); + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05); +} +#snake-canvas { + display: block; + background: #1a1a2e; + border-radius: 0 0 10px 10px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} + +/* Overlays */ +.snake-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.75); + display: flex; + align-items: center; + justify-content: center; + border-radius: 10px; + z-index: 10; +} +.snake-overlay-content { + text-align: center; + color: #fff; +} +.snake-overlay-content h2 { + font-size: 2rem; + margin-bottom: 20px; + color: #fff; +} +.snake-name-input { + display: block; + width: 220px; + margin: 0 auto 16px; + padding: 10px 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 8px; + background: rgba(255, 255, 255, 0.1); + color: #fff; + font-size: 1rem; + text-align: center; + outline: none; + transition: border-color 0.2s; +} +.snake-name-input:focus { + border-color: var(--theme-color); +} +.snake-name-input::placeholder { + color: rgba(255, 255, 255, 0.5); +} +.snake-btn { + padding: 12px 32px; + border: none; + border-radius: 8px; + font-size: 1.1rem; + font-weight: 600; + cursor: pointer; + transition: transform 0.15s, box-shadow 0.15s; +} +.snake-btn:hover { + transform: scale(1.05); +} +.snake-btn-play { + background: linear-gradient(135deg, #42b983, #3eaf7c); + color: #fff; + box-shadow: 0 4px 16px rgba(66, 185, 131, 0.4); +} +.snake-controls-hint { + margin-top: 16px; + font-size: 0.78rem; + color: rgba(255, 255, 255, 0.5); +} +.snake-final-score { + font-size: 4rem; + font-weight: 800; + color: var(--theme-color); + margin-bottom: 8px; + line-height: 1; +} +.snake-rank { + font-size: 1.1rem; + color: rgba(255, 255, 255, 0.8); + margin-bottom: 20px; +} + +/* Leaderboard */ +.snake-leaderboard { + flex: 1; + min-width: 260px; + background: var(--theme-front-main-color); + border-radius: 10px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); + padding: 20px; +} +.snake-leaderboard h3 { + font-size: 1.2rem; + margin-bottom: 14px; + color: var(--theme-text-color); +} +.snake-lb-tabs { + display: flex; + gap: 4px; + margin-bottom: 14px; +} +.snake-lb-tab { + flex: 1; + padding: 7px 12px; + border: 1px solid #ddd; + border-radius: 6px; + background: transparent; + cursor: pointer; + font-size: 0.82rem; + transition: all 0.2s; +} +.snake-lb-tab.active { + background: var(--theme-color); + color: #fff; + border-color: var(--theme-color); +} +.snake-lb-list { + max-height: 420px; + overflow-y: auto; +} +.snake-lb-loading { + text-align: center; + padding: 30px; + color: var(--theme-text-secondary); +} +.snake-lb-empty { + text-align: center; + padding: 30px; + color: #aaa; + font-size: 0.9rem; +} + +/* Leaderboard rows */ +.snake-lb-row { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: 6px; + transition: background 0.15s; +} +.snake-lb-row:hover { + background: rgba(0, 0, 0, 0.03); +} +.snake-lb-row.highlight { + background: rgba(66, 185, 131, 0.1); + border: 1px solid rgba(66, 185, 131, 0.3); +} +.snake-lb-rank { + width: 28px; + text-align: center; + font-weight: 700; + font-size: 0.9rem; + color: var(--theme-text-secondary); + flex-shrink: 0; +} +.snake-lb-rank.gold { color: #f59e0b; } +.snake-lb-rank.silver { color: #94a3b8; } +.snake-lb-rank.bronze { color: #d97706; } +.snake-lb-name { + flex: 1; + font-size: 0.88rem; + color: var(--theme-text-color); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.snake-lb-score { + font-weight: 700; + font-size: 0.95rem; + color: var(--theme-color); + flex-shrink: 0; +} +.snake-lb-time { + font-size: 0.72rem; + color: #aaa; + flex-shrink: 0; + width: 50px; + text-align: right; +} + +/* Responsive */ +@media (max-width: 720px) { + .snake-layout { + flex-direction: column; + align-items: center; + } + #snake-canvas { + width: 100% !important; + height: auto !important; + } + .snake-leaderboard { + width: 100%; + } +} diff --git a/common/snake/snake.js b/common/snake/snake.js new file mode 100644 index 0000000..b0f621e --- /dev/null +++ b/common/snake/snake.js @@ -0,0 +1,551 @@ +/** + * Snake Game - Modern smooth style with leaderboard + * @author Haibin + * @date 2026-04-17 + */ +(function () { + 'use strict'; + + var AJAX_URL = window.SNAKE_AJAX || (window.HOME ? window.HOME + '/wp-admin/admin-ajax.php' : '/wp-admin/admin-ajax.php'); + var DAY_SALT = window.SNAKE_DAY_SALT || ''; + + // Game config + var GRID = 20; // grid cells per row/col + var CELL; // pixel size per cell (calculated) + var BASE_SPEED = 150; // ms per tick at start + var MIN_SPEED = 60; // fastest tick + var SPEED_STEP = 3; // ms faster per food eaten + + // Game state + var canvas, ctx; + var snake, direction, nextDirection, food; + var score, gameTimer, startTime, duration; + var state; // 'start', 'playing', 'gameover' + var playerName = ''; + var lbType = 'alltime'; + var particles = []; + var animFrame; + + // Smooth animation + var lastTick = 0; + var tickInterval; + + /* ========== Init ========== */ + function init() { + canvas = document.getElementById('snake-canvas'); + ctx = canvas.getContext('2d'); + + // Responsive canvas + resizeCanvas(); + window.addEventListener('resize', resizeCanvas); + + // Restore name + try { playerName = localStorage.getItem('snake_player_name') || ''; } catch (e) {} + var nameInput = document.getElementById('snake-name-input'); + nameInput.value = playerName; + + // Events + document.getElementById('snake-start-btn').addEventListener('click', startGame); + document.getElementById('snake-restart-btn').addEventListener('click', startGame); + nameInput.addEventListener('keydown', function (e) { + if (e.key === 'Enter') startGame(); + }); + + document.addEventListener('keydown', handleKey); + + // Mobile swipe + var touchStartX, touchStartY; + canvas.addEventListener('touchstart', function (e) { + touchStartX = e.touches[0].clientX; + touchStartY = e.touches[0].clientY; + }, { passive: true }); + canvas.addEventListener('touchend', function (e) { + if (!touchStartX) return; + var dx = e.changedTouches[0].clientX - touchStartX; + var dy = e.changedTouches[0].clientY - touchStartY; + if (Math.abs(dx) < 20 && Math.abs(dy) < 20) return; + if (Math.abs(dx) > Math.abs(dy)) { + setDirection(dx > 0 ? 'right' : 'left'); + } else { + setDirection(dy > 0 ? 'down' : 'up'); + } + touchStartX = touchStartY = null; + }, { passive: true }); + + // Leaderboard tabs + document.querySelectorAll('.snake-lb-tab').forEach(function (btn) { + btn.addEventListener('click', function () { + document.querySelectorAll('.snake-lb-tab').forEach(function (b) { b.classList.remove('active'); }); + btn.classList.add('active'); + lbType = btn.dataset.type; + loadLeaderboard(); + }); + }); + + state = 'start'; + drawStartScreen(); + loadLeaderboard(); + } + + function resizeCanvas() { + var container = canvas.parentElement; + var maxW = Math.min(440, container.clientWidth); + canvas.width = maxW; + canvas.height = maxW; + canvas.style.width = maxW + 'px'; + canvas.style.height = maxW + 'px'; + CELL = maxW / GRID; + if (state === 'playing') drawGame(); + } + + /* ========== Game control ========== */ + function startGame() { + var nameInput = document.getElementById('snake-name-input'); + playerName = nameInput.value.trim(); + if (!playerName) { + nameInput.style.borderColor = '#ff4d4f'; + nameInput.focus(); + setTimeout(function () { nameInput.style.borderColor = ''; }, 1500); + return; + } + try { localStorage.setItem('snake_player_name', playerName); } catch (e) {} + + // Hide overlays + document.getElementById('snake-start-overlay').style.display = 'none'; + document.getElementById('snake-gameover-overlay').style.display = 'none'; + + // Init game state + var mid = Math.floor(GRID / 2); + snake = [{ x: mid, y: mid }, { x: mid - 1, y: mid }, { x: mid - 2, y: mid }]; + direction = 'right'; + nextDirection = 'right'; + score = 0; + particles = []; + tickInterval = BASE_SPEED; + startTime = Date.now(); + duration = 0; + state = 'playing'; + + spawnFood(); + updateScoreDisplay(); + + // Game loop + clearInterval(gameTimer); + lastTick = Date.now(); + cancelAnimationFrame(animFrame); + gameLoop(); + gameTimer = setInterval(gameTick, tickInterval); + } + + function gameTick() { + if (state !== 'playing') return; + + // Apply direction + direction = nextDirection; + + // Move + var head = { x: snake[0].x, y: snake[0].y }; + switch (direction) { + case 'up': head.y--; break; + case 'down': head.y++; break; + case 'left': head.x--; break; + case 'right': head.x++; break; + } + + // Wall wrap-around + if (head.x < 0) head.x = GRID - 1; + else if (head.x >= GRID) head.x = 0; + if (head.y < 0) head.y = GRID - 1; + else if (head.y >= GRID) head.y = 0; + + // Self collision + for (var i = 0; i < snake.length; i++) { + if (snake[i].x === head.x && snake[i].y === head.y) { + gameOver(); return; + } + } + + snake.unshift(head); + + // Eat food + if (head.x === food.x && head.y === food.y) { + score++; + updateScoreDisplay(); + spawnParticles(food.x, food.y); + spawnFood(); + + // Speed up + tickInterval = Math.max(MIN_SPEED, BASE_SPEED - score * SPEED_STEP); + clearInterval(gameTimer); + gameTimer = setInterval(gameTick, tickInterval); + } else { + snake.pop(); + } + } + + function gameLoop() { + if (state !== 'playing' && particles.length === 0) return; + drawGame(); + animFrame = requestAnimationFrame(gameLoop); + } + + function gameOver() { + state = 'gameover'; + clearInterval(gameTimer); + duration = Math.floor((Date.now() - startTime) / 1000); + + // Update display + document.getElementById('snake-final-score').textContent = score; + document.getElementById('snake-time-display').textContent = 'Time: ' + duration + 's'; + + // Submit score + submitScore(); + + // Show overlay with delay + setTimeout(function () { + document.getElementById('snake-gameover-overlay').style.display = ''; + }, 500); + } + + /* ========== Drawing ========== */ + function drawGame() { + var w = canvas.width, h = canvas.height; + ctx.clearRect(0, 0, w, h); + + // Background + ctx.fillStyle = '#1a1a2e'; + ctx.fillRect(0, 0, w, h); + + // Grid lines (subtle) + ctx.strokeStyle = 'rgba(255,255,255,0.03)'; + ctx.lineWidth = 0.5; + for (var i = 0; i <= GRID; i++) { + ctx.beginPath(); ctx.moveTo(i * CELL, 0); ctx.lineTo(i * CELL, h); ctx.stroke(); + ctx.beginPath(); ctx.moveTo(0, i * CELL); ctx.lineTo(w, i * CELL); ctx.stroke(); + } + + // Food - pulsating glow + drawFood(); + + // Snake + drawSnake(); + + // Particles + updateParticles(); + + // Timer + if (state === 'playing') { + duration = Math.floor((Date.now() - startTime) / 1000); + document.getElementById('snake-time-display').textContent = 'Time: ' + duration + 's'; + } + } + + function drawSnake() { + for (var i = snake.length - 1; i >= 0; i--) { + var s = snake[i]; + var ratio = i / snake.length; + var r = CELL * 0.44; + + // Gradient color: head is bright green, tail fades + var hue = 140 + ratio * 30; + var lightness = 55 - ratio * 15; + var alpha = 1 - ratio * 0.3; + + ctx.fillStyle = 'hsla(' + hue + ', 70%, ' + lightness + '%, ' + alpha + ')'; + ctx.beginPath(); + ctx.arc(s.x * CELL + CELL / 2, s.y * CELL + CELL / 2, r, 0, Math.PI * 2); + ctx.fill(); + + // Head details + if (i === 0) { + // Glow + ctx.shadowColor = 'rgba(66, 185, 131, 0.5)'; + ctx.shadowBlur = 12; + ctx.fillStyle = '#42b983'; + ctx.beginPath(); + ctx.arc(s.x * CELL + CELL / 2, s.y * CELL + CELL / 2, r, 0, Math.PI * 2); + ctx.fill(); + ctx.shadowBlur = 0; + + // Eyes + var eyeSize = CELL * 0.1; + var eyeOffset = CELL * 0.15; + var ex1, ey1, ex2, ey2; + switch (direction) { + case 'up': ex1 = -eyeOffset; ey1 = -eyeOffset; ex2 = eyeOffset; ey2 = -eyeOffset; break; + case 'down': ex1 = -eyeOffset; ey1 = eyeOffset; ex2 = eyeOffset; ey2 = eyeOffset; break; + case 'left': ex1 = -eyeOffset; ey1 = -eyeOffset; ex2 = -eyeOffset; ey2 = eyeOffset; break; + case 'right': ex1 = eyeOffset; ey1 = -eyeOffset; ex2 = eyeOffset; ey2 = eyeOffset; break; + } + ctx.fillStyle = '#fff'; + ctx.beginPath(); + ctx.arc(s.x * CELL + CELL / 2 + ex1, s.y * CELL + CELL / 2 + ey1, eyeSize, 0, Math.PI * 2); + ctx.fill(); + ctx.beginPath(); + ctx.arc(s.x * CELL + CELL / 2 + ex2, s.y * CELL + CELL / 2 + ey2, eyeSize, 0, Math.PI * 2); + ctx.fill(); + } + } + } + + function drawFood() { + var pulse = Math.sin(Date.now() / 200) * 0.15 + 0.85; + var r = CELL * 0.4 * pulse; + + // Glow + ctx.shadowColor = 'rgba(239, 68, 68, 0.6)'; + ctx.shadowBlur = 15; + + // Gradient + var grd = ctx.createRadialGradient( + food.x * CELL + CELL / 2, food.y * CELL + CELL / 2, 0, + food.x * CELL + CELL / 2, food.y * CELL + CELL / 2, r + ); + grd.addColorStop(0, '#ff6b6b'); + grd.addColorStop(1, '#ee5a24'); + ctx.fillStyle = grd; + + ctx.beginPath(); + ctx.arc(food.x * CELL + CELL / 2, food.y * CELL + CELL / 2, r, 0, Math.PI * 2); + ctx.fill(); + ctx.shadowBlur = 0; + } + + function drawStartScreen() { + ctx.fillStyle = '#1a1a2e'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + + /* ========== Particles ========== */ + function spawnParticles(gx, gy) { + var cx = gx * CELL + CELL / 2; + var cy = gy * CELL + CELL / 2; + for (var i = 0; i < 12; i++) { + var angle = Math.random() * Math.PI * 2; + var speed = 1.5 + Math.random() * 3; + particles.push({ + x: cx, y: cy, + vx: Math.cos(angle) * speed, + vy: Math.sin(angle) * speed, + life: 1, + decay: 0.02 + Math.random() * 0.03, + size: 2 + Math.random() * 3, + hue: 0 + Math.random() * 40 // red-orange + }); + } + } + + function updateParticles() { + for (var i = particles.length - 1; i >= 0; i--) { + var p = particles[i]; + p.x += p.vx; + p.y += p.vy; + p.life -= p.decay; + p.vx *= 0.97; + p.vy *= 0.97; + + if (p.life <= 0) { + particles.splice(i, 1); + continue; + } + + ctx.fillStyle = 'hsla(' + p.hue + ', 100%, 60%, ' + p.life + ')'; + ctx.beginPath(); + ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2); + ctx.fill(); + } + } + + /* ========== Controls ========== */ + function handleKey(e) { + if (state !== 'playing') return; + switch (e.key) { + case 'ArrowUp': case 'w': case 'W': e.preventDefault(); setDirection('up'); break; + case 'ArrowDown': case 's': case 'S': e.preventDefault(); setDirection('down'); break; + case 'ArrowLeft': case 'a': case 'A': e.preventDefault(); setDirection('left'); break; + case 'ArrowRight': case 'd': case 'D': e.preventDefault(); setDirection('right'); break; + } + } + + function setDirection(dir) { + var opposites = { up: 'down', down: 'up', left: 'right', right: 'left' }; + if (dir !== opposites[direction]) { + nextDirection = dir; + } + } + + /* ========== Helpers ========== */ + function spawnFood() { + var occupied = {}; + snake.forEach(function (s) { occupied[s.x + ',' + s.y] = true; }); + var attempts = 0; + do { + food = { x: Math.floor(Math.random() * GRID), y: Math.floor(Math.random() * GRID) }; + attempts++; + } while (occupied[food.x + ',' + food.y] && attempts < 1000); + } + + function updateScoreDisplay() { + document.getElementById('snake-score-display').textContent = 'Score: ' + score; + } + + /* ========== AJAX ========== */ + function ajax(action, data) { + var fd = new FormData(); + fd.append('action', action); + if (data) Object.keys(data).forEach(function (k) { fd.append(k, data[k]); }); + return fetch(AJAX_URL, { method: 'POST', body: fd, credentials: 'same-origin' }) + .then(function (r) { + if (!r.ok) { console.error('Snake AJAX HTTP error:', r.status, AJAX_URL); throw new Error('HTTP ' + r.status); } + return r.text(); + }) + .then(function (text) { + try { var res = JSON.parse(text); } catch (e) { console.error('Snake AJAX invalid JSON:', text.substring(0, 200)); throw e; } + if (res.success) return res.data; + console.error('Snake AJAX error:', res.data); + throw new Error(res.data || 'Error'); + }); + } + + function md5Token(score, duration) { + // Simple hash matching PHP: md5(score + '_' + duration + '_' + salt) + var str = score + '_' + duration + '_' + DAY_SALT; + return md5(str); + } + + function submitScore() { + var token = md5Token(score, duration); + ajax('snake_submit', { + name: playerName, + score: score, + duration: duration, + token: token + }).then(function (data) { + var rankEl = document.getElementById('snake-rank'); + if (data.rank <= 3) { + var medals = ['', '🥇', '🥈', '🥉']; + rankEl.innerHTML = medals[data.rank] + ' Rank #' + data.rank + '!'; + } else { + rankEl.textContent = 'Rank #' + data.rank; + } + loadLeaderboard(); + }).catch(function () { + document.getElementById('snake-rank').textContent = 'Score saved locally'; + }); + } + + function loadLeaderboard() { + ajax('snake_leaderboard', { type: lbType, limit: 20 }).then(function (rows) { + renderLeaderboard(rows); + }).catch(function (e) { + console.error('Leaderboard load failed:', e); + document.getElementById('snake-lb-list').innerHTML = '
    Failed to load leaderboard
    '; + }); + } + + function renderLeaderboard(rows) { + var el = document.getElementById('snake-lb-list'); + if (!rows || rows.length === 0) { + el.innerHTML = '
    No scores yet. Be the first!
    '; + return; + } + + var html = ''; + rows.forEach(function (row, i) { + var rank = i + 1; + var rankClass = rank === 1 ? 'gold' : rank === 2 ? 'silver' : rank === 3 ? 'bronze' : ''; + var rankIcon = rank === 1 ? '🥇' : rank === 2 ? '🥈' : rank === 3 ? '🥉' : rank; + var isMe = row.player_name === playerName; + + html += '
    '; + html += '
    ' + rankIcon + '
    '; + html += '
    ' + escapeHtml(row.player_name) + '
    '; + html += '
    ' + row.score + '
    '; + html += '
    ' + row.duration + 's
    '; + html += '
    '; + }); + + el.innerHTML = html; + } + + function escapeHtml(str) { + var d = document.createElement('div'); + d.textContent = str; + return d.innerHTML; + } + + /* ========== MD5 (minimal implementation) ========== */ + function md5(string) { + function md5cycle(x, k) { + var a = x[0], b = x[1], c = x[2], d = x[3]; + a = ff(a, b, c, d, k[0], 7, -680876936); d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); b = ff(b, c, d, a, k[15], 22, 1236535329); + a = gg(a, b, c, d, k[1], 5, -165796510); d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); b = gg(b, c, d, a, k[12], 20, -1926607734); + a = hh(a, b, c, d, k[5], 4, -378558); d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); b = hh(b, c, d, a, k[2], 23, -995338651); + a = ii(a, b, c, d, k[0], 6, -198630844); d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); b = ii(b, c, d, a, k[9], 21, -343485551); + x[0] = add32(a, x[0]); x[1] = add32(b, x[1]); x[2] = add32(c, x[2]); x[3] = add32(d, x[3]); + } + function cmn(q, a, b, x, s, t) { a = add32(add32(a, q), add32(x, t)); return add32((a << s) | (a >>> (32 - s)), b); } + function ff(a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t); } + function gg(a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t); } + function hh(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); } + function ii(a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t); } + function md51(s) { + var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i; + for (i = 64; i <= n; i += 64) md5cycle(state, md5blk(s.substring(i - 64, i))); + s = s.substring(i - 64); + var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < s.length; i++) tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { md5cycle(state, tail); for (i = 0; i < 16; i++) tail[i] = 0; } + tail[14] = n * 8; + md5cycle(state, tail); + return state; + } + function md5blk(s) { + var md5blks = [], i; + for (i = 0; i < 64; i += 4) md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + return md5blks; + } + var hex_chr = '0123456789abcdef'.split(''); + function rhex(n) { + var s = '', j = 0; + for (; j < 4; j++) s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + return s; + } + function hex(x) { for (var i = 0; i < x.length; i++) x[i] = rhex(x[i]); return x.join(''); } + function add32(a, b) { return (a + b) & 0xFFFFFFFF; } + return hex(md51(string)); + } + + /* ========== Start ========== */ + if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); + else init(); +})(); diff --git a/common/snake/snake.min.css b/common/snake/snake.min.css new file mode 100644 index 0000000..5b7e038 --- /dev/null +++ b/common/snake/snake.min.css @@ -0,0 +1 @@ +.snake-container{max-width:1000px;margin:0 auto;padding:20px 20px 60px}.snake-layout{display:flex;gap:30px;align-items:flex-start}.snake-game-area{position:relative;flex-shrink:0}.snake-score-bar{display:flex;justify-content:space-between;padding:10px 16px;background:var(--theme-front-main-color);border-radius:10px 10px 0 0;font-size:.95rem;font-weight:600;color:var(--theme-text-color);box-shadow:0 1px 6px #0000000d}#snake-canvas{display:block;background:#1a1a2e;border-radius:0 0 10px 10px;box-shadow:0 4px 20px #00000026}.snake-overlay{position:absolute;inset:0;background:#000000bf;display:flex;align-items:center;justify-content:center;border-radius:10px;z-index:10}.snake-overlay-content{text-align:center;color:#fff}.snake-overlay-content h2{font-size:2rem;margin-bottom:20px;color:#fff}.snake-name-input{display:block;width:220px;margin:0 auto 16px;padding:10px 16px;border:2px solid rgba(255,255,255,.3);border-radius:8px;background:#ffffff1a;color:#fff;font-size:1rem;text-align:center;outline:none;transition:border-color .2s}.snake-name-input:focus{border-color:var(--theme-color)}.snake-name-input::placeholder{color:#ffffff80}.snake-btn{padding:12px 32px;border:none;border-radius:8px;font-size:1.1rem;font-weight:600;cursor:pointer;transition:transform .15s,box-shadow .15s}.snake-btn:hover{transform:scale(1.05)}.snake-btn-play{background:linear-gradient(135deg,#42b983,#3eaf7c);color:#fff;box-shadow:0 4px 16px #42b98366}.snake-controls-hint{margin-top:16px;font-size:.78rem;color:#ffffff80}.snake-final-score{font-size:4rem;font-weight:800;color:var(--theme-color);margin-bottom:8px;line-height:1}.snake-rank{font-size:1.1rem;color:#fffc;margin-bottom:20px}.snake-leaderboard{flex:1;min-width:260px;background:var(--theme-front-main-color);border-radius:10px;box-shadow:0 2px 12px #0000000f;padding:20px}.snake-leaderboard h3{font-size:1.2rem;margin-bottom:14px;color:var(--theme-text-color)}.snake-lb-tabs{display:flex;gap:4px;margin-bottom:14px}.snake-lb-tab{flex:1;padding:7px 12px;border:1px solid #ddd;border-radius:6px;background:transparent;cursor:pointer;font-size:.82rem;transition:all .2s}.snake-lb-tab.active{background:var(--theme-color);color:#fff;border-color:var(--theme-color)}.snake-lb-list{max-height:420px;overflow-y:auto}.snake-lb-loading{text-align:center;padding:30px;color:var(--theme-text-secondary)}.snake-lb-empty{text-align:center;padding:30px;color:#aaa;font-size:.9rem}.snake-lb-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:6px;transition:background .15s}.snake-lb-row:hover{background:#00000008}.snake-lb-row.highlight{background:#42b9831a;border:1px solid rgba(66,185,131,.3)}.snake-lb-rank{width:28px;text-align:center;font-weight:700;font-size:.9rem;color:var(--theme-text-secondary);flex-shrink:0}.snake-lb-rank.gold{color:#f59e0b}.snake-lb-rank.silver{color:#94a3b8}.snake-lb-rank.bronze{color:#d97706}.snake-lb-name{flex:1;font-size:.88rem;color:var(--theme-text-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.snake-lb-score{font-weight:700;font-size:.95rem;color:var(--theme-color);flex-shrink:0}.snake-lb-time{font-size:.72rem;color:#aaa;flex-shrink:0;width:50px;text-align:right}@media(max-width:720px){.snake-layout{flex-direction:column;align-items:center}#snake-canvas{width:100%!important;height:auto!important}.snake-leaderboard{width:100%}} diff --git a/common/snake/snake.min.js b/common/snake/snake.min.js new file mode 100644 index 0000000..03db63a --- /dev/null +++ b/common/snake/snake.min.js @@ -0,0 +1 @@ +(function(){"use strict";var J=window.SNAKE_AJAX||(window.HOME?window.HOME+"/wp-admin/admin-ajax.php":"/wp-admin/admin-ajax.php"),Z=window.SNAKE_DAY_SALT||"",w=20,u,N=150,$=60,O=3,g,c,p,D,k,b,M,_,H,x,E,I="",j="alltime",P=[],Y,ee=0,F;function z(){g=document.getElementById("snake-canvas"),c=g.getContext("2d"),G(),window.addEventListener("resize",G);try{I=localStorage.getItem("snake_player_name")||""}catch{}var i=document.getElementById("snake-name-input");i.value=I,document.getElementById("snake-start-btn").addEventListener("click",R),document.getElementById("snake-restart-btn").addEventListener("click",R),i.addEventListener("keydown",function(l){l.key==="Enter"&&R()}),document.addEventListener("keydown",le);var o,d;g.addEventListener("touchstart",function(l){o=l.touches[0].clientX,d=l.touches[0].clientY},{passive:!0}),g.addEventListener("touchend",function(l){if(o){var v=l.changedTouches[0].clientX-o,h=l.changedTouches[0].clientY-d;Math.abs(v)<20&&Math.abs(h)<20||(Math.abs(v)>Math.abs(h)?C(v>0?"right":"left"):C(h>0?"down":"up"),o=d=null)}},{passive:!0}),document.querySelectorAll(".snake-lb-tab").forEach(function(l){l.addEventListener("click",function(){document.querySelectorAll(".snake-lb-tab").forEach(function(v){v.classList.remove("active")}),l.classList.add("active"),j=l.dataset.type,X()})}),E="start",re(),X()}function G(){var i=g.parentElement,o=Math.min(440,i.clientWidth);g.width=o,g.height=o,g.style.width=o+"px",g.style.height=o+"px",u=o/w,E==="playing"&&K()}function R(){var i=document.getElementById("snake-name-input");if(I=i.value.trim(),!I){i.style.borderColor="#ff4d4f",i.focus(),setTimeout(function(){i.style.borderColor=""},1500);return}try{localStorage.setItem("snake_player_name",I)}catch{}document.getElementById("snake-start-overlay").style.display="none",document.getElementById("snake-gameover-overlay").style.display="none";var o=Math.floor(w/2);p=[{x:o,y:o},{x:o-1,y:o},{x:o-2,y:o}],D="right",k="right",M=0,P=[],F=N,H=Date.now(),x=0,E="playing",U(),Q(),clearInterval(_),ee=Date.now(),cancelAnimationFrame(Y),q(),_=setInterval(W,F)}function W(){if(E==="playing"){D=k;var i={x:p[0].x,y:p[0].y};switch(D){case"up":i.y--;break;case"down":i.y++;break;case"left":i.x--;break;case"right":i.x++;break}i.x<0?i.x=w-1:i.x>=w&&(i.x=0),i.y<0?i.y=w-1:i.y>=w&&(i.y=0);for(var o=0;o=0;i--){var o=p[i],d=i/p.length,l=u*.44,v=140+d*30,h=55-d*15,f=1-d*.3;if(c.fillStyle="hsla("+v+", 70%, "+h+"%, "+f+")",c.beginPath(),c.arc(o.x*u+u/2,o.y*u+u/2,l,0,Math.PI*2),c.fill(),i===0){c.shadowColor="rgba(66, 185, 131, 0.5)",c.shadowBlur=12,c.fillStyle="#42b983",c.beginPath(),c.arc(o.x*u+u/2,o.y*u+u/2,l,0,Math.PI*2),c.fill(),c.shadowBlur=0;var B=u*.1,m=u*.15,S,A,T,y;switch(D){case"up":S=-m,A=-m,T=m,y=-m;break;case"down":S=-m,A=m,T=m,y=m;break;case"left":S=-m,A=-m,T=-m,y=m;break;case"right":S=m,A=-m,T=m,y=m;break}c.fillStyle="#fff",c.beginPath(),c.arc(o.x*u+u/2+S,o.y*u+u/2+A,B,0,Math.PI*2),c.fill(),c.beginPath(),c.arc(o.x*u+u/2+T,o.y*u+u/2+y,B,0,Math.PI*2),c.fill()}}}function ne(){var i=Math.sin(Date.now()/200)*.15+.85,o=u*.4*i;c.shadowColor="rgba(239, 68, 68, 0.6)",c.shadowBlur=15;var d=c.createRadialGradient(b.x*u+u/2,b.y*u+u/2,0,b.x*u+u/2,b.y*u+u/2,o);d.addColorStop(0,"#ff6b6b"),d.addColorStop(1,"#ee5a24"),c.fillStyle=d,c.beginPath(),c.arc(b.x*u+u/2,b.y*u+u/2,o,0,Math.PI*2),c.fill(),c.shadowBlur=0}function re(){c.fillStyle="#1a1a2e",c.fillRect(0,0,g.width,g.height)}function oe(i,o){for(var d=i*u+u/2,l=o*u+u/2,v=0;v<12;v++){var h=Math.random()*Math.PI*2,f=1.5+Math.random()*3;P.push({x:d,y:l,vx:Math.cos(h)*f,vy:Math.sin(h)*f,life:1,decay:.02+Math.random()*.03,size:2+Math.random()*3,hue:0+Math.random()*40})}}function ie(){for(var i=P.length-1;i>=0;i--){var o=P[i];if(o.x+=o.vx,o.y+=o.vy,o.life-=o.decay,o.vx*=.97,o.vy*=.97,o.life<=0){P.splice(i,1);continue}c.fillStyle="hsla("+o.hue+", 100%, 60%, "+o.life+")",c.beginPath(),c.arc(o.x,o.y,o.size*o.life,0,Math.PI*2),c.fill()}}function le(i){if(E==="playing")switch(i.key){case"ArrowUp":case"w":case"W":i.preventDefault(),C("up");break;case"ArrowDown":case"s":case"S":i.preventDefault(),C("down");break;case"ArrowLeft":case"a":case"A":i.preventDefault(),C("left");break;case"ArrowRight":case"d":case"D":i.preventDefault(),C("right");break}}function C(i){var o={up:"down",down:"up",left:"right",right:"left"};i!==o[D]&&(k=i)}function U(){var i={};p.forEach(function(d){i[d.x+","+d.y]=!0});var o=0;do b={x:Math.floor(Math.random()*w),y:Math.floor(Math.random()*w)},o++;while(i[b.x+","+b.y]&&o<1e3)}function Q(){document.getElementById("snake-score-display").textContent="Score: "+M}function V(i,o){var d=new FormData;return d.append("action",i),o&&Object.keys(o).forEach(function(l){d.append(l,o[l])}),fetch(J,{method:"POST",body:d,credentials:"same-origin"}).then(function(l){if(!l.ok)throw console.error("Snake AJAX HTTP error:",l.status,J),new Error("HTTP "+l.status);return l.text()}).then(function(l){try{var v=JSON.parse(l)}catch(h){throw console.error("Snake AJAX invalid JSON:",l.substring(0,200)),h}if(v.success)return v.data;throw console.error("Snake AJAX error:",v.data),new Error(v.data||"Error")})}function ce(i,o){var d=i+"_"+o+"_"+Z;return he(d)}function de(){var i=ce(M,x);V("snake_submit",{name:I,score:M,duration:x,token:i}).then(function(o){var d=document.getElementById("snake-rank");if(o.rank<=3){var l=["","🥇","🥈","🥉"];d.innerHTML=l[o.rank]+" Rank #"+o.rank+"!"}else d.textContent="Rank #"+o.rank;X()}).catch(function(){document.getElementById("snake-rank").textContent="Score saved locally"})}function X(){V("snake_leaderboard",{type:j,limit:20}).then(function(i){se(i)}).catch(function(i){console.error("Leaderboard load failed:",i),document.getElementById("snake-lb-list").innerHTML='
    Failed to load leaderboard
    '})}function se(i){var o=document.getElementById("snake-lb-list");if(!i||i.length===0){o.innerHTML='
    No scores yet. Be the first!
    ';return}var d="";i.forEach(function(l,v){var h=v+1,f=h===1?"gold":h===2?"silver":h===3?"bronze":"",B=h===1?"🥇":h===2?"🥈":h===3?"🥉":h,m=l.player_name===I;d+='
    ',d+='
    '+B+"
    ",d+='
    '+ue(l.player_name)+"
    ",d+='
    '+l.score+"
    ",d+='
    '+l.duration+"s
    ",d+="
    "}),o.innerHTML=d}function ue(i){var o=document.createElement("div");return o.textContent=i,o.innerHTML}function he(i){function o(s,n){var t=s[0],e=s[1],a=s[2],r=s[3];t=l(t,e,a,r,n[0],7,-680876936),r=l(r,t,e,a,n[1],12,-389564586),a=l(a,r,t,e,n[2],17,606105819),e=l(e,a,r,t,n[3],22,-1044525330),t=l(t,e,a,r,n[4],7,-176418897),r=l(r,t,e,a,n[5],12,1200080426),a=l(a,r,t,e,n[6],17,-1473231341),e=l(e,a,r,t,n[7],22,-45705983),t=l(t,e,a,r,n[8],7,1770035416),r=l(r,t,e,a,n[9],12,-1958414417),a=l(a,r,t,e,n[10],17,-42063),e=l(e,a,r,t,n[11],22,-1990404162),t=l(t,e,a,r,n[12],7,1804603682),r=l(r,t,e,a,n[13],12,-40341101),a=l(a,r,t,e,n[14],17,-1502002290),e=l(e,a,r,t,n[15],22,1236535329),t=v(t,e,a,r,n[1],5,-165796510),r=v(r,t,e,a,n[6],9,-1069501632),a=v(a,r,t,e,n[11],14,643717713),e=v(e,a,r,t,n[0],20,-373897302),t=v(t,e,a,r,n[5],5,-701558691),r=v(r,t,e,a,n[10],9,38016083),a=v(a,r,t,e,n[15],14,-660478335),e=v(e,a,r,t,n[4],20,-405537848),t=v(t,e,a,r,n[9],5,568446438),r=v(r,t,e,a,n[14],9,-1019803690),a=v(a,r,t,e,n[3],14,-187363961),e=v(e,a,r,t,n[8],20,1163531501),t=v(t,e,a,r,n[13],5,-1444681467),r=v(r,t,e,a,n[2],9,-51403784),a=v(a,r,t,e,n[7],14,1735328473),e=v(e,a,r,t,n[12],20,-1926607734),t=h(t,e,a,r,n[5],4,-378558),r=h(r,t,e,a,n[8],11,-2022574463),a=h(a,r,t,e,n[11],16,1839030562),e=h(e,a,r,t,n[14],23,-35309556),t=h(t,e,a,r,n[1],4,-1530992060),r=h(r,t,e,a,n[4],11,1272893353),a=h(a,r,t,e,n[7],16,-155497632),e=h(e,a,r,t,n[10],23,-1094730640),t=h(t,e,a,r,n[13],4,681279174),r=h(r,t,e,a,n[0],11,-358537222),a=h(a,r,t,e,n[3],16,-722521979),e=h(e,a,r,t,n[6],23,76029189),t=h(t,e,a,r,n[9],4,-640364487),r=h(r,t,e,a,n[12],11,-421815835),a=h(a,r,t,e,n[15],16,530742520),e=h(e,a,r,t,n[2],23,-995338651),t=f(t,e,a,r,n[0],6,-198630844),r=f(r,t,e,a,n[7],10,1126891415),a=f(a,r,t,e,n[14],15,-1416354905),e=f(e,a,r,t,n[5],21,-57434055),t=f(t,e,a,r,n[12],6,1700485571),r=f(r,t,e,a,n[3],10,-1894986606),a=f(a,r,t,e,n[10],15,-1051523),e=f(e,a,r,t,n[1],21,-2054922799),t=f(t,e,a,r,n[8],6,1873313359),r=f(r,t,e,a,n[15],10,-30611744),a=f(a,r,t,e,n[6],15,-1560198380),e=f(e,a,r,t,n[13],21,1309151649),t=f(t,e,a,r,n[4],6,-145523070),r=f(r,t,e,a,n[11],10,-1120210379),a=f(a,r,t,e,n[2],15,718787259),e=f(e,a,r,t,n[9],21,-343485551),s[0]=y(t,s[0]),s[1]=y(e,s[1]),s[2]=y(a,s[2]),s[3]=y(r,s[3])}function d(s,n,t,e,a,r){return n=y(y(n,s),y(e,r)),y(n<>>32-a,t)}function l(s,n,t,e,a,r,L){return d(n&t|~n&e,s,n,a,r,L)}function v(s,n,t,e,a,r,L){return d(n&e|t&~e,s,n,a,r,L)}function h(s,n,t,e,a,r,L){return d(n^t^e,s,n,a,r,L)}function f(s,n,t,e,a,r,L){return d(t^(n|~e),s,n,a,r,L)}function B(s){var n=s.length,t=[1732584193,-271733879,-1732584194,271733878],e;for(e=64;e<=n;e+=64)o(t,m(s.substring(e-64,e)));s=s.substring(e-64);var a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e>2]|=s.charCodeAt(e)<<(e%4<<3);if(a[e>>2]|=128<<(e%4<<3),e>55)for(o(t,a),e=0;e<16;e++)a[e]=0;return a[14]=n*8,o(t,a),t}function m(s){var n=[],t;for(t=0;t<64;t+=4)n[t>>2]=s.charCodeAt(t)+(s.charCodeAt(t+1)<<8)+(s.charCodeAt(t+2)<<16)+(s.charCodeAt(t+3)<<24);return n}var S="0123456789abcdef".split("");function A(s){for(var n="",t=0;t<4;t++)n+=S[s>>t*8+4&15]+S[s>>t*8&15];return n}function T(s){for(var n=0;n WordPress 主题 `theme-document` 内置的个人待办事项管理工具,仅管理员可访问。 + +## 概述 + +这是一个嵌入在 WordPress 主题中的轻量级任务管理页面,核心特色是 **艾森豪威尔矩阵(Eisenhower Matrix)2D 可视化** —— 将任务按「紧急程度」和「重要程度」两个维度投射到二维散点图上,帮助用户直观地判断任务优先级。同时集成了 **番茄钟(Pomodoro)** 专注计时功能,支持最多 3 个任务并行计时。 + +## 文件结构 + +``` +template/page/todo.php # WordPress 页面模板(91 行) +common/todo/todo.js # 前端核心逻辑(~1092 行) +common/todo/todo.css # 样式(~972 行) +include/config.php # 主题配置(注册资源) +``` + +## 功能模块 + +### 1. 任务 CRUD + +- **添加**:输入标题 + 选择紧急度 + 滑块设定重要度(1-5★)+ 可选截止日期 +- **编辑**:内联编辑,修改标题/紧急度/重要度/截止日期 +- **完成/恢复**:点击复选框切换 +- **删除**:确认后删除 +- **拖拽排序**:列表视图支持拖拽调整顺序 + +数据通过 AJAX 与 WordPress 后端同步,同时在 `localStorage` 缓存实现离线访问和快速渲染。 + +### 2. 紧急度体系(4 级 + 自动升级) + +| 紧急度 | 标签 | 级别 | +|--------|------|------| +| `urgent` | 紧急 | 4 | +| `twodays` | 这两天 | 3 | +| `thisweek` | 这周处理 | 2 | +| `anytime` | 随时可以 | 1 | + +**自动升级规则**(根据截止日期,只升不降): + +| 条件 | 升级为 | +|------|--------| +| 剩余 ≤ 0 天 | `urgent`(紧急) | +| 剩余 ≤ 3 天 | 至少 `twodays`(这两天) | +| 剩余 ≤ 14 天 | 至少 `thisweek`(这周处理) | + +### 3. 重要度与升星机制 + +基础重要度为用户设定的 1-5★。对于截止日期超过 14 天的长期任务,系统会自动计算升星值,避免长期任务被遗忘: + +``` +effectiveImportance = max(原importance, 3 + (days - 14) / 7 * 0.5) +``` + +即 14 天 = 3★ 起步,每多 7 天升 0.5★,上限 5★。 + +### 4. 2D 艾森豪威尔矩阵视图(默认视图) + +使用 Canvas 2D 绘制散点图,X 轴为剩余天数(左紧急→右不紧急),Y 轴为重要度(下→上)。 + +#### 四象限划分 + +``` + 紧急(≤3.2天) 不紧急(>3.2天) + ┌──────────────────┬─────────────────────────────┐ + 5★ │ │ │ + │ 紧急且重要 │ 计划做 │ + │ 立即做! │ (梯形,上边沿升星斜线) │ +3.2★ ├──────────────────┼─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ ← 升星斜线 + │ │ │ y=3.2(x≤14) + │ 快速做 │ 一般事务 │ y=3+(x-14)/7*0.5(x>14) + │ │ (梯形,下沿斜线到底) │ + 1★ └──────────────────┴─────────────────────────────┘ + 过期 今天 3天 1周 2周 3周 +``` + +**关键设计**: +- X 轴分割线在 **3.2 天**(而非整数 3,避免恰好 3 天的任务被误判) +- Y 轴分割线在 **3.2★**,且 x > 14 天后沿升星斜线上升 +- 「计划做」和「一般事务」是梯形(因为分割线有斜率),使用 Canvas 多边形路径绘制 +- 今天位置有红色虚线标注 `TODAY` + +#### 散点防重叠 + +当多个任务坐标相近时,使用 **黄金角螺旋(Golden Angle Spiral)** 排列避免重叠: + +```javascript +angle = step * 2.4; // 黄金角 ≈ 137.5° +ring = minDist * 0.5 * Math.sqrt(step); +``` + +最小间距 22px,最多尝试 30 步偏移。 + +#### 散点样式 + +- 未完成任务:实心圆,半径 10px,颜色按紧急度 +- 已完成任务:半径 6px + 删除线 + 半透明 +- 悬停:外圈高亮光环 +- 番茄钟专注中:红色脉冲闪烁光环(`sin(Date.now() / 400)` 驱动) +- 点击:选中跳动效果 + +#### 宽屏模式 + +矩阵视图激活时,`.main-main` 自动切换为 `todo-wide-mode`(宽度 90%,最大 1400px),为图表和侧边栏腾出空间。 + +### 5. 侧边栏分类 + +矩阵视图底部显示 4 列分类卡片,与四象限对应: + +| 分类 | 颜色 | 条件 | +|------|------|------| +| 立即做 | 红色 | 紧急(≤3.2天)且重要(≥3.2★) | +| 快速做 | 橙色 | 紧急(≤3.2天)且不重要(<3.2★) | +| 计划做 | 黄色 | 不紧急且重要(含升星斜线判断) | +| 一般事务 | 绿色 | 不紧急且不重要 | + +每个任务旁有 ▶ 按钮可直接启动番茄钟。 + +### 6. 列表视图 + +传统列表,支持: +- 紧急度色条标识 +- 倒计时显示(过期/今天/明天/剩余N天) +- 番茄数显示(🍅×N) +- 内联编辑 +- 拖拽排序 +- 番茄钟活跃任务红色边框高亮 + +### 7. 番茄钟(Pomodoro Timer) + +#### 规则 + +| 阶段 | 时长 | 颜色 | +|------|------|------| +| 专注 | 25 分钟 | 红色渐变 | +| 短休息 | 5 分钟 | 浅绿色渐变 | +| 长休息(第 4 轮后) | 15 分钟 | 深绿色渐变 | + +完成一轮专注后番茄数 +1,手动停止不计。 + +#### 多任务并行 + +- 最多同时运行 **3 个** 番茄钟 +- 每个番茄钟有独立的 `setInterval` 计时器 +- 所有计时条在输入区域下方 **一行横排等分展示**(1 个占满、2 个各半、3 个各三分之一) +- 多个计时器时自动隐藏「专注中/短休息/长休息」文字,为任务标题留出空间 + +#### 数据结构 + +```javascript +pomodoroSlots = [ + { + todoId: Number, // 关联的待办 ID + phase: 'focus', // 'focus' | 'break' | 'longbreak' + remaining: Number, // 剩余秒数 + paused: Boolean, + count: Number, // 本次启动以来完成的番茄数 + timer: IntervalID + } +] +``` + +#### 持久化 + +计时状态保存到 `localStorage`(key: `pomodoro_state`),记录 `endTime` 时间戳,刷新页面后自动恢复: + +- 运行中的计时器:根据 `endTime` 和当前时间差重算剩余时间 +- 暂停中的计时器:直接恢复 `remaining` 值 +- 已超时的计时器:自动触发阶段切换 + +番茄计数另存于 `pomodoro_counts`,记录每个 todo.id 的历史番茄总数。 + +#### 提醒方式 + +1. **Notification API** — 浏览器系统通知 +2. **AudioContext** — 短促提示音(880Hz + 660Hz 两连音) +3. **页面标题闪烁** — 显示倒计时和阶段状态 + +### 8. 进度条 + +页面顶部显示完成百分比进度条。 + +### 9. 筛选 + +支持三种筛选:全部 / 未完成 / 已完成。 + +## 技术实现 + +### 架构 + +- **纯前端 IIFE**,无框架依赖,仅依赖 Chart.js 以外的原生 Canvas 2D API +- `localStorage` 本地缓存 + WordPress AJAX 后端同步 +- 所有交互通过 `window._todo` 对象暴露给 HTML onclick 事件 + +### 暴露的 API + +```javascript +window._todo = { + toggle, // 切换完成状态 + deleteTodo, // 删除 + startEdit, // 开始编辑 + saveEdit, // 保存编辑 + cancelEdit, // 取消编辑 + startPomodoro, // 启动番茄钟 + pauseSlot, // 暂停/继续番茄钟 + stopSlot // 停止番茄钟 +} +``` + +### 深色模式 + +全面适配深色模式,包括: +- 矩阵四象限背景色(降低亮度 + 提高透明度) +- 分割线/网格线颜色 +- 番茄钟计时条 +- 侧边栏卡片 +- 进度条 + +### 安全 + +- `todo.php` 入口检测 `current_user_can('administrator')`,非管理员返回 404 +- HTML 输出统一使用 `escapeHtml()` 转义 + +## 版本历史 + +| 版本 | 日期 | 主要变更 | +|------|------|----------| +| 1.4.3 | 2026-04-17 | 初始版本:2D 矩阵 + 列表视图 + 番茄钟 + 多任务并行 | + +## 开发备注 + +- 矩阵图使用原生 Canvas 2D 而非 Chart.js,以实现梯形象限背景和自定义散点交互 +- 番茄钟使用 `setInterval` 而非 `requestAnimationFrame` 计时(精度足够,省电) +- 脉冲动画使用 `requestAnimationFrame` 驱动 `renderChart` 重绘 +- 升星斜线的数学表达:`y = 3 + (x - 14) / 7 * 0.5`(x 为剩余天数,x > 14 时生效) diff --git a/common/todo/todo.css b/common/todo/todo.css new file mode 100644 index 0000000..729db9f --- /dev/null +++ b/common/todo/todo.css @@ -0,0 +1,972 @@ +/* 待办事项样式 */ + +.todo-container { + max-width: 1100px; + margin: 0 auto; + padding: 30px 20px; +} + +/* 页面标题区 */ +.todo-header { + display: flex; + justify-content: space-between; + align-items: baseline; + margin-bottom: 20px; +} + +.todo-header h2 { + font-size: 1.4rem; + font-weight: 700; + margin: 0; + color: var(--theme-text-color, #1a1a2e); +} + +.todo-header p { + font-size: var(--theme-secondary, 0.78rem); + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); + margin: 0; +} + +/* 添加区域 */ +.todo-add { + display: flex; + gap: 8px; + margin-bottom: 20px; + flex-wrap: wrap; + padding: 12px 14px; + background: var(--theme-bg-color, #fff); + border: 1px solid var(--theme-border-color, #e8e8e8); + border-radius: 12px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04); +} + +.todo-add-input { + flex: 1; + min-width: 200px; + padding: 7px 12px; + border: 1px solid var(--theme-border-color, #ddd); + border-radius: 8px; + font-size: var(--theme-text, 1rem); + background: var(--theme-bg-color, #fafafa); + color: var(--theme-text-color, #333); + outline: none; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.todo-add-input:focus { + border-color: var(--theme-color, #4a90d9); + box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.1); + background: var(--theme-bg-color, #fff); +} + +.todo-add-select, +.todo-add-date { + padding: 7px 10px; + border: 1px solid var(--theme-border-color, #ddd); + border-radius: 8px; + font-size: var(--theme-secondary, 0.78rem); + background: var(--theme-bg-color, #fafafa); + color: var(--theme-text-color, #333); + outline: none; + transition: border-color 0.2s; +} + +.todo-add-select:focus, +.todo-add-date:focus { + border-color: var(--theme-color, #4a90d9); +} + +.todo-add-btn { + padding: 7px 20px; + border: none; + border-radius: 8px; + background: var(--theme-color, #4a90d9); + color: #fff; + font-size: var(--theme-text, 1rem); + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + box-shadow: 0 2px 6px rgba(74, 144, 217, 0.3); +} + +.todo-add-btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(74, 144, 217, 0.4); +} + +.todo-add-btn:active { + transform: translateY(0); +} + +/* 工具栏:筛选 + 统计 */ +.todo-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 18px; + flex-wrap: wrap; + gap: 10px; +} + +.todo-filters { + display: flex; + gap: 6px; +} + +.todo-filter-btn { + padding: 6px 16px; + border: 1px solid var(--theme-border-color, #ddd); + border-radius: 20px; + background: transparent; + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); + font-size: var(--theme-secondary, 0.78rem); + cursor: pointer; + transition: all 0.2s; +} + +.todo-filter-btn:hover { + border-color: var(--theme-color, #4a90d9); + color: var(--theme-color, #4a90d9); +} + +.todo-filter-btn.active { + background: var(--theme-color, #4a90d9); + color: #fff; + border-color: var(--theme-color, #4a90d9); +} + +/* 统计信息 */ +.todo-stats { + font-size: var(--theme-secondary, 0.78rem); + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); + display: flex; + gap: 14px; +} + +.todo-stats span { + display: flex; + align-items: center; + gap: 4px; +} + +/* 进度条 */ +.todo-progress { + width: 100%; + height: 4px; + background: var(--theme-border-color, #eee); + border-radius: 2px; + margin-bottom: 20px; + overflow: hidden; +} + +.todo-progress-bar { + height: 100%; + background: linear-gradient(90deg, var(--theme-color, #4a90d9), #67b8f7); + border-radius: 2px; + transition: width 0.4s ease; +} + +/* 列表 */ +.todo-list { + list-style: none; + padding: 0; + margin: 0; +} + +/* 每条待办 — 左侧优先级彩条 */ +.todo-item { + display: flex; + align-items: stretch; + border: 1px solid var(--theme-border-color, #e8e8e8); + border-radius: 10px; + margin-bottom: 8px; + background: var(--theme-bg-color, #fff); + transition: all 0.25s ease; + cursor: grab; + overflow: hidden; +} + +.todo-item:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.07); + transform: translateY(-1px); +} + +.todo-item.dragging { + opacity: 0.4; + transform: scale(0.98); +} + +.todo-item.completed { + opacity: 0.6; +} + +.todo-item.completed .todo-title { + text-decoration: line-through; + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); +} + +/* 优先级竖条 — 最左侧 */ +.todo-priority-bar { + width: 4px; + flex-shrink: 0; + transition: background 0.2s; +} + +.todo-priority-bar.high, +.todo-priority-bar.urgent { + background: #ef4444; +} + +.todo-priority-bar.twodays { + background: #f59e0b; +} + +.todo-priority-bar.medium, +.todo-priority-bar.thisweek { + background: #3b82f6; +} + +.todo-priority-bar.low, +.todo-priority-bar.anytime { + background: #10b981; +} + +.todo-item.completed .todo-priority-bar { + background: var(--theme-border-color, #d0d0d0); +} + +/* 条目主体 */ +.todo-body { + flex: 1; + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; + min-width: 0; +} + +/* 复选框 */ +.todo-checkbox { + width: 22px; + height: 22px; + border-radius: 50%; + border: 2px solid var(--theme-border-color, #ccc); + cursor: pointer; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; +} + +.todo-checkbox:hover { + border-color: var(--theme-color, #4a90d9); + box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.1); +} + +.todo-item.completed .todo-checkbox { + background: var(--theme-color, #4a90d9); + border-color: var(--theme-color, #4a90d9); +} + +.todo-item.completed .todo-checkbox::after { + content: ''; + width: 6px; + height: 10px; + border: 2px solid #fff; + border-top: none; + border-left: none; + transform: rotate(45deg) translate(-1px, -1px); +} + +/* 内容区 */ +.todo-content { + flex: 1; + min-width: 0; +} + +.todo-title { + font-size: var(--theme-text, 1rem); + color: var(--theme-text-color, #333); + word-break: break-word; + line-height: 1.5; +} + +.todo-title-input { + width: 100%; + padding: 6px 10px; + border: 1px solid var(--theme-color, #4a90d9); + border-radius: 6px; + font-size: var(--theme-text, 1rem); + background: var(--theme-bg-color, #fff); + color: var(--theme-text-color, #333); + outline: none; + box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.1); +} + +.todo-meta { + display: flex; + gap: 12px; + margin-top: 4px; + font-size: var(--theme-text-mini, 0.76rem); + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); + align-items: center; +} + +/* 优先级文字标签(meta 里的小标签) */ +.todo-priority-tag { + padding: 1px 8px; + border-radius: 10px; + font-size: var(--theme-text-more-mini, 0.69rem); + font-weight: 600; + letter-spacing: 0.5px; +} + +.todo-priority-tag.high, +.todo-priority-tag.urgent { + background: #fef2f2; + color: #dc2626; +} + +.todo-priority-tag.twodays { + background: #fffbeb; + color: #d97706; +} + +.todo-priority-tag.medium, +.todo-priority-tag.thisweek { + background: #eff6ff; + color: #2563eb; +} + +.todo-priority-tag.low, +.todo-priority-tag.anytime { + background: #ecfdf5; + color: #059669; +} + +/* 截止日期 + 倒计时 */ +.todo-due { + display: flex; + align-items: center; + gap: 4px; + font-size: var(--theme-text-more-mini, 0.69rem); +} + +.todo-due-icon { + font-size: 12px; +} + +.todo-countdown { + padding: 1px 6px; + border-radius: 8px; + font-weight: 500; + font-size: var(--theme-text-more-mini-2, 0.62rem); +} + +.todo-countdown.urgent { + background: #fef2f2; + color: #dc2626; +} + +.todo-countdown.soon { + background: #fffbeb; + color: #d97706; +} + +.todo-countdown.normal { + background: #ecfdf5; + color: #059669; +} + +.todo-countdown.overdue { + background: #fef2f2; + color: #dc2626; + font-weight: 700; +} + +/* 操作按钮 */ +.todo-actions { + display: flex; + gap: 4px; + flex-shrink: 0; + opacity: 0; + transition: opacity 0.2s; + padding-right: 4px; + align-items: center; +} + +.todo-item:hover .todo-actions { + opacity: 1; +} + +.todo-action-btn { + width: 30px; + height: 30px; + border: none; + border-radius: 8px; + background: transparent; + cursor: pointer; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); + transition: all 0.2s; +} + +.todo-action-btn:hover { + background: var(--theme-border-color, #f0f0f0); + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); +} + +.todo-action-btn.delete:hover { + background: #fef2f2; + color: #dc2626; +} + +/* 空状态 */ +.todo-empty { + text-align: center; + padding: 60px 20px; + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); + font-size: var(--theme-secondary, 0.78rem); +} + +.todo-empty-icon { + font-size: 48px; + margin-bottom: 12px; + opacity: 0.3; +} + +/* 加载状态 */ +.todo-loading { + text-align: center; + padding: 40px; + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); +} + +/* 编辑行内元素 */ +.todo-edit-inline { + display: flex; + gap: 8px; + margin-top: 8px; + align-items: center; +} + +.todo-edit-inline select, +.todo-edit-inline input[type="date"] { + padding: 5px 10px; + border: 1px solid var(--theme-border-color, #ddd); + border-radius: 6px; + font-size: var(--theme-text-mini, 0.76rem); + background: var(--theme-bg-color, #fff); + color: var(--theme-text-color, #333); +} + +.todo-edit-inline .todo-action-btn { + width: 28px; + height: 28px; + font-size: 13px; +} + +/* 重要度星星 */ +.todo-importance-tag { + color: #f59e0b; + font-size: var(--theme-text-more-mini, 0.69rem); + letter-spacing: -1px; +} + +/* 重要度滑块 */ +.todo-add-importance { + display: flex; + align-items: center; + gap: 6px; + font-size: var(--theme-secondary, 0.78rem); +} + +.todo-add-importance input[type="range"] { + width: 80px; + accent-color: #f59e0b; +} + +.todo-add-importance .importance-label { + color: #f59e0b; + font-size: var(--theme-text-more-mini, 0.69rem); + min-width: 60px; + letter-spacing: -1px; +} + +/* 视图切换按钮 */ +.todo-views { + display: flex; + gap: 4px; + background: var(--theme-border-color, #eee); + border-radius: 8px; + padding: 3px; +} + +.todo-view-btn { + padding: 5px 14px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); + font-size: var(--theme-secondary, 0.78rem); + cursor: pointer; + transition: all 0.2s; +} + +.todo-view-btn.active { + background: var(--theme-bg-color, #fff); + color: var(--theme-text-color, #333); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +/* 矩阵视图宽屏模式 — 扩展外层容器 */ +.main-main.todo-wide-mode { + width: 90% !important; + max-width: 1400px !important; +} + +/* 矩阵视图整体布局 */ +.todo-matrix-wrap { + display: flex; + flex-direction: column; + gap: 20px; +} + +/* 2D图容器 */ +.todo-chart-wrap { + flex: 1; + min-width: 0; + border: 1px solid var(--theme-border-color, #e8e8e8); + border-radius: 12px; + overflow: hidden; + background: var(--theme-bg-color, #fff); +} + +.todo-chart-wrap canvas { + display: block; + width: 100%; +} + +/* 侧边栏 — 横排4列卡片 */ +.todo-sidebar { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 14px; +} + +.todo-sidebar-group { + background: var(--theme-bg-color, #fff); + border: 1px solid var(--theme-border-color, #e8e8e8); + border-radius: 10px; + padding: 14px 16px; + min-height: 80px; +} + +.todo-sidebar-group:nth-child(1) { + border-top: 3px solid #ef4444; +} + +.todo-sidebar-group:nth-child(2) { + border-top: 3px solid #f59e0b; +} + +.todo-sidebar-group:nth-child(3) { + border-top: 3px solid #f59e0b; +} + +.todo-sidebar-group:nth-child(4) { + border-top: 3px solid #10b981; +} + +.todo-sidebar-title { + font-size: var(--theme-secondary, 0.78rem); + font-weight: 700; + margin-bottom: 8px; + color: var(--theme-text-color, #333); + display: flex; + align-items: center; + gap: 6px; +} + +.todo-sidebar-count { + background: var(--theme-border-color, #eee); + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); + font-size: var(--theme-text-more-mini-2, 0.62rem); + font-weight: 600; + padding: 1px 7px; + border-radius: 10px; +} + +.todo-sidebar-item { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 0; + font-size: var(--theme-text-mini, 0.76rem); + color: var(--theme-text-color, #333); + line-height: 1.4; +} + +.todo-sidebar-dot { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; +} + +.todo-sidebar-dot.urgent { background: #ef4444; } +.todo-sidebar-dot.twodays { background: #f59e0b; } +.todo-sidebar-dot.thisweek { background: #3b82f6; } +.todo-sidebar-dot.anytime { background: #10b981; } + +.todo-sidebar-text { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.todo-sidebar-due { + font-size: var(--theme-text-more-mini-2, 0.62rem); + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); + flex-shrink: 0; +} + +.todo-sidebar-empty { + font-size: var(--theme-text-more-mini, 0.69rem); + color: var(--theme-text-secondary, rgba(0, 0, 0, 0.65)); + text-align: center; + padding: 4px 0; +} + +/* 深色模式适配 */ +.dark .todo-add { + border-color: #3a3a3a; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); +} + +.dark .todo-add-input, +.dark .todo-add-select, +.dark .todo-add-date { + background: var(--theme-front-color, #2a2a2b); + border-color: #3a3a3a; + color: var(--theme-text-color); +} + +.dark .todo-item { + border-color: #3a3a3a; + background: var(--theme-front-color, #2a2a2b); +} + +.dark .todo-item:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); +} + +.dark .todo-checkbox { + border-color: #555; +} + +.dark .todo-title-input { + background: var(--theme-front-color, #2a2a2b); + color: var(--theme-text-color); +} + +.dark .todo-filter-btn { + border-color: #3a3a3a; + color: var(--theme-text-secondary); +} + +.dark .todo-progress { + background: #3a3a3a; +} + +.dark .todo-edit-inline select, +.dark .todo-edit-inline input[type="date"] { + background: var(--theme-front-color, #2a2a2b); + border-color: #3a3a3a; + color: var(--theme-text-color); +} + +.dark .todo-action-btn { + color: var(--theme-text-secondary); +} + +.dark .todo-action-btn:hover { + background: #3a3a3a; +} + +.dark .todo-priority-tag.high, +.dark .todo-priority-tag.urgent { + background: rgba(220, 38, 38, 0.15); +} + +.dark .todo-priority-tag.twodays { + background: rgba(217, 119, 6, 0.15); +} + +.dark .todo-priority-tag.medium, +.dark .todo-priority-tag.thisweek { + background: rgba(37, 99, 235, 0.15); +} + +.dark .todo-priority-tag.low, +.dark .todo-priority-tag.anytime { + background: rgba(5, 150, 105, 0.15); +} + +.dark .todo-countdown.urgent, +.dark .todo-countdown.overdue { + background: rgba(220, 38, 38, 0.15); +} + +.dark .todo-countdown.soon { + background: rgba(217, 119, 6, 0.15); +} + +.dark .todo-countdown.normal { + background: rgba(5, 150, 105, 0.15); +} + +.dark .todo-views { + background: #2a2a2b; +} + +.dark .todo-view-btn.active { + background: #3a3a3a; + color: var(--theme-text-color); +} + +.dark .todo-chart-wrap { + border-color: #3a3a3a; + background: var(--theme-front-color, #2a2a2b); +} + +.dark .todo-sidebar-group { + border-color: #3a3a3a; + background: var(--theme-front-color, #2a2a2b); +} + +.dark .todo-sidebar-count { + background: #3a3a3a; +} + +/* 番茄钟容器 */ +.pomodoro-container { + display: flex; + flex-direction: row; + gap: 8px; + margin-bottom: 16px; +} + +.pomodoro-container:empty { + margin-bottom: 0; +} + +/* 番茄钟计时条 */ +.pomodoro-bar { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 18px; + flex: 1; + min-width: 0; + border-radius: 12px; + background: linear-gradient(135deg, #ef4444, #dc2626); + color: #fff; + box-shadow: 0 4px 16px rgba(239, 68, 68, 0.3); + transition: background 0.4s, box-shadow 0.4s; + flex-wrap: wrap; +} + +.pomodoro-bar.break { + background: linear-gradient(135deg, #6ee7b7, #34d399); + box-shadow: 0 4px 16px rgba(110, 231, 183, 0.3); +} + +.pomodoro-bar.longbreak { + background: linear-gradient(135deg, #059669, #047857); + box-shadow: 0 4px 16px rgba(5, 150, 105, 0.3); +} + +.pomodoro-bar.paused { + opacity: 0.7; +} + +.pomodoro-icon { + font-size: 20px; + flex-shrink: 0; +} + +.pomodoro-task-name { + flex: 1; + min-width: 0; + font-size: var(--theme-secondary, 0.78rem); + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.pomodoro-timer { + font-size: 1.5rem; + font-weight: 700; + font-family: 'Courier New', monospace; + letter-spacing: 2px; + flex-shrink: 0; +} + +.pomodoro-phase { + font-size: var(--theme-text-more-mini, 0.69rem); + padding: 2px 10px; + border-radius: 10px; + background: rgba(255, 255, 255, 0.2); + flex-shrink: 0; +} + +.pomodoro-phase.hide-when-crowded { + display: none; +} + +.pomodoro-actions { + display: flex; + gap: 4px; + flex-shrink: 0; +} + +.pomodoro-btn { + width: 32px; + height: 32px; + border: none; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + color: #fff; + font-size: 14px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s; +} + +.pomodoro-btn:hover { + background: rgba(255, 255, 255, 0.35); +} + +.pomodoro-count { + font-size: var(--theme-secondary, 0.78rem); + flex-shrink: 0; +} + +/* 当前番茄钟任务高亮 */ +.todo-item.pomodoro-active { + border-color: #ef4444; + box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2); +} + +/* 番茄数标签 */ +.todo-pomodoro-count { + color: #ef4444; + font-size: var(--theme-text-more-mini, 0.69rem); + font-weight: 600; + display: flex; + align-items: center; + gap: 2px; +} + +/* 列表中的番茄钟启动按钮 */ +.todo-action-btn.pomodoro { + color: #ef4444; +} + +.todo-action-btn.pomodoro:hover { + background: #fef2f2; + color: #dc2626; +} + +.dark .todo-action-btn.pomodoro:hover { + background: rgba(220, 38, 38, 0.15); +} + +/* 侧边栏番茄数 */ +.todo-sidebar-pomodoro { + color: #ef4444; + font-size: var(--theme-text-more-mini-2, 0.62rem); + flex-shrink: 0; +} + +/* 侧边栏番茄钟启动按钮 */ +.todo-sidebar-play { + width: 18px; + height: 18px; + border: none; + border-radius: 50%; + background: transparent; + color: #ef4444; + font-size: 10px; + cursor: pointer; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s; + padding: 0; +} + +.todo-sidebar-play:hover { + background: #fef2f2; +} + +.dark .todo-sidebar-play:hover { + background: rgba(220, 38, 38, 0.15); +} + +/* 响应式 */ +@media (max-width: 768px) { + .todo-container { + padding: 16px 12px; + } + + .todo-add { + flex-direction: column; + padding: 14px; + } + + .todo-add-input { + min-width: unset; + } + + .todo-toolbar { + flex-direction: column; + align-items: flex-start; + } + + .todo-matrix-wrap { + flex-direction: column; + } + + .todo-sidebar { + grid-template-columns: repeat(2, 1fr); + gap: 8px; + } + + .main-main.todo-wide-mode { + width: 100% !important; + } + + .todo-body { + padding: 12px; + } + + .todo-actions { + opacity: 1; + } + + .todo-header h2 { + font-size: 1.3rem; + } +} diff --git a/common/todo/todo.js b/common/todo/todo.js new file mode 100644 index 0000000..c823346 --- /dev/null +++ b/common/todo/todo.js @@ -0,0 +1,1158 @@ +/** + * 待办事项前端逻辑 + * LocalStorage 缓存 + AJAX 同步 + 2D 艾森豪威尔矩阵视图 + * @author Haibin + * @date 2026-04-17 + */ +(function () { + 'use strict'; + + const STORAGE_KEY = 'document_todos'; + const AJAX_URL = window.HOME + '/wp-admin/admin-ajax.php'; + + let todos = []; + let currentFilter = 'all'; + let currentView = 'chart'; + let editingId = null; + let dragItem = null; + + // Chart state + let chartCanvas = null; + let chartCtx = null; + let chartPoints = []; + let hoveredPoint = null; + let chartPadding = { top: 40, right: 40, bottom: 50, left: 60 }; + + /* ========== LocalStorage ========== */ + function saveToLocal() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); } catch (e) { } } + function loadFromLocal() { try { var d = localStorage.getItem(STORAGE_KEY); return d ? JSON.parse(d) : []; } catch (e) { return []; } } + + /* ========== AJAX ========== */ + function ajax(action, data) { + return new Promise(function (resolve, reject) { + var fd = new FormData(); + fd.append('action', action); + if (data) Object.keys(data).forEach(function (k) { fd.append(k, data[k]); }); + fetch(AJAX_URL, { method: 'POST', body: fd, credentials: 'same-origin' }) + .then(function (r) { return r.json(); }) + .then(function (res) { res.success ? resolve(res.data) : reject(res.data); }) + .catch(reject); + }); + } + function syncFromServer() { + ajax('todo_list').then(function (data) { todos = data; saveToLocal(); render(); }).catch(function () { }); + } + + /* ========== 日期计算 ========== */ + function getDaysRemaining(dateStr) { + if (!dateStr) return null; + var today = new Date(); today.setHours(0, 0, 0, 0); + var due = new Date(dateStr); due.setHours(0, 0, 0, 0); + return Math.ceil((due - today) / 86400000); + } + function formatDate(dateStr) { + if (!dateStr) return ''; + var d = new Date(dateStr); + return (d.getMonth() + 1) + '月' + d.getDate() + '日'; + } + function getCountdownHtml(todo) { + if (!todo.due_date || todo.completed == 1) return ''; + var days = getDaysRemaining(todo.due_date); + if (days === null) return ''; + var cls, text; + if (days < 0) { cls = 'overdue'; text = '已过期' + Math.abs(days) + '天'; } + else if (days === 0) { cls = 'urgent'; text = '今天截止'; } + else if (days === 1) { cls = 'urgent'; text = '明天截止'; } + else if (days <= 3) { cls = 'urgent'; text = '剩余' + days + '天'; } + else if (days <= 14) { cls = 'soon'; text = '剩余' + days + '天'; } + else { cls = 'normal'; text = '剩余' + days + '天'; } + return '' + text + ''; + } + function isOverdue(todo) { + if (!todo.due_date || todo.completed == 1) return false; + var d = getDaysRemaining(todo.due_date); return d !== null && d < 0; + } + + /* ========== 紧急度:4级 + 自动升级 ========== */ + // priority: anytime(随时可以) / thisweek(这周处理) / twodays(这两天) / urgent(紧急) + var PRIORITY_MAP = { + urgent: { label: '紧急', css: 'urgent', level: 4 }, + twodays: { label: '这两天', css: 'twodays', level: 3 }, + thisweek: { label: '这周处理', css: 'thisweek', level: 2 }, + anytime: { label: '随时可以', css: 'anytime', level: 1 }, + // 向后兼容旧数据 + high: { label: '紧急', css: 'urgent', level: 4 }, + medium: { label: '这周处理', css: 'thisweek', level: 2 }, + low: { label: '随时可以', css: 'anytime', level: 1 } + }; + + function priorityLabel(p) { + return (PRIORITY_MAP[p] || PRIORITY_MAP.thisweek).label; + } + function priorityCss(p) { + return (PRIORITY_MAP[p] || PRIORITY_MAP.thisweek).css; + } + + /** + * 根据截止日期自动升级紧急度(只升不降) + * 规则: + * <= 0天 → urgent + * <= 3天 → 至少 twodays + * <= 14天 → 至少 thisweek + */ + function getEffectivePriority(todo) { + var base = todo.priority || 'thisweek'; + if (!todo.due_date || todo.completed == 1) return base; + var days = getDaysRemaining(todo.due_date); + if (days === null) return base; + + var baseLevel = (PRIORITY_MAP[base] || PRIORITY_MAP.thisweek).level; + + if (days <= 0 && baseLevel < 4) return 'urgent'; + if (days <= 3 && baseLevel < 3) return 'twodays'; + if (days <= 14 && baseLevel < 2) return 'thisweek'; + return base; + } + + function importanceStars(v) { + v = parseInt(v) || 3; + var s = ''; + for (var i = 0; i < 5; i++) s += i < v ? '★' : '☆'; + return s; + } + + /** + * 计算有效重要度(图表用) + * >14天的待办:每隔7天升高0.5★,14天=3★ + * 即 effectiveImportance = max(原importance, 3 + (days - 14) / 7 * 0.5) + * 上限5 + */ + function getEffectiveImportance(todo) { + var imp = parseInt(todo.importance) || 3; + if (!todo.due_date || todo.completed == 1) return imp; + var days = getDaysRemaining(todo.due_date); + if (days === null || days <= 14) return imp; + var boost = 3 + (days - 14) / 7 * 0.5; + return Math.min(5, Math.max(imp, boost)); + } + + /* ========== CRUD ========== */ + function addTodo() { + var input = document.getElementById('todo-input'); + var title = input.value.trim(); + if (!title) return; + var priority = document.getElementById('todo-priority').value; + var dueDate = document.getElementById('todo-date').value; + var importance = parseInt(document.getElementById('todo-importance').value) || 3; + + var tempId = 'temp_' + Date.now(); + var tempTodo = { + id: tempId, title: title, completed: 0, priority: priority, + importance: importance, due_date: dueDate || null, sort_order: 0, + created_at: new Date().toISOString().slice(0, 19).replace('T', ' '), + updated_at: new Date().toISOString().slice(0, 19).replace('T', ' ') + }; + todos.unshift(tempTodo); saveToLocal(); render(); + input.value = ''; document.getElementById('todo-date').value = ''; input.focus(); + + ajax('todo_create', { title: title, priority: priority, due_date: dueDate, importance: importance }) + .then(function (row) { + var idx = todos.findIndex(function (t) { return t.id === tempId; }); + if (idx !== -1) todos[idx] = row; + saveToLocal(); render(); + }) + .catch(function () { + todos = todos.filter(function (t) { return t.id !== tempId; }); + saveToLocal(); render(); + }); + } + + function toggleTodo(id) { + var todo = todos.find(function (t) { return t.id == id; }); + if (!todo) return; + todo.completed = todo.completed == 1 ? 0 : 1; + saveToLocal(); render(); + ajax('todo_update', { id: id, completed: todo.completed }); + } + + function deleteTodo(id) { + if (!confirm('确定删除这条待办?')) return; + todos = todos.filter(function (t) { return t.id != id; }); + saveToLocal(); render(); + ajax('todo_delete', { id: id }); + } + + function startEdit(id) { editingId = id; render(); var el = document.getElementById('edit-title-' + id); if (el) { el.focus(); el.select(); } } + + function saveEdit(id) { + var el = document.getElementById('edit-title-' + id); + var priorityEl = document.getElementById('edit-priority-' + id); + var dateEl = document.getElementById('edit-date-' + id); + var impEl = document.getElementById('edit-importance-' + id); + var todo = todos.find(function (t) { return t.id == id; }); + if (!todo) return; + var data = { id: id }; + var newTitle = el ? el.value.trim() : todo.title; + todo.title = newTitle || todo.title; data.title = todo.title; + if (priorityEl) { todo.priority = priorityEl.value; data.priority = priorityEl.value; } + if (dateEl) { todo.due_date = dateEl.value || null; data.due_date = dateEl.value || ''; } + if (impEl) { todo.importance = parseInt(impEl.value) || 3; data.importance = todo.importance; } + editingId = null; saveToLocal(); render(); + ajax('todo_update', data); + } + + function cancelEdit() { editingId = null; render(); } + function setFilter(f) { currentFilter = f; render(); } + function setView(v) { + currentView = v; + document.querySelectorAll('.todo-view-btn').forEach(function (b) { b.classList.toggle('active', b.dataset.view === v); }); + document.getElementById('todo-list').style.display = v === 'list' ? 'block' : 'none'; + document.getElementById('todo-matrix-wrap').style.display = v === 'chart' ? '' : 'none'; + // 矩阵视图时扩展外层容器宽度 + var mainMain = document.querySelector('.main-main'); + if (mainMain) mainMain.classList.toggle('todo-wide-mode', v === 'chart'); + render(); + } + + /* ========== 拖拽 ========== */ + function handleDragStart(e) { dragItem = e.currentTarget; dragItem.classList.add('dragging'); e.dataTransfer.effectAllowed = 'move'; } + function handleDragOver(e) { + e.preventDefault(); var target = e.target.closest('.todo-item'); + if (target && target !== dragItem) { + var list = document.getElementById('todo-list'); + var items = Array.from(list.children); + if (items.indexOf(dragItem) < items.indexOf(target)) list.insertBefore(dragItem, target.nextSibling); + else list.insertBefore(dragItem, target); + } + } + function handleDragEnd() { + if (dragItem) dragItem.classList.remove('dragging'); dragItem = null; + var list = document.getElementById('todo-list'); + var orders = []; + Array.from(list.children).forEach(function (item, idx) { + var id = item.dataset.id; var todo = todos.find(function (t) { return t.id == id; }); + if (todo) { todo.sort_order = idx; orders.push({ id: id, sort_order: idx }); } + }); + saveToLocal(); ajax('todo_reorder', { orders: JSON.stringify(orders) }); + } + + /* ========== 渲染 ========== */ + function getFilteredTodos() { + return todos.filter(function (t) { + if (currentFilter === 'active') return t.completed == 0; + if (currentFilter === 'completed') return t.completed == 1; + return true; + }); + } + + function render() { + var filtered = getFilteredTodos(); + var total = todos.length; + var done = todos.filter(function (t) { return t.completed == 1; }).length; + var percent = total > 0 ? Math.round(done / total * 100) : 0; + + document.getElementById('todo-stats').innerHTML = '共 ' + total + ' 项待完成 ' + (total - done) + '已完成 ' + done + '' + percent + '%'; + var pb = document.getElementById('todo-progress-bar'); if (pb) pb.style.width = percent + '%'; + document.querySelectorAll('.todo-filter-btn').forEach(function (b) { b.classList.toggle('active', b.dataset.filter === currentFilter); }); + + if (currentView === 'list') renderList(filtered); + else { renderChart(filtered); renderSidebar(filtered); } + } + + /* ========== 侧边栏:按象限分类 ========== */ + function renderSidebar(filtered) { + var sidebar = document.getElementById('todo-sidebar'); + if (!sidebar) return; + + var IMP_SPLIT = 3.2; + var groups = { + do_now: { label: '🔴 立即做', items: [] }, + do_fast: { label: '🟠 快速做', items: [] }, + plan: { label: '🟡 计划做', items: [] }, + general: { label: '🟢 一般事务', items: [] } + }; + + filtered.forEach(function (todo) { + if (todo.completed == 1) return; + var days = getDaysRemaining(todo.due_date); + var imp = getEffectiveImportance(todo); + var urgent = (days !== null && days <= 3.2); + // 计划做阈值:x>14时沿升星斜线 + var daysVal = (days === null) ? 21 : Math.max(-3, Math.min(21, days)); + var impThreshold = daysVal > 14 ? 3 + (daysVal - 14) / 7 * 0.5 : IMP_SPLIT; + + if (urgent && imp >= IMP_SPLIT) groups.do_now.items.push(todo); + else if (urgent && imp < IMP_SPLIT) groups.do_fast.items.push(todo); + else if (imp >= impThreshold) groups.plan.items.push(todo); + else groups.general.items.push(todo); + }); + + var html = ''; + ['do_now', 'do_fast', 'plan', 'general'].forEach(function (key) { + var g = groups[key]; + html += '
    '; + html += '
    ' + g.label + ' ' + g.items.length + '
    '; + if (g.items.length === 0) { + html += '
    - 无 -
    '; + } else { + g.items.forEach(function (todo) { + var effP = getEffectivePriority(todo); + var css = priorityCss(effP); + html += '
    '; + html += ''; + html += ''; + html += '' + escapeHtml(todo.title) + ''; + if (todo.due_date) { + var d = getDaysRemaining(todo.due_date); + var dText = d < 0 ? '过期' + Math.abs(d) + '天' : d === 0 ? '今天' : d + '天'; + html += '' + dText + ''; + } + var pc = getPomodoroCount(todo.id); + var pm = getPomodoroMinutes(todo.id); + if (pc > 0 || pm > 0) { + var parts = []; + if (pc > 0) parts.push('🍅' + pc); + if (pm > 0) parts.push(formatMinutes(pm)); + html += '' + parts.join(' · ') + ''; + } + html += '
    '; + }); + } + html += '
    '; + }); + + sidebar.innerHTML = html; + } + + function renderList(filtered) { + var listEl = document.getElementById('todo-list'); + if (filtered.length === 0) { + listEl.innerHTML = '
    📋
    ' + + (currentFilter === 'all' ? '暂无待办事项,添加一个吧' : currentFilter === 'active' ? '所有任务都完成了!' : '还没有已完成的待办') + '
    '; + return; + } + var html = ''; + filtered.forEach(function (todo) { + var isEditing = editingId == todo.id; + var effPriority = getEffectivePriority(todo); + var effCss = priorityCss(effPriority); + var upgraded = effPriority !== (todo.priority || 'thisweek') && todo.completed != 1; + + html += '
    '; + html += '
    '; + html += '
    '; + html += '
    '; + html += '
    '; + if (isEditing) { + html += ''; + html += '
    '; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += '
    '; + } else { + html += '
    ' + escapeHtml(todo.title) + '
    '; + html += '
    '; + html += '' + priorityLabel(effPriority); + if (upgraded) html += ' ↑'; + html += ''; + html += '' + importanceStars(todo.importance) + ''; + html += pomodoroCountHtml(todo.id); + if (todo.due_date) { + html += '📅' + formatDate(todo.due_date) + ''; + html += getCountdownHtml(todo); + } + html += '
    '; + } + html += '
    '; + if (!isEditing) { + html += '
    '; + if (todo.completed != 1) html += ''; + html += ''; + html += ''; + html += '
    '; + } + html += '
    '; + }); + listEl.innerHTML = html; + listEl.querySelectorAll('.todo-item').forEach(function (item) { + item.addEventListener('dragstart', handleDragStart); + item.addEventListener('dragover', handleDragOver); + item.addEventListener('dragend', handleDragEnd); + }); + } + + /* ========== 2D Chart ========== */ + + function renderChart(filtered) { + var wrap = document.getElementById('todo-chart-wrap'); + chartCanvas = document.getElementById('todo-chart'); + if (!chartCanvas) return; + chartCtx = chartCanvas.getContext('2d'); + + var rect = wrap.getBoundingClientRect(); + var dpr = window.devicePixelRatio || 1; + var w = rect.width; + var h = Math.max(400, Math.min(w * 0.65, 520)); + chartCanvas.width = w * dpr; + chartCanvas.height = h * dpr; + chartCanvas.style.width = w + 'px'; + chartCanvas.style.height = h + 'px'; + chartCtx.scale(dpr, dpr); + + var isDark = document.documentElement.classList.contains('dark') || document.body.classList.contains('dark'); + var p = chartPadding; + var cw = w - p.left - p.right; + var ch = h - p.top - p.bottom; + + chartCtx.clearRect(0, 0, w, h); + + // X轴分割线位置:3天 和 14天(按比例) + var MAX_DAYS = 21; // X轴最大天数 + var XOFFSET = 3; // 左边留给过期的 + var xTotalRange = MAX_DAYS + XOFFSET; + var x3 = p.left + ((3.2 + XOFFSET) / xTotalRange) * cw; + var x14 = p.left + ((14 + XOFFSET) / xTotalRange) * cw; + var IMP_SPLIT = 3.2; // 重要/不重要分界线 + var impYRatio = (IMP_SPLIT - 1) / 4; // 在0~1范围内的位置 + var yPadC = 0.08; + var splitY = p.top + ch * (1 - yPadC) - impYRatio * ch * (1 - 2 * yPadC); + + // 辅助函数:天数→屏幕X,重要度→屏幕Y + function daysToX(d) { return p.left + ((d + XOFFSET) / xTotalRange) * cw; } + function impToY(v) { return p.top + ch * (1 - yPadC) - ((v - 1) / 4) * ch * (1 - 2 * yPadC); } + + // 升星分界线:x≤14天时 y=3.2★, x>14天时 y=3+(x-14)/7*0.5 + // 这条线就是计划做和一般事务的边界 + var slopeStartX = daysToX(14); + var slopeStartY = impToY(3.0); // 14天时boost=3 + var slopeEndDays = MAX_DAYS; + var slopeEndImp = Math.min(5, 3 + (slopeEndDays - 14) / 7 * 0.5); // 21天时=3.5 + var slopeEndX = daysToX(slopeEndDays); + var slopeEndY = impToY(slopeEndImp); + + var chartLeft = p.left; + var chartRight = p.left + cw; + var chartTop = p.top; + var chartBottom = p.top + ch; + + // 四象限背景 — 使用path绘制多边形 + + // 1. 紧急+重要 (左上) — 红:矩形 + chartCtx.fillStyle = isDark ? 'rgba(239,68,68,0.35)' : 'rgba(239,68,68,0.25)'; + chartCtx.fillRect(chartLeft, chartTop, x3 - chartLeft, splitY - chartTop); + + // 2. 紧急+不重要 (左下) — 橙黄:矩形 + chartCtx.fillStyle = isDark ? 'rgba(234,179,8,0.30)' : 'rgba(234,179,8,0.22)'; + chartCtx.fillRect(chartLeft, splitY, x3 - chartLeft, chartBottom - splitY); + + // 3. 计划做 (右上) — 橙:梯形多边形 + // 从x3到x14: 底边是splitY (y=3.2) + // 从x14到xEnd: 底边沿升星斜线上升 + chartCtx.fillStyle = isDark ? 'rgba(245,158,11,0.28)' : 'rgba(245,158,11,0.18)'; + chartCtx.beginPath(); + chartCtx.moveTo(x3, chartTop); // 左上角 + chartCtx.lineTo(chartRight, chartTop); // 右上角 + chartCtx.lineTo(chartRight, slopeEndY); // 右边沿斜线 + chartCtx.lineTo(slopeStartX, slopeStartY); // 14天处 y=3 + chartCtx.lineTo(x3, splitY); // 3.2天处 y=3.2 + chartCtx.closePath(); + chartCtx.fill(); + + // 4. 一般事务 (右下) — 绿:梯形多边形(剩余区域) + chartCtx.fillStyle = isDark ? 'rgba(16,185,129,0.25)' : 'rgba(16,185,129,0.16)'; + chartCtx.beginPath(); + chartCtx.moveTo(x3, splitY); // 左边 y=3.2 + chartCtx.lineTo(slopeStartX, slopeStartY); // 14天处 y=3 + chartCtx.lineTo(chartRight, slopeEndY); // 右边沿斜线 + chartCtx.lineTo(chartRight, chartBottom); // 右下角 + chartCtx.lineTo(x3, chartBottom); // 左下角 + chartCtx.closePath(); + chartCtx.fill(); + + // 象限文字 — 放在各区域视觉中心 + chartCtx.font = 'bold 16px sans-serif'; + chartCtx.textAlign = 'center'; + + // 左上:立即做(矩形中心) + var luCx = (chartLeft + x3) / 2; + var luCy = (chartTop + splitY) / 2; + chartCtx.fillStyle = isDark ? '#f87171' : '#dc2626'; + chartCtx.fillText('紧急且重要', luCx, luCy - 8); + chartCtx.fillText('立即做!', luCx, luCy + 12); + + // 右上:计划做(梯形,取x中点处的上下边中点) + var planCx = (x3 + chartRight) / 2; + // 计算planCx对应的斜线y值 + var planDays = ((planCx - p.left) / cw) * xTotalRange - XOFFSET; + var planBottomImp = planDays > 14 ? 3 + (planDays - 14) / 7 * 0.5 : 3.2; + var planBottomY = impToY(planBottomImp); + var planCy = (chartTop + planBottomY) / 2; + chartCtx.fillStyle = isDark ? '#fbbf24' : '#d97706'; + chartCtx.fillText('计划做', planCx, planCy); + + // 左下:快速做(矩形中心) + var llCx = (chartLeft + x3) / 2; + var llCy = (splitY + chartBottom) / 2; + chartCtx.fillStyle = isDark ? '#facc15' : '#b45309'; + chartCtx.fillText('快速做', llCx, llCy); + + // 右下:一般事务(梯形,取x中点处的斜线和底边中点) + var genCx = planCx; + var genCy = (planBottomY + chartBottom) / 2; + chartCtx.fillStyle = isDark ? '#34d399' : '#047857'; + chartCtx.fillText('一般事务', genCx, genCy); + + // 分割虚线 — x=3.2天为主竖线,y=3.2★+升星斜线为横线 + chartCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.15)'; + chartCtx.setLineDash([6, 4]); + chartCtx.lineWidth = 1.5; + chartCtx.beginPath(); + chartCtx.moveTo(x3, chartTop); chartCtx.lineTo(x3, chartBottom); + chartCtx.stroke(); + // 水平段 splitY (x3→x14) + 斜线段 (x14→xEnd) + chartCtx.beginPath(); + chartCtx.moveTo(x3, splitY); + chartCtx.lineTo(slopeStartX, slopeStartY); + chartCtx.lineTo(chartRight, slopeEndY); + chartCtx.stroke(); + chartCtx.setLineDash([]); + + // 今天红虚线 + var xToday = p.left + ((0 + XOFFSET) / xTotalRange) * cw; + chartCtx.strokeStyle = isDark ? 'rgba(239,68,68,0.6)' : 'rgba(220,38,38,0.5)'; + chartCtx.setLineDash([4, 3]); + chartCtx.lineWidth = 1.5; + chartCtx.beginPath(); + chartCtx.moveTo(xToday, p.top); chartCtx.lineTo(xToday, p.top + ch); + chartCtx.stroke(); + chartCtx.setLineDash([]); + // "今天"标注 + chartCtx.fillStyle = isDark ? 'rgba(239,68,68,0.8)' : 'rgba(220,38,38,0.65)'; + chartCtx.font = 'bold 13px sans-serif'; + chartCtx.textAlign = 'center'; + chartCtx.fillText('TODAY', xToday, p.top - 6); + + // 坐标轴 + var axisColor = isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.35)'; + chartCtx.strokeStyle = axisColor; + chartCtx.lineWidth = 1.5; + chartCtx.beginPath(); + chartCtx.moveTo(p.left, p.top); chartCtx.lineTo(p.left, p.top + ch); chartCtx.lineTo(p.left + cw, p.top + ch); + chartCtx.stroke(); + chartCtx.lineWidth = 1; + + // X轴标签 + var textColor = isDark ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.7)'; + chartCtx.fillStyle = textColor; + chartCtx.font = '14px sans-serif'; + chartCtx.textAlign = 'center'; + + var xTickDays = [-2, 0, 1, 2, 3, 7, 14, 21]; + var xTickLabels = ['过期', '今天', '明天', '后天', '3天', '1周', '2周', '3周']; + + // 在3天处加粗刻度标注(主分割线) + for (var i = 0; i < xTickDays.length; i++) { + var tx = p.left + ((xTickDays[i] + XOFFSET) / xTotalRange) * cw; + var isSplit = (xTickDays[i] === 3); + chartCtx.font = isSplit ? 'bold 14px sans-serif' : '14px sans-serif'; + chartCtx.fillStyle = isSplit ? (isDark ? '#f87171' : '#dc2626') : textColor; + chartCtx.fillText(xTickLabels[i], tx, p.top + ch + 18); + // 小刻度线 + chartCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.15)'; + chartCtx.beginPath(); chartCtx.moveTo(tx, p.top + ch); chartCtx.lineTo(tx, p.top + ch + 5); chartCtx.stroke(); + } + chartCtx.fillStyle = textColor; + chartCtx.font = 'bold 14px sans-serif'; + chartCtx.fillText('← 紧急 不紧急 →', p.left + cw / 2, p.top + ch + 40); + + // Y轴标签 + chartCtx.font = '14px sans-serif'; + chartCtx.textAlign = 'right'; + var yLabelPad = 0.08; + for (var j = 1; j <= 5; j++) { + var yy = p.top + ch * (1 - yLabelPad) - (j - 1) / 4 * ch * (1 - 2 * yLabelPad); + chartCtx.fillStyle = textColor; + chartCtx.fillText(j + '★', p.left - 8, yy + 4); + } + chartCtx.save(); + chartCtx.translate(14, p.top + ch / 2); + chartCtx.rotate(-Math.PI / 2); + chartCtx.textAlign = 'center'; + chartCtx.font = 'bold 14px sans-serif'; chartCtx.fillStyle = textColor; + chartCtx.fillText('重要程度', 0, 0); + chartCtx.restore(); + + // Y轴网格线 + chartCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)'; + var yGridPad = 0.08; + for (var g = 1; g <= 5; g++) { + var gy = p.top + ch * (1 - yGridPad) - (g - 1) / 4 * ch * (1 - 2 * yGridPad); + chartCtx.beginPath(); chartCtx.moveTo(p.left, gy); chartCtx.lineTo(p.left + cw, gy); chartCtx.stroke(); + } + + // 计算散点 + chartPoints = []; + filtered.forEach(function (todo) { + var days = getDaysRemaining(todo.due_date); + var xVal; + if (days === null) xVal = MAX_DAYS; + else if (days < -XOFFSET) xVal = -XOFFSET; + else if (days > MAX_DAYS) xVal = MAX_DAYS; + else xVal = days; + + var imp = getEffectiveImportance(todo); + var xRatio = (xVal + XOFFSET) / xTotalRange; + var yPad = 0.08; // 上下各留8%,避免5★顶到象限标题 + var yRatio = (imp - 1) / 4; + var sx = p.left + xRatio * cw; + var sy = p.top + ch * (1 - yPad) - yRatio * ch * (1 - 2 * yPad); + + chartPoints.push({ x: xVal, y: imp, screenX: sx, screenY: sy, todo: todo }); + }); + + // 散开重叠的点 — 使用螺旋排列 + var minDist = 22; // 两点最小间距 + for (var i = 0; i < chartPoints.length; i++) { + var pi = chartPoints[i]; + var angle = 0, ring = 0, step = 0; + var maxTries = 30; + while (step < maxTries) { + var collides = false; + for (var k = 0; k < i; k++) { + var pk = chartPoints[k]; + var dx = pi.screenX - pk.screenX; + var dy = pi.screenY - pk.screenY; + if (Math.sqrt(dx * dx + dy * dy) < minDist) { + collides = true; + break; + } + } + if (!collides) break; + step++; + angle = step * 2.4; // 黄金角螺旋 + ring = minDist * 0.5 * Math.sqrt(step); + var origX = p.left + ((pi.x + XOFFSET) / xTotalRange) * cw; + var yPad2 = 0.08; + var origY = p.top + ch * (1 - yPad2) - ((pi.y - 1) / 4) * ch * (1 - 2 * yPad2); + pi.screenX = origX + Math.cos(angle) * ring; + pi.screenY = origY + Math.sin(angle) * ring; + } + } + + // 画散点 + chartPoints.forEach(function (pt) { + var completed = pt.todo.completed == 1; + var r = completed ? 6 : 10; + var alpha = completed ? 0.35 : 0.9; + + // 颜色根据4象限(紧急=3.2天内,重要=3.2★+含升星斜线) + var impThreshold = pt.x > 14 ? 3 + (pt.x - 14) / 7 * 0.5 : 3.2; + var color; + if (pt.x <= 3.2 && pt.y >= 3.2) color = 'rgba(220,38,38,' + alpha + ')'; // 立即做 红 + else if (pt.x <= 3.2 && pt.y < 3.2) color = 'rgba(217,119,6,' + alpha + ')'; // 快速做 橙 + else if (pt.y >= impThreshold) color = 'rgba(245,158,11,' + alpha + ')'; // 计划做 金橙 + else color = 'rgba(16,185,129,' + alpha + ')'; // 一般事务 绿 + + // 阴影 + chartCtx.shadowColor = 'rgba(0,0,0,0.15)'; + chartCtx.shadowBlur = 4; + chartCtx.shadowOffsetY = 2; + + chartCtx.beginPath(); + chartCtx.arc(pt.screenX, pt.screenY, r, 0, Math.PI * 2); + chartCtx.fillStyle = color; + chartCtx.fill(); + + chartCtx.shadowColor = 'transparent'; + + // 边框 + chartCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.2)'; + chartCtx.lineWidth = 1.5; + chartCtx.stroke(); + + // 已完成划线 + if (completed) { + chartCtx.beginPath(); + chartCtx.moveTo(pt.screenX - r, pt.screenY); + chartCtx.lineTo(pt.screenX + r, pt.screenY); + chartCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.6)' : 'rgba(0,0,0,0.5)'; + chartCtx.lineWidth = 2; + chartCtx.stroke(); + } + + // Hover 高亮 + if (hoveredPoint && hoveredPoint.todo.id === pt.todo.id) { + chartCtx.beginPath(); + chartCtx.arc(pt.screenX, pt.screenY, r + 5, 0, Math.PI * 2); + chartCtx.strokeStyle = color; + chartCtx.lineWidth = 2.5; + chartCtx.stroke(); + } + + // 番茄钟专注中 — 脉冲闪烁光环 + if (isPomodoring(pt.todo.id)) { + var pulse = (Math.sin(Date.now() / 400) + 1) / 2; // 0~1 脉冲 + var pulseR = r + 6 + pulse * 8; + var pulseAlpha = 0.15 + pulse * 0.35; + chartCtx.beginPath(); + chartCtx.arc(pt.screenX, pt.screenY, pulseR, 0, Math.PI * 2); + chartCtx.strokeStyle = 'rgba(239,68,68,' + pulseAlpha + ')'; + chartCtx.lineWidth = 2.5; + chartCtx.stroke(); + // 内层红色光晕 + chartCtx.beginPath(); + chartCtx.arc(pt.screenX, pt.screenY, r + 3, 0, Math.PI * 2); + chartCtx.strokeStyle = 'rgba(239,68,68,' + (0.4 + pulse * 0.3) + ')'; + chartCtx.lineWidth = 2; + chartCtx.stroke(); + } + }); + + // Tooltip + if (hoveredPoint) drawTooltip(hoveredPoint, w, h, isDark); + } + + function drawTooltip(pt, canvasW, canvasH, isDark) { + var todo = pt.todo; + var effP = getEffectivePriority(todo); + var lines = [escapeHtmlPlain(todo.title)]; + lines.push('紧急度: ' + priorityLabel(effP)); + lines.push('重要度: ' + importanceStars(todo.importance)); + if (todo.due_date) { + var days = getDaysRemaining(todo.due_date); + var dueText = formatDate(todo.due_date); + if (days < 0) dueText += ' (已过期' + Math.abs(days) + '天)'; + else if (days === 0) dueText += ' (今天)'; + else dueText += ' (剩余' + days + '天)'; + lines.push('截止: ' + dueText); + } else { + lines.push('截止: 未设定'); + } + if (todo.completed == 1) lines.push('✓ 已完成'); + + chartCtx.font = '12px sans-serif'; + var maxW = 0; + lines.forEach(function (l) { var m = chartCtx.measureText(l).width; if (m > maxW) maxW = m; }); + var tw = maxW + 24; + var th = lines.length * 22 + 16; + var tx = pt.screenX + 16; + var ty = pt.screenY - th / 2; + if (tx + tw > canvasW - 10) tx = pt.screenX - tw - 16; + if (ty < 5) ty = 5; + if (ty + th > canvasH - 5) ty = canvasH - th - 5; + + // 背景 + chartCtx.shadowColor = 'rgba(0,0,0,0.15)'; + chartCtx.shadowBlur = 12; + chartCtx.shadowOffsetY = 4; + chartCtx.fillStyle = isDark ? 'rgba(30,30,30,0.97)' : 'rgba(255,255,255,0.98)'; + chartCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.12)'; + chartCtx.lineWidth = 1; + roundRect(chartCtx, tx, ty, tw, th, 10); + chartCtx.fill(); + chartCtx.stroke(); + chartCtx.shadowColor = 'transparent'; + + // 文字 + chartCtx.fillStyle = isDark ? 'rgba(255,255,255,0.95)' : '#222'; + chartCtx.textAlign = 'left'; + chartCtx.font = 'bold 13px sans-serif'; + chartCtx.fillText(lines[0], tx + 12, ty + 22); + chartCtx.font = '11px sans-serif'; + chartCtx.fillStyle = isDark ? 'rgba(255,255,255,0.7)' : '#555'; + for (var i = 1; i < lines.length; i++) { + chartCtx.fillText(lines[i], tx + 12, ty + 22 + i * 22); + } + } + + function roundRect(ctx, x, y, w, h, r) { + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.lineTo(x + w - r, y); ctx.quadraticCurveTo(x + w, y, x + w, y + r); + ctx.lineTo(x + w, y + h - r); ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); + ctx.lineTo(x + r, y + h); ctx.quadraticCurveTo(x, y + h, x, y + h - r); + ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); + ctx.closePath(); + } + + function findPointAt(mx, my) { + var closest = null, minDist = 20; + chartPoints.forEach(function (pt) { + var dx = pt.screenX - mx, dy = pt.screenY - my; + var dist = Math.sqrt(dx * dx + dy * dy); + if (dist < minDist) { minDist = dist; closest = pt; } + }); + return closest; + } + + function initChartEvents() { + var canvas = document.getElementById('todo-chart'); + if (!canvas) return; + canvas.addEventListener('mousemove', function (e) { + var rect = canvas.getBoundingClientRect(); + var pt = findPointAt(e.clientX - rect.left, e.clientY - rect.top); + if (pt !== hoveredPoint) { + hoveredPoint = pt; + canvas.style.cursor = pt ? 'pointer' : 'default'; + if (currentView === 'chart') renderChart(getFilteredTodos()); + } + }); + canvas.addEventListener('mouseleave', function () { + if (hoveredPoint) { hoveredPoint = null; if (currentView === 'chart') renderChart(getFilteredTodos()); } + }); + canvas.addEventListener('click', function (e) { + var rect = canvas.getBoundingClientRect(); + var pt = findPointAt(e.clientX - rect.left, e.clientY - rect.top); + if (pt) { setView('list'); startEdit(pt.todo.id); } + }); + } + + /* ========== Pomodoro 番茄钟(最多3个并行) ========== */ + var pomodoroSlots = []; // [{todoId, phase, remaining, paused, count, timer}] + var pomodoroOrigTitle = ''; + var pomodoroAnimFrame = null; + var FOCUS_DURATION = 25 * 60; + var BREAK_DURATION = 5 * 60; + var LONG_BREAK_DURATION = 15 * 60; + var POMODORO_STORAGE = 'pomodoro_counts'; + var POMODORO_STATE = 'pomodoro_state'; + var MAX_POMODORO = 3; + + var pomodoroCounts = {}; // 番茄计数缓存 + var pomodoroMinutes = {}; // 累计专注分钟数 + + function getPomodoroCount(todoId) { + return pomodoroCounts[todoId] || 0; + } + function getPomodoroMinutes(todoId) { + return pomodoroMinutes[todoId] || 0; + } + function addPomodoroCount(todoId) { + pomodoroCounts[todoId] = (pomodoroCounts[todoId] || 0) + 1; + } + function addPomodoroMinutes(todoId, mins) { + pomodoroMinutes[todoId] = (pomodoroMinutes[todoId] || 0) + mins; + } + function formatMinutes(m) { + m = Math.round(m); + if (m < 60) return m + '分钟'; + var h = Math.floor(m / 60); + var r = m % 60; + return r > 0 ? h + 'h' + r + 'm' : h + 'h'; + } + + function findSlot(todoId) { + return pomodoroSlots.find(function (s) { return s.todoId == todoId; }); + } + function isPomodoring(todoId) { return !!findSlot(todoId); } + + function savePomodoroState() { + var stateData = pomodoroSlots.map(function (s) { + return { + todoId: s.todoId, phase: s.phase, count: s.count, paused: s.paused, + endTime: s.paused ? null : Date.now() + s.remaining * 1000, + remaining: s.remaining + }; + }); + // 本地缓存 + try { + if (pomodoroSlots.length === 0) localStorage.removeItem(POMODORO_STATE); + else localStorage.setItem(POMODORO_STATE, JSON.stringify(stateData)); + localStorage.setItem(POMODORO_STORAGE, JSON.stringify(pomodoroCounts)); + localStorage.setItem('pomodoro_minutes', JSON.stringify(pomodoroMinutes)); + } catch (e) { } + // 同步到服务端 + savePomodoroToServer(); + } + + var _pomodoroSaveTimer = null; + function savePomodoroToServer() { + // 防抖:500ms 内多次调用只发一次请求 + clearTimeout(_pomodoroSaveTimer); + _pomodoroSaveTimer = setTimeout(function () { + var stateData = pomodoroSlots.map(function (s) { + return { + todoId: s.todoId, phase: s.phase, count: s.count, paused: s.paused, + endTime: s.paused ? null : Date.now() + s.remaining * 1000, + remaining: s.remaining + }; + }); + ajax('pomodoro_save', { + state: JSON.stringify(stateData), + counts: JSON.stringify(pomodoroCounts), + minutes: JSON.stringify(pomodoroMinutes) + }).catch(function () { }); + }, 500); + } + + function restorePomodoro() { + // 优先从服务端加载,失败则用 localStorage + ajax('pomodoro_get').then(function (data) { + applyPomodoroData(data.state || [], data.counts || {}, data.minutes || {}); + }).catch(function () { + // 降级到本地 + try { + var arr = JSON.parse(localStorage.getItem(POMODORO_STATE)); + var counts = JSON.parse(localStorage.getItem(POMODORO_STORAGE) || '{}'); + var minutes = JSON.parse(localStorage.getItem('pomodoro_minutes') || '{}'); + applyPomodoroData(Array.isArray(arr) ? arr : [], counts || {}, minutes || {}); + } catch (e) { } + }); + } + + function applyPomodoroData(arr, counts, minutes) { + pomodoroCounts = counts || {}; + pomodoroMinutes = minutes || {}; + if (!Array.isArray(arr) || arr.length === 0) return; + arr.forEach(function (s) { + var todo = todos.find(function (t) { return t.id == s.todoId; }); + if (!todo) return; + // 避免重复恢复 + if (findSlot(s.todoId)) return; + var slot = { + todoId: s.todoId, phase: s.phase, count: s.count || 0, + paused: s.paused || false, remaining: 0, timer: null + }; + if (s.paused) { + slot.remaining = s.remaining || 0; + } else { + slot.remaining = Math.max(0, Math.round((s.endTime - Date.now()) / 1000)); + } + pomodoroSlots.push(slot); + slot.timer = setInterval(function () { tickSlot(slot); }, 1000); + if (slot.remaining <= 0 && !slot.paused) { + endSlotPhase(slot); + } + }); + if (pomodoroSlots.length > 0) { + pomodoroOrigTitle = document.title; + renderPomodoroUI(); + startPomodoroAnim(); + } + } + + function startPomodoro(todoId) { + if (findSlot(todoId)) { + alert('这个任务已经在计时了!'); + return; + } + if (pomodoroSlots.length >= MAX_POMODORO) { + alert('最多同时专注' + MAX_POMODORO + '件事情!\n完成或停止一个再开始新的吧。'); + return; + } + if (!pomodoroOrigTitle) pomodoroOrigTitle = document.title; + var todo = todos.find(function (t) { return t.id == todoId; }); + var slot = { + todoId: todoId, phase: 'focus', remaining: FOCUS_DURATION, + paused: false, count: 0, timer: null, phaseStartedAt: Date.now() + }; + pomodoroSlots.push(slot); + slot.timer = setInterval(function () { tickSlot(slot); }, 1000); + renderPomodoroUI(); + savePomodoroState(); + startPomodoroAnim(); + render(); + if ('Notification' in window && Notification.permission === 'default') { + Notification.requestPermission(); + } + } + + function tickSlot(slot) { + if (slot.paused) return; + slot.remaining--; + if (slot.remaining <= 0) { + endSlotPhase(slot); + } + renderPomodoroUI(); + savePomodoroState(); + } + + function endSlotPhase(slot) { + var todo = todos.find(function (t) { return t.id == slot.todoId; }); + var name = todo ? todo.title : ''; + if (slot.phase === 'focus') { + slot.count++; + addPomodoroCount(slot.todoId); + addPomodoroMinutes(slot.todoId, FOCUS_DURATION / 60); + render(); + if (slot.count % 4 === 0) { + slot.phase = 'longbreak'; + slot.remaining = LONG_BREAK_DURATION; + } else { + slot.phase = 'break'; + slot.remaining = BREAK_DURATION; + } + pomodoroNotify('「' + name + '」专注完成! 休息一下'); + } else { + slot.phase = 'focus'; + slot.remaining = FOCUS_DURATION; + pomodoroNotify('「' + name + '」休息结束! 继续专注'); + } + renderPomodoroUI(); + } + + function pauseSlot(todoId) { + var slot = findSlot(todoId); + if (!slot) return; + slot.paused = !slot.paused; + if (slot.paused) document.title = pomodoroOrigTitle; + renderPomodoroUI(); + savePomodoroState(); + } + + function stopSlot(todoId) { + var idx = pomodoroSlots.findIndex(function (s) { return s.todoId == todoId; }); + if (idx === -1) return; + var slot = pomodoroSlots[idx]; + // 如果在专注阶段,记录已经专注的分钟数 + if (slot.phase === 'focus') { + var totalSec = FOCUS_DURATION; + var elapsed = totalSec - slot.remaining; + if (elapsed > 0) addPomodoroMinutes(todoId, elapsed / 60); + } + clearInterval(slot.timer); + pomodoroSlots.splice(idx, 1); + if (pomodoroSlots.length === 0) { + stopPomodoroAnim(); + document.title = pomodoroOrigTitle || document.title; + } + renderPomodoroUI(); + savePomodoroState(); + render(); + } + + function renderPomodoroUI() { + var container = document.getElementById('pomodoro-container'); + if (!container) return; + if (pomodoroSlots.length === 0) { container.innerHTML = ''; return; } + + var html = ''; + pomodoroSlots.forEach(function (slot) { + var todo = todos.find(function (t) { return t.id == slot.todoId; }); + var m = Math.floor(slot.remaining / 60); + var s = slot.remaining % 60; + var timeStr = (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; + var phaseText = slot.phase === 'focus' ? '专注中' : slot.phase === 'break' ? '短休息' : '长休息'; + var isFocus = slot.phase === 'focus'; + var barClass = 'pomodoro-bar' + (slot.phase === 'longbreak' ? ' longbreak' : !isFocus ? ' break' : '') + (slot.paused ? ' paused' : ''); + + html += '
    '; + html += '🍅'; + html += '' + escapeHtml(todo ? todo.title : '') + ''; + html += '' + timeStr + ''; + html += '' + phaseText + ''; + html += '
    '; + html += ''; + html += ''; + html += '
    '; + var countStr = ''; + for (var i = 0; i < slot.count; i++) countStr += '🍅'; + if (countStr) html += '' + countStr + ''; + html += '
    '; + }); + container.innerHTML = html; + + // 标题栏显示第一个运行中的计时 + var active = pomodoroSlots.find(function (s) { return !s.paused; }); + if (active) { + var am = Math.floor(active.remaining / 60); + var as = active.remaining % 60; + var at = (am < 10 ? '0' : '') + am + ':' + (as < 10 ? '0' : '') + as; + var ap = active.phase === 'focus' ? '专注中' : '休息中'; + document.title = at + ' ' + ap + ' - 待办事项'; + } else { + document.title = pomodoroOrigTitle || '待办事项'; + } + } + + function pomodoroNotify(msg) { + if ('Notification' in window && Notification.permission === 'granted') { + new Notification('番茄钟', { body: msg }); + } + try { + var ctx = new (window.AudioContext || window.webkitAudioContext)(); + var osc = ctx.createOscillator(); + var gain = ctx.createGain(); + osc.connect(gain); gain.connect(ctx.destination); + osc.frequency.value = 880; osc.type = 'sine'; + gain.gain.setValueAtTime(0.3, ctx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.5); + osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.5); + } catch (e) { } + } + + function pomodoroCountHtml(todoId) { + var c = getPomodoroCount(todoId); + var m = getPomodoroMinutes(todoId); + if (c === 0 && m === 0) return ''; + var parts = []; + if (c > 0) parts.push('🍅' + (c > 1 ? '×' + c : '')); + if (m > 0) parts.push(formatMinutes(m)); + return '' + parts.join(' · ') + ''; + } + + function startPomodoroAnim() { + stopPomodoroAnim(); + function frame() { + if (currentView === 'chart' && pomodoroSlots.length > 0) { + renderChart(getFilteredTodos()); + } + pomodoroAnimFrame = requestAnimationFrame(frame); + } + pomodoroAnimFrame = requestAnimationFrame(frame); + } + function stopPomodoroAnim() { + if (pomodoroAnimFrame) { cancelAnimationFrame(pomodoroAnimFrame); pomodoroAnimFrame = null; } + } + + function initPomodoroEvents() { + // 事件通过 onclick 直接绑定到动态渲染的按钮上 + } + + /* ========== Helpers ========== */ + function escapeHtml(str) { var d = document.createElement('div'); d.textContent = str; return d.innerHTML; } + function escapeHtmlPlain(str) { return str.replace(/&/g, '&').replace(//g, '>'); } + + /* ========== Init ========== */ + function init() { + todos = loadFromLocal(); + // 默认矩阵视图,应用宽屏模式 + var mainMain = document.querySelector('.main-main'); + if (mainMain) mainMain.classList.add('todo-wide-mode'); + render(); syncFromServer(); + + document.getElementById('todo-add-btn').addEventListener('click', addTodo); + document.getElementById('todo-input').addEventListener('keydown', function (e) { if (e.key === 'Enter') addTodo(); }); + document.querySelectorAll('.todo-filter-btn').forEach(function (b) { b.addEventListener('click', function () { setFilter(this.dataset.filter); }); }); + document.querySelectorAll('.todo-view-btn').forEach(function (b) { b.addEventListener('click', function () { setView(this.dataset.view); }); }); + + initChartEvents(); + initPomodoroEvents(); + restorePomodoro(); + var resizeTimer; + window.addEventListener('resize', function () { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(function () { if (currentView === 'chart') render(); }, 200); + }); + + var impSlider = document.getElementById('todo-importance'); + var impLabel = document.getElementById('todo-importance-label'); + if (impSlider && impLabel) { + impSlider.addEventListener('input', function () { impLabel.textContent = importanceStars(this.value); }); + } + } + + window._todo = { toggle: toggleTodo, deleteTodo: deleteTodo, startEdit: startEdit, saveEdit: saveEdit, cancelEdit: cancelEdit, startPomodoro: startPomodoro, pauseSlot: pauseSlot, stopSlot: stopSlot }; + if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); + else init(); +})(); diff --git a/common/todo/todo.min.css b/common/todo/todo.min.css new file mode 100644 index 0000000..8c564f5 --- /dev/null +++ b/common/todo/todo.min.css @@ -0,0 +1 @@ +.todo-container{max-width:1100px;margin:0 auto;padding:30px 20px}.todo-header{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:20px}.todo-header h2{font-size:1.4rem;font-weight:700;margin:0;color:var(--theme-text-color, #1a1a2e)}.todo-header p{font-size:var(--theme-secondary, .78rem);color:var(--theme-text-secondary, rgba(0, 0, 0, .65));margin:0}.todo-add{display:flex;gap:8px;margin-bottom:20px;flex-wrap:wrap;padding:12px 14px;background:var(--theme-bg-color, #fff);border:1px solid var(--theme-border-color, #e8e8e8);border-radius:12px;box-shadow:0 1px 4px #0000000a}.todo-add-input{flex:1;min-width:200px;padding:7px 12px;border:1px solid var(--theme-border-color, #ddd);border-radius:8px;font-size:var(--theme-text, 1rem);background:var(--theme-bg-color, #fafafa);color:var(--theme-text-color, #333);outline:none;transition:border-color .2s,box-shadow .2s}.todo-add-input:focus{border-color:var(--theme-color, #4a90d9);box-shadow:0 0 0 3px #4a90d91a;background:var(--theme-bg-color, #fff)}.todo-add-select,.todo-add-date{padding:7px 10px;border:1px solid var(--theme-border-color, #ddd);border-radius:8px;font-size:var(--theme-secondary, .78rem);background:var(--theme-bg-color, #fafafa);color:var(--theme-text-color, #333);outline:none;transition:border-color .2s}.todo-add-select:focus,.todo-add-date:focus{border-color:var(--theme-color, #4a90d9)}.todo-add-btn{padding:7px 20px;border:none;border-radius:8px;background:var(--theme-color, #4a90d9);color:#fff;font-size:var(--theme-text, 1rem);font-weight:500;cursor:pointer;transition:all .2s;box-shadow:0 2px 6px #4a90d94d}.todo-add-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px #4a90d966}.todo-add-btn:active{transform:translateY(0)}.todo-toolbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px;flex-wrap:wrap;gap:10px}.todo-filters{display:flex;gap:6px}.todo-filter-btn{padding:6px 16px;border:1px solid var(--theme-border-color, #ddd);border-radius:20px;background:transparent;color:var(--theme-text-secondary, rgba(0, 0, 0, .65));font-size:var(--theme-secondary, .78rem);cursor:pointer;transition:all .2s}.todo-filter-btn:hover{border-color:var(--theme-color, #4a90d9);color:var(--theme-color, #4a90d9)}.todo-filter-btn.active{background:var(--theme-color, #4a90d9);color:#fff;border-color:var(--theme-color, #4a90d9)}.todo-stats{font-size:var(--theme-secondary, .78rem);color:var(--theme-text-secondary, rgba(0, 0, 0, .65));display:flex;gap:14px}.todo-stats span{display:flex;align-items:center;gap:4px}.todo-progress{width:100%;height:4px;background:var(--theme-border-color, #eee);border-radius:2px;margin-bottom:20px;overflow:hidden}.todo-progress-bar{height:100%;background:linear-gradient(90deg,var(--theme-color, #4a90d9),#67b8f7);border-radius:2px;transition:width .4s ease}.todo-list{list-style:none;padding:0;margin:0}.todo-item{display:flex;align-items:stretch;border:1px solid var(--theme-border-color, #e8e8e8);border-radius:10px;margin-bottom:8px;background:var(--theme-bg-color, #fff);transition:all .25s ease;cursor:grab;overflow:hidden}.todo-item:hover{box-shadow:0 4px 16px #00000012;transform:translateY(-1px)}.todo-item.dragging{opacity:.4;transform:scale(.98)}.todo-item.completed{opacity:.6}.todo-item.completed .todo-title{text-decoration:line-through;color:var(--theme-text-secondary, rgba(0, 0, 0, .65))}.todo-priority-bar{width:4px;flex-shrink:0;transition:background .2s}.todo-priority-bar.high,.todo-priority-bar.urgent{background:#ef4444}.todo-priority-bar.twodays{background:#f59e0b}.todo-priority-bar.medium,.todo-priority-bar.thisweek{background:#3b82f6}.todo-priority-bar.low,.todo-priority-bar.anytime{background:#10b981}.todo-item.completed .todo-priority-bar{background:var(--theme-border-color, #d0d0d0)}.todo-body{flex:1;display:flex;align-items:center;gap:12px;padding:14px 16px;min-width:0}.todo-checkbox{width:22px;height:22px;border-radius:50%;border:2px solid var(--theme-border-color, #ccc);cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:all .2s}.todo-checkbox:hover{border-color:var(--theme-color, #4a90d9);box-shadow:0 0 0 3px #4a90d91a}.todo-item.completed .todo-checkbox{background:var(--theme-color, #4a90d9);border-color:var(--theme-color, #4a90d9)}.todo-item.completed .todo-checkbox:after{content:"";width:6px;height:10px;border:2px solid #fff;border-top:none;border-left:none;transform:rotate(45deg) translate(-1px,-1px)}.todo-content{flex:1;min-width:0}.todo-title{font-size:var(--theme-text, 1rem);color:var(--theme-text-color, #333);word-break:break-word;line-height:1.5}.todo-title-input{width:100%;padding:6px 10px;border:1px solid var(--theme-color, #4a90d9);border-radius:6px;font-size:var(--theme-text, 1rem);background:var(--theme-bg-color, #fff);color:var(--theme-text-color, #333);outline:none;box-shadow:0 0 0 3px #4a90d91a}.todo-meta{display:flex;gap:12px;margin-top:4px;font-size:var(--theme-text-mini, .76rem);color:var(--theme-text-secondary, rgba(0, 0, 0, .65));align-items:center}.todo-priority-tag{padding:1px 8px;border-radius:10px;font-size:var(--theme-text-more-mini, .69rem);font-weight:600;letter-spacing:.5px}.todo-priority-tag.high,.todo-priority-tag.urgent{background:#fef2f2;color:#dc2626}.todo-priority-tag.twodays{background:#fffbeb;color:#d97706}.todo-priority-tag.medium,.todo-priority-tag.thisweek{background:#eff6ff;color:#2563eb}.todo-priority-tag.low,.todo-priority-tag.anytime{background:#ecfdf5;color:#059669}.todo-due{display:flex;align-items:center;gap:4px;font-size:var(--theme-text-more-mini, .69rem)}.todo-due-icon{font-size:12px}.todo-countdown{padding:1px 6px;border-radius:8px;font-weight:500;font-size:var(--theme-text-more-mini-2, .62rem)}.todo-countdown.urgent{background:#fef2f2;color:#dc2626}.todo-countdown.soon{background:#fffbeb;color:#d97706}.todo-countdown.normal{background:#ecfdf5;color:#059669}.todo-countdown.overdue{background:#fef2f2;color:#dc2626;font-weight:700}.todo-actions{display:flex;gap:4px;flex-shrink:0;opacity:0;transition:opacity .2s;padding-right:4px;align-items:center}.todo-item:hover .todo-actions{opacity:1}.todo-action-btn{width:30px;height:30px;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center;color:var(--theme-text-secondary, rgba(0, 0, 0, .65));transition:all .2s}.todo-action-btn:hover{background:var(--theme-border-color, #f0f0f0);color:var(--theme-text-secondary, rgba(0, 0, 0, .65))}.todo-action-btn.delete:hover{background:#fef2f2;color:#dc2626}.todo-empty{text-align:center;padding:60px 20px;color:var(--theme-text-secondary, rgba(0, 0, 0, .65));font-size:var(--theme-secondary, .78rem)}.todo-empty-icon{font-size:48px;margin-bottom:12px;opacity:.3}.todo-loading{text-align:center;padding:40px;color:var(--theme-text-secondary, rgba(0, 0, 0, .65))}.todo-edit-inline{display:flex;gap:8px;margin-top:8px;align-items:center}.todo-edit-inline select,.todo-edit-inline input[type=date]{padding:5px 10px;border:1px solid var(--theme-border-color, #ddd);border-radius:6px;font-size:var(--theme-text-mini, .76rem);background:var(--theme-bg-color, #fff);color:var(--theme-text-color, #333)}.todo-edit-inline .todo-action-btn{width:28px;height:28px;font-size:13px}.todo-importance-tag{color:#f59e0b;font-size:var(--theme-text-more-mini, .69rem);letter-spacing:-1px}.todo-add-importance{display:flex;align-items:center;gap:6px;font-size:var(--theme-secondary, .78rem)}.todo-add-importance input[type=range]{width:80px;accent-color:#f59e0b}.todo-add-importance .importance-label{color:#f59e0b;font-size:var(--theme-text-more-mini, .69rem);min-width:60px;letter-spacing:-1px}.todo-views{display:flex;gap:4px;background:var(--theme-border-color, #eee);border-radius:8px;padding:3px}.todo-view-btn{padding:5px 14px;border:none;border-radius:6px;background:transparent;color:var(--theme-text-secondary, rgba(0, 0, 0, .65));font-size:var(--theme-secondary, .78rem);cursor:pointer;transition:all .2s}.todo-view-btn.active{background:var(--theme-bg-color, #fff);color:var(--theme-text-color, #333);box-shadow:0 1px 3px #0000001a}.main-main.todo-wide-mode{width:90%!important;max-width:1400px!important}.todo-matrix-wrap{display:flex;flex-direction:column;gap:20px}.todo-chart-wrap{flex:1;min-width:0;border:1px solid var(--theme-border-color, #e8e8e8);border-radius:12px;overflow:hidden;background:var(--theme-bg-color, #fff)}.todo-chart-wrap canvas{display:block;width:100%}.todo-sidebar{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.todo-sidebar-group{background:var(--theme-bg-color, #fff);border:1px solid var(--theme-border-color, #e8e8e8);border-radius:10px;padding:14px 16px;min-height:80px}.todo-sidebar-group:nth-child(1){border-top:3px solid #ef4444}.todo-sidebar-group:nth-child(2){border-top:3px solid #f59e0b}.todo-sidebar-group:nth-child(3){border-top:3px solid #f59e0b}.todo-sidebar-group:nth-child(4){border-top:3px solid #10b981}.todo-sidebar-title{font-size:var(--theme-secondary, .78rem);font-weight:700;margin-bottom:8px;color:var(--theme-text-color, #333);display:flex;align-items:center;gap:6px}.todo-sidebar-count{background:var(--theme-border-color, #eee);color:var(--theme-text-secondary, rgba(0, 0, 0, .65));font-size:var(--theme-text-more-mini-2, .62rem);font-weight:600;padding:1px 7px;border-radius:10px}.todo-sidebar-item{display:flex;align-items:center;gap:6px;padding:4px 0;font-size:var(--theme-text-mini, .76rem);color:var(--theme-text-color, #333);line-height:1.4}.todo-sidebar-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}.todo-sidebar-dot.urgent{background:#ef4444}.todo-sidebar-dot.twodays{background:#f59e0b}.todo-sidebar-dot.thisweek{background:#3b82f6}.todo-sidebar-dot.anytime{background:#10b981}.todo-sidebar-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.todo-sidebar-due{font-size:var(--theme-text-more-mini-2, .62rem);color:var(--theme-text-secondary, rgba(0, 0, 0, .65));flex-shrink:0}.todo-sidebar-empty{font-size:var(--theme-text-more-mini, .69rem);color:var(--theme-text-secondary, rgba(0, 0, 0, .65));text-align:center;padding:4px 0}.dark .todo-add{border-color:#3a3a3a;box-shadow:0 1px 4px #0003}.dark .todo-add-input,.dark .todo-add-select,.dark .todo-add-date{background:var(--theme-front-color, #2a2a2b);border-color:#3a3a3a;color:var(--theme-text-color)}.dark .todo-item{border-color:#3a3a3a;background:var(--theme-front-color, #2a2a2b)}.dark .todo-item:hover{box-shadow:0 4px 16px #0000004d}.dark .todo-checkbox{border-color:#555}.dark .todo-title-input{background:var(--theme-front-color, #2a2a2b);color:var(--theme-text-color)}.dark .todo-filter-btn{border-color:#3a3a3a;color:var(--theme-text-secondary)}.dark .todo-progress{background:#3a3a3a}.dark .todo-edit-inline select,.dark .todo-edit-inline input[type=date]{background:var(--theme-front-color, #2a2a2b);border-color:#3a3a3a;color:var(--theme-text-color)}.dark .todo-action-btn{color:var(--theme-text-secondary)}.dark .todo-action-btn:hover{background:#3a3a3a}.dark .todo-priority-tag.high,.dark .todo-priority-tag.urgent{background:#dc262626}.dark .todo-priority-tag.twodays{background:#d9770626}.dark .todo-priority-tag.medium,.dark .todo-priority-tag.thisweek{background:#2563eb26}.dark .todo-priority-tag.low,.dark .todo-priority-tag.anytime{background:#05966926}.dark .todo-countdown.urgent,.dark .todo-countdown.overdue{background:#dc262626}.dark .todo-countdown.soon{background:#d9770626}.dark .todo-countdown.normal{background:#05966926}.dark .todo-views{background:#2a2a2b}.dark .todo-view-btn.active{background:#3a3a3a;color:var(--theme-text-color)}.dark .todo-chart-wrap,.dark .todo-sidebar-group{border-color:#3a3a3a;background:var(--theme-front-color, #2a2a2b)}.dark .todo-sidebar-count{background:#3a3a3a}.pomodoro-container{display:flex;flex-direction:row;gap:8px;margin-bottom:16px}.pomodoro-container:empty{margin-bottom:0}.pomodoro-bar{display:flex;align-items:center;gap:12px;padding:10px 18px;flex:1;min-width:0;border-radius:12px;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;box-shadow:0 4px 16px #ef44444d;transition:background .4s,box-shadow .4s;flex-wrap:wrap}.pomodoro-bar.break{background:linear-gradient(135deg,#6ee7b7,#34d399);box-shadow:0 4px 16px #6ee7b74d}.pomodoro-bar.longbreak{background:linear-gradient(135deg,#059669,#047857);box-shadow:0 4px 16px #0596694d}.pomodoro-bar.paused{opacity:.7}.pomodoro-icon{font-size:20px;flex-shrink:0}.pomodoro-task-name{flex:1;min-width:0;font-size:var(--theme-secondary, .78rem);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pomodoro-timer{font-size:1.5rem;font-weight:700;font-family:Courier New,monospace;letter-spacing:2px;flex-shrink:0}.pomodoro-phase{font-size:var(--theme-text-more-mini, .69rem);padding:2px 10px;border-radius:10px;background:#fff3;flex-shrink:0}.pomodoro-phase.hide-when-crowded{display:none}.pomodoro-actions{display:flex;gap:4px;flex-shrink:0}.pomodoro-btn{width:32px;height:32px;border:none;border-radius:50%;background:#fff3;color:#fff;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s}.pomodoro-btn:hover{background:#ffffff59}.pomodoro-count{font-size:var(--theme-secondary, .78rem);flex-shrink:0}.todo-item.pomodoro-active{border-color:#ef4444;box-shadow:0 0 0 2px #ef444433}.todo-pomodoro-count{color:#ef4444;font-size:var(--theme-text-more-mini, .69rem);font-weight:600;display:flex;align-items:center;gap:2px}.todo-action-btn.pomodoro{color:#ef4444}.todo-action-btn.pomodoro:hover{background:#fef2f2;color:#dc2626}.dark .todo-action-btn.pomodoro:hover{background:#dc262626}.todo-sidebar-pomodoro{color:#ef4444;font-size:var(--theme-text-more-mini-2, .62rem);flex-shrink:0}.todo-sidebar-play{width:18px;height:18px;border:none;border-radius:50%;background:transparent;color:#ef4444;font-size:10px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:background .2s;padding:0}.todo-sidebar-play:hover{background:#fef2f2}.dark .todo-sidebar-play:hover{background:#dc262626}@media(max-width:768px){.todo-container{padding:16px 12px}.todo-add{flex-direction:column;padding:14px}.todo-add-input{min-width:unset}.todo-toolbar{flex-direction:column;align-items:flex-start}.todo-matrix-wrap{flex-direction:column}.todo-sidebar{grid-template-columns:repeat(2,1fr);gap:8px}.main-main.todo-wide-mode{width:100%!important}.todo-body{padding:12px}.todo-actions{opacity:1}.todo-header h2{font-size:1.3rem}} diff --git a/common/todo/todo.min.js b/common/todo/todo.min.js new file mode 100644 index 0000000..3ba6e96 --- /dev/null +++ b/common/todo/todo.min.js @@ -0,0 +1,2 @@ +(function(){"use strict";const Ie="document_todos",ct=window.HOME+"/wp-admin/admin-ajax.php";let v=[],R="all",Y="chart",Q=null,E=null,x=null,o=null,L=[],I=null,ut={top:40,right:40,bottom:50,left:60};function _(){try{localStorage.setItem(Ie,JSON.stringify(v))}catch{}}function ft(){try{var e=localStorage.getItem(Ie);return e?JSON.parse(e):[]}catch{return[]}}function P(e,t){return new Promise(function(n,r){var l=new FormData;l.append("action",e),t&&Object.keys(t).forEach(function(i){l.append(i,t[i])}),fetch(ct,{method:"POST",body:l,credentials:"same-origin"}).then(function(i){return i.json()}).then(function(i){i.success?n(i.data):r(i.data)}).catch(r)})}function vt(){P("todo_list").then(function(e){v=e,_(),y()}).catch(function(){})}function k(e){if(!e)return null;var t=new Date;t.setHours(0,0,0,0);var n=new Date(e);return n.setHours(0,0,0,0),Math.ceil((n-t)/864e5)}function _e(e){if(!e)return"";var t=new Date(e);return t.getMonth()+1+"\u6708"+t.getDate()+"\u65E5"}function mt(e){if(!e.due_date||e.completed==1)return"";var t=k(e.due_date);if(t===null)return"";var n,r;return t<0?(n="overdue",r="\u5DF2\u8FC7\u671F"+Math.abs(t)+"\u5929"):t===0?(n="urgent",r="\u4ECA\u5929\u622A\u6B62"):t===1?(n="urgent",r="\u660E\u5929\u622A\u6B62"):t<=3?(n="urgent",r="\u5269\u4F59"+t+"\u5929"):t<=14?(n="soon",r="\u5269\u4F59"+t+"\u5929"):(n="normal",r="\u5269\u4F59"+t+"\u5929"),''+r+""}function Qt(e){if(!e.due_date||e.completed==1)return!1;var t=k(e.due_date);return t!==null&&t<0}var N={urgent:{label:"\u7D27\u6025",css:"urgent",level:4},twodays:{label:"\u8FD9\u4E24\u5929",css:"twodays",level:3},thisweek:{label:"\u8FD9\u5468\u5904\u7406",css:"thisweek",level:2},anytime:{label:"\u968F\u65F6\u53EF\u4EE5",css:"anytime",level:1},high:{label:"\u7D27\u6025",css:"urgent",level:4},medium:{label:"\u8FD9\u5468\u5904\u7406",css:"thisweek",level:2},low:{label:"\u968F\u65F6\u53EF\u4EE5",css:"anytime",level:1}};function Pe(e){return(N[e]||N.thisweek).label}function ke(e){return(N[e]||N.thisweek).css}function le(e){var t=e.priority||"thisweek";if(!e.due_date||e.completed==1)return t;var n=k(e.due_date);if(n===null)return t;var r=(N[t]||N.thisweek).level;return n<=0&&r<4?"urgent":n<=3&&r<3?"twodays":n<=14&&r<2?"thisweek":t}function Z(e){e=parseInt(e)||3;for(var t="",n=0;n<5;n++)t+=n0?Math.round(n/t*100):0;document.getElementById("todo-stats").innerHTML="\u5171 "+t+" \u9879\u5F85\u5B8C\u6210 "+(t-n)+"\u5DF2\u5B8C\u6210 "+n+""+r+"%";var l=document.getElementById("todo-progress-bar");l&&(l.style.width=r+"%"),document.querySelectorAll(".todo-filter-btn").forEach(function(i){i.classList.toggle("active",i.dataset.filter===R)}),Y==="list"?It(e):(ee(e),St(e))}function St(e){var t=document.getElementById("todo-sidebar");if(t){var n=3.2,r={do_now:{label:"🔴 \u7ACB\u5373\u505A",items:[]},do_fast:{label:"🟠 \u5FEB\u901F\u505A",items:[]},plan:{label:"🟡 \u8BA1\u5212\u505A",items:[]},general:{label:"🟢 \u4E00\u822C\u4E8B\u52A1",items:[]}};e.forEach(function(i){if(i.completed!=1){var d=k(i.due_date),a=Me(i),s=d!==null&&d<=3.2,c=d===null?21:Math.max(-3,Math.min(21,d)),g=c>14?3+(c-14)/7*.5:n;s&&a>=n?r.do_now.items.push(i):s&&a=g?r.plan.items.push(i):r.general.items.push(i)}});var l="";["do_now","do_fast","plan","general"].forEach(function(i){var d=r[i];l+='
    ',l+='
    '+d.label+' '+d.items.length+"
    ",d.items.length===0?l+='
    - \u65E0 -
    ':d.items.forEach(function(a){var s=le(a),c=ke(s);if(l+='
    ',l+='',l+='',l+=''+re(a.title)+"",a.due_date){var g=k(a.due_date),m=g<0?"\u8FC7\u671F"+Math.abs(g)+"\u5929":g===0?"\u4ECA\u5929":g+"\u5929";l+=''+m+""}var p=Be(a.id),f=Re(a.id);if(p>0||f>0){var w=[];p>0&&w.push("🍅"+p),f>0&&w.push(Ne(f)),l+=''+w.join(" · ")+""}l+="
    "}),l+="
    "}),t.innerHTML=l}}function It(e){var t=document.getElementById("todo-list");if(e.length===0){t.innerHTML='
    📋
    '+(R==="all"?"\u6682\u65E0\u5F85\u529E\u4E8B\u9879\uFF0C\u6DFB\u52A0\u4E00\u4E2A\u5427":R==="active"?"\u6240\u6709\u4EFB\u52A1\u90FD\u5B8C\u6210\u4E86\uFF01":"\u8FD8\u6CA1\u6709\u5DF2\u5B8C\u6210\u7684\u5F85\u529E")+"
    ";return}var n="";e.forEach(function(r){var l=Q==r.id,i=le(r),d=ke(i),a=i!==(r.priority||"thisweek")&&r.completed!=1;if(n+='
    ',n+='
    ',n+='
    ',n+='
    ',n+='
    ',l){n+='`,n+='
    ',n+='",n+='",n+='',n+='',n+='',n+="
    "}else n+='
    '+re(r.title)+"
    ",n+='
    ',n+=''+Pe(i),a&&(n+=" \u2191"),n+="",n+=''+Z(r.importance)+"",n+=Rt(r.id),r.due_date&&(n+='📅'+_e(r.due_date)+"",n+=mt(r)),n+="
    ";n+="
    ",l||(n+='
    ',r.completed!=1&&(n+=''),n+='',n+='',n+="
    "),n+="
    "}),t.innerHTML=n,t.querySelectorAll(".todo-item").forEach(function(r){r.addEventListener("dragstart",wt),r.addEventListener("dragover",Tt),r.addEventListener("dragend",Et)})}function ee(e){var t=document.getElementById("todo-chart-wrap");if(x=document.getElementById("todo-chart"),!x)return;o=x.getContext("2d");var n=t.getBoundingClientRect(),r=window.devicePixelRatio||1,l=n.width,i=Math.max(400,Math.min(l*.65,520));x.width=l*r,x.height=i*r,x.style.width=l+"px",x.style.height=i+"px",o.scale(r,r);var d=document.documentElement.classList.contains("dark")||document.body.classList.contains("dark"),a=ut,s=l-a.left-a.right,c=i-a.top-a.bottom;o.clearRect(0,0,l,i);var g=21,m=3,p=g+m,f=a.left+(3.2+m)/p*s,w=a.left+(14+m)/p*s,A=3.2,C=(A-1)/4,J=.08,M=a.top+c*(1-J)-C*c*(1-2*J);function Ke(u){return a.left+(u+m)/p*s}function se(u){return a.top+c*(1-J)-(u-1)/4*c*(1-2*J)}var ce=Ke(14),ue=se(3),je=g,Nt=Math.min(5,3+(je-14)/7*.5),$t=Ke(je),fe=se(Nt),W=a.left,U=a.left+s,D=a.top,V=a.top+c;o.fillStyle=d?"rgba(239,68,68,0.35)":"rgba(239,68,68,0.25)",o.fillRect(W,D,f-W,M-D),o.fillStyle=d?"rgba(234,179,8,0.30)":"rgba(234,179,8,0.22)",o.fillRect(W,M,f-W,V-M),o.fillStyle=d?"rgba(245,158,11,0.28)":"rgba(245,158,11,0.18)",o.beginPath(),o.moveTo(f,D),o.lineTo(U,D),o.lineTo(U,fe),o.lineTo(ce,ue),o.lineTo(f,M),o.closePath(),o.fill(),o.fillStyle=d?"rgba(16,185,129,0.25)":"rgba(16,185,129,0.16)",o.beginPath(),o.moveTo(f,M),o.lineTo(ce,ue),o.lineTo(U,fe),o.lineTo(U,V),o.lineTo(f,V),o.closePath(),o.fill(),o.font="bold 16px sans-serif",o.textAlign="center";var ze=(W+f)/2,Qe=(D+M)/2;o.fillStyle=d?"#f87171":"#dc2626",o.fillText("\u7D27\u6025\u4E14\u91CD\u8981",ze,Qe-8),o.fillText("\u7ACB\u5373\u505A!",ze,Qe+12);var ve=(f+U)/2,Ze=(ve-a.left)/s*p-m,Xt=Ze>14?3+(Ze-14)/7*.5:3.2,$e=se(Xt),qt=(D+$e)/2;o.fillStyle=d?"#fbbf24":"#d97706",o.fillText("\u8BA1\u5212\u505A",ve,qt);var Ft=(W+f)/2,Ht=(M+V)/2;o.fillStyle=d?"#facc15":"#b45309",o.fillText("\u5FEB\u901F\u505A",Ft,Ht);var Jt=ve,Wt=($e+V)/2;o.fillStyle=d?"#34d399":"#047857",o.fillText("\u4E00\u822C\u4E8B\u52A1",Jt,Wt),o.strokeStyle=d?"rgba(255,255,255,0.2)":"rgba(0,0,0,0.15)",o.setLineDash([6,4]),o.lineWidth=1.5,o.beginPath(),o.moveTo(f,D),o.lineTo(f,V),o.stroke(),o.beginPath(),o.moveTo(f,M),o.lineTo(ce,ue),o.lineTo(U,fe),o.stroke(),o.setLineDash([]);var me=a.left+(0+m)/p*s;o.strokeStyle=d?"rgba(239,68,68,0.6)":"rgba(220,38,38,0.5)",o.setLineDash([4,3]),o.lineWidth=1.5,o.beginPath(),o.moveTo(me,a.top),o.lineTo(me,a.top+c),o.stroke(),o.setLineDash([]),o.fillStyle=d?"rgba(239,68,68,0.8)":"rgba(220,38,38,0.65)",o.font="bold 13px sans-serif",o.textAlign="center",o.fillText("TODAY",me,a.top-6);var Ut=d?"rgba(255,255,255,0.5)":"rgba(0,0,0,0.35)";o.strokeStyle=Ut,o.lineWidth=1.5,o.beginPath(),o.moveTo(a.left,a.top),o.lineTo(a.left,a.top+c),o.lineTo(a.left+s,a.top+c),o.stroke(),o.lineWidth=1;var j=d?"rgba(255,255,255,0.85)":"rgba(0,0,0,0.7)";o.fillStyle=j,o.font="14px sans-serif",o.textAlign="center";for(var pe=[-2,0,1,2,3,7,14,21],Vt=["\u8FC7\u671F","\u4ECA\u5929","\u660E\u5929","\u540E\u5929","3\u5929","1\u5468","2\u5468","3\u5468"],T=0;Tg?b=g:b=O;var B=Me(u),Te=(b+m)/p,S=.08,z=(B-1)/4,Ee=a.left+Te*s,Se=a.top+c*(1-S)-z*c*(1-2*S);L.push({x:b,y:B,screenX:Ee,screenY:Se,todo:u})});for(var rt=22,T=0;T14?3+(u.x-14)/7*.5:3.2,S;if(u.x<=3.2&&u.y>=3.2?S="rgba(220,38,38,"+B+")":u.x<=3.2&&u.y<3.2?S="rgba(217,119,6,"+B+")":u.y>=Te?S="rgba(245,158,11,"+B+")":S="rgba(16,185,129,"+B+")",o.shadowColor="rgba(0,0,0,0.15)",o.shadowBlur=4,o.shadowOffsetY=2,o.beginPath(),o.arc(u.screenX,u.screenY,b,0,Math.PI*2),o.fillStyle=S,o.fill(),o.shadowColor="transparent",o.strokeStyle=d?"rgba(255,255,255,0.25)":"rgba(0,0,0,0.2)",o.lineWidth=1.5,o.stroke(),O&&(o.beginPath(),o.moveTo(u.screenX-b,u.screenY),o.lineTo(u.screenX+b,u.screenY),o.strokeStyle=d?"rgba(255,255,255,0.6)":"rgba(0,0,0,0.5)",o.lineWidth=2,o.stroke()),I&&I.todo.id===u.todo.id&&(o.beginPath(),o.arc(u.screenX,u.screenY,b+5,0,Math.PI*2),o.strokeStyle=S,o.lineWidth=2.5,o.stroke()),Xe(u.todo.id)){var z=(Math.sin(Date.now()/400)+1)/2,Ee=b+6+z*8,Se=.15+z*.35;o.beginPath(),o.arc(u.screenX,u.screenY,Ee,0,Math.PI*2),o.strokeStyle="rgba(239,68,68,"+Se+")",o.lineWidth=2.5,o.stroke(),o.beginPath(),o.arc(u.screenX,u.screenY,b+3,0,Math.PI*2),o.strokeStyle="rgba(239,68,68,"+(.4+z*.3)+")",o.lineWidth=2,o.stroke()}}),I&&_t(I,l,i,d)}function _t(e,t,n,r){var l=e.todo,i=le(l),d=[Yt(l.title)];if(d.push("\u7D27\u6025\u5EA6: "+Pe(i)),d.push("\u91CD\u8981\u5EA6: "+Z(l.importance)),l.due_date){var a=k(l.due_date),s=_e(l.due_date);a<0?s+=" (\u5DF2\u8FC7\u671F"+Math.abs(a)+"\u5929)":a===0?s+=" (\u4ECA\u5929)":s+=" (\u5269\u4F59"+a+"\u5929)",d.push("\u622A\u6B62: "+s)}else d.push("\u622A\u6B62: \u672A\u8BBE\u5B9A");l.completed==1&&d.push("\u2713 \u5DF2\u5B8C\u6210"),o.font="12px sans-serif";var c=0;d.forEach(function(A){var C=o.measureText(A).width;C>c&&(c=C)});var g=c+24,m=d.length*22+16,p=e.screenX+16,f=e.screenY-m/2;p+g>t-10&&(p=e.screenX-g-16),f<5&&(f=5),f+m>n-5&&(f=n-m-5),o.shadowColor="rgba(0,0,0,0.15)",o.shadowBlur=12,o.shadowOffsetY=4,o.fillStyle=r?"rgba(30,30,30,0.97)":"rgba(255,255,255,0.98)",o.strokeStyle=r?"rgba(255,255,255,0.2)":"rgba(0,0,0,0.12)",o.lineWidth=1,Pt(o,p,f,g,m,10),o.fill(),o.stroke(),o.shadowColor="transparent",o.fillStyle=r?"rgba(255,255,255,0.95)":"#222",o.textAlign="left",o.font="bold 13px sans-serif",o.fillText(d[0],p+12,f+22),o.font="11px sans-serif",o.fillStyle=r?"rgba(255,255,255,0.7)":"#555";for(var w=1;w0?t+"h"+n+"m":t+"h"}function ne(e){return h.find(function(t){return t.todoId==e})}function Xe(e){return!!ne(e)}function oe(){var e=h.map(function(t){return{todoId:t.todoId,phase:t.phase,count:t.count,paused:t.paused,endTime:t.paused?null:Date.now()+t.remaining*1e3,remaining:t.remaining}});try{h.length===0?localStorage.removeItem(de):localStorage.setItem(de,JSON.stringify(e)),localStorage.setItem(Ce,JSON.stringify(q)),localStorage.setItem("pomodoro_minutes",JSON.stringify(F))}catch{}Lt()}var qe=null;function Lt(){clearTimeout(qe),qe=setTimeout(function(){var e=h.map(function(t){return{todoId:t.todoId,phase:t.phase,count:t.count,paused:t.paused,endTime:t.paused?null:Date.now()+t.remaining*1e3,remaining:t.remaining}});P("pomodoro_save",{state:JSON.stringify(e),counts:JSON.stringify(q),minutes:JSON.stringify(F)}).catch(function(){})},500)}function At(){P("pomodoro_get").then(function(e){Fe(e.state||[],e.counts||{},e.minutes||{})}).catch(function(){try{var e=JSON.parse(localStorage.getItem(de)),t=JSON.parse(localStorage.getItem(Ce)||"{}"),n=JSON.parse(localStorage.getItem("pomodoro_minutes")||"{}");Fe(Array.isArray(e)?e:[],t||{},n||{})}catch{}})}function Fe(e,t,n){q=t||{},F=n||{},!(!Array.isArray(e)||e.length===0)&&(e.forEach(function(r){var l=v.find(function(d){return d.id==r.todoId});if(l&&!ne(r.todoId)){var i={todoId:r.todoId,phase:r.phase,count:r.count||0,paused:r.paused||!1,remaining:0,timer:null};r.paused?i.remaining=r.remaining||0:i.remaining=Math.max(0,Math.round((r.endTime-Date.now())/1e3)),h.push(i),i.timer=setInterval(function(){He(i)},1e3),i.remaining<=0&&!i.paused&&Je(i)}}),h.length>0&&(X=document.title,H(),Ue()))}function Ct(e){if(ne(e)){alert("\u8FD9\u4E2A\u4EFB\u52A1\u5DF2\u7ECF\u5728\u8BA1\u65F6\u4E86\uFF01");return}if(h.length>=De){alert("\u6700\u591A\u540C\u65F6\u4E13\u6CE8"+De+`\u4EF6\u4E8B\u60C5\uFF01 +\u5B8C\u6210\u6216\u505C\u6B62\u4E00\u4E2A\u518D\u5F00\u59CB\u65B0\u7684\u5427\u3002`);return}X||(X=document.title);var t=v.find(function(r){return r.id==e}),n={todoId:e,phase:"focus",remaining:te,paused:!1,count:0,timer:null,phaseStartedAt:Date.now()};h.push(n),n.timer=setInterval(function(){He(n)},1e3),H(),oe(),Ue(),y(),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission()}function He(e){e.paused||(e.remaining--,e.remaining<=0&&Je(e),H(),oe())}function Je(e){var t=v.find(function(r){return r.id==e.todoId}),n=t?t.title:"";e.phase==="focus"?(e.count++,xt(e.todoId),Ye(e.todoId,te/60),y(),e.count%4===0?(e.phase="longbreak",e.remaining=Ot):(e.phase="break",e.remaining=Mt),We("\u300C"+n+"\u300D\u4E13\u6CE8\u5B8C\u6210! \u4F11\u606F\u4E00\u4E0B")):(e.phase="focus",e.remaining=te,We("\u300C"+n+"\u300D\u4F11\u606F\u7ED3\u675F! \u7EE7\u7EED\u4E13\u6CE8")),H()}function Dt(e){var t=ne(e);t&&(t.paused=!t.paused,t.paused&&(document.title=X),H(),oe())}function Bt(e){var t=h.findIndex(function(i){return i.todoId==e});if(t!==-1){var n=h[t];if(n.phase==="focus"){var r=te,l=r-n.remaining;l>0&&Ye(e,l/60)}clearInterval(n.timer),h.splice(t,1),h.length===0&&(Ve(),document.title=X||document.title),H(),oe(),y()}}function H(){var e=document.getElementById("pomodoro-container");if(e){if(h.length===0){e.innerHTML="";return}var t="";h.forEach(function(a){var s=v.find(function(J){return J.id==a.todoId}),c=Math.floor(a.remaining/60),g=a.remaining%60,m=(c<10?"0":"")+c+":"+(g<10?"0":"")+g,p=a.phase==="focus"?"\u4E13\u6CE8\u4E2D":a.phase==="break"?"\u77ED\u4F11\u606F":"\u957F\u4F11\u606F",f=a.phase==="focus",w="pomodoro-bar"+(a.phase==="longbreak"?" longbreak":f?"":" break")+(a.paused?" paused":"");t+='
    ',t+='🍅',t+=''+re(s?s.title:"")+"",t+=''+m+"",t+=''+p+"",t+='
    ',t+='",t+='',t+="
    ";for(var A="",C=0;C'+A+""),t+="
    "}),e.innerHTML=t;var n=h.find(function(a){return!a.paused});if(n){var r=Math.floor(n.remaining/60),l=n.remaining%60,i=(r<10?"0":"")+r+":"+(l<10?"0":"")+l,d=n.phase==="focus"?"\u4E13\u6CE8\u4E2D":"\u4F11\u606F\u4E2D";document.title=i+" "+d+" - \u5F85\u529E\u4E8B\u9879"}else document.title=X||"\u5F85\u529E\u4E8B\u9879"}}function We(e){"Notification"in window&&Notification.permission==="granted"&&new Notification("\u756A\u8304\u949F",{body:e});try{var t=new(window.AudioContext||window.webkitAudioContext),n=t.createOscillator(),r=t.createGain();n.connect(r),r.connect(t.destination),n.frequency.value=880,n.type="sine",r.gain.setValueAtTime(.3,t.currentTime),r.gain.exponentialRampToValueAtTime(.001,t.currentTime+.5),n.start(t.currentTime),n.stop(t.currentTime+.5)}catch{}}function Rt(e){var t=Be(e),n=Re(e);if(t===0&&n===0)return"";var r=[];return t>0&&r.push("🍅"+(t>1?"×"+t:"")),n>0&&r.push(Ne(n)),''+r.join(" · ")+""}function Ue(){Ve();function e(){Y==="chart"&&h.length>0&&ee($()),K=requestAnimationFrame(e)}K=requestAnimationFrame(e)}function Ve(){K&&(cancelAnimationFrame(K),K=null)}function Zt(){}function re(e){var t=document.createElement("div");return t.textContent=e,t.innerHTML}function Yt(e){return e.replace(/&/g,"&").replace(//g,">")}function Ge(){v=ft();var e=document.querySelector(".main-main");e&&e.classList.add("todo-wide-mode"),y(),vt(),document.getElementById("todo-add-btn").addEventListener("click",Oe),document.getElementById("todo-input").addEventListener("keydown",function(l){l.key==="Enter"&&Oe()}),document.querySelectorAll(".todo-filter-btn").forEach(function(l){l.addEventListener("click",function(){bt(this.dataset.filter)})}),document.querySelectorAll(".todo-view-btn").forEach(function(l){l.addEventListener("click",function(){Le(this.dataset.view)})}),kt(),At();var t;window.addEventListener("resize",function(){clearTimeout(t),t=setTimeout(function(){Y==="chart"&&y()},200)});var n=document.getElementById("todo-importance"),r=document.getElementById("todo-importance-label");n&&r&&n.addEventListener("input",function(){r.textContent=Z(this.value)})}window._todo={toggle:pt,deleteTodo:gt,startEdit:xe,saveEdit:ht,cancelEdit:yt,startPomodoro:Ct,pauseSlot:Dt,stopSlot:Bt},document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Ge):Ge()})(); diff --git a/common/widget/widget.min.js b/common/widget/widget.min.js new file mode 100644 index 0000000..0861062 --- /dev/null +++ b/common/widget/widget.min.js @@ -0,0 +1 @@ +let vm=!1;jQuery(function(){const e=jQuery;let n,i;const s=()=>{if(!n)return;const t=i.state().get("selection").toJSON()[0].url;n.val(t),n.trigger("change")};i=wp.media({title:"\u9009\u62E9\u6216\u4E0A\u4F20\u56FE\u7247",button:{text:"\u9009\u62E9"},editable:!0,allowLocalEdits:!0,displaySettings:!0,displayUserSettings:!0,multiple:!1}),i.on("select",s),e("body").on("click",function(){e(".select-datetime").each(function(){let t=e(this).data("count"),l=e("#select-datetime-"+t);l&&l.datepicker({altFormat:"yy-mm-dd"})}),e(".select-media").on("click",function(){i&&(n=e(this).parent().find("input"),i.open())}),!(e(".widget-liquid-right #jq-tabs").length==0||vm)&&(vm=!0,e(".widget-liquid-right #jq-tabs").tabs())});function a(t){var l=new CustomEvent(t,{detail:this});window.dispatchEvent(l)}var d=window.XMLHttpRequest;function r(){var t=new d;return t.addEventListener("abort",function(){a.call(this,"ajaxAbort")},!1),t.addEventListener("error",function(){a.call(this,"ajaxError")},!1),t.addEventListener("load",function(){a.call(this,"ajaxLoad")},!1),t.addEventListener("loadstart",function(){a.call(this,"ajaxLoadStart")},!1),t.addEventListener("progress",function(){a.call(this,"ajaxProgress")},!1),t.addEventListener("timeout",function(){a.call(this,"ajaxTimeout")},!1),t.addEventListener("loadend",function(){a.call(this,"ajaxLoadEnd"),e(".widget-liquid-right #jq-tabs").tabs()},!1),t.addEventListener("readystatechange",function(){a.call(this,"ajaxReadyStateChange")},!1),t}window.XMLHttpRequest=r}); diff --git a/diagnose-jquery.php b/diagnose-jquery.php new file mode 100644 index 0000000..944580a --- /dev/null +++ b/diagnose-jquery.php @@ -0,0 +1,267 @@ + + + + + + + jQuery 冲突诊断工具 + + + +
    +

    🔍 jQuery 冲突诊断工具

    + +
    + ⚠️ 重要提示:诊断完成后,请务必删除此文件(diagnose-jquery.php),避免安全风险。 +
    + +

    1. 检查已加载的脚本

    +
    +

    正在扫描页面中加载的脚本...

    +
    + +

    2. jQuery 版本检测

    +
    +

    正在检测 jQuery 版本...

    +
    + +

    3. 可疑脚本来源

    +
    +

    正在分析可疑脚本...

    +
    + +

    4. 修复建议

    +
    +

    如果发现 jQuery 1.6.2:

    +
      +
    1. 检查插件:在 WordPress 后台 → 插件,逐个禁用插件,刷新页面查看是否还有 jQuery 1.6.2
    2. +
    3. 检查主题:搜索主题文件中是否有 jquery-1.6.2jquery.*1\.6
    4. +
    5. 检查 functions.php:确保使用 wp_enqueue_script('jquery') 而不是手动引入
    6. +
    7. 使用 WordPress 钩子:如果必须移除某个脚本,可以使用: +
      function remove_old_jquery() {
      +    wp_deregister_script('jquery');
      +    wp_deregister_script('jquery-core');
      +    // 然后重新注册正确的版本
      +}
      +add_action('wp_enqueue_scripts', 'remove_old_jquery', 1);
      +
    8. +
    +
    +
    + + + + diff --git a/functions.php b/functions.php index a65a4bd..0b31523 100644 --- a/functions.php +++ b/functions.php @@ -1,5 +1,59 @@ ]*src=["\'][^"\']*jquery[^"\']*1\.(6|5|4|3|2|1)[^"\']*["\'][^>]*><\/script>/i', + '', + $buffer + ); + return $buffer; + }); +} +// 注意:这个函数需要在输出前执行,但可能会影响性能,谨慎使用 + +/** + * 主题脚本加载函数 + * 注意:如果主题已有 nicen_theme_load_source() 函数,此函数可能会冲突 + * 如果发现冲突,请注释掉此函数,使用主题原有的加载方式 + */ +function mytheme_enqueue_scripts() { + // jQuery - 使用 WordPress 自带的 jQuery,确保版本正确 + wp_enqueue_script('jquery'); + + // Swiper + wp_enqueue_script('swiper', 'https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.js', array(), null, true); + wp_enqueue_style('swiper-style', 'https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.css'); + + // 你的主题脚本 - 修复路径:从 /js/main.js 改为 /common/main.js + // 注意:主题已有 /common/main.js 的加载(在 nicen_theme_load_source 中) + // 如果出现重复加载,请注释掉下面这行 + // wp_enqueue_script('theme-main', get_template_directory_uri() . '/common/main.js', array('jquery', 'swiper'), filemtime(get_template_directory() . '/common/main.js'), true); +} +// 如果主题已有加载函数,请注释掉下面这行以避免冲突 +// add_action('wp_enqueue_scripts', 'mytheme_enqueue_scripts'); @@ -19,4 +73,5 @@ include_once get_template_directory() . '/include/functions/smtp.php';//加载smtp include_once get_template_directory() . '/include/class/CommentsWalker.php';//自定义评论输出 include_once get_template_directory() . '/include/functions/initialize.php';//覆盖wordpress默认设置 +include_once get_template_directory() . '/include/functions/toys.php';//小玩具导航管理 diff --git a/include/admin/render.php b/include/admin/render.php index 2e179db..7b8dd99 100644 --- a/include/admin/render.php +++ b/include/admin/render.php @@ -24,7 +24,7 @@ function nicen_theme_getAllCat() if (count($terms) > 0) { foreach ($terms as $term) { $cat[] = [ - 'label' => '分类:' . $term->name, + 'label' => 'Category: ' . $term->name, 'value' => $term->term_id ]; } diff --git a/include/clipboard/api.php b/include/clipboard/api.php new file mode 100644 index 0000000..9fdf630 --- /dev/null +++ b/include/clipboard/api.php @@ -0,0 +1,282 @@ +prefix . 'document_clipboard'; + + $content = wp_unslash( $_POST['content'] ?? '' ); + if ( empty( trim( $content ) ) ) { + wp_send_json_error( 'Empty content' ); + } + + // Limit text to 1MB + if ( strlen( $content ) > 1048576 ) { + wp_send_json_error( 'Text too large (max 1MB)' ); + } + + // Deduplicate: skip if same content was saved in last 3 seconds + $user_id = get_current_user_id(); + $recent = $wpdb->get_var( $wpdb->prepare( + "SELECT id FROM $table WHERE user_id = %d AND type = 'text' AND content = %s AND created_at > DATE_SUB(NOW(), INTERVAL 3 SECOND) LIMIT 1", + $user_id, $content + )); + if ( $recent ) { + wp_send_json_success( [ 'deduplicated' => true ] ); + } + + $wpdb->insert( $table, [ + 'user_id' => $user_id, + 'type' => 'text', + 'content' => $content, + ], [ '%d', '%s', '%s' ] ); + + // Keep only last 200 entries per user + document_clipboard_cleanup( $user_id ); + + $row = $wpdb->get_row( $wpdb->prepare( + "SELECT * FROM $table WHERE id = %d", $wpdb->insert_id + ), ARRAY_A ); + + wp_send_json_success( $row ); +} +add_action( 'wp_ajax_clipboard_save', 'document_clipboard_save' ); + +/** + * Upload file (<10MB) + */ +function document_clipboard_upload() { + document_clipboard_check_permission(); + global $wpdb; + $table = $wpdb->prefix . 'document_clipboard'; + + if ( empty( $_FILES['file'] ) ) { + wp_send_json_error( 'No file uploaded' ); + } + + $file = $_FILES['file']; + $max_size = 10 * 1024 * 1024; // 10MB + + if ( $file['error'] !== UPLOAD_ERR_OK ) { + wp_send_json_error( 'Upload error: ' . $file['error'] ); + } + if ( $file['size'] > $max_size ) { + wp_send_json_error( 'File too large (max 10MB)' ); + } + + $user_id = get_current_user_id(); + $upload_dir = wp_upload_dir(); + $clipboard_dir = $upload_dir['basedir'] . '/clipboard/' . $user_id; + + if ( ! file_exists( $clipboard_dir ) ) { + wp_mkdir_p( $clipboard_dir ); + // Protect directory + file_put_contents( $clipboard_dir . '/.htaccess', "Options -Indexes\n" ); + } + + // Sanitize filename and make unique + $filename = sanitize_file_name( $file['name'] ); + $ext = pathinfo( $filename, PATHINFO_EXTENSION ); + $base = pathinfo( $filename, PATHINFO_FILENAME ); + $dest = $clipboard_dir . '/' . $filename; + $i = 1; + while ( file_exists( $dest ) ) { + $dest = $clipboard_dir . '/' . $base . '-' . $i . '.' . $ext; + $i++; + } + + if ( ! move_uploaded_file( $file['tmp_name'], $dest ) ) { + wp_send_json_error( 'Failed to save file' ); + } + + // Store relative path + $relative_path = str_replace( $upload_dir['basedir'], '', $dest ); + + $wpdb->insert( $table, [ + 'user_id' => $user_id, + 'type' => 'file', + 'filename' => sanitize_file_name( basename( $dest ) ), + 'filepath' => $relative_path, + 'filesize' => $file['size'], + ], [ '%d', '%s', '%s', '%s', '%d' ] ); + + document_clipboard_cleanup( $user_id ); + + $row = $wpdb->get_row( $wpdb->prepare( + "SELECT * FROM $table WHERE id = %d", $wpdb->insert_id + ), ARRAY_A ); + + // Add download URL + $row['download_url'] = $upload_dir['baseurl'] . $relative_path; + + wp_send_json_success( $row ); +} +add_action( 'wp_ajax_clipboard_upload', 'document_clipboard_upload' ); + +/** + * List clipboard history for current user + */ +function document_clipboard_list() { + document_clipboard_check_permission(); + global $wpdb; + $table = $wpdb->prefix . 'document_clipboard'; + $user_id = get_current_user_id(); + + $page = max( 1, intval( $_POST['page'] ?? 1 ) ); + $per_page = 50; + $offset = ( $page - 1 ) * $per_page; + + $search = sanitize_text_field( $_POST['search'] ?? '' ); + + $where = $wpdb->prepare( "WHERE user_id = %d", $user_id ); + if ( $search ) { + $like = '%' . $wpdb->esc_like( $search ) . '%'; + $where .= $wpdb->prepare( " AND (content LIKE %s OR filename LIKE %s)", $like, $like ); + } + + $type_filter = sanitize_text_field( $_POST['type'] ?? '' ); + if ( $type_filter && in_array( $type_filter, [ 'text', 'file' ] ) ) { + $where .= $wpdb->prepare( " AND type = %s", $type_filter ); + } + + $total = $wpdb->get_var( "SELECT COUNT(*) FROM $table $where" ); + $rows = $wpdb->get_results( + "SELECT * FROM $table $where ORDER BY created_at DESC LIMIT $per_page OFFSET $offset", + ARRAY_A + ); + + // Add download URLs for files + $upload_dir = wp_upload_dir(); + foreach ( $rows as &$row ) { + if ( $row['type'] === 'file' && $row['filepath'] ) { + $row['download_url'] = $upload_dir['baseurl'] . $row['filepath']; + } + } + + wp_send_json_success( [ + 'items' => $rows ?: [], + 'total' => intval( $total ), + 'page' => $page, + 'per_page' => $per_page, + ] ); +} +add_action( 'wp_ajax_clipboard_list', 'document_clipboard_list' ); + +/** + * Delete a clipboard entry + */ +function document_clipboard_delete() { + document_clipboard_check_permission(); + global $wpdb; + $table = $wpdb->prefix . 'document_clipboard'; + $user_id = get_current_user_id(); + + $id = intval( $_POST['id'] ?? 0 ); + if ( ! $id ) { + wp_send_json_error( 'Invalid ID' ); + } + + // Only delete own entries + $row = $wpdb->get_row( $wpdb->prepare( + "SELECT * FROM $table WHERE id = %d AND user_id = %d", $id, $user_id + ), ARRAY_A ); + + if ( ! $row ) { + wp_send_json_error( 'Entry not found' ); + } + + // Delete file from disk if it's a file entry + if ( $row['type'] === 'file' && $row['filepath'] ) { + $upload_dir = wp_upload_dir(); + $full_path = $upload_dir['basedir'] . $row['filepath']; + if ( file_exists( $full_path ) ) { + unlink( $full_path ); + } + } + + $wpdb->delete( $table, [ 'id' => $id, 'user_id' => $user_id ], [ '%d', '%d' ] ); + wp_send_json_success( [ 'deleted' => $id ] ); +} +add_action( 'wp_ajax_clipboard_delete', 'document_clipboard_delete' ); + +/** + * Clear all clipboard entries for current user + */ +function document_clipboard_clear() { + document_clipboard_check_permission(); + global $wpdb; + $table = $wpdb->prefix . 'document_clipboard'; + $user_id = get_current_user_id(); + + // Delete all files + $files = $wpdb->get_results( $wpdb->prepare( + "SELECT filepath FROM $table WHERE user_id = %d AND type = 'file' AND filepath IS NOT NULL", + $user_id + ), ARRAY_A ); + + $upload_dir = wp_upload_dir(); + foreach ( $files as $f ) { + $full_path = $upload_dir['basedir'] . $f['filepath']; + if ( file_exists( $full_path ) ) { + unlink( $full_path ); + } + } + + $wpdb->delete( $table, [ 'user_id' => $user_id ], [ '%d' ] ); + wp_send_json_success( [ 'cleared' => true ] ); +} +add_action( 'wp_ajax_clipboard_clear', 'document_clipboard_clear' ); + +/** + * Keep only latest 200 entries per user, delete oldest + their files + */ +function document_clipboard_cleanup( $user_id ) { + global $wpdb; + $table = $wpdb->prefix . 'document_clipboard'; + + $count = $wpdb->get_var( $wpdb->prepare( + "SELECT COUNT(*) FROM $table WHERE user_id = %d", $user_id + )); + + if ( $count > 200 ) { + // Get IDs to delete (oldest beyond 200) + $to_delete = $wpdb->get_results( $wpdb->prepare( + "SELECT id, type, filepath FROM $table WHERE user_id = %d ORDER BY created_at DESC LIMIT %d, %d", + $user_id, 200, $count - 200 + ), ARRAY_A ); + + $upload_dir = wp_upload_dir(); + $ids = []; + foreach ( $to_delete as $row ) { + $ids[] = intval( $row['id'] ); + if ( $row['type'] === 'file' && $row['filepath'] ) { + $full_path = $upload_dir['basedir'] . $row['filepath']; + if ( file_exists( $full_path ) ) { + unlink( $full_path ); + } + } + } + + if ( $ids ) { + $ids_str = implode( ',', $ids ); + $wpdb->query( "DELETE FROM $table WHERE id IN ($ids_str)" ); + } + } +} diff --git a/include/clipboard/install.php b/include/clipboard/install.php new file mode 100644 index 0000000..dc2ea12 --- /dev/null +++ b/include/clipboard/install.php @@ -0,0 +1,43 @@ +prefix . 'document_clipboard'; + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE $table_name ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + user_id bigint(20) unsigned NOT NULL, + type varchar(10) NOT NULL DEFAULT 'text', + content longtext, + filename varchar(255) DEFAULT NULL, + filepath varchar(500) DEFAULT NULL, + filesize bigint(20) unsigned NOT NULL DEFAULT 0, + created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY idx_user_id (user_id), + KEY idx_created (created_at), + KEY idx_type (type) + ) $charset_collate;"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $sql ); +} + +/* Create table on theme activation */ +add_action( 'after_switch_theme', 'document_clipboard_create_table' ); + +/* Auto-create table if not exists */ +function document_clipboard_maybe_create_table() { + global $wpdb; + $table_name = $wpdb->prefix . 'document_clipboard'; + if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) !== $table_name ) { + document_clipboard_create_table(); + } +} +add_action( 'init', 'document_clipboard_maybe_create_table' ); diff --git a/include/config.php b/include/config.php index bfca84e..59680bb 100644 --- a/include/config.php +++ b/include/config.php @@ -1,7 +1,7 @@ [ 'template' => 'template/page/board.php', + ], + 'My Corner' => [ + 'template' => 'template/page/toys.php', + ], + '待办事项' => [ + 'template' => 'template/page/todo.php', + 'dependent' => [ + 'styles' => [ + '/common/todo/todo.css' + ], + 'scripts' => [ + '/common/todo/todo.js' + ] + ] + ], + 'Clipboard' => [ + 'template' => 'template/page/clipboard.php', + 'dependent' => [ + 'styles' => [ + '/common/clipboard/clipboard.css' + ], + 'scripts' => [ + '/common/clipboard/clipboard.js' + ] + ] + ], + 'Snake Game' => [ + 'template' => 'template/page/snake.php', + 'dependent' => [ + 'styles' => [ + '/common/snake/snake.css' + ], + 'scripts' => [ + '/common/snake/snake.js' + ] + ] + ], + 'Doom FPS' => [ + 'template' => 'template/page/doom.php', + 'dependent' => [ + 'styles' => [ + '/common/doom/doom.css' + ], + 'scripts' => [ + '/common/doom/doom.js' + ] + ] ] ]; \ No newline at end of file diff --git a/include/doom/api.php b/include/doom/api.php new file mode 100644 index 0000000..aec2203 --- /dev/null +++ b/include/doom/api.php @@ -0,0 +1,113 @@ +prefix . 'document_doom_scores'; + + $name = sanitize_text_field( $_POST['name'] ?? '' ); + $score = intval( $_POST['score'] ?? 0 ); + $kills = intval( $_POST['kills'] ?? 0 ); + $duration = intval( $_POST['duration'] ?? 0 ); + $token = sanitize_text_field( $_POST['token'] ?? '' ); + + $name = mb_substr( trim( $name ), 0, 20 ); + if ( empty( $name ) ) { + wp_send_json_error( 'Name is required' ); + } + if ( $score < 0 || $score > 99999 ) { + wp_send_json_error( 'Invalid score' ); + } + if ( $duration < 0 || $duration > 36000 ) { + wp_send_json_error( 'Invalid duration' ); + } + + // Anti-cheat token check + $expected = md5( $score . '_' . $duration . '_' . md5( document_doom_salt() ) ); + if ( $token !== $expected ) { + wp_send_json_error( 'Invalid token' ); + } + + // Rate limit: 1 submission per 5 seconds per IP + $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; + $transient_key = 'doom_rate_' . md5( $ip ); + if ( get_transient( $transient_key ) ) { + wp_send_json_error( 'Too fast, please wait' ); + } + set_transient( $transient_key, 1, 5 ); + + $wpdb->insert( $table, [ + 'player_name' => $name, + 'score' => $score, + 'kills' => $kills, + 'duration' => $duration, + ], [ '%s', '%d', '%d', '%d' ] ); + + $id = $wpdb->insert_id; + + $rank = $wpdb->get_var( $wpdb->prepare( + "SELECT COUNT(*) + 1 FROM $table WHERE score > %d", $score + ) ); + + document_doom_cleanup(); + + wp_send_json_success( [ + 'id' => $id, + 'rank' => intval( $rank ), + ] ); +} +add_action( 'wp_ajax_doom_submit', 'document_doom_submit' ); +add_action( 'wp_ajax_nopriv_doom_submit', 'document_doom_submit' ); + +/** + * Get leaderboard + */ +function document_doom_leaderboard() { + global $wpdb; + $table = $wpdb->prefix . 'document_doom_scores'; + + $type = sanitize_text_field( $_POST['type'] ?? 'alltime' ); + + $where = ''; + $limit = 20; + if ( $type === 'weekly' ) { + $where = "WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)"; + $limit = 10; + } + + $rows = $wpdb->get_results( + "SELECT player_name, score, kills, duration, created_at FROM $table $where ORDER BY score DESC, duration ASC LIMIT $limit", + ARRAY_A + ); + + wp_send_json_success( $rows ?: [] ); +} +add_action( 'wp_ajax_doom_leaderboard', 'document_doom_leaderboard' ); +add_action( 'wp_ajax_nopriv_doom_leaderboard', 'document_doom_leaderboard' ); + +/** + * Cleanup old and excess scores + */ +function document_doom_cleanup() { + global $wpdb; + $table = $wpdb->prefix . 'document_doom_scores'; + + $wpdb->query( "DELETE FROM $table WHERE created_at < DATE_SUB(NOW(), INTERVAL 3 WEEK)" ); + + $keep_ids = $wpdb->get_col( "SELECT id FROM $table ORDER BY score DESC, duration ASC LIMIT 20" ); + if ( ! empty( $keep_ids ) ) { + $ids_str = implode( ',', array_map( 'intval', $keep_ids ) ); + $wpdb->query( "DELETE FROM $table WHERE id NOT IN ($ids_str)" ); + } +} diff --git a/include/doom/install.php b/include/doom/install.php new file mode 100644 index 0000000..b93545f --- /dev/null +++ b/include/doom/install.php @@ -0,0 +1,37 @@ +prefix . 'document_doom_scores'; + $charset = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE $table ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + player_name VARCHAR(50) NOT NULL, + score INT(11) NOT NULL DEFAULT 0, + kills INT(11) NOT NULL DEFAULT 0, + duration INT(11) NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY idx_score (score DESC), + KEY idx_created (created_at) + ) $charset;"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $sql ); +} + +add_action( 'after_switch_theme', 'document_doom_create_table' ); + +add_action( 'init', function () { + global $wpdb; + $table = $wpdb->prefix . 'document_doom_scores'; + if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) { + document_doom_create_table(); + } +} ); diff --git a/include/functions/common.php b/include/functions/common.php index 9f723e3..994ca4a 100644 --- a/include/functions/common.php +++ b/include/functions/common.php @@ -1065,3 +1065,54 @@ function get_new_post_modified_time( $format ) { } } + + +/** + * 获取文章字数统计 + * @param int $postID 文章ID,默认为当前文章 + * @param string $type 统计类型:'words' 总词数(默认,中文字符数+英文单词数),'chars' 字符数 + * @return int 字数 + */ +function nicen_theme_getPostWordCount( $postID = null, $type = 'words' ) { + + if ( $postID === null ) { + global $post; + $postID = $post->ID; + } + + $content = get_post_field( 'post_content', $postID ); + + // 先去除链接标签(避免统计链接中的文本和URL) + $content = preg_replace( '/]*>.*?<\/a>/is', '', $content ); + + // 去除其他HTML标签和短代码 + $content = strip_tags( $content ); + $content = preg_replace( "/\[[\s\S]*?]/", "", $content ); + + // 去除URL(http://、https://、www. 开头的链接) + $content = preg_replace( '/https?:\/\/[^\s]+/i', '', $content ); + $content = preg_replace( '/www\.[^\s]+/i', '', $content ); + + // 去除邮箱地址 + $content = preg_replace( '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/', '', $content ); + + if ( $type === 'chars' ) { + // 字符数模式 + return mb_strlen( $content, 'UTF-8' ); + } else { + // 总词数模式(默认):中文字符数 + 英文单词数 + + // 统计中文字符数 + preg_match_all( '/[\x{4e00}-\x{9fa5}]/u', $content, $chinese_matches ); + $chinese_count = count( $chinese_matches[0] ); + + // 统计英文单词数 + $english_content = preg_replace( '/[\x{4e00}-\x{9fa5}]/u', ' ', $content ); // 将中文替换为空格 + $english_content = preg_replace( '/[^\w\s]/u', ' ', $english_content ); // 去除标点,保留字母数字和空格 + $english_content = preg_replace( '/\s+/', ' ', trim( $english_content ) ); // 规范化空格 + $english_words = !empty( trim( $english_content ) ) ? count( explode( ' ', $english_content ) ) : 0; + + // 总词数 = 中文字符数 + 英文单词数 + return $chinese_count + $english_words; + } +} diff --git a/include/functions/toys.php b/include/functions/toys.php new file mode 100644 index 0000000..3c1f7c6 --- /dev/null +++ b/include/functions/toys.php @@ -0,0 +1,83 @@ + sanitize_text_field( $toy['name'] ?? '' ), + 'icon' => sanitize_text_field( $toy['icon'] ?? '' ), + 'desc' => sanitize_text_field( $toy['desc'] ?? '' ), + 'url' => $url, + 'admin_only' => ! empty( $toy['admin_only'] ), + ]; +} diff --git a/include/snake/api.php b/include/snake/api.php new file mode 100644 index 0000000..b04a376 --- /dev/null +++ b/include/snake/api.php @@ -0,0 +1,127 @@ +prefix . 'document_snake_scores'; + + $name = sanitize_text_field( $_POST['name'] ?? '' ); + $score = intval( $_POST['score'] ?? 0 ); + $duration = intval( $_POST['duration'] ?? 0 ); + $token = sanitize_text_field( $_POST['token'] ?? '' ); + + // Validate name + $name = mb_substr( trim( $name ), 0, 20 ); + if ( empty( $name ) ) { + wp_send_json_error( 'Name is required' ); + } + + // Validate score + if ( $score < 0 || $score > 9999 ) { + wp_send_json_error( 'Invalid score' ); + } + if ( $duration < 0 || $duration > 36000 ) { + wp_send_json_error( 'Invalid duration' ); + } + + // Anti-cheat token check + $expected = md5( $score . '_' . $duration . '_' . md5( document_snake_salt() ) ); + if ( $token !== $expected ) { + wp_send_json_error( 'Invalid token' ); + } + + // Rate limit: 1 submission per 5 seconds per IP + $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; + $transient_key = 'snake_rate_' . md5( $ip ); + if ( get_transient( $transient_key ) ) { + wp_send_json_error( 'Too fast, please wait' ); + } + set_transient( $transient_key, 1, 5 ); + + $wpdb->insert( $table, [ + 'player_name' => $name, + 'score' => $score, + 'duration' => $duration, + ], [ '%s', '%d', '%d' ] ); + + $id = $wpdb->insert_id; + + // Get rank + $rank = $wpdb->get_var( $wpdb->prepare( + "SELECT COUNT(*) + 1 FROM $table WHERE score > %d", $score + ) ); + + // Cleanup: keep only top 20 all-time scores, discard records older than 3 weeks + document_snake_cleanup(); + + wp_send_json_success( [ + 'id' => $id, + 'rank' => intval( $rank ), + ] ); +} +add_action( 'wp_ajax_snake_submit', 'document_snake_submit' ); +add_action( 'wp_ajax_nopriv_snake_submit', 'document_snake_submit' ); + +/** + * Get leaderboard + */ +function document_snake_leaderboard() { + global $wpdb; + $table = $wpdb->prefix . 'document_snake_scores'; + + $type = sanitize_text_field( $_POST['type'] ?? 'alltime' ); + + $where = ''; + $limit = 20; + if ( $type === 'weekly' ) { + $where = "WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)"; + $limit = 10; + } + + $rows = $wpdb->get_results( + "SELECT player_name, score, duration, created_at FROM $table $where ORDER BY score DESC, duration ASC LIMIT $limit", + ARRAY_A + ); + + wp_send_json_success( $rows ?: [] ); +} +add_action( 'wp_ajax_snake_leaderboard', 'document_snake_leaderboard' ); +add_action( 'wp_ajax_nopriv_snake_leaderboard', 'document_snake_leaderboard' ); + +/** + * Cleanup old and excess scores + * - Keep only top 20 all-time scores + * - Discard all records older than 3 weeks + */ +function document_snake_cleanup() { + global $wpdb; + $table = $wpdb->prefix . 'document_snake_scores'; + + // Delete records older than 3 weeks + $wpdb->query( "DELETE FROM $table WHERE created_at < DATE_SUB(NOW(), INTERVAL 3 WEEK)" ); + + // Keep only top 20 all-time: delete everything ranked below 20 + $cutoff = $wpdb->get_var( "SELECT score FROM $table ORDER BY score DESC, duration ASC LIMIT 1 OFFSET 19" ); + if ( $cutoff !== null ) { + // Get the id of the 20th row to handle ties properly + $keep_ids = $wpdb->get_col( "SELECT id FROM $table ORDER BY score DESC, duration ASC LIMIT 20" ); + if ( ! empty( $keep_ids ) ) { + $ids_str = implode( ',', array_map( 'intval', $keep_ids ) ); + $wpdb->query( "DELETE FROM $table WHERE id NOT IN ($ids_str)" ); + } + } +} diff --git a/include/snake/install.php b/include/snake/install.php new file mode 100644 index 0000000..e02a5ae --- /dev/null +++ b/include/snake/install.php @@ -0,0 +1,37 @@ +prefix . 'document_snake_scores'; + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE $table_name ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + player_name varchar(50) NOT NULL, + score int(11) NOT NULL DEFAULT 0, + duration int(11) NOT NULL DEFAULT 0, + created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY idx_score (score), + KEY idx_created (created_at) + ) $charset_collate;"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $sql ); +} + +add_action( 'after_switch_theme', 'document_snake_create_table' ); + +function document_snake_maybe_create_table() { + global $wpdb; + $table_name = $wpdb->prefix . 'document_snake_scores'; + if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) !== $table_name ) { + document_snake_create_table(); + } +} +add_action( 'init', 'document_snake_maybe_create_table' ); diff --git a/include/themes/initialize.php b/include/themes/initialize.php index 02631bc..acdb7a3 100644 --- a/include/themes/initialize.php +++ b/include/themes/initialize.php @@ -151,6 +151,33 @@ function nicen_theme_fit_html_cat_mode( $content ) { } +/** + * 为文章内容中的图片统一添加原生懒加载(loading="lazy") + * + * - 已经包含 loading 属性的 标签不会被修改(例如 lightbox 短代码里已经加过) + * - 只对文章内容里的 HTML 生效(通过 the_content 输出) + */ +function nicen_theme_add_lazy_loading_to_images( $content ) { + + // 只处理包含 ]*>/i', function ( $matches ) { + $tag = $matches[0]; + + // 已经带有 loading 属性的,保持不变 + if ( stripos( $tag, ' loading=' ) !== false ) { + return $tag; + } + + // 在 prefix . 'document_todos'; + + $rows = $wpdb->get_results( + "SELECT * FROM $table ORDER BY sort_order ASC, created_at DESC", + ARRAY_A + ); + + wp_send_json_success( $rows ?: [] ); +} +add_action( 'wp_ajax_todo_list', 'document_todo_list' ); + +/** + * 创建待办 + */ +function document_todo_create() { + document_todo_check_permission(); + global $wpdb; + $table = $wpdb->prefix . 'document_todos'; + + $title = sanitize_text_field( $_POST['title'] ?? '' ); + $priority = sanitize_text_field( $_POST['priority'] ?? 'medium' ); + $due_date = sanitize_text_field( $_POST['due_date'] ?? '' ); + $importance = intval( $_POST['importance'] ?? 3 ); + + if ( empty( $title ) ) { + wp_send_json_error( '标题不能为空' ); + } + + if ( ! in_array( $priority, [ 'urgent', 'twodays', 'thisweek', 'anytime', 'high', 'medium', 'low' ] ) ) { + $priority = 'thisweek'; + } + + $importance = max( 1, min( 5, $importance ) ); + + $data = [ + 'title' => $title, + 'priority' => $priority, + 'importance' => $importance, + 'due_date' => $due_date ?: null, + ]; + $format = [ '%s', '%s', '%d', '%s' ]; + + $wpdb->insert( $table, $data, $format ); + $id = $wpdb->insert_id; + + $row = $wpdb->get_row( + $wpdb->prepare( "SELECT * FROM $table WHERE id = %d", $id ), + ARRAY_A + ); + + wp_send_json_success( $row ); +} +add_action( 'wp_ajax_todo_create', 'document_todo_create' ); + +/** + * 更新待办 + */ +function document_todo_update() { + document_todo_check_permission(); + global $wpdb; + $table = $wpdb->prefix . 'document_todos'; + + $id = intval( $_POST['id'] ?? 0 ); + if ( ! $id ) { + wp_send_json_error( 'ID无效' ); + } + + $update = []; + $format = []; + + if ( isset( $_POST['title'] ) ) { + $update['title'] = sanitize_text_field( $_POST['title'] ); + $format[] = '%s'; + } + if ( isset( $_POST['completed'] ) ) { + $update['completed'] = intval( $_POST['completed'] ) ? 1 : 0; + $format[] = '%d'; + } + if ( isset( $_POST['priority'] ) ) { + $p = sanitize_text_field( $_POST['priority'] ); + if ( in_array( $p, [ 'urgent', 'twodays', 'thisweek', 'anytime', 'high', 'medium', 'low' ] ) ) { + $update['priority'] = $p; + $format[] = '%s'; + } + } + if ( isset( $_POST['due_date'] ) ) { + $update['due_date'] = sanitize_text_field( $_POST['due_date'] ) ?: null; + $format[] = '%s'; + } + if ( isset( $_POST['importance'] ) ) { + $update['importance'] = max( 1, min( 5, intval( $_POST['importance'] ) ) ); + $format[] = '%d'; + } + + if ( empty( $update ) ) { + wp_send_json_error( '没有要更新的字段' ); + } + + $wpdb->update( $table, $update, [ 'id' => $id ], $format, [ '%d' ] ); + + $row = $wpdb->get_row( + $wpdb->prepare( "SELECT * FROM $table WHERE id = %d", $id ), + ARRAY_A + ); + + wp_send_json_success( $row ); +} +add_action( 'wp_ajax_todo_update', 'document_todo_update' ); + +/** + * 删除待办 + */ +function document_todo_delete() { + document_todo_check_permission(); + global $wpdb; + $table = $wpdb->prefix . 'document_todos'; + + $id = intval( $_POST['id'] ?? 0 ); + if ( ! $id ) { + wp_send_json_error( 'ID无效' ); + } + + $wpdb->delete( $table, [ 'id' => $id ], [ '%d' ] ); + wp_send_json_success( [ 'deleted' => $id ] ); +} +add_action( 'wp_ajax_todo_delete', 'document_todo_delete' ); + +/** + * 批量更新排序 + */ +function document_todo_reorder() { + document_todo_check_permission(); + global $wpdb; + $table = $wpdb->prefix . 'document_todos'; + + $orders = json_decode( stripslashes( $_POST['orders'] ?? '[]' ), true ); + if ( ! is_array( $orders ) ) { + wp_send_json_error( '数据格式错误' ); + } + + foreach ( $orders as $item ) { + $wpdb->update( + $table, + [ 'sort_order' => intval( $item['sort_order'] ) ], + [ 'id' => intval( $item['id'] ) ], + [ '%d' ], + [ '%d' ] + ); + } + + wp_send_json_success( true ); +} +add_action( 'wp_ajax_todo_reorder', 'document_todo_reorder' ); + +/** + * 获取番茄钟状态(多端同步) + */ +function document_pomodoro_get() { + document_todo_check_permission(); + $state = get_option( 'document_pomodoro_state', '[]' ); + $counts = get_option( 'document_pomodoro_counts', '{}' ); + $minutes = get_option( 'document_pomodoro_minutes', '{}' ); + wp_send_json_success( [ + 'state' => json_decode( $state, true ) ?: [], + 'counts' => json_decode( $counts, true ) ?: new \stdClass(), + 'minutes' => json_decode( $minutes, true ) ?: new \stdClass(), + ] ); +} +add_action( 'wp_ajax_pomodoro_get', 'document_pomodoro_get' ); + +/** + * 保存番茄钟状态(多端同步) + */ +function document_pomodoro_save() { + document_todo_check_permission(); + $state = stripslashes( $_POST['state'] ?? '[]' ); + $counts = stripslashes( $_POST['counts'] ?? '{}' ); + $minutes = stripslashes( $_POST['minutes'] ?? '{}' ); + // 基本校验 + if ( ! is_array( json_decode( $state, true ) ) ) $state = '[]'; + if ( ! is_object( json_decode( $counts ) ) && ! is_array( json_decode( $counts, true ) ) ) $counts = '{}'; + if ( ! is_object( json_decode( $minutes ) ) && ! is_array( json_decode( $minutes, true ) ) ) $minutes = '{}'; + update_option( 'document_pomodoro_state', $state, false ); + update_option( 'document_pomodoro_counts', $counts, false ); + update_option( 'document_pomodoro_minutes', $minutes, false ); + wp_send_json_success( true ); +} +add_action( 'wp_ajax_pomodoro_save', 'document_pomodoro_save' ); diff --git a/include/todo/install.php b/include/todo/install.php new file mode 100644 index 0000000..549fe2e --- /dev/null +++ b/include/todo/install.php @@ -0,0 +1,57 @@ +prefix . 'document_todos'; + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE $table_name ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + title varchar(500) NOT NULL DEFAULT '', + completed tinyint(1) NOT NULL DEFAULT 0, + priority varchar(10) NOT NULL DEFAULT 'medium', + importance tinyint(1) NOT NULL DEFAULT 3, + due_date date DEFAULT NULL, + sort_order int(11) NOT NULL DEFAULT 0, + created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY idx_completed (completed), + KEY idx_priority (priority), + KEY idx_importance (importance), + KEY idx_sort_order (sort_order) + ) $charset_collate;"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $sql ); +} + +/* 主题激活时创建表 */ +add_action( 'after_switch_theme', 'document_todo_create_table' ); + +/* 如果表不存在则自动创建 */ +function document_todo_maybe_create_table() { + global $wpdb; + $table_name = $wpdb->prefix . 'document_todos'; + if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) !== $table_name ) { + document_todo_create_table(); + } +} +add_action( 'init', 'document_todo_maybe_create_table' ); + +/* 升级:为已有表添加 importance 字段 */ +function document_todo_maybe_add_importance() { + global $wpdb; + $table_name = $wpdb->prefix . 'document_todos'; + $column = $wpdb->get_results( "SHOW COLUMNS FROM $table_name LIKE 'importance'" ); + if ( empty( $column ) ) { + $wpdb->query( "ALTER TABLE $table_name ADD COLUMN importance tinyint(1) NOT NULL DEFAULT 3 AFTER priority" ); + $wpdb->query( "ALTER TABLE $table_name ADD KEY idx_importance (importance)" ); + } +} +add_action( 'init', 'document_todo_maybe_add_importance' ); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e3d0d0d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,500 @@ +{ + "name": "theme-document", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "theme-document", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "esbuild": "^0.28.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8407ae7 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "theme-document", + "version": "1.0.0", + "description": "
    \r 点击展开查看原作者对本仓库介绍", + "main": "index.js", + "scripts": { + "build": "node build.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/HaibinLai/theme-document.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "bugs": { + "url": "https://github.com/HaibinLai/theme-document/issues" + }, + "homepage": "https://github.com/HaibinLai/theme-document#readme", + "devDependencies": { + "esbuild": "^0.28.0" + } +} diff --git a/style.css b/style.css index 1b707a5..5290c74 100644 --- a/style.css +++ b/style.css @@ -2,10 +2,10 @@ /* Theme Name: Document Theme URI: https://nicen.cn -Author: 友人a丶 +Author: 友人a丶 Haibin Author URI: https://nicen.cn Description: 一个基于文档类型的博客主题,更加方便的记录、查询学习笔记 -Version: 1.0 +Version: 1.5.2 License: GNU General Public License v2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Text Domain: Document @@ -22,7 +22,7 @@ Tags: 文档,自适应,主题切换,阅读进度跟随 --theme-text-mini: 0.76rem; --theme-text-more-mini: 0.69rem; --theme-text-more-mini-2: 0.62rem; - --theme-text: 0.84rem; + --theme-text: 1rem; --theme-header-size: 1.1rem; --theme-secondary: 0.78rem; --theme-first-level-title: 1.35rem; @@ -2313,3 +2313,155 @@ html { display: none; } html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea .emoji-list { width: 80%; } } + + +html body .main-container .main-main .main-content .main-article blockquote{ + margin: 1rem 0; + padding: .75rem 1rem; + border-left: 4px solid var(--theme-color); + background: rgba(0,0,0,.04); + border-radius: 6px; +} + +html body .main-container .main-main .main-content .main-article blockquote p{ + margin: .4rem 0; +} + +/* Details 和 Summary 可折叠专栏样式 */ +html body .main-container .main-main .main-content .main-article details { + margin: 1rem 0; + padding: 0; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 6px; + background: rgba(0, 0, 0, 0.02); + overflow: hidden; + transition: all 0.3s ease; +} + +html body .main-container .main-main .main-content .main-article details:hover { + border-color: var(--theme-color); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + +html body .main-container .main-main .main-content .main-article details[open] { + background: rgba(0, 0, 0, 0.03); + border-color: var(--theme-color); +} + +html body .main-container .main-main .main-content .main-article details summary { + padding: 0.75rem 1rem; + cursor: pointer; + font-weight: 500; + color: var(--theme-text-color); + background: rgba(0, 0, 0, 0.02); + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + user-select: none; + list-style: none; + position: relative; + transition: all 0.2s ease; +} + +html body .main-container .main-main .main-content .main-article details summary::-webkit-details-marker { + display: none; +} + +html body .main-container .main-main .main-content .main-article details summary::before { + content: '▶'; + display: inline-block; + margin-right: 0.5rem; + transition: transform 0.2s ease; + color: var(--theme-color); + font-size: 0.8em; +} + +html body .main-container .main-main .main-content .main-article details[open] summary::before { + transform: rotate(90deg); +} + +html body .main-container .main-main .main-content .main-article details summary:hover { + background: rgba(0, 0, 0, 0.04); + color: var(--theme-color); +} + +html body .main-container .main-main .main-content .main-article details summary:active { + background: rgba(0, 0, 0, 0.06); +} + +html body .main-container .main-main .main-content .main-article details > *:not(summary) { + padding: 1rem 1rem 1rem 1rem; + margin: 0; + background: rgba(255, 255, 255, 0.5); +} + +html body .main-container .main-main .main-content .main-article details > *:not(summary):first-of-type { + padding-top: 1rem; +} + +html body .main-container .main-main .main-content .main-article details > *:not(summary):last-child { + padding-bottom: 1rem; +} + +html body .main-container .main-main .main-content .main-article details p { + margin: 0.5rem 0; + line-height: 1.6; + color: var(--theme-text-color); +} + +html body .main-container .main-main .main-content .main-article details p:first-child { + margin-top: 0; +} + +html body .main-container .main-main .main-content .main-article details p:last-child { + margin-bottom: 0; +} + +html body .main-container .main-main .main-content .main-article details h1, +html body .main-container .main-main .main-content .main-article details h2, +html body .main-container .main-main .main-content .main-article details h3, +html body .main-container .main-main .main-content .main-article details h4, +html body .main-container .main-main .main-content .main-article details h5, +html body .main-container .main-main .main-content .main-article details h6 { + margin-top: 1rem; + margin-bottom: 0.5rem; + color: var(--theme-text-color); +} + +html body .main-container .main-main .main-content .main-article details h1:first-child, +html body .main-container .main-main .main-content .main-article details h2:first-child, +html body .main-container .main-main .main-content .main-article details h3:first-child, +html body .main-container .main-main .main-content .main-article details h4:first-child, +html body .main-container .main-main .main-content .main-article details h5:first-child, +html body .main-container .main-main .main-content .main-article details h6:first-child { + margin-top: 0; +} + +html body .main-container .main-main .main-content .main-article details ul, +html body .main-container .main-main .main-content .main-article details ol { + margin: 0.5rem 0; + padding-left: 1.5rem; +} + +html body .main-container .main-main .main-content .main-article details code { + background: var(--theme-code-bg); + padding: 0.2em 0.4em; + border-radius: 3px; + font-size: 0.9em; +} + +html body .main-container .main-main .main-content .main-article details pre { + background: var(--theme-pre-code-bg); + padding: 1rem; + border-radius: 6px; + overflow-x: auto; + margin: 0.5rem 0; +} + +html body .main-container .main-main .main-content .main-article details blockquote { + margin: 0.5rem 0; + padding: 0.5rem 1rem; + border-left: 3px solid var(--theme-color); + background: rgba(0, 0, 0, 0.02); + border-radius: 4px; +} + + diff --git a/style.min.css b/style.min.css new file mode 100644 index 0000000..7eb2bfd --- /dev/null +++ b/style.min.css @@ -0,0 +1 @@ +@charset "UTF-8";:root{--theme-text-super-mini: .4rem;--theme-text-mini: .76rem;--theme-text-more-mini: .69rem;--theme-text-more-mini-2: .62rem;--theme-text: 1rem;--theme-header-size: 1.1rem;--theme-secondary: .78rem;--theme-first-level-title: 1.35rem;--theme-secondary-level-title: 1.15rem;--theme-third-level-title: .96rem;--theme-fourth-level-title: .84rem;--theme-black-text: #333;--theme-white: #f5f7fd;--theme-black: #333;--theme-color: #3eaf7c;--theme-color-20: rgba(66, 185, 131, .2);--theme-text-color: #262626;--theme-text-secondary: rgba(0, 0, 0, .65);--theme-text-mini-color: rgba(0, 0, 0, .65);--theme-margin-bottom: 1rem;--theme-margin-bottom-max: 1.1rem;--theme-li-padding: 1.2rem;--theme-bg-color: var(--theme-white);--theme-pre: #ccc;--theme-front-main-color: #fff;--theme-front-color: #fafbfc;--theme-header-color: var(--theme-color);--theme-header-border: #e1e1e1;--theme-hover-bg: #444;--theme-hover-color: white;--theme-hover-shadow: rgba(0, 0, 0, .1);--theme-code-bg: #eeeeee;--theme-code-text: #4e6e8e;--theme-success-bg-color: #f3f4f5;--theme-success-line-color: #42b983;--theme-success-title-color: #2c3e50;--theme-success-text-color: #2c3e50;--theme-alert-bg-color: #fffae3;--theme-alert-title-color: #ad9000;--theme-alert-line-color: #e7c000;--theme-alert-text-color: #746000;--theme-error-bg-color: #ffe0e0;--theme-error-title-color: #990000;--theme-error-line-color: #cc0000;--theme-error-text-color: #660000;--theme-first-index-title: .78rem;--theme-secondary-index-title: .78rem;--theme-third-index-title: .78rem;--theme-pre-code-bg: #f7f7f7;--theme-pre-code-shadow: #fff;--theme-label-bg: #aaa;--theme-label-color: #fff;--theme-label-hover: #8d8d8d;--theme-emoji-size: 1.3rem;--theme-shadow: 0 2px 6px 0 rgba(0, 0, 0, .22);--theme-placeholder: #a4a4a4}.theme-one{--theme-color: #f5222d !important}.theme-two{--theme-color: #fa541c !important}.theme-three{--theme-color: #fadb14 !important}.theme-four{--theme-color: #3eaf7c !important}.theme-five{--theme-color: #13c2c2 !important}.theme-six{--theme-color: #1890ff !important}.theme-seven{--theme-color: #722ed1 !important}.theme-eight{--theme-color: #eb2f96 !important}.dark{--theme-text-color: rgba(255, 255, 255, .87);--theme-black-text: white;--theme-bg-color: var(--theme-black);--theme-header-bg-color: #141414;--theme-header-font-color: white;--theme-sub-menu-bg-color: #141414;--theme-sub-menu-font-color: white;--theme-header-border-color: transparent;--theme-header-shadow-color: transparent;--theme-footer-bg-color: transparent;--theme-footer-font-color: white;--theme-text-secondary: rgba(235, 235, 235, .99);--theme-hover-bg: var(--theme-text-color);--theme-hover-color: var(--theme-black);--theme-front-color: #2a2a2b;--theme-text-mini-color: rgba(235, 235, 235, .99);--theme-hover-shadow: rgba(223, 223, 223, .5);--theme-header-color: #141414;--theme-header-border: #141414;--theme-front-main-color: #2a2a2b;--theme-pre: rgba(255, 255, 255, .87);--theme-pre-code-bg: #141414;--theme-pre-code-shadow: transparent;--theme-label-bg: #141414;--theme-label-hover: #2a2a2b;--theme-success-bg-color: #10b98129;--theme-success-line-color: rgba(66, 185, 131, .5);--theme-success-title-color: #dfdfd6;--theme-success-text-color: #dfdfd6;--theme-alert-bg-color: #eab30829;--theme-alert-line-color: rgba(231, 192, 0, .5);--theme-alert-title-color: #dfdfd6;--theme-alert-text-color: #dfdfd6;--theme-error-bg-color: rgba(244, 63, 94, .16);--theme-error-line-color: rgba(204, 0, 0, .5);--theme-error-title-color: #dfdfd6;--theme-error-text-color: #dfdfd6}@font-face{font-family:iconfont;src:url(data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAABJAAAsAAAAAIoAAABHyAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACHfAqvbKcNATYCJAN8C0AABCAFhGcHgwYbHx0zozaUtPJK9n9J4HQIJJapvxGhYqNdjY5VxYd4iYjPjr6x/1k5qM66r/hPyRCGhNq0p5Ghhpfr1UvHCLdNm2WGUkKUtrIl7e4TsoEoAqYzAMZIlM19nUP7MwqAwzkbgzARpihnnzz6W3qBpu01NBqibTWE9oqAtLGAiYUuHbsWvLhWgl1XXCJXCXpfF8VX60e1euH53/gZDRBAYL7Mz3sAgKAgAuA2RlSo943xP/Re9pnWQRmsT1ZwoPMz8zvzGyxaL9TtPbQHKZhaS+1fwq6A8iYT3rJQda5C7u1dYHfv4cIXvg/hfoj/i9+yI5BVjcpU3QWI6xB9px5QudoKX2mqU7dS/aUwMgmH+vx+83fBWLR9HDbGFBt1+3qxHBTgS7FWdIdOPQYQOHnE7KnEsrhfrMCvriZPGDcCbz0WK2Wjwtvi8A5rBalt2KDX65fAVufjq7+UtgCNbbjPqM/YjqO5K3j6GFcTDPOT5dW1QvcasAED5AsWp+enZ6mZGA/nO9Q37s8qS3UCWklmkXhEiyOBEipVphwl+skAGSIjZabMkzWyQ67JL/kjbvmnbur8NXvXbq896+nTRFGP3sfqfr0UrR6IPEyNFyHCRUgUxiYQf7wJFoAvhiB8iBYpVIwoseLES+Bg4UeA8o/nQKv6A6hVKE6CYAAoEE1wZAcAXiCBACGQUIBwSCpABCQTIBGSAxAGKQGwQfoRqpIHAMAfZAiAN8hIgGDITIAAkHkAviBrAAzIDoAgkGsKPpB/QSEa8h8oREJ2g9AF+R8UYiDXASAK7lSAWLjzAeLgrgGIh7s3QAJqtys4jnwWABZ4eiv4Qe6DBAIGfXigFZgNXk/Ap567+T9FMltRYdAEfgyQjy2d+gKhQGHjZXhakjVHbzQiGlYJQmOjKNFBUNfSMnaK2Hj3a2o2scYj22oEoToyYfRsjmt+iFBbwm1UPRmo51EFoYYY8lyD0NwmtnNcLBShF20BLN/KsqFmOsuN49nWlpBsLa3UnzxuLqeDw57qyY6DDYrrbtB8X1W9vayierEiRPQqO8dJCRVxHhY497Ep/Ce8WbRobECBF1yi2TZAWR1NPl38QtP4GQUMJiadKpApWglczuMX6CV53jvxUpJKxg1FO8KJsimYzG1BaDLQlbuntWM6R55TGj6jWfwCVKOXs7DxypsMR+yOLPogKpECxZFhGFKA+dpa8WTqNPaeOkgbnJGSJDuHOh6PbRH0HZRLcYdkiOZrpSl20uLTsoNVPqGbsbkJiQWuktdJAaFVaaZselGKDx2vmoPgAr7aoQrkJXqBn9kVM8PohTRO8/nD3UlOKqEX41XLmxTPoZ1/5uP/6+ktuIhKTGtbT2Wvb3TjjJ1KWtk0f/K01bkRTwinThqwQKQzp8SZsHgFi6ctVBoSTp2xHxY6+ZPGGY6q93XfQ4VOSO8QOEwxKmEJiiUYBCJLR+CQVFBtKJEmyozIPhDVO5AJDkRHhfvn2CXJ1wCywPrhxdlKqG90oGemkec/rZ1rCsL7fdnx2S4xZPDxQLy9xti5JjM0zhQSwWAqbE/ITQBIPOFt11BlvPqvuiPXH1Lf7lAyT8PaQ3eekng8Qfu+So73jtvmzJTTZWrgN3l3Fo/ViO8jz8OOA133Ai4CMEsGzisWkvbYuYyZiPM7DawMU+R8YYqyTYRdkY2MdFbcbSGZWyqYrJrwTIqIIyqkc7Jm4DMpYTioEdeFjoM9D/m+UrckRsoJTelxuyqOl0fA4E4VTGdBtWxgYYw0W7JzgikoplGR44n9ye4JNiP3dVz83vj/4y39bVGjempVL+oxS+xJo8lPJhenV0b3jfG18owSfLh7yuBff0z/ZCY/My4zqeAhpw1EO0dJKaF3Dfg45YoGE8HOKPaNdEwtL30mS/ZgKZAMJSdXpleWR8oTy4HRGbYj2KmzsmGIKovFLKh9Z/eUFeeNwy01nKJx6rIWVqNz9zhdalpJ0nGyfLWSuboKQ5UyRYNTDndmQJUUBArG06JqJVM5QZmK/qUzxFUSZboJV8K+f8rz1u0dufl+RVFjMGWAvD/e3seuv8RGxSzP5/FkC+fhjQLiFsI1UQCVUWjdQKagKTQTG4TH7zz18MQetEvW8zu4nJWKGW/kRd8cuvaoTtdhiDZFUacMioGSyMleQb3jnnMV/dQ8vH9pmq6Cr77RLP7xBlTTOJtMSWdKH9/LxeGxJ5kq+HwcR16Gq5mjCkMpSNO46Q46+f6asvJ4NBnVorc/mUtN+FmghpdrxrWfF7pX4ik7b44/4WOlY0L2gv9efmW4trIUDHkerOqwQt//7Kgs/lVpZ52FIYY7QFcx5O8WbGEXj7NyGJ1khFWmUIaPnw9Qaui3G9nb48R36o3e3+X/XQDQ4MunR/ZRuSO4L/G9rg5/hg+kuipP5NKMDtA4T9EsJovj224grrbOkN+Uhr+qwH/erP5lmtlcIK8sWqG/hFgOMDKLDb5q9EwclkS8kHg4n7tW1nlmt9zJfy/NURzDAao305lUSx8aZ3NQOSY0by9eNnAxKa2YyLdXEQrm00URIN/fwCy3ktL6v+EE9nZ/aYuV3y50Pjg1XvrIW/7n61xn13OXIQvszAdMfXcMrwn06TEp+vn57u7wfmDkOhz9yT1ch1u7/fbkfje6B/B1Ea41877F3MiuZzmkNjfHbnSj6wFfF+HenlujXc68nBHMDDLpy0E/mfia/zUYJsaHmXOgHeUvYPzKzVEJDlBkjOPopkbqN9zXOTjndd6bV6mNjVQJf2ODyIn7joqYJWYEoRW/+HRjQ3r+IG8ZIlCfJqHeXF9+7ASdUxTewPjpADjXTGYz4pKurkxNzf3PfpDxTNqVtOBC3GKIJzYicPIzmyOeIdcmv1YFCWIFUOmvJY1LLg1FJAoZXwgyKBXfRsJ2yj1u+e4Tyea8L54JT03S34r10sBIdIu2SSNtk7vHZ7TdKrdb1a2ZbcbTK9N1Rgm7pvjbw6N3D5p5J1Vo5LndirwPk2dwe4zcLKmkfbi/sLmg4cKWTvoHQPbOCBFxEFvoCNcHkNipuMnYzriHcJwSSrxCuZL4bBdjlblqhL7kmuPSeLSBh06E0mrg6rRqGH50KWNv5t4dK3l4VfSW6O0Hu30/oUAXadynq1YBYpNZJVLZ2zT/PJSyUD2bAs37pepn++YXUxbnq413X6Ms2bpHHg4tw8uhGdt1QeKa17QNW1+CBA3PFkozxNSExX2lQPWT3ZwgSUASrgIkQQIpX1LiJQlAEMII/SYgIG8bhuIG8RskMo5izuJk0htiA+45oH6ugBugOOD25weglhF+cGk1Z+Xw6qFT1MLelWh2PGFgPLVZvPlJc7OzsCI/v6LQuSRYsC/tLFwK6TYVVlqzTVlZpmzrOUJCXtqafY7+ZwH755UUYGagWHIHr4XeTnebHKOcmjIcE/Xk9sWNU2bRdltrHzIo3UTuID7q2H0IbqhVdMV74trgdv6ZwKUrsQtH0IuSCvdNI8MSC1Qys4957czKtWO2ixnNnrVEJbPtpyM9Z8DNz0n36xvTI8oWFcd+o8sJRPJfF0uHi/sHb2shf/0rCI5pE22a4OD+bpZT7iVaNhxnLqTbedoy2u71izDKsICkvU4X13eX0bQ8of2RM44NouXecsqJ37kEx7SJ9r5eUph3Ia8kV4TVCpa1xqsI/n0JCQKcc1ewvsCHxgAvMI2TyYsGA+7z81wfpXwDg9/38/w+3GBYJJPx6QDvI1cxcXru4JvjuT4+i6OoNz3H+5hkHDAYBnYIlnWHV8ugFzsLCpytBs3aSjSQF37vW2o4zQbsqS+/9bT++NOd0/gBP98Xp8qN2/XiC1bqFap1dUVQZIOrnZKDJL9/HLxjf6t8v0X3d/snbAE76emhe+O3fA8wkCoYbzBOBxVZp7ZGPnxwrvXIqBRdOUwOrkp6hk8eiLaklp++t39Hzj0fpB24XNhfZN97Zu3X2s13D7/Q9YS445N/XIeH77kZ3zJaYWHBR7zHfc9sDzC1Uwc65+/42Sxa7cqmAoQR3bOFQrvw+8UfMqHen5ADX3YfcUwB9WE3qDxDb6X9lk5hf5uaMs1YYXiZtpAtWGg55fLoqPKlA7YDuWtrrMRCaiGhkL8DwM4NKUz8L9QPgEnSeR5Vu1LZrvKEBQ32pT2qMNWd1Huqi6vE4qri6tcICQmn+9cOrCXG0Ge4+oHpXyLy33tinf7/v4b9Pom/3nHiVN8p9IPd5932U/HE+8Any8+/dLzgtOsIY52x3XXgGPDDiJwhh6smZ481ZLsH6WOcNWaymMzc2c1mS2aiOTOEzah1i0vdrlR1TAjWfZvsUCo71LlwxRsO+KyMUsOsZVKWhJ0/PmYOosFFoU8IPH11ZXmY7NbJK61jCP5PfWTYhLqRKcKw3M/1qHtd+wY6bbVPEmy8qFDrYvTj0ZF26/Sk3UboT1I0yWVNQ9rQJBuWvFryLVnGjAxjBTYYMysk/K4MAJPMs91aj1rt0XbPWjK3SNOacyTXZHf2M8a9WdHRvxgsm0c9dWe0vXWSGgSpkdR9YNBgQ47WO9qieaCDnlIXHYYHlL1RLK5JDWqCaoPEuOgRH5sSeqIgo4Pb6vKhe8ohXRd+1IjgOcFIFL+LDtLuQQOtlRNvX6WZwLWYvhjdzaYPv8SB2XPor7/EbqSmgTTLDsRnzfxkW0FrkqfStUkZ8bEcZrbT4LMARI2lQrFW7N3XVHoPJgE8ch4qEqF5vfDsGxdhby72ch9+PRp9DCrIF6GoKI98NjarU9uu7KnsXvr7V7T0a7cfbz0Rq2eJIvBDy51+S9ez9xtIEMeH+iB+nAACMS4DbeP0mDDiW9adgMsmNKroJXtqt71UbVHcocjol3pee+glqkacxQV3RtH/7yeOngla67YNYfw7+NjQYa3BM6Ovbkv4hsUmfGN9/hi9ixlmluqJ4oOEMPW/zg4EbKsSUoXA8Ag5Qup2Jo9Y/MHKCecwQlHq/60pySghRi94ic6FCPqCTuEiu9v/Amr1/yWVuxuG3xNRFULDACbhndcZbJbVS6X1suZlwYINp2X5gAd7SZcGbdMPaHt6tAP6/wTLpu2Zlv8OD7l7klpX74vuaWzwc2GOH69IX2sk74ao2+H4nTKkoV52iwvxRc9Qp73guaXl+xSdGMbp4LRxWjnuY8fcBLeR+hjWqdi3oNjbuQn3518uGN9eNL7yhovhSHImbW1mhuFyox0Y9xtvjhcYRXXP9ttfvt9xzwwUvHHDzxFwAveAQa4gFJY1NTa9ddYUq/xPb/U9/un7mXa8xx4Tyd8107Tt2jXMcgDzXv/ebV5pzOZavairwfonNB3wywxlN240/gEXvTR9U2zth83+eopRli6gadkwrh1/x3DThr+pzqD8c5dNJf/FQtMh4JYzpr33A/qIF4Kuofg4oYOqoG9ETTnuM410Sh64Zbr//N8dGxalpsP2LeryuH8Jbvgzy+8IUZqWdR91M8UbnYTVBf20f2+D7ScAdj89G5L2+59oII427F9NREECkuwmsEADvo9z4Mf7jFC/xTWHcUt/gJjD/ECNF3GBBm9SOcs7N9DGj9JAB29aBvqSp+PIfoSZjkFZPkCmlbtARbBjgZpArmET5/0EW/D+EmgTyY0deDcE+jJIBYzPj8z19H9zCAErdn0yzakTsgSnvd8oYwMuKv8fndfIZUWaB0u/sENXxgD+JssQBBOOWvapzgybhljvqEYeUhVCf8kyETdAyqkdvDmEYIWumOvzQM2pE+4D++l9o4wNOE7uX3WeItJ+ppDKObgv2nE5FaSuv8lSIKpgBnakpGU+dQjcaEXE9PEFNfJzSkoVKOgvsoc7wUvT+s3tQqHMO5f3tyepUCSWSGVyhVKl1mh1eoPRZLZYbXaH0+X2+Pj6BYtXA1ndSXei0C25wk5K6uRUxJfSi2fEJo6R84ag4irYVECKHbglh92YK5g+NDQwf0XoGdBT6BTquU8hJ1NsXtBBJUn1HZ2eFBNO/ZMKPEI59qc+PMoXaEQF8cWuhoKEg64EQhLFJgF9WPSVaE3sinyKrgtoZ67iacMrkoa1xsrK0exOFYZ5zsfOvKL4xO7h+pHUxGkK/GQRg3m+OjOBXYN6b4tNIK/SOhMw8xR9pMEAAA==) format("woff2")}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-biaoqing:before{content:"\e600"}.icon-biaoqian:before{content:"\e602"}.icon-chenggong:before{content:"\e686"}.icon-fuzhi:before{content:"\e8b0"}.icon-yueliang:before{content:"\e631"}.icon-loading:before{content:"\e891"}.icon-daohangmoren:before{content:"\e606"}.icon-cha:before{content:"\e65d"}.icon-xiala:before{content:"\e6b9"}.icon-zhankai:before{content:"\e677"}.icon-anhei:before{content:"\ea4f"}.icon-shezhi1:before{content:"\e654"}.icon-baitian-qing:before{content:"\e672"}.icon-baitian-qing-copy:before{content:"\ea50"}.icon-you-copy-copy-copy:before{content:"\e652"}.icon-shangyi:before{content:"\e692"}.icon-xiangxiazhankai1:before{content:"\e662"}.icon-daohang-caidan:before{content:"\e60e"}.icon-icon-test:before{content:"\e6e7"}.icon-pinglun1:before{content:"\e629"}.icon-shijian:before{content:"\e612"}.icon-chuangzuozhejieshao:before{content:"\e622"}.icon-wodewo:before{content:"\e6df"}.icon-pinglun:before{content:"\e6f5"}.icon-shouye:before{content:"\e6dd"}.icon-bijijilu:before{content:"\e6f3"}.icon-jihua:before{content:"\e722"}.icon-shoucang:before{content:"\e72c"}.icon-fenlei:before{content:"\e739"}.icon-fenxiangzhuanfa:before{content:"\e74f"}.icon-sousuo:before{content:"\e626"}@keyframes rotate{0%{transform:rotate(0)}50%{transform:rotate(180deg)}to{transform:rotate(360deg)}}*{font-size:var(--theme-text);color:var(--theme-text-color);box-sizing:border-box;line-height:1.75;word-wrap:break-word;word-break:break-all;word-spacing:normal;-webkit-tap-highlight-color:rgba(0,0,0,0)!important}*::-webkit-scrollbar{-webkit-appearance:none;background:transparent;width:6px!important;height:6px!important}*:hover::-webkit-scrollbar-thumb{display:block;border-radius:4px;box-shadow:0 0 0 2px transparent}*::-webkit-scrollbar-thumb{background:var(--theme-color);border-radius:4px;border-style:solid;border-color:transparent;border-width:2px;background-clip:padding-box}pre *{color:#65a4e3}p{margin:0 0 var(--theme-margin-bottom) 0;font-weight:400}::selection{background-color:var(--theme-color);color:#fff}.tooltip{position:relative}.tooltip:hover:before{position:absolute;top:calc(100% + 5px);left:5%;width:0;height:0;content:" ";border:5px solid transparent;border-bottom-color:#2c3e50}.tooltip:hover:after{position:absolute;top:calc(100% + .81rem);left:1%;z-index:100;padding:10px .81rem;box-shadow:0 0 5px 1px #2c3e5080;font-size:.5rem;color:#fff;content:attr(data-hint);background-color:#2c3e50;white-space:nowrap;line-height:1.5;letter-spacing:1px;border-radius:.45rem}html{min-width:1100px;-webkit-font-smoothing:antialiased}html body{background-color:var(--theme-bg-color);padding:0;margin:0;font-family:-apple-system,BlinkMacSystemFont,Helvetica Neue,PingFang SC,Microsoft YaHei,Source Han Sans SC,Noto Sans CJK SC,WenQuanYi Micro Hei,sans-serif;min-height:100vh;display:flex;justify-content:space-between;flex-direction:column}html body h1,html body h2,html body h3,html body h4{font-weight:550;cursor:pointer;margin:0 0 var(--theme-margin-bottom) 0}html body h1{font-weight:510;font-size:var(--theme-first-level-title);margin-top:1rem}html body h2{font-size:var(--theme-secondary-level-title)}html body h3{font-size:var(--theme-third-level-title)}html body h4{font-size:var(--theme-fourth-level-title)}html body .main-header{display:flex;position:fixed;background:var(--theme-header-bg-color);border-bottom:1px solid var(--theme-header-border-color);box-shadow:0 10px 10px var(--theme-header-shadow-color);justify-content:space-between;align-items:center;padding:0 3.25rem 0 2rem;top:0;left:0;height:60px;width:100vw;min-width:1100px;z-index:100;box-sizing:border-box}html body .main-header a{color:inherit;font-size:inherit;text-decoration:none}html body .main-header .left{display:flex;justify-content:space-between;align-items:center;padding:0;margin:0;height:100%}html body .main-header .left a.logo{height:55%;margin-right:1rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}html body .main-header .left a.logo img{height:100%;min-width:30px;margin-right:1rem}html body .main-header .left a.logo .title{font-size:var(--theme-header-size);letter-spacing:.1rem;margin:0!important;font-weight:600;font-family:webfont}html body .main-header .daohang{cursor:pointer;display:none}html body .main-header .right{height:100%;width:100%;position:relative;display:flex;justify-content:flex-end;align-items:center}html body .main-header .right .menu{list-style:none;height:100%;margin:0;display:flex;align-items:center;align-content:center}html body .main-header .right .menu .read-mode .iconfont{color:inherit;font-size:var(--theme-text)}html body .main-header .right .menu .menu-item-has-children>a{font-family:iconfont!important;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}html body .main-header .right .menu .menu-item-has-children>a:after{font-size:10px;display:inline-flex;margin:0 3px;transform:rotate(180deg);transition:all .2s linear;content:"\e652"}html body .main-header .right .menu .menu-item-has-children:hover>a:after{transform:rotate(360deg)}html body .main-header .right .menu .menu-item{display:flex;align-items:center;position:relative;margin-left:1.3rem;height:100%;cursor:pointer;font-size:var(--theme-text);color:var(--theme-header-font-color);white-space:nowrap}html body .main-header .right .menu .menu-item:hover>.sub-menu{visibility:visible;opacity:1}html body .main-header .right .menu .menu-item:hover>a{color:var(--theme-color)}html body .main-header .right .menu .menu-item:hover .iconfont{transform:rotate(0)}html body .main-header .right .menu .menu-item .sub-menu{display:block;visibility:hidden;opacity:0;position:absolute;background:var(--theme-sub-menu-bg-color);box-shadow:0 12px 32px var(--theme-hover-shadow),0 2px 6px var(--theme-hover-shadow);margin:0;padding:.5rem 0;border-radius:.45rem;transition:all .25s;top:66px;left:-1.2rem;list-style:none}html body .main-header .right .menu .menu-item .sub-menu .menu-item{margin:0;color:var(--theme-sub-menu-font-color);padding:3px 1.2rem}html body .main-header .right .menu .menu-item .sub-menu .menu-item:hover>a{color:var(--theme-color)!important}html body .main-header .right .menu .menu-item .sub-menu .sub-menu{top:0;left:105%}html body .main-header .right .menu-left{display:flex;align-items:center}html body .main-header .right .menu-left .search-div{display:flex;align-items:center;height:60px;position:relative}html body .main-header .right .menu-left .search-div .search-icon{display:flex;justify-content:space-between;align-items:center;height:100%;position:absolute;color:var(--theme-text-color);left:.5rem;top:0}html body .main-header .right .menu-left .search-div .iconfont{cursor:pointer;color:var(--theme-header-font-color)}html body .main-header .right .menu-left .search-div .search{height:60%;width:12rem;border:1px solid #cfd4db;outline:none;background-color:transparent;padding:0 1rem 0 1.6rem;font-size:var(--theme-secondary);border-radius:32px;color:var(--theme-header-font-color)}html body .main-header .right .menu-left .search-div .search::placeholder{color:#757575}html body .main-header .right .menu-left .search-div .search:focus,html body .main-header .right .menu-left .search-div .search:hover{box-shadow:0 0 5px 2px var(--theme-color-20);border-color:var(--theme-color)}html body .password{width:100%;height:100vh;padding-top:calc(60px + 4.5rem);display:flex;justify-content:flex-start;align-items:center;flex-direction:column}html body .password img{width:25%;margin-bottom:2rem}html body .password p{display:flex;justify-content:center;align-items:center;color:var(--theme-text-secondary);font-size:var(--theme-secondary);width:70%;flex-wrap:wrap;text-align:center;margin:0 auto}html body .password p input[type=password]{border:1px solid #cfd4db;padding:0 .45rem;margin-right:.5rem;border-radius:2px;outline:none}html body .password p input[type=password]:hover,html body .password p input[type=password]:focus{box-shadow:0 0 5px 2px var(--theme-color-20);border-color:var(--theme-color)}html body .password p input[type=password]::placeholder{color:var(--theme-placeholder)}html body .password p input[type=submit]{background-color:var(--theme-color);border:none;color:#fff;border-radius:4px;height:60%;padding:0 1rem;cursor:pointer;margin-top:1rem}html body .password p label{color:transparent;margin-top:-.8rem}html body .password p label input{height:2rem;width:100%}html body .main-container{margin-top:calc(60px + 1rem);display:flex;justify-content:center;height:100%;position:relative;overflow:hidden}html body .main-container #fixed{min-width:16.6rem;padding:0;margin:0 0 0 1rem;height:fit-content}html body .main-container #fixed .main-right{flex-shrink:0;height:fit-content;width:16.6rem;display:flex;flex-direction:column}html body .main-container #fixed .main-right .recent .ul li,html body .main-container #fixed .main-right .update .ul li{margin-top:.6rem}html body .main-container #fixed .main-right .recent .ul li a,html body .main-container #fixed .main-right .update .ul li a{display:flex;align-items:center;width:100%;box-sizing:border-box;overflow:hidden;text-decoration:none;max-height:65px}html body .main-container #fixed .main-right .recent .ul li a .thumnbnail,html body .main-container #fixed .main-right .update .ul li a .thumnbnail{flex-shrink:0;width:30%;margin-right:.8rem;overflow:hidden;border-radius:5px;height:56px}html body .main-container #fixed .main-right .recent .ul li a .thumnbnail img,html body .main-container #fixed .main-right .update .ul li a .thumnbnail img{width:100%;height:100%;transition:all .3s ease-out}html body .main-container #fixed .main-right .recent .ul li a .thumnbnail img:hover,html body .main-container #fixed .main-right .update .ul li a .thumnbnail img:hover{transform:scale(1.1)}html body .main-container #fixed .main-right .recent .ul li a .article,html body .main-container #fixed .main-right .update .ul li a .article{display:flex;width:100%;height:65px;flex-direction:column;justify-content:space-between}html body .main-container #fixed .main-right .recent .ul li a .article .caption,html body .main-container #fixed .main-right .update .ul li a .article .caption{white-space:normal;line-height:1.55;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;font-size:var(--theme-text-more-mini)}html body .main-container #fixed .main-right .recent .ul li a .article .caption:hover,html body .main-container #fixed .main-right .recent .ul li a .article .caption:active,html body .main-container #fixed .main-right .update .ul li a .article .caption:hover,html body .main-container #fixed .main-right .update .ul li a .article .caption:active{color:var(--theme-color)}html body .main-container #fixed .main-right .recent .ul li a .article .datetime,html body .main-container #fixed .main-right .update .ul li a .article .datetime{font-size:var(--theme-text-more-mini-2);color:var(--theme-text-mini-color)}html body .main-container #fixed .main-right .author{background-color:var(--theme-front-main-color);padding:0;margin-bottom:1rem;border-radius:4px;width:100%;position:relative}html body .main-container #fixed .main-right .author .author-beijin{border-radius:4px;overflow:hidden;display:flex;width:100%}html body .main-container #fixed .main-right .author .author-beijin img{width:100%}html body .main-container #fixed .main-right .author .statistic{display:flex;justify-content:space-between;margin-top:10px}html body .main-container #fixed .main-right .author .statistic .item{display:flex;align-items:center;flex-direction:column;width:33.3%}html body .main-container #fixed .main-right .author .statistic .item .top{font-size:var(--theme-text-mini);white-space:nowrap}html body .main-container #fixed .main-right .author .statistic .item .bottom{color:var(--theme-black-text);font-size:1.1rem}html body .main-container #fixed .main-right .author .offset{position:relative;padding:.45rem .5rem 1rem}html body .main-container #fixed .main-right .author .offset .author-avatar{position:absolute;top:-1.75rem;left:0;width:100%;display:flex;justify-content:center}html body .main-container #fixed .main-right .author .offset .author-avatar img{width:3.5rem;height:3.5rem;border-radius:50%}html body .main-container #fixed .main-right .author .offset .author-info{margin-top:2rem;width:100%;align-items:center;display:flex;justify-content:center}html body .main-container #fixed .main-right .author .offset .author-info .nickname{font-size:var(--theme-third-level-title)}html body .main-container #fixed .main-right .author .offset .author-info .tag{background-color:var(--theme-color);border-radius:6px;padding:2px .5rem;color:#fff;height:fit-content;line-height:normal;font-size:var(--theme-text-super-mini)}html body .main-container #fixed .main-right .author .offset .author-self{margin-top:.45rem;line-height:1.6;padding:.5rem;font-size:var(--theme-text-mini);color:var(--theme-text-secondary)}html body .main-container #space{display:flex;min-width:16.6rem;max-width:16.6rem;padding:0;width:16.6rem;margin:0 1rem 0 0;height:50vh;min-height:50vh}html body .main-container #space #navigator{position:fixed;top:0;left:0;background-color:var(--theme-front-main-color);min-height:50vh;height:fit-content;max-height:calc(100vh - 60px - 2rem);margin:0 1rem 0 0;flex-shrink:0;min-width:16.6rem;width:16.6rem;display:flex;flex-direction:column;overflow:hidden;padding-top:.45rem;border-radius:4px;opacity:0;z-index:10}html body .main-container #space #navigator .tag{list-style:none;padding:0}html body .main-container #space #navigator .tag li{display:inline;background-color:var(--theme-color);color:#fff;padding:.2rem .5rem;margin:.2rem .5rem;border-radius:8px}html body .main-container #space #navigator .tag li a{font-size:var(--theme-text-mini);color:inherit;text-decoration:none}html body .main-container #space #navigator .main-top{display:flex;justify-content:space-between;align-items:center;font-size:var(--theme-third-level-title);width:100%;padding:.15rem 1rem 5px;border-bottom:1px solid #e1e1e1}html body .main-container #space #navigator .main-top .iconfont{font-size:var(--theme-text-mini);cursor:pointer}html body .main-container #space #navigator .main-top *{color:inherit;font-weight:inherit;font-size:inherit}html body .main-container #space #navigator .main-top ul{list-style:none;padding:0;margin:0}html body .main-container #space #navigator .main-top ul li{padding:.45rem 0;cursor:pointer;display:inline;margin-right:.5rem}html body .main-container #space #navigator .main-top ul .active{font-weight:510;border-bottom:2px solid var(--theme-color)}html body .main-container #space #navigator .scroll{height:100%;position:relative;overflow-y:auto;margin-bottom:4.2rem;padding:.7rem .5rem 0 0;-ms-overflow-style:none;scrollbar-width:none}html body .main-container #space #navigator .scroll::-webkit-scrollbar{display:none}html body .main-container #space #navigator .scroll .iconfont{position:absolute;margin-right:3px;cursor:pointer;font-size:var(--theme-text-mini);transition:all .5s ease;left:8px}html body .main-container #space #navigator .scroll .collapse{transform:rotate(-180deg)}html body .main-container #space #navigator .scroll li .first-index,html body .main-container #space #navigator .scroll li .secondary-index,html body .main-container #space #navigator .scroll li .third-index{margin-right:1px;font-size:var(--theme-text-more-mini)}html body .main-container #space #navigator .scroll .line{position:absolute;background-color:var(--theme-color);left:4.5px;top:0;height:30px;width:4.5px;border-radius:5px;transition:all;transition-duration:.3s}html body .main-container #space #navigator .scroll ul{margin:0;padding:0;list-style:none}html body .main-container #space #navigator .scroll li{display:block;padding:0 0 0 1rem}html body .main-container #space #navigator .scroll li:hover{color:var(--theme-color)}html body .main-container #space #navigator .scroll li .first-index,html body .main-container #space #navigator .scroll li .secondary-index,html body .main-container #space #navigator .scroll li .third-index{display:flex;align-items:center;position:relative;padding:3px 1em;color:inherit}html body .main-container #space #navigator .scroll li .secondary-index div{padding:0 0 0 1.2rem}html body .main-container #space #navigator .scroll li .secondary-index .iconfont{left:1.616rem}html body .main-container #space #navigator .scroll li .third-index div{padding:0 0 0 2.4rem}html body .main-container #space #navigator .scroll li .first-index div,html body .main-container #space #navigator .scroll li .secondary-index div,html body .main-container #space #navigator .scroll li .third-index div{color:inherit;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}html body .main-container #space #navigator .scroll li .first-index div a,html body .main-container #space #navigator .scroll li .secondary-index div a,html body .main-container #space #navigator .scroll li .third-index div a,html body .main-container #space #navigator .scroll li .first-index div a:visited,html body .main-container #space #navigator .scroll li .first-index div a:active,html body .main-container #space #navigator .scroll li .secondary-index div a:visited,html body .main-container #space #navigator .scroll li .secondary-index div a:active,html body .main-container #space #navigator .scroll li .third-index div a:visited,html body .main-container #space #navigator .scroll li .third-index div a:active{color:inherit;font-size:inherit;text-decoration:none}html body .main-container #space #navigator .scroll li i{color:inherit;float:left;margin-left:-1em}html body .main-container #space #navigator .scroll li i.rotate{transform:rotate(-90deg)}html body .main-container #space #navigator .scroll .first li div{cursor:pointer;font-size:var(--theme-first-index-title);font-weight:400}html body .main-container #space #navigator .scroll .first .secondary li div{font-size:var(--theme-secondary-index-title);font-weight:400}html body .main-container #space #navigator .scroll .first .secondary .third li div{font-size:var(--theme-third-index-title);font-weight:400}html body .main-container #space #navigator .scroll .first .first-index:hover,html body .main-container #space #navigator .scroll .first .secondary-index:hover,html body .main-container #space #navigator .scroll .first .third-index:hover{color:var(--theme-color)}html body .main-container #space #navigator .index-scroll{margin-bottom:.7rem}html body .main-container #space #navigator .index-scroll li .first-index div,html body .main-container #space #navigator .index-scroll li .secondary-index div,html body .main-container #space #navigator .index-scroll li .third-index div{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;white-space:normal}html body .main-container #space #navigator .divider{font-size:var(--theme-text-mini)}html body .main-container #space #navigator .icp-beian{position:absolute;bottom:0;display:flex;width:100%;font-size:var(--theme-text-more-mini);padding:.25rem 1rem .5rem;justify-content:center}html body .main-container #space #navigator .icp-beian div{display:flex;justify-content:center;align-items:center;padding:.3rem;cursor:pointer;position:relative;width:2.5rem;transition:all;transition-duration:.3s;border:1px solid #e1e1e1;border-radius:50%;margin:0 1rem}html body .main-container #space #navigator .icp-beian div:active{background-color:var(--theme-color)}html body .main-container #space #navigator .icp-beian div .number{position:absolute;top:-.5rem;right:-.5rem;border-radius:50%;display:flex;justify-content:center;align-items:center;width:1.2rem;height:1.2rem;color:#2c3e50;font-size:var(--theme-text-more-mini);background-color:var(--theme-code-bg);white-space:nowrap}html body .main-container #space #navigator .icp-beian div img{width:100%}html body .main-container .div-info{padding:.1rem 1rem 1rem 1.1rem;margin-bottom:1rem;width:100%;border-radius:4px;background-color:var(--theme-front-main-color);font-size:var(--theme-secondary);color:var(--theme-text)}html body .main-container .div-info *{color:inherit}html body .main-container .div-info .ul,html body .main-container .div-info .ul li{white-space:nowrap;font-size:var(--theme-secondary)}html body .main-container .div-info span{font-size:var(--theme-text-mini)}html body .main-container .div-info .ul{padding:.3rem 0 0;margin:0;display:flex;list-style:none;flex-direction:column}html body .main-container .div-info .title,html body .main-container .div-info .comment-widget{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:.2rem 0;margin-left:3px;font-size:var(--theme-text-more-mini)}html body .main-container .div-info .title a,html body .main-container .div-info .comment-widget a{text-decoration:none;font-size:var(--theme-secondary);color:var(--theme-text-secondary)}html body .main-container .div-info .title a:hover,html body .main-container .div-info .comment-widget a:hover{color:var(--theme-color)}html body .main-container .div-info .title a .iconfont,html body .main-container .div-info .comment-widget a .iconfont{color:var(--theme-pre);display:inline-block;transform:rotate(-90deg);font-size:var(--theme-text-super-mini);margin-right:3px}html body .main-container .div-info .comment-widget{display:flex;align-items:center;align-items:flex-start;padding-left:0}html body .main-container .div-info .comment-widget .author-avatar{width:20px;border-radius:50%}html body .main-container .div-info .comment-widget a{display:-webkit-box;width:90%;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}html body .main-container .div-info .comment-widget a img{width:12px}html body .main-container .div-info .comment-widget a .name{color:var(--theme-black-text);margin:0 3px;font-size:var(--theme-text)}html body .main-container .div-info .comment-widget a .comment-widget-content{white-space:normal}html body .main-container .div-info .header{display:flex;justify-content:space-between;align-items:center;font-size:var(--theme-third-level-title);width:100%;padding:.15rem 0 5px;border-bottom:1px solid #e1e1e1}html body .main-container .div-info .header *{color:inherit;font-weight:inherit;font-size:inherit}html body .main-container .div-info .header ul{list-style:none;padding:0;margin:0}html body .main-container .div-info .header ul li{padding:.45rem 0;cursor:pointer;display:inline;margin-right:.5rem;height:inherit}html body .main-container .div-info .header ul .active{display:flex;align-items:center}html body .main-container .div-info .header ul .active .mark{display:flex;align-items:center;margin-right:.5rem}html body .main-container .div-info .header ul .active .mark:before,html body .main-container .div-info .header ul .active .mark:after{top:1px;display:block;content:" ";font-weight:510;border-radius:5px;width:3px;height:1rem;margin-right:.3rem;transform:rotate(15deg);transition:all .4s ease-in-out;background-color:var(--theme-color);flex:1}html body .main-container .div-info .header ul .active .mark:after{height:.9rem;opacity:.6}html body .main-container .div-info:hover .header ul .active .mark:before,html body .main-container .div-info:hover .header ul .active .mark:after{transform:rotate(-165deg)}html body .main-container .main-main{display:flex;flex-direction:column;width:55%;padding:0;height:fit-content;margin:0;margin-bottom:1rem}html body .main-container .main-main .div-info{margin-bottom:0}html body .main-container .main-main .main-content{position:relative;width:100%;padding:1rem 2rem 0;border-radius:4px;margin-bottom:1rem;background-color:var(--theme-front-main-color)}html body .main-container .main-main .main-content .dynamic{display:flex;height:2.5rem;margin:.5rem 0 .38rem;border-bottom:1px solid #e1e1e1}html body .main-container .main-main .main-content .dynamic ul.list{display:block;white-space:nowrap;padding:0;margin:0;height:100%;list-style:none;width:100%;overflow:hidden;text-overflow:ellipsis}html body .main-container .main-main .main-content .dynamic ul.list .tab{display:inline-flex;align-items:center;height:100%;margin-right:1.2rem;font-size:var(--theme-third-level-title);color:var(--theme-text-secondary);white-space:nowrap;cursor:pointer;border-bottom:1px solid transparent;overflow:hidden;text-overflow:ellipsis;flex-shrink:0}html body .main-container .main-main .main-content .dynamic ul.list .tab:last-of-type{flex-shrink:1}html body .main-container .main-main .main-content .dynamic ul.list .tab:hover,html body .main-container .main-main .main-content .dynamic ul.list .tab:active{color:var(--theme-color)}html body .main-container .main-main .main-content .dynamic ul.list .active-tab{color:var(--theme-color);border-bottom-color:var(--theme-color)}html body .main-container .main-main .main-content #dynamic{display:none;justify-content:center;align-items:center;height:8rem;width:100%}html body .main-container .main-main .main-content #dynamic .iconfont{color:var(--theme-color);font-size:1.5rem;animation-iteration-count:infinite;animation-duration:.3s;animation-timing-function:linear}html body .main-container .main-main .main-content #dynamic .animate-rotate{animation-name:rotate}html body .main-container .main-main .main-content .breadcrumb{border-bottom:1px solid #e1e1e1;font-size:var(--theme-text-mini);color:var(--theme-text-secondary);padding:0 0 .9rem}html body .main-container .main-main .main-content .breadcrumb a{color:inherit;font-size:inherit}html body .main-container .main-main .main-content .breadcrumb a:hover{color:var(--theme-text-color);transform:scale(1.1)}html body .main-container .main-main .main-content a{text-decoration:none}html body .main-container .main-main .main-content .article-info{padding-bottom:.3rem}html body .main-container .main-main .main-content .article-info ul{list-style:none;display:flex;margin:0;padding:0 0 .1rem;margin-bottom:var(--theme-margin-bottom)}html body .main-container .main-main .main-content .article-info ul li{padding:0 .45rem;cursor:pointer;border-right:1px solid #e1e1e1;display:inline;white-space:nowrap;font-size:var(--theme-text-mini);color:var(--theme-text-mini-color)}html body .main-container .main-main .main-content .article-info ul li a{color:inherit;font-size:inherit}html body .main-container .main-main .main-content .article-info ul li .iconfont{font-size:var(--theme-text-mini);color:var(--theme-text-mini-color);margin-right:.24rem}html body .main-container .main-main .main-content .article-info ul li:hover{color:var(--theme-text-color);transform:scale(1.1)}html body .main-container .main-main .main-content .article-info ul li:hover .iconfont{color:var(--theme-text-color)}html body .main-container .main-main .main-content .main-article h2{display:flex;justify-content:flex-start;align-items:center;padding:0 0 .25rem;border-bottom:1px solid #e1e1e1;font-weight:400;vertical-align:middle}html body .main-container .main-main .main-content .main-article h2:before{display:block;content:" ";font-weight:510;border-radius:5px;width:3px;margin-top:.1rem;height:1rem;margin-right:.6rem;background-color:var(--theme-color)}html body .main-container .main-main .main-content .main-article h2\ff0ch3,html body .main-container .main-main .main-content .main-article h4{position:relative;cursor:pointer}html body .main-container .main-main .main-content .main-article>div{margin-bottom:var(--theme-margin-bottom)}html body .main-container .main-main .main-content .main-article ul,html body .main-container .main-main .main-content .main-article ol,html body .main-container .main-main .main-content .main-article dl{margin:0;padding:0;margin-bottom:var(--theme-margin-bottom);margin-left:1.25rem}html body .main-container .main-main .main-content .main-article ul li,html body .main-container .main-main .main-content .main-article ol li,html body .main-container .main-main .main-content .main-article dl li{margin-top:1px}html body .main-container .main-main .main-content .main-article a,html body .main-container .main-main .main-content .main-article a:visited,html body .main-container .main-main .main-content .main-article a:active{color:var(--theme-color)}html body .main-container .main-main .main-content .main-article code.code{background-color:var(--theme-code-bg);margin:0 .2rem;overflow-wrap:break-word;color:var(--theme-code-text);font-size:var(--theme-first-index-title);padding:.2rem .5rem .21rem;border-radius:4px}html body .main-container .main-main .main-content .main-article .custom-container{border-radius:3px;margin:0 0 var(--theme-margin-bottom) 0;padding:.8rem 1.22rem .7rem}html body .main-container .main-main .main-content .main-article .custom-container .content{font-size:var(--theme-secondary)}html body .main-container .main-main .main-content .main-article .custom-container .title{font-weight:550;padding:0 0 .8rem}html body .main-container .main-main .main-content .main-article .custom-container ol:last-child,html body .main-container .main-main .main-content .main-article .custom-container ul:last-child,html body .main-container .main-main .main-content .main-article .custom-container p:last-child,html body .main-container .main-main .main-content .main-article .custom-container div:last-child{margin-bottom:0}html body .main-container .main-main .main-content .main-article .success{border-left:.31rem solid var(--theme-success-line-color);background-color:var(--theme-success-bg-color)}html body .main-container .main-main .main-content .main-article .success .content,html body .main-container .main-main .main-content .main-article .success .content *{color:var(--theme-success-text-color)}html body .main-container .main-main .main-content .main-article .success .title{color:var(--theme-success-title-color)}html body .main-container .main-main .main-content .main-article .alert{border-left:.31rem solid var(--theme-alert-line-color);background-color:var(--theme-alert-bg-color)}html body .main-container .main-main .main-content .main-article .alert .content,html body .main-container .main-main .main-content .main-article .alert .content *{color:var(--theme-alert-text-color)}html body .main-container .main-main .main-content .main-article .alert .title{color:var(--theme-alert-title-color)}html body .main-container .main-main .main-content .main-article .error{border-left:.31rem solid var(--theme-error-line-color);background-color:var(--theme-error-bg-color)}html body .main-container .main-main .main-content .main-article .error .content,html body .main-container .main-main .main-content .main-article .error .content *{color:var(--theme-error-text-color)}html body .main-container .main-main .main-content .main-article .error .title{color:var(--theme-error-title-color)}html body .main-container .main-main .main-content .main-article img{cursor:pointer;border-radius:5px;max-width:80%;margin:0 auto;height:unset}html body .main-container .main-main .main-content .main-article .container-image{width:100%;display:flex;flex-direction:column;align-items:center;margin:0 auto var(--theme-margin-bottom) auto}html body .main-container .main-main .main-content .main-article .container-image img{cursor:pointer;border-radius:5px;max-width:80%;height:unset}html body .main-container .main-main .main-content .main-article .container-image .image-info{font-size:var(--theme-text-mini);color:var(--theme-text-secondary);margin-top:10px}html body .main-container .main-main .main-content .main-article table{border-spacing:0;border-collapse:collapse;margin:0 0 var(--theme-margin-bottom-max) 0}html body .main-container .main-main .main-content .main-article table tr:hover{background:#3c5a640a}html body .main-container .main-main .main-content .main-article table tr td,html body .main-container .main-main .main-content .main-article table tr th{padding:.3rem .66rem;border:1px solid #d0d7de}html body .main-container .main-main .main-content footer{padding:2.6rem 0 0;width:100%}html body .main-container .main-main .main-content footer .article-footer{display:flex;flex-direction:column;align-items:center;justify-content:center}html body .main-container .main-main .main-content footer .article-footer .copyright{color:var(--theme-text-secondary);font-size:var(--theme-text-more-mini)}html body .main-container .main-main .main-content footer .article-footer .copyright a{font-size:var(--theme-text-more-mini)}html body .main-container .main-main .main-content footer .article-footer .donate button{background-color:var(--theme-color);margin-top:1.2rem;cursor:pointer;color:#fff;border:none;padding:2px .81rem;border-radius:5px;font-size:var(--theme-text-mini)}html body .main-container .main-main .main-content footer .article-footer .label{display:block;margin:1.2rem 0 .3rem;width:100%}html body .main-container .main-main .main-content footer .article-footer .label .iconfont{color:var(--theme-label-bg)}html body .main-container .main-main .main-content footer .article-footer .label ul{list-style:none;display:inline;padding:0;margin:0}html body .main-container .main-main .main-content footer .article-footer .label ul li{display:inline;cursor:pointer;white-space:nowrap;border-radius:2px;margin:0 3.5px;padding:2px 8px}html body .main-container .main-main .main-content footer .article-footer .label ul li,html body .main-container .main-main .main-content footer .article-footer .label ul li *{color:var(--theme-label-color);font-size:var(--theme-text-more-mini);background-color:var(--theme-label-bg)}html body .main-container .main-main .main-content footer .article-footer .label ul li:hover,html body .main-container .main-main .main-content footer .article-footer .label ul li:hover *{background-color:var(--theme-label-hover)}html body .main-container .main-main .main-content footer .footer-nav{width:100%;border-top:1px solid #e1e1e1;margin-top:0;padding:1.4rem 0;display:flex;justify-content:space-between}html body .main-container .main-main .main-content footer .footer-nav .to{display:flex;width:48%;flex-direction:column;color:var(--theme-text-secondary)}html body .main-container .main-main .main-content footer .footer-nav .to .text{color:inherit;font-size:var(--theme-text-mini)}html body .main-container .main-main .main-content footer .footer-nav .to a{font-size:var(--theme-secondary);color:inherit;text-decoration:none;white-space:nowrap;width:100%;overflow:hidden;text-overflow:ellipsis;margin-top:.35rem}html body .main-container .main-main .main-content footer .footer-nav .to a:hover{color:var(--theme-color)}html body .main-container .main-main .main-content footer .footer-nav .right{align-items:flex-end}html body .main-container .main-main .main-content footer .footer-nav .right a{text-align:right}html body .main-container .main-main .div-info .comment-form{display:flex;flex-direction:column}html body .main-container .main-main .div-info .comment-form *{border-radius:4px}html body .main-container .main-main .div-info .comment-form .comment-header{width:100%;padding:0 .35rem;display:flex;justify-content:space-between;align-items:center;margin:1rem 0}html body .main-container .main-main .div-info .comment-form .comment-header .comment-person{width:70%;display:flex;justify-content:flex-start;align-items:center}html body .main-container .main-main .div-info .comment-form .comment-header .comment-person .comment-avatar{display:flex;justify-content:center;align-items:center}html body .main-container .main-main .div-info .comment-form .comment-header .comment-person .comment-avatar img{border-radius:50%;width:3rem;height:3rem}html body .main-container .main-main .div-info .comment-form .comment-header .comment-person .comment-info{display:flex;flex-direction:column;justify-content:center;margin-left:.6rem}html body .main-container .main-main .div-info .comment-form .comment-header .comment-person .comment-info .comment-info-top{font-weight:550;font-size:var(--theme-text)}html body .main-container .main-main .div-info .comment-form .comment-header .comment-person .comment-info .comment-info-bottom{color:var(--theme-text-secondary);font-size:var(--theme-secondary)}html body .main-container .main-main .div-info .comment-form .comment-header .comment-loginout{width:30%;display:flex;justify-content:flex-end}html body .main-container .main-main .div-info .comment-form .comment-header .comment-loginout a{margin:0 .3rem;cursor:pointer;text-decoration:none;border:none}html body .main-container .main-main .div-info .comment-form .comment-header .comment-loginout a:hover{color:var(--theme-color)}html body .main-container .main-main .div-info .comment-form .comment-form-main{padding:0 .35rem;display:flex;flex-direction:column}html body .main-container .main-main .div-info .comment-form .comment-form-main textarea,html body .main-container .main-main .div-info .comment-form .comment-form-main input{border:1px solid #cfd4db;background-color:var(--theme-front-main-color)}html body .main-container .main-main .div-info .comment-form .comment-form-main textarea::placeholder,html body .main-container .main-main .div-info .comment-form .comment-form-main input::placeholder{font-size:var(--theme-secondary);color:var(--theme-placeholder)}html body .main-container .main-main .div-info .comment-form .comment-form-main textarea:hover,html body .main-container .main-main .div-info .comment-form .comment-form-main textarea:focus,html body .main-container .main-main .div-info .comment-form .comment-form-main input:hover,html body .main-container .main-main .div-info .comment-form .comment-form-main input:focus{box-shadow:0 0 5px 2px var(--theme-color-20);border-color:var(--theme-color);outline:none}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea{position:relative}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea .comment-content{width:100%;margin-bottom:.7rem;padding:.35rem .5rem;color:var(--theme-text-color);font-size:var(--theme-secondary)}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea .focus{box-shadow:0 0 5px 2px var(--theme-color-20);border-color:var(--theme-color);outline:none}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea .emoji{position:absolute;right:15px;bottom:15px;cursor:pointer;color:var(--theme-text-secondary);font-size:var(--theme-emoji-size)}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea .emoji:hover{color:var(--theme-color)}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea .emoji-list{display:flex;align-items:center;flex-wrap:wrap;position:absolute;right:10px;bottom:36px;list-style:none;display:none;user-select:none;width:65%;padding:20px 10px 10px 15px;border-radius:5px;box-shadow:0 12px 32px var(--theme-hover-shadow),0 2px 6px var(--theme-hover-shadow),0 -2px 6px var(--theme-hover-shadow);border:none;background-color:var(--theme-front-color)}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea .emoji-list li{cursor:pointer;padding:1px}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea .emoji-list li img{width:25px}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea .emoji-list:before{position:absolute;top:100%;right:10px;width:0;height:0;content:" ";border:6px solid transparent;border-top-color:var(--theme-front-color)}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-container{display:flex;width:100%;justify-content:space-between;margin-bottom:1.2rem;border:none;box-shadow:unset!important}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-container *{height:2.1rem;padding:0 .5rem}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-container input{font-size:var(--theme-secondary)}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-container input::placeholder{color:var(--theme-placeholder)}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-container .comment-name{width:33%}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-container .comment-mail{width:33%;margin:0 .5rem}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-container .comment-url{width:33%}html body .main-container .main-main .div-info .comment-form .comment-form-footer{display:flex;justify-content:flex-end}html body .main-container .main-main .div-info .comment-form .comment-form-footer button{color:var(--theme-color);padding:.2rem 2rem;background-color:var(--theme-front-color);border:1px solid var(--theme-color);cursor:pointer}html body .main-container .main-main .div-info .comment-form .comment-form-footer button:hover{color:#fff;background-color:var(--theme-color)}html body .main-container .main-main .div-info .pagination{width:100%;display:flex;justify-content:center;padding:1rem 0}html body .main-container .main-main .div-info .pagination a,html body .main-container .main-main .div-info .pagination span{line-height:1.5;background-color:#0000000d;padding:.3rem .72rem;margin:0 .32rem;font-size:14px;color:var(--theme-text-secondary);border-radius:4px;text-decoration:none}html body .main-container .main-main .div-info .pagination span.current,html body .main-container .main-main .div-info .pagination a:hover{padding:.3rem .72rem;font-size:14px;background-color:var(--theme-color);color:#fff;border-radius:4px}html body .main-container .main-main .div-info .comment_list{padding:.5rem .5rem 3rem;margin-top:.2rem}html body .main-container .main-main .div-info .comment_list *{text-decoration:none!important;font-style:normal!important}html body .main-container .main-main .div-info .comment_list ul{list-style:none;padding-left:2.5rem}html body .main-container .main-main .div-info .comment_list .front{margin-bottom:0;padding-left:0}html body .main-container .main-main .div-info .comment_list li .comment-body{display:flex;flex-wrap:wrap;border-top:1px solid #e1e1e170;padding:1rem 0}html body .main-container .main-main .div-info .comment_list li .comment-body a{color:inherit;font-size:inherit}html body .main-container .main-main .div-info .comment_list li .comment-body .comment-author{order:1;width:100%;display:flex;align-items:center}html body .main-container .main-main .div-info .comment_list li .comment-body .comment-author .url{font-size:var(--theme-third-level-title)}html body .main-container .main-main .div-info .comment_list li .comment-body .comment-author .says{margin-left:.3rem}html body .main-container .main-main .div-info .comment_list li .comment-body .comment-author .avatar{width:2.5rem;height:2.5rem;margin-right:1rem;border-radius:50%;box-shadow:var(--theme-shadow)}html body .main-container .main-main .div-info .comment_list li .comment-body .comment-content{order:2;display:block;width:100%;padding-left:3.5rem;margin:.83rem 0 0}html body .main-container .main-main .div-info .comment_list li .comment-body .comment-content img{vertical-align:middle;max-width:25px;margin:0 1px}html body .main-container .main-main .div-info .comment_list li .comment-body .comment-awaiting-moderation{margin:1.2rem 3.5rem;color:var(--theme-color)}html body .main-container .main-main .div-info .comment_list li .comment-body .comment-meta{order:3;padding-left:3.5rem;color:var(--theme-text-secondary);font-size:var(--theme-text-mini)}html body .main-container .main-main .div-info .comment_list li .comment-body .comment-meta .comment-edit-link{display:none}html body .main-container .main-main .div-info .comment_list li .comment-body .reply{color:var(--theme-text-secondary);font-size:var(--theme-text-mini);margin-left:.3rem;order:4}html body .main-container .main-main .div-info .comment_list li .comment-body .reply:hover{color:var(--theme-text)}html body .main-container .main-main .recommend{margin-bottom:1rem;padding:0;overflow:hidden}html body .main-container .main-main .recommend .list{width:100%;padding:0 1rem 1rem;margin:0;position:relative}html body .main-container .main-main .recommend .list .swiper-slide{width:calc(20% - 12px);display:inline-block;margin:1rem 12px 0 0;border-radius:4px;flex-shrink:0;border:1px solid rgba(0,0,0,.1);transition-property:all;transition-duration:.3s}html body .main-container .main-main .recommend .list .swiper-slide:hover{box-shadow:0 2px 10px 3px #00000026;transform:translateY(-3px)}html body .main-container .main-main .recommend .list .swiper-slide a{display:flex;flex-direction:column;text-decoration:none;height:100%;position:relative}html body .main-container .main-main .recommend .list .swiper-slide a .thumnbnail{width:100%;height:120px;border-top-left-radius:4px;border-top-right-radius:4px;overflow:hidden}html body .main-container .main-main .recommend .list .swiper-slide a .thumnbnail img{width:100%;height:100%;object-fit:cover}html body .main-container .main-main .recommend .list .swiper-slide a .caption{font-size:var(--theme-text);color:var(--theme-text-color);margin:.8rem .65rem;display:-webkit-box;line-height:1.5;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;min-height:2.5rem}html body .main-container .main-main .recommend .list .swiper-slide a .detail{display:flex;align-items:center;justify-content:space-between;margin:0 .5rem .5rem;white-space:nowrap}html body .main-container .main-main .recommend .list .swiper-slide a .detail *{font-size:var(--theme-text-mini);color:var(--theme-text-mini-color)}html body .main-container .main-main .recommend .list .swiper-slide a .category{position:absolute;top:6px;left:6px;width:fit-content;padding:0 .5rem;border-radius:5px;font-size:var(--theme-text-mini);color:#fff;background-color:#0009}html body .main-container .main-main .recommend .list .swiper-slide a .category:hover{color:#fff;transform:scale(1);background-color:var(--theme-color)}html body .main-container .main-main .recommend .list .swiper-button-next,html body .main-container .main-main .recommend .list .swiper-button-prev{color:var(--theme-color)}html body .main-container .main-main .recommend .empty{display:flex;align-items:center;flex-direction:column}html body .main-container .main-main .recommend .empty img{max-width:25%;min-height:80px}html body .main-container .main-main .recommend .empty span{margin-top:-25px;margin-bottom:20px;font-size:var(--theme-text-mini);color:var(--theme-text-mini-color)}html body .main-container .main-main .no-title{background-color:transparent}html body .main-container .main-main .no-title .list{background-color:transparent;padding:0}html body .main-container .main-main .no-title .list .swiper-slide{margin-top:0;background-color:#fff}html body .main-bottom{width:100%;text-align:center;color:var(--theme-footer-font-color);padding:2rem 1rem;font-size:var(--theme-text-mini);background-color:var(--theme-footer-bg-color)}html body .main-bottom *{font-size:inherit;color:inherit;text-decoration:none}html body .main-bottom .plain,html body .main-bottom a{color:inherit}html body .fixed{display:flex;flex-direction:column;position:fixed;z-index:1000;right:.5rem;bottom:10vh}html body .fixed #theme-color{position:relative}html body .fixed #theme-color .iconfont{color:inherit;font-size:1rem}html body .fixed #theme-color:hover .theme-color{padding:0 .2rem;width:15.6rem}html body .fixed #theme-color .theme-color{display:flex;align-items:center;width:0;height:120%;position:absolute;right:calc(100% + .3rem);top:-10%;transition:all;transition-duration:.2s;box-shadow:0 12px 32px var(--theme-hover-shadow),0 2px 6px var(--theme-hover-shadow);background-color:var(--theme-front-color)}html body .fixed #theme-color .theme-color ul{width:100%;list-style:none;margin:0;padding:0;display:flex}html body .fixed #theme-color .theme-color ul li{width:15%;margin:0 1%;display:inline-block}html body .fixed #theme-color .theme-color ul li div{border-radius:4px;cursor:pointer;height:0;padding-bottom:100%;background-color:var(--theme-color)}html body .fixed button{height:1.8rem;width:1.6rem;white-space:nowrap;outline:none;position:relative;box-sizing:content-box;display:flex;justify-content:center;align-items:center;cursor:pointer;padding-top:.2rem;box-shadow:0 12px 32px var(--theme-hover-shadow),0 2px 6px var(--theme-hover-shadow);border:none;background-color:var(--theme-front-color);margin-top:1vh;border-radius:3px;font-weight:bolder;font-size:.5rem}html body .fixed button:hover{background-color:var(--theme-hover-bg)!important;color:var(--theme-hover-color)}html body .fixed .toTop{visibility:hidden}html body .fixed .toTop .iconfont{color:inherit;transform:rotate(0)!important;font-size:1rem}html body .index .main-main .non-result{background-color:var(--theme-front-main-color);width:100%;padding:.8rem 2rem;border-radius:4px;display:flex;margin-bottom:1rem;flex-direction:column}html body .index .main-main .non-result .in{display:flex;align-items:center;margin-bottom:0;font-weight:lighter;font-size:1.1rem}html body .index .main-main .non-result .in .belong{font-size:var(--theme-first-level-title);font-weight:550;color:var(--theme-color);margin-right:.5rem}html body .index .main-main .non-result .in *{font-size:inherit;font-weight:inherit;color:inherit}html body .index .main-main .non-result .number{display:flex;font-size:1rem;font-weight:lighter;align-items:center}html body .index .main-main .non-result .number .iconfont{font-size:1.5rem;margin-right:.5rem;color:var(--theme-text-color)}html body .index .main-main .main-content{padding-top:1.2rem;margin-bottom:0}html body .index .main-main .main-content .pagination{width:100%;display:flex;justify-content:center;padding:1rem 0 1.8rem}html body .index .main-main .main-content .pagination .loadnext{background-color:transparent;border:1px solid var(--theme-color);color:var(--theme-color);font-size:var(--theme-text-mini);padding:3px 2.3rem;border-radius:5px;cursor:pointer;outline:none}html body .index .main-main .main-content .pagination .loadnext span{color:inherit;font-size:inherit}html body .index .main-main .main-content .pagination .loadnext .iconfont{color:inherit;font-size:inherit;display:none;animation-iteration-count:infinite;animation-duration:.3s;animation-timing-function:linear}html body .index .main-main .main-content .pagination .loadnext .animate-rotate{animation-name:rotate}html body .index .main-main .main-content .pagination .loadnext:active{background-color:var(--theme-color);color:#fff}html body .index .main-main .main-content .pagination ul{list-style:none;margin:0;padding:0}html body .index .main-main .main-content .pagination ul li{display:inline;margin:0 2px}html body .index .main-main .main-content .pagination ul li a,html body .index .main-main .main-content .pagination ul li span{background-color:#0000000d;font-size:.72rem;padding:.3rem .72rem;color:var(--theme-text-secondary);border-radius:4px;text-decoration:none}html body .index .main-main .main-content .pagination ul li span.current,html body .index .main-main .main-content .pagination ul li a:hover{padding:.3rem .72rem;font-size:.72rem;background-color:var(--theme-color);color:#fff;border-radius:4px}html body .index .main-main .main-content .article-list{overflow:hidden}html body .index .main-main .main-content .i-article{margin-bottom:1.05rem;display:flex;justify-content:flex-start;border-bottom:1px solid #e1e1e1;position:relative;padding:0 0 1.05rem}html body .index .main-main .main-content .i-article:first-of-type{margin-top:.85rem}html body .index .main-main .main-content .i-article .i-article-summary{width:100%;overflow:hidden}html body .index .main-main .main-content .i-article .i-article-summary .i-article-title{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-weight:400;font-size:1.12rem;line-height:1.55;color:inherit;text-overflow:ellipsis}html body .index .main-main .main-content .i-article .i-article-summary .i-article-title a{text-decoration:none;font-size:inherit}html body .index .main-main .main-content .i-article .i-article-summary .i-article-title a:hover{color:var(--theme-color)}html body .index .main-main .main-content .i-article .i-article-summary .i-article-excerpt{font-size:.8rem;color:var(--theme-text-secondary);margin:.6rem 0 1rem;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;min-height:2.5rem}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info{margin-top:.6rem;font-size:.8rem;color:var(--theme-text-secondary);display:flex;justify-content:space-between}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul{list-style:none;margin:0;white-space:nowrap;padding:0}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul .category{position:absolute;top:.9rem;right:.3rem;z-index:99;color:#fff;padding:0 .5rem;border-radius:5px;background-color:#0009;border-right:none}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul .category:hover{color:#fff;transform:scale(1);background-color:var(--theme-color)}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul .category-left{right:unset;left:.3rem}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul li{padding:0 .45rem;border-right:1px solid #e1e1e1;cursor:pointer;display:inline;white-space:nowrap;font-size:var(--theme-text-mini);color:var(--theme-text-mini-color)}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul li a{color:inherit;font-size:inherit}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul li.first{padding:0 .45rem 0 0}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul li:hover{color:var(--theme-text-color);transform:scale(1.1)}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul li:hover .iconfont{color:var(--theme-text-color)}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul li .iconfont{font-size:var(--theme-text-mini);color:var(--theme-text-mini-color);margin-right:.24rem}html body .index .main-main .main-content .i-article .i-article-thumb{width:24%;flex-shrink:0;margin-left:1.5rem;overflow:hidden;height:inherit;display:flex;align-items:center;border-radius:3px}html body .index .main-main .main-content .i-article .i-article-thumb a{display:flex;overflow:hidden;border-radius:3px;height:94%;width:100%}html body .index .main-main .main-content .i-article .i-article-thumb a img{width:100%;object-fit:cover;max-height:140px;margin:0;border-radius:3px;transition:all;transition-duration:.5s}html body .index .main-main .main-content .i-article .i-article-thumb a img:hover{transform:scale(1.35)}html body .index .main-main .main-content .i-article .i-article-left{margin:0 1.5rem 0 0;order:1}html body .index .main-main .nopage .i-article:last-of-type{margin-bottom:.5rem;border-bottom:none}html body .index .main-main .hasDynamic{padding-top:0}@media(min-width:1024px)and (max-width:1580px){html body .main-container .main-main{width:55%}html body .main-container .main-main .recommend .list .swiper-slide{width:calc(25% - 12px)}}@media(max-width:1024px){html{min-width:unset}html body .main-container{justify-content:center}html body .main-container .main-main{margin-left:0}html body .main-header{min-width:unset;padding:0 1rem 0 1.25rem}html body .main-header .daohang{display:block;font-size:1.6rem}html body .main-header .right{flex-direction:column;justify-content:flex-start;align-items:flex-start;position:fixed;top:0;left:-70%;width:60%;background:var(--theme-sub-menu-bg-color);padding:1rem .7rem;box-shadow:0 12px 32px var(--theme-hover-shadow);transition-property:all;transition-duration:.3s;overflow:auto}html body .main-header .right::-webkit-scrollbar{display:none}html body .main-header .right .menu-left{margin:0;padding:0 .2rem;width:100%}html body .main-header .right .menu-left .search-div{display:flex;width:100%}html body .main-header .right .menu-left .search-div .iconfont{display:none}html body .main-header .right .menu-left .search-div .search{height:35px;width:100%;flex-shrink:0;padding:0 1rem 0 .6rem;color:var(--theme-sub-menu-font-color)}html body .main-header .right .menu-left .search-icon{height:unset;margin-right:.2rem;line-height:normal;color:var(--theme-sub-menu-font-color)}html body .main-header .right .menu{align-content:center;flex-direction:column;align-items:flex-start;padding:0;width:100%;height:unset}html body .main-header .right .menu .menu-item{width:100%;margin:0;box-sizing:border-box;padding:.5rem 1rem;color:var(--theme-sub-menu-font-color);justify-content:space-between;font-weight:lighter;flex-wrap:wrap;font-size:var(--theme-third-level-title);border-bottom:1px solid rgba(0,0,0,.05)}html body .main-header .right .menu .menu-item i{line-height:normal}html body .main-header .right .menu .menu-item span{font-size:1rem}html body .main-header .right .menu .menu-item-has-children>a:after{display:none}html body .main-header .right .menu .menu-item-has-children .sub-menu{display:none}html body .main-header .right .menu .read-mode{position:absolute!important;display:none;top:1rem;z-index:50;right:12px;margin-top:0;height:fit-content;width:fit-content;color:var(--theme-front-color)}html body .main-header .right .menu .read-mode img{width:1.2rem;padding:0}html body .main-container .main-main .non-result{padding:.8rem 1rem}html body .main-container .main-main .main-content .main-article .container-image img{max-width:100%}}@media(min-width:768px)and (max-width:1024px){html{min-width:unset}html body .password img{width:50%}html body .main-container .main-main{width:80%}html body .main-container .main-main .recommend .list .swiper-slide{width:calc(25% - 12px)}html body .main-container #space,html body .main-container #fixed{display:none}}@media(min-width:481px)and (max-width:767px){html{min-width:unset}html body .password img{width:45%}html body .main-container .main-main{width:95%}html body .main-container .main-main .recommend .list .swiper-slide{width:calc(33.3% - 12px)}html body .main-container #space,html body .main-container #fixed{display:none}}@media(max-width:480px){:root{--theme-first-level-title: 1.25rem;--theme-text: .95rem;--theme-secondary: .85rem;--theme-text-mini: .75rem;--theme-text-more-mini: .75rem;--theme-secondary-level-title: 1.05rem;--theme-third-level-title: 1rem;--theme-fourth-level-title: .95rem;--theme-header-size: 1.15rem;--theme-margin-bottom: 1.1rem;--theme-margin-bottom-max: 1.2rem}html{min-width:unset}html body h1{margin-top:.6rem;line-height:1.5}html body #category{padding-left:0!important}html body .main-header{height:55px;border-top:1px solid var(--theme-header-border);border-bottom:none;box-shadow:0 0 8px #0000001a}html body .main-header .daohang{font-size:1.45rem}html body .main-header .right .menu-left .menu .menu-item{font-size:var(--theme-text)}html body .password img{width:60%}html body .main-container{margin-top:55px}html body .main-container .div-info{padding:.1rem 1rem .85rem 1.1rem}html body .main-container .main-main{width:100%}html body .main-container .main-main .non-result{margin-top:1rem}html body .main-container .main-main .non-result .in{font-size:var(--theme-header-size)}html body .main-container .main-main .non-result .in .belong{font-size:inherit}html body .main-container .main-main .non-result .recommend .list .swiper-slide{width:calc(50% - 12px)}html body .main-container .main-main .main-bottom{padding:0 .3rem}html body .main-container .main-main .main-content{padding:1.1rem 1rem 0}html body .main-container .main-main .main-content .main-article h2{font-weight:700!important}html body .main-container .main-main .main-content .main-article h3,html body .main-container .main-main .main-content .main-article h4{font-weight:700}html body .main-container .main-main .main-content .breadcrumb{padding-bottom:.75rem}html body .main-container .main-main .main-content .dynamic ul.list .tab{font-size:var(--theme-text)}html body .main-container #space,html body .main-container #fixed{display:none}html body .index .main-main .main-content{padding:1rem .9rem 0}html body .index .main-main .main-content .i-article{margin-top:.9rem;padding-bottom:.9rem}html body .index .main-main .main-content .i-article h2{margin:0;min-height:50px}html body .index .main-main .main-content .i-article:first-of-type{margin-top:1rem}html body .index .main-main .main-content .i-article .i-article-thumb{width:30%;margin-left:.5rem}html body .index .main-main .main-content .i-article .i-article-thumb a{display:flex;align-items:flex-start;height:90%}html body .index .main-main .main-content .i-article .i-article-thumb a img{min-height:64px;max-height:75px;height:100%}html body .index .main-main .main-content .i-article .i-article-left{margin-right:.5rem;margin-left:0}html body .index .main-main .main-content .i-article .i-article-summary{display:flex;flex-direction:column;justify-content:space-between}html body .index .main-main .main-content .i-article .i-article-summary .i-article-title{font-size:var(--theme-text)}html body .index .main-main .main-content .i-article .i-article-summary .i-article-title a{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;line-height:1.55}html body .index .main-main .main-content .i-article .i-article-summary .i-article-excerpt{display:none}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info{margin-top:.25rem}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul li{font-size:var(--theme-text-mini)}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info ul li:nth-child(3){padding-left:0}html body .index .main-main .main-content .i-article .i-article-summary .i-article-info .category{display:none}html body .index .main-main .hasDynamic{padding-top:0}html body #author{display:none}html body .main-container .main-main .div-info .comment-form .comment-form-main .comment-textarea .emoji-list{width:80%}}html body .main-container .main-main .main-content .main-article blockquote{margin:1rem 0;padding:.75rem 1rem;border-left:4px solid var(--theme-color);background:#0000000a;border-radius:6px}html body .main-container .main-main .main-content .main-article blockquote p{margin:.4rem 0}html body .main-container .main-main .main-content .main-article details{margin:1rem 0;padding:0;border:1px solid rgba(0,0,0,.1);border-radius:6px;background:#00000005;overflow:hidden;transition:all .3s ease}html body .main-container .main-main .main-content .main-article details:hover{border-color:var(--theme-color);box-shadow:0 2px 8px #00000014}html body .main-container .main-main .main-content .main-article details[open]{background:#00000008;border-color:var(--theme-color)}html body .main-container .main-main .main-content .main-article details summary{padding:.75rem 1rem;cursor:pointer;font-weight:500;color:var(--theme-text-color);background:#00000005;border-bottom:1px solid rgba(0,0,0,.05);user-select:none;list-style:none;position:relative;transition:all .2s ease}html body .main-container .main-main .main-content .main-article details summary::-webkit-details-marker{display:none}html body .main-container .main-main .main-content .main-article details summary:before{content:"\25b6";display:inline-block;margin-right:.5rem;transition:transform .2s ease;color:var(--theme-color);font-size:.8em}html body .main-container .main-main .main-content .main-article details[open] summary:before{transform:rotate(90deg)}html body .main-container .main-main .main-content .main-article details summary:hover{background:#0000000a;color:var(--theme-color)}html body .main-container .main-main .main-content .main-article details summary:active{background:#0000000f}html body .main-container .main-main .main-content .main-article details>*:not(summary){padding:1rem;margin:0;background:#ffffff80}html body .main-container .main-main .main-content .main-article details>*:not(summary):first-of-type{padding-top:1rem}html body .main-container .main-main .main-content .main-article details>*:not(summary):last-child{padding-bottom:1rem}html body .main-container .main-main .main-content .main-article details p{margin:.5rem 0;line-height:1.6;color:var(--theme-text-color)}html body .main-container .main-main .main-content .main-article details p:first-child{margin-top:0}html body .main-container .main-main .main-content .main-article details p:last-child{margin-bottom:0}html body .main-container .main-main .main-content .main-article details h1,html body .main-container .main-main .main-content .main-article details h2,html body .main-container .main-main .main-content .main-article details h3,html body .main-container .main-main .main-content .main-article details h4,html body .main-container .main-main .main-content .main-article details h5,html body .main-container .main-main .main-content .main-article details h6{margin-top:1rem;margin-bottom:.5rem;color:var(--theme-text-color)}html body .main-container .main-main .main-content .main-article details h1:first-child,html body .main-container .main-main .main-content .main-article details h2:first-child,html body .main-container .main-main .main-content .main-article details h3:first-child,html body .main-container .main-main .main-content .main-article details h4:first-child,html body .main-container .main-main .main-content .main-article details h5:first-child,html body .main-container .main-main .main-content .main-article details h6:first-child{margin-top:0}html body .main-container .main-main .main-content .main-article details ul,html body .main-container .main-main .main-content .main-article details ol{margin:.5rem 0;padding-left:1.5rem}html body .main-container .main-main .main-content .main-article details code{background:var(--theme-code-bg);padding:.2em .4em;border-radius:3px;font-size:.9em}html body .main-container .main-main .main-content .main-article details pre{background:var(--theme-pre-code-bg);padding:1rem;border-radius:6px;overflow-x:auto;margin:.5rem 0}html body .main-container .main-main .main-content .main-article details blockquote{margin:.5rem 0;padding:.5rem 1rem;border-left:3px solid var(--theme-color);background:#00000005;border-radius:4px} diff --git a/style.scss b/style.scss index 739fad3..3946c54 100644 --- a/style.scss +++ b/style.scss @@ -4,10 +4,10 @@ /* Theme Name: Document Theme URI: https://nicen.cn -Author: 友人a丶 +Author: 友人a丶 Haibin Author URI: https://nicen.cn Description: 一个基于文档类型的博客主题,更加方便的记录、查询学习笔记 -Version: 1.0 +Version: 1.3.1 License: GNU General Public License v2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Text Domain: Document diff --git a/template/index/article-footer.php b/template/index/article-footer.php index 0cdf781..6394cac 100644 --- a/template/index/article-footer.php +++ b/template/index/article-footer.php @@ -25,7 +25,7 @@ @@ -68,21 +68,21 @@ diff --git a/template/index/article-header.php b/template/index/article-header.php index 42b770e..61e9aaf 100644 --- a/template/index/article-header.php +++ b/template/index/article-header.php @@ -39,8 +39,9 @@
  • -
  • 热度
  • -
  • 评论
  • +
  • Views
  • +
  • Comments
  • +
  • Words
  • - 热度 + Views
  • 评论 + class="iconfont icon-pinglun"> Comments
  • diff --git a/template/index/breadcrumb.php b/template/index/breadcrumb.php index ad51002..64c0b30 100644 --- a/template/index/breadcrumb.php +++ b/template/index/breadcrumb.php @@ -36,7 +36,7 @@ ?>
    • - 最新文章 + Latest Articles
    • - 的文章列表 + article list
      - 共found_posts; ?>篇文章 + found_posts; ?> articles
      diff --git a/template/index/search.php b/template/index/search.php index cc4b8cf..0eac117 100644 --- a/template/index/search.php +++ b/template/index/search.php @@ -9,6 +9,6 @@
      - + diff --git a/template/index/sidebar-index-left.php b/template/index/sidebar-index-left.php index d1e998b..433a995 100644 --- a/template/index/sidebar-index-left.php +++ b/template/index/sidebar-index-left.php @@ -20,7 +20,7 @@