侧边栏壁纸
博主头像
离开的兔子博主等级

行动起来,活在当下

  • 累计撰写 4 篇文章
  • 累计创建 1 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

抽奖功能

Administrator
2025-11-15 / 0 评论 / 0 点赞 / 8 阅读 / 50837 字

1、代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>年会抽奖系统</title>
  <style>
    html,body { height:100%; margin:0; background:#0a0e1a; overflow:hidden; }
    #app { width:100%; height:100%; }
    .header {
      position:fixed; top:0; left:0; right:0; height:100px;
      background:linear-gradient(180deg, rgba(10,14,26,0.95) 0%, rgba(10,14,26,0) 100%);
      z-index:50; display:flex; align-items:center; justify-content:center;
    }
    .header-title {
      font:700 48px system-ui; color:#fff;
      text-shadow:0 0 30px rgba(102,126,234,0.8), 0 4px 20px rgba(0,0,0,0.5);
      letter-spacing:8px;
    }
    .controls {
      position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
      z-index:100; text-align:center; pointer-events:none;
      width:98vw; max-width:1800px;
      display:flex;
      flex-direction:column;
      align-items:center;
    }
    .lottery-btn {
      pointer-events:all; padding:24px 80px; font:700 32px system-ui;
      background:linear-gradient(135deg, #f59e0b 0%, #dc2626 100%);
      color:#fff; border:none; border-radius:60px; cursor:pointer;
      box-shadow:0 12px 50px rgba(245,158,11,0.6), 0 0 0 0 rgba(245,158,11,0.7);
      transition:all 0.3s; position:relative; overflow:hidden;
      transform:translateZ(0); backface-visibility:hidden;
      letter-spacing:4px;
    }
    .lottery-btn::before {
      content:''; position:absolute; top:50%; left:50%;
      width:0; height:0; border-radius:50%;
      background:rgba(255,255,255,0.6);
      transform:translate(-50%,-50%);
      transition:width 0.6s, height 0.6s;
    }
    .lottery-btn:hover { 
      transform:translateY(-4px) scale(1.08); 
      box-shadow:0 18px 60px rgba(245,158,11,0.8), 0 0 40px rgba(245,158,11,0.6); 
    }
    .lottery-btn:active { 
      transform:translateY(-2px) scale(1.02); 
    }
    .lottery-btn.click-wave::before {
      width:350px; height:350px; opacity:0;
      transition:width 0.6s, height 0.6s, opacity 0.6s;
    }
    .lottery-btn.running {
      background:linear-gradient(135deg, #ec4899 0%, #8b5cf6 100%);
      animation:pulse 1s infinite, rotate-gradient 3s linear infinite;
    }
    @keyframes pulse {
      0%, 100% { 
        box-shadow:0 12px 50px rgba(236,72,153,0.6), 0 0 30px rgba(236,72,153,0.7); 
        transform:scale(1);
      }
      50% { 
        box-shadow:0 18px 70px rgba(236,72,153,1), 0 0 50px rgba(236,72,153,1); 
        transform:scale(1.08);
      }
    }
    @keyframes rotate-gradient {
      0% { filter:hue-rotate(0deg); }
      100% { filter:hue-rotate(360deg); }
    }
    .result-display {
      margin-top:40px; color:#fff;
      opacity:0; transition:opacity 0.3s; 
      max-width:100%; max-height:65vh;
      overflow-y:auto; overflow-x:hidden;
      padding:20px 80px;
      pointer-events:auto;
    }
    .result-display::-webkit-scrollbar {
      width:10px;
    }
    .result-display::-webkit-scrollbar-track {
      background:rgba(15,23,42,0.6); border-radius:5px;
    }
    .result-display::-webkit-scrollbar-thumb {
      background:linear-gradient(180deg, #fbbf24 0%, #f59e0b 100%);
      border-radius:5px;
    }
    .result-display::-webkit-scrollbar-thumb:hover {
      background:linear-gradient(180deg, #fcd34d 0%, #fbbf24 100%);
    }
    .result-display.show { opacity:1; animation:result-fade-in 0.5s ease-out; }
    @keyframes result-fade-in {
      0% { opacity:0; transform:translateY(-20px); }
      100% { opacity:1; transform:translateY(0); }
    }
    .result-title {
      font:700 42px system-ui; text-align:center;
      margin-bottom:25px; color:#fbbf24;
      text-shadow:0 0 20px rgba(251,191,36,0.8), 0 4px 15px rgba(0,0,0,0.5);
      letter-spacing:3px;
    }
    .result-title .prize-level-badge {
      display:inline-block; padding:8px 20px; margin-left:15px;
      background:linear-gradient(135deg, #4ade80 0%, #22d3ee 100%);
      border-radius:25px; font-size:28px;
      box-shadow:0 4px 20px rgba(74,222,128,0.5);
      animation:badge-glow 2s infinite;
    }
    @keyframes badge-glow {
      0%, 100% { box-shadow:0 4px 20px rgba(74,222,128,0.5); }
      50% { box-shadow:0 6px 30px rgba(74,222,128,0.8); }
    }
    .winner-label {
      padding:30px 40px;
      background:linear-gradient(135deg, rgba(251,191,36,0.3) 0%, rgba(245,158,11,0.2) 100%);
      border:3px solid rgba(251,191,36,0.7);
      border-radius:20px; 
      box-shadow:0 8px 32px rgba(251,191,36,0.4), inset 0 0 40px rgba(251,191,36,0.15);
      position:relative; 
      color:#fff; text-align:center;
      backdrop-filter:blur(15px);
      transition:all 0.3s;
      min-height:120px;
      display:flex;
      flex-direction:column;
      justify-content:center;
      align-items:center;
    }
    .winner-label.compact {
      padding:15px 20px;
      min-height:70px;
      border-radius:12px;
    }
    .winner-label.single-winner {
      min-height:220px;
      padding:50px 80px;
      background:linear-gradient(135deg, rgba(251,191,36,0.5) 0%, rgba(245,158,11,0.4) 100%);
      border:5px solid #fbbf24;
      box-shadow:0 10px 50px rgba(251,191,36,0.7), inset 0 0 60px rgba(251,191,36,0.35),
                 0 0 80px rgba(251,191,36,0.5);
      animation:single-winner-glow 2s infinite;
    }
    @keyframes single-winner-glow {
      0%, 100% { 
        box-shadow:0 10px 50px rgba(251,191,36,0.7), inset 0 0 60px rgba(251,191,36,0.35),
                   0 0 80px rgba(251,191,36,0.5);
        transform:scale(1);
      }
      50% { 
        box-shadow:0 15px 70px rgba(251,191,36,0.9), inset 0 0 80px rgba(251,191,36,0.55),
                   0 0 120px rgba(251,191,36,0.7);
        transform:scale(1.05);
      }
    }
    .winner-label:hover {
      background:linear-gradient(135deg, rgba(251,191,36,0.4) 0%, rgba(245,158,11,0.3) 100%);
      border-color:#fbbf24;
      box-shadow:0 10px 35px rgba(251,191,36,0.6), inset 0 0 50px rgba(251,191,36,0.25);
      transform:scale(1.05);
    }
    .winner-label.single-winner:hover {
      transform:scale(1.08);
    }
    .winner-label .winner-name {
      color:#fff; text-shadow:0 3px 15px rgba(0,0,0,0.6), 0 0 30px rgba(251,191,36,0.5);
      font-size:36px; font-weight:700; letter-spacing:4px;
    }
    .winner-label.compact .winner-name {
      font-size:24px; letter-spacing:1px;
    }
    .winner-label.single-winner .winner-name {
      font-size:72px; letter-spacing:12px;
      text-shadow:0 5px 25px rgba(251,191,36,0.9), 0 0 50px rgba(251,191,36,0.7),
                  0 0 80px rgba(251,191,36,0.5);
    }
    .winners-container {
      display:grid;
      grid-template-columns:repeat(4, 1fr);
      gap:20px;
      width:100%;
      justify-items:center;
    }
    .winners-container.many-winners {
      grid-template-columns:repeat(auto-fill, minmax(100px, 1fr));
      gap:12px;
    }
    .winners-container.single-winner-container {
      grid-template-columns:1fr;
      max-width:700px;
      margin:0 auto;
    }
    @media (max-width: 1200px) {
      .winners-container {
        grid-template-columns:repeat(auto-fill, minmax(180px, 1fr));
        gap:18px;
      }
      .winners-container.many-winners {
        grid-template-columns:repeat(auto-fill, minmax(140px, 1fr));
        gap:12px;
      }
    }
    @media (max-width: 768px) {
      .winners-container {
        grid-template-columns:repeat(auto-fill, minmax(120px, 1fr));
        gap:15px;
      }
      .winners-container.many-winners {
        grid-template-columns:repeat(auto-fill, minmax(90px, 1fr));
        gap:10px;
      }
      .winner-label .winner-name {
        font-size:28px;
      }
    }
    .winner-label::before {
      content:'🎉'; position:absolute; left:-60px; top:50%;
      transform:translateY(-50%); font-size:48px;
      animation:celebrate 1s infinite;
    }
    .winner-label::after {
      content:'🎉'; position:absolute; right:-60px; top:50%;
      transform:translateY(-50%); font-size:48px;
      animation:celebrate 1s infinite 0.5s;
    }
    @keyframes celebrate {
      0%, 100% { transform:translateY(-50%) scale(1); }
      50% { transform:translateY(-50%) scale(1.3); }
    }
    @keyframes winner-glow {
      0%, 100% { box-shadow:0 12px 50px rgba(251,191,36,0.8); }
      50% { box-shadow:0 18px 70px rgba(251,191,36,1), 0 0 80px rgba(251,191,36,0.8); }
    }
    .stats {
      position:fixed; top:120px; left:30px; color:#e0e7ff;
      font:16px/2 system-ui; background:rgba(15,23,42,0.85);
      padding:20px 30px; border-radius:16px; backdrop-filter:blur(12px);
      border:1px solid rgba(102,126,234,0.3);
      box-shadow:0 8px 32px rgba(0,0,0,0.3);
    }
    .stats div { margin:5px 0; }
    .stats span { color:#fbbf24; font-weight:700; font-size:18px; }
    .draw-count-setting {
      margin-top:15px; padding-top:15px;
      border-top:1px solid rgba(102,126,234,0.3);
    }
    .draw-count-setting label {
      display:block; margin-bottom:8px; color:#94a3b8;
      font-size:14px;
    }
    .draw-count-input {
      width:100%; padding:8px 12px; font-size:16px;
      background:rgba(30,41,59,0.8); border:1px solid rgba(102,126,234,0.4);
      border-radius:8px; color:#fbbf24; font-weight:700;
      outline:none; transition:all 0.3s;
    }
    .draw-count-input:focus {
      border-color:#667eea; box-shadow:0 0 0 3px rgba(102,126,234,0.2);
    }
    .draw-count-input:hover {
      border-color:#667eea;
    }
    .prize-level-setting {
      margin-top:15px; padding-top:15px;
      border-top:1px solid rgba(102,126,234,0.3);
    }
    .prize-level-select {
      width:100%; padding:8px 12px; font-size:16px;
      background:rgba(30,41,59,0.8); border:1px solid rgba(102,126,234,0.4);
      border-radius:8px; color:#4ade80; font-weight:700;
      outline:none; transition:all 0.3s;
      cursor:pointer;
    }
    .prize-level-select:focus {
      border-color:#4ade80; box-shadow:0 0 0 3px rgba(74,222,128,0.2);
    }
    .prize-level-select:hover {
      border-color:#4ade80;
    }
    .prize-level-select option {
      background:#1e293b; color:#fff; padding:8px;
    }
    .history-btn {
      width:100%; padding:10px 16px; margin-top:10px;
      background:linear-gradient(135deg, rgba(102,126,234,0.6), rgba(74,144,226,0.6));
      border:1px solid rgba(102,126,234,0.5);
      border-radius:10px; color:#fff; font-size:14px; font-weight:600;
      cursor:pointer; transition:all 0.3s;
      box-shadow:0 4px 15px rgba(102,126,234,0.3);
      position:relative; overflow:hidden;
    }
    .history-btn:hover {
      background:linear-gradient(135deg, rgba(102,126,234,0.8), rgba(74,144,226,0.8));
      transform:translateY(-2px);
      box-shadow:0 6px 20px rgba(102,126,234,0.5);
    }
    .history-btn:active {
      transform:translateY(0);
    }
    .history-btn.clear-btn {
      background:linear-gradient(135deg, rgba(239,68,68,0.6), rgba(220,38,38,0.6));
      border-color:rgba(239,68,68,0.5);
      box-shadow:0 4px 15px rgba(239,68,68,0.3);
    }
    .history-btn.clear-btn:hover {
      background:linear-gradient(135deg, rgba(239,68,68,0.8), rgba(220,38,38,0.8));
      box-shadow:0 6px 20px rgba(239,68,68,0.5);
    }
    .history-modal {
      position:fixed; top:0; left:0; width:100%; height:100%;
      background:rgba(0,0,0,0.8); backdrop-filter:blur(8px);
      display:none; justify-content:center; align-items:center;
      z-index:10000;
    }
    .history-modal.show {
      display:flex;
    }
    .history-content {
      background:linear-gradient(135deg, rgba(15,23,42,0.95), rgba(30,41,59,0.95));
      border:1px solid rgba(102,126,234,0.4);
      border-radius:20px; padding:30px;
      max-width:800px; width:90%; max-height:80vh;
      box-shadow:0 20px 60px rgba(0,0,0,0.5);
      position:relative;
    }
    .history-title {
      font-size:24px; font-weight:700; color:#fbbf24;
      margin-bottom:20px; text-align:center;
      text-shadow:0 0 20px rgba(251,191,36,0.5);
    }
    .history-log {
      background:rgba(10,14,26,0.6);
      border:1px solid rgba(102,126,234,0.3);
      border-radius:12px; padding:20px;
      max-height:50vh; overflow-y:auto;
      font-family:monospace; font-size:14px;
      line-height:1.8; color:#e0e7ff;
      white-space:pre-wrap; word-break:break-all;
    }
    .history-log::-webkit-scrollbar {
      width:8px;
    }
    .history-log::-webkit-scrollbar-track {
      background:rgba(30,41,59,0.5); border-radius:4px;
    }
    .history-log::-webkit-scrollbar-thumb {
      background:rgba(102,126,234,0.5); border-radius:4px;
    }
    .history-log::-webkit-scrollbar-thumb:hover {
      background:rgba(102,126,234,0.8);
    }
    .history-empty {
      text-align:center; color:#94a3b8; padding:40px;
      font-size:16px;
    }
    .history-close {
      position:absolute; top:20px; right:20px;
      width:36px; height:36px; border-radius:50%;
      background:rgba(239,68,68,0.2);
      border:1px solid rgba(239,68,68,0.5);
      color:#ef4444; font-size:20px; font-weight:700;
      cursor:pointer; transition:all 0.3s;
      display:flex; align-items:center; justify-content:center;
    }
    .history-close:hover {
      background:rgba(239,68,68,0.4);
      transform:rotate(90deg);
    }
    .back-btn {
      position:fixed; top:120px; right:30px;
      padding:15px 30px; font-size:16px; font-weight:600;
      background:linear-gradient(135deg, rgba(74,222,128,0.6), rgba(34,197,94,0.6));
      border:1px solid rgba(74,222,128,0.5);
      border-radius:12px; color:#fff;
      cursor:pointer; transition:all 0.3s;
      box-shadow:0 8px 32px rgba(74,222,128,0.3);
      backdrop-filter:blur(12px);
      display:none;
      z-index:100;
    }
    .back-btn.show {
      display:block;
    }
    .back-btn:hover {
      background:linear-gradient(135deg, rgba(74,222,128,0.8), rgba(34,197,94,0.8));
      transform:translateY(-3px);
      box-shadow:0 12px 40px rgba(74,222,128,0.5);
    }
    .back-btn:active {
      transform:translateY(-1px);
    }
    .hint {
      position:fixed; left:50%; bottom:40px; transform:translateX(-50%);
      color:#9ca3af; font:14px/1.6 system-ui; text-align:center;
      background:rgba(15,23,42,0.7); padding:12px 30px;
      border-radius:25px; backdrop-filter:blur(8px);
    }
    .firework {
      position:fixed; width:8px; height:8px; border-radius:50%;
      pointer-events:none; z-index:999;
    }
    .click-particle {
      position:fixed; width:6px; height:6px; border-radius:50%;
      pointer-events:none; z-index:1000;
    }
    .shockwave {
      position:fixed; border:3px solid rgba(245,158,11,0.9);
      border-radius:50%; pointer-events:none; z-index:999;
      animation:shockwave-anim 0.8s ease-out forwards;
    }
    @keyframes shockwave-anim {
      0% { width:0; height:0; opacity:1; }
      100% { width:500px; height:500px; opacity:0; }
    }
    .energy-ring {
      position:absolute; top:50%; left:50%; 
      width:120%; height:120%; border-radius:50%;
      border:3px solid rgba(251,191,36,0.7);
      transform:translate(-50%,-50%) scale(0);
      opacity:0; pointer-events:none;
    }
    .lottery-btn.running .energy-ring {
      animation:energy-pulse 1s ease-out infinite;
    }
    @keyframes energy-pulse {
      0% { transform:translate(-50%,-50%) scale(0.8); opacity:0; }
      50% { opacity:0.8; }
      100% { transform:translate(-50%,-50%) scale(1.8); opacity:0; }
    }
    .confetti {
      position:fixed; width:10px; height:10px;
      pointer-events:none; z-index:1001;
    }
  </style>
</head>
<body>
<div id="app"></div>
<div class="header">
  <div class="header-title">🎊 年会幸运抽奖 🎊</div>
</div>
<div class="controls">
  <button class="lottery-btn" id="lotteryBtn">
    开始抽奖
    <div class="energy-ring"></div>
    <div class="energy-ring" style="animation-delay:0.3s;"></div>
    <div class="energy-ring" style="animation-delay:0.6s;"></div>
  </button>
  <div class="result-display" id="resultDisplay"></div>
</div>
<div class="stats">
  <div style="font-size:18px; margin-bottom:8px; color:#fbbf24; font-weight:700;">📊 抽奖统计</div>
  <div>总人数:<span id="totalCount">0</span></div>
  <div>已中奖:<span id="drawnCount">0</span></div>
  <div>剩余人数:<span id="remainCount">0</span></div>
  <div class="draw-count-setting">
    <label for="drawCountInput">🎯 每次抽取人数</label>
    <input type="number" id="drawCountInput" class="draw-count-input" 
           value="1" min="1" max="50" />
  </div>
  <div class="prize-level-setting">
    <label for="prizeLevelSelect">🏆 奖项等级</label>
    <select id="prizeLevelSelect" class="prize-level-select">
      <option value="">加载中...</option>
    </select>
  </div>
  <div class="history-actions" style="margin-top:20px; padding-top:20px; border-top:1px solid rgba(102,126,234,0.3);">
    <button class="history-btn" id="viewHistoryBtn">
      📝 查看记录
    </button>
    <button class="history-btn clear-btn" id="clearHistoryBtn">
      🗑️ 清除记录
    </button>
  </div>
</div>
<div class="hint">点击按钮开始抽奖 · 拖拽旋转 · 滚轮缩放</div>

<!-- 返回按钮 -->
<button class="back-btn" id="backBtn">↺ 返回</button>

<!-- 历史记录弹窗 -->
<div class="history-modal" id="historyModal">
  <div class="history-content">
    <div class="history-close" id="closeHistoryBtn">×</div>
    <div class="history-title">📝 抽奖历史记录</div>
    <div class="history-log" id="historyLog"></div>
  </div>
</div>

<script type="module">
import * as THREE from '../component/threejs/build/three.module.min.js';

const container = document.getElementById('app');
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
container.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0e1a);
scene.fog = new THREE.Fog(0x0a0e1a, 100, 1000);

// 添加星空背景
const starGeometry = new THREE.BufferGeometry();
const starCount = 1500;
const starPositions = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
  starPositions[i * 3] = (Math.random() - 0.5) * 2000;
  starPositions[i * 3 + 1] = (Math.random() - 0.5) * 2000;
  starPositions[i * 3 + 2] = (Math.random() - 0.5) * 2000;
}
starGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
const starMaterial = new THREE.PointsMaterial({
  size: 1.5,
  color: 0xffffff,
  transparent: true,
  opacity: 0.8
});
const stars = new THREE.Points(starGeometry, starMaterial);
scene.add(stars);
const camera = new THREE.PerspectiveCamera(60, container.clientWidth / container.clientHeight, 1, 2000);
camera.position.set(0, 0, 400);

const ambient = new THREE.AmbientLight(0xffffff, 0.25);
scene.add(ambient);
const dir = new THREE.DirectionalLight(0xffffff, 0.8);
dir.position.set(200, 300, 400);
scene.add(dir);

// 添加粒子系统背景
const particleGeometry = new THREE.BufferGeometry();
const particleCount = 800;
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
  positions[i * 3] = (Math.random() - 0.5) * 1000;
  positions[i * 3 + 1] = (Math.random() - 0.5) * 1000;
  positions[i * 3 + 2] = (Math.random() - 0.5) * 1000;
  const color = new THREE.Color();
  color.setHSL(0.6 + Math.random() * 0.2, 0.8, 0.5);
  colors[i * 3] = color.r;
  colors[i * 3 + 1] = color.g;
  colors[i * 3 + 2] = color.b;
}
particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particleGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const particleMaterial = new THREE.PointsMaterial({
  size: 2,
  vertexColors: true,
  transparent: true,
  opacity: 0.6,
  blending: THREE.AdditiveBlending
});
const particleSystem = new THREE.Points(particleGeometry, particleMaterial);
scene.add(particleSystem);

// 添加科技线框
const lineGeometry = new THREE.BufferGeometry();
const linePositions = [];
for (let i = 0; i < 50; i++) {
  const angle = (i / 50) * Math.PI * 2;
  const r = 250;
  linePositions.push(Math.cos(angle) * r, 0, Math.sin(angle) * r);
}
lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute(linePositions, 3));
const lineMaterial = new THREE.LineBasicMaterial({ 
  color: 0x667eea, 
  transparent: true, 
  opacity: 0.3 
});
const techCircle = new THREE.LineLoop(lineGeometry, lineMaterial);
scene.add(techCircle);

const techCircle2 = techCircle.clone();
techCircle2.rotation.x = Math.PI / 2;
scene.add(techCircle2);

const techCircle3 = techCircle.clone();
techCircle3.rotation.z = Math.PI / 2;
scene.add(techCircle3);

// 添加全息投影效果网格
const gridHelper = new THREE.GridHelper(800, 40, 0x00ffff, 0x004488);
gridHelper.position.y = -200;
gridHelper.material.transparent = true;
gridHelper.material.opacity = 0.15;
scene.add(gridHelper);

// 添加数据流线条
const dataStreams = [];
for (let i = 0; i < 20; i++) {
  const curve = new THREE.CatmullRomCurve3([
    new THREE.Vector3((Math.random() - 0.5) * 500, -300, (Math.random() - 0.5) * 500),
    new THREE.Vector3((Math.random() - 0.5) * 300, 0, (Math.random() - 0.5) * 300),
    new THREE.Vector3((Math.random() - 0.5) * 500, 300, (Math.random() - 0.5) * 500)
  ]);
  const points = curve.getPoints(50);
  const streamGeometry = new THREE.BufferGeometry().setFromPoints(points);
  const streamMaterial = new THREE.LineBasicMaterial({
    color: 0x00ffff,
    transparent: true,
    opacity: 0
  });
  const stream = new THREE.Line(streamGeometry, streamMaterial);
  scene.add(stream);
  dataStreams.push({ mesh: stream, material: streamMaterial, progress: Math.random() });
}

// 添加扫描线效果
const scanLineGeometry = new THREE.PlaneGeometry(600, 2);
const scanLineMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ffff,
  transparent: true,
  opacity: 0,
  side: THREE.DoubleSide,
  blending: THREE.AdditiveBlending
});
const scanLine = new THREE.Mesh(scanLineGeometry, scanLineMaterial);
scene.add(scanLine);

// 添加能量球效果
const energySphereGeometry = new THREE.SphereGeometry(280, 32, 32);
const energySphereMaterial = new THREE.MeshBasicMaterial({
  color: 0x667eea,
  transparent: true,
  opacity: 0,
  wireframe: true,
  blending: THREE.AdditiveBlending
});
const energySphere = new THREE.Mesh(energySphereGeometry, energySphereMaterial);
scene.add(energySphere);

// 添加六边形网格
const hexGeometry = new THREE.TorusGeometry(300, 2, 6, 6);
const hexMaterial = new THREE.MeshBasicMaterial({
  color: 0x4ade80,
  transparent: true,
  opacity: 0,
  wireframe: true
});
const hexGrid = new THREE.Mesh(hexGeometry, hexMaterial);
hexGrid.rotation.x = Math.PI / 2;
scene.add(hexGrid);

// 添加光束效果
const beams = [];
for (let i = 0; i < 8; i++) {
  const beamGeometry = new THREE.CylinderGeometry(1, 1, 500, 8);
  const beamMaterial = new THREE.MeshBasicMaterial({
    color: 0x667eea,
    transparent: true,
    opacity: 0,
    blending: THREE.AdditiveBlending
  });
  const beam = new THREE.Mesh(beamGeometry, beamMaterial);
  const angle = (i / 8) * Math.PI * 2;
  beam.position.set(Math.cos(angle) * 250, 0, Math.sin(angle) * 250);
  beam.rotation.z = Math.PI / 2;
  beam.rotation.y = angle;
  scene.add(beam);
  beams.push(beam);
}

const names = [];
const drawnNames = new Set();
let isLotteryRunning = false;
let currentWinner = null;
const radius = 180;
const labels = new THREE.Group();
scene.add(labels);

// 奖项配置数据
let prizeConfig = [];

// 加载奖项配置
fetch('./server.conf')
  .then(response => response.text())
  .then(data => {
    // 解析配置文件
    const lines = data.split('\n');
    const prizes = [];
    
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i].trim();
      if (line && !line.startsWith('level:')) {
        // 匹配格式:奖项名称: 人数
        const match = line.match(/^([^:]+):\s*(\d+)/);
        if (match) {
          const name = match[1].trim();
          const count = parseInt(match[2]);
          prizes.push({ name, count });
        }
      }
    }
    
    prizeConfig = prizes;
    
    // 填充下拉框
    const prizeLevelSelect = document.getElementById('prizeLevelSelect');
    prizeLevelSelect.innerHTML = '';
    
    if (prizes.length > 0) {
      prizes.forEach((prize, index) => {
        const option = document.createElement('option');
        option.value = index;
        option.textContent = `${prize.name} (${prize.count}人)`;
        prizeLevelSelect.appendChild(option);
      });
      
      // 设置默认抽取人数
      updateDrawCount();
    } else {
      prizeLevelSelect.innerHTML = '<option value="">暂无奖项配置</option>';
    }
  })
  .catch(error => {
    console.error('加载奖项配置失败:', error);
    const prizeLevelSelect = document.getElementById('prizeLevelSelect');
    prizeLevelSelect.innerHTML = '<option value="">加载失败</option>';
  });

