Initial commit
This commit is contained in:
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
__pycache__/
|
||||
build/
|
||||
builder/
|
||||
dist/
|
||||
dmgcreator/
|
||||
website/
|
||||
CardCopyer.spec
|
||||
.DS_Store
|
||||
BIN
appicon.png
Normal file
BIN
appicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 MiB |
82
config.json
Normal file
82
config.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "CardCopyer-拷贝乐",
|
||||
"version": "1.1.2",
|
||||
"author": "SuperJia",
|
||||
"description": "现代化的DIT拷卡软件"
|
||||
},
|
||||
"ui": {
|
||||
"theme": "darkly",
|
||||
"window_size": [1400, 900],
|
||||
"font_family": "Segoe UI",
|
||||
"font_size": 11,
|
||||
"colors": {
|
||||
"primary": "#007acc",
|
||||
"secondary": "#6c757d",
|
||||
"success": "#28a745",
|
||||
"info": "#17a2b8",
|
||||
"warning": "#ffc107",
|
||||
"danger": "#dc3545",
|
||||
"light": "#f8f9fa",
|
||||
"dark": "#343a40"
|
||||
}
|
||||
},
|
||||
"copy": {
|
||||
"buffer_size": 8192,
|
||||
"max_threads": 4,
|
||||
"retry_count": 3,
|
||||
"retry_delay": 1,
|
||||
"verify_after_copy": true,
|
||||
"create_checksum_file": true,
|
||||
"auto_rename_duplicates": true
|
||||
},
|
||||
"verification": {
|
||||
"enable_md5": true,
|
||||
"verify_chunk_size": 8192,
|
||||
"parallel_verification": true,
|
||||
"verification_threads": 2,
|
||||
"skip_verification_for_small_files": false,
|
||||
"small_file_threshold": 1024
|
||||
},
|
||||
"logging": {
|
||||
"level": "INFO",
|
||||
"log_to_file": true,
|
||||
"log_file": "dit_copy_tool.log",
|
||||
"max_log_size": 10485760,
|
||||
"backup_count": 5,
|
||||
"log_format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
},
|
||||
"performance": {
|
||||
"update_interval": 100,
|
||||
"progress_refresh_rate": 50,
|
||||
"memory_optimization": true,
|
||||
"cache_size": 1000
|
||||
},
|
||||
"safety": {
|
||||
"confirm_before_overwrite": true,
|
||||
"backup_before_copy": false,
|
||||
"check_disk_space": true,
|
||||
"min_free_space_gb": 1,
|
||||
"exclude_system_files": true,
|
||||
"exclude_patterns": [
|
||||
"*.tmp",
|
||||
"*.temp",
|
||||
"Thumbs.db",
|
||||
".DS_Store",
|
||||
"*.log"
|
||||
]
|
||||
},
|
||||
"notifications": {
|
||||
"show_completion_notification": true,
|
||||
"play_sound": true,
|
||||
"sound_file": "completion.wav",
|
||||
"celebration_animation": true,
|
||||
"auto_close_celebration": 3000
|
||||
},
|
||||
"advanced": {
|
||||
"enable_experimental_features": false,
|
||||
"debug_mode": false,
|
||||
"profile_performance": false,
|
||||
"enable_telemetry": false
|
||||
}
|
||||
}
|
||||
BIN
icon/32.ico
Normal file
BIN
icon/32.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
icon/appicon.png
Normal file
BIN
icon/appicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 MiB |
BIN
icon/appicon512.png
Normal file
BIN
icon/appicon512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 379 KiB |
BIN
icon/dtw1j-qypsc-001.ico
Normal file
BIN
icon/dtw1j-qypsc-001.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 281 KiB |
227
md5_verifier.py
Normal file
227
md5_verifier.py
Normal file
@@ -0,0 +1,227 @@
|
||||
"""
|
||||
MD5验证模块
|
||||
用于文件完整性验证
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, Tuple
|
||||
import json
|
||||
|
||||
class MD5Verifier:
|
||||
"""MD5验证器"""
|
||||
|
||||
def __init__(self):
|
||||
self.checksums = {} # 存储文件校验和
|
||||
|
||||
def calculate_md5(self, file_path: str, chunk_size: int = 8192) -> str:
|
||||
"""
|
||||
计算文件的MD5值
|
||||
|
||||
Args:
|
||||
file_path: 文件路径
|
||||
chunk_size: 分块大小
|
||||
|
||||
Returns:
|
||||
MD5哈希值
|
||||
"""
|
||||
md5_hash = hashlib.md5()
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
while chunk := f.read(chunk_size):
|
||||
md5_hash.update(chunk)
|
||||
return md5_hash.hexdigest()
|
||||
except Exception as e:
|
||||
raise Exception(f"计算MD5失败 {file_path}: {str(e)}")
|
||||
|
||||
def verify_file(self, source_path: str, dest_path: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
验证两个文件是否相同
|
||||
|
||||
Args:
|
||||
source_path: 源文件路径
|
||||
dest_path: 目标文件路径
|
||||
|
||||
Returns:
|
||||
(是否相同, 错误信息)
|
||||
"""
|
||||
try:
|
||||
source_md5 = self.calculate_md5(source_path)
|
||||
dest_md5 = self.calculate_md5(dest_path)
|
||||
|
||||
if source_md5 == dest_md5:
|
||||
return True, ""
|
||||
else:
|
||||
return False, f"MD5不匹配: 源={source_md5}, 目标={dest_md5}"
|
||||
|
||||
except Exception as e:
|
||||
return False, f"验证失败: {str(e)}"
|
||||
|
||||
def create_checksum_file(self, folder_path: str, output_file: str = None) -> str:
|
||||
"""
|
||||
为文件夹创建校验和文件
|
||||
|
||||
Args:
|
||||
folder_path: 文件夹路径
|
||||
output_file: 输出文件路径(可选)
|
||||
|
||||
Returns:
|
||||
校验和文件路径
|
||||
"""
|
||||
if output_file is None:
|
||||
output_file = os.path.join(folder_path, "checksums.md5")
|
||||
|
||||
checksums = {}
|
||||
|
||||
for root, dirs, files in os.walk(folder_path):
|
||||
for file in files:
|
||||
if file == "checksums.md5": # 跳过已有的校验和文件
|
||||
continue
|
||||
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, folder_path)
|
||||
|
||||
try:
|
||||
md5_hash = self.calculate_md5(file_path)
|
||||
checksums[rel_path] = md5_hash
|
||||
except Exception as e:
|
||||
print(f"计算MD5失败 {file_path}: {str(e)}")
|
||||
|
||||
# 保存校验和文件
|
||||
try:
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
for rel_path, md5_hash in checksums.items():
|
||||
f.write(f"{md5_hash} {rel_path}\n")
|
||||
return output_file
|
||||
except Exception as e:
|
||||
raise Exception(f"保存校验和文件失败: {str(e)}")
|
||||
|
||||
def load_checksum_file(self, checksum_file: str) -> Dict[str, str]:
|
||||
"""
|
||||
加载校验和文件
|
||||
|
||||
Args:
|
||||
checksum_file: 校验和文件路径
|
||||
|
||||
Returns:
|
||||
文件路径到MD5的映射字典
|
||||
"""
|
||||
checksums = {}
|
||||
|
||||
try:
|
||||
with open(checksum_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
parts = line.split(' ', 1) # 使用两个空格分隔
|
||||
if len(parts) == 2:
|
||||
md5_hash, file_path = parts
|
||||
checksums[file_path] = md5_hash
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"加载校验和文件失败: {str(e)}")
|
||||
|
||||
return checksums
|
||||
|
||||
def verify_folder(self, source_folder: str, dest_folder: str, create_checksums: bool = True) -> Dict[str, Tuple[bool, str]]:
|
||||
"""
|
||||
验证两个文件夹是否相同
|
||||
|
||||
Args:
|
||||
source_folder: 源文件夹
|
||||
dest_folder: 目标文件夹
|
||||
create_checksums: 是否创建校验和文件
|
||||
|
||||
Returns:
|
||||
验证结果字典
|
||||
"""
|
||||
results = {}
|
||||
|
||||
# 为源文件夹创建校验和文件
|
||||
if create_checksums:
|
||||
try:
|
||||
source_checksum_file = self.create_checksum_file(source_folder)
|
||||
print(f"创建源文件夹校验和文件: {source_checksum_file}")
|
||||
except Exception as e:
|
||||
print(f"创建源文件夹校验和文件失败: {str(e)}")
|
||||
|
||||
# 获取源文件夹的所有文件
|
||||
source_files = {}
|
||||
for root, dirs, files in os.walk(source_folder):
|
||||
for file in files:
|
||||
if file == "checksums.md5":
|
||||
continue
|
||||
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, source_folder)
|
||||
source_files[rel_path] = file_path
|
||||
|
||||
# 验证每个文件
|
||||
for rel_path, source_file in source_files.items():
|
||||
dest_file = os.path.join(dest_folder, rel_path)
|
||||
|
||||
if not os.path.exists(dest_file):
|
||||
results[rel_path] = (False, "目标文件不存在")
|
||||
continue
|
||||
|
||||
is_valid, error_msg = self.verify_file(source_file, dest_file)
|
||||
results[rel_path] = (is_valid, error_msg)
|
||||
|
||||
return results
|
||||
|
||||
def get_file_info(self, file_path: str) -> Dict[str, any]:
|
||||
"""
|
||||
获取文件信息
|
||||
|
||||
Args:
|
||||
file_path: 文件路径
|
||||
|
||||
Returns:
|
||||
文件信息字典
|
||||
"""
|
||||
try:
|
||||
stat = os.stat(file_path)
|
||||
return {
|
||||
'size': stat.st_size,
|
||||
'modified': stat.st_mtime,
|
||||
'md5': self.calculate_md5(file_path)
|
||||
}
|
||||
except Exception as e:
|
||||
raise Exception(f"获取文件信息失败: {str(e)}")
|
||||
|
||||
def test_md5_verifier():
|
||||
"""测试MD5验证器"""
|
||||
verifier = MD5Verifier()
|
||||
|
||||
# 创建测试文件
|
||||
test_file = "test_file.txt"
|
||||
with open(test_file, 'w') as f:
|
||||
f.write("这是一个测试文件\n")
|
||||
|
||||
try:
|
||||
# 计算MD5
|
||||
md5_hash = verifier.calculate_md5(test_file)
|
||||
print(f"文件MD5: {md5_hash}")
|
||||
|
||||
# 验证文件
|
||||
is_valid, error_msg = verifier.verify_file(test_file, test_file)
|
||||
print(f"文件验证结果: {is_valid}, {error_msg}")
|
||||
|
||||
# 创建校验和文件
|
||||
checksum_file = verifier.create_checksum_file(".")
|
||||
print(f"校验和文件: {checksum_file}")
|
||||
|
||||
# 加载校验和文件
|
||||
checksums = verifier.load_checksum_file(checksum_file)
|
||||
print(f"加载的校验和: {checksums}")
|
||||
|
||||
finally:
|
||||
# 清理测试文件
|
||||
if os.path.exists(test_file):
|
||||
os.remove(test_file)
|
||||
if os.path.exists("checksums.md5"):
|
||||
os.remove("checksums.md5")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_md5_verifier()
|
||||
64
readme/PACKAGING.md
Normal file
64
readme/PACKAGING.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# CardCopyer-拷贝乐 打包说明
|
||||
|
||||
## 已完成的打包
|
||||
|
||||
### macOS 版本
|
||||
- ✅ **CardCopyer-拷贝乐.app** - macOS 应用程序包
|
||||
- ✅ **CardCopyer** - macOS 可执行文件 (内部二进制名称)
|
||||
|
||||
打包文件位置:`/Users/peterjia/Documents/项目工程/cardcopy/dist/`
|
||||
|
||||
## Windows 版本打包方法
|
||||
|
||||
### 方法一:使用打包脚本
|
||||
1. 在 Windows 系统中安装 Python 3.7+
|
||||
2. 将项目文件复制到 Windows 电脑
|
||||
3. 双击运行 `build_windows.bat`
|
||||
|
||||
### 方法二:手动打包
|
||||
1. 安装依赖:
|
||||
```cmd
|
||||
pip install pyinstaller ttkbootstrap psutil Pillow
|
||||
```
|
||||
|
||||
2. 执行打包:
|
||||
```cmd
|
||||
pyinstaller CardCopyer_windows.spec
|
||||
```
|
||||
|
||||
3. 打包完成后,可执行文件在 `dist\CardCopyer-拷贝乐.exe`
|
||||
|
||||
## 打包配置说明
|
||||
|
||||
### 包含的文件
|
||||
- `styles.css` - 程序样式文件
|
||||
- `config.json` - 配置文件
|
||||
- `launcher.py` - 程序启动器
|
||||
- `main.py` - 主程序文件
|
||||
- `md5_verifier.py` - MD5 验证模块
|
||||
|
||||
### 隐藏导入的模块
|
||||
- ttkbootstrap 及其子模块
|
||||
- psutil
|
||||
- PIL (Pillow)
|
||||
|
||||
### 打包选项
|
||||
- 不显示控制台窗口(windowed 模式)
|
||||
- 启用 UPX 压缩
|
||||
- 包含所有依赖项
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **图标文件**:可以在 spec 文件中设置 `icon` 参数来添加程序图标
|
||||
2. **代码签名**:macOS 版本可以添加代码签名以避免安全警告
|
||||
3. **权限**:确保程序有访问文件系统的权限
|
||||
|
||||
## 文件结构
|
||||
```
|
||||
dist/
|
||||
├── CardCopyer-拷贝乐.app/ # macOS 应用程序包
|
||||
│ └── Contents/
|
||||
├── CardCopyer # macOS 可执行文件
|
||||
└── [Windows 打包后]
|
||||
└── CardCopyer-拷贝乐.exe # Windows 可执行文件
|
||||
```
|
||||
301
styles.css
Normal file
301
styles.css
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
DIT拷卡软件 - 自定义样式
|
||||
现代化界面美化样式
|
||||
*/
|
||||
|
||||
/* 全局样式 */
|
||||
* {
|
||||
font-family: 'Segoe UI', 'Microsoft YaHei', Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 主窗口样式 */
|
||||
.main-window {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||
}
|
||||
|
||||
/* 标题样式 */
|
||||
.title-label {
|
||||
background: linear-gradient(45deg, #007acc, #00d4ff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-shadow: 0 0 20px rgba(0, 122, 204, 0.5);
|
||||
}
|
||||
|
||||
/* 框架样式 */
|
||||
.labelframe {
|
||||
border-radius: 15px;
|
||||
border: 2px solid #007acc;
|
||||
background: rgba(0, 26, 51, 0.8);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* 列表框样式 */
|
||||
.listbox {
|
||||
border-radius: 10px;
|
||||
border: 1px solid #007acc;
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
color: #ffffff;
|
||||
selection-background-color: #007acc;
|
||||
selection-color: #ffffff;
|
||||
}
|
||||
|
||||
.listbox:hover {
|
||||
border-color: #00d4ff;
|
||||
box-shadow: 0 0 15px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.button {
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.button:pressed {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 进度条样式 */
|
||||
.progressbar {
|
||||
border-radius: 10px;
|
||||
height: 25px;
|
||||
border: 1px solid #007acc;
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
}
|
||||
|
||||
.progressbar::chunk {
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(45deg, #007acc, #00d4ff);
|
||||
}
|
||||
|
||||
/* 文本样式 */
|
||||
.text {
|
||||
border-radius: 10px;
|
||||
border: 1px solid #007acc;
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
color: #ffffff;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* 标签样式 */
|
||||
.label {
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.label:disabled {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
/* 单选按钮样式 */
|
||||
.radiobutton {
|
||||
color: #ffffff;
|
||||
spacing: 10px;
|
||||
}
|
||||
|
||||
.radiobutton::indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #007acc;
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
}
|
||||
|
||||
.radiobutton::indicator:checked {
|
||||
background: #007acc;
|
||||
border-color: #00d4ff;
|
||||
}
|
||||
|
||||
/* 复选框样式 */
|
||||
.checkbutton {
|
||||
color: #ffffff;
|
||||
spacing: 10px;
|
||||
}
|
||||
|
||||
.checkbutton::indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid #007acc;
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
}
|
||||
|
||||
.checkbutton::indicator:checked {
|
||||
background: #007acc;
|
||||
border-color: #00d4ff;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.scrollbar {
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 122, 204, 0.3);
|
||||
border: none;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.scrollbar::handle {
|
||||
border-radius: 6px;
|
||||
background: #007acc;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.scrollbar::handle:hover {
|
||||
background: #00d4ff;
|
||||
}
|
||||
|
||||
.scrollbar::add-line, .scrollbar::sub-line {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
.entry {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #007acc;
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
color: #ffffff;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.entry:focus {
|
||||
border-color: #00d4ff;
|
||||
box-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 框架标题样式 */
|
||||
.labelframe-title {
|
||||
color: #00d4ff;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
background: rgba(0, 26, 51, 0.8);
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 成功状态颜色 */
|
||||
.success {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
/* 警告状态颜色 */
|
||||
.warning {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
/* 错误状态颜色 */
|
||||
.danger {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
/* 信息状态颜色 */
|
||||
.info {
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
0% { box-shadow: 0 0 5px rgba(0, 212, 255, 0.5); }
|
||||
50% { box-shadow: 0 0 20px rgba(0, 212, 255, 0.8); }
|
||||
100% { box-shadow: 0 0 5px rgba(0, 212, 255, 0.5); }
|
||||
}
|
||||
|
||||
/* 庆祝动画 */
|
||||
.celebration {
|
||||
animation: pulse 0.5s ease-in-out 3, glow 1s ease-in-out 3;
|
||||
}
|
||||
|
||||
/* 按钮特殊效果 */
|
||||
.button-success {
|
||||
background: linear-gradient(45deg, #28a745, #20c997);
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
.button-info {
|
||||
background: linear-gradient(45deg, #17a2b8, #20c997);
|
||||
}
|
||||
|
||||
.button-warning {
|
||||
background: linear-gradient(45deg, #ffc107, #fd7e14);
|
||||
}
|
||||
|
||||
.button-danger {
|
||||
background: linear-gradient(45deg, #dc3545, #e83e8c);
|
||||
}
|
||||
|
||||
/* 进度条动画 */
|
||||
.progressbar-animated {
|
||||
animation: progress-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes progress-pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.8; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* 玻璃拟态效果 */
|
||||
.glass-effect {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
/* 霓虹灯效果 */
|
||||
.neon-text {
|
||||
text-shadow:
|
||||
0 0 10px #00d4ff,
|
||||
0 0 20px #00d4ff,
|
||||
0 0 30px #00d4ff,
|
||||
0 0 40px #007acc;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.main-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section {
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色模式优化 */
|
||||
.dark-mode {
|
||||
background: #0a0a0a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.dark-mode .card {
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.dark-mode .hover-effect:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
Reference in New Issue
Block a user