chore: CLAUDE 移至 .claude、清理 obsolete 文件、火山销售系统更新
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+172
-3
@@ -1,5 +1,174 @@
|
||||
# AIBIS Dream - Project Context
|
||||
<!-- # CLAUDE.md
|
||||
|
||||
Full project documentation is in the repository root:
|
||||
Claude Code 与 Cursor 的项目上下文,对话开始时自动加载。
|
||||
|
||||
@../CLAUDE.md
|
||||
## 项目概览
|
||||
|
||||
AIBIS Dream(又名 AllOurBrokenParts)是一款基于 Unity 的叙事冒险游戏,带有调查元素。使用 Yarn Spinner 管理对话,采用自定义模块化框架,包含多个小游戏(核心之一是火山 HuoShan 的语言粒子系统)。
|
||||
|
||||
**Unity 版本:** Unity 2022.3.x LTS(Universal Render Pipeline)
|
||||
|
||||
## 架构
|
||||
|
||||
### 核心框架(Assets/Scripts/Framework/)
|
||||
|
||||
- **ActionKit**:动作序列与命令模式
|
||||
- **AudioKit**:音频管理与音效
|
||||
- **Config**:配置与设置
|
||||
- **Core**:核心系统与工具
|
||||
- **EventSystemKit**:解耦事件系统
|
||||
- **PoolKit**:对象池
|
||||
- **ResourceKit**:资源加载
|
||||
- **SingletonKit**:单例基类
|
||||
- **StateMachineKit**:状态机
|
||||
- **TimelineKit**:Timeline 事件序列
|
||||
|
||||
### 主要游戏系统
|
||||
|
||||
- **Dialog System**(Assets/Scripts/Dialog System/):Yarn Spinner 对话
|
||||
- **Clue System**(Assets/Scripts/Clue/):调查与证据收集
|
||||
- **MiniGame System**(Assets/Scripts/MiniGame/):小游戏框架
|
||||
- **HuoShan(火山)**:语言粒子系统,用于情绪表达玩法
|
||||
- **SceneManagement**:场景与流程
|
||||
- **FixSystem**:互动维修/解谜
|
||||
|
||||
### Web 原型
|
||||
|
||||
位于 `web-prototype/` 与 `WebPrototype/`,用于在 Unity 实现前验证玩法。
|
||||
|
||||
### 设计文档(Docs)
|
||||
|
||||
**任何开发工作,按需阅读对应的设计文档。** 文档采用单源真相,定义只维护一份。
|
||||
|
||||
| 索引 | 说明 |
|
||||
|------|------|
|
||||
| `Docs/HuoShan/INDEX.md` | 火山文档入口 |
|
||||
|
||||
## 常用开发命令
|
||||
|
||||
### Unity 构建
|
||||
|
||||
```bash
|
||||
# Windows 64-bit
|
||||
"C:\Program Files\Unity\Hub\Editor\2022.3.x\Editor\Unity.exe" -batchmode -quit -projectPath . -buildWindows64Player "Build/AIBIS_Dream.exe" -logFile Build/log.txt
|
||||
|
||||
# WebGL
|
||||
"C:\Program Files\Unity\Hub\Editor\2022.3.x\Editor\Unity.exe" -batchmode -quit -projectPath . -executeMethod BuildScript.WebGL -logFile Build/webgl_log.txt
|
||||
```
|
||||
|
||||
### 资源与工程
|
||||
|
||||
```bash
|
||||
# 重新生成 Visual Studio 工程
|
||||
Assets\Open C# Project.regenerate-sln.bat
|
||||
# 刷新资源库:菜单 Assets > Refresh 或 Ctrl+R
|
||||
```
|
||||
|
||||
## 关键依赖
|
||||
|
||||
### Unity 包
|
||||
|
||||
- Yarn Spinner、TextMesh Pro、URP、2D Animation、Cinemachine、Timeline、Localization
|
||||
|
||||
### 第三方资源
|
||||
|
||||
- More Mountains Feedbacks、FMOD、DOTween、Shapes、QFramework、Destructible 2D
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 代码组织
|
||||
|
||||
1. 各系统放在 `Assets/Scripts/` 下独立文件夹
|
||||
2. 可复用工具以 Kit 后缀放在 `Framework/`
|
||||
3. 全局管理类使用 Singleton
|
||||
4. 组件优先于继承
|
||||
|
||||
### 场景结构
|
||||
|
||||
- 场景在 `Assets/Scenes/`
|
||||
- 主场景命名:`{角色}_{Day}{时间}`(如 `Fiction_Day1_begin`)
|
||||
- 小游戏场景:`HuoShan*`、`languageTest*` 等
|
||||
- 需加入 EditorBuildSettings
|
||||
|
||||
### Yarn 对话文件
|
||||
|
||||
- 位于 `Assets/Resources/Yarn/`,按角色与日期组织
|
||||
- 文件命名:`{角色}_{阶段}.yarn`
|
||||
- 使用 UTF-8
|
||||
|
||||
### 资源命名
|
||||
|
||||
- **脚本**:PascalCase,带用途(如 `DialogController.cs`)
|
||||
- **Prefab**:PascalCase 加类型后缀
|
||||
- **场景**:Snake_Case
|
||||
- **Resources**:按系统/类型分组
|
||||
|
||||
## 配置要点
|
||||
|
||||
### 编辑器
|
||||
|
||||
- 文本编码:UTF-8(尤其中文)
|
||||
- 行尾:C# 使用 Windows (CRLF),保持一致
|
||||
|
||||
### 构建
|
||||
|
||||
- 平台:Windows、WebGL
|
||||
- Scripting Backend:IL2CPP(Windows)、Emscripten(WebGL)
|
||||
- API:.NET Standard 2.1
|
||||
|
||||
### Git
|
||||
|
||||
- LFS:大文件(.unity、贴图、音频)
|
||||
- 忽略:Library/、Temp/、Logs/、Build/、*.csproj.user
|
||||
|
||||
## 测试与迭代
|
||||
|
||||
### 小游戏独立测试
|
||||
|
||||
- 火山语言游戏:`Assets/Scenes/languageTest.unity`
|
||||
- 火山维修:`Assets/Scenes/HuoShanFixScene.unity`
|
||||
- 表情测试:`Assets/Scenes/HuoShanExpressionTest.unity`
|
||||
|
||||
### Web 原型
|
||||
|
||||
1. 在 `web-prototype/` 建 HTML/JS 原型
|
||||
2. 逻辑与 Unity 实现保持一致
|
||||
3. 浏览器快速验证
|
||||
|
||||
## 常见任务
|
||||
|
||||
### 新增小游戏
|
||||
|
||||
1. 建文件夹 `Assets/Scripts/MiniGame/{名称}/`
|
||||
2. 用 Framework 实现逻辑
|
||||
3. 建测试场景 `{名称}Test.unity`
|
||||
4. 资源放 `Assets/Resources/{名称}/`
|
||||
5. 在 `README_{名称}.md` 中记录
|
||||
|
||||
### 添加 Yarn 对话
|
||||
|
||||
1. 在 `Assets/Resources/Yarn/{角色}/` 建 `.yarn`
|
||||
2. 通过 Resources.Load 加载
|
||||
3. 挂到场景的 DialogueRunner
|
||||
4. 用 DialogueController 测试
|
||||
|
||||
### 调试
|
||||
|
||||
- LogKit 做分类日志
|
||||
- ProjectSettings/LogKit 可调详细级别
|
||||
- 粒子多时用 Unity Profiler 排查
|
||||
|
||||
## 性能
|
||||
|
||||
- 频繁创建对象用 PoolKit
|
||||
- 资源加载用 ResourceKit 异步
|
||||
- 粒子系统注意 overdraw
|
||||
- 音效用池,复杂音频用 FMOD
|
||||
|
||||
## 重要提醒
|
||||
|
||||
- 以叙事为核心,对话和剧情优先
|
||||
- 火山语言粒子是核心玩法,改动需保持情绪表达逻辑
|
||||
- Web 原型仅供验证,正式实现用 Unity/C#
|
||||
- 保持 Windows 与 WebGL 兼容
|
||||
- **开发前按需读 `Docs/` 下对应索引,避免与设计偏离** -->
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
description: AIBIS Dream 项目核心上下文
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# AIBIS Dream - 项目概览
|
||||
|
||||
Unity 叙事冒险游戏(AllOurBrokenParts),使用 Yarn Spinner 对话。
|
||||
|
||||
**Unity**: 2022.3.x LTS (URP) | **关键路径**: `Assets/Scripts/Framework/`、`MiniGame/HuoShan/`、`Resources/Yarn/`
|
||||
|
||||
完整文档见项目根目录 `CLAUDE.md`。
|
||||
@@ -1,174 +0,0 @@
|
||||
<!-- # CLAUDE.md
|
||||
|
||||
Claude Code 与 Cursor 的项目上下文,对话开始时自动加载。
|
||||
|
||||
## 项目概览
|
||||
|
||||
AIBIS Dream(又名 AllOurBrokenParts)是一款基于 Unity 的叙事冒险游戏,带有调查元素。使用 Yarn Spinner 管理对话,采用自定义模块化框架,包含多个小游戏(核心之一是火山 HuoShan 的语言粒子系统)。
|
||||
|
||||
**Unity 版本:** Unity 2022.3.x LTS(Universal Render Pipeline)
|
||||
|
||||
## 架构
|
||||
|
||||
### 核心框架(Assets/Scripts/Framework/)
|
||||
|
||||
- **ActionKit**:动作序列与命令模式
|
||||
- **AudioKit**:音频管理与音效
|
||||
- **Config**:配置与设置
|
||||
- **Core**:核心系统与工具
|
||||
- **EventSystemKit**:解耦事件系统
|
||||
- **PoolKit**:对象池
|
||||
- **ResourceKit**:资源加载
|
||||
- **SingletonKit**:单例基类
|
||||
- **StateMachineKit**:状态机
|
||||
- **TimelineKit**:Timeline 事件序列
|
||||
|
||||
### 主要游戏系统
|
||||
|
||||
- **Dialog System**(Assets/Scripts/Dialog System/):Yarn Spinner 对话
|
||||
- **Clue System**(Assets/Scripts/Clue/):调查与证据收集
|
||||
- **MiniGame System**(Assets/Scripts/MiniGame/):小游戏框架
|
||||
- **HuoShan(火山)**:语言粒子系统,用于情绪表达玩法
|
||||
- **SceneManagement**:场景与流程
|
||||
- **FixSystem**:互动维修/解谜
|
||||
|
||||
### Web 原型
|
||||
|
||||
位于 `web-prototype/` 与 `WebPrototype/`,用于在 Unity 实现前验证玩法。
|
||||
|
||||
### 设计文档(Docs)
|
||||
|
||||
**任何开发工作,按需阅读对应的设计文档。** 文档采用单源真相,定义只维护一份。
|
||||
|
||||
| 索引 | 说明 |
|
||||
|------|------|
|
||||
| `Docs/HuoShan/INDEX.md` | 火山文档入口 |
|
||||
|
||||
## 常用开发命令
|
||||
|
||||
### Unity 构建
|
||||
|
||||
```bash
|
||||
# Windows 64-bit
|
||||
"C:\Program Files\Unity\Hub\Editor\2022.3.x\Editor\Unity.exe" -batchmode -quit -projectPath . -buildWindows64Player "Build/AIBIS_Dream.exe" -logFile Build/log.txt
|
||||
|
||||
# WebGL
|
||||
"C:\Program Files\Unity\Hub\Editor\2022.3.x\Editor\Unity.exe" -batchmode -quit -projectPath . -executeMethod BuildScript.WebGL -logFile Build/webgl_log.txt
|
||||
```
|
||||
|
||||
### 资源与工程
|
||||
|
||||
```bash
|
||||
# 重新生成 Visual Studio 工程
|
||||
Assets\Open C# Project.regenerate-sln.bat
|
||||
# 刷新资源库:菜单 Assets > Refresh 或 Ctrl+R
|
||||
```
|
||||
|
||||
## 关键依赖
|
||||
|
||||
### Unity 包
|
||||
|
||||
- Yarn Spinner、TextMesh Pro、URP、2D Animation、Cinemachine、Timeline、Localization
|
||||
|
||||
### 第三方资源
|
||||
|
||||
- More Mountains Feedbacks、FMOD、DOTween、Shapes、QFramework、Destructible 2D
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 代码组织
|
||||
|
||||
1. 各系统放在 `Assets/Scripts/` 下独立文件夹
|
||||
2. 可复用工具以 Kit 后缀放在 `Framework/`
|
||||
3. 全局管理类使用 Singleton
|
||||
4. 组件优先于继承
|
||||
|
||||
### 场景结构
|
||||
|
||||
- 场景在 `Assets/Scenes/`
|
||||
- 主场景命名:`{角色}_{Day}{时间}`(如 `Fiction_Day1_begin`)
|
||||
- 小游戏场景:`HuoShan*`、`languageTest*` 等
|
||||
- 需加入 EditorBuildSettings
|
||||
|
||||
### Yarn 对话文件
|
||||
|
||||
- 位于 `Assets/Resources/Yarn/`,按角色与日期组织
|
||||
- 文件命名:`{角色}_{阶段}.yarn`
|
||||
- 使用 UTF-8
|
||||
|
||||
### 资源命名
|
||||
|
||||
- **脚本**:PascalCase,带用途(如 `DialogController.cs`)
|
||||
- **Prefab**:PascalCase 加类型后缀
|
||||
- **场景**:Snake_Case
|
||||
- **Resources**:按系统/类型分组
|
||||
|
||||
## 配置要点
|
||||
|
||||
### 编辑器
|
||||
|
||||
- 文本编码:UTF-8(尤其中文)
|
||||
- 行尾:C# 使用 Windows (CRLF),保持一致
|
||||
|
||||
### 构建
|
||||
|
||||
- 平台:Windows、WebGL
|
||||
- Scripting Backend:IL2CPP(Windows)、Emscripten(WebGL)
|
||||
- API:.NET Standard 2.1
|
||||
|
||||
### Git
|
||||
|
||||
- LFS:大文件(.unity、贴图、音频)
|
||||
- 忽略:Library/、Temp/、Logs/、Build/、*.csproj.user
|
||||
|
||||
## 测试与迭代
|
||||
|
||||
### 小游戏独立测试
|
||||
|
||||
- 火山语言游戏:`Assets/Scenes/languageTest.unity`
|
||||
- 火山维修:`Assets/Scenes/HuoShanFixScene.unity`
|
||||
- 表情测试:`Assets/Scenes/HuoShanExpressionTest.unity`
|
||||
|
||||
### Web 原型
|
||||
|
||||
1. 在 `web-prototype/` 建 HTML/JS 原型
|
||||
2. 逻辑与 Unity 实现保持一致
|
||||
3. 浏览器快速验证
|
||||
|
||||
## 常见任务
|
||||
|
||||
### 新增小游戏
|
||||
|
||||
1. 建文件夹 `Assets/Scripts/MiniGame/{名称}/`
|
||||
2. 用 Framework 实现逻辑
|
||||
3. 建测试场景 `{名称}Test.unity`
|
||||
4. 资源放 `Assets/Resources/{名称}/`
|
||||
5. 在 `README_{名称}.md` 中记录
|
||||
|
||||
### 添加 Yarn 对话
|
||||
|
||||
1. 在 `Assets/Resources/Yarn/{角色}/` 建 `.yarn`
|
||||
2. 通过 Resources.Load 加载
|
||||
3. 挂到场景的 DialogueRunner
|
||||
4. 用 DialogueController 测试
|
||||
|
||||
### 调试
|
||||
|
||||
- LogKit 做分类日志
|
||||
- ProjectSettings/LogKit 可调详细级别
|
||||
- 粒子多时用 Unity Profiler 排查
|
||||
|
||||
## 性能
|
||||
|
||||
- 频繁创建对象用 PoolKit
|
||||
- 资源加载用 ResourceKit 异步
|
||||
- 粒子系统注意 overdraw
|
||||
- 音效用池,复杂音频用 FMOD
|
||||
|
||||
## 重要提醒
|
||||
|
||||
- 以叙事为核心,对话和剧情优先
|
||||
- 火山语言粒子是核心玩法,改动需保持情绪表达逻辑
|
||||
- Web 原型仅供验证,正式实现用 Unity/C#
|
||||
- 保持 Windows 与 WebGL 兼容
|
||||
- **开发前按需读 `Docs/` 下对应索引,避免与设计偏离** -->
|
||||
-346
@@ -1,346 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Split Commit Tool - 交互式拆分未提交变更为多个原子提交
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# 配置
|
||||
PROTECTED_BRANCHES = ['main', 'master', 'release/', 'hotfix/']
|
||||
REPO_ROOT = Path(__file__).parent.absolute()
|
||||
os.chdir(REPO_ROOT)
|
||||
|
||||
def run_command(cmd, capture_output=True):
|
||||
"""执行Git命令"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
capture_output=capture_output,
|
||||
text=True,
|
||||
encoding='utf-8'
|
||||
)
|
||||
return result.returncode == 0, result.stdout, result.stderr
|
||||
except Exception as e:
|
||||
return False, "", str(e)
|
||||
|
||||
def is_protected_branch(branch_name):
|
||||
"""检查是否为保护分支"""
|
||||
for protected in PROTECTED_BRANCHES:
|
||||
if protected.endswith('/') and branch_name.startswith(protected):
|
||||
return True
|
||||
if branch_name == protected:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_current_branch():
|
||||
"""获取当前分支"""
|
||||
success, stdout, stderr = run_command("git branch --show-current")
|
||||
return stdout.strip() if success else ""
|
||||
|
||||
def analyze_changes():
|
||||
"""分析变更"""
|
||||
print("=" * 60)
|
||||
print("步骤 1: 分析变更")
|
||||
print("=" * 60)
|
||||
|
||||
# git status
|
||||
print("\n[git status]")
|
||||
success, stdout, stderr = run_command("git status")
|
||||
print(stdout or stderr)
|
||||
|
||||
# git diff --stat
|
||||
print("\n[git diff --stat]")
|
||||
success, stdout, stderr = run_command("git diff --stat")
|
||||
print(stdout or stderr)
|
||||
|
||||
return True
|
||||
|
||||
def create_commit(msg):
|
||||
"""使用msg.txt创建提交(避免中文乱码)"""
|
||||
msg_file = REPO_ROOT / "msg.txt"
|
||||
try:
|
||||
with open(msg_file, 'w', encoding='utf-8') as f:
|
||||
f.write(msg)
|
||||
f.write("\n\nCo-Authored-By: Claude Code <noreply@anthropic.com>\n")
|
||||
|
||||
success, stdout, stderr = run_command("git commit -F msg.txt")
|
||||
if not success:
|
||||
print(f"❌ 提交失败: {stderr}")
|
||||
return False
|
||||
|
||||
print(f"✅ 提交成功: {msg}")
|
||||
return True
|
||||
finally:
|
||||
if msg_file.exists():
|
||||
msg_file.unlink()
|
||||
|
||||
def commit_all_changes():
|
||||
"""一次性提交所有变更"""
|
||||
print("\n" + "=" * 60)
|
||||
print("一次性提交所有变更")
|
||||
print("=" * 60)
|
||||
|
||||
# 处理删除的文件
|
||||
run_command("git ls-files --deleted -z | xargs -0 git rm", capture_output=False)
|
||||
|
||||
# 添加所有变更
|
||||
run_command("git add -A", capture_output=False)
|
||||
|
||||
# 创建提交
|
||||
create_commit("chore: 批量提交变更")
|
||||
|
||||
# 显示提交历史
|
||||
print("\n[提交历史]")
|
||||
run_command("git log --oneline -5", capture_output=False)
|
||||
|
||||
def interactive_split():
|
||||
"""交互式拆分提交"""
|
||||
print("\n" + "=" * 60)
|
||||
print("步骤 2: 分组变更")
|
||||
print("=" * 60)
|
||||
|
||||
# 获取所有变更
|
||||
success, status_output, _ = run_command("git status --short")
|
||||
if not success:
|
||||
print("❌ 无法获取变更状态")
|
||||
return False
|
||||
|
||||
# 分析变更类型
|
||||
staged_files = []
|
||||
modified_files = []
|
||||
deleted_files = []
|
||||
untracked_files = []
|
||||
|
||||
for line in status_output.strip().split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
|
||||
status = line[:2]
|
||||
file_path = line[3:]
|
||||
|
||||
if status == 'M ':
|
||||
staged_files.append(file_path)
|
||||
elif status == ' M':
|
||||
modified_files.append(file_path)
|
||||
elif status == 'D ':
|
||||
deleted_files.append(file_path)
|
||||
elif status == ' D':
|
||||
deleted_files.append(file_path)
|
||||
elif status == '??':
|
||||
untracked_files.append(file_path)
|
||||
elif status == 'MM':
|
||||
staged_files.append(file_path)
|
||||
modified_files.append(file_path)
|
||||
|
||||
# 显示分组建议
|
||||
groups = []
|
||||
print("\n[变更分组方案]")
|
||||
|
||||
order = 1
|
||||
|
||||
# 删除文件
|
||||
if deleted_files:
|
||||
groups.append({
|
||||
'order': order,
|
||||
'files': deleted_files,
|
||||
'message': '删除旧文件',
|
||||
'type': 'delete'
|
||||
})
|
||||
print(f"{order}. 删除 {len(deleted_files)} 个文件")
|
||||
for f in deleted_files[:3]:
|
||||
print(f" - {f}")
|
||||
if len(deleted_files) > 3:
|
||||
print(f" ... 还有 {len(deleted_files) - 3} 个文件")
|
||||
order += 1
|
||||
|
||||
# 修改的meta文件
|
||||
meta_files = [f for f in modified_files if f.endswith('.meta')]
|
||||
if meta_files:
|
||||
groups.append({
|
||||
'order': order,
|
||||
'files': meta_files,
|
||||
'message': '更新.meta文件',
|
||||
'type': 'meta'
|
||||
})
|
||||
print(f"{order}. 修改 {len(meta_files)} 个.meta文件")
|
||||
for f in meta_files[:3]:
|
||||
print(f" - {f}")
|
||||
if len(meta_files) > 3:
|
||||
print(f" ... 还有 {len(meta_files) - 3} 个文件")
|
||||
order += 1
|
||||
|
||||
# 场景文件
|
||||
scene_files = [f for f in modified_files if f.endswith('.unity')]
|
||||
if scene_files:
|
||||
groups.append({
|
||||
'order': order,
|
||||
'files': scene_files,
|
||||
'message': '更新场景',
|
||||
'type': 'scene'
|
||||
})
|
||||
print(f"{order}. 修改 {len(scene_files)} 个场景文件")
|
||||
for f in scene_files:
|
||||
print(f" - {f}")
|
||||
order += 1
|
||||
|
||||
# 脚本文件
|
||||
script_files = [f for f in modified_files if f.endswith('.cs')]
|
||||
if script_files:
|
||||
for script_file in script_files:
|
||||
groups.append({
|
||||
'order': order,
|
||||
'files': [script_file],
|
||||
'message': f'更新脚本: {Path(script_file).name}',
|
||||
'type': 'script'
|
||||
})
|
||||
print(f"{order}. 修改脚本: {script_file}")
|
||||
order += 1
|
||||
|
||||
# 未跟踪文件
|
||||
if untracked_files:
|
||||
groups.append({
|
||||
'order': order,
|
||||
'files': untracked_files,
|
||||
'message': '添加新文件',
|
||||
'type': 'untracked'
|
||||
})
|
||||
print(f"{order}. 添加 {len(untracked_files)} 个新文件")
|
||||
for f in untracked_files[:3]:
|
||||
print(f" - {f}")
|
||||
if len(untracked_files) > 3:
|
||||
print(f" ... 还有 {len(untracked_files) - 3} 个文件")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("步骤 3: 确认拆分方案")
|
||||
print("=" * 60)
|
||||
|
||||
choice = input("\n是否开始拆分? [1] 开始拆分 [2] 取消拆分 [3] 自定义分组: ")
|
||||
|
||||
if choice == '2':
|
||||
print("\n已取消拆分")
|
||||
return False
|
||||
|
||||
if choice == '3':
|
||||
print("\n自定义分组功能暂未实现,请使用自动分组")
|
||||
return False
|
||||
|
||||
# 开始拆分
|
||||
print("\n" + "=" * 60)
|
||||
print("步骤 4: 执行拆分")
|
||||
print("=" * 60)
|
||||
|
||||
for group in groups:
|
||||
print(f"\n>>> 处理第 {group['order']} 组")
|
||||
|
||||
# 根据类型执行不同的git操作
|
||||
if group['type'] == 'delete':
|
||||
# 删除文件
|
||||
for file_path in group['files']:
|
||||
run_command(f'git rm "{file_path}"', capture_output=False)
|
||||
elif group['type'] == 'untracked':
|
||||
# 添加新文件
|
||||
run_command('git add -A', capture_output=False)
|
||||
else:
|
||||
# 修改文件
|
||||
for file_path in group['files']:
|
||||
run_command(f'git add "{file_path}"', capture_output=False)
|
||||
|
||||
# 创建提交
|
||||
success = create_commit(group['message'])
|
||||
if not success:
|
||||
print(f"❌ 第 {group['order']} 组提交失败,拆分中止")
|
||||
return False
|
||||
|
||||
input("\n按回车继续下一组...")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("步骤 5: 完成")
|
||||
print("=" * 60)
|
||||
|
||||
print("\n[提交历史]")
|
||||
success, stdout, _ = run_command("git log --oneline -" + str(len(groups)))
|
||||
print(stdout)
|
||||
|
||||
print(f"\n✅ 拆分完成!共创建 {len(groups)} 个原子提交")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("Split Commit - 交互式拆分提交工具")
|
||||
print("=" * 60)
|
||||
|
||||
# 检查保护分支
|
||||
current_branch = get_current_branch()
|
||||
print(f"\n当前分支: {current_branch}")
|
||||
|
||||
if is_protected_branch(current_branch):
|
||||
print("\n❌ 当前为保护分支,无法直接拆分")
|
||||
print("\n选项:")
|
||||
print("[1] 创建新分支再拆分 (推荐)")
|
||||
print("[2] 压成单commit并移到新分支")
|
||||
print("[3] 取消")
|
||||
|
||||
choice = input("\n请选择: ")
|
||||
|
||||
if choice == '1':
|
||||
branch_name = input("请输入新分支名: ")
|
||||
if not branch_name:
|
||||
print("❌ 分支名不能为空")
|
||||
return
|
||||
|
||||
print("\n执行:")
|
||||
print("1. git stash push --include-untracked")
|
||||
print(f"2. git checkout -b {branch_name}")
|
||||
print("3. git stash pop")
|
||||
|
||||
confirm = input("\n确认执行?(y/n): ")
|
||||
if confirm.lower() == 'y':
|
||||
run_command("git stash push --include-untracked", capture_output=False)
|
||||
run_command(f"git checkout -b {branch_name}", capture_output=False)
|
||||
run_command("git stash pop", capture_output=False)
|
||||
print(f"\n✅ 已切换到新分支: {branch_name}")
|
||||
print("请重新运行此工具进行拆分")
|
||||
|
||||
elif choice == '2':
|
||||
print("\n功能暂未实现")
|
||||
|
||||
return
|
||||
|
||||
# 非保护分支,继续拆分流程
|
||||
print("\n✅ 非保护分支,可以进行拆分")
|
||||
|
||||
# 分析变更
|
||||
if not analyze_changes():
|
||||
return
|
||||
|
||||
# 选择模式
|
||||
print("\n" + "=" * 60)
|
||||
print("选择拆分模式:")
|
||||
print("=" * 60)
|
||||
print("[1] 交互式拆分 (推荐)")
|
||||
print("[2] 一次性提交所有变更")
|
||||
|
||||
mode = input("\n请选择: ")
|
||||
|
||||
if mode == '1':
|
||||
interactive_split()
|
||||
elif mode == '2':
|
||||
commit_all_changes()
|
||||
else:
|
||||
print("❌ 无效选择")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n已取消操作")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"\n❌ 发生错误: {e}")
|
||||
sys.exit(1)
|
||||
-497
@@ -1,497 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>思维侧写</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;500&display=swap');
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
font-family: 'Noto Serif SC', serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#stage {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#analysis-canvas {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 最终图片 */
|
||||
#final-image-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 420px;
|
||||
height: 520px;
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.real-photo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 漂浮的句子 */
|
||||
.clue {
|
||||
position: absolute;
|
||||
color: rgba(220, 200, 180, 0.5);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
cursor: crosshair;
|
||||
transition: color 0.15s, text-shadow 0.15s;
|
||||
animation: float 4s ease-in-out infinite alternate;
|
||||
z-index: 50;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.clue:hover {
|
||||
color: rgba(255, 245, 230, 1);
|
||||
text-shadow: 0 0 15px rgba(255, 200, 150, 0.6);
|
||||
}
|
||||
|
||||
.clue.triggered {
|
||||
pointer-events: none;
|
||||
animation: none;
|
||||
transition: opacity 0.2s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ASCII字符层 */
|
||||
#ascii-layer {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 单个ASCII字符 */
|
||||
.char {
|
||||
position: absolute;
|
||||
font-family: 'Noto Serif SC', serif;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
text-align: center;
|
||||
transition: opacity 0.2s ease;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.char.placed {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.char.flying {
|
||||
transition: left 0.35s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
top 0.35s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
opacity 0.15s ease;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0% { transform: translateY(0) rotate(-0.5deg); }
|
||||
100% { transform: translateY(-6px) rotate(0.5deg); }
|
||||
}
|
||||
|
||||
/* 进度 */
|
||||
#progress {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
color: rgba(255,255,255,0.3);
|
||||
font-size: 12px;
|
||||
font-family: 'Courier New', monospace;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
/* 提示 */
|
||||
#hint {
|
||||
position: fixed;
|
||||
top: 30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
font-size: 12px;
|
||||
letter-spacing: 5px;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
#hint.hidden { opacity: 0; }
|
||||
|
||||
/* 结局文字 */
|
||||
#reveal-text {
|
||||
position: absolute;
|
||||
bottom: 5%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: rgba(255, 240, 220, 0);
|
||||
font-size: 14px;
|
||||
letter-spacing: 12px;
|
||||
z-index: 100;
|
||||
transition: color 2s ease 0.5s;
|
||||
}
|
||||
#reveal-text.show {
|
||||
color: rgba(255, 240, 220, 0.85);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="stage">
|
||||
<canvas id="analysis-canvas"></canvas>
|
||||
|
||||
<div id="ascii-layer"></div>
|
||||
|
||||
<div id="final-image-container">
|
||||
<img id="source-image"
|
||||
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=800&auto=format&fit=crop"
|
||||
crossorigin="anonymous"
|
||||
class="real-photo" alt="">
|
||||
</div>
|
||||
<div id="reveal-text">就 是 她</div>
|
||||
</div>
|
||||
|
||||
<div id="progress">0%</div>
|
||||
<div id="hint">移动鼠标 · 拼凑真相</div>
|
||||
|
||||
<script>
|
||||
// 80句
|
||||
const vocab = [
|
||||
"黑色风衣女人", "长发遮半边脸", "雨夜独自站", "高跟鞋声响",
|
||||
"淡淡的烟味", "红唇微颤抖", "沉默的背影", "躲闪的眼神",
|
||||
"昨晚十点整", "最后的渡轮", "她在说谎", "左手的戒指",
|
||||
"指尖有泥土", "侧脸的轮廓", "电话突然断", "车票的日期",
|
||||
"颤抖的双手", "欲言又止", "匆忙离现场", "丝巾遮脖颈",
|
||||
"眼角的泪痕", "紧握的拳头", "深夜的来访", "无法解释的",
|
||||
"那晚她在场", "目击者证词", "最后见到她", "消失的证据",
|
||||
"她知道真相", "不在场证明", "香水的味道", "雨中的身影",
|
||||
"码头的尽头", "转身的瞬间", "路灯下剪影", "低语的声音",
|
||||
"犹豫的脚步", "紧锁的眉头", "冰冷的手指", "破碎的谎言",
|
||||
"隐藏的秘密", "午夜的电话", "模糊的记忆", "真相的碎片",
|
||||
"无声的控诉", "逃离的背影", "最后的晚餐", "致命的证据",
|
||||
"她就是凶手", "就是她", "那个女人", "雨夜的秘密",
|
||||
"无人知晓的", "深藏的恐惧", "午夜的访客", "不可告人的",
|
||||
"最后的线索", "关键的证人", "遗失的记忆", "隐藏的伤痕",
|
||||
"沉默的真相", "破碎的承诺", "无法回头的", "命运的交点",
|
||||
"时间的裂缝", "被遗忘的夜", "最后的告白", "无声的呐喊",
|
||||
"迷失的方向", "黑暗中的影", "月光下的她", "寂静的街道",
|
||||
"最后的机会", "不能说的秘", "被掩盖的罪", "午夜的约定"
|
||||
];
|
||||
|
||||
// 配置
|
||||
const IMG_WIDTH = 420;
|
||||
const IMG_HEIGHT = 520;
|
||||
const CELL_SIZE = 10; // 更小的网格 = 更密集
|
||||
const totalClues = 300; // 120个句子
|
||||
|
||||
let collectedCount = 0;
|
||||
let isRevealed = false;
|
||||
let gridData = [];
|
||||
let gridIndex = 0;
|
||||
|
||||
const stage = document.getElementById('stage');
|
||||
const asciiLayer = document.getElementById('ascii-layer');
|
||||
const finalImageContainer = document.getElementById('final-image-container');
|
||||
const sourceImage = document.getElementById('source-image');
|
||||
const canvas = document.getElementById('analysis-canvas');
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
const progress = document.getElementById('progress');
|
||||
const hint = document.getElementById('hint');
|
||||
const revealText = document.getElementById('reveal-text');
|
||||
|
||||
// 分析图像
|
||||
function analyzeImage() {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.onload = () => {
|
||||
canvas.width = IMG_WIDTH;
|
||||
canvas.height = IMG_HEIGHT;
|
||||
|
||||
// 强对比度 + 灰度
|
||||
ctx.filter = 'contrast(1.8) brightness(1.0) saturate(0)';
|
||||
ctx.drawImage(img, 0, 0, IMG_WIDTH, IMG_HEIGHT);
|
||||
ctx.filter = 'none';
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, IMG_WIDTH, IMG_HEIGHT);
|
||||
const data = imageData.data;
|
||||
|
||||
// 构建亮度图并找极值
|
||||
let brightnessMap = [];
|
||||
let minB = 255, maxB = 0;
|
||||
for (let y = 0; y < IMG_HEIGHT; y++) {
|
||||
brightnessMap[y] = [];
|
||||
for (let x = 0; x < IMG_WIDTH; x++) {
|
||||
const i = (y * IMG_WIDTH + x) * 4;
|
||||
const b = data[i] * 0.299 + data[i+1] * 0.587 + data[i+2] * 0.114;
|
||||
brightnessMap[y][x] = b;
|
||||
if (b < minB) minB = b;
|
||||
if (b > maxB) maxB = b;
|
||||
}
|
||||
}
|
||||
|
||||
// 归一化
|
||||
const range = maxB - minB || 1;
|
||||
for (let y = 0; y < IMG_HEIGHT; y++) {
|
||||
for (let x = 0; x < IMG_WIDTH; x++) {
|
||||
brightnessMap[y][x] = ((brightnessMap[y][x] - minB) / range) * 255;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成网格数据
|
||||
gridData = [];
|
||||
const cols = Math.floor(IMG_WIDTH / CELL_SIZE);
|
||||
const rows = Math.floor(IMG_HEIGHT / CELL_SIZE);
|
||||
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const cx = col * CELL_SIZE + CELL_SIZE / 2;
|
||||
const cy = row * CELL_SIZE + CELL_SIZE / 2;
|
||||
|
||||
// 采样3x3区域取平均,更平滑
|
||||
let sum = 0, count = 0;
|
||||
for (let dy = -1; dy <= 1; dy++) {
|
||||
for (let dx = -1; dx <= 1; dx++) {
|
||||
const sy = Math.floor(cy) + dy;
|
||||
const sx = Math.floor(cx) + dx;
|
||||
if (sy >= 0 && sy < IMG_HEIGHT && sx >= 0 && sx < IMG_WIDTH) {
|
||||
sum += brightnessMap[sy][sx];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
const brightness = sum / count;
|
||||
const darkness = 255 - brightness;
|
||||
|
||||
// 更低的阈值,捕获更多细节
|
||||
if (darkness > 15) {
|
||||
// 非线性映射,增强对比
|
||||
const d = darkness / 255;
|
||||
const dd = Math.pow(d, 0.8); // 提升暗部
|
||||
|
||||
const size = 5 + dd * 9; // 5-14px
|
||||
const alpha = 0.08 + dd * 0.9; // 0.08-0.98
|
||||
|
||||
gridData.push({
|
||||
x: cx,
|
||||
y: cy,
|
||||
size: size,
|
||||
alpha: alpha,
|
||||
darkness: darkness
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按暗度排序
|
||||
gridData.sort((a, b) => b.darkness - a.darkness);
|
||||
|
||||
// 分块打乱
|
||||
const n = gridData.length;
|
||||
const c1 = shuffleArray(gridData.slice(0, n * 0.2));
|
||||
const c2 = shuffleArray(gridData.slice(n * 0.2, n * 0.4));
|
||||
const c3 = shuffleArray(gridData.slice(n * 0.4, n * 0.6));
|
||||
const c4 = shuffleArray(gridData.slice(n * 0.6, n * 0.8));
|
||||
const c5 = shuffleArray(gridData.slice(n * 0.8));
|
||||
gridData = [...c1, ...c2, ...c3, ...c4, ...c5];
|
||||
|
||||
console.log(`生成了 ${gridData.length} 个网格位置`);
|
||||
resolve();
|
||||
};
|
||||
img.onerror = () => {
|
||||
generateFallbackGrid();
|
||||
resolve();
|
||||
};
|
||||
img.src = sourceImage.src;
|
||||
});
|
||||
}
|
||||
|
||||
function generateFallbackGrid() {
|
||||
gridData = [];
|
||||
const cols = Math.floor(IMG_WIDTH / CELL_SIZE);
|
||||
const rows = Math.floor(IMG_HEIGHT / CELL_SIZE);
|
||||
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const cx = col * CELL_SIZE + CELL_SIZE / 2;
|
||||
const cy = row * CELL_SIZE + CELL_SIZE / 2;
|
||||
const dx = (cx - IMG_WIDTH/2) / (IMG_WIDTH * 0.4);
|
||||
const dy = (cy - IMG_HEIGHT/2) / (IMG_HEIGHT * 0.48);
|
||||
if (dx*dx + dy*dy < 1) {
|
||||
const d = 1 - Math.sqrt(dx*dx + dy*dy);
|
||||
gridData.push({
|
||||
x: cx, y: cy,
|
||||
size: 6 + d * 8,
|
||||
alpha: 0.2 + d * 0.6,
|
||||
darkness: d * 200
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
gridData = shuffleArray(gridData);
|
||||
}
|
||||
|
||||
function shuffleArray(array) {
|
||||
const arr = [...array];
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function getNextGridCells(count) {
|
||||
const cells = [];
|
||||
for (let i = 0; i < count && gridIndex < gridData.length; i++) {
|
||||
cells.push(gridData[gridIndex++]);
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await analyzeImage();
|
||||
|
||||
const centerX = window.innerWidth / 2;
|
||||
const centerY = window.innerHeight / 2;
|
||||
|
||||
asciiLayer.style.width = IMG_WIDTH + 'px';
|
||||
asciiLayer.style.height = IMG_HEIGHT + 'px';
|
||||
|
||||
// 创建更多句子
|
||||
for (let i = 0; i < totalClues; i++) {
|
||||
const el = document.createElement('div');
|
||||
el.classList.add('clue');
|
||||
const text = vocab[i % vocab.length];
|
||||
el.innerText = text;
|
||||
el.dataset.text = text;
|
||||
|
||||
const angle = (i / totalClues) * Math.PI * 2 + Math.random() * 0.3;
|
||||
const dist = 280 + Math.random() * 280;
|
||||
const startX = centerX + Math.cos(angle) * dist;
|
||||
const startY = centerY + Math.sin(angle) * dist;
|
||||
el.style.left = startX + 'px';
|
||||
el.style.top = startY + 'px';
|
||||
el.dataset.startX = startX;
|
||||
el.dataset.startY = startY;
|
||||
|
||||
el.style.fontSize = (11 + Math.random() * 3) + 'px';
|
||||
el.style.animationDelay = (Math.random() * 3) + 's';
|
||||
|
||||
el.addEventListener('mouseenter', handleHover);
|
||||
stage.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
function handleHover(e) {
|
||||
const el = e.target;
|
||||
if (el.classList.contains('triggered') || isRevealed) return;
|
||||
|
||||
el.classList.add('triggered');
|
||||
|
||||
const text = el.dataset.text;
|
||||
const startX = parseFloat(el.dataset.startX);
|
||||
const startY = parseFloat(el.dataset.startY);
|
||||
|
||||
const imgRect = finalImageContainer.getBoundingClientRect();
|
||||
const imgCenterX = imgRect.left + imgRect.width / 2;
|
||||
const imgCenterY = imgRect.top + imgRect.height / 2;
|
||||
|
||||
const chars = text.split('');
|
||||
const cells = getNextGridCells(chars.length);
|
||||
|
||||
chars.forEach((char, i) => {
|
||||
if (i >= cells.length) return;
|
||||
|
||||
const cell = cells[i];
|
||||
const charEl = document.createElement('div');
|
||||
charEl.classList.add('char', 'flying');
|
||||
charEl.innerText = char;
|
||||
|
||||
const startPosX = startX - imgCenterX + IMG_WIDTH/2;
|
||||
const startPosY = startY - imgCenterY + IMG_HEIGHT/2;
|
||||
charEl.style.left = startPosX + 'px';
|
||||
charEl.style.top = startPosY + 'px';
|
||||
|
||||
charEl.style.fontSize = cell.size + 'px';
|
||||
charEl.style.color = `rgba(255, 255, 255, ${cell.alpha})`;
|
||||
charEl.style.width = CELL_SIZE + 'px';
|
||||
charEl.style.height = CELL_SIZE + 'px';
|
||||
charEl.style.lineHeight = CELL_SIZE + 'px';
|
||||
|
||||
asciiLayer.appendChild(charEl);
|
||||
|
||||
setTimeout(() => {
|
||||
charEl.style.left = cell.x + 'px';
|
||||
charEl.style.top = cell.y + 'px';
|
||||
charEl.classList.add('placed');
|
||||
}, 5 + i * 10);
|
||||
});
|
||||
|
||||
collectedCount++;
|
||||
const pct = Math.round((gridIndex / gridData.length) * 100);
|
||||
progress.textContent = pct + '%';
|
||||
|
||||
if (collectedCount > 5) hint.classList.add('hidden');
|
||||
|
||||
// 90% 格子填满触发结局
|
||||
if (gridIndex >= gridData.length * 0.9) {
|
||||
revealIdentity();
|
||||
}
|
||||
}
|
||||
|
||||
function revealIdentity() {
|
||||
if (isRevealed) return;
|
||||
isRevealed = true;
|
||||
|
||||
const allChars = document.querySelectorAll('.char.placed');
|
||||
allChars.forEach((el, i) => {
|
||||
setTimeout(() => {
|
||||
el.style.transition = 'opacity 1.5s ease';
|
||||
el.style.opacity = '0.03';
|
||||
}, i * 0.3);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
finalImageContainer.style.transition = 'opacity 2s ease';
|
||||
finalImageContainer.style.opacity = 1;
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
revealText.classList.add('show');
|
||||
}, 800);
|
||||
|
||||
document.querySelectorAll('.clue:not(.triggered)').forEach(el => {
|
||||
el.style.transition = 'opacity 0.3s';
|
||||
el.style.opacity = 0;
|
||||
});
|
||||
|
||||
progress.style.transition = 'opacity 0.5s';
|
||||
progress.style.opacity = 0;
|
||||
}
|
||||
|
||||
window.onload = init;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user