// 当选择奖项时,自动更新抽取人数
function updateDrawCount() {
  const prizeLevelSelect = document.getElementById('prizeLevelSelect');
  const drawCountInput = document.getElementById('drawCountInput');
  const selectedIndex = parseInt(prizeLevelSelect.value);
  
  if (!isNaN(selectedIndex) && prizeConfig[selectedIndex]) {
    drawCountInput.value = prizeConfig[selectedIndex].count;
  }
}

// 监听奖项选择变化
setTimeout(() => {
  const prizeLevelSelect = document.getElementById('prizeLevelSelect');
  if (prizeLevelSelect) {
    prizeLevelSelect.addEventListener('change', updateDrawCount);
  }
}, 100);

// 从文件读取用户名单
fetch('./user.conf')
  .then(response => response.text())
  .then(data => {
    const users = data.split('\n').filter(line => line.trim() !== '');
    names.push(...users);
    console.log(`加载了 ${names.length} 个用户`);
    
    // 更新统计信息
    document.getElementById('totalCount').textContent = names.length;
    document.getElementById('remainCount').textContent = names.length;
    
    // 创建名字标签
    for (let i = 0; i < names.length; i++) {
      const sp = makeLabel(names[i]);
      const r = radius * (0.7 + Math.random() * 0.3);
      const phi = Math.random() * Math.PI * 2;
      const cost = Math.random() * 2 - 1;
      const sint = Math.sqrt(1 - cost * cost);
      sp.position.set(r * Math.cos(phi) * sint, r * cost, r * Math.sin(phi) * sint);
      sp.rotation.y = Math.random() * Math.PI * 2;
      sp.userData.phi = phi;
      sp.userData.r = r;
      sp.userData.speed = 0.3 + Math.random() * 0.7;
      sp.userData.bob = 0.3 + Math.random() * 1.0;
      sp.userData.name = names[i];
      sp.userData.index = i;
      sp.userData.isDrawn = false;
      labels.add(sp);
    }
  })
  .catch(error => {
    console.error('加载用户文件失败:', error);
    // 如果加载失败,使用默认数据
    for (let i = 1; i <= 500; i++) {
      names.push('参与者' + i);
    }
    document.getElementById('totalCount').textContent = names.length;
    document.getElementById('remainCount').textContent = names.length;
    
    for (let i = 0; i < names.length; i++) {
      const sp = makeLabel(names[i]);
      const r = radius * (0.7 + Math.random() * 0.3);
      const phi = Math.random() * Math.PI * 2;
      const cost = Math.random() * 2 - 1;
      const sint = Math.sqrt(1 - cost * cost);
      sp.position.set(r * Math.cos(phi) * sint, r * cost, r * Math.sin(phi) * sint);
      sp.rotation.y = Math.random() * Math.PI * 2;
      sp.userData.phi = phi;
      sp.userData.r = r;
      sp.userData.speed = 0.3 + Math.random() * 0.7;
      sp.userData.bob = 0.3 + Math.random() * 1.0;
      sp.userData.name = names[i];
      sp.userData.index = i;
      sp.userData.isDrawn = false;
      labels.add(sp);
    }
  });

