重新整理项目结构

This commit is contained in:
2025-09-09 13:45:04 +08:00
parent 44a4f1bae1
commit 2b5a6740d4
317 changed files with 1391 additions and 45676 deletions

211
.gitignore vendored
View File

@@ -1,211 +1,4 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
#项目自忽略
frontend/react-app/node_modules/
frontend/node_modules/
frontend/build/
.vscode

16
backend/.env Normal file
View File

@@ -0,0 +1,16 @@
# InfoGenie 环境变量配置文件
# 请勿将此文件提交到版本控制系统
# 邮件配置
# 请将下面的邮箱地址替换为您的实际QQ邮箱
MAIL_USERNAME=3205788256@qq.com
MAIL_PASSWORD=szcaxvbftusqddhi
# 数据库配置
MONGO_URI=mongodb://shumengya:tyh%4019900420@47.108.90.0:27018/InfoGenie?authSource=admin
# 应用密钥
SECRET_KEY=infogenie-secret-key-2025
# 环境配置
FLASK_ENV=development

Binary file not shown.

Binary file not shown.

View File

@@ -16,11 +16,8 @@ import secrets
# 导入模块
from modules.auth import auth_bp
from modules.api_60s import api_60s_bp
from modules.user_management import user_bp
from modules.email_service import init_mail
from modules.smallgame import smallgame_bp
from modules.aimodelapp import aimodelapp_bp
from config import Config
@@ -43,10 +40,7 @@ def create_app():
# 注册蓝图
app.register_blueprint(auth_bp, url_prefix='/api/auth')
app.register_blueprint(api_60s_bp, url_prefix='/api/60s')
app.register_blueprint(user_bp, url_prefix='/api/user')
app.register_blueprint(smallgame_bp, url_prefix='/api/smallgame')
app.register_blueprint(aimodelapp_bp, url_prefix='/api/aimodelapp')
# 基础路由
@app.route('/')

View File

@@ -27,6 +27,9 @@ class Config:
SESSION_COOKIE_SECURE = False # 开发环境设为False生产环境设为True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_DOMAIN = None # 开发环境设为None生产环境设为具体域名
SESSION_COOKIE_PATH = '/'
SESSION_REFRESH_EACH_REQUEST = True # 每次请求刷新会话过期时间
# 邮件配置
MAIL_SERVER = 'smtp.qq.com'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,78 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
AI应用模块 - 提供AI应用静态文件服务和目录扫描
Created by: 神奇万事通
Date: 2025-09-02
"""
from flask import Blueprint, jsonify
import os
aimodelapp_bp = Blueprint('aimodelapp', __name__)
@aimodelapp_bp.route('/scan-directories', methods=['GET'])
def scan_directories():
"""扫描aimodelapp目录结构"""
try:
# 获取项目根目录
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
ai_directory = os.path.join(project_root, 'frontend', 'aimodelapp')
if not os.path.exists(ai_directory):
return jsonify({
'success': False,
'message': 'aimodelapp目录不存在'
}), 404
apps = []
# 颜色渐变配置
gradient_colors = [
'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
'linear-gradient(135deg, #fa709a 0%, #fee140 100%)'
]
# 扫描目录
for i, app_name in enumerate(os.listdir(ai_directory)):
app_path = os.path.join(ai_directory, app_name)
index_path = os.path.join(app_path, 'index.html')
if os.path.isdir(app_path) and os.path.exists(index_path) and not app_name.endswith('.txt'):
# 读取HTML文件获取标题
try:
with open(index_path, 'r', encoding='utf-8') as f:
html_content = f.read()
title_match = html_content.find('<title>')
if title_match != -1:
title_end = html_content.find('</title>', title_match)
if title_end != -1:
title = html_content[title_match + 7:title_end].strip()
else:
title = app_name
else:
title = app_name
except:
title = app_name
apps.append({
'title': title,
'description': f'{app_name}AI应用',
'link': f'/aimodelapp/{app_name}/index.html',
'status': 'active',
'color': gradient_colors[i % len(gradient_colors)]
})
return jsonify({
'success': True,
'apps': apps
})
except Exception as e:
return jsonify({
'success': False,
'message': f'扫描目录时出错: {str(e)}'
}), 500

View File

@@ -1,103 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
60s API模块 - 提供各种实时数据接口
Created by: 神奇万事通
Date: 2025-09-02
"""
from flask import Blueprint, jsonify
import os
api_60s_bp = Blueprint('api_60s', __name__)
@api_60s_bp.route('/scan-directories', methods=['GET'])
def scan_directories():
"""扫描60sapi目录结构"""
try:
# 获取项目根目录
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
api_directory = os.path.join(project_root, 'frontend', '60sapi')
if not os.path.exists(api_directory):
return jsonify({
'success': False,
'message': '60sapi目录不存在'
}), 404
categories = []
# 定义分类配置
category_config = {
'热搜榜单': {'color': '#66bb6a'},
'日更资讯': {'color': '#4caf50'},
'实用功能': {'color': '#388e3c'},
'娱乐消遣': {'color': '#66bb6a'}
}
# 颜色渐变配置
gradient_colors = [
'linear-gradient(135deg, #81c784 0%, #66bb6a 100%)',
'linear-gradient(135deg, #a5d6a7 0%, #81c784 100%)',
'linear-gradient(135deg, #c8e6c9 0%, #a5d6a7 100%)',
'linear-gradient(135deg, #66bb6a 0%, #4caf50 100%)',
'linear-gradient(135deg, #4caf50 0%, #388e3c 100%)'
]
# 扫描目录
for category_name in os.listdir(api_directory):
category_path = os.path.join(api_directory, category_name)
if os.path.isdir(category_path) and category_name in category_config:
apis = []
# 扫描分类下的模块
for i, module_name in enumerate(os.listdir(category_path)):
module_path = os.path.join(category_path, module_name)
index_path = os.path.join(module_path, 'index.html')
if os.path.isdir(module_path) and os.path.exists(index_path):
# 读取HTML文件获取标题
try:
with open(index_path, 'r', encoding='utf-8') as f:
html_content = f.read()
title_match = html_content.find('<title>')
if title_match != -1:
title_end = html_content.find('</title>', title_match)
if title_end != -1:
title = html_content[title_match + 7:title_end].strip()
else:
title = module_name
else:
title = module_name
except:
title = module_name
# 根据环境获取基础URL
base_url = 'https://infogenie.api.shumengya.top'
apis.append({
'title': title,
'description': f'{module_name}相关功能',
'link': f'{base_url}/60sapi/{category_name}/{module_name}/index.html',
'status': 'active',
'color': gradient_colors[i % len(gradient_colors)]
})
if apis:
categories.append({
'title': category_name,
'color': category_config[category_name]['color'],
'apis': apis
})
return jsonify({
'success': True,
'categories': categories
})
except Exception as e:
return jsonify({
'success': False,
'message': f'扫描目录时出错: {str(e)}'
}), 500

View File

