'commit'
This commit is contained in:
BIN
WeiXin/Screenshots/T6_debug_view.jpg
Normal file
BIN
WeiXin/Screenshots/T6_debug_view.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
BIN
WeiXin/Screenshots/t6_live_shot.jpg
Normal file
BIN
WeiXin/Screenshots/t6_live_shot.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
87
WeiXin/T6_CV_Voice_Debug.py
Normal file
87
WeiXin/T6_CV_Voice_Debug.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# coding=utf-8
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
import cv2
|
||||
import uiautomator2 as u2
|
||||
|
||||
# 添加项目根目录到 sys.path
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if project_root not in sys.path:
|
||||
sys.path.append(project_root)
|
||||
|
||||
from WeiXin.WxUtil import find_all_template_matches
|
||||
|
||||
|
||||
def run_cv_debug():
|
||||
# 1. 拍照 (获取当前设备屏幕)
|
||||
print("📸 正在连接设备并截取屏幕...")
|
||||
try:
|
||||
d = u2.connect()
|
||||
screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots")
|
||||
if not os.path.exists(screenshot_dir):
|
||||
os.makedirs(screenshot_dir)
|
||||
|
||||
image_path = os.path.join(screenshot_dir, "t6_live_shot.jpg")
|
||||
output_path = os.path.join(screenshot_dir, "T6_debug_view.jpg")
|
||||
|
||||
d.screenshot(image_path)
|
||||
print(f"✅ 截图已保存: {image_path}")
|
||||
except Exception as e:
|
||||
print(f"❌ 拍照失败: {e}")
|
||||
return
|
||||
|
||||
print(f"🔍 正在分析实时图片...")
|
||||
|
||||
# 模板路径
|
||||
audio_template = r"d:\dsWork\aiData\WeiXin\Templates\audio.jpg"
|
||||
red_point_template = r"d:\dsWork\aiData\WeiXin\Templates\red_point.jpg"
|
||||
|
||||
if not os.path.exists(audio_template) or not os.path.exists(red_point_template):
|
||||
print("错误: 模板文件不存在")
|
||||
return
|
||||
|
||||
# 2. 识别逻辑
|
||||
audio_matches = find_all_template_matches(image_path, audio_template, threshold=0.8)
|
||||
red_points = find_all_template_matches(image_path, red_point_template, threshold=0.8)
|
||||
|
||||
print(f"发现语音图标数量: {len(audio_matches)}")
|
||||
print(f"发现红点数量: {len(red_points)}")
|
||||
|
||||
# 3. 读取图片并绘制
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
print("错误: 无法读取图片")
|
||||
return
|
||||
|
||||
for ax, ay in audio_matches:
|
||||
# 排除顶部标题栏和底部输入区 (假设 300-1800 为有效区)
|
||||
if ay < 300 or ay > 1800:
|
||||
continue
|
||||
|
||||
sender = "对方" if ax < 500 else "我"
|
||||
is_unread = False
|
||||
|
||||
# 1. 绘制语音图标绿框 (根据模板大小,假设 60x60)
|
||||
cv2.rectangle(img, (int(ax-30), int(ay-30)), (int(ax+30), int(ay+30)), (0, 255, 0), 3)
|
||||
|
||||
# 2. 绘制模拟点击红点 (用户要求用红点标出模拟点击位置)
|
||||
cv2.circle(img, (int(ax), int(ay)), 15, (0, 0, 255), -1)
|
||||
|
||||
# 3. 检查并标记未读红点 (如果存在)
|
||||
for rx, ry in red_points:
|
||||
if abs(ry - ay) < 50 and rx > ax:
|
||||
is_unread = True
|
||||
# 绘制未读红点
|
||||
cv2.circle(img, (int(rx), int(ry)), 12, (0, 0, 255), -1)
|
||||
break
|
||||
|
||||
print(f"标注语音消息: ({ax}, {ay}), 发送者: {sender}, 未读: {is_unread}")
|
||||
|
||||
# 保存结果
|
||||
cv2.imwrite(output_path, img)
|
||||
print(f"✅ 调试图片已保存至: {output_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_cv_debug()
|
||||
@@ -15,7 +15,7 @@ if project_root not in sys.path:
|
||||
|
||||
from Util import Win32Patch
|
||||
|
||||
from WeiXin.WxUtil import perform_input_action, clean_screenshots_dir, is_in_chat_interface, find_template_match, find_all_template_matches
|
||||
from WeiXin.WxUtil import perform_input_action, clean_screenshots_dir, find_template_match, find_all_template_matches
|
||||
from Util.LlmUtil import get_llm_response
|
||||
from Util.EasyOcrKit import EasyOcrKit
|
||||
|
||||
@@ -255,14 +255,8 @@ class ChatBot:
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 0.5 检查是否在聊天界面
|
||||
if not is_in_chat_interface(self.d):
|
||||
logger.warning("📵 当前不在聊天界面,跳过扫描...")
|
||||
await asyncio.sleep(CHECK_INTERVAL)
|
||||
continue
|
||||
|
||||
logger.info("🔍 正在扫描当前界面内容...")
|
||||
|
||||
|
||||
# 1. 截图
|
||||
tmp_shot = os.path.join(self.screenshot_dir, "t6_monitor_temp.jpg")
|
||||
logger.info(f"📸 正在截取屏幕... ({datetime.now().strftime('%H:%M:%S')})")
|
||||
@@ -340,10 +334,10 @@ class ChatBot:
|
||||
click_x, click_y = ax, ay
|
||||
|
||||
# 绘制视觉反馈
|
||||
# 语音图标用绿框
|
||||
# 1. 语音图标用绿框
|
||||
cv2.rectangle(debug_img, (int(ax-30), int(ay-30)), (int(ax+30), int(ay+30)), (0, 255, 0), 3)
|
||||
# 点击位置用红十字
|
||||
cv2.drawMarker(debug_img, (int(click_x), int(click_y)), (0, 0, 255), cv2.MARKER_CROSS, 35, 3)
|
||||
# 2. 点击位置用红点 (用户偏好)
|
||||
cv2.circle(debug_img, (int(click_x), int(click_y)), 15, (0, 0, 255), -1)
|
||||
|
||||
v_msg = {
|
||||
"type": "voice",
|
||||
Binary file not shown.
571
static/Test/地狱大转盘V3.html
Normal file
571
static/Test/地狱大转盘V3.html
Normal file
@@ -0,0 +1,571 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🍲 地狱火锅 V3 - 谁是那块肉? 🍲</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&display=swap');
|
||||
|
||||
:root {
|
||||
--soup-red: #d63031;
|
||||
--soup-dark: #8b0000;
|
||||
--pot-gold: #fdcb6e;
|
||||
--steam-white: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #1a1a1a;
|
||||
color: #fff;
|
||||
font-family: 'ZCOOL KuaiLe', cursive, sans-serif;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
color: var(--pot-gold);
|
||||
text-shadow: 3px 3px 0px #000, 0 0 20px #ff4757;
|
||||
margin-bottom: 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
color: #fab1a0;
|
||||
margin-bottom: 20px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 火锅容器 */
|
||||
.pot-container {
|
||||
position: relative;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
background: radial-gradient(circle, var(--soup-red) 0%, var(--soup-dark) 70%, #000 100%);
|
||||
border: 20px solid #57606f;
|
||||
border-radius: 50%;
|
||||
box-shadow: inset 0 0 50px rgba(0,0,0,0.8), 0 0 30px rgba(214, 48, 49, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 锅中间的隔断(鸳鸯锅效果) */
|
||||
.pot-divider {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
border: 8px solid #57606f;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.pot-inner-rim {
|
||||
position: absolute;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
border: 2px solid rgba(255,255,255,0.1);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 沸腾气泡 */
|
||||
.bubble {
|
||||
position: absolute;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
animation: boil-bubble 2s infinite ease-in;
|
||||
}
|
||||
|
||||
@keyframes boil-bubble {
|
||||
0% { transform: translateY(0) scale(0); opacity: 0.5; }
|
||||
50% { opacity: 0.8; }
|
||||
100% { transform: translateY(-100px) scale(1.5); opacity: 0; }
|
||||
}
|
||||
|
||||
/* 蒸汽效果 */
|
||||
.steam {
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: radial-gradient(circle, var(--steam-white) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
filter: blur(20px);
|
||||
pointer-events: none;
|
||||
z-index: 20;
|
||||
animation: steam-move 4s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes steam-move {
|
||||
0% { transform: translate(-50%, -50%) scale(1); opacity: 0; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { transform: translate(50%, -150%) scale(2); opacity: 0; }
|
||||
}
|
||||
|
||||
/* 食材(学生头像) */
|
||||
.ingredient {
|
||||
position: absolute;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
border: 4px solid var(--pot-gold);
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
||||
transition: transform 0.3s, opacity 0.5s, filter 0.5s;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.ingredient img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.ingredient .label {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: rgba(0,0,0,0.7);
|
||||
color: #fff;
|
||||
font-size: 0.7rem;
|
||||
text-align: center;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
/* 选中状态 */
|
||||
.ingredient.active {
|
||||
transform: scale(1.2);
|
||||
border-color: #fff;
|
||||
box-shadow: 0 0 20px #fff;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
/* 被捞走状态 */
|
||||
.ingredient.removed {
|
||||
transform: translateY(-200px) scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 控制按钮 */
|
||||
#actionBtn {
|
||||
margin-top: 30px;
|
||||
padding: 15px 50px;
|
||||
font-size: 1.8rem;
|
||||
font-family: 'ZCOOL KuaiLe', cursive;
|
||||
background: linear-gradient(to bottom, #ff7675, #d63031);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 10px 0 #8b0000, 0 15px 25px rgba(0,0,0,0.5);
|
||||
transition: all 0.1s;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#actionBtn:active {
|
||||
transform: translateY(5px);
|
||||
box-shadow: 0 5px 0 #8b0000, 0 10px 15px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
#actionBtn:disabled {
|
||||
background: #636e72;
|
||||
box-shadow: 0 5px 0 #2d3436;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 结果展示层 */
|
||||
#resultOverlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.9);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
animation: fadeIn 0.5s forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.result-card {
|
||||
background: #fff;
|
||||
padding: 30px;
|
||||
border-radius: 20px;
|
||||
border: 10px solid var(--pot-gold);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
transform: scale(0);
|
||||
animation: popIn 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0.5s forwards;
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
to { transform: scale(1); }
|
||||
}
|
||||
|
||||
.result-image {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
border-radius: 50%;
|
||||
border: 8px solid var(--soup-red);
|
||||
object-fit: cover;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.result-name {
|
||||
font-size: 3rem;
|
||||
color: var(--soup-red);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 1.5rem;
|
||||
color: #636e72;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#closeBtn {
|
||||
padding: 10px 30px;
|
||||
background: var(--soup-red);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
font-family: 'ZCOOL KuaiLe', cursive;
|
||||
}
|
||||
|
||||
/* 吐槽弹幕 */
|
||||
.danmu {
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
font-size: 1.2rem;
|
||||
color: #fab1a0;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
|
||||
pointer-events: none;
|
||||
z-index: 50;
|
||||
animation: danmu-move 5s linear forwards;
|
||||
}
|
||||
|
||||
@keyframes danmu-move {
|
||||
from { left: 100%; }
|
||||
to { left: -100%; }
|
||||
}
|
||||
|
||||
/* 筷子 */
|
||||
.chopsticks {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 400px;
|
||||
background: #747d8c;
|
||||
border-radius: 5px;
|
||||
z-index: 60;
|
||||
pointer-events: none;
|
||||
transform-origin: top center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chopstick-left { transform: rotate(5deg); margin-left: -15px; }
|
||||
.chopstick-right { transform: rotate(-5deg); margin-left: 15px; }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>🍲 地狱火锅 V3 🍲</h1>
|
||||
<div class="subtitle">—— 今天的你,是什么口味的食材? ——</div>
|
||||
|
||||
<div class="pot-container" id="pot">
|
||||
<div class="pot-divider"></div>
|
||||
<div class="pot-inner-rim"></div>
|
||||
<!-- 气泡和食材将在这里生成 -->
|
||||
</div>
|
||||
|
||||
<div class="chopsticks" id="chopstickLeft"></div>
|
||||
<div class="chopsticks" id="chopstickRight"></div>
|
||||
|
||||
<button id="actionBtn" onclick="startCooking()">开始开涮!</button>
|
||||
|
||||
<div id="resultOverlay">
|
||||
<div class="result-card">
|
||||
<img src="" class="result-image" id="resultImg">
|
||||
<div class="result-name" id="resultName">???</div>
|
||||
<div class="result-title" id="resultTitle">美味食材</div>
|
||||
<button id="closeBtn" onclick="resetPot()">再涮一锅</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const totalStudents = 14;
|
||||
const imagePath = './Result/';
|
||||
const pot = document.getElementById('pot');
|
||||
const ingredients = [];
|
||||
const ingredientNames = [
|
||||
"雪花肥牛", "极品毛肚", "脆皮肠", "手打虾滑", "鲜嫩脑花",
|
||||
"爽口裙带菜", "魔芋丝", "功夫土豆片", "大刀腰片", "鸭血",
|
||||
"千层肚", "贡菜", "水晶粉", "虎皮凤爪"
|
||||
];
|
||||
const funnyComments = [
|
||||
"火开大点!", "别抢我的肉!", "这个同学一看就很嫩", "哎呀烫手!",
|
||||
"锅底不够辣啊", "谁把香菜放进来了?", "救命,我要被煮熟了",
|
||||
"捞我!捞我!", "还没熟呢,再等等", "这是天选之肉"
|
||||
];
|
||||
|
||||
// 音效系统
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
function playSound(freq, type, duration, vol = 0.1) {
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.type = type;
|
||||
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
|
||||
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + duration);
|
||||
}
|
||||
|
||||
function playBoilSound() {
|
||||
// 低频噪音模拟沸腾
|
||||
const bufferSize = audioCtx.sampleRate * 2;
|
||||
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
|
||||
const data = buffer.getChannelData(0);
|
||||
for (let i = 0; i < bufferSize; i++) {
|
||||
data[i] = Math.random() * 2 - 1;
|
||||
}
|
||||
const noise = audioCtx.createBufferSource();
|
||||
noise.buffer = buffer;
|
||||
const filter = audioCtx.createBiquadFilter();
|
||||
filter.type = 'lowpass';
|
||||
filter.frequency.value = 100;
|
||||
const gain = audioCtx.createGain();
|
||||
gain.gain.value = 0.05;
|
||||
noise.connect(filter);
|
||||
filter.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
noise.start();
|
||||
return { noise, gain };
|
||||
}
|
||||
|
||||
// 初始化食材
|
||||
function init() {
|
||||
for (let i = 1; i <= totalStudents; i++) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'ingredient';
|
||||
div.id = `student-${i}`;
|
||||
|
||||
// 随机位置
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const radius = Math.random() * 200;
|
||||
const x = 300 + Math.cos(angle) * radius - 40;
|
||||
const y = 300 + Math.sin(angle) * radius - 40;
|
||||
|
||||
div.style.left = x + 'px';
|
||||
div.style.top = y + 'px';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = `${imagePath}${i}.png`;
|
||||
img.onerror = () => img.src = 'https://via.placeholder.com/80?text=Meat';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'label';
|
||||
label.innerText = ingredientNames[i-1];
|
||||
|
||||
div.appendChild(img);
|
||||
div.appendChild(label);
|
||||
pot.appendChild(div);
|
||||
|
||||
ingredients.push({
|
||||
el: div,
|
||||
x: x,
|
||||
y: y,
|
||||
vx: (Math.random() - 0.5) * 2,
|
||||
vy: (Math.random() - 0.5) * 2,
|
||||
angle: angle,
|
||||
radius: radius,
|
||||
removed: false
|
||||
});
|
||||
}
|
||||
|
||||
// 生成背景气泡
|
||||
for (let i = 0; i < 20; i++) {
|
||||
createBubble();
|
||||
}
|
||||
|
||||
animate();
|
||||
startDanmu();
|
||||
}
|
||||
|
||||
function createBubble() {
|
||||
const b = document.createElement('div');
|
||||
b.className = 'bubble';
|
||||
const size = Math.random() * 20 + 5;
|
||||
b.style.width = size + 'px';
|
||||
b.style.height = size + 'px';
|
||||
b.style.left = Math.random() * 100 + '%';
|
||||
b.style.top = Math.random() * 100 + '%';
|
||||
b.style.animationDelay = Math.random() * 2 + 's';
|
||||
pot.appendChild(b);
|
||||
}
|
||||
|
||||
function createSteam() {
|
||||
const s = document.createElement('div');
|
||||
s.className = 'steam';
|
||||
s.style.left = Math.random() * 100 + '%';
|
||||
s.style.top = Math.random() * 100 + '%';
|
||||
pot.appendChild(s);
|
||||
setTimeout(() => s.remove(), 4000);
|
||||
}
|
||||
|
||||
function startDanmu() {
|
||||
setInterval(() => {
|
||||
if (Math.random() > 0.3) return;
|
||||
const d = document.createElement('div');
|
||||
d.className = 'danmu';
|
||||
d.innerText = funnyComments[Math.floor(Math.random() * funnyComments.length)];
|
||||
d.style.top = (Math.random() * 60 + 20) + '%';
|
||||
document.body.appendChild(d);
|
||||
setTimeout(() => d.remove(), 5000);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
let isCooking = false;
|
||||
let speedMultiplier = 1;
|
||||
|
||||
function animate() {
|
||||
ingredients.forEach(item => {
|
||||
if (item.removed) return;
|
||||
|
||||
item.x += item.vx * speedMultiplier;
|
||||
item.y += item.vy * speedMultiplier;
|
||||
|
||||
// 边界碰撞(锅是圆的)
|
||||
const dx = item.x + 40 - 300;
|
||||
const dy = item.y + 40 - 300;
|
||||
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||
|
||||
if (dist > 250) {
|
||||
const nx = dx / dist;
|
||||
const ny = dy / dist;
|
||||
// 反射向量
|
||||
const dot = item.vx * nx + item.vy * ny;
|
||||
item.vx = (item.vx - 2 * dot * nx) * 0.9;
|
||||
item.vy = (item.vy - 2 * dot * ny) * 0.9;
|
||||
// 修正位置防止卡住
|
||||
item.x = 300 + nx * 249 - 40;
|
||||
item.y = 300 + ny * 249 - 40;
|
||||
}
|
||||
|
||||
item.el.style.left = item.x + 'px';
|
||||
item.el.style.top = item.y + 'px';
|
||||
});
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
async function startCooking() {
|
||||
if (isCooking) return;
|
||||
isCooking = true;
|
||||
document.getElementById('actionBtn').disabled = true;
|
||||
document.getElementById('actionBtn').innerText = "火热开涮中...";
|
||||
|
||||
if (audioCtx.state === 'suspended') audioCtx.resume();
|
||||
const boilSound = playBoilSound();
|
||||
|
||||
// 加速阶段
|
||||
for (let i = 0; i < 20; i++) {
|
||||
speedMultiplier += 0.2;
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
if (i % 5 === 0) playSound(200 + i * 10, 'sine', 0.1);
|
||||
}
|
||||
|
||||
// 陆续淘汰阶段
|
||||
const activeIngredients = ingredients.filter(i => !i.removed);
|
||||
const totalSteps = activeIngredients.length - 1;
|
||||
|
||||
for (let i = 0; i < totalSteps; i++) {
|
||||
const currentActive = ingredients.filter(i => !i.removed);
|
||||
const randomIndex = Math.floor(Math.random() * currentActive.length);
|
||||
const toRemove = currentActive[randomIndex];
|
||||
|
||||
toRemove.removed = true;
|
||||
toRemove.el.classList.add('removed');
|
||||
|
||||
playSound(150, 'triangle', 0.3, 0.2);
|
||||
createSteam();
|
||||
|
||||
// 间隔时间逐渐拉长
|
||||
await new Promise(r => setTimeout(r, 300 + i * 100));
|
||||
}
|
||||
|
||||
// 最后的胜者
|
||||
const winner = ingredients.find(i => !i.removed);
|
||||
showResult(winner);
|
||||
|
||||
// 停止声音
|
||||
boilSound.gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1);
|
||||
setTimeout(() => boilSound.noise.stop(), 1000);
|
||||
}
|
||||
|
||||
function showResult(winner) {
|
||||
const overlay = document.getElementById('resultOverlay');
|
||||
const img = document.getElementById('resultImg');
|
||||
const name = document.getElementById('resultName');
|
||||
const title = document.getElementById('resultTitle');
|
||||
|
||||
const studentIdx = winner.el.id.split('-')[1];
|
||||
img.src = `${imagePath}${studentIdx}.png`;
|
||||
name.innerText = `天选之子: ${ingredientNames[studentIdx-1]}`;
|
||||
|
||||
const titles = ["真香!", "肉质鲜美!", "有点辣手", "极品食材", "嚼劲十足", "入口即化"];
|
||||
title.innerText = titles[Math.floor(Math.random() * titles.length)];
|
||||
|
||||
overlay.style.display = 'flex';
|
||||
playSound(523.25, 'square', 0.5, 0.2); // C5
|
||||
setTimeout(() => playSound(659.25, 'square', 0.5, 0.2), 150); // E5
|
||||
setTimeout(() => playSound(783.99, 'square', 1.0, 0.2), 300); // G5
|
||||
}
|
||||
|
||||
function resetPot() {
|
||||
document.getElementById('resultOverlay').style.display = 'none';
|
||||
document.getElementById('actionBtn').disabled = false;
|
||||
document.getElementById('actionBtn').innerText = "开始开涮!";
|
||||
isCooking = false;
|
||||
speedMultiplier = 1;
|
||||
|
||||
ingredients.forEach(item => {
|
||||
item.removed = false;
|
||||
item.el.classList.remove('removed');
|
||||
// 随机重置位置
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const radius = Math.random() * 200;
|
||||
item.x = 300 + Math.cos(angle) * radius - 40;
|
||||
item.y = 300 + Math.sin(angle) * radius - 40;
|
||||
item.vx = (Math.random() - 0.5) * 2;
|
||||
item.vy = (Math.random() - 0.5) * 2;
|
||||
});
|
||||
}
|
||||
|
||||
window.onload = init;
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
459
static/Test/地狱大转盘V4.html
Normal file
459
static/Test/地狱大转盘V4.html
Normal file
@@ -0,0 +1,459 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🎰 地狱扭蛋机 V4 - 非酋还是欧皇? 🎰</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&display=swap');
|
||||
|
||||
:root {
|
||||
--machine-red: #ff4757;
|
||||
--machine-dark: #c0392b;
|
||||
--gold: #f1c40f;
|
||||
--glass: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #2f3542;
|
||||
color: #fff;
|
||||
font-family: 'ZCOOL KuaiLe', cursive, sans-serif;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
color: var(--gold);
|
||||
text-shadow: 2px 2px 0px #000;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 扭蛋机外壳 */
|
||||
.gacha-machine {
|
||||
position: relative;
|
||||
width: 400px;
|
||||
height: 600px;
|
||||
background: var(--machine-red);
|
||||
border: 10px solid #2f3542;
|
||||
border-radius: 50px 50px 20px 20px;
|
||||
box-shadow: 0 20px 0 var(--machine-dark), 0 30px 50px rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 透明视窗 */
|
||||
.glass-dome {
|
||||
position: relative;
|
||||
width: 340px;
|
||||
height: 300px;
|
||||
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.4) 0%, transparent 60%),
|
||||
rgba(255,255,255,0.1);
|
||||
border: 8px solid #333;
|
||||
border-radius: 40px;
|
||||
margin-top: 30px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 30px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
/* 扭蛋球 */
|
||||
.ball {
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
box-shadow: inset -5px -5px 10px rgba(0,0,0,0.3), 0 5px 10px rgba(0,0,0,0.2);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.ball img {
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 1px solid rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
/* 扭蛋机下半部 */
|
||||
.controls {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
/* 旋转摇杆 */
|
||||
.handle-container {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: #dfe6e9;
|
||||
border-radius: 50%;
|
||||
border: 8px solid #636e72;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
.handle-bar {
|
||||
width: 80%;
|
||||
height: 20px;
|
||||
background: #636e72;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.handle-bar::before, .handle-bar::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #2d3436;
|
||||
border-radius: 50%;
|
||||
top: -5px;
|
||||
}
|
||||
.handle-bar::before { left: -10px; }
|
||||
.handle-bar::after { right: -10px; }
|
||||
|
||||
.handle-container.spinning {
|
||||
animation: spin-handle 0.5s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin-handle {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 出蛋口 */
|
||||
.exit-chute {
|
||||
width: 100px;
|
||||
height: 60px;
|
||||
background: #2d3436;
|
||||
border-radius: 10px 10px 0 0;
|
||||
position: relative;
|
||||
box-shadow: inset 0 10px 10px rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
.exit-chute::after {
|
||||
content: 'PUSH';
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
color: #636e72;
|
||||
}
|
||||
|
||||
/* 结果弹窗 */
|
||||
#resultOverlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.95);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
perspective: 1000px;
|
||||
width: 300px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform-style: preserve-3d;
|
||||
transition: transform 0.8s;
|
||||
animation: card-reveal 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
|
||||
}
|
||||
|
||||
@keyframes card-reveal {
|
||||
0% { transform: scale(0) rotateY(0deg); }
|
||||
100% { transform: scale(1) rotateY(720deg); }
|
||||
}
|
||||
|
||||
.card-front {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #f1c40f 0%, #f39c12 100%);
|
||||
border: 10px solid #fff;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 50px rgba(241, 196, 15, 0.5);
|
||||
}
|
||||
|
||||
.rarity-badge {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: #ff4757;
|
||||
color: white;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
transform: rotate(15deg);
|
||||
box-shadow: 2px 2px 5px rgba(0,0,0,0.3);
|
||||
animation: flash 0.5s infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
from { opacity: 0.8; transform: rotate(15deg) scale(1); }
|
||||
to { opacity: 1; transform: rotate(15deg) scale(1.1); }
|
||||
}
|
||||
|
||||
.student-photo {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 15px;
|
||||
border: 5px solid #fff;
|
||||
object-fit: cover;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-size: 2.5rem;
|
||||
color: #2d3436;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.student-desc {
|
||||
font-size: 1rem;
|
||||
color: #636e72;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#closeBtn {
|
||||
margin-top: 30px;
|
||||
padding: 10px 40px;
|
||||
background: var(--gold);
|
||||
color: #2d3436;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
font-family: 'ZCOOL KuaiLe', cursive;
|
||||
}
|
||||
|
||||
/* 提示文字 */
|
||||
.hint {
|
||||
margin-top: 20px;
|
||||
color: #fab1a0;
|
||||
font-size: 1.2rem;
|
||||
animation: bounce 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-5px); }
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>🎰 地狱扭蛋机 V4 🎰</h1>
|
||||
|
||||
<div class="gacha-machine">
|
||||
<div class="glass-dome" id="glassDome">
|
||||
<!-- 扭蛋球将由JS生成 -->
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="handle-container" id="handle" onclick="drawGacha()">
|
||||
<div class="handle-bar"></div>
|
||||
</div>
|
||||
|
||||
<div class="exit-chute"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hint">点击摇杆,抽取今日“欧皇”!</div>
|
||||
|
||||
<div id="resultOverlay">
|
||||
<div class="card-container">
|
||||
<div class="card">
|
||||
<div class="card-front">
|
||||
<div class="rarity-badge" id="rarity">SSR</div>
|
||||
<img src="" class="student-photo" id="resultImg">
|
||||
<div class="student-name" id="resultName">???</div>
|
||||
<div class="student-desc" id="resultDesc">这是一个极其稀有的灵魂</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="closeBtn" onclick="hideResult()">收下并返回</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const totalStudents = 14;
|
||||
const imagePath = './Result/';
|
||||
const ballColors = ['#ff7675', '#74b9ff', '#55efc4', '#ffeaa7', '#a29bfe', '#fd79a8'];
|
||||
const balls = [];
|
||||
const glassDome = document.getElementById('glassDome');
|
||||
|
||||
const rarities = [
|
||||
{ name: 'N', color: '#bdc3c7', desc: '平平无奇的小可爱' },
|
||||
{ name: 'R', color: '#3498db', desc: '有点意思的灵魂' },
|
||||
{ name: 'SR', color: '#9b59b6', desc: '闪闪发光的候选人' },
|
||||
{ name: 'SSR', color: '#f1c40f', desc: '传说中的天选之子' },
|
||||
{ name: 'UR', color: '#e74c3c', desc: '绝无仅有的地狱之皇' }
|
||||
];
|
||||
|
||||
// 音效系统
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
function playSound(freq, type, duration, vol = 0.1) {
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.type = type;
|
||||
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
|
||||
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + duration);
|
||||
}
|
||||
|
||||
function init() {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const ball = document.createElement('div');
|
||||
ball.className = 'ball';
|
||||
|
||||
// 添加学生头像
|
||||
const img = document.createElement('img');
|
||||
const studentId = (i % totalStudents) + 1;
|
||||
img.src = `${imagePath}${studentId}.png`;
|
||||
img.onerror = () => img.src = 'https://via.placeholder.com/60?text=?';
|
||||
ball.appendChild(img);
|
||||
|
||||
const x = Math.random() * 280;
|
||||
const y = Math.random() * 240;
|
||||
ball.style.left = x + 'px';
|
||||
ball.style.top = y + 'px';
|
||||
|
||||
// 为扭蛋壳添加随机透明色
|
||||
const color = ballColors[Math.floor(Math.random() * ballColors.length)];
|
||||
ball.style.backgroundColor = color + '66'; // 增加透明度
|
||||
ball.style.borderColor = color;
|
||||
|
||||
glassDome.appendChild(ball);
|
||||
|
||||
balls.push({
|
||||
el: ball,
|
||||
x: x,
|
||||
y: y,
|
||||
vx: (Math.random() - 0.5) * 6,
|
||||
vy: (Math.random() - 0.5) * 6
|
||||
});
|
||||
}
|
||||
animate();
|
||||
}
|
||||
|
||||
let isSpinning = false;
|
||||
let shakeIntensity = 0;
|
||||
|
||||
function animate() {
|
||||
balls.forEach(ball => {
|
||||
ball.x += ball.vx * (1 + shakeIntensity);
|
||||
ball.y += ball.vy * (1 + shakeIntensity);
|
||||
|
||||
if (ball.x < 0 || ball.x > 280) ball.vx *= -1;
|
||||
if (ball.y < 0 || ball.y > 240) ball.vy *= -1;
|
||||
|
||||
ball.el.style.left = ball.x + 'px';
|
||||
ball.el.style.top = ball.y + 'px';
|
||||
});
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
async function drawGacha() {
|
||||
if (isSpinning) return;
|
||||
isSpinning = true;
|
||||
if (audioCtx.state === 'suspended') audioCtx.resume();
|
||||
|
||||
const handle = document.getElementById('handle');
|
||||
handle.classList.add('spinning');
|
||||
|
||||
// 震动效果
|
||||
shakeIntensity = 5;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
playSound(100 + i * 50, 'square', 0.1, 0.05);
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
}
|
||||
|
||||
shakeIntensity = 0;
|
||||
handle.classList.remove('spinning');
|
||||
|
||||
// 抽取结果
|
||||
const studentIdx = Math.floor(Math.random() * totalStudents) + 1;
|
||||
const rarityIdx = Math.random() < 0.1 ? 4 : (Math.random() < 0.2 ? 3 : (Math.random() < 0.3 ? 2 : (Math.random() < 0.4 ? 1 : 0)));
|
||||
|
||||
showResult(studentIdx, rarityIdx);
|
||||
}
|
||||
|
||||
function showResult(idx, rarityIdx) {
|
||||
const overlay = document.getElementById('resultOverlay');
|
||||
const img = document.getElementById('resultImg');
|
||||
const name = document.getElementById('resultName');
|
||||
const rarity = document.getElementById('rarity');
|
||||
const desc = document.getElementById('resultDesc');
|
||||
const rarityData = rarities[rarityIdx];
|
||||
|
||||
img.src = `${imagePath}${idx}.png`;
|
||||
img.onerror = () => img.src = 'https://via.placeholder.com/200?text=Student';
|
||||
name.innerText = `同学 ${idx}`; // 这里可以根据需要替换成真实姓名列表
|
||||
rarity.innerText = rarityData.name;
|
||||
rarity.style.backgroundColor = rarityData.color;
|
||||
desc.innerText = rarityData.desc;
|
||||
|
||||
overlay.style.display = 'flex';
|
||||
|
||||
// 胜利音效
|
||||
if (rarityIdx >= 3) {
|
||||
// SSR/UR 华丽音效
|
||||
playSound(523, 'sawtooth', 0.5, 0.1);
|
||||
setTimeout(() => playSound(659, 'sawtooth', 0.5, 0.1), 100);
|
||||
setTimeout(() => playSound(783, 'sawtooth', 0.8, 0.1), 200);
|
||||
} else {
|
||||
playSound(440, 'sine', 0.5, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
function hideResult() {
|
||||
document.getElementById('resultOverlay').style.display = 'none';
|
||||
isSpinning = false;
|
||||
}
|
||||
|
||||
window.onload = init;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
435
static/Test/地狱大转盘V5.html
Normal file
435
static/Test/地狱大转盘V5.html
Normal file
@@ -0,0 +1,435 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🔥 地狱炙烤 V8 - 谁是最后那块肉? 🔥</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&display=swap');
|
||||
|
||||
:root {
|
||||
--fire-orange: #ff9f43;
|
||||
--fire-red: #ee5253;
|
||||
--charcoal: #2d3436;
|
||||
--danger-bg: #1a1a1a;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--danger-bg);
|
||||
color: #fff;
|
||||
font-family: 'ZCOOL KuaiLe', cursive, sans-serif;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
color: var(--fire-orange);
|
||||
text-shadow: 0 0 20px var(--fire-red);
|
||||
margin-bottom: 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 心理压力:温度计 */
|
||||
.temp-gauge {
|
||||
width: 600px;
|
||||
height: 30px;
|
||||
background: #000;
|
||||
border: 3px solid #555;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#tempFill {
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #ff9f43, #ee5253, #ff0000);
|
||||
transition: width 0.5s linear;
|
||||
}
|
||||
|
||||
.temp-label {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
}
|
||||
|
||||
/* 烤炉区域 */
|
||||
.grill-area {
|
||||
position: relative;
|
||||
width: 800px;
|
||||
height: 500px;
|
||||
background: radial-gradient(circle at center, #3d3d3d 0%, #1a1a1a 100%);
|
||||
border: 15px solid #444;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: inset 0 0 100px rgba(255, 69, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 铁丝网效果 */
|
||||
.grill-area::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-image:
|
||||
linear-gradient(90deg, rgba(255,255,255,0.05) 1px, transparent 1px),
|
||||
linear-gradient(rgba(255,255,255,0.05) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 学生头像(肉块) */
|
||||
.student-meat {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 15px;
|
||||
border: 4px solid #555;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.student-meat img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 危险状态:颤抖 */
|
||||
.student-meat.danger {
|
||||
animation: shake 0.1s infinite;
|
||||
border-color: var(--fire-red);
|
||||
box-shadow: 0 0 20px var(--fire-red);
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0% { transform: translate(1px, 1px) rotate(0deg); }
|
||||
20% { transform: translate(-1px, -2px) rotate(-1deg); }
|
||||
40% { transform: translate(-3px, 0px) rotate(1deg); }
|
||||
60% { transform: translate(3px, 2px) rotate(0deg); }
|
||||
80% { transform: translate(1px, -1px) rotate(1deg); }
|
||||
100% { transform: translate(-1px, 2px) rotate(-1deg); }
|
||||
}
|
||||
|
||||
/* 淘汰状态:被烤焦 */
|
||||
.student-meat.charred {
|
||||
filter: grayscale(1) brightness(0.2) blur(2px);
|
||||
transform: scale(0.8);
|
||||
opacity: 0.5;
|
||||
border-color: #000;
|
||||
}
|
||||
|
||||
/* 幸存状态:飞走 */
|
||||
.student-meat.saved {
|
||||
transform: translateY(-500px) rotate(360deg) scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 火焰特效层 */
|
||||
.fire-layer {
|
||||
position: absolute;
|
||||
bottom: -50px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background: url('https://www.transparenttextures.com/patterns/carbon-fibre.png'); /* 占位背景 */
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
opacity: 0;
|
||||
transition: opacity 2s;
|
||||
}
|
||||
|
||||
/* 最终结果弹窗 */
|
||||
#resultOverlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.95);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.roasted-card {
|
||||
position: relative;
|
||||
background: #000;
|
||||
padding: 40px;
|
||||
border-radius: 30px;
|
||||
border: 10px solid var(--fire-orange);
|
||||
text-align: center;
|
||||
box-shadow: 0 0 100px var(--fire-red);
|
||||
animation: emerge 1s forwards;
|
||||
}
|
||||
|
||||
@keyframes emerge {
|
||||
from { transform: scale(0) rotate(-20deg); }
|
||||
to { transform: scale(1) rotate(0deg); }
|
||||
}
|
||||
|
||||
.roasted-img {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border-radius: 20px;
|
||||
border: 5px solid #fff;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.roasted-title {
|
||||
font-size: 4rem;
|
||||
color: var(--fire-red);
|
||||
margin: 20px 0;
|
||||
text-shadow: 0 0 10px #fff;
|
||||
}
|
||||
|
||||
/* 控制按钮 */
|
||||
#startBtn {
|
||||
margin-top: 30px;
|
||||
padding: 15px 60px;
|
||||
font-size: 2rem;
|
||||
background: linear-gradient(to bottom, #ff9f43, #ee5253);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 10px 0 #8b0000;
|
||||
transition: all 0.1s;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#startBtn:active {
|
||||
transform: translateY(5px);
|
||||
box-shadow: 0 5px 0 #8b0000;
|
||||
}
|
||||
|
||||
#startBtn:disabled {
|
||||
background: #555;
|
||||
box-shadow: 0 5px 0 #222;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 吐槽气泡 */
|
||||
.bubble {
|
||||
position: absolute;
|
||||
background: white;
|
||||
color: black;
|
||||
padding: 5px 10px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.9rem;
|
||||
top: -40px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
animation: bounceIn 0.3s forwards;
|
||||
}
|
||||
|
||||
@keyframes bounceIn {
|
||||
from { transform: translateX(-50%) scale(0); }
|
||||
to { transform: translateX(-50%) scale(1); }
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>🔥 地狱炙烤 V8 🔥</h1>
|
||||
|
||||
<div class="temp-gauge">
|
||||
<div id="tempFill"></div>
|
||||
<div class="temp-label">当前温度: <span id="tempVal">25</span>°C</div>
|
||||
</div>
|
||||
|
||||
<div class="grill-area" id="grill">
|
||||
<!-- 学生肉块将由JS生成 -->
|
||||
</div>
|
||||
|
||||
<button id="startBtn" onclick="startRoasting()">开始加火!</button>
|
||||
|
||||
<div id="resultOverlay">
|
||||
<div class="roasted-card">
|
||||
<div style="font-size: 2rem; color: #ff9f43;">恭喜!这块肉烤得恰到好处!</div>
|
||||
<img src="" class="roasted-img" id="resultImg">
|
||||
<div class="roasted-title" id="resultName">???</div>
|
||||
<button id="startBtn" onclick="location.reload()" style="background: #27ae60; box-shadow: 0 10px 0 #1e8449;">再烤一锅</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const totalStudents = 14;
|
||||
const imagePath = './Result/';
|
||||
const grill = document.getElementById('grill');
|
||||
const students = [];
|
||||
const scaryQuotes = ["好烫!好烫!", "救命啊!", "我不想被烤!", "火太大了!", "轮到谁了?", "鸭梨山大...", "感觉要熟了"];
|
||||
|
||||
// 音效系统
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
function playSound(freq, type, duration, vol = 0.1) {
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.type = type;
|
||||
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
|
||||
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + duration);
|
||||
}
|
||||
|
||||
// 心跳/节奏音
|
||||
let heartbeatInterval;
|
||||
function startHeartbeat(bpm) {
|
||||
clearInterval(heartbeatInterval);
|
||||
heartbeatInterval = setInterval(() => {
|
||||
playSound(150, 'sine', 0.1, 0.2);
|
||||
setTimeout(() => playSound(100, 'sine', 0.1, 0.2), 150);
|
||||
}, (60 / bpm) * 1000);
|
||||
}
|
||||
|
||||
function init() {
|
||||
for (let i = 1; i <= totalStudents; i++) {
|
||||
const meat = document.createElement('div');
|
||||
meat.className = 'student-meat';
|
||||
meat.id = `meat-${i}`;
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = `${imagePath}${i}.png`;
|
||||
img.onerror = () => img.src = 'https://via.placeholder.com/120?text=Meat';
|
||||
|
||||
meat.appendChild(img);
|
||||
grill.appendChild(meat);
|
||||
|
||||
students.push({
|
||||
el: meat,
|
||||
id: i,
|
||||
status: 'raw' // raw, charred, saved, roasted
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let temperature = 25;
|
||||
let isRoasting = false;
|
||||
|
||||
async function startRoasting() {
|
||||
if (isRoasting) return;
|
||||
isRoasting = true;
|
||||
document.getElementById('startBtn').disabled = true;
|
||||
if (audioCtx.state === 'suspended') audioCtx.resume();
|
||||
|
||||
// 1. 心理压力启动:心跳声和温度上升
|
||||
let bpm = 60;
|
||||
startHeartbeat(bpm);
|
||||
|
||||
// 温度上升动画
|
||||
const tempFill = document.getElementById('tempFill');
|
||||
const tempVal = document.getElementById('tempVal');
|
||||
|
||||
// 陆续排除过程
|
||||
const aliveList = [...students];
|
||||
|
||||
while (aliveList.length > 1) {
|
||||
// 提高温度和心跳频率
|
||||
temperature += Math.floor(Math.random() * 50) + 20;
|
||||
bpm += 20;
|
||||
startHeartbeat(bpm);
|
||||
|
||||
tempFill.style.width = (temperature / 1000 * 100) + '%';
|
||||
tempVal.innerText = temperature;
|
||||
|
||||
// 让所有人颤抖
|
||||
aliveList.forEach(s => s.el.classList.add('danger'));
|
||||
|
||||
// 随机吐槽
|
||||
if (Math.random() > 0.5) {
|
||||
const randomS = aliveList[Math.floor(Math.random() * aliveList.length)];
|
||||
showBubble(randomS.el, scaryQuotes[Math.floor(Math.random() * scaryQuotes.length)]);
|
||||
}
|
||||
|
||||
// 心理压力:随机停顿时间(越来越长)
|
||||
const waitTime = 1000 + (14 - aliveList.length) * 300;
|
||||
await new Promise(r => setTimeout(r, waitTime));
|
||||
|
||||
// 排除一个:要么被救走,要么被烤焦(视觉上只是排除)
|
||||
const removeIdx = Math.floor(Math.random() * aliveList.length);
|
||||
const removed = aliveList.splice(removeIdx, 1)[0];
|
||||
|
||||
if (Math.random() > 0.5) {
|
||||
removed.el.classList.remove('danger');
|
||||
removed.el.classList.add('saved');
|
||||
playSound(440, 'sine', 0.5, 0.1); // 逃生成功音
|
||||
} else {
|
||||
removed.el.classList.remove('danger');
|
||||
removed.el.classList.add('charred');
|
||||
playSound(200, 'sawtooth', 0.5, 0.1); // 被淘汰音
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 最终时刻:只剩一人
|
||||
const winner = aliveList[0];
|
||||
temperature = 999;
|
||||
tempFill.style.width = '100%';
|
||||
tempVal.innerText = temperature;
|
||||
|
||||
// 最后的挣扎
|
||||
winner.el.style.transform = 'scale(1.5)';
|
||||
showBubble(winner.el, "不!!!!!!");
|
||||
startHeartbeat(240); // 极速心跳
|
||||
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
|
||||
// 终极火焰
|
||||
clearInterval(heartbeatInterval);
|
||||
playSound(100, 'noise', 2, 0.3); // 喷火声
|
||||
|
||||
showResult(winner.id);
|
||||
}
|
||||
|
||||
function showBubble(parent, text) {
|
||||
const b = document.createElement('div');
|
||||
b.className = 'bubble';
|
||||
b.innerText = text;
|
||||
parent.appendChild(b);
|
||||
setTimeout(() => b.remove(), 1000);
|
||||
}
|
||||
|
||||
function showResult(id) {
|
||||
const overlay = document.getElementById('resultOverlay');
|
||||
const img = document.getElementById('resultImg');
|
||||
const name = document.getElementById('resultName');
|
||||
|
||||
img.src = `${imagePath}${id}.png`;
|
||||
name.innerText = `玩家 ${id} 熟透了!`;
|
||||
overlay.style.display = 'flex';
|
||||
|
||||
// 最终音效
|
||||
playSound(523, 'sawtooth', 0.5);
|
||||
setTimeout(() => playSound(392, 'sawtooth', 0.5), 200);
|
||||
setTimeout(() => playSound(523, 'sawtooth', 1.0), 400);
|
||||
}
|
||||
|
||||
window.onload = init;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
556
static/Test/地狱大转盘V6.html
Normal file
556
static/Test/地狱大转盘V6.html
Normal file
@@ -0,0 +1,556 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🧸 地狱抓娃娃机 V6 - 抓到谁就是谁! 🧸</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&display=swap');
|
||||
|
||||
:root {
|
||||
--machine-pink: #fd79a8;
|
||||
--machine-purple: #a29bfe;
|
||||
--neon-blue: #00d2ff;
|
||||
--neon-pink: #ff00ff;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #2d3436;
|
||||
color: #fff;
|
||||
font-family: 'ZCOOL KuaiLe', cursive, sans-serif;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 10px var(--neon-blue), 0 0 20px var(--neon-pink);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 娃娃机主体 */
|
||||
.claw-machine {
|
||||
position: relative;
|
||||
width: 500px;
|
||||
height: 650px;
|
||||
background: #34495e;
|
||||
border: 15px solid #2c3e50;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 50px rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 玻璃展示区 */
|
||||
.display-area {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
background: linear-gradient(to bottom, #1e272e, #2f3542);
|
||||
overflow: hidden;
|
||||
border-bottom: 10px solid #2c3e50;
|
||||
}
|
||||
|
||||
/* 天花板轨道 */
|
||||
.rail {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50px;
|
||||
right: 50px;
|
||||
height: 10px;
|
||||
background: #7f8c8d;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* 爪子组件 */
|
||||
.claw-assembly {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50px;
|
||||
width: 60px;
|
||||
transition: left 0.5s ease;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 激光射线 */
|
||||
.laser-beam {
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
left: 50%;
|
||||
width: 4px;
|
||||
height: 0;
|
||||
background: linear-gradient(to bottom, #ff0000, transparent);
|
||||
transform: translateX(-50%);
|
||||
z-index: 90;
|
||||
display: none;
|
||||
box-shadow: 0 0 10px #ff0000;
|
||||
}
|
||||
|
||||
.laser-beam.firing {
|
||||
display: block;
|
||||
height: 400px;
|
||||
animation: laserFlash 0.1s infinite;
|
||||
}
|
||||
|
||||
@keyframes laserFlash {
|
||||
0% { opacity: 0.8; width: 4px; }
|
||||
50% { opacity: 1; width: 8px; }
|
||||
100% { opacity: 0.8; width: 4px; }
|
||||
}
|
||||
|
||||
.claw-string {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 4px;
|
||||
height: 50px;
|
||||
background: #bdc3c7;
|
||||
transform: translateX(-50%);
|
||||
transition: height 1s ease;
|
||||
}
|
||||
|
||||
.claw-head {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 50%;
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
background: #ecf0f1;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 10px 10px 0 0;
|
||||
transition: top 1s ease;
|
||||
}
|
||||
|
||||
.claw-arm {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 30px;
|
||||
background: #ecf0f1;
|
||||
top: 20px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.arm-left { left: 0; transform-origin: top right; transform: rotate(30deg); }
|
||||
.arm-right { right: 0; transform-origin: top left; transform: rotate(-30deg); }
|
||||
|
||||
.claw-assembly.grabbing .arm-left { transform: rotate(0deg); }
|
||||
.claw-assembly.grabbing .arm-right { transform: rotate(0deg); }
|
||||
|
||||
/* 娃娃 (学生) */
|
||||
.doll {
|
||||
position: absolute;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
border-radius: 15px;
|
||||
border: 4px solid var(--neon-blue);
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
.doll img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 娃娃消失动画 */
|
||||
.doll.eliminated {
|
||||
animation: explode 0.5s forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes explode {
|
||||
0% { transform: scale(1) rotate(0deg); filter: brightness(1); }
|
||||
50% { transform: scale(1.5) rotate(20deg); filter: brightness(5) saturate(2); opacity: 1; }
|
||||
100% { transform: scale(0) rotate(-45deg); opacity: 0; }
|
||||
}
|
||||
|
||||
/* 瞄准红点 */
|
||||
.target-dot {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #ff0000;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 10px #ff0000;
|
||||
display: none;
|
||||
z-index: 80;
|
||||
animation: blink 0.2s infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* 操作面板 */
|
||||
.control-panel {
|
||||
flex-grow: 1;
|
||||
background: var(--machine-pink);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.joystick-base {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: #c0392b;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
box-shadow: inset 0 5px 10px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.joystick-stick {
|
||||
width: 20px;
|
||||
height: 60px;
|
||||
background: #7f8c8d;
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
left: 30px;
|
||||
border-radius: 10px;
|
||||
transform-origin: bottom center;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.joystick-ball {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #e74c3c;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
left: -10px;
|
||||
box-shadow: 0 5px 0 #c0392b;
|
||||
}
|
||||
|
||||
.btn-grab {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: #f1c40f;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
color: #2d3436;
|
||||
font-family: 'ZCOOL KuaiLe', cursive;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 0 #d4ac0d, 0 10px 20px rgba(0,0,0,0.3);
|
||||
transition: all 0.1s;
|
||||
}
|
||||
|
||||
.btn-grab:active {
|
||||
transform: translateY(4px);
|
||||
box-shadow: 0 4px 0 #d4ac0d;
|
||||
}
|
||||
|
||||
.btn-grab:disabled {
|
||||
background: #bdc3c7;
|
||||
box-shadow: 0 4px 0 #95a5a6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 结果展示 */
|
||||
#resultOverlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.9);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.prize-card {
|
||||
background: #fff;
|
||||
padding: 30px;
|
||||
border-radius: 20px;
|
||||
border: 10px solid var(--neon-blue);
|
||||
text-align: center;
|
||||
animation: bounceIn 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@keyframes bounceIn {
|
||||
0% { transform: scale(0); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.prize-img {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
border-radius: 15px;
|
||||
object-fit: cover;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.prize-name {
|
||||
font-size: 3rem;
|
||||
color: #2d3436;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#closeBtn {
|
||||
padding: 10px 40px;
|
||||
background: var(--neon-blue);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
font-family: 'ZCOOL KuaiLe', cursive;
|
||||
}
|
||||
|
||||
/* 装饰霓虹灯 */
|
||||
.neon-light {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
background: var(--neon-blue);
|
||||
box-shadow: 0 0 10px var(--neon-blue);
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>🧸 地狱抓娃娃机 V6 🧸</h1>
|
||||
|
||||
<div class="claw-machine">
|
||||
<div class="neon-light"></div>
|
||||
<div class="display-area" id="displayArea">
|
||||
<div class="rail"></div>
|
||||
<div class="target-dot" id="targetDot"></div>
|
||||
|
||||
<!-- 爪子 -->
|
||||
<div class="claw-assembly" id="claw">
|
||||
<div class="laser-beam" id="laserBeam"></div>
|
||||
<div class="claw-string" id="clawString"></div>
|
||||
<div class="claw-head" id="clawHead">
|
||||
<div class="claw-arm arm-left"></div>
|
||||
<div class="claw-arm arm-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 娃娃将由JS生成 -->
|
||||
</div>
|
||||
|
||||
<div class="control-panel">
|
||||
<div class="joystick-base">
|
||||
<div class="joystick-stick" id="joystick">
|
||||
<div class="joystick-ball"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-grab" id="grabBtn" onclick="startGrabbing()">抓取!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resultOverlay">
|
||||
<div class="prize-card">
|
||||
<div style="font-size: 1.5rem; color: var(--neon-blue); margin-bottom: 10px;">恭喜抓到幸运儿!</div>
|
||||
<img src="" class="prize-img" id="resultImg">
|
||||
<div class="prize-name" id="resultName">???</div>
|
||||
<button id="closeBtn" onclick="resetMachine()">放回去重抓</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const totalStudents = 14;
|
||||
const imagePath = './Result/';
|
||||
const displayArea = document.getElementById('displayArea');
|
||||
const dolls = [];
|
||||
let isGrabbing = false;
|
||||
|
||||
// 音效系统
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
function playSound(freq, type, duration, vol = 0.1) {
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.type = type;
|
||||
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
|
||||
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + duration);
|
||||
}
|
||||
|
||||
function init() {
|
||||
for (let i = 1; i <= totalStudents; i++) {
|
||||
const doll = document.createElement('div');
|
||||
doll.className = 'doll';
|
||||
doll.id = `doll-${i}`;
|
||||
|
||||
// 底部堆叠
|
||||
const x = 50 + Math.random() * 330;
|
||||
const y = 350 + (Math.random() - 0.5) * 30;
|
||||
doll.style.left = x + 'px';
|
||||
doll.style.top = y + 'px';
|
||||
doll.style.transform = `rotate(${(Math.random()-0.5)*30}deg)`;
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = `${imagePath}${i}.png`;
|
||||
img.onerror = () => img.src = 'https://via.placeholder.com/70?text=Doll';
|
||||
|
||||
doll.appendChild(img);
|
||||
displayArea.appendChild(doll);
|
||||
|
||||
dolls.push({
|
||||
el: doll,
|
||||
id: i,
|
||||
x: x,
|
||||
y: y
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function startGrabbing() {
|
||||
if (isGrabbing) return;
|
||||
isGrabbing = true;
|
||||
document.getElementById('grabBtn').disabled = true;
|
||||
if (audioCtx.state === 'suspended') audioCtx.resume();
|
||||
|
||||
const claw = document.getElementById('claw');
|
||||
const clawString = document.getElementById('clawString');
|
||||
const clawHead = document.getElementById('clawHead');
|
||||
const joystick = document.getElementById('joystick');
|
||||
const laser = document.getElementById('laserBeam');
|
||||
const targetDot = document.getElementById('targetDot');
|
||||
|
||||
// 1. 左右扫描 (制造恐慌)
|
||||
const scanTimes = 3;
|
||||
for(let i=0; i<scanTimes; i++) {
|
||||
const scanX = 50 + Math.random() * 330;
|
||||
claw.style.left = scanX + 'px';
|
||||
joystick.style.transform = scanX > 200 ? 'rotate(20deg)' : 'rotate(-20deg)';
|
||||
playSound(200 + i*100, 'sine', 0.3, 0.05);
|
||||
|
||||
// 扫描过程中随机开火
|
||||
if (Math.random() > 0.4) {
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
await fireLaser(scanX);
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 600));
|
||||
}
|
||||
|
||||
// 2. 最终锁定目标
|
||||
const aliveDolls = dolls.filter(d => !d.eliminated);
|
||||
if (aliveDolls.length === 0) {
|
||||
alert("所有人都被消灭了... 地狱之门重新开启!");
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
const targetDoll = aliveDolls[Math.floor(Math.random() * aliveDolls.length)];
|
||||
const targetX = targetDoll.x;
|
||||
|
||||
claw.style.left = targetX + 'px';
|
||||
joystick.style.transform = targetX > 200 ? 'rotate(20deg)' : 'rotate(-20deg)';
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
joystick.style.transform = 'rotate(0deg)';
|
||||
|
||||
// 3. 下降
|
||||
clawString.style.height = '350px';
|
||||
clawHead.style.top = '350px';
|
||||
playSound(300, 'sine', 1, 0.05);
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
|
||||
// 4. 抓取动作
|
||||
claw.classList.add('grabbing');
|
||||
playSound(150, 'square', 0.2, 0.1);
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
|
||||
// 5. 上升
|
||||
targetDoll.el.style.transition = 'top 1s ease, left 1s ease';
|
||||
targetDoll.el.style.zIndex = '150';
|
||||
targetDoll.el.style.top = '50px';
|
||||
targetDoll.el.style.left = (targetX - 5) + 'px';
|
||||
|
||||
clawString.style.height = '50px';
|
||||
clawHead.style.top = '50px';
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
|
||||
// 6. 展示结果
|
||||
showResult(targetDoll.id);
|
||||
}
|
||||
|
||||
async function fireLaser(x) {
|
||||
const laser = document.getElementById('laserBeam');
|
||||
const targetDot = document.getElementById('targetDot');
|
||||
|
||||
// 显示红点
|
||||
targetDot.style.left = (x + 25) + 'px';
|
||||
targetDot.style.top = '380px';
|
||||
targetDot.style.display = 'block';
|
||||
playSound(800, 'sine', 0.1, 0.2);
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
|
||||
// 发射激光
|
||||
laser.classList.add('firing');
|
||||
playSound(100, 'sawtooth', 0.3, 0.3);
|
||||
|
||||
// 判定是否击中
|
||||
const hitDoll = dolls.find(d => !d.eliminated && Math.abs(d.x - x) < 40);
|
||||
if (hitDoll) {
|
||||
hitDoll.eliminated = true;
|
||||
hitDoll.el.classList.add('eliminated');
|
||||
playSound(50, 'noise', 0.5, 0.5);
|
||||
}
|
||||
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
laser.classList.remove('firing');
|
||||
targetDot.style.display = 'none';
|
||||
}
|
||||
|
||||
function showResult(id) {
|
||||
const overlay = document.getElementById('resultOverlay');
|
||||
const img = document.getElementById('resultImg');
|
||||
const name = document.getElementById('resultName');
|
||||
|
||||
img.src = `${imagePath}${id}.png`;
|
||||
name.innerText = `玩家 ${id} 号`;
|
||||
overlay.style.display = 'flex';
|
||||
|
||||
playSound(523, 'sine', 0.3);
|
||||
setTimeout(() => playSound(659, 'sine', 0.3), 150);
|
||||
setTimeout(() => playSound(783, 'sine', 0.5), 300);
|
||||
}
|
||||
|
||||
function resetMachine() {
|
||||
document.getElementById('resultOverlay').style.display = 'none';
|
||||
document.getElementById('grabBtn').disabled = false;
|
||||
isGrabbing = false;
|
||||
|
||||
const claw = document.getElementById('claw');
|
||||
claw.classList.remove('grabbing');
|
||||
|
||||
// 重置娃娃位置
|
||||
dolls.forEach(d => {
|
||||
d.el.style.transition = 'none';
|
||||
d.el.style.zIndex = '10';
|
||||
d.el.classList.remove('eliminated');
|
||||
d.eliminated = false;
|
||||
const x = 50 + Math.random() * 330;
|
||||
const y = 350 + (Math.random() - 0.5) * 30;
|
||||
d.x = x; d.y = y;
|
||||
d.el.style.left = x + 'px';
|
||||
d.el.style.top = y + 'px';
|
||||
d.el.style.transform = `rotate(${(Math.random()-0.5)*30}deg) scale(1)`;
|
||||
d.el.style.opacity = '1';
|
||||
});
|
||||
}
|
||||
|
||||
window.onload = init;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user