function makeLabel(text) {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  const padX = 50, padY = 35;
  ctx.font = '700 64px system-ui, -apple-system, Segoe UI, Microsoft YaHei';
  const metrics = ctx.measureText(text);
  const w = Math.ceil(metrics.width) + padX * 2;
  const h = 64 + padY * 2;
  const dpr = Math.min(3, window.devicePixelRatio || 1);
  canvas.width = Math.ceil(w * dpr);
  canvas.height = Math.ceil(h * dpr);
  ctx.scale(dpr, dpr);
  ctx.font = '700 64px system-ui, -apple-system, Segoe UI, Microsoft YaHei';
  const grd = ctx.createLinearGradient(0, 0, 0, h);
  grd.addColorStop(0, '#2d3748');
  grd.addColorStop(1, '#1a202c');
  ctx.fillStyle = grd;
  const r = 16;
  ctx.beginPath();
  ctx.moveTo(r, 0);
  ctx.arcTo(w, 0, w, h, r);
  ctx.arcTo(w, h, 0, h, r);
  ctx.arcTo(0, h, 0, 0, r);
  ctx.arcTo(0, 0, w, 0, r);
  ctx.closePath();
  ctx.fill();
  ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
  ctx.shadowBlur = 4;
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 2;
  ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
  ctx.lineWidth = 2;
  ctx.stroke();
  ctx.shadowColor = 'transparent';
  ctx.fillStyle = '#f0f4f8';
  ctx.textBaseline = 'middle';
  ctx.fillText(text, padX, h / 2);
  const tex = new THREE.CanvasTexture(canvas);
  tex.anisotropy = 16;
  tex.minFilter = THREE.LinearFilter;
  tex.magFilter = THREE.LinearFilter;
  const mat = new THREE.SpriteMaterial({ map: tex, transparent: true, depthWrite: false, color: 0xffffff });
  const sp = new THREE.Sprite(mat);
  const scale = 0.8;
  sp.scale.set(w * scale / 32, h * scale / 32, 1);
  sp.userData = { baseColor: 0xffffff };
  return sp;
}

