Files
aiData/static/Test/地狱大转盘V1.html
HuangHai 1f6a48004b 'commit'
2026-01-25 18:17:37 +08:00

730 lines
25 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔥 地狱大转盘 - 谁是那个倒霉蛋? 🔥</title>
<style>
:root {
--primary: #ff4757;
--secondary: #2f3542;
--bg: #1e272e;
--text: #f1f2f6;
}
body {
margin: 0;
padding: 0;
background-color: var(--bg);
color: var(--text);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
user-select: none;
}
h1 {
margin-bottom: 20px;
text-shadow: 0 0 10px #ff6b6b;
font-size: 2.5rem;
letter-spacing: 2px;
text-align: center;
}
/* 转盘容器 */
.wheel-container {
position: relative;
width: 500px;
height: 500px;
filter: drop-shadow(0 0 20px rgba(0,0,0,0.5));
}
canvas#wheelCanvas {
width: 100%;
height: 100%;
transform: rotate(0deg);
transition: transform 5s cubic-bezier(0.25, 0.1, 0.25, 1);
}
/* 指针 */
.pointer {
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 60px;
background: #ff4757;
clip-path: polygon(50% 100%, 0 0, 100% 0);
z-index: 10;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5));
}
/* 按钮 */
button#spinBtn {
margin-top: 40px;
padding: 15px 50px;
font-size: 1.5rem;
background: linear-gradient(45deg, #ff4757, #ff6b6b);
border: none;
border-radius: 50px;
color: white;
cursor: pointer;
box-shadow: 0 5px 15px rgba(255, 71, 87, 0.4);
transition: transform 0.1s, box-shadow 0.1s;
outline: none;
font-weight: bold;
}
button#spinBtn:active {
transform: scale(0.95);
box-shadow: 0 2px 5px rgba(255, 71, 87, 0.4);
}
button#spinBtn:disabled {
background: #57606f;
cursor: not-allowed;
box-shadow: none;
}
/* 结果模态框 - 地狱火烤风格 */
#resultModal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s;
}
#resultModal.active {
opacity: 1;
pointer-events: all;
}
.lucky-title {
font-size: 2rem;
color: #ced6e0;
margin-bottom: 20px;
opacity: 0;
transform: translateY(-20px);
transition: all 0.5s 0.5s;
}
#resultModal.active .lucky-title {
opacity: 1;
transform: translateY(0);
}
.victim-name {
font-size: 6rem;
font-weight: 900;
color: #fff;
position: relative;
z-index: 2;
text-shadow: 0 0 20px #ff4757;
animation: shake 0.5s infinite;
}
/* 痛苦的表情 */
.face-emoji {
font-size: 4rem;
position: absolute;
top: -60px;
right: -60px;
animation: bounce 1s infinite alternate;
}
/* 火焰画布 */
canvas#fireCanvas {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 60%;
z-index: 1;
pointer-events: none;
}
/* 弹幕吐槽 */
.danmaku {
position: absolute;
color: rgba(255, 255, 255, 0.8);
font-size: 1.5rem;
white-space: nowrap;
animation: fly linear forwards;
font-weight: bold;
text-shadow: 1px 1px 2px black;
}
@keyframes fly {
from { transform: translateX(100vw); }
to { transform: translateX(-100vw); }
}
@keyframes shake {
0% { transform: translate(1px, 1px) rotate(0deg); }
10% { transform: translate(-1px, -2px) rotate(-1deg); }
20% { transform: translate(-3px, 0px) rotate(1deg); }
30% { transform: translate(3px, 2px) rotate(0deg); }
40% { transform: translate(1px, -1px) rotate(1deg); }
50% { transform: translate(-1px, 2px) rotate(-1deg); }
60% { transform: translate(-3px, 1px) rotate(0deg); }
70% { transform: translate(3px, 1px) rotate(-1deg); }
80% { transform: translate(-1px, -1px) rotate(1deg); }
90% { transform: translate(1px, 2px) rotate(0deg); }
100% { transform: translate(1px, -2px) rotate(-1deg); }
}
@keyframes bounce {
from { transform: translateY(0); }
to { transform: translateY(-20px); }
}
/* 枪击特效样式 */
.gun-wrapper {
position: absolute;
right: -300px; /* 初始在屏幕外 */
top: 50%;
transform: translateY(-50%);
width: 300px;
height: 200px;
z-index: 50;
transition: right 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
pointer-events: none;
}
.gun-wrapper.active {
right: 50px; /* 伸出来 */
}
/* 枪身 SVG 容器 */
.gun-svg {
width: 100%;
height: 100%;
filter: drop-shadow(5px 5px 5px rgba(0,0,0,0.5));
}
/* 枪口火光 */
.muzzle-flash {
position: absolute;
left: -40px;
top: 35px;
width: 80px;
height: 80px;
background: radial-gradient(circle, #fff, #ffff00, #ff0000, transparent);
border-radius: 50%;
transform: scale(0);
opacity: 0;
z-index: 51;
}
.muzzle-flash.bang {
animation: flash 0.1s ease-out;
}
@keyframes flash {
0% { transform: scale(0); opacity: 1; }
50% { transform: scale(1.5); opacity: 1; }
100% { transform: scale(0.5); opacity: 0; }
}
/* 开枪时的后坐力 */
.gun-wrapper.shoot .gun-svg {
animation: recoil 0.2s ease-out;
}
@keyframes recoil {
0% { transform: translateX(0) rotate(0); }
10% { transform: translateX(50px) rotate(10deg); }
100% { transform: translateX(0) rotate(0); }
}
/* 砰文字 */
.bang-text {
position: absolute;
left: -80px;
top: 0;
font-size: 4rem;
color: #fff;
font-weight: bold;
text-shadow: 0 0 10px red;
transform: scale(0) rotate(-20deg);
opacity: 0;
z-index: 52;
}
.bang-text.show {
animation: bangPop 0.5s ease-out;
}
@keyframes bangPop {
0% { transform: scale(0) rotate(-20deg); opacity: 1; }
50% { transform: scale(1.5) rotate(0deg); opacity: 1; }
100% { transform: scale(1) rotate(0deg); opacity: 0; }
}
/* 名字倒地动画 */
.victim-name.dead {
animation: fallDead 1.5s forwards cubic-bezier(0.6, -0.28, 0.735, 0.045);
}
.victim-name.dead .face-emoji {
content: "😵"; /* 可以在 CSS 中无法直接改内容,通过 JS 改 */
animation: none; /* 停止跳动 */
transform: rotate(180deg); /* 表情倒过来 */
transition: all 0.5s;
}
@keyframes fallDead {
0% { transform: rotate(0); color: #fff; }
10% { transform: rotate(-5deg) translateX(0); color: #ff0000; } /* 中弹瞬间变红 */
30% { transform: rotate(10deg) translateX(10px); }
100% {
transform: rotate(100deg) translateY(300px);
opacity: 0.6;
color: #57606f; /* 变成死灰色 */
text-shadow: none;
}
}
.close-btn {
margin-top: 50px;
padding: 10px 30px;
background: transparent;
border: 2px solid #fff;
color: #fff;
font-size: 1.2rem;
cursor: pointer;
z-index: 10;
border-radius: 30px;
transition: 0.3s;
}
.close-btn:hover {
background: #fff;
color: #000;
}
</style>
</head>
<body>
<h1>🔮 命运之轮 🔮</h1>
<div class="wheel-container">
<div class="pointer"></div>
<canvas id="wheelCanvas" width="500" height="500"></canvas>
</div>
<button id="spinBtn">开始点名</button>
<!-- 结果模态框 -->
<div id="resultModal">
<canvas id="fireCanvas"></canvas>
<div class="lucky-title">恭喜这位“幸运”同学...</div>
<div class="victim-name" id="victimName">
???
<div class="face-emoji">😱</div>
</div>
<!-- 枪的容器 -->
<div id="gunWrapper" class="gun-wrapper">
<div class="bang-text">BANG!</div>
<div class="muzzle-flash"></div>
<!-- SVG 手枪 -->
<svg class="gun-svg" viewBox="0 0 100 60" fill="#333">
<path d="M10,10 L60,10 L60,20 L80,20 L80,30 L60,30 L60,40 L30,40 L30,50 L10,50 Z" stroke="#000" stroke-width="2"/>
<rect x="0" y="5" width="10" height="5" fill="#555"/>
<rect x="60" y="15" width="5" height="5" fill="#555"/>
<!-- 简单的手枪造型优化 -->
<path d="M5,10 h50 a5,5 0 0 1 5,5 v5 h25 v10 h-25 v15 a5,5 0 0 1 -5,5 h-20 a5,5 0 0 1 -5,-5 v-15 h-20 a5,5 0 0 1 -5,-5 v-10 a5,5 0 0 1 5,-5 z" fill="#2d3436"/>
<rect x="60" y="12" width="20" height="4" fill="#000"/>
</svg>
</div>
<button class="close-btn" onclick="closeModal()">放过他/她吧</button>
</div>
<script>
// ================= 配置数据 =================
const students = ["刘美希", "林子琪", "唐纯瑞", "林子皓", "刘若曦","冯筱壹", "王艺诺", "邹泓凯", "王梓博", "肖靖泽", "彭馨瑶", "刘丰源", "黄琬乔", "赵敏智"];
const colors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8',
'#F7DC6F', '#BB8FCE', '#F1948A', '#82E0AA', '#D7BDE2',
'#F0B27A', '#85C1E9', '#F5B7B1'
];
// 吐槽弹幕库
const comments = [
"怎么又是我?!", "老师别看我...", "由于太帅被系统选中",
"如果能重来...", "此时一位靓仔失去了梦想", "不要啊!!!",
"这也太准了吧", "这就是命", "正在假装断网...", "瑟瑟发抖中"
];
// ================= 变量与初始化 =================
const canvas = document.getElementById('wheelCanvas');
const ctx = canvas.getContext('2d');
const spinBtn = document.getElementById('spinBtn');
const modal = document.getElementById('resultModal');
const victimNameEl = document.getElementById('victimName');
const fireCanvas = document.getElementById('fireCanvas');
const fireCtx = fireCanvas.getContext('2d');
let startAngle = 0;
const arc = Math.PI * 2 / students.length;
let spinTimeout = null;
let spinAngleStart = 10;
let spinTime = 0;
let spinTimeTotal = 0;
let isSpinning = false;
let fireAnimationId;
// 初始化
resizeFireCanvas();
drawRouletteWheel();
window.addEventListener('resize', resizeFireCanvas);
// ================= 转盘逻辑 =================
function drawRouletteWheel() {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const outsideRadius = 240;
const textRadius = 170;
const insideRadius = 50;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold 18px Helvetica, Arial';
for(let i = 0; i < students.length; i++) {
const angle = startAngle + i * arc;
// 绘制扇形
ctx.fillStyle = colors[i % colors.length];
ctx.beginPath();
ctx.arc(centerX, centerY, outsideRadius, angle, angle + arc, false);
ctx.arc(centerX, centerY, insideRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
// 绘制文字
ctx.save();
ctx.shadowColor = "rgba(0,0,0,0.5)";
ctx.shadowBlur = 4;
ctx.fillStyle = "white";
ctx.translate(centerX + Math.cos(angle + arc / 2) * textRadius,
centerY + Math.sin(angle + arc / 2) * textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
const text = students[i];
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
}
}
function rotateWheel() {
spinTime += 30;
if(spinTime >= spinTimeTotal) {
stopRotateWheel();
return;
}
// 缓动算法
const spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
startAngle += (spinAngle * Math.PI / 180);
drawRouletteWheel();
// 播放转动音效(滴答声)
playTickSound();
spinTimeout = setTimeout(rotateWheel, 30);
}
function stopRotateWheel() {
clearTimeout(spinTimeout);
isSpinning = false;
spinBtn.disabled = false;
spinBtn.innerText = "再来一次";
const degrees = startAngle * 180 / Math.PI + 90;
const arcd = arc * 180 / Math.PI;
const index = Math.floor((360 - degrees % 360) / arcd);
const selectedStudent = students[index];
showResult(selectedStudent);
}
function easeOut(t, b, c, d) {
const ts = (t/=d)*t;
const tc = ts*t;
return b+c*(tc + -3*ts + 3*t);
}
spinBtn.addEventListener('click', () => {
if(isSpinning) return;
isSpinning = true;
spinBtn.disabled = true;
spinBtn.innerText = "命运抉择中...";
spinAngleStart = Math.random() * 10 + 10;
spinTime = 0;
spinTimeTotal = Math.random() * 3000 + 4000; // 4-7秒
rotateWheel();
});
// ================= 结果与特效 =================
function showResult(name) {
victimNameEl.innerHTML = `${name} <div class="face-emoji">😱</div>`;
victimNameEl.classList.remove('dead'); // 重置状态
modal.classList.add('active');
// 启动火焰粒子
initFire();
// 枪击流程
startShootingSequence(name);
}
function startShootingSequence(name) {
const gunWrapper = document.getElementById('gunWrapper');
const muzzleFlash = document.querySelector('.muzzle-flash');
const bangText = document.querySelector('.bang-text');
const faceEmoji = victimNameEl.querySelector('.face-emoji');
// 1. 枪伸出来 (0.5s后)
setTimeout(() => {
gunWrapper.classList.add('active');
}, 500);
// 2. 开枪 (1.5s后)
setTimeout(() => {
// 视觉特效
gunWrapper.classList.add('shoot');
muzzleFlash.classList.add('bang');
bangText.classList.add('show');
// 名字倒地
victimNameEl.classList.add('dead');
faceEmoji.innerText = "😵"; // 表情变成晕倒
// 音效
playGunShot();
playScreamSound(); // 惨叫配合枪声
// 朗读名字
setTimeout(() => speakName(name), 500);
}, 1500);
// 3. 弹幕稍后出现 (2.5s后)
setTimeout(() => {
launchDanmaku();
}, 2500);
}
function closeModal() {
modal.classList.remove('active');
cancelAnimationFrame(fireAnimationId);
// 重置枪的状态
const gunWrapper = document.getElementById('gunWrapper');
const muzzleFlash = document.querySelector('.muzzle-flash');
const bangText = document.querySelector('.bang-text');
gunWrapper.classList.remove('active', 'shoot');
muzzleFlash.classList.remove('bang');
bangText.classList.remove('show');
// 清除弹幕
document.querySelectorAll('.danmaku').forEach(el => el.remove());
}
function launchDanmaku() {
const container = document.getElementById('resultModal');
for(let i=0; i<15; i++) {
setTimeout(() => {
const el = document.createElement('div');
el.className = 'danmaku';
el.innerText = comments[Math.floor(Math.random() * comments.length)];
el.style.top = Math.random() * 80 + '%';
el.style.animationDuration = (Math.random() * 3 + 2) + 's';
container.appendChild(el);
// 动画结束后移除
el.addEventListener('animationend', () => el.remove());
}, Math.random() * 2000);
}
}
// ================= 音效系统 (Web Audio API) =================
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
function playTickSound() {
if (audioCtx.state === 'suspended') audioCtx.resume();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(800, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.1);
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.1);
}
function playGunShot() {
if (audioCtx.state === 'suspended') audioCtx.resume();
// 噪声源模拟枪声
const bufferSize = audioCtx.sampleRate * 0.5; // 0.5秒
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 = 1000;
// 包络
const gain = audioCtx.createGain();
gain.gain.setValueAtTime(1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.2);
noise.connect(filter);
filter.connect(gain);
gain.connect(audioCtx.destination);
noise.start();
}
function playScreamSound() {
// 模拟一个低沉的“咚”声,表示厄运降临
if (audioCtx.state === 'suspended') audioCtx.resume();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(100, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(10, audioCtx.currentTime + 1);
gain.gain.setValueAtTime(0.5, audioCtx.currentTime);
gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 1);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 1);
}
function speakName(name) {
// 使用浏览器语音合成
if ('speechSynthesis' in window) {
const utterance = new SpeechSynthesisUtterance("恭喜" + name + "同学");
utterance.pitch = 0.5; // 低沉的声音,比较吓人
utterance.rate = 0.8; // 慢一点
utterance.volume = 1;
window.speechSynthesis.speak(utterance);
}
}
// ================= 火焰粒子特效 (Canvas) =================
let particles = [];
function resizeFireCanvas() {
fireCanvas.width = window.innerWidth;
fireCanvas.height = window.innerHeight * 0.6;
}
class Particle {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * fireCanvas.width;
this.y = fireCanvas.height;
this.size = Math.random() * 20 + 10;
this.speedY = Math.random() * 3 + 2;
this.speedX = (Math.random() - 0.5) * 2;
this.life = 100; // 生命值
this.color = { r: 255, g: Math.floor(Math.random() * 150), b: 0 };
}
update() {
this.y -= this.speedY;
this.x += this.speedX;
this.size *= 0.95; // 逐渐变小
this.life -= 2;
// 颜色从黄变红变黑
if (this.life < 80) this.color.g -= 5;
if (this.color.g < 0) this.color.g = 0;
}
draw() {
fireCtx.beginPath();
fireCtx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
fireCtx.fillStyle = `rgba(${this.color.r}, ${this.color.g}, ${this.color.b}, ${this.life / 100})`;
fireCtx.fill();
}
}
function initFire() {
particles = [];
for (let i = 0; i < 100; i++) {
particles.push(new Particle());
}
animateFire();
}
function animateFire() {
fireCtx.clearRect(0, 0, fireCanvas.width, fireCanvas.height);
// 产生新粒子
if (particles.length < 300) {
particles.push(new Particle());
particles.push(new Particle());
}
particles.forEach((p, index) => {
p.update();
p.draw();
if (p.life <= 0 || p.size <= 0.5) {
particles.splice(index, 1);
particles.push(new Particle()); // 循环利用
}
});
fireAnimationId = requestAnimationFrame(animateFire);
}
</script>
</body>
</html>