@@ -10,11 +10,53 @@ from flask import Blueprint, request, jsonify, session, current_app
from werkzeug.security import generate_password_hash, check_password_hash
import hashlib
import re
from datetime import datetime
import jwt
from datetime import datetime, timedelta
from functools import wraps
from .email_service import send_verification_email, verify_code, is_qq_email, get_qq_avatar_url
auth_bp = Blueprint('auth', __name__)
def generate_token(user_data):
"""生成JWT token"""
payload = {
'user_id': user_data['user_id'],
'email': user_data['email'],
'username': user_data['username'],
'exp': datetime.utcnow() + timedelta(days=7), # 7天过期
'iat': datetime.utcnow()
}
return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
def verify_token(token):
"""验证JWT token"""
try:
payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
return {'success': True, 'data': payload}
except jwt.ExpiredSignatureError:
return {'success': False, 'message': 'Token已过期'}
except jwt.InvalidTokenError:
return {'success': False, 'message': 'Token无效'}
def token_required(f):
"""JWT token验证装饰器"""
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
return jsonify({'success': False, 'message': '缺少认证token'}), 401
if token.startswith('Bearer '):
token = token[7:]
result = verify_token(token)
if not result['success']:
return jsonify({'success': False, 'message': result['message']}), 401
request.current_user = result['data']
return f(*args, **kwargs)
return decorated
def validate_qq_email(email):
"""验证QQ邮箱格式"""
return is_qq_email(email)
@@ -313,16 +355,18 @@ def login():
}
)
# 设置会话
session['user_id'] = str(user['_id'])
session['email'] = email
session['username'] = user.get('用户名', '')
session['logged_in'] = True
session.permanent = True
# 生成JWT token
user_data = {
'user_id': str(user['_id']),
'email': email,
'username': user.get('用户名', '')
}
token = generate_token(user_data)
return jsonify({
'success': True,
'message': '登录成功!',
'token': token,
'user': {
'id': str(user['_id']),
'email': email,
@@ -373,17 +417,11 @@ def login():
def logout():
"""用户登出"""
try:
if 'logged_in' in session:
session.clear()
# JWT是无状态的客户端删除token即可
return jsonify({
'success': True,
'message': '已成功登出'
}), 200
else:
return jsonify({
'success': False,
'message': '用户未登录'
}), 401
except Exception as e:
return jsonify({
@@ -395,14 +433,26 @@ def logout():
def check_login():
"""检查登录状态"""
try:
if session.get('logged_in') and session.get('user_id'):
token = request.headers.get('Authorization')
if not token:
return jsonify({
'success': True,
'logged_in': False
}), 200
if token.startswith('Bearer '):
token = token[7:]
result = verify_token(token)
if result['success']:
user_data = result['data']
return jsonify({
'success': True,
'logged_in': True,
'user': {
'id': session.get('user_id'),
'email': session.get('email'),
'username': session.get('username')
'id': user_data['user_id'],
'email': user_data['email'],
'username': user_data['username']
}
}), 200
else:

View File

@@ -1,78 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
小游戏模块 - 提供小游戏静态文件服务和目录扫描
Created by: 神奇万事通
Date: 2025-09-02
"""
from flask import Blueprint, jsonify
import os
smallgame_bp = Blueprint('smallgame', __name__)
@smallgame_bp.route('/scan-directories', methods=['GET'])
def scan_directories():
"""扫描smallgame目录结构"""
try:
# 获取项目根目录
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
game_directory = os.path.join(project_root, 'frontend', 'smallgame')
if not os.path.exists(game_directory):
return jsonify({
'success': False,
'message': 'smallgame目录不存在'
}), 404
games = []
# 颜色渐变配置
gradient_colors = [
'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)',
'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)',
'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)',
'linear-gradient(135deg, #ff8a80 0%, #ffab91 100%)',
'linear-gradient(135deg, #81c784 0%, #aed581 100%)'
]
# 扫描目录
for i, game_name in enumerate(os.listdir(game_directory)):
game_path = os.path.join(game_directory, game_name)
index_path = os.path.join(game_path, 'index.html')
if os.path.isdir(game_path) and os.path.exists(index_path) and not game_name.endswith('.txt'):
# 读取HTML文件获取标题
try:
with open(index_path, 'r', encoding='utf-8') as f:
html_content = f.read()
title_match = html_content.find('<title>')
if title_match != -1:
title_end = html_content.find('</title>', title_match)
if title_end != -1:
title = html_content[title_match + 7:title_end].strip()
else:
title = game_name
else:
title = game_name
except:
title = game_name
games.append({
'title': title,
'description': f'{game_name}小游戏',
'link': f'/smallgame/{game_name}/index.html',
'status': 'active',
'color': gradient_colors[i % len(gradient_colors)]
})
return jsonify({
'success': True,
'games': games
})
except Exception as e:
return jsonify({
'success': False,
'message': f'扫描目录时出错: {str(e)}'
}), 500

View File

@@ -10,6 +10,9 @@ pymongo==4.5.0
# 密码加密
Werkzeug==2.3.7
# JWT认证
PyJWT==2.8.0
# HTTP请求
requests==2.31.0

View File

@@ -1,190 +0,0 @@
/* 背景样式文件 */
/* 主体背景 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 50%, #a5d6a7 100%);
background-attachment: fixed;
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
}
/* 背景动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 装饰性背景元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(76, 175, 80, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(129, 199, 132, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(165, 214, 167, 0.08) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 浮动装饰圆点 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(129, 199, 132, 0.2), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(165, 214, 167, 0.3), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(76, 175, 80, 0.2), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(129, 199, 132, 0.3), transparent);
background-repeat: repeat;
background-size: 200px 100px;
animation: float 20s linear infinite;
pointer-events: none;
z-index: -1;
opacity: 0.6;
}
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
100% {
transform: translateY(0px);
}
}
/* 题目容器背景增强 */
.question-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow:
0 8px 32px rgba(26, 77, 26, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
/* 错误容器背景 */
.error-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* 结果容器背景 */
.result-container {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 代码块背景 */
.code-block {
background: rgba(248, 249, 250, 0.9);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
/* 选项背景 */
.option {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
.option:hover {
background: rgba(76, 175, 80, 0.05);
}
.option.selected {
background: linear-gradient(135deg, rgba(76, 175, 80, 0.15), rgba(76, 175, 80, 0.08));
}
/* 按钮背景增强 */
.submit-btn {
background: linear-gradient(135deg, #4caf50, #45a049);
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.2);
}
.show-answer-btn {
background: linear-gradient(135deg, #2196f3, #1976d2);
box-shadow: 0 4px 15px rgba(33, 150, 243, 0.2);
}
.retry-btn {
background: linear-gradient(135deg, #ff9800, #f57c00);
box-shadow: 0 4px 15px rgba(255, 152, 0, 0.2);
}
.refresh-btn {
background: linear-gradient(135deg, #4caf50, #45a049);
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.2);
}
/* 移动端背景优化 */
@media (max-width: 768px) {
body {
background-attachment: scroll;
}
body::after {
opacity: 0.4;
background-size: 150px 75px;
}
.question-container {
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
}
/* 高对比度模式支持 */
@media (prefers-contrast: high) {
body {
background: #f0f8f0;
}
body::before,
body::after {
display: none;
}
.question-container {
background: #ffffff;
border: 2px solid #4caf50;
}
}
/* 减少动画模式支持 */
@media (prefers-reduced-motion: reduce) {
body {
animation: none;
background: #e8f5e8;
}
body::after {
animation: none;
}
.refresh-btn:hover {
transform: scale(1.1);
}
}

View File

@@ -1,597 +0,0 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
color: #2d5a27;
min-height: 100vh;
overflow-x: hidden;
}
/* 容器布局 */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 40px;
padding: 30px 0;
}
.header h1 {
font-size: 2.5rem;
color: #1a4d1a;
margin-bottom: 10px;
font-weight: 700;
text-shadow: 0 2px 4px rgba(26, 77, 26, 0.1);
}
.subtitle {
font-size: 1.1rem;
color: #4a7c59;
opacity: 0.8;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
justify-content: center;
align-items: flex-start;
}
/* 加载动画 */
.loading {
text-align: center;
padding: 60px 20px;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid #e8f5e8;
border-top: 4px solid #4caf50;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading p {
color: #4a7c59;
font-size: 1.1rem;
}
/* 题目容器 */
.question-container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 10px 30px rgba(26, 77, 26, 0.1);
border: 2px solid rgba(76, 175, 80, 0.2);
width: 100%;
max-width: 800px;
backdrop-filter: blur(10px);
}
/* 题目头部 */
.question-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 2px solid #e8f5e8;
}
.question-id {
font-size: 1.1rem;
color: #4caf50;
font-weight: 600;
background: linear-gradient(135deg, #e8f5e8, #c8e6c9);
padding: 8px 16px;
border-radius: 20px;
}
.refresh-btn {
background: linear-gradient(135deg, #4caf50, #45a049);
border: none;
border-radius: 50%;
width: 45px;
height: 45px;
color: white;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.refresh-btn:hover {
transform: rotate(180deg) scale(1.1);
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3);
}
/* 题目文本 */
.question-text h2 {
font-size: 1.5rem;
color: #1a4d1a;
margin-bottom: 25px;
text-align: center;
}
/* 代码块 */
.code-block {
background: #f8f9fa;
border: 2px solid #e8f5e8;
border-radius: 12px;
margin: 25px 0;
overflow-x: auto;
position: relative;
}
.code-block pre {
margin: 0;
padding: 25px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 0.95rem;
line-height: 1.5;
color: #2d5a27;
white-space: pre-wrap;
word-wrap: break-word;
background: transparent !important;
}
.code-block code {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
background: transparent !important;
}
/* 代码高亮自定义样式 - 丰富的语法高亮 */
.code-block .hljs {
background: transparent !important;
color: #333333 !important;
}
/* JavaScript 关键字 - 蓝色 */
.code-block .hljs-keyword {
color: #0066cc !important;
font-weight: 600;
}
/* 字符串 - 绿色 */
.code-block .hljs-string {
color: #22aa22 !important;
}
/* 数字 - 橙色 */
.code-block .hljs-number {
color: #ff6600 !important;
}
/* 函数名 - 紫色 */
.code-block .hljs-function,
.code-block .hljs-title.function_ {
color: #9933cc !important;
font-weight: 600;
}
/* 变量名 - 深蓝色 */
.code-block .hljs-variable,
.code-block .hljs-name {
color: #0066aa !important;
}
/* 注释 - 灰色 */
.code-block .hljs-comment {
color: #888888 !important;
font-style: italic;
}
/* 内置对象和方法 - 深紫色 */
.code-block .hljs-built_in {
color: #663399 !important;
font-weight: 500;
}
/* 字面量 (true, false, null) - 红色 */
.code-block .hljs-literal {
color: #cc0000 !important;
font-weight: 600;
}
/* 操作符 - 深灰色 */
.code-block .hljs-operator {
color: #666666 !important;
}
/* 标点符号 - 深灰色 */
.code-block .hljs-punctuation {
color: #666666 !important;
}
/* 属性名 - 深蓝色 */
.code-block .hljs-property,
.code-block .hljs-attr {
color: #0066aa !important;
}
/* 类名和构造函数 - 深绿色 */
.code-block .hljs-title.class_,
.code-block .hljs-title {
color: #228833 !important;
font-weight: 600;
}
/* 参数 - 深蓝色 */
.code-block .hljs-params {
color: #0066aa !important;
}
/* 正则表达式 - 深红色 */
.code-block .hljs-regexp {
color: #aa0066 !important;
}
/* 模板字符串 - 深绿色 */
.code-block .hljs-template-variable,
.code-block .hljs-template-tag {
color: #228833 !important;
}
/* 选项容器 */
.options-container {
margin: 30px 0;
}
.option {
background: rgba(255, 255, 255, 0.8);
border: 2px solid #e8f5e8;
border-radius: 12px;
padding: 15px 20px;
margin: 12px 0;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1rem;
position: relative;
overflow: hidden;
}
.option:hover {
border-color: #4caf50;
background: rgba(76, 175, 80, 0.05);
transform: translateX(5px);
}
.option.selected {
border-color: #4caf50;
background: linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(76, 175, 80, 0.05));
color: #1a4d1a;
font-weight: 600;
}
.option.correct {
border-color: #4caf50;
background: linear-gradient(135deg, rgba(76, 175, 80, 0.2), rgba(76, 175, 80, 0.1));
color: #1a4d1a;
}
.option.incorrect {
border-color: #f44336;
background: linear-gradient(135deg, rgba(244, 67, 54, 0.1), rgba(244, 67, 54, 0.05));
color: #c62828;
}
/* 按钮样式 */
.action-buttons {
display: flex;
gap: 15px;
margin: 30px 0;
justify-content: center;
flex-wrap: wrap;
}
.submit-btn, .show-answer-btn, .retry-btn, .export-btn {
padding: 12px 30px;
border: none;
border-radius: 25px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 120px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.submit-btn {
background: linear-gradient(135deg, #4caf50, #45a049);
color: white;
}
.submit-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3);
}
.submit-btn:disabled {
background: #cccccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.show-answer-btn {
background: linear-gradient(135deg, #2196f3, #1976d2);
color: white;
}
.show-answer-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(33, 150, 243, 0.3);
}
.retry-btn {
background: linear-gradient(135deg, #ff9800, #f57c00);
color: white;
}
.retry-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(255, 152, 0, 0.3);
}
.export-btn {
background: linear-gradient(135deg, #9c27b0, #7b1fa2);
color: white;
}
.export-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(156, 39, 176, 0.3);
}
.export-btn svg {
width: 16px;
height: 16px;
}
/* 结果容器 */
.result-container {
margin-top: 30px;
padding: 25px;
background: rgba(255, 255, 255, 0.9);
border-radius: 15px;
border: 2px solid #e8f5e8;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e8f5e8;
}
.result-status {
font-size: 1.2rem;
font-weight: 600;
}
.result-status.correct {
color: #4caf50;
}
.result-status.incorrect {
color: #f44336;
}
.correct-answer {
font-weight: 600;
color: #4caf50;
background: rgba(76, 175, 80, 0.1);
padding: 5px 12px;
border-radius: 15px;
}
.explanation {
color: #2d5a27;
line-height: 1.7;
font-size: 1rem;
}
.explanation pre {
background: #f8f9fa;
border: 1px solid #e8f5e8;
border-radius: 8px;
padding: 15px;
margin: 15px 0;
overflow-x: auto;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
}
/* 错误容器 */
.error-container {
text-align: center;
padding: 60px 20px;
background: rgba(255, 255, 255, 0.9);
border-radius: 20px;
border: 2px solid rgba(244, 67, 54, 0.2);
max-width: 500px;
margin: 0 auto;
}
.error-icon {
font-size: 3rem;
margin-bottom: 20px;
}
.error-container h3 {
color: #f44336;
margin-bottom: 15px;
font-size: 1.5rem;
}
.error-container p {
color: #666;
margin-bottom: 25px;
font-size: 1.1rem;
}
/* 底部 */
.footer {
text-align: center;
padding: 30px 0;
margin-top: 40px;
color: #4a7c59;
opacity: 0.7;
border-top: 1px solid rgba(76, 175, 80, 0.2);
}
/* 平板端适配 (768px - 1024px) */
@media (max-width: 1024px) and (min-width: 768px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2.2rem;
}
.question-container {
padding: 30px;
}
.action-buttons {
flex-wrap: wrap;
}
}
/* 手机端适配 (最大768px) */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header {
margin-bottom: 25px;
padding: 20px 0;
}
.header h1 {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
}
.question-container {
padding: 20px;
border-radius: 15px;
}
.question-header {
flex-direction: column;
gap: 15px;
text-align: center;
}
.question-text h2 {
font-size: 1.3rem;
}
.code-block pre {
padding: 15px;
font-size: 0.85rem;
}
.option {
padding: 12px 15px;
font-size: 0.95rem;
}
.action-buttons {
flex-direction: column;
align-items: center;
}
.submit-btn, .show-answer-btn, .retry-btn {
width: 100%;
max-width: 200px;
}
.result-header {
flex-direction: column;
gap: 10px;
text-align: center;
}
.explanation {
font-size: 0.95rem;
}
.explanation pre {
padding: 10px;
font-size: 0.8rem;
}
}
/* 小屏手机适配 (最大480px) */
@media (max-width: 480px) {
.container {
padding: 8px;
}
.header h1 {
font-size: 1.6rem;
}
.question-container {
padding: 15px;
}
.question-id {
font-size: 1rem;
padding: 6px 12px;
}
.refresh-btn {
width: 40px;
height: 40px;
}
.code-block pre {
font-size: 0.8rem;
padding: 12px;
}
.option {
padding: 10px 12px;
font-size: 0.9rem;
}
.submit-btn, .show-answer-btn, .retry-btn {
padding: 10px 20px;
font-size: 0.95rem;
}
}

View File

@@ -1,91 +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>随机JavaScript趣味题</title>
<link rel="preconnect" href="https://cdnjs.cloudflare.com">
<link rel="dns-prefetch" href="https://60s.api.shumengya.top">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js"></script>
</head>
<body>
<div class="container">
<header class="header">
<h1>JavaScript趣味题</h1>
<p class="subtitle">测试你的JavaScript知识</p>
</header>
<main class="main-content">
<div class="loading" id="loading">
<div class="spinner"></div>
<p>正在加载题目...</p>
</div>
<div class="question-container" id="questionContainer" style="display: none;">
<div class="question-header">
<span class="question-id" id="questionId">题目 #1</span>
<button class="refresh-btn" id="refreshBtn" title="获取新题目">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10"></polyline>
<polyline points="1 20 1 14 7 14"></polyline>
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>
</svg>
</button>
</div>
<div class="question-text" id="questionText">
<h2>输出是什么?</h2>
</div>
<div class="code-block" id="codeBlock">
<pre><code id="codeContent" class="language-javascript"></code></pre>
</div>
<div class="options-container" id="optionsContainer">
<!-- 选项将通过JavaScript动态生成 -->
</div>
<div class="action-buttons">
<button class="submit-btn" id="submitBtn" disabled>提交答案</button>
<button class="show-answer-btn" id="showAnswerBtn">查看答案</button>
<button class="export-btn" id="exportBtn" title="导出为Markdown文件">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
导出MD
</button>
</div>
<div class="result-container" id="resultContainer" style="display: none;">
<div class="result-header">
<span class="result-status" id="resultStatus"></span>
<span class="correct-answer" id="correctAnswer"></span>
</div>
<div class="explanation" id="explanation">
<!-- 解析内容 -->
</div>
</div>
</div>
<div class="error-container" id="errorContainer" style="display: none;">
<div class="error-icon">⚠️</div>
<h3>加载失败</h3>
<p id="errorMessage">网络连接异常,请稍后重试</p>
<button class="retry-btn" id="retryBtn">重新加载</button>
</div>
</main>
<footer class="footer">
<p>JavaScript趣味题集合</p>
</footer>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,582 +0,0 @@
// JavaScript趣味题应用
class JSQuizApp {
constructor() {
this.apiEndpoints = [
'https://60s.api.shumengya.top',
];
this.currentApiIndex = 0;
this.currentQuestion = null;
this.selectedOption = null;
this.isAnswered = false;
this.loadStartTime = null;
this.initElements();
this.bindEvents();
this.preloadResources();
this.loadQuestion();
}
// 初始化DOM元素
initElements() {
this.elements = {
loading: document.getElementById('loading'),
questionContainer: document.getElementById('questionContainer'),
errorContainer: document.getElementById('errorContainer'),
questionId: document.getElementById('questionId'),
questionText: document.getElementById('questionText'),
codeContent: document.getElementById('codeContent'),
optionsContainer: document.getElementById('optionsContainer'),
submitBtn: document.getElementById('submitBtn'),
showAnswerBtn: document.getElementById('showAnswerBtn'),
refreshBtn: document.getElementById('refreshBtn'),
retryBtn: document.getElementById('retryBtn'),
exportBtn: document.getElementById('exportBtn'),
resultContainer: document.getElementById('resultContainer'),
resultStatus: document.getElementById('resultStatus'),
correctAnswer: document.getElementById('correctAnswer'),
explanation: document.getElementById('explanation'),
errorMessage: document.getElementById('errorMessage')
};
}
// 预加载资源
preloadResources() {
// 预连接API服务器
this.apiEndpoints.forEach(endpoint => {
const link = document.createElement('link');
link.rel = 'preconnect';
link.href = endpoint;
document.head.appendChild(link);
});
}
// 绑定事件
bindEvents() {
this.elements.submitBtn.addEventListener('click', () => this.submitAnswer());
this.elements.showAnswerBtn.addEventListener('click', () => this.showAnswer());
this.elements.refreshBtn.addEventListener('click', () => this.loadQuestion());
this.elements.retryBtn.addEventListener('click', () => this.loadQuestion());
this.elements.exportBtn.addEventListener('click', () => this.exportToMarkdown());
}
// 显示加载状态
showLoading() {
this.elements.loading.style.display = 'block';
this.elements.questionContainer.style.display = 'none';
this.elements.errorContainer.style.display = 'none';
}
// 显示题目
showQuestion() {
this.elements.loading.style.display = 'none';
this.elements.questionContainer.style.display = 'block';
this.elements.errorContainer.style.display = 'none';
}
// 显示错误
showError(message) {
this.elements.loading.style.display = 'none';
this.elements.questionContainer.style.display = 'none';
this.elements.errorContainer.style.display = 'block';
this.elements.errorMessage.textContent = message;
}
// 获取当前API地址
getCurrentApiUrl() {
return `${this.apiEndpoints[this.currentApiIndex]}/v2/awesome-js`;
}
// 切换到下一个API
switchToNextApi() {
this.currentApiIndex = (this.currentApiIndex + 1) % this.apiEndpoints.length;
}
// 加载题目
async loadQuestion() {
this.loadStartTime = Date.now();
this.showLoading();
this.resetQuestion();
let attempts = 0;
const maxAttempts = this.apiEndpoints.length;
while (attempts < maxAttempts) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(this.getCurrentApiUrl(), {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.code === 200 && data.data) {
this.currentQuestion = data.data;
const loadTime = Date.now() - this.loadStartTime;
console.log(`题目加载完成,耗时: ${loadTime}ms`);
this.displayQuestion();
return;
} else {
throw new Error(data.message || '数据格式错误');
}
} catch (error) {
console.warn(`API ${this.getCurrentApiUrl()} 请求失败:`, error.message);
attempts++;
if (attempts < maxAttempts) {
this.switchToNextApi();
console.log(`切换到备用API: ${this.getCurrentApiUrl()}`);
} else {
this.showError(`所有API接口都无法访问请检查网络连接后重试。\n最后尝试的错误: ${error.message}`);
return;
}
}
}
}
// 重置题目状态
resetQuestion() {
this.selectedOption = null;
this.isAnswered = false;
this.elements.resultContainer.style.display = 'none';
this.elements.submitBtn.disabled = true;
this.elements.submitBtn.textContent = '提交答案';
this.elements.showAnswerBtn.style.display = 'inline-block';
// 清空选项容器,防止重复显示
this.elements.optionsContainer.innerHTML = '';
// 移除所有选项的事件监听器
const existingOptions = document.querySelectorAll('.option');
existingOptions.forEach(option => {
option.removeEventListener('click', this.selectOption);
});
}
// 显示题目内容
displayQuestion() {
const question = this.currentQuestion;
console.log('显示题目:', question);
// 设置题目ID
this.elements.questionId.textContent = `题目 #${question.id}`;
// 设置题目文本
this.elements.questionText.innerHTML = `<h2>${this.escapeHtml(question.question)}</h2>`;
// 设置代码内容并应用语法高亮
this.elements.codeContent.textContent = question.code;
// 应用语法高亮
if (typeof hljs !== 'undefined') {
hljs.highlightElement(this.elements.codeContent);
}
// 确保选项容器已清空
this.elements.optionsContainer.innerHTML = '';
// 生成选项
this.generateOptions(question.options);
this.showQuestion();
}
// 生成选项
generateOptions(options) {
// 确保清空容器
this.elements.optionsContainer.innerHTML = '';
// 验证选项数据
if (!Array.isArray(options) || options.length === 0) {
console.error('选项数据无效:', options);
return;
}
// 移除可能存在的重复选项
const uniqueOptions = [...new Set(options)];
uniqueOptions.forEach((option, index) => {
const optionElement = document.createElement('div');
optionElement.className = 'option';
optionElement.textContent = option;
optionElement.dataset.index = index;
optionElement.dataset.value = option.charAt(0); // A, B, C, D
optionElement.addEventListener('click', () => this.selectOption(optionElement));
this.elements.optionsContainer.appendChild(optionElement);
});
console.log('生成选项:', uniqueOptions);
}
// 选择选项
selectOption(optionElement) {
if (this.isAnswered) return;
// 移除之前的选中状态
document.querySelectorAll('.option.selected').forEach(el => {
el.classList.remove('selected');
});
// 设置当前选中
optionElement.classList.add('selected');
this.selectedOption = optionElement.dataset.value;
// 启用提交按钮
this.elements.submitBtn.disabled = false;
}
// 提交答案
submitAnswer() {
if (!this.selectedOption || this.isAnswered) return;
this.isAnswered = true;
this.elements.submitBtn.disabled = true;
this.elements.submitBtn.textContent = '已提交';
this.elements.showAnswerBtn.style.display = 'none';
const isCorrect = this.selectedOption === this.currentQuestion.answer;
// 显示结果
this.showResult(isCorrect);
// 标记选项
this.markOptions();
}
// 显示答案
showAnswer() {
this.isAnswered = true;
this.elements.submitBtn.disabled = true;
this.elements.submitBtn.textContent = '已显示答案';
this.elements.showAnswerBtn.style.display = 'none';
// 显示结果(不判断对错)
this.showResult(null);
// 标记正确答案
this.markCorrectAnswer();
}
// 显示结果
showResult(isCorrect) {
const resultContainer = this.elements.resultContainer;
const resultStatus = this.elements.resultStatus;
const correctAnswer = this.elements.correctAnswer;
const explanation = this.elements.explanation;
// 设置结果状态
if (isCorrect === true) {
resultStatus.textContent = '✅ 回答正确!';
resultStatus.className = 'result-status correct';
} else if (isCorrect === false) {
resultStatus.textContent = '❌ 回答错误';
resultStatus.className = 'result-status incorrect';
} else {
resultStatus.textContent = '💡 答案解析';
resultStatus.className = 'result-status';
}
// 设置正确答案
correctAnswer.textContent = `正确答案: ${this.currentQuestion.answer}`;
// 设置解析内容
explanation.innerHTML = this.formatExplanation(this.currentQuestion.explanation);
// 显示结果容器
resultContainer.style.display = 'block';
// 滚动到结果区域
setTimeout(() => {
resultContainer.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 100);
}
// 标记选项
markOptions() {
const options = document.querySelectorAll('.option');
const correctAnswer = this.currentQuestion.answer;
options.forEach(option => {
const optionValue = option.dataset.value;
if (optionValue === correctAnswer) {
option.classList.add('correct');
} else if (option.classList.contains('selected')) {
option.classList.add('incorrect');
}
// 禁用点击
option.style.pointerEvents = 'none';
});
}
// 标记正确答案
markCorrectAnswer() {
const options = document.querySelectorAll('.option');
const correctAnswer = this.currentQuestion.answer;
options.forEach(option => {
const optionValue = option.dataset.value;
if (optionValue === correctAnswer) {
option.classList.add('correct');
}
// 禁用点击
option.style.pointerEvents = 'none';
});
}
// 格式化解析内容
formatExplanation(explanation) {
// 转义HTML
let formatted = this.escapeHtml(explanation);
// 处理代码块
formatted = formatted.replace(/```js\n([\s\S]*?)\n```/g, '<pre><code>$1</code></pre>');
formatted = formatted.replace(/```javascript\n([\s\S]*?)\n```/g, '<pre><code>$1</code></pre>');
formatted = formatted.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
// 处理行内代码
formatted = formatted.replace(/`([^`]+)`/g, '<code style="background: #f0f0f0; padding: 2px 4px; border-radius: 3px; font-family: monospace;">$1</code>');
// 处理换行
formatted = formatted.replace(/\n\n/g, '</p><p>');
formatted = formatted.replace(/\n/g, '<br>');
// 包装段落
if (!formatted.includes('<p>')) {
formatted = '<p>' + formatted + '</p>';
}
return formatted;
}
// HTML转义
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 导出为Markdown
exportToMarkdown() {
if (!this.currentQuestion) {
alert('请先加载题目后再导出!');
return;
}
const question = this.currentQuestion;
const timestamp = new Date().toLocaleString('zh-CN');
// 构建Markdown内容
let markdown = `# JavaScript趣味题 #${question.id}\n\n`;
markdown += `> 导出时间: ${timestamp}\n\n`;
// 题目部分
markdown += `## 题目\n\n`;
markdown += `${question.question}\n\n`;
// 代码部分
markdown += `## 代码\n\n`;
markdown += `\`\`\`javascript\n${question.code}\n\`\`\`\n\n`;
// 选项部分
markdown += `## 选项\n\n`;
question.options.forEach((option, index) => {
const letter = String.fromCharCode(65 + index); // A, B, C, D
const isCorrect = letter === question.answer;
markdown += `${letter}. ${option}${isCorrect ? ' ✅' : ''}\n`;
});
markdown += `\n`;
// 答案部分
markdown += `## 正确答案\n\n`;
markdown += `**${question.answer}**\n\n`;
// 解析部分
markdown += `## 答案解析\n\n`;
// 清理解析内容中的HTML标签转换为Markdown格式
let explanation = question.explanation;
explanation = explanation.replace(/<br\s*\/?>/gi, '\n');
explanation = explanation.replace(/<p>/gi, '\n');
explanation = explanation.replace(/<\/p>/gi, '\n');
explanation = explanation.replace(/<code[^>]*>/gi, '`');
explanation = explanation.replace(/<\/code>/gi, '`');
explanation = explanation.replace(/<pre><code>/gi, '\n```javascript\n');
explanation = explanation.replace(/<\/code><\/pre>/gi, '\n```\n');
explanation = explanation.replace(/<[^>]*>/g, ''); // 移除其他HTML标签
explanation = explanation.replace(/\n\s*\n/g, '\n\n'); // 清理多余空行
markdown += explanation.trim() + '\n\n';
// 添加页脚
markdown += `---\n\n`;
markdown += `*本题目来源于JavaScript趣味题集合*\n`;
markdown += `*导出工具: JavaScript趣味题网页版*\n`;
// 创建下载
this.downloadMarkdown(markdown, `JavaScript趣味题_${question.id}_${new Date().getTime()}.md`);
}
// 下载Markdown文件
downloadMarkdown(content, filename) {
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 清理URL对象
setTimeout(() => {
URL.revokeObjectURL(url);
}, 100);
// 显示成功提示
this.showExportSuccess(filename);
}
// 显示导出成功提示
showExportSuccess(filename) {
// 创建临时提示元素
const toast = document.createElement('div');
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #4caf50, #45a049);
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
z-index: 10000;
font-size: 14px;
max-width: 300px;
word-wrap: break-word;
animation: slideInRight 0.3s ease-out;
`;
toast.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<div>
<div style="font-weight: 600;">导出成功!</div>
<div style="font-size: 12px; opacity: 0.9; margin-top: 2px;">${filename}</div>
</div>
</div>
`;
// 添加动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
document.head.appendChild(style);
document.body.appendChild(toast);
// 3秒后自动消失
setTimeout(() => {
toast.style.animation = 'slideOutRight 0.3s ease-in';
setTimeout(() => {
if (toast.parentNode) {
document.body.removeChild(toast);
}
if (style.parentNode) {
document.head.removeChild(style);
}
}, 300);
}, 3000);
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
new JSQuizApp();
});
// 添加键盘快捷键支持
document.addEventListener('keydown', (e) => {
// 按R键刷新题目
if (e.key.toLowerCase() === 'r' && !e.ctrlKey && !e.metaKey) {
const refreshBtn = document.getElementById('refreshBtn');
if (refreshBtn && !document.querySelector('.loading').style.display !== 'none') {
refreshBtn.click();
}
}
// 按数字键1-4选择选项
if (['1', '2', '3', '4'].includes(e.key)) {
const options = document.querySelectorAll('.option');
const index = parseInt(e.key) - 1;
if (options[index] && !options[index].style.pointerEvents) {
options[index].click();
}
}
// 按Enter键提交答案
if (e.key === 'Enter') {
const submitBtn = document.getElementById('submitBtn');
if (submitBtn && !submitBtn.disabled) {
submitBtn.click();
}
}
});
// 添加触摸设备支持
if ('ontouchstart' in window) {
document.addEventListener('touchstart', () => {}, { passive: true });
}
// 添加网络状态监听
if ('navigator' in window && 'onLine' in navigator) {
window.addEventListener('online', () => {
console.log('网络连接已恢复');
});
window.addEventListener('offline', () => {
console.log('网络连接已断开');
});
}

View File

@@ -1,3 +0,0 @@
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,17 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"id": 11,
"question": "输出是什么?",
"code": "function Person(firstName, lastName) {\n this.firstName = firstName;\n this.lastName = lastName;\n}\n\nconst member = new Person(\"Lydia\", \"Hallie\");\nPerson.getFullName = function () {\n return `${this.firstName} ${this.lastName}`;\n}\n\nconsole.log(member.getFullName());",
"options": [
"A: `TypeError`",
"B: `SyntaxError`",
"C: `Lydia Hallie`",
"D: `undefined` `undefined`"
],
"answer": "A",
"explanation": "你不能像常规对象那样,给构造函数添加属性。如果你想一次性给所有实例添加特性,你应该使用原型。因此本例中,使用如下方式:\n\n```js\nPerson.prototype.getFullName = function () {\n return `${this.firstName} ${this.lastName}`;\n}\n```\n\n这才会使 `member.getFullName()` 起作用。为什么这么做有益的?假设我们将这个方法添加到构造函数本身里。也许不是每个 `Person` 实例都需要这个方法。这将浪费大量内存空间,因为它们仍然具有该属性,这将占用每个实例的内存空间。相反,如果我们只将它添加到原型中,那么它只存在于内存中的一个位置,但是所有实例都可以访问它!"
}
}

View File

@@ -1,81 +0,0 @@
/* 背景样式文件 */
body {
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 25%, #ffd3a5 50%, #a8e6cf 75%, #88d8a3 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
position: relative;
}
/* 背景动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(39, 174, 96, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(46, 204, 113, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(26, 188, 156, 0.05) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 浮动装饰圆点 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(39, 174, 96, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(46, 204, 113, 0.2), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(26, 188, 156, 0.3), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(39, 174, 96, 0.2), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(46, 204, 113, 0.3), transparent);
background-repeat: repeat;
background-size: 200px 100px;
animation: floatDots 20s linear infinite;
pointer-events: none;
z-index: -1;
}
@keyframes floatDots {
0% {
transform: translateY(0px);
}
100% {
transform: translateY(-100px);
}
}
/* 响应式背景调整 */
@media (max-width: 768px) {
body::after {
background-size: 150px 75px;
animation-duration: 25s;
}
}
@media (max-width: 480px) {
body::after {
background-size: 100px 50px;
animation-duration: 30s;
}
}

View File

@@ -1,339 +0,0 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #2c3e50;
min-height: 100vh;
overflow-x: hidden;
}
/* 容器布局 */
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 40px;
padding: 30px 0;
}
.title {
font-size: 2.5rem;
font-weight: 700;
color: #27ae60;
margin-bottom: 10px;
text-shadow: 0 2px 4px rgba(39, 174, 96, 0.2);
}
.subtitle {
font-size: 1.1rem;
color: #7f8c8d;
font-weight: 400;
}
/* 主要内容区域 */
.main {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.content-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(39, 174, 96, 0.1);
width: 100%;
max-width: 600px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.content-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
}
/* KFC文案内容 */
.kfc-content {
min-height: 200px;
padding: 30px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 15px;
border-left: 5px solid #27ae60;
margin-bottom: 30px;
position: relative;
overflow: hidden;
}
.kfc-content::before {
content: '"';
position: absolute;
top: 10px;
left: 15px;
font-size: 3rem;
color: #27ae60;
opacity: 0.3;
font-family: serif;
}
.kfc-content p {
font-size: 1.1rem;
line-height: 1.8;
color: #2c3e50;
margin-left: 20px;
position: relative;
z-index: 1;
}
.loading-text {
text-align: center;
color: #7f8c8d;
font-style: italic;
}
/* 按钮组 */
.button-group {
display: flex;
gap: 15px;
justify-content: center;
margin-bottom: 20px;
}
.generate-btn, .copy-btn {
padding: 15px 30px;
border: none;
border-radius: 50px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.generate-btn {
background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
color: white;
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
}
.generate-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
}
.generate-btn:active {
transform: translateY(0);
}
.generate-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.copy-btn {
background: linear-gradient(135deg, #3498db 0%, #5dade2 100%);
color: white;
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
}
.copy-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
}
/* 编号信息 */
.index-info {
text-align: center;
padding: 10px;
background: rgba(39, 174, 96, 0.1);
border-radius: 10px;
border: 1px solid rgba(39, 174, 96, 0.2);
}
.index-text {
color: #27ae60;
font-weight: 600;
font-size: 0.9rem;
}
#indexNumber {
color: #2c3e50;
font-weight: 700;
}
/* 底部 */
.footer {
text-align: center;
padding: 20px 0;
color: #7f8c8d;
font-size: 0.9rem;
margin-top: 40px;
}
/* 提示框 */
.toast {
position: fixed;
top: 20px;
right: 20px;
background: #27ae60;
color: white;
padding: 15px 25px;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
transform: translateX(400px);
transition: transform 0.3s ease;
z-index: 1000;
font-weight: 600;
}
.toast.show {
transform: translateX(0);
}
/* 平板端适配 (768px - 1024px) */
@media (max-width: 1024px) and (min-width: 768px) {
.container {
padding: 15px;
}
.title {
font-size: 2.2rem;
}
.content-card {
padding: 35px;
max-width: 550px;
}
.kfc-content {
padding: 25px;
min-height: 180px;
}
.button-group {
flex-direction: row;
gap: 12px;
}
.generate-btn, .copy-btn {
padding: 12px 25px;
font-size: 0.95rem;
}
}
/* 手机端适配 (最大768px) */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header {
margin-bottom: 30px;
padding: 20px 0;
}
.title {
font-size: 1.8rem;
margin-bottom: 8px;
}
.subtitle {
font-size: 1rem;
}
.content-card {
padding: 25px;
margin: 0 5px;
border-radius: 15px;
}
.kfc-content {
padding: 20px;
min-height: 150px;
margin-bottom: 25px;
}
.kfc-content::before {
font-size: 2.5rem;
top: 5px;
left: 10px;
}
.kfc-content p {
font-size: 1rem;
line-height: 1.7;
margin-left: 15px;
}
.button-group {
flex-direction: column;
gap: 10px;
}
.generate-btn, .copy-btn {
padding: 12px 20px;
font-size: 0.9rem;
width: 100%;
}
.footer {
font-size: 0.8rem;
margin-top: 30px;
}
.toast {
right: 10px;
left: 10px;
transform: translateY(-100px);
font-size: 0.9rem;
}
.toast.show {
transform: translateY(0);
}
}
/* 小屏手机适配 (最大480px) */
@media (max-width: 480px) {
.title {
font-size: 1.6rem;
}
.content-card {
padding: 20px;
margin: 0;
}
.kfc-content {
padding: 15px;
min-height: 120px;
}
.kfc-content p {
font-size: 0.95rem;
margin-left: 10px;
}
.generate-btn, .copy-btn {
padding: 10px 15px;
font-size: 0.85rem;
}
}

View File

@@ -1,46 +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>随机KFC文案生成器</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1 class="title">🍗 随机KFC文案生成器</h1>
<p class="subtitle">疯狂星期四,文案来一套!</p>
</header>
<main class="main">
<div class="content-card">
<div class="kfc-content" id="kfcContent">
<p class="loading-text">点击按钮获取随机KFC文案...</p>
</div>
<div class="button-group">
<button class="generate-btn" id="generateBtn">
<span class="btn-text">生成文案</span>
<span class="btn-loading" style="display: none;">生成中...</span>
</button>
<button class="copy-btn" id="copyBtn" style="display: none;">复制文案</button>
</div>
<div class="index-info" id="indexInfo" style="display: none;">
<span class="index-text">文案编号: <span id="indexNumber"></span></span>
</div>
</div>
</main>
<footer class="footer">
<p>© 2024 KFC文案生成器 | 让每个星期四都疯狂起来</p>
</footer>
</div>
<div class="toast" id="toast"></div>
<script src="js/main.js"></script>
</body>
</html>

View File

@@ -1,240 +0,0 @@
// KFC文案生成器主要功能
class KFCGenerator {
constructor() {
this.apiEndpoints = [];
this.currentApiIndex = 0;
this.isLoading = false;
this.init();
}
// 初始化
async init() {
await this.loadApiEndpoints();
this.bindEvents();
}
// 加载API接口列表
async loadApiEndpoints() {
try {
// 直接硬编码API端点避免CORS问题
this.apiEndpoints = ["https://60s.api.shumengya.top"];
} catch (error) {
console.error('加载API接口列表失败:', error);
this.showToast('加载接口配置失败', 'error');
}
}
// 绑定事件
bindEvents() {
const generateBtn = document.getElementById('generateBtn');
const copyBtn = document.getElementById('copyBtn');
generateBtn.addEventListener('click', () => this.generateKFC());
copyBtn.addEventListener('click', () => this.copyContent());
}
// 生成KFC文案
async generateKFC() {
if (this.isLoading) return;
this.setLoadingState(true);
let success = false;
let attempts = 0;
const maxAttempts = this.apiEndpoints.length;
while (!success && attempts < maxAttempts) {
try {
const apiUrl = this.apiEndpoints[this.currentApiIndex];
const data = await this.fetchKFCData(apiUrl);
if (data && data.code === 200 && data.data && data.data.kfc) {
this.displayKFC(data.data);
success = true;
} else {
throw new Error('API返回数据格式错误');
}
} catch (error) {
console.error(`API ${this.currentApiIndex + 1} 请求失败:`, error);
this.currentApiIndex = (this.currentApiIndex + 1) % this.apiEndpoints.length;
attempts++;
}
}
if (!success) {
this.showError('所有API接口都无法访问请稍后重试');
}
this.setLoadingState(false);
}
// 请求KFC数据
async fetchKFCData(apiUrl) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
const response = await fetch(`${apiUrl}/v2/kfc`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
// 显示KFC文案
displayKFC(data) {
const contentElement = document.getElementById('kfcContent');
const indexElement = document.getElementById('indexNumber');
const indexInfo = document.getElementById('indexInfo');
const copyBtn = document.getElementById('copyBtn');
// 显示文案内容
contentElement.innerHTML = `<p>${this.escapeHtml(data.kfc)}</p>`;
// 显示编号信息
if (data.index) {
indexElement.textContent = data.index;
indexInfo.style.display = 'block';
} else {
indexInfo.style.display = 'none';
}
// 显示复制按钮
copyBtn.style.display = 'inline-block';
// 添加显示动画
contentElement.style.opacity = '0';
contentElement.style.transform = 'translateY(20px)';
setTimeout(() => {
contentElement.style.transition = 'all 0.5s ease';
contentElement.style.opacity = '1';
contentElement.style.transform = 'translateY(0)';
}, 100);
}
// 显示错误信息
showError(message) {
const contentElement = document.getElementById('kfcContent');
contentElement.innerHTML = `<p class="loading-text" style="color: #e74c3c;">${this.escapeHtml(message)}</p>`;
const copyBtn = document.getElementById('copyBtn');
const indexInfo = document.getElementById('indexInfo');
copyBtn.style.display = 'none';
indexInfo.style.display = 'none';
}
// 复制文案内容
async copyContent() {
const contentElement = document.getElementById('kfcContent');
const textContent = contentElement.querySelector('p')?.textContent;
if (!textContent || textContent.includes('点击按钮获取') || textContent.includes('失败')) {
this.showToast('没有可复制的内容', 'error');
return;
}
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(textContent);
} else {
// 降级方案
const textArea = document.createElement('textarea');
textArea.value = textContent;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
document.execCommand('copy');
textArea.remove();
}
this.showToast('文案已复制到剪贴板', 'success');
} catch (error) {
console.error('复制失败:', error);
this.showToast('复制失败,请手动选择复制', 'error');
}
}
// 设置加载状态
setLoadingState(loading) {
this.isLoading = loading;
const generateBtn = document.getElementById('generateBtn');
const btnText = generateBtn.querySelector('.btn-text');
const btnLoading = generateBtn.querySelector('.btn-loading');
if (loading) {
generateBtn.disabled = true;
btnText.style.display = 'none';
btnLoading.style.display = 'inline';
} else {
generateBtn.disabled = false;
btnText.style.display = 'inline';
btnLoading.style.display = 'none';
}
}
// 显示提示消息
showToast(message, type = 'success') {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = `toast ${type}`;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// HTML转义
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
const generator = new KFCGenerator();
// 页面加载完成后自动生成一条文案
setTimeout(() => {
generator.generateKFC();
}, 1000);
});
// 添加键盘快捷键支持
document.addEventListener('keydown', (event) => {
// 按空格键生成文案
if (event.code === 'Space' && event.target.tagName !== 'INPUT' && event.target.tagName !== 'TEXTAREA') {
event.preventDefault();
document.getElementById('generateBtn').click();
}
// Ctrl+C 复制文案
if (event.ctrlKey && event.key === 'c' && event.target.tagName !== 'INPUT' && event.target.tagName !== 'TEXTAREA') {
const copyBtn = document.getElementById('copyBtn');
if (copyBtn.style.display !== 'none') {
event.preventDefault();
copyBtn.click();
}
}
});

View File

@@ -1,3 +0,0 @@
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,8 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"index": 78,
"kfc": "我叫夯大力 立冬给我准备了糖炒栗子了没有 没准备的自动绝交 再 v 我 50 吃疯狂星期四 然后再给我点杯奶茶 再给我两万块钱 懂吗你们"
}
}

View File

@@ -1,167 +0,0 @@
/* 背景样式文件 - 金色光辉主题 */
/* 主背景 */
body {
background: linear-gradient(
135deg,
#fff8dc 0%,
#ffeaa7 25%,
#fdcb6e 50%,
#e17055 75%,
#d63031 100%
);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
position: relative;
}
/* 背景装饰层 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 80%, rgba(255, 215, 0, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 223, 0, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(212, 175, 55, 0.05) 0%, transparent 50%);
pointer-events: none;
z-index: 1;
}
/* 动态光点效果 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(255, 215, 0, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(255, 223, 0, 0.2), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(212, 175, 55, 0.4), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(255, 215, 0, 0.2), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(255, 223, 0, 0.3), transparent);
background-repeat: repeat;
background-size: 200px 100px;
animation: sparkle 20s linear infinite;
pointer-events: none;
z-index: 2;
}
/* 背景动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
@keyframes sparkle {
0% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(-100vh);
opacity: 0;
}
}
/* 响应式背景调整 */
/* 平板端背景 */
@media (min-width: 768px) and (max-width: 1024px) {
body::after {
background-size: 250px 120px;
}
}
/* 电脑端背景 */
@media (min-width: 1024px) {
body {
background-size: 300% 300%;
}
body::after {
background-size: 300px 150px;
animation-duration: 25s;
}
}
/* 手机端背景优化 */
@media (max-width: 767px) {
body {
background-size: 200% 200%;
animation-duration: 10s;
}
body::before {
background:
radial-gradient(circle at 30% 70%, rgba(255, 215, 0, 0.08) 0%, transparent 40%),
radial-gradient(circle at 70% 30%, rgba(255, 223, 0, 0.08) 0%, transparent 40%);
}
body::after {
background-size: 150px 80px;
animation-duration: 15s;
}
}
/* 超小屏幕背景 */
@media (max-width: 479px) {
body {
background: linear-gradient(
135deg,
#fff8dc 0%,
#ffeaa7 50%,
#fdcb6e 100%
);
background-size: 150% 150%;
}
body::after {
background-size: 120px 60px;
}
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
body {
background: linear-gradient(
135deg,
#2c1810 0%,
#3d2914 25%,
#4a3319 50%,
#5c3e1f 75%,
#6b4423 100%
);
}
body::before {
background:
radial-gradient(circle at 20% 80%, rgba(255, 215, 0, 0.05) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 223, 0, 0.05) 0%, transparent 50%);
}
}
/* 减少动画效果(用户偏好) */
@media (prefers-reduced-motion: reduce) {
body,
body::before,
body::after {
animation: none;
}
body {
background: linear-gradient(135deg, #fff8dc 0%, #ffeaa7 50%, #fdcb6e 100%);
}
}

View File

@@ -1,357 +0,0 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #2c1810;
overflow-x: hidden;
}
/* 容器布局 */
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
position: relative;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 40px;
z-index: 10;
}
.title {
font-size: 3rem;
font-weight: 700;
color: #d4af37;
text-shadow:
0 0 10px rgba(212, 175, 55, 0.8),
0 0 20px rgba(212, 175, 55, 0.6),
0 0 30px rgba(212, 175, 55, 0.4);
margin-bottom: 10px;
animation: titleGlow 3s ease-in-out infinite alternate;
}
.subtitle {
font-size: 1.2rem;
color: #b8860b;
opacity: 0.9;
text-shadow: 0 0 5px rgba(184, 134, 11, 0.5);
}
/* 主内容区域 */
.main-content {
width: 100%;
max-width: 800px;
z-index: 10;
}
/* 一言容器 */
.quote-container {
background: linear-gradient(135deg, rgba(255, 215, 0, 0.1), rgba(255, 223, 0, 0.05));
border: 2px solid rgba(212, 175, 55, 0.3);
border-radius: 20px;
padding: 40px;
margin-bottom: 30px;
backdrop-filter: blur(10px);
box-shadow:
0 8px 32px rgba(212, 175, 55, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
position: relative;
overflow: hidden;
}
.quote-container::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, #ffd700, #ffed4e, #ffd700, #ffed4e);
border-radius: 22px;
z-index: -1;
animation: borderGlow 4s linear infinite;
}
/* 加载状态 */
.loading {
display: none;
text-align: center;
color: #d4af37;
}
.loading.show {
display: block;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(212, 175, 55, 0.3);
border-top: 4px solid #d4af37;
border-radius: 50%;
margin: 0 auto 15px;
animation: spin 1s linear infinite;
}
/* 一言显示 */
.quote-display {
display: block;
text-align: center;
}
.quote-display.hide {
display: none;
}
.quote-text {
font-size: 1.8rem;
line-height: 1.8;
color: #2c1810;
margin-bottom: 20px;
text-shadow: 0 1px 2px rgba(212, 175, 55, 0.1);
font-weight: 500;
}
.quote-index {
font-size: 0.9rem;
color: #b8860b;
opacity: 0.8;
}
/* 错误信息 */
.error-message {
display: none;
text-align: center;
color: #cd853f;
}
.error-message.show {
display: block;
}
.error-icon {
font-size: 2rem;
margin-bottom: 10px;
}
.error-text {
font-size: 1.1rem;
line-height: 1.5;
}
/* 控制按钮 */
.controls {
text-align: center;
}
.refresh-btn {
background: linear-gradient(135deg, #ffd700, #ffed4e);
border: none;
border-radius: 50px;
padding: 15px 30px;
font-size: 1.1rem;
font-weight: 600;
color: #2c1810;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 10px;
transition: all 0.3s ease;
box-shadow:
0 4px 15px rgba(212, 175, 55, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
position: relative;
overflow: hidden;
}
.refresh-btn:hover {
transform: translateY(-2px);
box-shadow:
0 6px 20px rgba(212, 175, 55, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.refresh-btn:active {
transform: translateY(0);
}
.refresh-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-icon {
font-size: 1.2rem;
transition: transform 0.3s ease;
}
.refresh-btn:hover .btn-icon {
transform: rotate(180deg);
}
/* 底部 */
.footer {
margin-top: 40px;
text-align: center;
color: #b8860b;
opacity: 0.8;
font-size: 0.9rem;
}
/* 动画效果 */
@keyframes titleGlow {
0% {
text-shadow:
0 0 10px rgba(212, 175, 55, 0.8),
0 0 20px rgba(212, 175, 55, 0.6),
0 0 30px rgba(212, 175, 55, 0.4);
}
100% {
text-shadow:
0 0 15px rgba(212, 175, 55, 1),
0 0 25px rgba(212, 175, 55, 0.8),
0 0 35px rgba(212, 175, 55, 0.6);
}
}
@keyframes borderGlow {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 30px;
}
.title {
font-size: 3.5rem;
}
.subtitle {
font-size: 1.3rem;
}
.quote-container {
padding: 50px;
}
.quote-text {
font-size: 2rem;
}
}
/* 电脑端适配 (1024px+) */
@media (min-width: 1024px) {
.container {
padding: 40px;
}
.title {
font-size: 4rem;
}
.subtitle {
font-size: 1.4rem;
}
.quote-container {
padding: 60px;
max-width: 900px;
}
.quote-text {
font-size: 2.2rem;
line-height: 1.9;
}
.refresh-btn {
padding: 18px 36px;
font-size: 1.2rem;
}
}
/* 手机端适配 (小于768px) */
@media (max-width: 767px) {
.container {
padding: 15px;
}
.header {
margin-bottom: 30px;
}
.title {
font-size: 2.5rem;
}
.subtitle {
font-size: 1rem;
}
.quote-container {
padding: 25px;
border-radius: 15px;
}
.quote-text {
font-size: 1.4rem;
line-height: 1.6;
}
.refresh-btn {
padding: 12px 24px;
font-size: 1rem;
}
.footer {
margin-top: 30px;
font-size: 0.8rem;
}
}
/* 超小屏幕适配 (小于480px) */
@media (max-width: 479px) {
.title {
font-size: 2rem;
}
.quote-container {
padding: 20px;
}
.quote-text {
font-size: 1.2rem;
}
.refresh-btn {
padding: 10px 20px;
font-size: 0.9rem;
}
}

View File

@@ -1,52 +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>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1 class="title">随机一言</h1>
<p class="subtitle">每一句话都是心灵的光芒</p>
</header>
<main class="main-content">
<div class="quote-container">
<div class="loading" id="loading">
<div class="loading-spinner"></div>
<p>正在获取一言...</p>
</div>
<div class="quote-display" id="quoteDisplay">
<div class="quote-text" id="quoteText">
点击下方按钮获取一言
</div>
<div class="quote-index" id="quoteIndex"></div>
</div>
<div class="error-message" id="errorMessage">
<div class="error-icon">⚠️</div>
<div class="error-text" id="errorText"></div>
</div>
</div>
<div class="controls">
<button class="refresh-btn" id="refreshBtn">
<span class="btn-icon">🔄</span>
<span class="btn-text">获取新一言</span>
</button>
</div>
</main>
<footer class="footer">
<p>愿每一句话都能温暖你的心</p>
</footer>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,222 +0,0 @@
// 随机一言 JavaScript 功能实现
class HitokotoApp {
constructor() {
// API接口列表
this.apiEndpoints = [
"https://60s.api.shumengya.top"
];
this.currentEndpointIndex = 0;
this.isLoading = false;
// DOM 元素
this.elements = {
loading: document.getElementById('loading'),
quoteDisplay: document.getElementById('quoteDisplay'),
quoteText: document.getElementById('quoteText'),
quoteIndex: document.getElementById('quoteIndex'),
errorMessage: document.getElementById('errorMessage'),
errorText: document.getElementById('errorText'),
refreshBtn: document.getElementById('refreshBtn')
};
this.init();
}
// 初始化应用
init() {
this.bindEvents();
this.hideAllStates();
this.showQuoteDisplay();
}
// 绑定事件
bindEvents() {
this.elements.refreshBtn.addEventListener('click', () => {
this.fetchHitokoto();
});
// 键盘快捷键支持
document.addEventListener('keydown', (e) => {
if (e.code === 'Space' && !this.isLoading) {
e.preventDefault();
this.fetchHitokoto();
}
});
}
// 隐藏所有状态
hideAllStates() {
this.elements.loading.classList.remove('show');
this.elements.quoteDisplay.classList.remove('hide');
this.elements.errorMessage.classList.remove('show');
}
// 显示加载状态
showLoading() {
this.hideAllStates();
this.elements.loading.classList.add('show');
this.elements.quoteDisplay.classList.add('hide');
this.elements.refreshBtn.disabled = true;
this.isLoading = true;
}
// 显示一言内容
showQuoteDisplay() {
this.hideAllStates();
this.elements.quoteDisplay.classList.remove('hide');
this.elements.refreshBtn.disabled = false;
this.isLoading = false;
}
// 显示错误信息
showError(message) {
this.hideAllStates();
this.elements.errorMessage.classList.add('show');
this.elements.errorText.textContent = message;
this.elements.refreshBtn.disabled = false;
this.isLoading = false;
}
// 获取一言数据
async fetchHitokoto() {
if (this.isLoading) return;
this.showLoading();
// 尝试所有API接口
for (let i = 0; i < this.apiEndpoints.length; i++) {
const endpointIndex = (this.currentEndpointIndex + i) % this.apiEndpoints.length;
const endpoint = this.apiEndpoints[endpointIndex];
try {
const result = await this.tryFetchFromEndpoint(endpoint);
if (result.success) {
this.currentEndpointIndex = endpointIndex;
this.displayHitokoto(result.data);
return;
}
} catch (error) {
console.warn(`接口 ${endpoint} 请求失败:`, error.message);
continue;
}
}
// 所有接口都失败
this.showError('所有接口都无法访问,请检查网络连接或稍后重试');
}
// 尝试从指定接口获取数据
async tryFetchFromEndpoint(endpoint) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
// 移除URL中的encoding=text参数确保返回JSON格式
const response = await fetch(`${endpoint}/v2/hitokoto`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// 验证返回数据格式
if (data.code === 200 && data.data && data.data.hitokoto) {
return {
success: true,
data: data.data
};
} else {
throw new Error('返回数据格式不正确');
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时');
}
throw error;
}
}
// 显示一言内容
displayHitokoto(data) {
// 更新一言文本
this.elements.quoteText.textContent = data.hitokoto;
// 更新序号信息
if (data.index) {
this.elements.quoteIndex.textContent = `${data.index}`;
} else {
this.elements.quoteIndex.textContent = '';
}
// 添加淡入动画效果
this.elements.quoteText.style.opacity = '0';
this.elements.quoteIndex.style.opacity = '0';
setTimeout(() => {
this.elements.quoteText.style.transition = 'opacity 0.5s ease';
this.elements.quoteIndex.style.transition = 'opacity 0.5s ease';
this.elements.quoteText.style.opacity = '1';
this.elements.quoteIndex.style.opacity = '1';
}, 100);
this.showQuoteDisplay();
// 控制台输出调试信息
console.log('一言获取成功:', {
content: data.hitokoto,
index: data.index,
endpoint: this.apiEndpoints[this.currentEndpointIndex]
});
}
// 获取随机接口(用于负载均衡)
getRandomEndpoint() {
const randomIndex = Math.floor(Math.random() * this.apiEndpoints.length);
return this.apiEndpoints[randomIndex];
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
const app = new HitokotoApp();
// 添加全局错误处理
window.addEventListener('error', (event) => {
console.error('页面发生错误:', event.error);
});
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的Promise拒绝:', event.reason);
});
// 页面可见性变化时的处理
document.addEventListener('visibilitychange', () => {
if (!document.hidden && !app.isLoading) {
// 页面重新可见时,可以选择刷新内容
console.log('页面重新可见');
}
});
console.log('随机一言应用初始化完成');
});
// 导出应用类(如果需要在其他地方使用)
if (typeof module !== 'undefined' && module.exports) {
module.exports = HitokotoApp;
}

View File

@@ -1,3 +0,0 @@
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,8 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"index": 2862,
"hitokoto": "你带上罪恶之冠,即使背负上所有罪恶和孤独,绝不让你受伤"
}
}

View File

@@ -1,16 +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>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="card">
<p id="joke">加载中...</p>
<button id="next">换一个</button>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@@ -1,107 +0,0 @@
/* background.css - 动态渐变背景 */
body {
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
:root {
--bg-yellow: #FFFDE7; /* 浅黄 */
--bg-blue: #E3F2FD; /* 淡蓝 */
}
body {
background: linear-gradient(180deg, var(--bg-yellow) 0%, var(--bg-blue) 100%);
background-attachment: fixed; /* 固定背景,滚动时不移动 */
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
overflow: hidden;
transition: background-color 0.5s ease;
}
/* Light Theme (Default) */
[data-theme="light"] {
background: linear-gradient(to bottom, #87CEEB, #B0E0E6);
}
/* Dark Theme */
[data-theme="dark"] {
background: linear-gradient(to bottom, #232526, #414345);
}
[data-theme="dark"] .snowflake {
color: #999;
}
/* Winter Theme */
[data-theme="winter"] {
background: linear-gradient(to bottom, #a1c4fd, #c2e9fb);
}
[data-theme="winter"] .background-bottom {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 100px;
background: linear-gradient(to top, white, rgba(255, 255, 255, 0));
z-index: -1;
border-radius: 50% 50% 0 0 / 20px;
box-shadow: 0 -10px 20px rgba(255, 255, 255, 0.5);
}
#snowflake-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
.snowflake {
position: absolute;
top: -10%;
color: white;
font-size: 20px;
user-select: none;
animation: fall linear infinite;
}
@keyframes fall {
to {
transform: translateY(105vh) rotate(360deg);
}
}
#frost-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('https://www.transparenttextures.com/patterns/ice-age.png') repeat;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease-in-out;
z-index: 100;
}
#frost-overlay.is-frosted {
opacity: 0.3;
}

View File

@@ -1,217 +0,0 @@
:root {
--primary-color-light: #4A90E2;
--text-color-light: #333;
--card-bg-light: rgba(255, 255, 255, 0.85);
--primary-color-dark: #5271C4;
--text-color-dark: #E0E0E0;
--card-bg-dark: rgba(40, 40, 40, 0.85);
--primary-color-winter: #6A82FB;
--text-color-winter: #2c3e50;
--card-bg-winter: rgba(255, 255, 255, 0.7);
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 20px;
box-sizing: border-box;
text-align: center;
position: relative;
z-index: 1;
}
.top-nav {
position: absolute;
top: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.3);
padding: 5px;
border-radius: 50px;
backdrop-filter: blur(5px);
}
.theme-switcher {
display: flex;
gap: 5px;
}
.theme-btn {
background: transparent;
border: 2px solid transparent;
border-radius: 50%;
width: 40px;
height: 40px;
font-size: 1.5em;
cursor: pointer;
transition: transform 0.2s, border-color 0.2s;
}
.theme-btn:hover {
transform: scale(1.1);
}
.theme-btn.active {
border-color: white;
}
.title {
font-family: 'ZCOOL KuaiLe', cursive;
font-size: 3em;
margin-bottom: 20px;
transition: color 0.5s ease;
}
.joke-stream {
width: 100%;
max-width: 500px;
display: flex;
flex-direction: column;
gap: 25px;
}
.joke-card {
border-radius: 20px;
padding: 30px 40px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
width: 100%;
max-width: 500px;
min-height: 150px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: background-color 0.5s ease, border-color 0.5s ease;
}
#joke-text {
font-size: 1.5em;
line-height: 1.6;
transition: opacity 0.3s, color 0.5s ease;
}
/* --- Theming --- */
/* Light Theme */
[data-theme="light"] .title { color: white; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); }
[data-theme="light"] .joke-card { background-color: var(--card-bg-light); }
[data-theme="light"] #joke-text { color: var(--text-color-light); }
[data-theme="light"] #new-joke-btn { background-color: var(--primary-color-light); box-shadow: 0 4px 15px rgba(74, 144, 226, 0.4); }
[data-theme="light"] footer { color: rgba(255, 255, 255, 0.8); }
/* Dark Theme */
[data-theme="dark"] .title { color: #EAEAEA; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); }
[data-theme="dark"] .joke-card { background-color: var(--card-bg-dark); border-color: rgba(255, 255, 255, 0.1); }
[data-theme="dark"] #joke-text { color: var(--text-color-dark); }
[data-theme="dark"] #new-joke-btn { background-color: var(--primary-color-dark); box-shadow: 0 4px 15px rgba(82, 113, 196, 0.4); }
[data-theme="dark"] footer { color: rgba(200, 200, 200, 0.7); }
/* Winter Theme */
[data-theme="winter"] .title { color: #1e3a5f; text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.7); }
[data-theme="winter"] .joke-card {
background-color: var(--card-bg-winter);
border-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1), inset 0 0 15px rgba(255, 255, 255, 0.5);
}
[data-theme="winter"] #joke-text { color: var(--text-color-winter); }
[data-theme="winter"] #new-joke-btn { background-color: var(--primary-color-winter); box-shadow: 0 4px 15px rgba(106, 130, 251, 0.4); }
[data-theme="winter"] footer { color: #1e3a5f; }
.controls {
margin-top: 30px;
}
#new-joke-btn {
color: white;
font-size: 1.2em;
font-weight: bold;
padding: 15px 35px;
border: none;
border-radius: 50px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.5s ease;
}
#new-joke-btn:hover {
transform: translateY(-3px);
}
#new-joke-btn:active {
transform: translateY(1px);
}
.interactions {
margin-top: 25px;
display: flex;
gap: 20px;
}
.interaction-btn {
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.9);
border-radius: 50%;
width: 50px;
height: 50px;
font-size: 1.5em;
cursor: pointer;
transition: transform 0.2s, background-color 0.2s;
}
.interaction-btn:hover {
transform: scale(1.1);
background: white;
}
footer {
position: absolute;
bottom: 10px;
font-size: 0.9em;
transition: color 0.5s ease;
}
/* Loader */
#loader {
position: absolute;
transition: color 0.5s ease;
}
[data-theme="light"] #loader { color: var(--primary-color-light); }
[data-theme="dark"] #loader { color: var(--primary-color-dark); }
[data-theme="winter"] #loader { color: var(--primary-color-winter); }
.snowflake-loader {
font-size: 40px;
display: inline-block;
animation: spin 1.5s linear infinite;
}
.snowflake-loader::before {
content: '❄';
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.hidden {
display: none;
}
/* Responsive */
@media (max-width: 600px) {
.title {
font-size: 2.5em;
}
.joke-card {
padding: 25px;
}
#joke-text {
font-size: 1.2em;
}
.top-nav {
top: 10px;
right: 10px;
}
}

View File

@@ -1,58 +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>
<link href="https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body data-theme="light">
<div id="snowflake-container"></div>
<div id="frost-overlay"></div>
<div class="background-bottom"></div>
<nav class="top-nav">
<div class="theme-switcher">
<button class="theme-btn" data-theme-target="light" title="清新风">☀️</button>
<button class="theme-btn" data-theme-target="dark" title="暗黑风">🌙</button>
<button class="theme-btn" data-theme-target="winter" title="冰雪风">❄️</button>
</div>
</nav>
<div class="container">
<header>
<h1 class="title">冷笑话生成器</h1>
</header>
<main class="joke-card">
<div id="loader" class="hidden">
<div class="snowflake-loader"></div>
<p>思考中...</p>
</div>
<p id="joke-text">点击下面的按钮,来点冷笑话吧!</p>
</main>
<div class="controls">
<button id="new-joke-btn">再来一个</button>
</div>
<div class="interactions">
<button class="interaction-btn" id="like-btn" title="好笑">👍</button>
<button class="interaction-btn" id="dislike-btn" title="不好笑">👎</button>
<button class="interaction-btn" id="collect-btn" title="收藏">⭐️</button>
<button class="interaction-btn" id="share-btn" title="分享">🔗</button>
</div>
</div>
<footer>
<p>&copy; 2024 冷笑话工坊</p>
</footer>
<audio id="wind-sound" src="https://www.soundjay.com/nature/sounds/wind-howl-01.mp3" preload="auto"></audio>
<audio id="snow-sound" src="https://www.soundjay.com/nature/sounds/walking-in-snow-01.mp3" preload="auto"></audio>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,117 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const jokeTextElem = document.getElementById('joke-text');
const newJokeBtn = document.getElementById('new-joke-btn');
const snowflakeContainer = document.getElementById('snowflake-container');
const frostOverlay = document.getElementById('frost-overlay');
const windSound = document.getElementById('wind-sound');
const snowSound = document.getElementById('snow-sound');
const loader = document.getElementById('loader');
const themeBtns = document.querySelectorAll('.theme-btn');
const apiEndpoints = [
'https://60s.api.shumengya.top/v2/dad-joke',
];
let currentApiIndex = 0;
async function fetchJoke() {
jokeTextElem.classList.add('hidden');
loader.classList.remove('hidden');
try {
const response = await fetch(apiEndpoints[currentApiIndex]);
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
if (data.code === 200 && data.data.content) {
updateJokeText(data.data.content);
if (document.body.dataset.theme === 'winter' && Math.random() < 0.3) {
triggerFrostEffect();
}
} else {
throw new Error('API returned invalid data');
}
} catch (error) {
console.error('Fetch error:', error);
currentApiIndex = (currentApiIndex + 1) % apiEndpoints.length;
if (currentApiIndex !== 0) {
fetchJoke();
} else {
jokeTextElem.textContent = '冰箱坏了,暂时没有冷笑话...';
}
} finally {
loader.classList.add('hidden');
jokeTextElem.classList.remove('hidden');
}
}
function updateJokeText(text) {
jokeTextElem.textContent = '';
let i = 0;
const typing = setInterval(() => {
if (i < text.length) {
jokeTextElem.textContent += text.charAt(i);
i++;
} else {
clearInterval(typing);
}
}, 50);
}
function createSnowflakes() {
const snowflakeCount = document.body.dataset.theme === 'dark' ? 50 : 30;
snowflakeContainer.innerHTML = '';
for (let i = 0; i < snowflakeCount; i++) {
const snowflake = document.createElement('div');
snowflake.className = 'snowflake';
snowflake.textContent = '❄️';
snowflake.style.left = `${Math.random() * 100}vw`;
snowflake.style.fontSize = `${Math.random() * 15 + 10}px`;
snowflake.style.opacity = Math.random() * 0.5 + 0.3;
const duration = Math.random() * 10 + 8;
const delay = Math.random() * 10;
snowflake.style.animation = `fall ${duration}s linear ${delay}s infinite`;
snowflakeContainer.appendChild(snowflake);
}
}
function triggerFrostEffect() {
frostOverlay.classList.add('is-frosted');
windSound.play().catch(e => console.error("Audio play failed:", e));
setTimeout(() => {
frostOverlay.classList.remove('is-frosted');
}, 2000);
}
function setTheme(theme) {
document.body.dataset.theme = theme;
localStorage.setItem('joke-theme', theme);
themeBtns.forEach(btn => {
btn.classList.toggle('active', btn.dataset.themeTarget === theme);
});
if (theme === 'winter') {
snowSound.play().catch(e => console.error("Audio play failed:", e));
}
// Recreate snowflakes for theme-specific density
createSnowflakes();
}
themeBtns.forEach(btn => {
btn.addEventListener('click', () => {
setTheme(btn.dataset.themeTarget);
});
});
newJokeBtn.addEventListener('click', fetchJoke);
// Initial setup
const savedTheme = localStorage.getItem('joke-theme') || 'light';
setTheme(savedTheme);
fetchJoke();
});

View File

@@ -1 +0,0 @@
["https://60s.api.shumengya.top"]

View File

@@ -1,8 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"index": 121,
"content": "这个世界上谁最懂猪?蜘蛛(知猪)人。"
}
}

View File

@@ -1,90 +0,0 @@
body {
background-color: #1a1a1a;
color: #e0e0e0;
font-family: 'Courier New', Courier, monospace;
overflow: hidden;
margin: 0;
padding: 0;
}
#bg-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -2;
overflow: hidden;
transition: transform 0.2s ease-out;
}
.floating-emoji {
position: absolute;
user-select: none;
opacity: 0;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@keyframes float-top-to-bottom {
0% { transform: translateY(-10vh) rotate(0deg); opacity: 0; }
10%, 90% { opacity: 0.7; }
100% { transform: translateY(110vh) rotate(360deg); opacity: 0; }
}
@keyframes float-bottom-to-top {
0% { transform: translateY(110vh) rotate(0deg); opacity: 0; }
10%, 90% { opacity: 0.7; }
100% { transform: translateY(-10vh) rotate(360deg); opacity: 0; }
}
@keyframes float-left-to-right {
0% { transform: translateX(-10vw) rotate(0deg); opacity: 0; }
10%, 90% { opacity: 0.7; }
100% { transform: translateX(110vw) rotate(360deg); opacity: 0; }
}
@keyframes float-right-to-left {
0% { transform: translateX(110vw) rotate(0deg); opacity: 0; }
10%, 90% { opacity: 0.7; }
100% { transform: translateX(-10vw) rotate(360deg); opacity: 0; }
}
.text-fragment {
position: absolute;
font-size: 24px;
color: rgba(255, 0, 255, 0.4);
opacity: 0;
animation: float-fragment 15s linear infinite, fade-in-out 15s linear infinite;
user-select: none;
}
@keyframes float-fragment {
0% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(20px, 40px) rotate(15deg); }
50% { transform: translate(-30px, -10px) rotate(-10deg); }
75% { transform: translate(10px, -30px) rotate(5deg); }
100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes fade-in-out {
0%, 100% { opacity: 0; }
10%, 90% { opacity: 0.4; }
}
.screen-crack {
position: absolute;
width: 200px;
height: 200px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M 100 100 L 0 0 M 100 100 L 200 0 M 100 100 L 50 200 M 100 100 L 150 200" stroke="rgba(255,255,255,0.5)" stroke-width="1" fill="none"/></svg>');
opacity: 0;
animation: flicker-crack 25s steps(1, end) infinite;
}
@keyframes flicker-crack {
0%, 100% { opacity: 0; }
50% { opacity: 0.3; }
51% { opacity: 0; }
75% { opacity: 0.2; }
76% { opacity: 0; }
}

View File

@@ -1,235 +0,0 @@
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
box-sizing: border-box;
position: relative;
z-index: 1;
}
.content-card {
background: rgba(20, 20, 20, 0.7);
border: none;
padding: 40px;
max-width: 600px;
width: 100%;
text-align: center;
backdrop-filter: blur(5px);
position: relative;
clip-path: polygon(2% 5%, 97% 0%, 100% 95%, 0% 100%);
}
.body-animated .content-card {
animation: tremble 0.4s infinite, glitch-shadow 1.5s steps(1, end) infinite;
}
@keyframes tremble {
0% { clip-path: polygon(2% 5%, 97% 0%, 100% 95%, 0% 100%); }
25% { clip-path: polygon(2% 5%, 98% 2%, 99% 100%, 1% 98%); }
50% { clip-path: polygon(3% 4%, 96% 1%, 100% 96%, 2% 100%); }
75% { clip-path: polygon(1% 6%, 97% 3%, 98% 95%, 0% 99%); }
100% { clip-path: polygon(2% 5%, 97% 0%, 100% 95%, 0% 100%); }
}
@keyframes glitch-shadow {
0% {
box-shadow:
0 0 8px rgba(255, 0, 255, 0.5),
inset 0 0 8px rgba(255, 0, 255, 0.4);
}
33% {
box-shadow:
0 0 8px rgba(0, 255, 255, 0.5),
inset 0 0 8px rgba(0, 255, 255, 0.4);
}
66% {
box-shadow:
0 0 8px rgba(0, 255, 0, 0.5),
inset 0 0 8px rgba(0, 255, 0, 0.4);
}
100% {
box-shadow:
0 0 8px rgba(255, 0, 255, 0.5),
inset 0 0 8px rgba(255, 0, 255, 0.4);
}
}
#literature-text {
font-size: 1.2em;
line-height: 1.8;
min-height: 100px;
color: #e0e0e0;
text-shadow: 0 0 5px rgba(0, 255, 135, 0.5);
animation: text-flicker 15s linear infinite;
}
.body-animated #literature-text {
animation: text-flicker 15s linear infinite, text-shadow-glitch 2s steps(1, end) infinite;
}
@keyframes text-flicker {
0%, 100% { opacity: 1; }
50.0% { opacity: 0.95; }
50.5% { opacity: 1; }
}
@keyframes text-shadow-glitch {
0% {
text-shadow:
1px 0 0 rgba(255,0,255,0.5),
-1px 0 0 rgba(0,255,255,0.5);
}
10% {
text-shadow:
-1px 0 0 rgba(255,0,255,0.5),
1px 0 0 rgba(0,255,255,0.5);
}
11%, 100% {
text-shadow: none;
}
}
.controls {
margin-top: 30px;
display: flex;
align-items: center;
gap: 20px;
}
#new-literature-btn {
background-color: #ff00ff;
color: #fff;
border: none;
padding: 12px 25px;
font-size: 1em;
cursor: pointer;
border-radius: 5px;
text-transform: uppercase;
font-weight: bold;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 0 10px #ff00ff, 0 0 20px #ff00ff;
}
#new-literature-btn:hover {
transform: scale(1.05);
box-shadow: 0 0 15px #ff00ff, 0 0 30px #ff00ff;
}
#new-literature-btn:active {
transform: scale(0.95);
}
/* Animation Toggle Switch */
.switch-container {
display: flex;
align-items: center;
gap: 10px;
color: #aaa;
}
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
}
input:checked + .slider {
background-color: #ff00ff;
}
input:checked + .slider:before {
transform: translateX(26px);
}
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
/* Glitch Overlay & Animations */
#glitch-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
}
.body-animated #glitch-overlay {
animation: color-shift 15s steps(1, end) infinite;
}
@keyframes color-shift {
0%, 100% { background: transparent; }
10% { background: rgba(255, 0, 0, 0.05); }
10.1% { background: transparent; }
20% { background: rgba(0, 255, 0, 0.05); }
20.1% { background: transparent; }
30% { background: rgba(0, 0, 255, 0.05); }
30.1% { background: transparent; }
}
.flicker-block {
position: absolute;
background: rgba(255, 255, 255, 0.1);
opacity: 0;
}
.body-animated .flicker-block {
animation: flicker 3s infinite;
}
@keyframes flicker {
0%, 100% { opacity: 0; }
50% { opacity: 1; }
}
/* Responsive Design */
@media (max-width: 768px) {
.content-card {
padding: 20px;
}
#literature-text {
font-size: 1em;
}
.controls {
flex-direction: column;
}
}

View File

@@ -1,34 +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>
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="bg-container">
</div>
<div id="glitch-overlay"></div>
<div class="container">
<div class="content-card">
<p id="literature-text">正在加载发病文学...</p>
</div>
<div class="controls">
<button id="new-literature-btn">再疯一次</button>
<div class="switch-container">
<label for="animation-toggle">关闭动画</label>
<label class="switch">
<input type="checkbox" id="animation-toggle" checked>
<span class="slider round"></span>
</label>
</div>
</div>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,147 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const literatureTextElem = document.getElementById('literature-text');
const newLiteratureBtn = document.getElementById('new-literature-btn');
const animationToggle = document.getElementById('animation-toggle');
const bgContainer = document.getElementById('bg-container');
const body = document.body;
const apiEndpoints = [
'https://60s.api.shumengya.top/v2/fabing',
// Add fallback APIs here if available
];
let currentApiIndex = 0;
async function fetchLiterature() {
literatureTextElem.textContent = '正在卖力发疯中...';
literatureTextElem.style.opacity = '0.5';
try {
const response = await fetch(apiEndpoints[currentApiIndex]);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
if (data.code === 200) {
literatureTextElem.textContent = data.data.saying;
} else {
throw new Error('API returned an error');
}
} catch (error) {
console.error('Fetch error:', error);
currentApiIndex = (currentApiIndex + 1) % apiEndpoints.length;
if (currentApiIndex !== 0) {
fetchLiterature(); // Retry with the next API
} else {
literatureTextElem.textContent = '疯不起来了,请稍后再试。';
}
} finally {
literatureTextElem.style.opacity = '1';
}
}
function createFloatingEmojis() {
const existingEmojis = bgContainer.querySelectorAll('.floating-emoji');
existingEmojis.forEach(e => e.remove());
const emojis = ['🤯', '😵', '🤪', '🥴', '🤡', '👹', '👻', '💀', '💥', '🔥', '🌪️', '😵‍💫'];
const animationNames = ['float-top-to-bottom', 'float-bottom-to-top', 'float-left-to-right', 'float-right-to-left'];
const emojiCount = 25;
for (let i = 0; i < emojiCount; i++) {
const emojiEl = document.createElement('div');
emojiEl.className = 'floating-emoji';
emojiEl.textContent = emojis[Math.floor(Math.random() * emojis.length)];
const animationName = animationNames[Math.floor(Math.random() * animationNames.length)];
emojiEl.style.animationName = animationName;
emojiEl.style.animationDuration = `${Math.random() * 10 + 15}s`; // 15-25 seconds
emojiEl.style.animationDelay = `${Math.random() * 20}s`;
emojiEl.style.fontSize = `${Math.random() * 20 + 20}px`;
// Set initial position based on animation direction
if (animationName.includes('top') || animationName.includes('bottom')) { // Vertical movement
emojiEl.style.left = `${Math.random() * 100}vw`;
} else { // Horizontal movement
emojiEl.style.top = `${Math.random() * 100}vh`;
}
bgContainer.appendChild(emojiEl);
}
}
function createFragments() {
const existingFragments = bgContainer.querySelectorAll('.text-fragment');
existingFragments.forEach(f => f.remove());
const fragments = ['我', '疯', '了', '', '', '…', '救命', '为什么', '好烦', '啊啊啊'];
for (let i = 0; i < 20; i++) {
const frag = document.createElement('div');
frag.className = 'text-fragment';
frag.textContent = fragments[Math.floor(Math.random() * fragments.length)];
frag.style.top = `${Math.random() * 100}%`;
frag.style.left = `${Math.random() * 100}%`;
frag.style.animationDelay = `${Math.random() * 15}s`;
frag.style.fontSize = `${Math.random() * 12 + 12}px`;
bgContainer.appendChild(frag);
}
}
function createCracks() {
for (let i = 0; i < 2; i++) {
const crack = document.createElement('div');
crack.className = 'screen-crack';
crack.style.top = `${Math.random() * 80}%`;
crack.style.left = `${Math.random() * 80}%`;
crack.style.transform = `rotate(${Math.random() * 360}deg)`;
crack.style.animationDelay = `${Math.random() * 25}s`;
bgContainer.appendChild(crack);
}
}
function createFlickerBlocks() {
const glitchOverlay = document.getElementById('glitch-overlay');
for (let i = 0; i < 3; i++) {
const block = document.createElement('div');
block.className = 'flicker-block';
block.style.width = `${Math.random() * 100 + 50}px`;
block.style.height = `${Math.random() * 100 + 50}px`;
block.style.top = `${Math.random() * 90}%`;
block.style.left = `${Math.random() * 90}%`;
block.style.animationDuration = `${Math.random() * 2 + 2}s`;
block.style.animationDelay = `${Math.random() * 3}s`;
glitchOverlay.appendChild(block);
}
}
function toggleAnimations() {
if (animationToggle.checked) {
body.classList.add('body-animated');
} else {
body.classList.remove('body-animated');
}
}
document.addEventListener('mousemove', (e) => {
if (!animationToggle.checked) return;
const x = (window.innerWidth / 2) - e.pageX;
const y = (window.innerHeight / 2) - e.pageY;
bgContainer.style.transform = `translateX(${x / 50}px) translateY(${y / 50}px)`;
});
newLiteratureBtn.addEventListener('click', fetchLiterature);
animationToggle.addEventListener('change', toggleAnimations);
// Initial setup
createFloatingEmojis();
createFragments();
createCracks();
createFlickerBlocks();
toggleAnimations();
fetchLiterature();
window.addEventListener('resize', () => {
createFloatingEmojis();
createFragments();
});
});

View File

@@ -1 +0,0 @@
["https://60s.api.shumengya.top"]

View File

@@ -1,8 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"index": 347,
"duanzi": "我不想读书,主要是因为家里牛啊,猪啊羊啊都没人喂。"
}
}

View File

@@ -1,251 +0,0 @@
/* 随机唱歌音频 - 淡绿色清新风格样式 */
/* 重置样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 50%, #ffd3a5 100%);
min-height: 100vh;
color: #2d5016;
line-height: 1.6;
overflow-x: hidden;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
/* 头部 */
.header {
text-align: center;
margin-bottom: 20px;
background: rgba(255, 255, 255, 0.85);
border-radius: 20px;
padding: 24px;
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
backdrop-filter: blur(10px);
}
.header h1 {
font-size: 2rem;
color: #2d5016;
margin-bottom: 10px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
.header p {
color: #5a7c65;
font-size: 1rem;
}
/* 用户卡片 */
.user-card {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
background: rgba(255, 255, 255, 0.9);
padding: 16px;
border-radius: 15px;
box-shadow: 0 4px 18px rgba(45, 80, 22, 0.08);
margin-bottom: 15px;
text-align: center;
}
.avatar {
width: 56px;
height: 56px;
border-radius: 50%;
object-fit: cover;
border: 3px solid rgba(129, 199, 132, 0.5);
}
.user-info {
display: flex;
flex-direction: column;
}
.nickname {
font-weight: 700;
font-size: 1.1rem;
color: #2d5016;
}
.meta {
color: #5a7c65;
font-size: 0.9rem;
}
/* 歌曲信息 */
.song-card {
background: rgba(255, 255, 255, 0.9);
padding: 16px;
border-radius: 15px;
box-shadow: 0 4px 18px rgba(45, 80, 22, 0.08);
margin-bottom: 15px;
text-align: center;
}
.song-title {
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 8px;
color: #1b5e20;
}
.song-meta {
color: #5a7c65;
font-size: 0.95rem;
margin-bottom: 10px;
}
/* 歌词 */
.lyrics {
background: rgba(129, 199, 132, 0.1);
border-radius: 12px;
padding: 12px;
max-height: 220px;
overflow: auto;
}
.lyrics p {
margin-bottom: 6px;
}
/* 音频播放器卡片 */
.audio-card {
background: rgba(255, 255, 255, 0.9);
padding: 16px;
border-radius: 15px;
box-shadow: 0 4px 18px rgba(45, 80, 22, 0.08);
margin-bottom: 15px;
}
.audio-actions {
display: flex;
flex-direction: column;
gap: 12px;
align-items: center;
margin-top: 10px;
}
.btn {
background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%);
color: white;
border: none;
padding: 10px 18px;
border-radius: 10px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: all 0.25s ease;
box-shadow: 0 4px 12px rgba(129, 199, 132, 0.35);
text-decoration: none;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 18px rgba(129, 199, 132, 0.45);
}
.info {
color: #5a7c65;
font-size: 0.9rem;
}
/* 加载与错误 */
.loading, .error {
text-align: center;
padding: 30px;
background: rgba(255, 255, 255, 0.85);
border-radius: 15px;
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
}
.spinner {
width: 36px;
height: 36px;
border: 4px solid #e8f5e8;
border-top: 4px solid #81c784;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 18px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 动画 */
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 平板端适配 */
@media (max-width: 1024px) and (min-width: 768px) {
.container { padding: 16px; }
.header h1 { font-size: 1.8rem; }
}
/* 手机端优先优化 */
@media (max-width: 767px) {
.container { padding: 12px; }
.header { padding: 18px; }
.header h1 { font-size: 1.6rem; gap: 8px; }
.user-card {
padding: 16px;
flex-direction: column;
text-align: center;
}
.avatar {
width: 80px;
height: 80px;
margin-bottom: 8px;
}
.song-card, .audio-card {
padding: 16px;
}
.lyrics {
max-height: 180px;
text-align: left;
padding: 16px;
}
.audio-actions {
flex-direction: column;
gap: 15px;
align-items: center;
}
.info {
text-align: center;
line-height: 1.8;
}
.btn {
width: 100%;
max-width: 200px;
text-align: center;
}
}

View File

@@ -1,67 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>随机唱歌音频 - 60s API 集合</title>
<meta name="description" content="随机唱歌音频,数据源自 60s.viki.moe提供用户信息、歌曲信息、歌词与音频播放。" />
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<div class="container">
<header class="header">
<h1>
🎵 随机唱歌音频
</h1>
<p>数据来自官方/权威源头,以确保稳定与实时 · 支持本地数据回退</p>
</header>
<!-- 加载与错误状态 -->
<section id="loading" class="loading">
<div class="spinner"></div>
<p>正在加载中,请稍候…</p>
</section>
<section id="error" class="error" style="display: none;">
<p>获取数据失败,请稍后重试</p>
</section>
<!-- 内容区域 -->
<main id="content" style="display: none;" class="fade-in">
<!-- 用户信息 -->
<div class="user-card">
<img id="avatar" class="avatar" src="" alt="用户头像" />
<div class="user-info">
<div class="nickname" id="nickname">-</div>
<div class="meta">性别:<span id="gender">-</span></div>
</div>
</div>
<!-- 歌曲信息 -->
<div class="song-card">
<div class="song-title" id="song-title">-</div>
<div class="song-meta" id="song-meta">-</div>
<div class="lyrics" id="lyrics"></div>
</div>
<!-- 音频播放 -->
<div class="audio-card">
<audio id="audio" controls style="width: 100%;"></audio>
<div class="audio-actions">
<button class="btn" id="refresh-btn">换一首</button>
<div class="info">
❤ 喜欢:<span id="like-count">-</span>
· ⏱ 时长:<span id="duration">--:--</span>
· 🗓 发布:<span id="publish-time">-</span>
· 🔗 <a id="link" href="#" class="btn" style="padding: 6px 10px; border-radius: 8px; background: #81c784;">查看原帖</a>
</div>
</div>
</div>
</main>
</div>
<script src="./js/script.js"></script>
</body>
</html>

View File

@@ -1,252 +0,0 @@
// 随机唱歌音频 页面脚本
(function () {
'use strict';
const API = {
endpoints: [],
currentIndex: 0,
params: {
encoding: 'json'
},
localFallback: '返回接口.json',
// 初始化API接口列表
async init() {
try {
const res = await fetch('./接口集合.json');
const endpoints = await res.json();
this.endpoints = endpoints.map(endpoint => `${endpoint}/v2/changya`);
} catch (e) {
// 如果无法加载接口集合,使用默认接口
this.endpoints = ['https://60s.api.shumengya.top/v2/changya'];
}
},
// 获取当前接口URL
getCurrentUrl() {
if (this.endpoints.length === 0) return null;
const url = new URL(this.endpoints[this.currentIndex]);
Object.entries(this.params).forEach(([k, v]) => url.searchParams.append(k, v));
return url.toString();
},
// 切换到下一个接口
switchToNext() {
this.currentIndex = (this.currentIndex + 1) % this.endpoints.length;
return this.currentIndex < this.endpoints.length;
},
// 重置到第一个接口
reset() {
this.currentIndex = 0;
}
};
// DOM 元素引用
const els = {
loading: null,
error: null,
container: null,
avatar: null,
nickname: null,
gender: null,
songTitle: null,
songMeta: null,
lyrics: null,
audio: null,
likeCount: null,
publishTime: null,
link: null,
refreshBtn: null,
};
function initDom() {
els.loading = document.getElementById('loading');
els.error = document.getElementById('error');
els.container = document.getElementById('content');
els.avatar = document.getElementById('avatar');
els.nickname = document.getElementById('nickname');
els.gender = document.getElementById('gender');
els.songTitle = document.getElementById('song-title');
els.songMeta = document.getElementById('song-meta');
els.lyrics = document.getElementById('lyrics');
els.audio = document.getElementById('audio');
els.likeCount = document.getElementById('like-count');
els.publishTime = document.getElementById('publish-time');
els.link = document.getElementById('link');
els.refreshBtn = document.getElementById('refresh-btn');
}
function showLoading() {
els.loading.style.display = 'block';
els.error.style.display = 'none';
els.container.style.display = 'none';
}
function showError(msg) {
els.loading.style.display = 'none';
els.error.style.display = 'block';
els.container.style.display = 'none';
els.error.querySelector('p').textContent = msg || '获取数据失败,请稍后重试';
}
function showContent() {
els.loading.style.display = 'none';
els.error.style.display = 'none';
els.container.style.display = 'block';
}
function formatDuration(ms) {
if (!ms && ms !== 0) return '';
const totalSeconds = Math.floor(ms / 1000);
const m = Math.floor(totalSeconds / 60).toString().padStart(2, '0');
const s = (totalSeconds % 60).toString().padStart(2, '0');
return `${m}:${s}`;
}
function safeText(text) {
const div = document.createElement('div');
div.textContent = text == null ? '' : String(text);
return div.innerHTML;
}
async function fetchFromAPI() {
// 初始化API接口列表
await API.init();
// 重置API索引到第一个接口
API.reset();
// 尝试所有API接口
for (let i = 0; i < API.endpoints.length; i++) {
try {
const url = API.getCurrentUrl();
console.log(`尝试接口 ${i + 1}/${API.endpoints.length}: ${url}`);
const resp = await fetch(url, {
cache: 'no-store',
timeout: 10000 // 10秒超时
});
if (!resp.ok) {
throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
}
const data = await resp.json();
if (data && data.code === 200) {
console.log(`接口 ${i + 1} 请求成功`);
return data;
}
throw new Error(data && data.message ? data.message : '接口返回异常');
} catch (e) {
console.warn(`接口 ${i + 1} 失败:`, e.message);
// 如果不是最后一个接口,切换到下一个
if (i < API.endpoints.length - 1) {
API.switchToNext();
continue;
}
// 所有接口都失败了
console.warn('所有远程接口都失败,尝试本地数据');
return null;
}
}
}
async function fetchFromLocal() {
try {
const resp = await fetch(API.localFallback + `?t=${Date.now()}`);
if (!resp.ok) throw new Error(`本地文件HTTP ${resp.status}`);
const data = await resp.json();
return data;
} catch (e) {
console.error('读取本地返回接口.json失败:', e);
return null;
}
}
function render(data) {
const d = data?.data || {};
const user = d.user || {};
const song = d.song || {};
const audio = d.audio || {};
// 用户信息
els.avatar.src = user.avatar_url || '';
els.avatar.alt = (user.nickname || '用户') + ' 头像';
els.nickname.textContent = user.nickname || '未知用户';
els.gender.textContent = user.gender === 'female' ? '女' : user.gender === 'male' ? '男' : '未知';
// 歌曲信息
els.songTitle.textContent = song.name || '未知歌曲';
els.songMeta.textContent = song.singer ? `演唱:${song.singer}` : '';
els.lyrics.innerHTML = '';
if (Array.isArray(song.lyrics)) {
const frag = document.createDocumentFragment();
song.lyrics.forEach(line => {
const p = document.createElement('p');
p.innerHTML = safeText(line);
frag.appendChild(p);
});
els.lyrics.appendChild(frag);
}
// 音频
els.audio.src = audio.url || '';
els.audio.preload = 'none';
// 其他信息
els.likeCount.textContent = typeof audio.like_count === 'number' ? audio.like_count : '-';
const publish = audio.publish || (audio.publish_at ? new Date(audio.publish_at).toLocaleString() : '');
els.publishTime.textContent = publish;
els.link.href = audio.link || '#';
els.link.target = '_blank';
// 时长信息
const durationEl = document.getElementById('duration');
durationEl.textContent = formatDuration(audio.duration);
showContent();
}
async function load() {
showLoading();
try {
// 先尝试远程API
const data = await fetchFromAPI();
if (data) {
render(data);
return;
}
// 远程API失败尝试本地数据
const localData = await fetchFromLocal();
if (localData) {
render(localData);
return;
}
// 都失败了
showError('获取数据失败,请稍后重试');
} catch (e) {
console.error('加载数据时发生错误:', e);
showError('获取数据失败,请稍后重试');
}
}
function bindEvents() {
if (els.refreshBtn) {
els.refreshBtn.addEventListener('click', load);
}
// 快捷键 Ctrl+R 刷新(不拦截浏览器默认刷新)
}
document.addEventListener('DOMContentLoaded', () => {
initDom();
bindEvents();
load();
});
})();

View File

@@ -1,3 +0,0 @@
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,32 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"user": {
"nickname": "𝑮𝑺_迷鹿_",
"gender": "female",
"avatar_url": "http://img-cdn.api.singduck.cn/user-img/6afbebcfae6144478c150d0c1d0d5899.jpg"
},
"song": {
"name": "恶作剧",
"singer": "王蓝茵",
"lyrics": [
"我想我会开始想念你",
"可是我刚刚才遇见了你",
"我怀疑这奇遇只是个恶作剧",
"我想我已慢慢喜欢你",
"因为我拥有爱情的勇气",
"我任性投入你给的恶作剧",
"你给的恶作剧"
]
},
"audio": {
"url": "http://audio-cdn.api.singduck.cn/ugc/220929_965696173_b822a290c553.wav?auth_key=1755845643-0-0-4029539b73e17337dcac49cc4e0ecfcc",
"duration": 35050,
"like_count": 955,
"link": "https://m.api.singduck.cn/user-piece/toGZlBfZbukck2sHb",
"publish": "2022/09/29 18:33:51",
"publish_at": 1664447631000
}
}
}

View File

@@ -1,36 +0,0 @@
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
min-height: 100vh;
overflow-x: hidden;
transition: background 0.5s ease;
}
/* Hand-drawn Comic Theme Background - NEW VIBRANT VERSION */
body.theme-comic {
background: linear-gradient(-45deg, #ff7e5f, #feb47b, #ffcc80, #ffecb3);
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
}
@keyframes gradientBG {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* Placeholder for Emoji Theme Background */
body.theme-emoji {
background-color: #fffde7;
}
/* Placeholder for Retro TV Theme Background */
body.theme-retro {
background-color: #3d2b1f;
}

View File

@@ -1,199 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Zhi+Mang+Xing&display=swap');
/* --- General & Theme Switcher --- */
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
text-align: center;
}
.theme-switcher {
position: fixed;
top: 15px;
right: 15px;
display: flex;
gap: 5px;
background: rgba(255, 255, 255, 0.8);
padding: 5px;
border-radius: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 100;
}
.theme-icon {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.2s ease;
border: 2px solid transparent;
}
.theme-icon.active {
border-color: #ff7043;
transform: scale(1.1);
}
/* --- Comic Theme Styles --- */
.theme-comic header h1 {
font-family: 'Zhi Mang Xing', cursive;
font-size: 4em;
color: #d84315; /* Deep Orange */
text-shadow: 2px 2px 0 #fff;
margin: 0.2em 0;
}
.theme-comic .divider {
height: 3px;
background: linear-gradient(90deg, #ffca28, #ff7043, #29b6f6, #66bb6a);
border-radius: 3px;
margin: 20px auto;
width: 80%;
}
.theme-comic .joke-card {
background: rgba(255, 255, 255, 0.85); /* White with transparency */
backdrop-filter: blur(5px);
border-radius: 15px;
padding: 40px;
min-height: 200px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
display: flex;
justify-content: center;
align-items: center;
position: relative;
margin-bottom: 20px;
transform: rotate(-1deg);
transition: transform 0.2s ease;
}
.theme-comic .joke-card:hover {
transform: rotate(1deg) scale(1.02);
}
.theme-comic .joke-text {
font-family: 'Zhi Mang Xing', cursive;
font-size: 2em;
line-height: 1.6;
color: #5d4037;
}
.theme-comic .new-joke-btn {
background: #1e88e5; /* Vibrant Blue */
color: white;
font-family: 'Zhi Mang Xing', cursive;
font-size: 2.5em;
border: none;
border-radius: 50px;
padding: 10px 30px;
cursor: pointer;
box-shadow: 0 5px 0 #1565c0; /* Darker Blue */
transition: all 0.1s ease-in-out;
}
.theme-comic .new-joke-btn:active {
transform: translateY(5px);
box-shadow: none;
}
/* --- Loading Animation --- */
.loading-container { display: none; }
.loading-container.visible { display: block; }
.loading-anim {
height: 60px;
width: 80px;
margin: 0 auto 10px;
}
.book {
transform-style: preserve-3d;
transform: rotateY(-30deg);
animation: flip 3s infinite;
}
.book, .book-page {
width: 40px;
height: 55px;
position: absolute;
left: 50%;
top: 50%;
margin-left: -20px;
margin-top: -27.5px;
}
.book-page {
background: #ffca28;
border: 1px solid #ff7043;
border-radius: 3px;
transform-origin: left;
}
.book-page:nth-child(1) { animation: flip-page 3s infinite; }
.book-page:nth-child(2) { animation: flip-page 3s -1s infinite; }
.book-page:nth-child(3) { animation: flip-page 3s -2s infinite; }
@keyframes flip { 50% { transform: rotateY(30deg); } }
@keyframes flip-page { 30%, 100% { transform: rotateY(180deg); } }
/* --- Feedback Buttons & Animations --- */
.feedback-buttons {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 30px;
}
.feedback-btn {
background: none;
border: none;
font-size: 2em;
cursor: pointer;
transition: transform 0.2s ease;
}
.feedback-btn:hover { transform: scale(1.2); }
#animation-container {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none; z-index: 999;
}
.confetti, .snowflake {
position: absolute;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.confetti {
width: 10px; height: 10px;
animation-name: fall;
}
.snowflake {
font-size: 20px; color: #fff;
animation-name: fall;
}
@keyframes fall {
from { transform: translateY(-10vh) rotate(0deg); }
to { transform: translateY(110vh) rotate(360deg); }
}
.joke-card.absurd {
animation: absurd-flash 0.5s 2;
}
@keyframes absurd-flash {
0%, 100% { border: 2px solid transparent; }
50% { border: 5px solid red; }
}
/* --- General Joke Text Visibility --- */
.joke-text {
opacity: 0;
transform: scale(0.9);
transition: opacity 0.4s ease, transform 0.4s ease;
}
.joke-text.visible {
opacity: 1;
transform: scale(1);
}
/* --- Responsive --- */
@media (max-width: 600px) {
.theme-comic header h1 { font-size: 3em; }
.theme-comic .joke-card { padding: 25px; transform: rotate(0); }
.theme-comic .joke-card:hover { transform: rotate(0); }
.theme-comic .joke-text { font-size: 1.5em; }
}

View File

@@ -1,59 +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>
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body class="theme-comic"> <!-- Default Theme -->
<div class="theme-switcher">
<div class="theme-icon" data-theme="theme-comic" title="手绘漫画">✏️</div>
<div class="theme-icon" data-theme="theme-emoji" title="表情包狂欢">😂</div>
<div class="theme-icon" data-theme="theme-retro" title="复古电视">📺</div>
</div>
<div class="container">
<header>
<h1>段子游乐场</h1>
<div class="divider"></div>
</header>
<main>
<div id="joke-card" class="joke-card">
<div class="loading-container">
<div class="loading-anim">
<div class="book">
<div class="book-page"></div>
<div class="book-page"></div>
<div class="book-page"></div>
</div>
</div>
<p>段子菌正在翻笑话库...</p>
</div>
<p id="joke-text" class="joke-text"></p>
</div>
<div class="feedback-buttons">
<button id="btn-lol" class="feedback-btn" title="笑到拍桌">🤣</button>
<button id="btn-cold" class="feedback-btn" title="有点冷">🥶</button>
<button id="btn-seen" class="feedback-btn" title="似曾相识">🤔</button>
<button id="btn-absurd" class="feedback-btn" title="离谱但好笑">🤯</button>
</div>
<button id="new-joke-btn" class="new-joke-btn">
<span class="btn-text">再来一个!</span>
</button>
</main>
</div>
<!-- Animation & Sound Containers -->
<div id="animation-container"></div>
<audio id="sound-lol" src="https://www.myinstants.com/media/sounds/yay-6326.mp3" preload="auto"></audio>
<audio id="sound-cold" src="https://www.myinstants.com/media/sounds/zapsplat_cartoon_whoosh_fast_swoosh_001_76761.mp3" preload="auto"></audio>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,122 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
// Elements
const body = document.body;
const jokeTextElem = document.getElementById('joke-text');
const newJokeBtn = document.getElementById('new-joke-btn');
const loadingContainer = document.querySelector('.loading-container');
const animationContainer = document.getElementById('animation-container');
const jokeCard = document.getElementById('joke-card');
// API
const apiBaseUrls = ["https://60s.api.shumengya.top"];
const apiPath = "/v2/duanzi";
let currentApiIndex = 0;
// --- Core Functions ---
const showLoading = (isLoading) => {
loadingContainer.classList.toggle('visible', isLoading);
if (isLoading) jokeTextElem.classList.remove('visible');
};
const displayJoke = (joke) => {
jokeTextElem.textContent = joke;
showLoading(false);
setTimeout(() => jokeTextElem.classList.add('visible'), 50);
};
const fetchJoke = async () => {
showLoading(true);
try {
const url = apiBaseUrls[currentApiIndex] + apiPath;
const response = await fetch(url, { timeout: 5000 });
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
if (data.code === 200 && data.data && data.data.duanzi) {
displayJoke(data.data.duanzi);
} else {
throw new Error('Invalid data format');
}
} catch (error) {
console.error(`API error with ${apiBaseUrls[currentApiIndex]}:`, error);
currentApiIndex = (currentApiIndex + 1) % apiBaseUrls.length;
if (currentApiIndex !== 0) {
fetchJoke(); // Try next API
} else {
displayJoke('段子菌迷路了!点击‘再来一个’让它重新找路~');
}
}
};
// --- Theme Switcher ---
const themeSwitcher = document.querySelector('.theme-switcher');
themeSwitcher.addEventListener('click', (e) => {
if (e.target.classList.contains('theme-icon')) {
const theme = e.target.dataset.theme;
body.className = theme; // Set body class to the selected theme
// Update active icon
themeSwitcher.querySelectorAll('.theme-icon').forEach(icon => icon.classList.remove('active'));
e.target.classList.add('active');
alert(`主题已切换!部分主题(如表情包、复古电视)将在后续阶段实现。`);
}
});
// Set initial active theme icon
themeSwitcher.querySelector(`[data-theme="${body.className}"]`).classList.add('active');
// --- Feedback Buttons & Animations ---
const btnLol = document.getElementById('btn-lol');
const btnCold = document.getElementById('btn-cold');
const btnSeen = document.getElementById('btn-seen');
const btnAbsurd = document.getElementById('btn-absurd');
const soundLol = document.getElementById('sound-lol');
const soundCold = document.getElementById('sound-cold');
btnLol.addEventListener('click', () => {
soundLol.play();
createParticles(20, 'confetti');
});
btnCold.addEventListener('click', () => {
soundCold.play();
createParticles(15, 'snowflake');
});
btnSeen.addEventListener('click', () => {
displayJoke("原来你也听过!那再给你换个新鲜的~");
setTimeout(fetchJoke, 1500);
});
btnAbsurd.addEventListener('click', () => {
jokeCard.classList.add('absurd');
setTimeout(() => jokeCard.classList.remove('absurd'), 1000);
});
function createParticles(count, type) {
animationContainer.innerHTML = ''; // Clear previous
const colors = ['#ffca28', '#ff7043', '#29b6f6', '#66bb6a'];
for (let i = 0; i < count; i++) {
const particle = document.createElement('div');
particle.classList.add(type);
if (type === 'confetti') {
particle.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
} else {
particle.textContent = '❄️';
}
particle.style.left = `${Math.random() * 100}vw`;
const duration = Math.random() * 3 + 2; // 2-5 seconds
const delay = Math.random() * -duration; // Start at different times
particle.style.animationDuration = `${duration}s`;
particle.style.animationDelay = `${delay}s`;
animationContainer.appendChild(particle);
}
}
// --- Event Listeners ---
newJokeBtn.addEventListener('click', fetchJoke);
// --- Initial Load ---
fetchJoke();
});

View File

@@ -1 +0,0 @@
["https://60s.api.shumengya.top"]

View File

@@ -1,8 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"index": 347,
"duanzi": "我不想读书,主要是因为家里牛啊,猪啊羊啊都没人喂。"
}
}

View File

@@ -1,26 +0,0 @@
body {
background: linear-gradient(-45deg, #0a021a, #2a0d3f, #4a1a6c, #7b2f8f);
background-size: 400% 400%;
animation: gradientBG 20s ease infinite;
color: #ffffff;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow-x: hidden;
}
@keyframes gradientBG {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}

View File

@@ -1,342 +0,0 @@
.container {
text-align: center;
padding: 20px;
max-width: 600px;
width: 100%;
box-sizing: border-box;
}
header h1 {
font-size: 2.8em;
color: #f0e6ff;
text-shadow: 0 0 10px #d1a9ff, 0 0 20px #d1a9ff;
margin-bottom: 0.2em;
}
header p {
font-size: 1.2em;
color: #e0c8ff;
margin-bottom: 40px;
}
.crystal-ball-container {
perspective: 1000px;
margin-bottom: 40px;
}
.crystal-ball {
width: 200px;
height: 200px;
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.6), rgba(200, 180, 255, 0.1));
border-radius: 50%;
margin: 0 auto;
position: relative;
box-shadow: 0 0 30px #c390ff, 0 0 60px #a060e0, inset 0 0 20px rgba(255, 220, 255, 0.3);
animation: float 6s ease-in-out infinite;
transform-style: preserve-3d;
}
.reflection {
width: 80px;
height: 40px;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
position: absolute;
top: 20px;
left: 40px;
transform: rotate(-30deg);
filter: blur(5px);
}
.swirl {
position: absolute;
top: 50%;
left: 50%;
width: 120%;
height: 120%;
background: linear-gradient(45deg, rgba(255, 192, 203, 0.1), rgba(128, 0, 128, 0.2));
border-radius: 50%;
animation: swirl 10s linear infinite;
transform: translate(-50%, -50%);
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
@keyframes swirl {
from { transform: translate(-50%, -50%) rotate(0deg); }
to { transform: translate(-50%, -50%) rotate(360deg); }
}
.fortune-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
padding: 30px;
margin-bottom: 30px;
min-height: 120px;
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
transition: opacity 0.5s ease-in-out;
}
.fortune-content {
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.fortune-content.visible {
opacity: 1;
}
#luck-desc {
font-size: 2em;
color: #ffc0cb;
margin: 0 0 10px;
}
#luck-tip {
font-size: 1.1em;
color: #e0e0e0;
margin: 0;
padding-bottom: 20px; /* Add some space before the new details */
}
.fortune-details {
display: flex;
justify-content: space-around;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
.detail-item {
text-align: center;
}
.detail-item h3 {
font-size: 0.9em;
color: #ffc0cb;
margin: 0 0 5px;
font-weight: normal;
}
.detail-item p {
font-size: 1.2em;
margin: 0;
font-weight: bold;
}
#lucky-color {
display: inline-block;
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid white;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
/* Remove the text content */
font-size: 0;
}
/* Tarot Card Styles */
.tarot-container {
margin-top: 40px;
margin-bottom: 40px;
}
.tarot-container h2 {
font-size: 1.5em;
color: #f0e6ff;
text-shadow: 0 0 8px #d1a9ff;
margin-bottom: 20px;
}
.tarot-card-container {
width: 180px;
height: 280px;
perspective: 1000px;
margin: 0 auto;
cursor: pointer;
}
.tarot-card-inner {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.8s;
transform-style: preserve-3d;
}
.tarot-card-container.flipped .tarot-card-inner {
transform: rotateY(180deg);
}
.tarot-card-front,
.tarot-card-back {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}
.tarot-card-back {
background: linear-gradient(135deg, #4a1a6c, #2a0d3f);
border: 2px solid #d1a9ff;
display: flex;
justify-content: center;
align-items: center;
font-size: 3em;
color: #d1a9ff;
}
.tarot-card-back::after {
content: '✧'; /* A simple star symbol */
text-shadow: 0 0 10px #f0e6ff;
}
.tarot-card-front {
background: linear-gradient(135deg, #3e165b, #592883);
border: 2px solid #d1a9ff;
color: white;
transform: rotateY(180deg);
padding: 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#tarot-name {
font-size: 1.4em;
color: #ffc0cb;
margin: 0 0 10px;
}
#tarot-interpretation {
font-size: 0.9em;
text-align: center;
margin: 0;
}
/* Side Decorations */
.side-decor {
position: fixed;
top: 0;
bottom: 0;
width: 15vw;
height: 100vh;
pointer-events: none;
z-index: 0;
}
.left-decor {
left: 0;
}
.right-decor {
right: 0;
}
.decor-symbol {
position: absolute;
color: rgba(209, 169, 255, 0.5);
text-shadow: 0 0 10px rgba(240, 230, 255, 0.7);
animation: floatSymbol 20s infinite ease-in-out;
}
@keyframes floatSymbol {
0%, 100% {
transform: translateY(0) rotate(0deg);
opacity: 0;
}
25%, 75% {
opacity: 0.8;
}
50% {
transform: translateY(-20vh) rotate(180deg);
opacity: 0.3;
}
}
#get-fortune-btn {
background: linear-gradient(45deg, #da70d6, #8a2be2);
color: white;
border: none;
border-radius: 50px;
padding: 15px 30px;
font-size: 1.1em;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 0 15px #c390ff;
}
#get-fortune-btn:hover {
transform: scale(1.05);
box-shadow: 0 0 25px #d1a9ff;
}
#get-fortune-btn:active {
transform: scale(0.98);
}
.loading-spinner {
border: 4px solid rgba(255, 255, 255, 0.2);
border-left-color: #ffc0cb;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
display: none; /* Hidden by default */
}
.loading-spinner.visible {
display: block;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
footer {
margin-top: 40px;
color: rgba(255, 255, 255, 0.6);
}
/* Responsive Design */
@media (max-width: 768px) {
header h1 {
font-size: 2.2em;
}
.crystal-ball {
width: 150px;
height: 150px;
}
.fortune-card {
padding: 20px;
}
.fortune-details {
flex-direction: column;
gap: 15px;
}
.tarot-card-container {
width: 150px;
height: 233px;
}
}
/* Hide side decor on smaller screens */
@media (max-width: 1200px) {
.side-decor {
display: none;
}
}

View File

@@ -1,71 +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>
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="side-decor left-decor"></div>
<div class="container">
<header>
<h1>水晶球占卜</h1>
<p>洞察你今日的运势</p>
</header>
<main>
<div class="crystal-ball-container">
<div class="crystal-ball">
<div class="reflection"></div>
<div class="swirl"></div>
</div>
</div>
<div id="fortune-card" class="fortune-card">
<div class="loading-spinner"></div>
<div class="fortune-content">
<h2 id="luck-desc"></h2>
<p id="luck-tip"></p>
<div class="fortune-details">
<div class="detail-item">
<h3>今日咒语</h3>
<p id="fortune-summary"></p>
</div>
<div class="detail-item">
<h3>幸运色</h3>
<p id="lucky-color"></p>
</div>
<div class="detail-item">
<h3>幸运数字</h3>
<p id="lucky-number"></p>
</div>
</div>
</div>
</div>
<!-- New Tarot Card Section -->
<div class="tarot-container">
<h2>每日塔罗指引</h2>
<div id="tarot-card" class="tarot-card-container">
<div class="tarot-card-inner">
<div class="tarot-card-back">
<!-- Back of the card design -->
</div>
<div class="tarot-card-front">
<h3 id="tarot-name"></h3>
<p id="tarot-interpretation"></p>
</div>
</div>
</div>
</div>
<button id="get-fortune-btn">再次占卜</button>
</main>
<footer>
<p>仅供娱乐,祝您好运</p>
</footer>
</div>
<div class="side-decor right-decor"></div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,170 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const getFortuneBtn = document.getElementById('get-fortune-btn');
const fortuneCard = document.getElementById('fortune-card');
const fortuneContent = fortuneCard.querySelector('.fortune-content');
const luckDescElem = document.getElementById('luck-desc');
const luckTipElem = document.getElementById('luck-tip');
const fortuneSummaryElem = document.getElementById('fortune-summary');
const luckyColorElem = document.getElementById('lucky-color');
const luckyNumberElem = document.getElementById('lucky-number');
const loadingSpinner = fortuneCard.querySelector('.loading-spinner');
const tarotCardContainer = document.getElementById('tarot-card');
const tarotNameElem = document.getElementById('tarot-name');
const tarotInterpretationElem = document.getElementById('tarot-interpretation');
const apiBaseUrls = [
"https://60s.api.shumengya.top",
];
const apiPath = "/v2/luck";
const mantras = [
"顺其自然,皆是美好。",
"相信直觉,它知道方向。",
"每一次呼吸都是新的开始。",
"心怀感恩,好运自来。",
"拥抱变化,发现惊喜。",
"你的能量,超乎想象。",
"保持微笑,宇宙会回应你。"
];
const tarotDeck = [
{ name: "愚者", interpretation: "新的开始,无限的潜力,天真和自由。勇敢地迈出第一步。" },
{ name: "魔术师", interpretation: "创造力,意志力,显化。你拥有实现目标所需的一切资源。" },
{ name: "女祭司", interpretation: "直觉,潜意识,神秘。倾听你内心的声音,智慧在你之内。" },
{ name: "皇后", interpretation: "丰饶,母性,创造。享受生活的美好,与自然和谐相处。" },
{ name: "皇帝", interpretation: "权威,结构,控制。建立秩序和纪律,掌控你的生活。" },
{ name: "教皇", interpretation: "传统,信仰,灵性指导。寻求智慧和知识,遵循传统。" },
{ name: "恋人", interpretation: "爱,和谐,选择。做出与你内心价值观一致的决定。" },
{ name: "战车", interpretation: "胜利,决心,控制。以坚定的意志力克服障碍,勇往直前。" },
{ name: "力量", interpretation: "勇气,内在力量,同情。用温柔和耐心驯服内心的野兽。" },
{ name: "隐士", interpretation: "内省,孤独,寻求真理。花时间独处,向内寻求答案。" },
{ name: "命运之轮", interpretation: "变化,命运,转折点。生活总在变化,顺应潮流。" },
{ name: "正义", interpretation: "公平,真理,因果。为你的行为负责,寻求平衡。" },
{ name: "倒吊人", interpretation: "新的视角,顺从,牺牲。放手,从不同的角度看问题。" },
{ name: "死神", interpretation: "结束,转变,新生。一个周期的结束是另一个周期的开始。" },
{ name: "节制", interpretation: "平衡,和谐,耐心。融合对立的力量,找到中间道路。" },
{ name: "恶魔", interpretation: "束缚,物质主义,诱惑。认识到你的束缚,并寻求解放。" },
{ name: "塔", interpretation: "突变,启示,解放。旧的结构正在崩塌,为新的结构让路。" },
{ name: "星星", interpretation: "希望,灵感,平静。在黑暗之后,总有希望的曙光。" },
{ name: "月亮", interpretation: "幻觉,恐惧,潜意识。面对你的恐惧,相信你的直觉。" },
{ name: "太阳", interpretation: "成功,喜悦,活力。拥抱光明,享受生活的乐趣。" },
{ name: "审判", interpretation: "觉醒,重生,评估。一个反思和更新的时刻。" },
{ name: "世界", interpretation: "完成,整合,成就。一个旅程的成功结束,庆祝你的成就。" }
];
let currentApiIndex = 0;
const showLoading = (isLoading) => {
if (isLoading) {
fortuneContent.classList.remove('visible');
loadingSpinner.classList.add('visible');
} else {
loadingSpinner.classList.remove('visible');
setTimeout(() => {
fortuneContent.classList.add('visible');
}, 100);
}
};
const fetchFortune = async () => {
showLoading(true);
tarotCardContainer.classList.remove('flipped'); // Reset card on new fetch
try {
const url = apiBaseUrls[currentApiIndex] + apiPath;
const response = await fetch(url, { timeout: 5000 });
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
if (data.code === 200 && data.data) {
updateFortune(data.data);
drawTarotCard(); // Draw a tarot card on success
} else {
throw new Error('Invalid data format');
}
} catch (error) {
console.error(`API error with ${apiBaseUrls[currentApiIndex]}:`, error);
currentApiIndex = (currentApiIndex + 1) % apiBaseUrls.length;
if (currentApiIndex !== 0) {
fetchFortune(); // Try next API
} else {
displayError();
}
}
};
const updateFortune = (data) => {
luckDescElem.textContent = data.luck_desc || '运势';
luckTipElem.textContent = data.luck_tip || '今日运势平平,保持好心情。';
// Generate and display additional details
fortuneSummaryElem.textContent = mantras[Math.floor(Math.random() * mantras.length)];
luckyColorElem.style.backgroundColor = `#${Math.floor(Math.random()*16777215).toString(16).padStart(6, '0')}`;
luckyNumberElem.textContent = Math.floor(Math.random() * 100);
showLoading(false);
};
const displayError = () => {
luckDescElem.textContent = '占卜失败';
luckTipElem.textContent = '无法连接到星辰之力,请稍后再试。';
fortuneSummaryElem.textContent = '---';
luckyColorElem.style.backgroundColor = 'transparent';
luckyNumberElem.textContent = '-';
showLoading(false);
tarotNameElem.textContent = '指引中断';
tarotInterpretationElem.textContent = '星辰之力暂时无法连接。';
tarotCardContainer.classList.add('flipped'); // Show error on card
};
const drawTarotCard = () => {
const card = tarotDeck[Math.floor(Math.random() * tarotDeck.length)];
tarotNameElem.textContent = card.name;
tarotInterpretationElem.textContent = card.interpretation;
// Flip the card after a short delay to allow the main content to appear
setTimeout(() => {
tarotCardContainer.classList.add('flipped');
}, 500);
};
const createSideDecorations = () => {
const leftContainer = document.querySelector('.left-decor');
const rightContainer = document.querySelector('.right-decor');
if (!leftContainer || !rightContainer) return;
const symbols = ['✧', '✦', '☾', '✶', '✵', '✩', '✨'];
const symbolCount = 15; // Number of symbols per side
const createSymbols = (container) => {
for (let i = 0; i < symbolCount; i++) {
const symbol = document.createElement('span');
symbol.classList.add('decor-symbol');
symbol.textContent = symbols[Math.floor(Math.random() * symbols.length)];
// Randomize properties for a more natural look
symbol.style.top = `${Math.random() * 90}vh`;
symbol.style.left = `${Math.random() * 80}%`;
symbol.style.fontSize = `${Math.random() * 20 + 10}px`;
symbol.style.animationDelay = `${Math.random() * 20}s`;
symbol.style.animationDuration = `${Math.random() * 20 + 15}s`; // Duration between 15s and 35s
container.appendChild(symbol);
}
};
createSymbols(leftContainer);
createSymbols(rightContainer);
};
getFortuneBtn.addEventListener('click', fetchFortune);
tarotCardContainer.addEventListener('click', () => {
tarotCardContainer.classList.toggle('flipped');
});
// Initial actions on page load
fetchFortune();
createSideDecorations();
});

View File

@@ -1,3 +0,0 @@
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,10 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"luck_desc": "恋愛運",
"luck_rank": 21,
"luck_tip": "闪亮的邂逅之日!顺其自然吧",
"luck_tip_index": 19
}
}

View File

@@ -1,330 +0,0 @@
/* Epic Games 免费游戏 - 淡绿色清新风格样式 */
/* 重置样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 50%, #ffd3a5 100%);
min-height: 100vh;
color: #2d5016;
line-height: 1.6;
overflow-x: hidden;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 头部 */
.header {
text-align: center;
margin-bottom: 30px;
background: rgba(255, 255, 255, 0.85);
border-radius: 20px;
padding: 24px;
box-shadow: 0 8px 25px rgba(45, 80, 22, 0.08);
backdrop-filter: blur(10px);
}
.header h1 {
font-size: 2.2rem;
color: #2d5016;
margin-bottom: 10px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
.header p {
color: #5a7c65;
font-size: 1rem;
}
/* 统计信息 */
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.stat-card {
background: rgba(255, 255, 255, 0.9);
padding: 16px;
border-radius: 15px;
text-align: center;
box-shadow: 0 4px 18px rgba(45, 80, 22, 0.08);
}
.stat-number {
font-size: 1.8rem;
font-weight: 700;
color: #1b5e20;
margin-bottom: 5px;
}
.stat-label {
color: #5a7c65;
font-size: 0.9rem;
}
/* 游戏网格 */
.games-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
/* 游戏卡片 */
.game-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
overflow: hidden;
box-shadow: 0 6px 25px rgba(45, 80, 22, 0.1);
transition: all 0.3s ease;
position: relative;
}
.game-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 35px rgba(45, 80, 22, 0.15);
}
.game-cover {
width: 100%;
height: 180px;
object-fit: cover;
border-radius: 0;
}
.game-info {
padding: 18px;
}
.game-title {
font-size: 1.1rem;
font-weight: 700;
color: #1b5e20;
margin-bottom: 8px;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.game-description {
color: #5a7c65;
font-size: 0.9rem;
margin-bottom: 12px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.4;
}
.game-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.game-price {
font-size: 1rem;
font-weight: 600;
}
.original-price {
color: #81c784;
text-decoration: line-through;
}
.free-price {
color: #2e7d32;
font-weight: 700;
}
.game-seller {
color: #5a7c65;
font-size: 0.85rem;
}
.game-dates {
background: rgba(129, 199, 132, 0.1);
padding: 10px;
border-radius: 10px;
margin-bottom: 12px;
font-size: 0.85rem;
color: #2d5016;
}
.free-period {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
}
.game-actions {
display: flex;
gap: 10px;
}
.btn {
flex: 1;
background: linear-gradient(135deg, #81c784 0%, #66bb6a 100%);
color: white;
border: none;
padding: 10px 16px;
border-radius: 10px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.25s ease;
box-shadow: 0 4px 12px rgba(129, 199, 132, 0.35);
text-decoration: none;
text-align: center;
display: inline-block;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 18px rgba(129, 199, 132, 0.45);
}
.btn-secondary {
background: linear-gradient(135deg, #a5d6a7 0%, #81c784 100%);
}
/* 状态标签 */
.status-badge {
position: absolute;
top: 12px;
right: 12px;
padding: 6px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
color: white;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.status-free {
background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
}
.status-upcoming {
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
}
/* 加载与错误 */
.loading, .error {
text-align: center;
padding: 40px;
background: rgba(255, 255, 255, 0.85);
border-radius: 15px;
box-shadow: 0 5px 20px rgba(45, 80, 22, 0.08);
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e8f5e8;
border-top: 4px solid #81c784;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 动画 */
.fade-in {
animation: fadeIn 0.6s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* 平板端适配 */
@media (max-width: 1024px) and (min-width: 768px) {
.container { padding: 16px; }
.header h1 { font-size: 2rem; }
.games-grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
}
/* 手机端优化 */
@media (max-width: 767px) {
.container { padding: 12px; }
.header { padding: 18px; }
.header h1 { font-size: 1.8rem; gap: 8px; }
.stats {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 8px;
}
.stat-card {
padding: 10px 8px;
flex: 1;
min-width: 0;
}
.stat-number { font-size: 1.4rem; }
.stat-label { font-size: 0.75rem; }
.games-grid {
grid-template-columns: 1fr;
gap: 16px;
}
.game-card { margin: 0 4px; }
.game-cover { height: 160px; }
.game-info { padding: 14px; }
.game-actions {
flex-direction: column;
}
.btn {
width: 100%;
padding: 12px;
}
}
/* 小屏手机优化 */
@media (max-width: 480px) {
.stats {
grid-template-columns: 1fr;
}
.game-meta {
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
}
/* 高分辨率显示器优化 */
@media (min-width: 1400px) {
.games-grid {
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
}
}

View File

@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Epic Games 免费游戏 - 60s API 集合</title>
<meta name="description" content="Epic Games 免费游戏列表,数据源自 60s.viki.moe提供当前免费和即将免费的游戏信息。" />
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<div class="container">
<header class="header">
<h1>
🎮 Epic Games 免费游戏
</h1>
</header>
<!-- 加载与错误状态 -->
<section id="loading" class="loading">
<div class="spinner"></div>
<p>正在加载游戏数据,请稍候…</p>
</section>
<section id="error" class="error" style="display: none;">
<p>获取数据失败,请稍后重试</p>
<button id="refresh-btn" class="btn" style="margin-top: 15px;">重新加载</button>
</section>
<!-- 内容区域 -->
<main id="content" style="display: none;" class="fade-in">
<!-- 统计信息 -->
<div class="stats">
<div class="stat-card">
<div class="stat-number" id="total-games">0</div>
<div class="stat-label">总游戏数</div>
</div>
<div class="stat-card">
<div class="stat-number" id="free-now">0</div>
<div class="stat-label">当前免费</div>
</div>
<div class="stat-card">
<div class="stat-number" id="upcoming">0</div>
<div class="stat-label">即将免费</div>
</div>
</div>
<!-- 游戏列表 -->
<div class="games-grid" id="games-grid">
<!-- 游戏卡片将通过 JavaScript 动态生成 -->
</div>
<!-- 刷新按钮 -->
<div style="text-align: center; margin-top: 30px;">
<button id="refresh-btn" class="btn btn-secondary">🔄 刷新数据</button>
</div>
</main>
</div>
<script src="./js/script.js"></script>
</body>
</html>

View File

@@ -1,266 +0,0 @@
// Epic Games 免费游戏 页面脚本
(function () {
'use strict';
const API = {
endpoints: [],
currentIndex: 0,
// 初始化API接口列表
async init() {
try {
const res = await fetch('./接口集合.json');
const endpoints = await res.json();
this.endpoints = endpoints.map(endpoint => `${endpoint}/v2/epic`);
} catch (e) {
// 如果无法加载接口集合,使用默认接口
this.endpoints = ['https://60s.api.shumengya.top/v2/epic'];
}
},
// 获取当前接口URL
getCurrentUrl(encoding) {
if (this.endpoints.length === 0) return null;
const url = new URL(this.endpoints[this.currentIndex]);
if (encoding) url.searchParams.set('encoding', encoding);
return url.toString();
},
// 切换到下一个接口
switchToNext() {
this.currentIndex = (this.currentIndex + 1) % this.endpoints.length;
return this.currentIndex < this.endpoints.length;
},
// 重置到第一个接口
reset() {
this.currentIndex = 0;
}
};
// DOM 元素引用
const els = {
loading: null,
error: null,
container: null,
gamesGrid: null,
totalGames: null,
freeNow: null,
upcoming: null,
refreshBtn: null,
};
function initDom() {
els.loading = document.getElementById('loading');
els.error = document.getElementById('error');
els.container = document.getElementById('content');
els.gamesGrid = document.getElementById('games-grid');
els.totalGames = document.getElementById('total-games');
els.freeNow = document.getElementById('free-now');
els.upcoming = document.getElementById('upcoming');
els.refreshBtn = document.getElementById('refresh-btn');
}
function showLoading() {
els.loading.style.display = 'block';
els.error.style.display = 'none';
els.container.style.display = 'none';
}
function showError(msg) {
els.loading.style.display = 'none';
els.error.style.display = 'block';
els.container.style.display = 'none';
els.error.querySelector('p').textContent = msg || '获取数据失败,请稍后重试';
}
function showContent() {
els.loading.style.display = 'none';
els.error.style.display = 'none';
els.container.style.display = 'block';
}
function safeText(text) {
const div = document.createElement('div');
div.textContent = text == null ? '' : String(text);
return div.innerHTML;
}
function formatDate(dateStr) {
if (!dateStr) return '';
try {
const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
} catch (e) {
return dateStr;
}
}
async function fetchData(preferLocal = false) {
if (preferLocal) {
try {
const res = await fetch('./返回接口.json', { cache: 'no-store' });
const json = await res.json();
return json;
} catch (e) {
throw new Error('本地数据加载失败');
}
}
// 重置API索引到第一个接口
API.reset();
// 尝试所有API接口
for (let i = 0; i < API.endpoints.length; i++) {
try {
const url = API.getCurrentUrl();
console.log(`尝试接口 ${i + 1}/${API.endpoints.length}: ${url}`);
const res = await fetch(url, {
cache: 'no-store',
timeout: 10000 // 10秒超时
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const json = await res.json();
if (json && json.code === 200) {
console.log(`接口 ${i + 1} 请求成功`);
return json;
}
throw new Error(json && json.message ? json.message : '接口返回异常');
} catch (e) {
console.warn(`接口 ${i + 1} 失败:`, e.message);
// 如果不是最后一个接口,切换到下一个
if (i < API.endpoints.length - 1) {
API.switchToNext();
continue;
}
// 所有接口都失败了,尝试本地数据
console.warn('所有远程接口都失败,尝试本地数据');
try {
const res = await fetch('./返回接口.json', { cache: 'no-store' });
const json = await res.json();
return json;
} catch (e2) {
throw new Error('所有接口和本地数据都无法访问');
}
}
}
}
function createGameCard(game) {
const isFree = game.is_free_now;
const statusClass = isFree ? 'status-free' : 'status-upcoming';
const statusText = isFree ? '限时免费' : '即将免费';
return `
<div class="game-card fade-in">
<div class="status-badge ${statusClass}">${statusText}</div>
<img class="game-cover" src="${safeText(game.cover)}" alt="${safeText(game.title)} 封面" loading="lazy" />
<div class="game-info">
<h3 class="game-title">${safeText(game.title)}</h3>
<p class="game-description">${safeText(game.description)}</p>
<div class="game-meta">
<div class="game-price">
<span class="original-price">${safeText(game.original_price_desc)}</span>
<span class="free-price">免费</span>
</div>
<div class="game-seller">${safeText(game.seller)}</div>
</div>
<div class="game-dates">
<div class="free-period">
<span>开始:${formatDate(game.free_start)}</span>
<span>结束:${formatDate(game.free_end)}</span>
</div>
</div>
<div class="game-actions">
<a href="${safeText(game.link)}" target="_blank" class="btn">
${isFree ? '立即领取' : '查看详情'}
</a>
</div>
</div>
</div>
`;
}
function updateStats(games) {
const total = games.length;
const freeNow = games.filter(game => game.is_free_now).length;
const upcoming = total - freeNow;
els.totalGames.textContent = total;
els.freeNow.textContent = freeNow;
els.upcoming.textContent = upcoming;
}
function renderGames(games) {
if (!Array.isArray(games) || games.length === 0) {
els.gamesGrid.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 40px; color: #5a7c65;">暂无游戏数据</div>';
return;
}
// 按状态排序:免费的在前
const sortedGames = [...games].sort((a, b) => {
if (a.is_free_now && !b.is_free_now) return -1;
if (!a.is_free_now && b.is_free_now) return 1;
return 0;
});
const html = sortedGames.map(game => createGameCard(game)).join('');
els.gamesGrid.innerHTML = html;
updateStats(games);
}
function render(data) {
const games = data?.data || [];
renderGames(games);
showContent();
}
async function load() {
showLoading();
// 初始化API接口列表
await API.init();
try {
const data = await fetchData(false);
render(data);
} catch (e) {
console.error('数据获取失败:', e);
showError(e.message || '获取数据失败,请稍后重试');
}
}
function bindEvents() {
if (els.refreshBtn) {
els.refreshBtn.addEventListener('click', load);
}
// 快捷键 Ctrl+R 刷新(不拦截浏览器默认刷新)
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'r' && !e.defaultPrevented) {
// 不阻止默认行为,让浏览器正常刷新
}
});
}
document.addEventListener('DOMContentLoaded', () => {
initDom();
bindEvents();
load();
});
})();

View File

@@ -1,3 +0,0 @@
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,66 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": [
{
"id": "9aa227e2ba294bb1a95c95fde892eb31",
"title": "《Totally Reliable Delivery Service》 Standard Edition",
"cover": "https://cdn1.epicgames.com/52b90f9a982a404781b189f6a7903226/offer/EGS_TotallyReliableDeliveryService_WereFiveGames_S1-2560x1440-47e6e9562d62705a75ea7b7096d0b8dc.jpg",
"original_price": 52,
"original_price_desc": "¥52.00",
"description": "穿好护腰护具发动货车送货的时间到啦在一个高度互动的沙盒世界中与最多三位好友一起随意地完成送货。货物已试投这就是我们靠谱快递Totally Reliable Delivery Service的品质保证",
"seller": "Infogrames LLC",
"is_free_now": true,
"free_start": "2025/08/14 23:00:00",
"free_start_at": 1755183600000,
"free_end": "2025/08/21 23:00:00",
"free_end_at": 1755788400000,
"link": "https://store.epicgames.com/store/zh-CN/p/totally-reliable-delivery-service/home"
},
{
"id": "8ea3500dc38e4f429702bf889c172d3d",
"title": "Hidden Folks",
"cover": "https://cdn1.epicgames.com/spt-assets/7bfd56b0586348dcb139945d9e59f988/hidden-folks-1b7hh.png",
"original_price": 47,
"original_price_desc": "¥47.00",
"description": "Search for hidden folks in hand-drawn, interactive, miniature landscapes. Unfurl tent flaps, cut through bushes, slam doors, and poke some crocodiles! Rooooaaaarrrr!!!!!",
"seller": "Adriaan de Jongh",
"is_free_now": true,
"free_start": "2025/08/14 23:00:00",
"free_start_at": 1755183600000,
"free_end": "2025/08/21 23:00:00",
"free_end_at": 1755788400000,
"link": "https://store.epicgames.com/store/zh-CN/p/hidden-folks-239d16"
},
{
"id": "4cbb6c3704d240f19c3dd5f5cb2b0cb4",
"title": "Kamaeru",
"cover": "https://cdn1.epicgames.com/spt-assets/44313cfbb62b4df5801d0c8d541c2624/kamaeru-40asc.png",
"original_price": 62,
"original_price_desc": "¥62.00",
"description": "Foster a sanctuary for frogs and restore the biodiversity of the wetlands in Kamaeru, a cozy frog collecting game, where you take pictures of frogs, play mini-games and decorate your habitat. Hop right to it!",
"seller": "Armor Games Studios",
"is_free_now": false,
"free_start": "2025/08/21 23:00:00",
"free_start_at": 1755788400000,
"free_end": "2025/08/28 23:00:00",
"free_end_at": 1756393200000,
"link": "https://store.epicgames.com/store/zh-CN/p/kamaeru-0c301e"
},
{
"id": "0d9a533f0e684cc18620a8f408e8e72c",
"title": "Strange Horticulture",
"cover": "https://cdn1.epicgames.com/spt-assets/15e8e3eba65a4763a815d6eae1d763b2/strange-horticulture-offer-2wghv.png",
"original_price": 45,
"original_price_desc": "¥45.00",
"description": "款神秘学解谜游戏,你将扮演当地植物商店的店主,寻找并识别新的植物,悠闲撸猫,与女巫团体交谈,或加入异教。收集各种强大的植物,用它们来影响故事走向,揭开昂德米尔镇的黑暗谜团。",
"seller": "Iceberg Interactive",
"is_free_now": false,
"free_start": "2025/08/21 23:00:00",
"free_start_at": 1755788400000,
"free_end": "2025/08/28 23:00:00",
"free_end_at": 1756393200000,
"link": "https://store.epicgames.com/store/zh-CN/p/strange-horticulture-360e80"
}
]
}

View File

@@ -1,233 +0,0 @@
/* 动态背景样式 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
z-index: -2;
}
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
z-index: -1;
animation: backgroundMove 20s ease-in-out infinite;
}
@keyframes backgroundMove {
0%, 100% {
background:
radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
}
25% {
background:
radial-gradient(circle at 60% 30%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 30% 70%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
}
50% {
background:
radial-gradient(circle at 80% 60%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 20% 30%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 60% 70%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
}
75% {
background:
radial-gradient(circle at 40% 90%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 70% 10%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 20% 60%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
}
}
/* 浮动粒子效果 */
.particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
}
.particle {
position: absolute;
width: 4px;
height: 4px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50%;
animation: float 15s infinite linear;
}
.particle:nth-child(1) {
left: 10%;
animation-delay: 0s;
animation-duration: 12s;
}
.particle:nth-child(2) {
left: 20%;
animation-delay: 2s;
animation-duration: 18s;
}
.particle:nth-child(3) {
left: 30%;
animation-delay: 4s;
animation-duration: 15s;
}
.particle:nth-child(4) {
left: 40%;
animation-delay: 6s;
animation-duration: 20s;
}
.particle:nth-child(5) {
left: 50%;
animation-delay: 8s;
animation-duration: 14s;
}
.particle:nth-child(6) {
left: 60%;
animation-delay: 10s;
animation-duration: 16s;
}
.particle:nth-child(7) {
left: 70%;
animation-delay: 12s;
animation-duration: 22s;
}
.particle:nth-child(8) {
left: 80%;
animation-delay: 14s;
animation-duration: 13s;
}
.particle:nth-child(9) {
left: 90%;
animation-delay: 16s;
animation-duration: 19s;
}
.particle:nth-child(10) {
left: 15%;
animation-delay: 18s;
animation-duration: 17s;
}
@keyframes float {
0% {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}
/* 网格背景效果 */
.grid-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px);
background-size: 50px 50px;
z-index: -1;
opacity: 0.3;
animation: gridMove 30s linear infinite;
}
@keyframes gridMove {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(50px, 50px);
}
}
/* 光晕效果 */
.glow-effect {
position: fixed;
top: 50%;
left: 50%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(74, 144, 226, 0.2) 0%, transparent 70%);
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: -1;
animation: pulse 4s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
transform: translate(-50%, -50%) scale(1);
opacity: 0.5;
}
50% {
transform: translate(-50%, -50%) scale(1.2);
opacity: 0.8;
}
}
/* 响应式背景调整 */
@media (max-width: 768px) {
.grid-background {
background-size: 30px 30px;
}
.glow-effect {
width: 200px;
height: 200px;
}
.particle {
width: 3px;
height: 3px;
}
}
@media (max-width: 480px) {
.grid-background {
background-size: 20px 20px;
}
.glow-effect {
width: 150px;
height: 150px;
}
.particle {
width: 2px;
height: 2px;
}
}

View File

@@ -1,445 +0,0 @@
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
min-height: 100vh;
overflow-x: hidden;
}
/* 容器样式 */
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
z-index: 1;
}
/* 头部样式 */
.header {
text-align: center;
padding: 3rem 2rem 2rem;
background: linear-gradient(135deg, rgba(74, 144, 226, 0.1), rgba(80, 200, 120, 0.1));
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.header h1 {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #4a90e2, #50c878);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header h1 i {
margin-right: 0.5rem;
background: linear-gradient(135deg, #4a90e2, #50c878);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
font-size: 1.1rem;
color: #666;
font-weight: 300;
}
/* 主要内容区域 */
.main-content {
flex: 1;
padding: 2rem;
max-width: 800px;
margin: 0 auto;
width: 100%;
}
/* 查询按钮区域 */
.query-section {
text-align: center;
margin-bottom: 2rem;
}
.query-btn {
background: linear-gradient(135deg, #4a90e2, #50c878);
color: white;
border: none;
padding: 1rem 2rem;
font-size: 1.1rem;
font-weight: 600;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(74, 144, 226, 0.3);
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-width: 200px;
justify-content: center;
}
.query-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(74, 144, 226, 0.4);
background: linear-gradient(135deg, #3a7bc8, #40a868);
}
.query-btn:active {
transform: translateY(0);
}
.query-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* 加载动画 */
.loading {
text-align: center;
padding: 2rem;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid #f3f3f3;
border-top: 4px solid #4a90e2;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading p {
color: #666;
font-size: 1rem;
}
/* IP信息卡片 */
.ip-info {
animation: fadeInUp 0.6s ease;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.ip-card {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.ip-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 2px solid #f0f0f0;
}
.ip-header i {
font-size: 1.5rem;
color: #4a90e2;
}
.ip-header h2 {
font-size: 1.5rem;
font-weight: 600;
color: #333;
}
.ip-display {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1.5rem;
background: linear-gradient(135deg, rgba(74, 144, 226, 0.1), rgba(80, 200, 120, 0.1));
border-radius: 15px;
border: 2px solid rgba(74, 144, 226, 0.2);
}
.ip-address {
font-size: 2rem;
font-weight: 700;
font-family: 'Courier New', monospace;
color: #2c3e50;
background: linear-gradient(135deg, #4a90e2, #50c878);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.copy-btn {
background: #4a90e2;
color: white;
border: none;
padding: 0.5rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1rem;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.copy-btn:hover {
background: #3a7bc8;
transform: scale(1.1);
}
.ip-details {
display: grid;
gap: 1rem;
}
.detail-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background: rgba(248, 249, 250, 0.8);
border-radius: 10px;
transition: all 0.3s ease;
}
.detail-item:hover {
background: rgba(74, 144, 226, 0.1);
transform: translateX(5px);
}
.detail-item i {
color: #4a90e2;
width: 20px;
text-align: center;
}
.detail-item .label {
font-weight: 600;
color: #555;
min-width: 80px;
}
.detail-item .value {
color: #333;
font-weight: 500;
}
/* IP地址说明区域 */
.ip-explanation {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.ip-explanation h3 {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
font-size: 1.3rem;
color: #333;
}
.ip-explanation h3 i {
color: #4a90e2;
}
.ip-explanation p {
color: #666;
line-height: 1.8;
margin-bottom: 1.5rem;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.feature-item {
display: flex;
align-items: flex-start;
gap: 1rem;
padding: 1rem;
background: rgba(248, 249, 250, 0.8);
border-radius: 12px;
transition: all 0.3s ease;
}
.feature-item:hover {
background: rgba(74, 144, 226, 0.1);
transform: translateY(-2px);
}
.feature-item i {
color: #4a90e2;
font-size: 1.5rem;
margin-top: 0.2rem;
}
.feature-item h4 {
font-size: 1rem;
font-weight: 600;
color: #333;
margin-bottom: 0.3rem;
}
.feature-item p {
font-size: 0.9rem;
color: #666;
line-height: 1.5;
margin: 0;
}
/* 错误信息 */
.error-message {
text-align: center;
padding: 2rem;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 99, 99, 0.3);
animation: fadeInUp 0.6s ease;
}
.error-message i {
font-size: 3rem;
color: #ff6b6b;
margin-bottom: 1rem;
}
.error-message p {
color: #666;
font-size: 1.1rem;
margin-bottom: 1.5rem;
}
.retry-btn {
background: #ff6b6b;
color: white;
border: none;
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: 600;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
}
.retry-btn:hover {
background: #ff5252;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
}
/* 页脚 */
.footer {
text-align: center;
padding: 2rem;
background: rgba(248, 249, 250, 0.8);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(255, 255, 255, 0.2);
color: #666;
font-size: 0.9rem;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header {
padding: 2rem 1rem 1.5rem;
}
.header h1 {
font-size: 2rem;
}
.main-content {
padding: 1rem;
}
.ip-card, .ip-explanation {
padding: 1.5rem;
}
.ip-address {
font-size: 1.5rem;
}
.ip-display {
flex-direction: column;
gap: 1rem;
}
.features {
grid-template-columns: 1fr;
}
.detail-item {
flex-direction: column;
align-items: flex-start;
gap: 0.3rem;
}
.detail-item .label {
min-width: auto;
}
}
@media (max-width: 480px) {
.header h1 {
font-size: 1.8rem;
}
.query-btn {
padding: 0.875rem 1.5rem;
font-size: 1rem;
min-width: 180px;
}
.ip-address {
font-size: 1.3rem;
}
.ip-card, .ip-explanation {
padding: 1rem;
}
}

View File

@@ -1,139 +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>公网IP地址查询</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<div class="container">
<!-- 头部 -->
<header class="header">
<h1><i class="fas fa-globe"></i> 公网IP地址查询</h1>
<p class="subtitle">快速获取您的公网IP地址信息</p>
</header>
<!-- 主要内容区域 -->
<main class="main-content">
<!-- 查询按钮区域 -->
<div class="query-section">
<button id="queryBtn" class="query-btn">
<i class="fas fa-search"></i>
<span>查询我的IP地址</span>
</button>
</div>
<!-- 加载动画 -->
<div id="loading" class="loading" style="display: none;">
<div class="spinner"></div>
<p>正在获取IP地址信息...</p>
</div>
<!-- IP信息展示区域 -->
<div id="ip-info" class="ip-info" style="display: none;">
<div class="ip-card">
<div class="ip-header">
<i class="fas fa-network-wired"></i>
<h2>您的公网IP地址</h2>
</div>
<div class="ip-display">
<span id="ip-address" class="ip-address">---.---.---.---</span>
<button id="copyBtn" class="copy-btn" title="复制IP地址">
<i class="fas fa-copy"></i>
</button>
</div>
<div class="ip-details">
<div class="detail-item">
<i class="fas fa-clock"></i>
<span class="label">查询时间:</span>
<span id="query-time" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-server"></i>
<span class="label">数据来源:</span>
<span class="value">60s.viki.moe</span>
</div>
<div class="detail-item">
<i class="fas fa-map-marker-alt"></i>
<span class="label">位置信息:</span>
<span id="location" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-building"></i>
<span class="label">网络服务商:</span>
<span id="isp" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-flag"></i>
<span class="label">国家:</span>
<span id="country" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-map"></i>
<span class="label">地区:</span>
<span id="region" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-city"></i>
<span class="label">城市:</span>
<span id="city" class="value">--</span>
</div>
<div class="detail-item">
<i class="fas fa-clock"></i>
<span class="label">时区:</span>
<span id="timezone" class="value">--</span>
</div>
</div>
</div>
<!-- IP地址信息说明 -->
<div class="ip-explanation">
<h3><i class="fas fa-info-circle"></i> 什么是公网IP地址</h3>
<p>公网IP地址是您的设备在互联网上的唯一标识符由您的网络服务提供商(ISP)分配。通过这个地址,互联网上的其他设备可以找到并与您的设备通信。</p>
<div class="features">
<div class="feature-item">
<i class="fas fa-shield-alt"></i>
<div>
<h4>隐私保护</h4>
<p>了解您的IP地址有助于保护网络隐私</p>
</div>
</div>
<div class="feature-item">
<i class="fas fa-map-marker-alt"></i>
<div>
<h4>地理位置</h4>
<p>IP地址可以大致确定您的地理位置</p>
</div>
</div>
<div class="feature-item">
<i class="fas fa-cogs"></i>
<div>
<h4>网络配置</h4>
<p>用于网络故障排除和配置</p>
</div>
</div>
</div>
</div>
</div>
<!-- 错误信息 -->
<div id="error-message" class="error-message" style="display: none;">
<i class="fas fa-exclamation-triangle"></i>
<p>获取IP地址失败请检查网络连接或稍后重试</p>
<button id="retryBtn" class="retry-btn">重试</button>
</div>
</main>
<!-- 页脚 -->
<footer class="footer">
<p>&copy; 2024 公网IP地址查询工具 | 数据来源: 60s.viki.moe</p>
</footer>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,343 +0,0 @@
// 公网IP地址查询应用
class IPQueryApp {
constructor() {
this.apiEndpoint = 'https://60s.api.shumengya.top/v2/ip';
this.init();
}
// 初始化应用
init() {
this.bindEvents();
this.createParticles();
this.createBackgroundElements();
console.log('IP查询应用初始化完成');
}
// 绑定事件
bindEvents() {
const queryBtn = document.getElementById('queryBtn');
const retryBtn = document.getElementById('retryBtn');
const copyBtn = document.getElementById('copyBtn');
if (queryBtn) {
queryBtn.addEventListener('click', () => this.queryIP());
}
if (retryBtn) {
retryBtn.addEventListener('click', () => this.queryIP());
}
if (copyBtn) {
copyBtn.addEventListener('click', () => this.copyIP());
}
// 页面加载完成后自动查询一次
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.queryIP(), 500);
});
}
// 创建浮动粒子
createParticles() {
const particlesContainer = document.createElement('div');
particlesContainer.className = 'particles';
document.body.appendChild(particlesContainer);
for (let i = 0; i < 10; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particlesContainer.appendChild(particle);
}
}
// 创建背景元素
createBackgroundElements() {
// 创建网格背景
const gridBackground = document.createElement('div');
gridBackground.className = 'grid-background';
document.body.appendChild(gridBackground);
// 创建光晕效果
const glowEffect = document.createElement('div');
glowEffect.className = 'glow-effect';
document.body.appendChild(glowEffect);
}
// 显示加载状态
showLoading() {
const loading = document.getElementById('loading');
const ipInfo = document.getElementById('ipInfo');
const errorMessage = document.getElementById('errorMessage');
const queryBtn = document.getElementById('queryBtn');
if (loading) loading.style.display = 'block';
if (ipInfo) ipInfo.style.display = 'none';
if (errorMessage) errorMessage.style.display = 'none';
if (queryBtn) {
queryBtn.disabled = true;
queryBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 查询中...';
}
}
// 隐藏加载状态
hideLoading() {
const loading = document.getElementById('loading');
const queryBtn = document.getElementById('queryBtn');
if (loading) loading.style.display = 'none';
if (queryBtn) {
queryBtn.disabled = false;
queryBtn.innerHTML = '<i class="fas fa-search"></i> 查询我的IP';
}
}
// 显示错误信息
showError(message) {
const errorMessage = document.getElementById('error-message');
const ipInfo = document.getElementById('ip-info');
if (errorMessage) {
errorMessage.style.display = 'block';
const errorText = errorMessage.querySelector('p');
if (errorText) errorText.textContent = message || '获取IP信息失败请稍后重试';
}
if (ipInfo) ipInfo.style.display = 'none';
this.hideLoading();
}
// 查询IP地址
async queryIP() {
try {
this.showLoading();
console.log('开始查询IP地址...');
const response = await fetch(this.apiEndpoint, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
console.log('API响应状态:', response.status);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
console.log('API返回数据:', data);
if (data.code === 200 && data.data) {
this.displayIPInfo(data.data);
} else {
throw new Error(data.message || '获取IP信息失败');
}
} catch (error) {
console.error('查询IP失败:', error);
this.showError(error.message);
}
}
// 显示IP信息
displayIPInfo(data) {
const ipInfo = document.getElementById('ip-info');
const errorMessage = document.getElementById('error-message');
// 更新IP地址显示
const ipAddressElement = document.getElementById('ip-address');
if (ipAddressElement && data.ip) {
ipAddressElement.textContent = data.ip;
}
// 更新查询时间
const queryTimeElement = document.getElementById('query-time');
if (queryTimeElement) {
const now = new Date();
queryTimeElement.textContent = now.toLocaleString('zh-CN');
}
// 更新详细信息 - 只显示API提供的数据
if (data.location) this.updateDetailItem('location', data.location);
else this.hideDetailItem('location');
if (data.isp) this.updateDetailItem('isp', data.isp);
else this.hideDetailItem('isp');
if (data.country) this.updateDetailItem('country', data.country);
else this.hideDetailItem('country');
if (data.region) this.updateDetailItem('region', data.region);
else this.hideDetailItem('region');
if (data.city) this.updateDetailItem('city', data.city);
else this.hideDetailItem('city');
if (data.timezone) this.updateDetailItem('timezone', data.timezone);
else this.hideDetailItem('timezone');
// 显示IP信息隐藏错误信息
if (ipInfo) ipInfo.style.display = 'block';
if (errorMessage) errorMessage.style.display = 'none';
this.hideLoading();
console.log('IP信息显示完成');
}
// 更新详细信息项
updateDetailItem(id, value) {
const element = document.getElementById(id);
if (element) {
element.textContent = value;
// 显示对应的详细信息行
const detailRow = element.closest('.detail-item');
if (detailRow) {
detailRow.style.display = 'flex';
}
}
}
// 隐藏详细信息项
hideDetailItem(id) {
const element = document.getElementById(id);
if (element) {
// 隐藏整个详细信息行
const detailRow = element.closest('.detail-item');
if (detailRow) {
detailRow.style.display = 'none';
}
}
}
// 复制IP地址
async copyIP() {
const ipAddressElement = document.getElementById('ip-address');
const copyBtn = document.getElementById('copyBtn');
if (!ipAddressElement || !ipAddressElement.textContent) {
this.showToast('没有可复制的IP地址', 'error');
return;
}
try {
await navigator.clipboard.writeText(ipAddressElement.textContent);
// 更新按钮状态
if (copyBtn) {
const originalHTML = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check"></i>';
copyBtn.style.background = '#50c878';
setTimeout(() => {
copyBtn.innerHTML = originalHTML;
copyBtn.style.background = '#4a90e2';
}, 1500);
}
this.showToast('IP地址已复制到剪贴板', 'success');
console.log('IP地址已复制:', ipAddressElement.textContent);
} catch (error) {
console.error('复制失败:', error);
this.showToast('复制失败,请手动选择复制', 'error');
}
}
// 显示提示消息
showToast(message, type = 'info') {
// 移除已存在的toast
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
// 创建新的toast
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
<span>${message}</span>
`;
// 添加toast样式
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? '#50c878' : type === 'error' ? '#ff6b6b' : '#4a90e2'};
color: white;
padding: 1rem 1.5rem;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
z-index: 1000;
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
animation: slideInRight 0.3s ease;
max-width: 300px;
`;
// 添加动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
document.head.appendChild(style);
document.body.appendChild(toast);
// 3秒后自动移除
setTimeout(() => {
toast.style.animation = 'slideOutRight 0.3s ease';
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
if (style.parentNode) {
style.remove();
}
}, 300);
}, 3000);
}
// 格式化时间
formatTime(timestamp) {
const date = new Date(timestamp);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
}
// 初始化应用
const app = new IPQueryApp();
// 导出到全局作用域(用于调试)
window.IPQueryApp = app;

View File

@@ -1,3 +0,0 @@
[
"https://60s.api.shumengya.top/v2/ip"
]

View File

@@ -1,17 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"ip": "2401:b60:16:83::"
}
}
// 注意此API只返回IP地址不包含以下信息
// - location (位置信息)
// - isp (网络服务商)
// - country (国家)
// - region (地区)
// - city (城市)
// - timezone (时区)
//
// 使API

View File

@@ -1,575 +0,0 @@
/* 农历主题背景样式 - 动态调节版本 */
body {
background: linear-gradient(135deg,
#fff8dc 0%, /* 玉米丝色 */
#ffd700 20%, /* 金黄色 */
#ffcc00 40%, /* 亮金色 */
#daa520 60%, /* 深金色 */
#b8860b 80%, /* 暗金色 */
#fff8dc 100% /* 玉米丝色 */
);
background-size: 400% 400%;
animation: goldenShift 25s ease infinite;
background-attachment: fixed;
min-height: 100vh;
position: relative;
}
@keyframes goldenShift {
0% { background-position: 0% 50%; }
25% { background-position: 100% 50%; }
50% { background-position: 100% 100%; }
75% { background-position: 0% 100%; }
100% { background-position: 0% 50%; }
}
/* 动态颜色调节系统 */
.adaptive-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(255, 255, 255, 0.15) 0%, transparent 50%),
linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.2) 100%);
pointer-events: none;
z-index: 1;
animation: adaptiveShift 60s ease infinite;
}
@keyframes adaptiveShift {
0% {
background:
radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(255, 255, 255, 0.15) 0%, transparent 50%),
linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.2) 100%);
}
25% {
background:
radial-gradient(circle at 70% 20%, rgba(255, 255, 255, 0.2) 0%, transparent 50%),
radial-gradient(circle at 30% 80%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.25) 100%);
}
50% {
background:
radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.15) 0%, transparent 50%),
radial-gradient(circle at 10% 90%, rgba(255, 255, 255, 0.12) 0%, transparent 50%),
linear-gradient(225deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.22) 100%);
}
75% {
background:
radial-gradient(circle at 90% 60%, rgba(255, 255, 255, 0.18) 0%, transparent 50%),
radial-gradient(circle at 40% 10%, rgba(255, 255, 255, 0.08) 0%, transparent 50%),
linear-gradient(315deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.2) 100%);
}
100% {
background:
radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(255, 255, 255, 0.15) 0%, transparent 50%),
linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.2) 100%);
}
}
/* 高清稻穗贴图层 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
/* 主稻穗束 - 高清细节 */
radial-gradient(ellipse 1.5px 12px at 50% 45%, #DAA520 0%, #B8860B 30%, transparent 80%),
radial-gradient(ellipse 1px 10px at 48% 50%, #FFD700 0%, #DAA520 40%, transparent 75%),
radial-gradient(ellipse 1.2px 11px at 52% 48%, #FFCC00 0%, #B8860B 35%, transparent 78%),
radial-gradient(ellipse 0.8px 9px at 49% 52%, #F4A460 0%, #DAA520 45%, transparent 70%),
radial-gradient(ellipse 1.3px 13px at 51% 46%, #DEB887 0%, #B8860B 38%, transparent 82%),
/* 次级稻穗 */
radial-gradient(ellipse 1px 8px at 30% 35%, #FFD700 0%, #DAA520 50%, transparent 75%),
radial-gradient(ellipse 0.9px 7px at 32% 38%, #FFCC00 0%, #B8860B 45%, transparent 70%),
radial-gradient(ellipse 1.1px 9px at 28% 36%, #DEB887 0%, #DAA520 40%, transparent 78%),
radial-gradient(ellipse 1px 8px at 70% 65%, #FFD700 0%, #DAA520 50%, transparent 75%),
radial-gradient(ellipse 0.8px 7px at 72% 68%, #F4A460 0%, #B8860B 45%, transparent 70%),
radial-gradient(ellipse 1.2px 9px at 68% 66%, #FFCC00 0%, #DAA520 40%, transparent 78%),
/* 散落稻粒 */
radial-gradient(ellipse 0.5px 4px at 20% 80%, #FFD700 0%, transparent 60%),
radial-gradient(ellipse 0.6px 5px at 80% 20%, #FFCC00 0%, transparent 65%),
radial-gradient(ellipse 0.4px 3px at 15% 25%, #DEB887 0%, transparent 55%),
radial-gradient(ellipse 0.7px 6px at 85% 75%, #DAA520 0%, transparent 70%),
/* 稻穗茎秆 - 更细致 */
linear-gradient(88deg, transparent 49%, #9ACD32 49.5%, #8FBC8F 50%, #9ACD32 50.5%, transparent 51%),
linear-gradient(92deg, transparent 49%, #8FBC8F 49.5%, #228B22 50%, #8FBC8F 50.5%, transparent 51%),
linear-gradient(85deg, transparent 49%, #32CD32 49.5%, #9ACD32 50%, #32CD32 50.5%, transparent 51%);
background-size:
25px 25px, 24px 24px, 26px 26px, 23px 23px, 27px 27px,
20px 20px, 19px 19px, 21px 21px,
22px 22px, 18px 18px, 23px 23px,
15px 15px, 16px 16px, 14px 14px, 17px 17px,
80px 80px, 85px 85px, 75px 75px;
background-position:
0 0, 12px 12px, 6px 18px, 18px 6px, 3px 21px,
40px 40px, 52px 48px, 35px 55px,
120px 120px, 135px 115px, 110px 130px,
200px 200px, 220px 180px, 180px 220px, 240px 160px,
0 0, 40px 40px, 20px 60px;
opacity: 0.25;
pointer-events: none;
z-index: -2;
animation: wheatSway 20s ease-in-out infinite;
}
@keyframes wheatSway {
0%, 100% {
transform: translateX(0) rotate(0deg);
}
25% {
transform: translateX(5px) rotate(0.5deg);
}
50% {
transform: translateX(-3px) rotate(-0.3deg);
}
75% {
transform: translateX(2px) rotate(0.2deg);
}
}
/* 大型稻穗背景层 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
/* 主稻穗茎秆 - 右侧大型 */
linear-gradient(85deg, transparent 45%, #9ACD32 47%, #8FBC8F 48%, #228B22 49%, #8FBC8F 50%, #9ACD32 51%, transparent 53%),
linear-gradient(87deg, transparent 46%, #32CD32 47.5%, #9ACD32 48.5%, #8FBC8F 49.5%, #9ACD32 50.5%, #32CD32 51.5%, transparent 54%),
/* 主稻穗穗头 - 大型椭圆稻粒群 */
radial-gradient(ellipse 8px 25px at 75% 15%, #FFD700 0%, #DAA520 30%, #B8860B 60%, transparent 85%),
radial-gradient(ellipse 7px 23px at 77% 18%, #FFCC00 0%, #DAA520 35%, transparent 80%),
radial-gradient(ellipse 9px 27px at 73% 12%, #F4A460 0%, #B8860B 40%, transparent 88%),
radial-gradient(ellipse 6px 22px at 79% 20%, #DEB887 0%, #DAA520 45%, transparent 75%),
radial-gradient(ellipse 8px 24px at 75% 16%, #FFD700 0%, #B8860B 38%, transparent 82%),
/* 稻穗分支 */
radial-gradient(ellipse 5px 18px at 72% 25%, #FFCC00 0%, #DAA520 50%, transparent 75%),
radial-gradient(ellipse 4px 16px at 78% 28%, #F4A460 0%, #B8860B 45%, transparent 70%),
radial-gradient(ellipse 6px 20px at 70% 22%, #DEB887 0%, #DAA520 40%, transparent 78%),
radial-gradient(ellipse 5px 17px at 80% 30%, #FFD700 0%, #B8860B 42%, transparent 76%),
/* 左侧稻穗茎秆 */
linear-gradient(95deg, transparent 15%, #9ACD32 17%, #8FBC8F 18%, #228B22 19%, #8FBC8F 20%, #9ACD32 21%, transparent 23%),
/* 左侧稻穗穗头 */
radial-gradient(ellipse 6px 20px at 25% 25%, #FFD700 0%, #DAA520 30%, transparent 80%),
radial-gradient(ellipse 5px 18px at 27% 28%, #FFCC00 0%, #B8860B 35%, transparent 75%),
radial-gradient(ellipse 7px 22px at 23% 22%, #F4A460 0%, #DAA520 40%, transparent 85%),
/* 麦田远景效果 */
linear-gradient(180deg, transparent 70%, rgba(255, 215, 0, 0.1) 75%, rgba(218, 165, 32, 0.15) 85%, rgba(255, 215, 0, 0.2) 95%, rgba(255, 215, 0, 0.25) 100%),
/* 散落稻粒 */
radial-gradient(ellipse 2px 8px at 60% 40%, #FFD700 0%, transparent 60%),
radial-gradient(ellipse 1.5px 6px at 40% 60%, #FFCC00 0%, transparent 65%),
radial-gradient(ellipse 2.5px 10px at 85% 50%, #DEB887 0%, transparent 70%),
radial-gradient(ellipse 1.8px 7px at 15% 80%, #DAA520 0%, transparent 68%);
background-size:
/* 主茎秆 */
100vw 80vh, 100vw 82vh,
/* 主穗头 */
50vw 60vh, 48vw 58vh, 52vw 62vh, 46vw 56vh, 50vw 60vh,
/* 分支 */
40vw 50vh, 38vw 48vh, 42vw 52vh, 36vw 46vh,
/* 左侧茎秆 */
100vw 70vh,
/* 左侧穗头 */
35vw 45vh, 33vw 43vh, 37vw 47vh,
/* 麦田远景 */
100vw 100vh,
/* 散落稻粒 */
20vw 20vh, 25vw 25vh, 30vw 30vh, 22vw 22vh;
background-position:
/* 主茎秆 */
70% 20%, 72% 18%,
/* 主穗头 */
60% 0%, 62% 2%, 58% -2%, 64% 4%, 60% 1%,
/* 分支 */
65% 15%, 67% 17%, 63% 13%, 69% 19%,
/* 左侧茎秆 */
20% 30%,
/* 左侧穗头 */
15% 20%, 17% 22%, 13% 18%,
/* 麦田远景 */
0% 0%,
/* 散落稻粒 */
30% 50%, 50% 70%, 80% 40%, 10% 80%;
background-repeat: no-repeat;
opacity: 0.4;
pointer-events: none;
z-index: -1;
animation: wheatSway 25s ease-in-out infinite;
}
@keyframes spiralRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 流星效果容器 */
.meteor-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
overflow: hidden;
}
/* 流星轨迹 */
.meteor {
position: absolute;
width: 2px;
height: 2px;
background: radial-gradient(circle, #FFD700 0%, #FFA500 50%, transparent 100%);
border-radius: 50%;
box-shadow:
0 0 10px #FFD700,
0 0 20px #FFA500,
0 0 30px #FF8C00;
animation: meteorFall linear infinite;
}
/* 流星尾迹 */
.meteor::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 1px;
background: linear-gradient(90deg,
#FFD700 0%,
#FFA500 30%,
#FF8C00 60%,
transparent 100%);
transform-origin: 0 50%;
transform: rotate(-45deg);
opacity: 0.8;
}
@keyframes meteorFall {
0% {
transform: translateX(-100px) translateY(-100px);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateX(calc(100vw + 100px)) translateY(calc(100vh + 100px));
opacity: 0;
}
}
/* 多个流星的不同轨迹 */
.meteor:nth-child(1) {
top: 10%;
left: -100px;
animation-duration: 8s;
animation-delay: 0s;
}
.meteor:nth-child(2) {
top: 20%;
left: -100px;
animation-duration: 12s;
animation-delay: 2s;
}
.meteor:nth-child(3) {
top: 30%;
left: -100px;
animation-duration: 10s;
animation-delay: 4s;
}
.meteor:nth-child(4) {
top: 50%;
left: -100px;
animation-duration: 15s;
animation-delay: 6s;
}
.meteor:nth-child(5) {
top: 70%;
left: -100px;
animation-duration: 9s;
animation-delay: 8s;
}
.meteor:nth-child(6) {
top: 80%;
left: -100px;
animation-duration: 11s;
animation-delay: 10s;
}
/* 金色粒子效果 */
.golden-particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
}
.particle {
position: absolute;
width: 3px;
height: 3px;
background: radial-gradient(circle, #FFD700 0%, #FFA500 70%, transparent 100%);
border-radius: 50%;
animation: particleFloat linear infinite;
}
@keyframes particleFloat {
0% {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}
/* 粒子的不同位置和动画时长 */
.particle:nth-child(1) { left: 10%; animation-duration: 20s; animation-delay: 0s; }
.particle:nth-child(2) { left: 20%; animation-duration: 25s; animation-delay: 2s; }
.particle:nth-child(3) { left: 30%; animation-duration: 18s; animation-delay: 4s; }
.particle:nth-child(4) { left: 40%; animation-duration: 22s; animation-delay: 6s; }
.particle:nth-child(5) { left: 50%; animation-duration: 24s; animation-delay: 8s; }
.particle:nth-child(6) { left: 60%; animation-duration: 19s; animation-delay: 10s; }
.particle:nth-child(7) { left: 70%; animation-duration: 21s; animation-delay: 12s; }
.particle:nth-child(8) { left: 80%; animation-duration: 23s; animation-delay: 14s; }
.particle:nth-child(9) { left: 90%; animation-duration: 26s; animation-delay: 16s; }
/* 响应式设计 */
@media (max-width: 768px) {
.meteor {
width: 1px;
height: 1px;
}
.meteor::before {
width: 50px;
}
.particle {
width: 2px;
height: 2px;
}
body::before {
background-size:
30px 30px, 25px 25px, 35px 35px, 28px 28px, 32px 32px,
40px 40px, 45px 45px;
}
body::after {
background-size: 200px 200px, 150px 150px, 100px 100px;
}
}
/* 麦穗飘舞特效 */
.wheat-floating {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 2;
overflow: hidden;
}
/* 移动设备性能优化 */
@media (max-width: 768px) {
.wheat-floating {
display: none;
}
.golden-particles {
display: none;
}
.meteor-container {
display: none;
}
.adaptive-overlay {
animation: none;
background: rgba(255, 255, 255, 0.1);
}
}
.wheat-particle {
position: absolute;
width: 8px;
height: 20px;
background: linear-gradient(180deg,
#FFD700 0%,
#DAA520 50%,
#B8860B 100%
);
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
opacity: 0.7;
animation: wheatFloat 15s linear infinite;
}
.wheat-particle::before {
content: '';
position: absolute;
top: -3px;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 8px;
background: #8B7355;
border-radius: 1px;
}
.wheat-particle::after {
content: '';
position: absolute;
top: 2px;
left: 1px;
width: 2px;
height: 4px;
background: #FFEC8C;
border-radius: 50%;
box-shadow:
3px 2px 0 #FFEC8C,
1px 6px 0 #FFEC8C,
4px 8px 0 #FFEC8C;
}
@keyframes wheatFloat {
0% {
transform: translateY(-100vh) translateX(0) rotate(0deg);
opacity: 0;
}
10% {
opacity: 0.7;
}
90% {
opacity: 0.7;
}
100% {
transform: translateY(100vh) translateX(50px) rotate(360deg);
opacity: 0;
}
}
/* 不同大小和速度的麦穗 */
.wheat-particle:nth-child(1) {
left: 10%;
animation-duration: 12s;
animation-delay: 0s;
transform: scale(0.8);
}
.wheat-particle:nth-child(2) {
left: 25%;
animation-duration: 18s;
animation-delay: 2s;
transform: scale(1.2);
}
.wheat-particle:nth-child(3) {
left: 40%;
animation-duration: 15s;
animation-delay: 4s;
transform: scale(0.9);
}
.wheat-particle:nth-child(4) {
left: 60%;
animation-duration: 20s;
animation-delay: 1s;
transform: scale(1.1);
}
.wheat-particle:nth-child(5) {
left: 75%;
animation-duration: 14s;
animation-delay: 3s;
transform: scale(0.7);
}
.wheat-particle:nth-child(6) {
left: 90%;
animation-duration: 16s;
animation-delay: 5s;
transform: scale(1.0);
}
.wheat-particle:nth-child(7) {
left: 5%;
animation-duration: 22s;
animation-delay: 6s;
transform: scale(0.6);
}
.wheat-particle:nth-child(8) {
left: 35%;
animation-duration: 13s;
animation-delay: 2.5s;
transform: scale(1.3);
}
/* 减少动画偏好设置 */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.meteor,
.particle {
display: none;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,109 +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>
<link rel="stylesheet" href="css/background.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- 动态调节遮罩层 -->
<div class="adaptive-overlay"></div>
<!-- 流星效果容器 -->
<div class="meteor-container">
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
<div class="meteor"></div>
</div>
<!-- 金色粒子效果容器 -->
<div class="golden-particles">
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
</div>
<!-- 麦穗飘舞特效 -->
<div class="wheat-floating">
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
<div class="wheat-particle"></div>
</div>
<div class="container">
<header class="header">
<div class="header-icon">🏮</div>
<h1 class="title">🌙 农历信息查询 📅</h1>
<p class="subtitle">传统文化 · 时光转换 · 节气查询</p>
<div class="date-selector">
<div class="input-group">
<label for="dateInput" class="input-label">
<span class="label-icon">📅</span>
选择日期
</label>
<input type="date" id="dateInput" class="date-input" />
</div>
<button id="queryBtn" class="query-btn">
<span class="btn-icon">🔍</span>
查询农历
</button>
</div>
<div class="update-time">
<span class="time-icon"></span>
<span id="updateTime">等待查询...</span>
</div>
</header>
<div class="loading" id="loading" style="display: none;">
<div class="loading-content">
<div class="glass-spinner"></div>
<div class="loading-text">
<span class="loading-emoji">🔮</span>
<p>正在查询农历信息...</p>
<div class="loading-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<div class="lunar-info" id="lunarInfo" style="display: none;">
<!-- 农历信息将动态生成 -->
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<div class="error-content">
<div class="error-icon">😔</div>
<h3>查询失败了</h3>
<p>无法获取农历信息,请稍后重试</p>
<button onclick="queryLunarInfo()" class="retry-btn">
<span>🔄</span>
重新查询
</button>
</div>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,481 +0,0 @@
// API接口列表
const API_ENDPOINTS = [
"https://60s.api.shumengya.top",
];
// 当前使用的API索引
let currentApiIndex = 0;
// DOM元素
const loadingElement = document.getElementById('loading');
const lunarInfoElement = document.getElementById('lunarInfo');
const errorMessageElement = document.getElementById('errorMessage');
const updateTimeElement = document.getElementById('updateTime');
const dateInput = document.getElementById('dateInput');
const queryBtn = document.getElementById('queryBtn');
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initializePage();
});
// 初始化页面
function initializePage() {
// 设置默认日期为今天
const today = new Date();
const dateString = today.toISOString().split('T')[0];
dateInput.value = dateString;
// 绑定事件
queryBtn.addEventListener('click', queryLunarInfo);
dateInput.addEventListener('change', queryLunarInfo);
// 自动查询当天信息
queryLunarInfo();
}
// 查询农历信息
async function queryLunarInfo() {
const selectedDate = dateInput.value;
if (!selectedDate) {
showError('请选择查询日期');
return;
}
showLoading();
hideError();
hideLunarInfo();
try {
const data = await fetchLunarData(selectedDate);
displayLunarInfo(data.data);
updateQueryTime();
} catch (error) {
console.error('查询失败:', error);
showError('查询农历信息失败,请稍后重试');
}
hideLoading();
}
// 获取农历数据
async function fetchLunarData(date) {
for (let i = 0; i < API_ENDPOINTS.length; i++) {
const apiUrl = API_ENDPOINTS[currentApiIndex];
try {
const response = await fetch(`${apiUrl}/v2/lunar?date=${date}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
timeout: 10000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (data.code === 200 && data.data) {
return data;
} else {
throw new Error('数据格式错误');
}
} catch (error) {
console.error(`API ${apiUrl} 请求失败:`, error);
currentApiIndex = (currentApiIndex + 1) % API_ENDPOINTS.length;
if (i === API_ENDPOINTS.length - 1) {
throw new Error('所有API接口都无法访问');
}
}
}
}
// 显示农历信息
function displayLunarInfo(lunarData) {
lunarInfoElement.innerHTML = `
<div class="info-card">
<div class="card-header">
<div class="card-icon">📅</div>
<div class="card-title">公历信息</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🗓️</div>
<div class="item-label">公历日期</div>
<div class="item-value">${lunarData.solar.year}${String(lunarData.solar.month).padStart(2, '0')}${String(lunarData.solar.day).padStart(2, '0')}日</div>
</div>
<div class="info-item">
<div class="item-icon">🌍</div>
<div class="item-label">星期</div>
<div class="item-value">${lunarData.solar.week_desc}</div>
</div>
<div class="info-item">
<div class="item-icon">🍂</div>
<div class="item-label">季节</div>
<div class="item-value">${lunarData.solar.season_name_desc}</div>
</div>
<div class="info-item">
<div class="item-icon">⭐</div>
<div class="item-label">星座</div>
<div class="item-value">${lunarData.constellation.name}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">🌙</div>
<div class="card-title">农历信息</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🏮</div>
<div class="item-label">农历日期</div>
<div class="item-value">${lunarData.lunar.desc_short}</div>
</div>
<div class="info-item">
<div class="item-icon">🐲</div>
<div class="item-label">生肖年</div>
<div class="item-value">${lunarData.zodiac.year}年</div>
</div>
<div class="info-item">
<div class="item-icon">☯️</div>
<div class="item-label">天干地支</div>
<div class="item-value">${lunarData.sixty_cycle.year.name}</div>
</div>
<div class="info-item">
<div class="item-icon">🌙</div>
<div class="item-label">月相</div>
<div class="item-value">${lunarData.phase.name}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">🌾</div>
<div class="card-title">节气节日</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🌱</div>
<div class="item-label">当前节气</div>
<div class="item-value">${lunarData.term.stage ? lunarData.term.stage.name : '无节气'}</div>
</div>
<div class="info-item">
<div class="item-icon">🎉</div>
<div class="item-label">法定假日</div>
<div class="item-value">${lunarData.legal_holiday ? lunarData.legal_holiday.name : '无假日'}</div>
</div>
<div class="info-item">
<div class="item-icon">🎊</div>
<div class="item-label">传统节日</div>
<div class="item-value">${lunarData.festival.both_desc || '无特殊节日'}</div>
</div>
<div class="info-item">
<div class="item-icon">📊</div>
<div class="item-label">一年第几天</div>
<div class="item-value">第${lunarData.stats.day_of_year}天</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">⏰</div>
<div class="card-title">时辰干支</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🕐</div>
<div class="item-label">当前时辰</div>
<div class="item-value">${lunarData.lunar.hour_desc}</div>
</div>
<div class="info-item">
<div class="item-icon">☯️</div>
<div class="item-label">时辰干支</div>
<div class="item-value">${lunarData.sixty_cycle.hour.name}</div>
</div>
<div class="info-item">
<div class="item-icon">🐾</div>
<div class="item-label">时辰生肖</div>
<div class="item-value">${lunarData.zodiac.hour}</div>
</div>
<div class="info-item">
<div class="item-icon">🎵</div>
<div class="item-label">纳音</div>
<div class="item-value">${lunarData.nayin.hour}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">📖</div>
<div class="card-title">黄历宜忌</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">✅</div>
<div class="item-label">宜</div>
<div class="item-value">${formatTabooText(lunarData.taboo.day.recommends)}</div>
</div>
<div class="info-item">
<div class="item-icon">❌</div>
<div class="item-label">忌</div>
<div class="item-value">${formatTabooText(lunarData.taboo.day.avoids)}</div>
</div>
<div class="info-item">
<div class="item-icon">🕐</div>
<div class="item-label">时辰宜</div>
<div class="item-value">${formatTabooText(lunarData.taboo.hour.recommends)}</div>
</div>
<div class="info-item">
<div class="item-icon">🚫</div>
<div class="item-label">时辰忌</div>
<div class="item-value">${formatTabooText(lunarData.taboo.hour.avoids)}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">🌟</div>
<div class="card-title">运势财运</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">🍀</div>
<div class="item-label">今日运势</div>
<div class="item-value">${lunarData.fortune.today_luck}</div>
</div>
<div class="info-item">
<div class="item-icon">💼</div>
<div class="item-label">事业运</div>
<div class="item-value">${lunarData.fortune.career}</div>
</div>
<div class="info-item">
<div class="item-icon">💰</div>
<div class="item-label">财运</div>
<div class="item-value">${lunarData.fortune.money}</div>
</div>
<div class="info-item">
<div class="item-icon">💖</div>
<div class="item-label">感情运</div>
<div class="item-value">${lunarData.fortune.love}</div>
</div>
</div>
</div>
<div class="info-card">
<div class="card-header">
<div class="card-icon">📈</div>
<div class="card-title">年度统计</div>
</div>
<div class="card-content">
<div class="info-item">
<div class="item-icon">📊</div>
<div class="item-label">年度进度</div>
<div class="item-value">${lunarData.stats.percents_formatted.year}</div>
</div>
<div class="info-item">
<div class="item-icon">📅</div>
<div class="item-label">本月进度</div>
<div class="item-value">${lunarData.stats.percents_formatted.month}</div>
</div>
<div class="info-item">
<div class="item-icon">🗓️</div>
<div class="item-label">本周第几天</div>
<div class="item-value">第${lunarData.stats.week_of_month}周</div>
</div>
<div class="info-item">
<div class="item-icon">⏰</div>
<div class="item-label">今日进度</div>
<div class="item-value">${lunarData.stats.percents_formatted.day}</div>
</div>
</div>
</div>
${generateHourlyTaboo(lunarData.taboo.hours)}
`;
showLunarInfo();
}
// 格式化宜忌文本
function formatTabooText(text) {
if (!text) return '无';
return text.replace(/\./g, '、');
}
// 生成十二时辰宜忌
function generateHourlyTaboo(hours) {
if (!hours || hours.length === 0) return '';
const hourCards = hours.map(hour => `
<div class="hour-item">
<div class="hour-name">${hour.hour}</div>
<div class="hour-content">
<div class="hour-recommends">
<span class="hour-label">宜:</span>
<span class="hour-text">${formatTabooText(hour.recommends) || '无'}</span>
</div>
<div class="hour-avoids">
<span class="hour-label">忌:</span>
<span class="hour-text">${formatTabooText(hour.avoids) || '无'}</span>
</div>
</div>
</div>
`).join('');
return `
<div class="info-card hours-card">
<div class="card-header">
<div class="card-icon">⏰</div>
<div class="card-title">十二时辰宜忌</div>
</div>
<div class="card-content">
<div class="hours-grid">
${hourCards}
</div>
</div>
</div>
`;
}
// 更新查询时间
function updateQueryTime() {
const now = new Date();
const timeStr = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
updateTimeElement.textContent = `查询时间: ${timeStr}`;
// 添加成功提示
showSuccessMessage('🌙 农历信息已更新');
}
// 显示成功消息
function showSuccessMessage(message) {
// 移除之前的提示
const existingToast = document.querySelector('.success-toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = 'success-toast';
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
color: rgba(255, 255, 255, 0.95);
padding: 12px 20px;
border-radius: 25px;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.2);
z-index: 1000;
font-weight: 600;
font-size: 0.9em;
animation: glassToastSlide 0.5s ease-out;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
`;
document.body.appendChild(toast);
// 3秒后自动移除
setTimeout(() => {
toast.style.animation = 'glassToastSlideOut 0.5s ease-in forwards';
setTimeout(() => toast.remove(), 500);
}, 3000);
}
// 显示加载状态
function showLoading() {
loadingElement.style.display = 'block';
}
// 隐藏加载状态
function hideLoading() {
loadingElement.style.display = 'none';
}
// 显示农历信息
function showLunarInfo() {
lunarInfoElement.style.display = 'block';
}
// 隐藏农历信息
function hideLunarInfo() {
lunarInfoElement.style.display = 'none';
}
// 显示错误信息
function showError(message = '查询失败,请稍后重试') {
errorMessageElement.style.display = 'block';
const errorContent = errorMessageElement.querySelector('.error-content p');
if (errorContent) {
errorContent.textContent = message;
}
}
// 隐藏错误信息
function hideError() {
errorMessageElement.style.display = 'none';
}
// 添加CSS动画到页面
if (!document.querySelector('#toast-styles')) {
const style = document.createElement('style');
style.id = 'toast-styles';
style.textContent = `
@keyframes glassToastSlide {
from {
opacity: 0;
transform: translateX(100px) scale(0.8);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
@keyframes glassToastSlideOut {
from {
opacity: 1;
transform: translateX(0) scale(1);
}
to {
opacity: 0;
transform: translateX(100px) scale(0.8);
}
}
`;
document.head.appendChild(style);
}
// 键盘快捷键支持
document.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
queryLunarInfo();
}
if (e.key === 'r' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
queryLunarInfo();
}
});

View File

@@ -1,3 +0,0 @@
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,647 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"solar": {
"year": 2025,
"month": 9,
"day": 1,
"hour": 17,
"minute": 58,
"second": 47,
"full": "2025-09-01",
"full_with_time": "2025-09-01 17:58:47",
"week": 1,
"week_desc": "星期一",
"week_desc_short": "一",
"season": 3,
"season_desc": "三季度",
"season_desc_short": "三",
"season_name": "秋",
"season_name_desc": "秋天",
"is_leap_year": false
},
"lunar": {
"year": "乙巳",
"month": "七",
"day": "初十",
"hour": "酉",
"full_with_hour": "农历乙巳年七月初十酉时",
"desc_short": "农历乙巳年七月初十",
"year_desc": "农历乙巳年",
"month_desc": "七月",
"day_desc": "初十",
"hour_desc": "酉时",
"is_leap_month": false
},
"stats": {
"day_of_year": 244,
"week_of_year": 36,
"week_of_month": 1,
"percents": {
"year": 0.665753424657534,
"month": 0.0333333333333333,
"week": 0.142857142857143,
"day": 0.749161909722222
},
"percents_formatted": {
"year": "66.58%",
"month": "3.33%",
"week": "14.29%",
"day": "74.92%"
}
},
"term": {
"today": null,
"stage": {
"name": "处暑",
"position": 10,
"is_jie": false,
"is_qi": true
}
},
"zodiac": {
"year": "蛇",
"month": "鸡",
"day": "鸡",
"hour": "鸡"
},
"sixty_cycle": {
"year": {
"heaven_stem": "乙",
"earth_branch": "巳",
"name": "乙巳年",
"name_short": "乙巳"
},
"month": {
"heaven_stem": "乙",
"earth_branch": "酉",
"name": "乙酉月",
"name_short": "乙酉"
},
"day": {
"heaven_stem": "癸",
"earth_branch": "酉",
"name": "癸酉日",
"name_short": "癸酉"
},
"hour": {
"heaven_stem": "辛",
"earth_branch": "酉",
"name": "辛酉时",
"name_short": "辛酉"
}
},
"legal_holiday": null,
"festival": {
"solar": null,
"lunar": null,
"both_desc": null
},
"phase": {
"name": "宵月",
"position": 10
},
"constellation": {
"name": "处女座",
"name_short": "处女"
},
"taboo": {
"day": {
"recommends": "解除.祭祀.祈福.求嗣.修造.动土.竖柱.上梁.安床.纳畜.盖屋.合脊.起基.入殓.破土.安葬",
"avoids": "出火.嫁娶.开光.进人口.出行.词讼.开市.入宅.移徙.赴任"
},
"hour": {
"hour": "酉时",
"hour_short": "酉",
"avoids": "乘船.造桥",
"recommends": "嫁娶.出行.移徙.入宅.开市.赴任.祈福.安床.开仓.盖屋.修造.求财"
},
"hours": [
{
"hour": "酉时",
"hour_short": "酉",
"recommends": "嫁娶.出行.移徙.入宅.开市.赴任.祈福.安床.开仓.盖屋.修造.求财",
"avoids": "乘船.造桥"
},
{
"hour": "戌时",
"hour_short": "戌",
"recommends": "嫁娶.移徙.安葬.进人口.求财",
"avoids": "出行.赴任.祈福.祭祀.开光.斋醮"
},
{
"hour": "亥时",
"hour_short": "亥",
"recommends": "嫁娶.移徙.交易.入宅.开市.安葬.求嗣.求财",
"avoids": "出行.赴任.动土.祈福.祭祀.修造.开光.斋醮"
},
{
"hour": "子时",
"hour_short": "子",
"recommends": "嫁娶.交易.入宅.开市.祈福.安葬.求嗣.求财",
"avoids": "出行.移徙.赴任.词讼.修造"
},
{
"hour": "丑时",
"hour_short": "丑",
"recommends": "嫁娶.祈福.安葬.祭祀.酬神.求财",
"avoids": "出行.赴任.动土.修造"
},
{
"hour": "寅时",
"hour_short": "寅",
"recommends": "嫁娶.出行.交易.开市.赴任.祈福.安床.祭祀.求嗣.求财",
"avoids": "盖屋.入殓.上梁"
},
{
"hour": "卯时",
"hour_short": "卯",
"recommends": "嫁娶.交易.入宅.开市.祈福.安床.安葬.求嗣.求财",
"avoids": "出行.赴任.修造"
},
{
"hour": "辰时",
"hour_short": "辰",
"recommends": "",
"avoids": "诸事不宜"
},
{
"hour": "巳时",
"hour_short": "巳",
"recommends": "嫁娶.出行.移徙.入宅.开市.祈福.安床.盖屋.祭祀.作灶",
"avoids": "安葬.修造.开光"
},
{
"hour": "午时",
"hour_short": "午",
"recommends": "嫁娶.出行.交易.开市.祈福.安床.求嗣.求财",
"avoids": "赴任.动土.词讼.修造"
},
{
"hour": "未时",
"hour_short": "未",
"recommends": "嫁娶.入宅.祈福.安葬.祭祀.修造.酬神.求财",
"avoids": "出行.赴任"
},
{
"hour": "申时",
"hour_short": "申",
"recommends": "嫁娶.出行.开市.赴任.安葬.求财",
"avoids": "祈福.祭祀.酬神.斋醮"
}
]
},
"julian_day": 2460919.5,
"nayin": {
"year": "覆灯火",
"month": "泉中水",
"day": "剑锋金",
"hour": "石榴木"
},
"baizi": {
"year_baizi": "性格温和,为人正直诚信。",
"day_baizi": "性格温和,为人正直诚信。"
},
"fortune": {
"today_luck": "今日学习运好,适合进修",
"career": "领导能力突出,升职有望",
"money": "偏财运不错,可小试投资",
"love": "感情需要沟通,避免误会"
},
"constants": {
"legal_holiday_list": [
{
"name": "元旦节",
"date": "2025-01-01",
"start": "2025-01-01",
"end": "2025-01-01"
},
{
"name": "春节",
"date": "2025-01-29",
"start": "2025-01-26",
"end": "2025-02-08"
},
{
"name": "清明节",
"date": "2025-04-04",
"start": "2025-04-04",
"end": "2025-04-06"
},
{
"name": "劳动节",
"date": "2025-05-01",
"start": "2025-04-27",
"end": "2025-05-05"
},
{
"name": "端午节",
"date": "2025-05-31",
"start": "2025-05-31",
"end": "2025-06-02"
},
{
"name": "国庆中秋",
"date": "2025-10-01",
"start": "2025-09-28",
"end": "2025-10-11"
}
],
"phase_list": [
{
"name": "朔月",
"lunar_day": 1
},
{
"name": "既朔月",
"lunar_day": 2
},
{
"name": "蛾眉新月",
"lunar_day": 3
},
{
"name": "蛾眉新月",
"lunar_day": 4
},
{
"name": "蛾眉月",
"lunar_day": 5
},
{
"name": "夕月",
"lunar_day": 6
},
{
"name": "上弦月",
"lunar_day": 7
},
{
"name": "上弦月",
"lunar_day": 8
},
{
"name": "九夜月",
"lunar_day": 9
},
{
"name": "宵月",
"lunar_day": 10
},
{
"name": "宵月",
"lunar_day": 11
},
{
"name": "宵月",
"lunar_day": 12
},
{
"name": "渐盈凸月",
"lunar_day": 13
},
{
"name": "小望月",
"lunar_day": 14
},
{
"name": "望月",
"lunar_day": 15
},
{
"name": "既望月",
"lunar_day": 16
},
{
"name": "立待月",
"lunar_day": 17
},
{
"name": "居待月",
"lunar_day": 18
},
{
"name": "寝待月",
"lunar_day": 19
},
{
"name": "更待月",
"lunar_day": 20
},
{
"name": "渐亏凸月",
"lunar_day": 21
},
{
"name": "下弦月",
"lunar_day": 22
},
{
"name": "下弦月",
"lunar_day": 23
},
{
"name": "有明月",
"lunar_day": 24
},
{
"name": "有明月",
"lunar_day": 25
},
{
"name": "蛾眉残月",
"lunar_day": 26
},
{
"name": "蛾眉残月",
"lunar_day": 27
},
{
"name": "残月",
"lunar_day": 28
},
{
"name": "晓月",
"lunar_day": 29
},
{
"name": "晦月",
"lunar_day": 30
}
],
"zodiac_list": [
"鼠",
"牛",
"虎",
"兔",
"龙",
"蛇",
"马",
"羊",
"猴",
"鸡",
"狗",
"猪"
],
"constellation_list": [
{
"name": "白羊",
"desc": "白羊座",
"start": "3月21日",
"end": "4月19日",
"range": "3月21日~4月19日",
"start_month": 3,
"start_day": 21,
"end_month": 4,
"end_day": 19
},
{
"name": "金牛",
"desc": "金牛座",
"start": "4月20日",
"end": "5月20日",
"range": "4月20日~5月20日",
"start_month": 4,
"start_day": 20,
"end_month": 5,
"end_day": 20
},
{
"name": "双子",
"desc": "双子座",
"start": "5月21日",
"end": "6月21日",
"range": "5月21日~6月21日",
"start_month": 5,
"start_day": 21,
"end_month": 6,
"end_day": 21
},
{
"name": "巨蟹",
"desc": "巨蟹座",
"start": "6月22日",
"end": "7月22日",
"range": "6月22日~7月22日",
"start_month": 6,
"start_day": 22,
"end_month": 7,
"end_day": 22
},
{
"name": "狮子",
"desc": "狮子座",
"start": "7月23日",
"end": "8月22日",
"range": "7月23日~8月22日",
"start_month": 7,
"start_day": 23,
"end_month": 8,
"end_day": 22
},
{
"name": "处女",
"desc": "处女座",
"start": "8月23日",
"end": "9月22日",
"range": "8月23日~9月22日",
"start_month": 8,
"start_day": 23,
"end_month": 9,
"end_day": 22
},
{
"name": "天秤",
"desc": "天秤座",
"start": "9月23日",
"end": "10月23日",
"range": "9月23日~10月23日",
"start_month": 9,
"start_day": 23,
"end_month": 10,
"end_day": 23
},
{
"name": "天蝎",
"desc": "天蝎座",
"start": "10月24日",
"end": "11月22日",
"range": "10月24日~11月22日",
"start_month": 10,
"start_day": 24,
"end_month": 11,
"end_day": 22
},
{
"name": "射手",
"desc": "射手座",
"start": "11月23日",
"end": "12月21日",
"range": "11月23日~12月21日",
"start_month": 11,
"start_day": 23,
"end_month": 12,
"end_day": 21
},
{
"name": "摩羯",
"desc": "摩羯座",
"start": "12月22日",
"end": "1月19日",
"range": "12月22日~1月19日",
"start_month": 12,
"start_day": 22,
"end_month": 1,
"end_day": 19
},
{
"name": "水瓶",
"desc": "水瓶座",
"start": "1月20日",
"end": "2月18日",
"range": "1月20日~2月18日",
"start_month": 1,
"start_day": 20,
"end_month": 2,
"end_day": 18
},
{
"name": "双鱼",
"desc": "双鱼座",
"start": "2月19日",
"end": "3月20日",
"range": "2月19日~3月20日",
"start_month": 2,
"start_day": 19,
"end_month": 3,
"end_day": 20
}
],
"heaven_stems": [
"甲",
"乙",
"丙",
"丁",
"戊",
"己",
"庚",
"辛",
"壬",
"癸"
],
"earth_branches": [
"子",
"丑",
"寅",
"卯",
"辰",
"巳",
"午",
"未",
"申",
"酉",
"戌",
"亥"
],
"solar_terms": [
{
"name": "立春",
"desc": "春季开始"
},
{
"name": "雨水",
"desc": "降雨增多"
},
{
"name": "惊蛰",
"desc": "春雷乍响"
},
{
"name": "春分",
"desc": "昼夜等长"
},
{
"name": "清明",
"desc": "天清地明"
},
{
"name": "谷雨",
"desc": "雨生百谷"
},
{
"name": "立夏",
"desc": "夏季开始"
},
{
"name": "小满",
"desc": "麦粒渐满"
},
{
"name": "芒种",
"desc": "麦类收割"
},
{
"name": "夏至",
"desc": "白昼最长"
},
{
"name": "小暑",
"desc": "天气渐热"
},
{
"name": "大暑",
"desc": "一年最热"
},
{
"name": "立秋",
"desc": "秋季开始"
},
{
"name": "处暑",
"desc": "暑热结束"
},
{
"name": "白露",
"desc": "露水增多"
},
{
"name": "秋分",
"desc": "昼夜等长"
},
{
"name": "寒露",
"desc": "露水渐凉"
},
{
"name": "霜降",
"desc": "开始降霜"
},
{
"name": "立冬",
"desc": "冬季开始"
},
{
"name": "小雪",
"desc": "开始降雪"
},
{
"name": "大雪",
"desc": "降雪增多"
},
{
"name": "冬至",
"desc": "白昼最短"
},
{
"name": "小寒",
"desc": "天气渐冷"
},
{
"name": "大寒",
"desc": "一年最冷"
}
]
}
}
}

View File

@@ -1,561 +0,0 @@
/* Reset and Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
overflow-x: hidden;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
position: relative;
}
/* Header Styles */
.header {
text-align: center;
margin-bottom: 40px;
position: relative;
z-index: 2;
}
.header-content {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-radius: 24px;
padding: 40px;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.header-content::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(255, 255, 255, 0.1) 100%);
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%, 100% { transform: translateX(-100%); }
50% { transform: translateX(100%); }
}
.logo {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
margin-bottom: 15px;
}
.logo i {
font-size: 48px;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.logo h1 {
font-size: 42px;
font-weight: 700;
color: white;
text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.subtitle {
font-size: 18px;
color: rgba(255, 255, 255, 0.9);
font-weight: 400;
letter-spacing: 0.5px;
}
/* Floating Shapes */
.header-decoration {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 1;
}
.floating-shapes {
position: relative;
width: 100%;
height: 100%;
}
.shape {
position: absolute;
border-radius: 50%;
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
animation: float 6s ease-in-out infinite;
}
.shape-1 {
width: 80px;
height: 80px;
top: 10%;
left: 10%;
animation-delay: 0s;
}
.shape-2 {
width: 60px;
height: 60px;
top: 20%;
right: 15%;
animation-delay: 2s;
}
.shape-3 {
width: 100px;
height: 100px;
bottom: 15%;
left: 20%;
animation-delay: 4s;
}
@keyframes float {
0%, 100% { transform: translateY(0px) rotate(0deg); }
33% { transform: translateY(-20px) rotate(120deg); }
66% { transform: translateY(10px) rotate(240deg); }
}
/* Input Section */
.input-section {
margin-bottom: 40px;
}
.input-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
}
.input-card:hover {
transform: translateY(-5px);
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.15);
}
.card-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.card-header i {
font-size: 24px;
color: #667eea;
}
.card-header h2 {
font-size: 24px;
font-weight: 600;
color: #333;
}
.input-wrapper {
position: relative;
}
#inputText {
width: 100%;
padding: 20px;
border: 2px solid #e1e5e9;
border-radius: 12px;
font-size: 16px;
font-family: inherit;
resize: vertical;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
}
#inputText:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
background: rgba(255, 255, 255, 0.95);
}
.input-actions {
display: flex;
gap: 15px;
margin-top: 20px;
justify-content: flex-end;
}
/* Button Styles */
.btn {
padding: 12px 24px;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
text-decoration: none;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn:hover::before {
left: 100%;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.8);
color: #666;
border: 1px solid #e1e5e9;
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.95);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
/* Results Section */
.results-section {
opacity: 0;
transform: translateY(30px);
transition: all 0.5s ease;
}
.results-section.show {
opacity: 1;
transform: translateY(0);
}
.results-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 30px;
}
.result-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.result-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4);
}
.result-card:hover {
transform: translateY(-5px);
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.15);
}
.result-card .card-header h3 {
font-size: 20px;
font-weight: 600;
color: #333;
}
.result-items {
display: flex;
flex-direction: column;
gap: 20px;
}
.result-item {
position: relative;
}
.result-item label {
display: block;
font-size: 14px;
font-weight: 500;
color: #666;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.result-value {
display: flex;
align-items: center;
background: rgba(248, 250, 252, 0.8);
border: 1px solid #e1e5e9;
border-radius: 8px;
padding: 12px 16px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
word-break: break-all;
position: relative;
transition: all 0.3s ease;
}
.result-value:hover {
background: rgba(248, 250, 252, 0.95);
border-color: #667eea;
}
.result-value .placeholder {
color: #999;
font-style: italic;
}
.copy-btn {
background: none;
border: none;
color: #667eea;
cursor: pointer;
padding: 8px;
border-radius: 6px;
transition: all 0.3s ease;
margin-left: auto;
flex-shrink: 0;
}
.copy-btn:hover {
background: rgba(102, 126, 234, 0.1);
color: #5a67d8;
}
/* Loading Overlay */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.loading-overlay.show {
opacity: 1;
visibility: visible;
}
.loading-spinner {
text-align: center;
color: white;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Toast Notification */
.toast {
position: fixed;
bottom: 30px;
right: 30px;
background: linear-gradient(135deg, #4ecdc4, #44a08d);
color: white;
padding: 16px 24px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
gap: 10px;
transform: translateX(400px);
transition: all 0.3s ease;
z-index: 1001;
}
.toast.show {
transform: translateX(0);
}
.toast i {
font-size: 18px;
}
/* Responsive Design */
@media (max-width: 768px) {
.container {
padding: 15px;
}
.header-content {
padding: 30px 20px;
}
.logo h1 {
font-size: 32px;
}
.logo i {
font-size: 36px;
}
.results-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.input-actions {
flex-direction: column;
}
.btn {
justify-content: center;
}
.toast {
bottom: 20px;
right: 20px;
left: 20px;
transform: translateY(100px);
}
.toast.show {
transform: translateY(0);
}
}
@media (max-width: 480px) {
.input-card,
.result-card {
padding: 20px;
}
.card-header h2,
.card-header h3 {
font-size: 18px;
}
.result-value {
font-size: 12px;
padding: 10px 12px;
}
}
/* Animation Classes */
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-in {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(-20px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}

View File

@@ -1,212 +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>多功能哈希工具 - Hash Toolkit</title>
<link rel="stylesheet" href="css/style.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div class="container">
<!-- Header Section -->
<header class="header">
<div class="header-content">
<div class="logo">
<i class="fas fa-fingerprint"></i>
<h1>Hash Toolkit</h1>
</div>
<p class="subtitle">多功能哈希、编码与压缩工具</p>
</div>
<div class="header-decoration">
<div class="floating-shapes">
<div class="shape shape-1"></div>
<div class="shape shape-2"></div>
<div class="shape shape-3"></div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="main-content">
<!-- Input Section -->
<section class="input-section">
<div class="input-card">
<div class="card-header">
<i class="fas fa-edit"></i>
<h2>输入内容</h2>
</div>
<div class="input-wrapper">
<textarea
id="inputText"
placeholder="请输入要处理的文本内容...\n支持中文、英文、特殊字符等"
rows="6"
></textarea>
<div class="input-actions">
<button id="clearBtn" class="btn btn-secondary">
<i class="fas fa-trash"></i>
清空
</button>
<button id="processBtn" class="btn btn-primary">
<i class="fas fa-cogs"></i>
开始处理
</button>
</div>
</div>
</div>
</section>
<!-- Results Section -->
<section class="results-section" id="resultsSection">
<div class="results-grid">
<!-- Hash Results -->
<div class="result-card hash-card">
<div class="card-header">
<i class="fas fa-hashtag"></i>
<h3>哈希算法</h3>
</div>
<div class="result-items">
<div class="result-item">
<label>MD5</label>
<div class="result-value" id="md5Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="md5Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>SHA1</label>
<div class="result-value" id="sha1Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="sha1Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>SHA256</label>
<div class="result-value" id="sha256Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="sha256Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>SHA512</label>
<div class="result-value" id="sha512Result">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="sha512Result">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Encoding Results -->
<div class="result-card encoding-card">
<div class="card-header">
<i class="fas fa-code"></i>
<h3>编码转换</h3>
</div>
<div class="result-items">
<div class="result-item">
<label>Base64 编码</label>
<div class="result-value" id="base64EncodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="base64EncodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>Base64 解码</label>
<div class="result-value" id="base64DecodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="base64DecodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>URL 编码</label>
<div class="result-value" id="urlEncodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="urlEncodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>URL 解码</label>
<div class="result-value" id="urlDecodeResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="urlDecodeResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Compression Results -->
<div class="result-card compression-card">
<div class="card-header">
<i class="fas fa-compress-alt"></i>
<h3>压缩算法</h3>
</div>
<div class="result-items">
<div class="result-item">
<label>Gzip 压缩</label>
<div class="result-value" id="gzipCompressResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="gzipCompressResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>Deflate 压缩</label>
<div class="result-value" id="deflateCompressResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="deflateCompressResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="result-item">
<label>Brotli 压缩</label>
<div class="result-value" id="brotliCompressResult">
<span class="placeholder">等待处理...</span>
<button class="copy-btn" data-target="brotliCompressResult">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner">
<div class="spinner"></div>
<p>正在处理中...</p>
</div>
</div>
<!-- Toast Notification -->
<div class="toast" id="toast">
<i class="fas fa-check-circle"></i>
<span id="toastMessage">复制成功!</span>
</div>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,394 +0,0 @@
// API配置
const API_BASE_URL = 'https://60s.api.shumengya.top/v2/hash';
// DOM元素
const elements = {
inputText: document.getElementById('inputText'),
processBtn: document.getElementById('processBtn'),
clearBtn: document.getElementById('clearBtn'),
resultsSection: document.getElementById('resultsSection'),
loadingOverlay: document.getElementById('loadingOverlay'),
toast: document.getElementById('toast'),
toastMessage: document.getElementById('toastMessage')
};
// 结果元素映射
const resultElements = {
md5: document.getElementById('md5Result'),
sha1: document.getElementById('sha1Result'),
sha256: document.getElementById('sha256Result'),
sha512: document.getElementById('sha512Result'),
base64Encode: document.getElementById('base64EncodeResult'),
base64Decode: document.getElementById('base64DecodeResult'),
urlEncode: document.getElementById('urlEncodeResult'),
urlDecode: document.getElementById('urlDecodeResult'),
gzipCompress: document.getElementById('gzipCompressResult'),
deflateCompress: document.getElementById('deflateCompressResult'),
brotliCompress: document.getElementById('brotliCompressResult')
};
// 初始化
document.addEventListener('DOMContentLoaded', function() {
initializeEventListeners();
addInputAnimation();
});
// 事件监听器初始化
function initializeEventListeners() {
// 处理按钮点击
elements.processBtn.addEventListener('click', handleProcess);
// 清空按钮点击
elements.clearBtn.addEventListener('click', handleClear);
// 输入框回车键
elements.inputText.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'Enter') {
handleProcess();
}
});
// 复制按钮事件委托
document.addEventListener('click', function(e) {
if (e.target.closest('.copy-btn')) {
const copyBtn = e.target.closest('.copy-btn');
const targetId = copyBtn.getAttribute('data-target');
const targetElement = document.getElementById(targetId);
const textContent = targetElement.textContent.trim();
if (textContent && textContent !== '等待处理...' && textContent !== '处理失败') {
copyToClipboard(textContent);
}
}
});
// 输入框实时验证
elements.inputText.addEventListener('input', function() {
const hasContent = this.value.trim().length > 0;
elements.processBtn.disabled = !hasContent;
if (hasContent) {
elements.processBtn.classList.remove('disabled');
} else {
elements.processBtn.classList.add('disabled');
}
});
}
// 添加输入动画效果
function addInputAnimation() {
elements.inputText.addEventListener('focus', function() {
this.parentElement.classList.add('focused');
});
elements.inputText.addEventListener('blur', function() {
this.parentElement.classList.remove('focused');
});
}
// 处理主要功能
async function handleProcess() {
const inputValue = elements.inputText.value.trim();
if (!inputValue) {
showToast('请输入要处理的内容', 'error');
return;
}
// 显示加载状态
showLoading(true);
resetResults();
try {
// 调用API
const response = await fetch(`${API_BASE_URL}?content=${encodeURIComponent(inputValue)}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 200 && data.data) {
displayResults(data.data);
showResultsSection();
showToast('处理完成!', 'success');
} else {
throw new Error(data.message || '处理失败');
}
} catch (error) {
console.error('处理错误:', error);
showToast(`处理失败: ${error.message}`, 'error');
displayError();
} finally {
showLoading(false);
}
}
// 显示结果
function displayResults(data) {
try {
// 哈希结果
updateResultElement('md5', data.md5 || '不可用');
// SHA系列
if (data.sha) {
updateResultElement('sha1', data.sha.sha1 || '不可用');
updateResultElement('sha256', data.sha.sha256 || '不可用');
updateResultElement('sha512', data.sha.sha512 || '不可用');
}
// Base64编码
if (data.base64) {
updateResultElement('base64Encode', data.base64.encoded || '不可用');
// BASE64解码只有当输入本身是BASE64格式时才显示解码结果
let base64DecodeResult = data.base64.decoded;
if (!base64DecodeResult) {
// 检查输入是否为有效的BASE64格式
const inputValue = elements.inputText.value.trim();
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
if (base64Regex.test(inputValue) && inputValue.length % 4 === 0) {
try {
base64DecodeResult = atob(inputValue);
} catch (e) {
base64DecodeResult = '解码失败';
}
} else {
base64DecodeResult = '输入非BASE64格式';
}
}
updateResultElement('base64Decode', base64DecodeResult || '不可用');
}
// URL编码
if (data.url) {
updateResultElement('urlEncode', data.url.encoded || '不可用');
updateResultElement('urlDecode', data.url.decoded || '不可用');
}
// 压缩结果(仅显示压缩,不显示解压)
if (data.gzip) {
updateResultElement('gzipCompress', data.gzip.encoded || '不可用');
}
if (data.deflate) {
updateResultElement('deflateCompress', data.deflate.encoded || '不可用');
}
if (data.brotli) {
updateResultElement('brotliCompress', data.brotli.encoded || '不可用');
}
} catch (error) {
console.error('显示结果时出错:', error);
showToast('显示结果时出错', 'error');
}
}
// 更新单个结果元素
function updateResultElement(key, value) {
const element = resultElements[key];
if (element) {
const textSpan = element.querySelector('span') || element;
textSpan.textContent = value;
textSpan.classList.remove('placeholder');
// 添加动画效果
element.classList.add('slide-in');
setTimeout(() => {
element.classList.remove('slide-in');
}, 300);
}
}
// 重置结果
function resetResults() {
Object.values(resultElements).forEach(element => {
if (element) {
const textSpan = element.querySelector('span') || element;
textSpan.textContent = '等待处理...';
textSpan.classList.add('placeholder');
}
});
}
// 显示错误状态
function displayError() {
Object.values(resultElements).forEach(element => {
if (element) {
const textSpan = element.querySelector('span') || element;
textSpan.textContent = '处理失败';
textSpan.classList.add('placeholder');
}
});
}
// 显示结果区域
function showResultsSection() {
elements.resultsSection.classList.add('show');
// 平滑滚动到结果区域
setTimeout(() => {
elements.resultsSection.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}, 100);
}
// 清空功能
function handleClear() {
elements.inputText.value = '';
elements.inputText.focus();
elements.resultsSection.classList.remove('show');
resetResults();
elements.processBtn.disabled = true;
elements.processBtn.classList.add('disabled');
showToast('内容已清空', 'info');
}
// 复制到剪贴板
async function copyToClipboard(text) {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
} else {
// 降级方案
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
document.execCommand('copy');
textArea.remove();
}
showToast('复制成功!', 'success');
} catch (error) {
console.error('复制失败:', error);
showToast('复制失败,请手动复制', 'error');
}
}
// 显示/隐藏加载状态
function showLoading(show) {
if (show) {
elements.loadingOverlay.classList.add('show');
elements.processBtn.disabled = true;
elements.processBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 处理中...';
} else {
elements.loadingOverlay.classList.remove('show');
elements.processBtn.disabled = false;
elements.processBtn.innerHTML = '<i class="fas fa-cogs"></i> 开始处理';
}
}
// 显示提示消息
function showToast(message, type = 'success') {
elements.toastMessage.textContent = message;
// 设置图标和样式
const icon = elements.toast.querySelector('i');
icon.className = getToastIcon(type);
elements.toast.className = `toast ${type}`;
elements.toast.classList.add('show');
// 自动隐藏
setTimeout(() => {
elements.toast.classList.remove('show');
}, 3000);
}
// 获取提示图标
function getToastIcon(type) {
const icons = {
success: 'fas fa-check-circle',
error: 'fas fa-exclamation-circle',
info: 'fas fa-info-circle',
warning: 'fas fa-exclamation-triangle'
};
return icons[type] || icons.success;
}
// 工具函数:防抖
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 工具函数:节流
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// 添加键盘快捷键支持
document.addEventListener('keydown', function(e) {
// Ctrl+Enter 处理
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
if (!elements.processBtn.disabled) {
handleProcess();
}
}
// Escape 清空
if (e.key === 'Escape') {
handleClear();
}
});
// 页面可见性变化处理
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
// 页面隐藏时的处理
console.log('页面已隐藏');
} else {
// 页面显示时的处理
console.log('页面已显示');
}
});
// 错误处理
window.addEventListener('error', function(e) {
console.error('全局错误:', e.error);
showToast('发生未知错误,请刷新页面重试', 'error');
});
// 未处理的Promise拒绝
window.addEventListener('unhandledrejection', function(e) {
console.error('未处理的Promise拒绝:', e.reason);
showToast('网络请求失败,请检查网络连接', 'error');
});
// 导出函数供测试使用
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
handleProcess,
copyToClipboard,
showToast,
debounce,
throttle
};
}

View File

@@ -1,3 +0,0 @@
[
"https://60s.api.shumengya.top"
]

View File

@@ -1,35 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"source": "hello",
"md5": "5d41402abc4b2a76b9719d911017c592",
"sha": {
"sha1": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d",
"sha256": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
"sha512": "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
},
"base64": {
"encoded": "aGVsbG8=",
"decoded": ""
},
"url": {
"encoded": "hello",
"decoded": "hello"
},
"gzip": {
"encoded": "1f8b0800000000000003cb48cdc9c9070086a6103605000000",
"decoded": ""
},
"deflate": {
"encoded": "789ccb48cdc9c90700062c0215",
"decoded": ""
},
"brotli": {
"encoded": "0b028068656c6c6f03",
"decoded": ""
}
}
}
API encoded/decoded encode/decode

View File

@@ -1,145 +0,0 @@
/* 背景样式文件 */
body {
background: linear-gradient(135deg, #e8f5e8 0%, #d4f1d4 25%, #c8ecc8 50%, #b8e6b8 75%, #a8d5ba 100%);
background-attachment: fixed;
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
position: relative;
overflow-x: hidden;
}
/* 背景渐变动画 */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(168, 213, 186, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(107, 183, 123, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(200, 236, 200, 0.1) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 浮动装饰圆点 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, rgba(168, 213, 186, 0.3), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(107, 183, 123, 0.2), transparent),
radial-gradient(1px 1px at 90px 40px, rgba(200, 236, 200, 0.4), transparent),
radial-gradient(1px 1px at 130px 80px, rgba(168, 213, 186, 0.2), transparent),
radial-gradient(2px 2px at 160px 30px, rgba(107, 183, 123, 0.3), transparent);
background-repeat: repeat;
background-size: 200px 100px;
animation: float 20s linear infinite;
pointer-events: none;
z-index: -1;
}
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
100% {
transform: translateY(0px);
}
}
/* 云朵装饰效果 */
.container::before {
content: '';
position: absolute;
top: -50px;
right: -50px;
width: 200px;
height: 100px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50px;
box-shadow:
-30px 20px 0 rgba(255, 255, 255, 0.08),
30px 40px 0 rgba(255, 255, 255, 0.06);
animation: cloudFloat 25s ease-in-out infinite;
pointer-events: none;
z-index: -1;
}
.container::after {
content: '';
position: absolute;
bottom: -30px;
left: -30px;
width: 150px;
height: 80px;
background: rgba(255, 255, 255, 0.08);
border-radius: 40px;
box-shadow:
20px 15px 0 rgba(255, 255, 255, 0.06),
-20px 25px 0 rgba(255, 255, 255, 0.04);
animation: cloudFloat 30s ease-in-out infinite reverse;
pointer-events: none;
z-index: -1;
}
@keyframes cloudFloat {
0%, 100% {
transform: translateX(0px) translateY(0px);
}
25% {
transform: translateX(20px) translateY(-10px);
}
50% {
transform: translateX(-10px) translateY(-20px);
}
75% {
transform: translateX(15px) translateY(-5px);
}
}
/* 响应式背景调整 */
@media (max-width: 768px) {
body::after {
background-size: 150px 75px;
}
.container::before,
.container::after {
display: none;
}
}
@media (max-width: 480px) {
body {
background: linear-gradient(135deg, #e8f5e8 0%, #d4f1d4 50%, #a8d5ba 100%);
animation: none;
}
body::before,
body::after {
display: none;
}
}

View File

@@ -1,412 +0,0 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
line-height: 1.6;
color: #333;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
color: #2d5a3d;
font-size: 2.5rem;
font-weight: 300;
margin-bottom: 10px;
}
/* 搜索区域 */
.search-section {
margin-bottom: 30px;
}
.search-box {
display: flex;
justify-content: center;
gap: 10px;
max-width: 500px;
margin: 0 auto;
}
#cityInput {
flex: 1;
padding: 12px 16px;
border: 2px solid #a8d5ba;
border-radius: 25px;
font-size: 16px;
outline: none;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.9);
}
#cityInput:focus {
border-color: #6bb77b;
box-shadow: 0 0 10px rgba(107, 183, 123, 0.3);
}
#searchBtn {
padding: 12px 24px;
background: linear-gradient(135deg, #6bb77b, #5a9f6a);
color: white;
border: none;
border-radius: 25px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
}
#searchBtn:hover {
background: linear-gradient(135deg, #5a9f6a, #4a8759);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(107, 183, 123, 0.4);
}
/* 加载动画 */
.loading {
text-align: center;
padding: 40px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e8f5e8;
border-top: 4px solid #6bb77b;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 天气容器 */
.weather-container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(168, 213, 186, 0.3);
}
/* 位置信息 */
.location-info {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #e8f5e8;
}
.location-info h2 {
color: #2d5a3d;
font-size: 2rem;
margin-bottom: 5px;
}
.location-info p {
color: #666;
font-size: 14px;
}
/* 当前天气 */
.current-weather {
margin-bottom: 30px;
}
.weather-main {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.temperature {
font-size: 4rem;
font-weight: 300;
color: #2d5a3d;
}
.unit {
font-size: 2rem;
color: #6bb77b;
}
.weather-desc p:first-child {
font-size: 1.5rem;
color: #2d5a3d;
margin-bottom: 5px;
}
.weather-desc p:last-child {
color: #666;
font-size: 14px;
}
/* 更新时间 */
.update-time {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e8f5e8;
color: #666;
font-size: 14px;
}
/* 错误信息 */
.error-message {
text-align: center;
padding: 40px;
background: rgba(255, 107, 107, 0.1);
border-radius: 15px;
border: 1px solid rgba(255, 107, 107, 0.2);
color: #d63031;
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 25px;
}
.header h1 {
font-size: 2.2rem;
}
.temperature {
font-size: 3.5rem;
}
.index-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 电脑端适配 (1024px+) */
@media (min-width: 1024px) {
.container {
padding: 40px;
}
.weather-container {
padding: 40px;
}
.weather-main {
justify-content: space-around;
}
.index-grid {
grid-template-columns: repeat(3, 1fr);
}
.search-box {
max-width: 600px;
}
}
/* 手机端适配 (768px以下) */
@media (max-width: 767px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2rem;
}
.search-box {
flex-direction: column;
gap: 15px;
}
#searchBtn {
padding: 14px 24px;
}
.weather-container {
padding: 20px;
margin: 0 -5px;
}
.weather-main {
flex-direction: column;
text-align: center;
gap: 20px;
}
.temperature {
font-size: 3rem;
}
.index-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.index-item {
padding: 15px;
}
.index-icon {
font-size: 1.5rem;
width: 40px;
margin-right: 12px;
}
}
/* 超小屏幕适配 (480px以下) */
@media (max-width: 480px) {
.container {
padding: 10px;
}
.header h1 {
font-size: 1.8rem;
}
.weather-container {
padding: 15px;
border-radius: 15px;
}
.temperature {
font-size: 2.5rem;
}
}
/* 预报区域样式 */
.forecast-section {
margin-top: 30px;
padding: 20px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.forecast-section h3 {
color: #2d5a3d;
font-size: 1.5rem;
margin-bottom: 20px;
text-align: center;
}
.forecast-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.forecast-item {
background: rgba(255, 255, 255, 0.8);
border-radius: 15px;
padding: 15px;
text-align: center;
border: 1px solid rgba(168, 213, 186, 0.3);
transition: all 0.3s ease;
}
.forecast-item:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.forecast-date {
font-weight: bold;
color: #2d5a3d;
margin-bottom: 10px;
font-size: 1.1rem;
}
.forecast-weather {
margin-bottom: 10px;
}
.weather-day {
color: #666;
font-size: 0.9rem;
}
.weather-night {
color: #888;
font-size: 0.85rem;
}
.forecast-temp {
margin-bottom: 10px;
display: flex;
justify-content: center;
gap: 10px;
}
.temp-high {
color: #ff6b6b;
font-weight: bold;
font-size: 1.2rem;
}
.temp-low {
color: #4ecdc4;
font-size: 1rem;
}
.forecast-wind {
color: #666;
font-size: 0.85rem;
margin-bottom: 5px;
}
.forecast-humidity {
color: #888;
font-size: 0.8rem;
}
/* 预报区域响应式设计 */
@media (max-width: 768px) {
.forecast-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
}
.forecast-item {
padding: 12px;
}
.forecast-date {
font-size: 1rem;
}
.temp-high {
font-size: 1.1rem;
}
}
@media (max-width: 480px) {
.forecast-section {
padding: 15px;
}
.forecast-grid {
grid-template-columns: repeat(2, 1fr);
}
}

View File

@@ -1,66 +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>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>天气预报</h1>
</header>
<div class="search-section">
<div class="search-box">
<input type="text" id="cityInput" placeholder="请输入城市名称(如:北京)" value="北京">
<button id="searchBtn">查询天气</button>
</div>
</div>
<div class="loading" id="loading" style="display: none;">
<div class="spinner"></div>
<p>正在获取天气信息...</p>
</div>
<div class="weather-container" id="weatherContainer" style="display: none;">
<div class="location-info">
<h2 id="locationName"></h2>
<p id="locationDetail"></p>
</div>
<div class="current-weather">
<div class="weather-main">
<div class="temperature">
<span id="temperature"></span>
<span class="unit">°C</span>
</div>
<div class="weather-desc">
<p id="weatherCondition"></p>
<p id="feelsLike"></p>
</div>
</div>
</div>
<div class="forecast-section">
<h3>未来天气预报</h3>
<div class="forecast-grid" id="forecastGrid">
<!-- 预报数据将通过JavaScript动态生成 -->
</div>
</div>
<div class="update-time">
<p>更新时间:<span id="updateTime"></span></p>
</div>
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<p>获取天气信息失败,请稍后重试</p>
</div>
</div>
<script src="js/script.js"></script>
</body>
</html>

View File

@@ -1,211 +0,0 @@
// 天气查询应用
class WeatherApp {
constructor() {
this.apiEndpoints = [
"https://60s.api.shumengya.top/v2/weather/forecast"
];
this.currentEndpointIndex = 0;
this.init();
}
init() {
this.bindEvents();
// 页面加载时自动查询北京天气
this.searchWeather('北京');
}
bindEvents() {
const searchBtn = document.getElementById('searchBtn');
const cityInput = document.getElementById('cityInput');
searchBtn.addEventListener('click', () => {
const city = cityInput.value.trim();
if (city) {
this.searchWeather(city);
} else {
this.showError('请输入城市名称');
}
});
cityInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const city = cityInput.value.trim();
if (city) {
this.searchWeather(city);
} else {
this.showError('请输入城市名称');
}
}
});
// 防止输入框为空时查询
cityInput.addEventListener('input', () => {
const searchBtn = document.getElementById('searchBtn');
searchBtn.disabled = !cityInput.value.trim();
});
}
async searchWeather(city) {
this.showLoading();
for (let i = 0; i < this.apiEndpoints.length; i++) {
try {
const endpoint = this.apiEndpoints[this.currentEndpointIndex];
const response = await this.fetchWeatherData(endpoint, city);
if (response && response.code === 200) {
this.displayWeatherData(response.data);
return;
}
} catch (error) {
console.warn(`API ${this.apiEndpoints[this.currentEndpointIndex]} 请求失败:`, error);
}
// 切换到下一个API端点
this.currentEndpointIndex = (this.currentEndpointIndex + 1) % this.apiEndpoints.length;
}
// 所有API都失败了
this.showError('获取天气信息失败,请检查网络连接或稍后重试');
}
async fetchWeatherData(endpoint, city) {
const url = `${endpoint}?query=${encodeURIComponent(city)}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
timeout: 10000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
displayWeatherData(data) {
const { location, forecast } = data;
// 显示位置信息
document.getElementById('locationName').textContent = location.formatted;
document.getElementById('locationDetail').textContent =
`${location.province} ${location.city} | 邮编: ${location.zip_code}`;
// 使用第一天的预报数据作为当前天气(今天的天气)
const todayWeather = forecast[0];
// 显示当前天气(使用今天的最高温度)
document.getElementById('temperature').textContent = todayWeather.temperature_high;
document.getElementById('weatherCondition').textContent =
`${todayWeather.weather_day}${todayWeather.weather_night}`;
// 体感温度(使用温度范围)
document.getElementById('feelsLike').textContent =
`温度范围 ${todayWeather.temperature_low}°C - ${todayWeather.temperature_high}°C`;
// 显示更新时间(使用当前时间)
document.getElementById('updateTime').textContent =
`${this.formatDate(new Date())} (基于预报数据)`;
// 显示天气预报
this.displayForecast(forecast);
this.showWeatherContainer();
}
displayForecast(forecast) {
const forecastGrid = document.getElementById('forecastGrid');
forecastGrid.innerHTML = '';
forecast.forEach((day, index) => {
const forecastItem = document.createElement('div');
forecastItem.className = 'forecast-item';
forecastItem.innerHTML = `
<div class="forecast-date">${day.date_desc}</div>
<div class="forecast-weather">
<div class="weather-day">${day.weather_day}</div>
<div class="weather-night">${day.weather_night}</div>
</div>
<div class="forecast-temp">
<span class="temp-high">${day.temperature_high}°</span>
<span class="temp-low">${day.temperature_low}°</span>
</div>
<div class="forecast-wind">
<div>${day.wind_direction_day} ${day.wind_strength_day}</div>
</div>
<div class="forecast-humidity">湿度: ${day.humidity}%</div>
`;
forecastGrid.appendChild(forecastItem);
});
}
// 华氏度转摄氏度
fahrenheitToCelsius(fahrenheit) {
const celsius = (fahrenheit - 32) * 5 / 9;
return Math.round(celsius * 10) / 10; // 保留一位小数
}
// 格式化时间
formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
showLoading() {
document.getElementById('loading').style.display = 'block';
document.getElementById('weatherContainer').style.display = 'none';
document.getElementById('errorMessage').style.display = 'none';
}
showWeatherContainer() {
document.getElementById('loading').style.display = 'none';
document.getElementById('weatherContainer').style.display = 'block';
document.getElementById('errorMessage').style.display = 'none';
}
showError(message) {
document.getElementById('loading').style.display = 'none';
document.getElementById('weatherContainer').style.display = 'none';
const errorElement = document.getElementById('errorMessage');
errorElement.style.display = 'block';
errorElement.querySelector('p').textContent = message;
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
new WeatherApp();
});
// 添加页面可见性检测,当页面重新可见时刷新数据
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
const cityInput = document.getElementById('cityInput');
const city = cityInput.value.trim() || '北京';
// 延迟1秒刷新避免频繁请求
setTimeout(() => {
if (window.weatherApp) {
window.weatherApp.searchWeather(city);
}
}, 1000);
}
});
// 将应用实例暴露到全局,方便调试和其他功能调用
window.weatherApp = null;
document.addEventListener('DOMContentLoaded', () => {
window.weatherApp = new WeatherApp();
});

View File

@@ -1,3 +0,0 @@
[
"https://60s.api.shumengya.top/v2/weather/forecast"
]

View File

@@ -1,101 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"location": {
"province": "北京",
"city": "北京",
"town": "北京",
"formatted": "北京",
"location_id": "101010100",
"detail_url": "http://www.weather.com.cn/weather/101010100.shtml",
"is_province": true,
"is_city": false,
"is_town": false,
"area_code": "10",
"zip_code": "100000"
},
"forecast": [
{
"date": "9/4",
"date_desc": "今天",
"weather_day": "多云",
"weather_night": "阴",
"weather_code_day": "01",
"weather_code_night": "02",
"temperature_high": 31,
"temperature_low": 21,
"wind_direction_day": "南风",
"wind_direction_night": "南风",
"wind_strength_day": "\u003C3级",
"wind_strength_night": "\u003C3级",
"rainfall": 96.1,
"humidity": 83
},
{
"date": "9/5",
"date_desc": "星期五",
"weather_day": "中雨",
"weather_night": "多云",
"weather_code_day": "08",
"weather_code_night": "01",
"temperature_high": 23,
"temperature_low": 19,
"wind_direction_day": "西南风",
"wind_direction_night": "北风",
"wind_strength_day": "\u003C3级",
"wind_strength_night": "\u003C3级",
"rainfall": 100,
"humidity": 68
},
{
"date": "9/6",
"date_desc": "星期六",
"weather_day": "多云",
"weather_night": "晴",
"weather_code_day": "01",
"weather_code_night": "00",
"temperature_high": 30,
"temperature_low": 19,
"wind_direction_day": "南风",
"wind_direction_night": "西南风",
"wind_strength_day": "\u003C3级",
"wind_strength_night": "\u003C3级",
"rainfall": 85.2,
"humidity": 36
},
{
"date": "9/7",
"date_desc": "星期日",
"weather_day": "多云",
"weather_night": "晴",
"weather_code_day": "01",
"weather_code_night": "00",
"temperature_high": 29,
"temperature_low": 20,
"wind_direction_day": "北风",
"wind_direction_night": "北风",
"wind_strength_day": "\u003C3级",
"wind_strength_night": "\u003C3级",
"rainfall": 87.3,
"humidity": 27
},
{
"date": "9/8",
"date_desc": "星期一",
"weather_day": "多云",
"weather_night": "多云",
"weather_code_day": "01",
"weather_code_night": "01",
"temperature_high": 28,
"temperature_low": 20,
"wind_direction_day": "南风",
"wind_direction_night": "南风",
"wind_strength_day": "\u003C3级",
"wind_strength_night": "\u003C3级",
"rainfall": 84.8,
"humidity": 41
}
]
}
}

View File

@@ -1,202 +0,0 @@
/* 背景样式文件 - 独立管理背景相关CSS */
/* 主体背景 */
body {
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f0f4c3 100%);
background-attachment: fixed;
background-size: cover;
position: relative;
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 80%, rgba(120, 219, 226, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(168, 230, 207, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(220, 237, 200, 0.1) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* 动态背景效果 */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.05) 50%, transparent 70%),
linear-gradient(-45deg, transparent 30%, rgba(168, 230, 207, 0.05) 50%, transparent 70%);
background-size: 200px 200px;
animation: backgroundMove 20s linear infinite;
pointer-events: none;
z-index: -1;
}
/* 背景动画 */
@keyframes backgroundMove {
0% {
background-position: 0 0, 0 0;
}
100% {
background-position: 200px 200px, -200px -200px;
}
}
/* 容器背景 */
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* 卡片背景增强 */
.weather-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
border: 1px solid rgba(168, 230, 207, 0.3);
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.1),
0 1px 8px rgba(168, 230, 207, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
/* 当前天气区域背景 */
.current-weather {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.8) 0%,
rgba(220, 237, 200, 0.8) 50%,
rgba(240, 244, 195, 0.8) 100%);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow:
0 4px 15px rgba(39, 174, 96, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.6);
}
/* 详情项背景 */
.detail-item {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.1) 0%,
rgba(255, 255, 255, 0.1) 100%);
backdrop-filter: blur(5px);
border: 1px solid rgba(168, 230, 207, 0.2);
box-shadow: 0 2px 8px rgba(39, 174, 96, 0.05);
}
/* 生活指数项背景 */
.index-item {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.05) 0%,
rgba(255, 255, 255, 0.1) 100%);
backdrop-filter: blur(5px);
border: 1px solid rgba(168, 230, 207, 0.15);
box-shadow: 0 2px 10px rgba(39, 174, 96, 0.05);
}
.index-item:hover {
background: linear-gradient(135deg,
rgba(168, 230, 207, 0.1) 0%,
rgba(255, 255, 255, 0.15) 100%);
box-shadow: 0 5px 20px rgba(39, 174, 96, 0.1);
}
/* 输入框背景 */
#cityInput {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border: 2px solid rgba(168, 230, 207, 0.6);
box-shadow: 0 2px 10px rgba(39, 174, 96, 0.1);
}
#cityInput:focus {
background: rgba(255, 255, 255, 0.95);
box-shadow:
0 0 15px rgba(39, 174, 96, 0.2),
0 2px 10px rgba(39, 174, 96, 0.1);
}
/* 按钮背景 */
#searchBtn {
background: linear-gradient(135deg,
#27ae60 0%,
#2ecc71 50%,
#58d68d 100%);
box-shadow:
0 4px 15px rgba(39, 174, 96, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
#searchBtn:hover {
background: linear-gradient(135deg,
#229954 0%,
#27ae60 50%,
#52c370 100%);
box-shadow:
0 6px 20px rgba(39, 174, 96, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
/* 错误消息背景 */
.error-message {
background: linear-gradient(135deg,
rgba(231, 76, 60, 0.1) 0%,
rgba(255, 255, 255, 0.1) 100%);
backdrop-filter: blur(10px);
border: 1px solid rgba(231, 76, 60, 0.2);
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.1);
}
/* 加载状态背景 */
.loading {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border-radius: 15px;
border: 1px solid rgba(168, 230, 207, 0.3);
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.1);
}
/* 移动端背景优化 */
@media (max-width: 767px) {
body::after {
background-size: 100px 100px;
animation-duration: 15s;
}
.container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(3px);
}
.weather-card {
backdrop-filter: blur(10px);
}
}
/* 高性能设备背景增强 */
@media (min-width: 1024px) {
body::before {
background-image:
radial-gradient(circle at 20% 80%, rgba(120, 219, 226, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(168, 230, 207, 0.15) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(220, 237, 200, 0.15) 0%, transparent 50%),
radial-gradient(circle at 60% 70%, rgba(240, 244, 195, 0.1) 0%, transparent 50%);
}
.weather-card {
backdrop-filter: blur(20px);
}
.current-weather {
backdrop-filter: blur(15px);
}
}

View File

@@ -1,143 +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>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="background.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>实时天气</h1>
<div class="search-box">
<input type="text" id="cityInput" placeholder="请输入城市名称..." value="北京">
<button id="searchBtn">查询</button>
</div>
</header>
<main class="main-content">
<div class="loading" id="loading">正在加载天气数据...</div>
<div class="weather-card" id="weatherCard" style="display: none;">
<div class="location-info">
<h2 id="locationName">北京</h2>
<p id="updateTime">更新时间: --</p>
</div>
<div class="current-weather">
<div class="temperature-section">
<span class="temperature" id="temperature">--°C</span>
<span class="weather-desc" id="weatherDesc">--</span>
</div>
<div class="weather-icon" id="weatherIcon">🌤️</div>
</div>
<div class="weather-details">
<div class="detail-item">
<span class="label">体感温度</span>
<span class="value" id="feelsLike">--°C</span>
</div>
<div class="detail-item">
<span class="label">湿度</span>
<span class="value" id="humidity">--%</span>
</div>
<div class="detail-item">
<span class="label">风向</span>
<span class="value" id="windDirection">--</span>
</div>
<div class="detail-item">
<span class="label">风力</span>
<span class="value" id="windStrength">--</span>
</div>
<div class="detail-item">
<span class="label">气压</span>
<span class="value" id="pressure">-- hPa</span>
</div>
<div class="detail-item">
<span class="label">能见度</span>
<span class="value" id="visibility">--</span>
</div>
<div class="detail-item">
<span class="label">空气质量</span>
<span class="value" id="aqi">AQI --</span>
</div>
<div class="detail-item">
<span class="label">PM2.5</span>
<span class="value" id="pm25">-- μg/m³</span>
</div>
</div>
<div class="life-index">
<h3>生活指数</h3>
<div class="index-grid">
<div class="index-item">
<div class="index-icon">🌡️</div>
<div class="index-content">
<div class="index-title">舒适度</div>
<div class="index-level" id="comfortLevel">--</div>
<div class="index-desc" id="comfortDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">👕</div>
<div class="index-content">
<div class="index-title">穿衣指数</div>
<div class="index-level" id="clothingLevel">--</div>
<div class="index-desc" id="clothingDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">☂️</div>
<div class="index-content">
<div class="index-title">雨伞指数</div>
<div class="index-level" id="umbrellaLevel">--</div>
<div class="index-desc" id="umbrellaDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">☀️</div>
<div class="index-content">
<div class="index-title">紫外线</div>
<div class="index-level" id="uvLevel">--</div>
<div class="index-desc" id="uvDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">🚗</div>
<div class="index-content">
<div class="index-title">洗车指数</div>
<div class="index-level" id="carWashLevel">--</div>
<div class="index-desc" id="carWashDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">🎒</div>
<div class="index-content">
<div class="index-title">旅游指数</div>
<div class="index-level" id="travelLevel">--</div>
<div class="index-desc" id="travelDesc">--</div>
</div>
</div>
<div class="index-item">
<div class="index-icon">🏃</div>
<div class="index-content">
<div class="index-title">运动指数</div>
<div class="index-level" id="sportLevel">--</div>
<div class="index-desc" id="sportDesc">--</div>
</div>
</div>
</div>
</div>
</div>
<div class="error-message" id="errorMessage" style="display: none;">
<p>获取天气数据失败,请检查网络连接或稍后重试</p>
</div>
</main>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@@ -1,354 +0,0 @@
// 天气应用主要功能
class WeatherApp {
constructor() {
this.apiUrl = 'https://60s.api.shumengya.top/v2/weather';
this.init();
}
init() {
this.bindEvents();
this.loadWeather('北京'); // 默认加载北京天气
}
bindEvents() {
const searchBtn = document.getElementById('searchBtn');
const cityInput = document.getElementById('cityInput');
// 搜索按钮点击事件
searchBtn.addEventListener('click', () => {
const city = cityInput.value.trim();
if (city) {
this.loadWeather(city);
}
});
// 输入框回车事件
cityInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const city = cityInput.value.trim();
if (city) {
this.loadWeather(city);
}
}
});
}
async loadWeather(city) {
this.showLoading();
try {
const response = await fetch(`${this.apiUrl}?query=${encodeURIComponent(city)}`);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const data = await response.json();
console.log('完整API响应:', data); // 调试日志
if (data.code === 200 && data.data) {
this.displayWeather(data.data);
this.hideLoading();
} else {
throw new Error(data.message || `API返回错误: code=${data.code}`);
}
} catch (error) {
console.error('获取天气数据失败:', error);
console.error('错误详情:', {
message: error.message,
stack: error.stack
});
this.showError(error.message);
this.hideLoading();
}
}
showLoading() {
document.getElementById('loading').style.display = 'block';
document.getElementById('weatherCard').style.display = 'none';
document.getElementById('errorMessage').style.display = 'none';
}
hideLoading() {
document.getElementById('loading').style.display = 'none';
}
showError(message = '获取天气数据失败,请检查网络连接或稍后重试') {
const errorElement = document.getElementById('errorMessage');
const errorText = errorElement.querySelector('p');
if (errorText) {
errorText.textContent = message;
}
errorElement.style.display = 'block';
document.getElementById('weatherCard').style.display = 'none';
}
displayWeather(data) {
console.log('API返回数据:', data); // 调试日志
// 根据实际API结构解构数据
const location = data.location || {};
const realtime = data.realtime || {};
const air_quality = realtime.air_quality || {};
const life_indices = realtime.life_indices || [];
// 显示位置信息
const locationName = location.formatted || location.city || location.name || '未知位置';
document.getElementById('locationName').textContent = locationName;
const updateTime = realtime.updated || '未知时间';
document.getElementById('updateTime').textContent = `更新时间: ${updateTime}`;
// 显示当前天气
const temperature = realtime.temperature !== undefined ? realtime.temperature : '--';
document.getElementById('temperature').textContent = `${temperature}°C`;
const condition = realtime.weather || realtime.weather_desc || '未知';
document.getElementById('weatherDesc').textContent = condition;
document.getElementById('weatherIcon').textContent = this.getWeatherIcon(condition);
// 显示天气详情
const feelsLike = realtime.temperature_feels_like !== undefined ? realtime.temperature_feels_like : temperature;
document.getElementById('feelsLike').textContent = `${feelsLike}°C`;
const humidity = realtime.humidity !== undefined ? realtime.humidity : '--';
document.getElementById('humidity').textContent = `${humidity}%`;
const windDirection = realtime.wind_direction || '--';
document.getElementById('windDirection').textContent = windDirection;
const windPower = realtime.wind_power || realtime.wind_strength || '--';
document.getElementById('windStrength').textContent = windPower;
const pressure = realtime.pressure !== undefined ? realtime.pressure : '--';
document.getElementById('pressure').textContent = `${pressure} hPa`;
document.getElementById('visibility').textContent = '--'; // API中没有能见度数据
const aqi = air_quality.aqi !== undefined ? air_quality.aqi : '--';
document.getElementById('aqi').textContent = `AQI ${aqi}`;
const pm25 = air_quality.pm25 !== undefined ? air_quality.pm25 : '--';
document.getElementById('pm25').textContent = `${pm25} μg/m³`;
// 显示生活指数
if (life_indices && life_indices.length > 0) {
this.displayLifeIndex(life_indices);
} else {
// 如果没有生活指数数据,重置显示
this.resetLifeIndex();
}
// 显示天气卡片
document.getElementById('weatherCard').style.display = 'block';
}
displayLifeIndex(lifeIndices) {
const indexMap = {
comfort: { level: 'comfortLevel', desc: 'comfortDesc' },
clothes: { level: 'clothingLevel', desc: 'clothingDesc' },
umbrella: { level: 'umbrellaLevel', desc: 'umbrellaDesc' },
ultraviolet: { level: 'uvLevel', desc: 'uvDesc' },
carwash: { level: 'carWashLevel', desc: 'carWashDesc' },
tourism: { level: 'travelLevel', desc: 'travelDesc' },
sports: { level: 'sportLevel', desc: 'sportDesc' }
};
// 重置所有指数显示
this.resetLifeIndex();
// 根据新的API数据结构更新生活指数
if (Array.isArray(lifeIndices)) {
lifeIndices.forEach(index => {
if (index && index.key && indexMap[index.key]) {
const { level, desc } = indexMap[index.key];
const levelElement = document.getElementById(level);
const descElement = document.getElementById(desc);
if (levelElement) levelElement.textContent = index.level || '--';
if (descElement) descElement.textContent = index.description || '--';
}
});
}
}
resetLifeIndex() {
const indexMap = {
comfort: { level: 'comfortLevel', desc: 'comfortDesc' },
clothes: { level: 'clothingLevel', desc: 'clothingDesc' },
umbrella: { level: 'umbrellaLevel', desc: 'umbrellaDesc' },
ultraviolet: { level: 'uvLevel', desc: 'uvDesc' },
carwash: { level: 'carWashLevel', desc: 'carWashDesc' },
tourism: { level: 'travelLevel', desc: 'travelDesc' },
sports: { level: 'sportLevel', desc: 'sportDesc' }
};
Object.values(indexMap).forEach(({ level, desc }) => {
const levelElement = document.getElementById(level);
const descElement = document.getElementById(desc);
if (levelElement) levelElement.textContent = '--';
if (descElement) descElement.textContent = '--';
});
}
getWeatherIcon(weather) {
const iconMap = {
'晴': '☀️',
'多云': '⛅',
'阴': '☁️',
'小雨': '🌦️',
'中雨': '🌧️',
'大雨': '⛈️',
'雷阵雨': '⛈️',
'雪': '❄️',
'小雪': '🌨️',
'中雪': '❄️',
'大雪': '❄️',
'雾': '🌫️',
'霾': '😷',
'沙尘暴': '🌪️'
};
// 查找匹配的天气图标
for (const [key, icon] of Object.entries(iconMap)) {
if (weather.includes(key)) {
return icon;
}
}
// 默认图标
return '🌤️';
}
// 获取空气质量等级颜色
getAQIColor(aqi) {
if (aqi <= 50) return '#00e400';
if (aqi <= 100) return '#ffff00';
if (aqi <= 150) return '#ff7e00';
if (aqi <= 200) return '#ff0000';
if (aqi <= 300) return '#8f3f97';
return '#7e0023';
}
// 格式化时间
formatTime(timeString) {
try {
const date = new Date(timeString);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
} catch (error) {
return timeString;
}
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
new WeatherApp();
});
// 添加一些实用的工具函数
const utils = {
// 防抖函数
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
// 节流函数
throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
// 检查网络状态
checkNetworkStatus() {
return navigator.onLine;
},
// 显示提示消息
showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
background: ${type === 'error' ? '#e74c3c' : '#27ae60'};
color: white;
border-radius: 8px;
z-index: 1000;
animation: slideIn 0.3s ease;
`;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 3000);
}
};
// 添加CSS动画
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
document.head.appendChild(style);
// 网络状态监听
window.addEventListener('online', () => {
utils.showToast('网络连接已恢复', 'success');
});
window.addEventListener('offline', () => {
utils.showToast('网络连接已断开', 'error');
});

View File

@@ -1,442 +0,0 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #2c3e50;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
color: #27ae60;
margin-bottom: 20px;
font-weight: 300;
text-shadow: 0 2px 4px rgba(39, 174, 96, 0.1);
}
.search-box {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}
#cityInput {
padding: 12px 16px;
border: 2px solid #a8e6cf;
border-radius: 25px;
font-size: 16px;
outline: none;
background: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
min-width: 200px;
}
#cityInput:focus {
border-color: #27ae60;
box-shadow: 0 0 10px rgba(39, 174, 96, 0.2);
}
#searchBtn {
padding: 12px 24px;
background: linear-gradient(135deg, #27ae60, #2ecc71);
color: white;
border: none;
border-radius: 25px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
}
#searchBtn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
}
#searchBtn:active {
transform: translateY(0);
}
/* 主要内容区域 */
.main-content {
display: flex;
justify-content: center;
align-items: flex-start;
}
.loading {
text-align: center;
font-size: 18px;
color: #27ae60;
padding: 40px;
}
.weather-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(168, 230, 207, 0.3);
width: 100%;
max-width: 800px;
}
/* 位置信息 */
.location-info {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #a8e6cf;
}
.location-info h2 {
font-size: 2rem;
color: #27ae60;
margin-bottom: 10px;
}
.location-info p {
color: #7f8c8d;
font-size: 14px;
}
/* 当前天气 */
.current-weather {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #a8e6cf, #dcedc8);
border-radius: 15px;
}
.temperature-section {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.temperature {
font-size: 3.5rem;
font-weight: 300;
color: #27ae60;
line-height: 1;
}
.weather-desc {
font-size: 1.2rem;
color: #2c3e50;
margin-top: 5px;
}
.weather-icon {
font-size: 4rem;
opacity: 0.8;
}
/* 天气详情 */
.weather-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.detail-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: rgba(168, 230, 207, 0.1);
border-radius: 10px;
border-left: 4px solid #27ae60;
}
.detail-item .label {
color: #7f8c8d;
font-size: 14px;
}
.detail-item .value {
color: #2c3e50;
font-weight: 500;
font-size: 16px;
}
/* 生活指数 */
.life-index h3 {
color: #27ae60;
margin-bottom: 20px;
font-size: 1.5rem;
text-align: center;
}
.index-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.index-item {
display: flex;
align-items: flex-start;
padding: 20px;
background: rgba(168, 230, 207, 0.05);
border-radius: 15px;
border: 1px solid rgba(168, 230, 207, 0.2);
transition: all 0.3s ease;
}
.index-item:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(39, 174, 96, 0.1);
}
.index-icon {
font-size: 2rem;
margin-right: 15px;
opacity: 0.8;
}
.index-content {
flex: 1;
}
.index-title {
font-weight: 500;
color: #2c3e50;
margin-bottom: 5px;
}
.index-level {
color: #27ae60;
font-weight: 600;
margin-bottom: 5px;
}
.index-desc {
color: #7f8c8d;
font-size: 14px;
line-height: 1.4;
}
.error-message {
text-align: center;
padding: 40px;
color: #e74c3c;
background: rgba(231, 76, 60, 0.1);
border-radius: 15px;
border: 1px solid rgba(231, 76, 60, 0.2);
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
padding: 25px;
}
.header h1 {
font-size: 2.8rem;
}
.search-box {
max-width: 500px;
margin: 0 auto 20px;
}
.weather-details {
grid-template-columns: repeat(2, 1fr);
}
.index-grid {
grid-template-columns: repeat(2, 1fr);
}
.current-weather {
padding: 25px;
}
.temperature {
font-size: 4rem;
}
}
/* 电脑端适配 (1024px+) */
@media (min-width: 1024px) {
.container {
padding: 40px;
}
.header h1 {
font-size: 3.2rem;
}
.search-box {
max-width: 600px;
margin: 0 auto 30px;
}
.weather-card {
padding: 40px;
}
.weather-details {
grid-template-columns: repeat(4, 1fr);
}
.index-grid {
grid-template-columns: repeat(3, 1fr);
}
.current-weather {
padding: 30px;
}
.temperature {
font-size: 4.5rem;
}
.index-item {
padding: 25px;
}
}
/* 手机端适配 (优先优化) */
@media (max-width: 767px) {
.container {
padding: 15px;
}
.header h1 {
font-size: 2rem;
margin-bottom: 15px;
}
.search-box {
flex-direction: column;
align-items: center;
gap: 15px;
}
#cityInput {
width: 100%;
max-width: 300px;
font-size: 16px;
}
#searchBtn {
width: 100%;
max-width: 300px;
padding: 14px 24px;
}
.weather-card {
padding: 20px;
margin: 0;
}
.current-weather {
flex-direction: column;
text-align: center;
gap: 20px;
padding: 20px;
}
.temperature {
font-size: 3rem;
}
.weather-icon {
font-size: 3rem;
}
.weather-details {
grid-template-columns: 1fr;
gap: 10px;
}
.detail-item {
padding: 12px;
}
.index-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.index-item {
padding: 15px;
}
.index-icon {
font-size: 1.5rem;
margin-right: 10px;
}
.life-index h3 {
font-size: 1.3rem;
margin-bottom: 15px;
}
.location-info h2 {
font-size: 1.5rem;
}
}
/* 超小屏幕适配 */
@media (max-width: 480px) {
.container {
padding: 10px;
}
.header h1 {
font-size: 1.8rem;
}
.weather-card {
padding: 15px;
}
.temperature {
font-size: 2.5rem;
}
.current-weather {
padding: 15px;
}
.detail-item {
padding: 10px;
font-size: 14px;
}
.index-item {
padding: 12px;
}
.index-desc {
font-size: 13px;
}
}

View File

@@ -1,68 +0,0 @@
{
"code": 200,
"message": "获取成功。数据来自官方/权威源头,以确保稳定与实时。开源地址 https://github.com/vikiboss/60s反馈群 595941841",
"data": {
"location": {
"province": "北京",
"city": "北京",
"town": "北京",
"formatted": "北京",
"location_id": "101010100",
"detail_url": "http://www.weather.com.cn/weather/101010100.shtml",
"is_province": true,
"is_city": false,
"is_town": false,
"area_code": "10",
"zip_code": "100000"
},
"realtime": {
"weather": "晴转雷阵雨",
"weather_desc": "未知",
"weather_code": "d0",
"temperature": 999,
"temperature_feels_like": 75.6,
"humidity": 63,
"wind_direction": "南风",
"wind_strength": "3-4级转\u003C3级",
"wind_speed": "1km/h",
"pressure": 1006,
"visibility": "21km",
"aqi": 41,
"pm25": 41,
"rainfall": 0,
"rainfall_24h": 0,
"updated": "2025-09-08 08:00:00",
"updated_at": "20:30",
"life_index": {
"comfort": {
"level": "舒适",
"desc": "白天温度宜人,风力不大。"
},
"clothing": {
"level": "舒适",
"desc": "建议穿长袖衬衫单裤等服装。"
},
"umbrella": {
"level": "带伞",
"desc": "有降水,短时间出行不必带伞。"
},
"uv": {
"level": "最弱",
"desc": "辐射弱涂擦SPF8-12防晒护肤品。"
},
"car_wash": {
"level": "不宜",
"desc": "有雨,雨水和泥水会弄脏爱车。"
},
"travel": {
"level": "一般",
"desc": "可能有雷暴,外出请尽量避开降雨时段。"
},
"sport": {
"level": "较不宜",
"desc": "有降水,推荐您在室内进行休闲运动。"
}
}
}
}
}

View File

@@ -1,878 +0,0 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
color: #2c3e50;
min-height: 100vh;
overflow-x: hidden;
}
/* 容器布局 */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 头部样式 */
.header {
text-align: center;
margin-bottom: 40px;
padding: 40px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20px;
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
color: white;
}
.header h1 {
font-size: 2.8rem;
font-weight: 700;
margin-bottom: 15px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.subtitle {
font-size: 1.2rem;
opacity: 0.9;
font-weight: 400;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 30px;
}
/* 输入容器 */
.input-container {
background: #ffffff;
border-radius: 20px;
padding: 40px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
border: 1px solid #e8ecf4;
}
.input-group {
margin-bottom: 30px;
}
.input-label {
display: block;
font-size: 1.1rem;
font-weight: 600;
color: #2c3e50;
margin-bottom: 15px;
}
.password-input-wrapper {
position: relative;
margin-bottom: 15px;
}
.password-input {
width: 100%;
padding: 18px 60px 18px 20px;
border: 2px solid #e8ecf4;
border-radius: 12px;
font-size: 1.1rem;
font-family: 'Courier New', monospace;
background: #f8fafc;
transition: all 0.3s ease;
letter-spacing: 1px;
}
.password-input:focus {
outline: none;
border-color: #667eea;
background: #ffffff;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
}
.password-input::placeholder {
color: #94a3b8;
letter-spacing: normal;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.toggle-visibility {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
padding: 8px;
border-radius: 6px;
color: #64748b;
transition: all 0.3s ease;
}
.toggle-visibility:hover {
background: #f1f5f9;
color: #475569;
}
.input-hint {
display: flex;
align-items: center;
gap: 8px;
color: #64748b;
font-size: 0.9rem;
}
.hint-icon {
font-size: 1rem;
}
/* 检测按钮 */
.check-btn {
width: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 18px 32px;
border-radius: 12px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
position: relative;
overflow: hidden;
}
.check-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(102, 126, 234, 0.4);
}
.check-btn:active {
transform: translateY(0);
}
.check-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.btn-icon {
font-size: 1.2rem;
}
/* 结果容器 */
.result-container {
background: #ffffff;
border-radius: 20px;
padding: 40px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
border: 1px solid #e8ecf4;
animation: slideIn 0.5s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 强度概览 */
.strength-overview {
margin-bottom: 40px;
padding: 30px;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-radius: 16px;
border: 1px solid #e2e8f0;
}
.strength-score {
display: flex;
align-items: center;
gap: 30px;
margin-bottom: 25px;
}
.score-circle {
width: 120px;
height: 120px;
border-radius: 50%;
background: conic-gradient(from 0deg, #e2e8f0 0deg, #e2e8f0 360deg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
transition: all 0.5s ease;
}
.score-circle::before {
content: '';
position: absolute;
width: 90px;
height: 90px;
background: white;
border-radius: 50%;
z-index: 1;
}
.score-value {
font-size: 2.5rem;
font-weight: 700;
color: #2c3e50;
z-index: 2;
position: relative;
}
.score-label {
font-size: 0.9rem;
color: #64748b;
z-index: 2;
position: relative;
}
.strength-info {
flex: 1;
}
.strength-level {
font-size: 2rem;
font-weight: 700;
margin-bottom: 8px;
color: #2c3e50;
}
.strength-description {
font-size: 1.1rem;
color: #64748b;
line-height: 1.5;
}
.strength-bar {
margin-top: 20px;
}
.bar-background {
width: 100%;
height: 12px;
background: #e2e8f0;
border-radius: 6px;
overflow: hidden;
margin-bottom: 10px;
}
.bar-fill {
height: 100%;
background: linear-gradient(90deg, #ef4444, #f97316, #eab308, #22c55e);
border-radius: 6px;
width: 0%;
transition: width 0.8s ease;
}
.bar-labels {
display: flex;
justify-content: space-between;
font-size: 0.85rem;
color: #64748b;
}
/* 详细信息网格 */
.details-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 25px;
margin-bottom: 30px;
}
.detail-card {
background: #f8fafc;
border-radius: 16px;
padding: 25px;
border: 1px solid #e2e8f0;
transition: all 0.3s ease;
}
.detail-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.card-icon {
font-size: 1.5rem;
}
.card-header h3 {
font-size: 1.3rem;
font-weight: 600;
color: #2c3e50;
}
.card-content {
display: flex;
flex-direction: column;
gap: 15px;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #e2e8f0;
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
font-weight: 500;
color: #64748b;
}
.info-value {
font-weight: 600;
color: #2c3e50;
}
/* 字符类型分析 */
.character-types {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.char-type {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
background: white;
border-radius: 8px;
border: 1px solid #e2e8f0;
font-size: 0.9rem;
}
.char-type.has-type {
background: #dcfce7;
border-color: #bbf7d0;
color: #166534;
}
.char-type.has-type .type-icon {
color: #22c55e;
}
.type-icon {
font-size: 1rem;
}
.character-issues {
display: flex;
flex-direction: column;
gap: 8px;
}
.issue-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 8px;
color: #dc2626;
font-size: 0.9rem;
}
.issue-item.hidden {
display: none;
}
.issue-icon {
font-size: 1rem;
}
/* 建议和提示区域 */
.recommendations-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 25px;
}
.recommendations-card,
.security-tips-card {
background: #f8fafc;
border-radius: 16px;
padding: 25px;
border: 1px solid #e2e8f0;
}
.recommendations-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 12px;
}
.recommendations-list li {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 12px 16px;
background: white;
border-radius: 10px;
border: 1px solid #e2e8f0;
color: #2c3e50;
line-height: 1.5;
}
.recommendations-list li::before {
content: '💡';
font-size: 1rem;
margin-top: 2px;
flex-shrink: 0;
}
.tips-container {
display: flex;
flex-direction: column;
gap: 12px;
}
.tip-item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 16px;
background: white;
border-radius: 10px;
border: 1px solid #e2e8f0;
color: #2c3e50;
line-height: 1.5;
}
.tip-icon {
font-size: 1rem;
margin-top: 2px;
flex-shrink: 0;
}
/* 错误容器 */
.error-container {
background: #ffffff;
border-radius: 20px;
padding: 50px 40px;
text-align: center;
box-shadow: 0 10px 40px rgba(239, 68, 68, 0.1);
border: 1px solid #fecaca;
}
.error-icon {
font-size: 4rem;
margin-bottom: 20px;
}
.error-container h3 {
color: #dc2626;
margin-bottom: 15px;
font-size: 1.5rem;
font-weight: 600;
}
.error-container p {
color: #64748b;
margin-bottom: 25px;
font-size: 1.1rem;
}
.retry-btn {
background: #dc2626;
color: white;
border: none;
padding: 14px 28px;
border-radius: 10px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1rem;
}
.retry-btn:hover {
background: #b91c1c;
transform: translateY(-1px);
}
/* 页脚 */
.footer {
text-align: center;
padding: 40px 20px;
color: #64748b;
margin-top: 40px;
}
.footer p {
margin-bottom: 8px;
font-size: 1rem;
}
.footer-note {
font-size: 0.9rem;
opacity: 0.8;
}
/* 提示框 */
.toast {
position: fixed;
top: 20px;
right: 20px;
background: #22c55e;
color: white;
padding: 16px 24px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(34, 197, 94, 0.3);
z-index: 1000;
animation: toastSlide 0.3s ease-out;
font-weight: 500;
}
@keyframes toastSlide {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* 强度等级颜色 */
.strength-weak {
color: #dc2626 !important;
}
.strength-medium {
color: #f59e0b !important;
}
.strength-strong {
color: #059669 !important;
}
.strength-very-strong {
color: #047857 !important;
}
/* 分数圆圈颜色 */
.score-weak {
background: conic-gradient(from 0deg, #dc2626 0deg, #dc2626 var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
}
.score-medium {
background: conic-gradient(from 0deg, #f59e0b 0deg, #f59e0b var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
}
.score-strong {
background: conic-gradient(from 0deg, #059669 0deg, #059669 var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
}
.score-very-strong {
background: conic-gradient(from 0deg, #047857 0deg, #047857 var(--score-deg), #e2e8f0 var(--score-deg), #e2e8f0 360deg) !important;
}
/* 平板端适配 (768px - 1024px) */
@media (min-width: 768px) and (max-width: 1024px) {
.container {
max-width: 900px;
padding: 25px;
}
.header h1 {
font-size: 2.4rem;
}
.input-container,
.result-container {
padding: 30px;
}
.details-grid {
grid-template-columns: 1fr;
}
.recommendations-section {
grid-template-columns: 1fr;
}
.strength-score {
flex-direction: column;
text-align: center;
gap: 20px;
}
}
/* 手机端适配 (最大767px) */
@media (max-width: 767px) {
.container {
padding: 15px;
max-width: 100%;
}
.header {
padding: 25px 15px;
margin-bottom: 25px;
}
.header h1 {
font-size: 2rem;
}
.subtitle {
font-size: 1rem;
}
.input-container,
.result-container {
padding: 25px;
border-radius: 15px;
}
.main-content {
gap: 20px;
}
.password-input {
padding: 16px 50px 16px 16px;
font-size: 1rem;
}
.check-btn {
padding: 16px 28px;
font-size: 1rem;
}
.strength-overview {
padding: 20px;
margin-bottom: 25px;
}
.strength-score {
flex-direction: column;
text-align: center;
gap: 20px;
}
.score-circle {
width: 100px;
height: 100px;
}
.score-circle::before {
width: 75px;
height: 75px;
}
.score-value {
font-size: 2rem;
}
.strength-level {
font-size: 1.6rem;
}
.details-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.detail-card {
padding: 20px;
}
.character-types {
grid-template-columns: 1fr;
}
.recommendations-section {
grid-template-columns: 1fr;
gap: 20px;
}
.recommendations-card,
.security-tips-card {
padding: 20px;
}
.toast {
right: 15px;
left: 15px;
top: 15px;
text-align: center;
}
}
/* 小屏手机适配 (最大480px) */
@media (max-width: 480px) {
.container {
padding: 10px;
}
.header {
padding: 20px 10px;
margin-bottom: 20px;
}
.header h1 {
font-size: 1.8rem;
}
.input-container,
.result-container {
padding: 20px;
}
.password-input {
padding: 14px 45px 14px 14px;
font-size: 0.95rem;
}
.check-btn {
padding: 14px 24px;
}
.detail-card {
padding: 15px;
}
.card-header h3 {
font-size: 1.1rem;
}
}
/* 触摸设备优化 */
@media (hover: none) and (pointer: coarse) {
.check-btn,
.retry-btn,
.toggle-visibility {
min-height: 44px;
}
.toggle-visibility {
padding: 12px;
}
}
/* 高对比度模式支持 */
@media (prefers-contrast: high) {
.input-container,
.result-container,
.detail-card {
border: 2px solid #2c3e50;
}
.password-input {
border: 2px solid #2c3e50;
}
}
/* 减少动画模式支持 */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
body {
background: #0f172a;
color: #e2e8f0;
}
.input-container,
.result-container,
.detail-card,
.recommendations-card,
.security-tips-card {
background: #1e293b;
border-color: #334155;
}
.password-input {
background: #334155;
border-color: #475569;
color: #e2e8f0;
}
.password-input:focus {
background: #1e293b;
border-color: #667eea;
}
.strength-overview {
background: #1e293b;
border-color: #334155;
}
.char-type,
.recommendations-list li,
.tip-item {
background: #334155;
border-color: #475569;
color: #e2e8f0;
}
}
/* 打印样式 */
@media print {
.header {
background: none !important;
color: black !important;
box-shadow: none !important;
}
.check-btn,
.retry-btn,
.toggle-visibility,
.toast {
display: none !important;
}
.input-container,
.result-container {
box-shadow: none !important;
border: 1px solid #ccc !important;
}
}

Some files were not shown because too many files have changed in this diff Show More