const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
let highlighted = null;

function setPointer(event) {
  const rect = renderer.domElement.getBoundingClientRect();
  const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
  const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
  pointer.set(x, y);
}

function highlight(obj, isWinner = false) {
  if (highlighted && highlighted.material && !currentWinners.includes(highlighted)) {
    const color = highlighted.userData.isDrawn ? 0x666666 : highlighted.userData.baseColor;
    highlighted.material.color.set(color);
    highlighted.scale.set(highlighted.userData.baseScale.x, highlighted.userData.baseScale.y, 1);
  }
  highlighted = obj || null;
  if (highlighted && highlighted.material) {
    const color = isWinner ? 0xffd700 : (isLotteryRunning ? 0xff6b9d : 0x4ade80);
    highlighted.material.color.set(color);
    if (!highlighted.userData.baseScale) {
      highlighted.userData.baseScale = { x: highlighted.scale.x, y: highlighted.scale.y };
    }
    const scale = isWinner ? 1.8 : 1.3;
    highlighted.scale.set(highlighted.userData.baseScale.x * scale, highlighted.userData.baseScale.y * scale, 1);
  }
}

function setDrawn(sprite) {
  sprite.userData.isDrawn = true;
  sprite.material.color.set(0x666666);
  sprite.material.opacity = 0.4;
}

renderer.domElement.addEventListener('mousemove', (e) => {
  setPointer(e);
});

renderer.domElement.addEventListener('wheel', (e) => {
  e.preventDefault();
  camera.position.z += e.deltaY * 0.2;
  camera.position.z = THREE.MathUtils.clamp(camera.position.z, 120, 800);
}, { passive: false });

let drag = false, lastX = 0;
renderer.domElement.addEventListener('pointerdown', (e) => { drag = true; lastX = e.clientX; });
renderer.domElement.addEventListener('pointerup', () => { drag = false; });
renderer.domElement.addEventListener('pointerleave', () => { drag = false; });
renderer.domElement.addEventListener('pointermove', (e) => {
  if (!drag) return;
  const dx = e.clientX - lastX; lastX = e.clientX;
  labels.rotation.y += dx * 0.002;
});

let t = 0;
let lotteryTimer = null;
let randomHighlightCount = 0;
let techModeActive = false;
let scanLineY = -300;
let currentWinners = [];
const lotteryBtn = document.getElementById('lotteryBtn');
const resultDisplay = document.getElementById('resultDisplay');
const totalCountEl = document.getElementById('totalCount');
const drawnCountEl = document.getElementById('drawnCount');
const remainCountEl = document.getElementById('remainCount');

function updateStats() {
  drawnCountEl.textContent = drawnNames.size;
  remainCountEl.textContent = names.length - drawnNames.size;
}

// 保存抽奖记录到localStorage
function saveLotteryHistory(prizeLevel, winners) {
  const now = new Date();
  const timestamp = now.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false
  });
  
  // 构建记录内容
  let logContent = '';
  winners.forEach(winner => {
    // 格式:[时间] 奖项等级 - 姓名
    logContent += `[${timestamp}] ${prizeLevel} - ${winner.userData.name}\n`;
  });
  
  // 读取现有记录(从localStorage)
  let existingContent = localStorage.getItem('lotteryHistoryLog') || '';
  
  // 追加新记录
  const updatedContent = existingContent + logContent;
  
  // 保存到localStorage
  localStorage.setItem('lotteryHistoryLog', updatedContent);
  
  console.log(`✅ 已保存抽奖记录到localStorage: ${prizeLevel} - ${winners.length}人`);
  console.log('💾 累计记录行数:', updatedContent.split('\n').filter(line => line.trim()).length);
}

function createFirework(x, y) {
  const colors = ['#fbbf24', '#f59e0b', '#dc2626', '#ec4899', '#8b5cf6', '#ffd700'];
  for (let i = 0; i < 40; i++) {
    const fw = document.createElement('div');
    fw.className = 'firework';
    fw.style.left = x + 'px';
    fw.style.top = y + 'px';
    fw.style.background = colors[Math.floor(Math.random() * colors.length)];
    fw.style.boxShadow = `0 0 10px ${colors[Math.floor(Math.random() * colors.length)]}`;
    document.body.appendChild(fw);
    const angle = (Math.PI * 2 * i) / 40;
    const velocity = 3 + Math.random() * 4;
    const vx = Math.cos(angle) * velocity;
    const vy = Math.sin(angle) * velocity;
    let px = x, py = y, life = 80;
    function animateFw() {
      px += vx; py += vy + life * 0.04;
      life--;
      fw.style.left = px + 'px';
      fw.style.top = py + 'px';
      fw.style.opacity = life / 80;
      if (life > 0) requestAnimationFrame(animateFw);
      else fw.remove();
    }
    animateFw();
  }
}

function createConfetti() {
  const colors = ['#fbbf24', '#f59e0b', '#dc2626', '#ec4899', '#8b5cf6', '#3b82f6'];
  for (let i = 0; i < 100; i++) {
    setTimeout(() => {
      const confetti = document.createElement('div');
      confetti.className = 'confetti';
      confetti.style.left = Math.random() * window.innerWidth + 'px';
      confetti.style.top = '-20px';
      confetti.style.background = colors[Math.floor(Math.random() * colors.length)];
      confetti.style.transform = `rotate(${Math.random() * 360}deg)`;
      document.body.appendChild(confetti);
      
      let py = -20, px = parseFloat(confetti.style.left);
      const vx = (Math.random() - 0.5) * 3;
      const rotSpeed = (Math.random() - 0.5) * 20;
      let rotation = 0;
      
      function animateConfetti() {
        py += 3;
        px += vx;
        rotation += rotSpeed;
        confetti.style.top = py + 'px';
        confetti.style.left = px + 'px';
        confetti.style.transform = `rotate(${rotation}deg)`;
        if (py < window.innerHeight) requestAnimationFrame(animateConfetti);
        else confetti.remove();
      }
      animateConfetti();
    }, i * 20);
  }
}

function createClickEffect(x, y) {
  // 冲击波效果
  const shockwave = document.createElement('div');
  shockwave.className = 'shockwave';
  shockwave.style.left = (x - 250) + 'px';
  shockwave.style.top = (y - 250) + 'px';
  document.body.appendChild(shockwave);
  setTimeout(() => shockwave.remove(), 800);
  
  // 粒子爆炸效果
  const colors = ['#fbbf24', '#f59e0b', '#dc2626', '#ec4899', '#8b5cf6', '#3b82f6'];
  for (let i = 0; i < 25; i++) {
    const particle = document.createElement('div');
    particle.className = 'click-particle';
    particle.style.left = x + 'px';
    particle.style.top = y + 'px';
    particle.style.background = colors[Math.floor(Math.random() * colors.length)];
    particle.style.boxShadow = `0 0 8px ${colors[Math.floor(Math.random() * colors.length)]}`;
    document.body.appendChild(particle);
    
    const angle = (Math.PI * 2 * i) / 25 + (Math.random() - 0.5) * 0.5;
    const velocity = 4 + Math.random() * 5;
    const vx = Math.cos(angle) * velocity;
    const vy = Math.sin(angle) * velocity;
    let px = x, py = y, life = 50 + Math.random() * 30;
    const maxLife = life;
    
    function animateParticle() {
      px += vx;
      py += vy + (1 - life / maxLife) * 2.5;
      life--;
      particle.style.left = px + 'px';
      particle.style.top = py + 'px';
      particle.style.opacity = life / maxLife;
      particle.style.transform = `scale(${life / maxLife})`;
      if (life > 0) requestAnimationFrame(animateParticle);
      else particle.remove();
    }
    animateParticle();
  }
  
  // 按钮波纹效果
  lotteryBtn.classList.add('click-wave');
  setTimeout(() => lotteryBtn.classList.remove('click-wave'), 600);
}

function startLottery() {
  if (isLotteryRunning) return;
  const available = labels.children.filter(sp => !sp.userData.isDrawn);
  if (available.length === 0) {
    alert('所有人都已被抽取!');
    return;
  }
  
  // 获取奖项等级
  const prizeLevelSelect = document.getElementById('prizeLevelSelect');
  const selectedIndex = parseInt(prizeLevelSelect.value);
  
  if (isNaN(selectedIndex) || !prizeConfig[selectedIndex]) {
    alert('请选择奖项等级!');
    return;
  }
  
  const prizeLevel = prizeConfig[selectedIndex].name;
  
  // 优先使用输入框的人数,如果输入框为空或无效,则使用配置的人数
  const drawCountInput = document.getElementById('drawCountInput');
  let drawCount = parseInt(drawCountInput.value);
  
  // 如果输入框的值无效,使用配置文件中的人数
  if (isNaN(drawCount) || drawCount < 1) {
    drawCount = prizeConfig[selectedIndex].count;
    drawCountInput.value = drawCount;
  }
  
  // 限制抽取人数在合理范围内
  drawCount = Math.max(1, Math.min(drawCount, available.length));
  
  if (drawCount > available.length) {
    alert(`剩余人数不足!需要${drawCount}人,剩余${available.length}人`);
    return;
  }
  
  isLotteryRunning = true;
  techModeActive = true;
  lotteryBtn.innerHTML = '抽奖中...<div class="energy-ring"></div><div class="energy-ring" style="animation-delay:0.3s;"></div><div class="energy-ring" style="animation-delay:0.6s;"></div>';
  lotteryBtn.classList.add('running');
  resultDisplay.classList.remove('show');
  resultDisplay.innerHTML = '';
  
  // 场景变暗效果
  scene.background.set(0x050508);
  scene.fog.color.set(0x050508);
  ambient.intensity = 0.1;
  dir.intensity = 0.3;
  
  // 粒子系统激活
  particleMaterial.opacity = 1;
  particleMaterial.size = 3;
  lineMaterial.opacity = 0.8;
  lineMaterial.color.set(0xfbbf24);
  
  // 激活所有科技效果
  scanLineMaterial.opacity = 0.6;
  energySphereMaterial.opacity = 0.15;
  hexMaterial.opacity = 0.4;
  beams.forEach(beam => beam.material.opacity = 0.3);
  gridHelper.material.opacity = 0.3;
  dataStreams.forEach(stream => stream.material.opacity = 0.4);
  scanLineY = -300;
  
  // 播放抽奖音效提示
  console.log(`🎵 抽奖开始...本次抽取${drawCount}人`);
  
  // 清除上次中奖者高亮
  if (currentWinners.length > 0) {
    currentWinners.forEach(winner => setDrawn(winner));
    currentWinners = [];
  }
  
  randomHighlightCount = 0;
  const duration = 3000;
  const startTime = Date.now();
  lotteryTimer = setInterval(() => {
    const elapsed = Date.now() - startTime;
    const progress = elapsed / duration;
    if (progress >= 1) {
      clearInterval(lotteryTimer);
      
      // 随机抽取多个获奖者
      const winners = [];
      const selectedIndices = new Set();
      while (winners.length < drawCount && winners.length < available.length) {
        const randomIndex = Math.floor(Math.random() * available.length);
        if (!selectedIndices.has(randomIndex)) {
          selectedIndices.add(randomIndex);
          winners.push(available[randomIndex]);
        }
      }
      
      currentWinners = winners;
      
      // 高亮所有获奖者
      winners.forEach(winner => {
        drawnNames.add(winner.userData.name);
        highlight(winner, true);
      });
      
      // 显示结果
      const totalWinners = winners.length;
      const isSingleWinner = totalWinners === 1;
      const isManyWinners = totalWinners > 10;
      
      let resultHTML = `<div class="result-title">🎉 中奖名单(${totalWinners}人)`;
      resultHTML += `<span class="prize-level-badge">${prizeLevel}</span></div>`;
      
      let containerClass = 'winners-container';
      if (isSingleWinner) {
        containerClass += ' single-winner-container';
      } else if (isManyWinners) {
        containerClass += ' many-winners';
      }
      
      resultHTML += `<div class="${containerClass}">`;
      winners.forEach((winner, index) => {
        let labelClass = 'winner-label';
        if (isSingleWinner) {
          labelClass += ' single-winner';
        } else if (isManyWinners) {
          labelClass += ' compact';
        }
        resultHTML += `<div class="${labelClass}" style="animation-delay:${index * 0.05}s">`;
        resultHTML += `<div class="winner-name">${winner.userData.name}</div>`;
        resultHTML += `</div>`;
      });
      resultHTML += '</div>';
      
      resultDisplay.innerHTML = resultHTML;
      resultDisplay.classList.add('show');
      lotteryBtn.innerHTML = '再次抽奖<div class="energy-ring"></div><div class="energy-ring" style="animation-delay:0.3s;"></div><div class="energy-ring" style="animation-delay:0.6s;"></div>';
      lotteryBtn.classList.remove('running');
      isLotteryRunning = false;
      techModeActive = false;
      updateStats();
      
      // 显示返回按钮
      backBtn.classList.add('show');
      
      // 保存抽奖记录到本地文件
      saveLotteryHistory(prizeLevel, winners);
      
      // 恢复场景亮度
      scene.background.set(0x0a0e1a);
      scene.fog.color.set(0x0a0e1a);
      ambient.intensity = 0.25;
      dir.intensity = 0.8;
      
      // 粒子系统恢复
      particleMaterial.opacity = 0.6;
      particleMaterial.size = 2;
      lineMaterial.opacity = 0.3;
      lineMaterial.color.set(0x667eea);
      
      // 关闭科技效果
      scanLineMaterial.opacity = 0;
      energySphereMaterial.opacity = 0;
      hexMaterial.opacity = 0;
      beams.forEach(beam => beam.material.opacity = 0);
      gridHelper.material.opacity = 0.15;
      dataStreams.forEach(stream => stream.material.opacity = 0);
      
      // 烟花和彩带效果
      for (let i = 0; i < 8; i++) {
        setTimeout(() => {
          createFirework(
            window.innerWidth / 2 + (Math.random() - 0.5) * 600,
            window.innerHeight / 2 + (Math.random() - 0.5) * 400
          );
        }, i * 250);
      }
      createConfetti();
      camera.position.z = Math.max(200, camera.position.z - 100);
      return;
    }
    const interval = 50 + progress * 200;
    if (randomHighlightCount % Math.max(1, Math.floor(interval / 50)) === 0) {
      const randomSprite = available[Math.floor(Math.random() * available.length)];
      highlight(randomSprite, false);
    }
    randomHighlightCount++;
  }, 50);
}

lotteryBtn.addEventListener('click', (e) => {
  const rect = lotteryBtn.getBoundingClientRect();
  const x = rect.left + rect.width / 2;
  const y = rect.top + rect.height / 2;
  createClickEffect(x, y);
  
  // 镜头震动效果
  const originalZ = camera.position.z;
  let shakeTime = 0;
  const shakeInterval = setInterval(() => {
    shakeTime += 16;
    if (shakeTime < 300) {
      camera.position.x = (Math.random() - 0.5) * 8;
      camera.position.y = (Math.random() - 0.5) * 8;
    } else {
      clearInterval(shakeInterval);
      camera.position.x = 0;
      camera.position.y = 0;
    }
  }, 16);
  
  startLottery();
});

function resize() {
  const w = container.clientWidth;
  const h = container.clientHeight;
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h);
}
window.addEventListener('resize', resize);

function animate() {
  requestAnimationFrame(animate);
  t += 0.016;
  
  // 名字云旋转
  if (!techModeActive) {
    labels.rotation.y += 0.0008;
    labels.rotation.x = Math.sin(t * 0.3) * 0.1;
  } else {
    // 抽奖模式:从左往右快速旋转
    labels.rotation.y += 0.035;
    labels.rotation.x = Math.sin(t * 2) * 0.15;
    labels.rotation.z = 0;
  }
  
  // 粒子系统动画
  if (particleSystem) {
    particleSystem.rotation.y += 0.0005;
    particleSystem.rotation.x += 0.0003;
    if (techModeActive) {
      particleSystem.rotation.y += 0.002;
      particleSystem.rotation.x += 0.001;
      // 粒子颜色动态变化
      const colors = particleGeometry.attributes.color;
      for (let i = 0; i < particleCount; i++) {
        const hue = (t * 0.1 + i * 0.001) % 1;
        const color = new THREE.Color();
        color.setHSL(hue, 0.8, 0.6);
        colors.setXYZ(i, color.r, color.g, color.b);
      }
      colors.needsUpdate = true;
    }
  }
  
  // 科技圆环动画
  if (techCircle) {
    techCircle.rotation.z += 0.001;
    techCircle2.rotation.y += 0.0015;
    techCircle3.rotation.x += 0.0012;
    if (techModeActive) {
      techCircle.rotation.z += 0.004;
      techCircle2.rotation.y += 0.006;
      techCircle3.rotation.x += 0.005;
      // 圆环脉冲效果
      const scale = 1 + Math.sin(t * 3) * 0.1;
      techCircle.scale.set(scale, scale, scale);
      techCircle2.scale.set(scale * 1.1, scale * 1.1, scale * 1.1);
      techCircle3.scale.set(scale * 0.9, scale * 0.9, scale * 0.9);
    } else {
      techCircle.scale.set(1, 1, 1);
      techCircle2.scale.set(1, 1, 1);
      techCircle3.scale.set(1, 1, 1);
    }
  }
  
  // 扫描线动画
  if (scanLine && techModeActive) {
    scanLineY += 4;
    if (scanLineY > 300) scanLineY = -300;
    scanLine.position.y = scanLineY;
    scanLineMaterial.opacity = 0.6 + Math.sin(t * 5) * 0.3;
  }
  
  // 能量球动画
  if (energySphere) {
    energySphere.rotation.y += 0.001;
    energySphere.rotation.x += 0.0005;
    if (techModeActive) {
      energySphere.rotation.y += 0.003;
      energySphere.rotation.x += 0.002;
      const pulse = 1 + Math.sin(t * 2) * 0.15;
      energySphere.scale.set(pulse, pulse, pulse);
      energySphereMaterial.color.setHSL((t * 0.1) % 1, 0.8, 0.5);
    } else {
      energySphere.scale.set(1, 1, 1);
    }
  }
  
  // 六边形网格动画
  if (hexGrid) {
    hexGrid.rotation.z += 0.002;
    if (techModeActive) {
      hexGrid.rotation.z += 0.008;
      const scale = 1 + Math.sin(t * 2.5) * 0.12;
      hexGrid.scale.set(scale, scale, scale);
      hexMaterial.color.setHSL(0.3 + Math.sin(t * 2) * 0.2, 0.8, 0.5);
    } else {
      hexGrid.scale.set(1, 1, 1);
    }
  }
  
  // 光束动画
  if (beams.length > 0 && techModeActive) {
    beams.forEach((beam, i) => {
      beam.rotation.y += 0.01;
      const offset = (i / beams.length) * Math.PI * 2;
      beam.material.opacity = 0.2 + Math.sin(t * 3 + offset) * 0.2;
      beam.material.color.setHSL((t * 0.1 + i * 0.125) % 1, 0.8, 0.5);
      beam.scale.y = 1 + Math.sin(t * 4 + offset) * 0.3;
    });
  }
  
  // 星空闪烁
  if (stars) {
    stars.rotation.y += 0.0001;
    starMaterial.opacity = 0.6 + Math.sin(t * 0.5) * 0.2;
  }
  
  // 网格脉冲
  if (gridHelper && techModeActive) {
    gridHelper.position.y = -200 + Math.sin(t * 1.5) * 20;
  }
  
  // 数据流动画
  if (dataStreams.length > 0 && techModeActive) {
    dataStreams.forEach(stream => {
      stream.progress += 0.01;
      if (stream.progress > 1) stream.progress = 0;
      const opacity = Math.sin(stream.progress * Math.PI) * 0.6;
      stream.material.opacity = opacity;
      stream.material.color.setHSL(0.5 + stream.progress * 0.2, 0.8, 0.5);
    });
  }
  
  camera.lookAt(0, 0, 0);

  if (!isLotteryRunning) {
    raycaster.setFromCamera(pointer, camera);
    const intersects = raycaster.intersectObjects(labels.children, false);
    if (intersects.length > 0 && !intersects[0].object.userData.isDrawn) {
      highlight(intersects[0].object);
    } else if (highlighted && !currentWinners.includes(highlighted)) {
      highlight(null);
    }
  }

  renderer.render(scene, camera);
}
animate();

// 查看历史记录功能
const historyModal = document.getElementById('historyModal');
const historyLog = document.getElementById('historyLog');
const viewHistoryBtn = document.getElementById('viewHistoryBtn');
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
const closeHistoryBtn = document.getElementById('closeHistoryBtn');
const backBtn = document.getElementById('backBtn');

// 返回功能
backBtn.addEventListener('click', () => {
  // 隐藏中奖结果
  resultDisplay.classList.remove('show');
  resultDisplay.innerHTML = '';
  
  // 恢复按钮文字
  lotteryBtn.innerHTML = '开始抽奖<div class="energy-ring"></div><div class="energy-ring" style="animation-delay:0.3s;"></div><div class="energy-ring" style="animation-delay:0.6s;"></div>';
  
  // 隐藏返回按钮
  backBtn.classList.remove('show');
  
  // 清除当前中奖者高亮(但保留已抽取状态)
  if (currentWinners.length > 0) {
    currentWinners.forEach(winner => {
      if (winner.userData.isDrawn) {
        // 保持已抽取状态
        winner.material.color.set(0x666666);
        winner.material.opacity = 0.4;
      }
    });
    currentWinners = [];
  }
  
  console.log('↺ 已返回到开始状态');
});

// 查看记录
viewHistoryBtn.addEventListener('click', () => {
  const logContent = localStorage.getItem('lotteryHistoryLog') || '';
  
  if (logContent.trim()) {
    historyLog.textContent = logContent;
  } else {
    historyLog.innerHTML = '<div class="history-empty">📦 暂无抽奖记录</div>';
  }
  
  historyModal.classList.add('show');
});

// 关闭弹窗
closeHistoryBtn.addEventListener('click', () => {
  historyModal.classList.remove('show');
});

// 点击背景关闭
historyModal.addEventListener('click', (e) => {
  if (e.target === historyModal) {
    historyModal.classList.remove('show');
  }
});

// 清除记录
clearHistoryBtn.addEventListener('click', () => {
  if (confirm('确定要清除所有抽奖记录吗?此操作不可恢复!')) {
    localStorage.removeItem('lotteryHistoryLog');
    alert('✅ 记录已清除');
    console.log('🗑️ 抽奖历史记录已清空');
  }
});
</script>
</body>
</html>

2、配置文件

1、等级配置文件

level:
  特等奖: 1
  一等奖: 2
  二等奖: 3
  三等奖: 4
  幸运奖: 5
  参与奖: 5
  钻石奖: 5
  至尊奖: 5
  至尊奖: 5
2、部分功能预览


2、用户配置文件

张伟
李娜
王芳
刘洋
陈静
杨明
黄丽
周杰
吴强
徐敏
孙涛
马超
朱婷
胡军
郭鹏
.......

0

评论区