您当前的位置:首页 > 计算机 > 编程开发 > Html+Div+Css(前端)

一套纯Html点名工具,专为教师课堂互动设计(带语音)

时间:08-24来源:作者:点击数:

纯Html点名系统(就一个HTML),带语音播报功能(保护喉咙),零依赖、无需网络、浏览器打开即用(可以按F11全屏),专为教师课堂互动设计。

五大功能:

上课提问:随机抽取学生,支持权重记忆

顺序点名:按名单顺序循环

随机点名:完全随机,公平无偏见

快速连抽:连续抽取多名学生,适合小组活动

计时器模式:倒计时,增加课堂紧张感。

调用的是系统语音(谷歌浏览器可能需要授权),目前测试谷歌内核和火狐浏览器没有问题。右上角点击语音按钮可以关闭。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="default">
    <title>高颜值语音点名器 v2.0</title>
    <!-- Classroom Voice Roll-Call v2.0 | MIT License | 仅供教学场景 -->
    <style>
        :root {
            /* 马卡龙主题(默认) */
            --bg: linear-gradient(135deg, #ffeef8 0%, #e6f3ff 100%);
            --card-bg: rgba(255, 255, 255, 0.9);
            --primary: #ff6b6b;
            --secondary: #ffd93d;
            --accent: #4ecdc4;
            --text: #2d3436;
            --text-light: #636e72;
            --border: rgba(255, 107, 107, 0.2);
            --shadow: rgba(255, 107, 107, 0.15);
            --success: #00b894;
            --warning: #fdcb6e;
            --error: #e17055;
            --primary-rgb: 255, 107, 107;
            --secondary-rgb: 255, 217, 61;
            --accent-rgb: 78, 205, 196;
        }
 
 
 
        [data-theme="geek"] {
            --bg: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 100%);
            --card-bg: rgba(25, 25, 35, 0.95);
            --primary: #00d4ff;
            --secondary: #0099cc;
            --accent: #ff6b35;
            --text: #e8e8e8;
            --text-light: #a0a0a0;
            --border: rgba(0, 212, 255, 0.2);
            --shadow: rgba(0, 0, 0, 0.5);
            --primary-rgb: 0, 212, 255;
            --secondary-rgb: 0, 153, 204;
            --accent-rgb: 255, 107, 53;
        }
 
        [data-theme="purple"] {
            --bg: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
            --card-bg: rgba(255, 255, 255, 0.15);
            --primary: #a855f7;
            --secondary: #c084fc;
            --accent: #f472b6;
            --text: #ffffff;
            --text-light: #e9d5ff;
            --border: rgba(168, 85, 247, 0.4);
            --shadow: rgba(102, 126, 234, 0.3);
            --primary-rgb: 168, 85, 247;
            --secondary-rgb: 192, 132, 252;
            --accent-rgb: 244, 114, 182;
        }
 
        [data-theme="purple"] body {
            background: var(--bg);
            background-attachment: fixed;
            background-size: 400% 400%;
            animation: gradientShift 8s ease infinite;
        }
 
        @keyframes gradientShift {
            0% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }
 
        [data-theme="purple"] .drawer-header {
            background: linear-gradient(135deg, rgba(168, 85, 247, 0.9), rgba(192, 132, 252, 0.9), rgba(244, 114, 182, 0.9));
            backdrop-filter: blur(15px);
            border-bottom: 1px solid rgba(168, 85, 247, 0.3);
        }
 
        [data-theme="purple"] .theme-btn--purple {
            background: linear-gradient(135deg, #a855f7, #c084fc, #f472b6) !important;
            box-shadow: 0 4px 15px rgba(168, 85, 247, 0.4);
            transition: all 0.3s ease;
        }
 
        [data-theme="purple"] .theme-btn--purple:hover {
            transform: scale(1.1) rotate(5deg);
            box-shadow: 0 6px 20px rgba(168, 85, 247, 0.6);
        }
 
        [data-theme="warm"] {
            --bg: linear-gradient(135deg, #fff5f5 0%, #ffe8e8 100%);
            --card-bg: rgba(255, 255, 255, 0.9);
            --primary: #ff6b6b;
            --secondary: #ff5252;
            --accent: #4ecdc4;
            --text: #2d3436;
            --text-light: #636e72;
            --border: rgba(255, 107, 107, 0.2);
            --shadow: rgba(255, 107, 107, 0.15);
            --primary-rgb: 255, 107, 107;
            --secondary-rgb: 255, 82, 82;
            --accent-rgb: 78, 205, 196;
        }
 
        [data-theme="nature"] {
            --bg: linear-gradient(135deg, #f0f9f0 0%, #e8f5e8 100%);
            --card-bg: rgba(255, 255, 255, 0.9);
            --primary: #4caf50;
            --secondary: #388e3c;
            --accent: #ff9800;
            --text: #1b5e20;
            --text-light: #4caf50;
            --border: rgba(76, 175, 80, 0.2);
            --shadow: rgba(76, 175, 80, 0.15);
            --primary-rgb: 76, 175, 80;
            --secondary-rgb: 56, 142, 60;
            --accent-rgb: 255, 152, 0;
        }
 
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
 
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
            background: var(--bg);
            min-height: 100vh;
            color: var(--text);
            overflow-x: hidden;
        }
 
        .container {
            display: flex;
            height: 100vh;
            position: relative;
        }
 
        /* 顶部栏 */
        .header {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            height: 60px;
            background: var(--card-bg);
            backdrop-filter: blur(10px);
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0 20px;
            box-shadow: 0 2px 10px var(--shadow);
            z-index: 1000;
        }
 
        .logo {
            font-size: 20px;
            font-weight: bold;
            background: linear-gradient(135deg, var(--primary), var(--secondary));
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }
 
        .header-controls {
            display: flex;
            gap: 15px;
            align-items: center;
        }
 
        .voice-toggle {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 8px 12px;
            background: var(--card-bg);
            border: 1px solid var(--border);
            border-radius: 20px;
            cursor: pointer;
            transition: all 0.3s ease;
        }
 
        .voice-toggle:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px var(--shadow);
        }
 
        .theme-selector {
            display: flex;
            gap: 5px;
            padding: 5px;
            background: var(--card-bg);
            border: 1px solid var(--border);
            border-radius: 25px;
        }
 
        .theme-btn {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            border: 2px solid transparent;
            cursor: pointer;
            transition: all 0.3s ease;
        }
 
        .theme-btn.active {
            border-color: var(--primary);
            transform: scale(1.1);
        }
 
        /* 左侧名单抽屉 */
        .drawer {
            position: fixed;
            left: 0;
            top: 60px;
            bottom: 0;
            width: 320px;
            background: var(--card-bg);
            backdrop-filter: blur(20px);
            box-shadow: 0 0 30px var(--shadow);
            transform: translateX(-100%);
            transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
            z-index: 1001;
            overflow-y: auto;
            border-right: 1px solid var(--border);
        }
 
        .drawer.open {
            transform: translateX(0);
            box-shadow: 0 0 40px var(--shadow), 0 0 80px rgba(0, 0, 0, 0.1);
        }
 
        .drawer-header {
            padding: 25px 20px;
            border-bottom: 1px solid var(--border);
            display: flex;
            justify-content: space-between;
            align-items: center;
            background: linear-gradient(135deg, var(--primary), var(--secondary));
            color: white;
            position: sticky;
            top: 0;
            z-index: 1002;
            backdrop-filter: blur(10px);
        }
 
        .drawer-header h3 {
            font-size: 20px;
            font-weight: 600;
            margin: 0;
            letter-spacing: 0.5px;
        }
 
        .drawer-content {
            padding: 25px 20px;
            background: linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.05) 100%);
        }
 
        .student-input {
            width: 100%;
            min-height: 250px;
            padding: 15px;
            border: 2px solid var(--border);
            border-radius: 12px;
            background: var(--card-bg);
            color: var(--text);
            resize: vertical;
            font-size: 14px;
            line-height: 1.6;
            transition: all 0.3s ease;
            box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.05);
        }
 
        .student-input:focus {
            outline: none;
            border-color: var(--primary);
            box-shadow: 0 0 0 3px rgba(var(--primary-rgb, 255, 107, 107), 0.1), inset 0 2px 8px rgba(0, 0, 0, 0.05);
        }
 
        .student-input::placeholder {
            color: var(--text-light);
            font-style: italic;
        }
 
        .drawer-actions {
            display: flex;
            gap: 12px;
            margin-top: 20px;
            justify-content: center;
            flex-wrap: wrap;
        }
 
        .drawer-actions .btn {
            padding: 12px 20px;
            border-radius: 25px;
            font-size: 14px;
            font-weight: 500;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            box-shadow: 0 2px 8px var(--shadow);
            border: none;
            position: relative;
            overflow: hidden;
        }
 
        .drawer-actions .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px var(--shadow);
        }
 
        .drawer-actions .btn:active {
            transform: translateY(0);
        }
 
        .drawer-actions .btn-primary {
            background: linear-gradient(135deg, var(--primary), var(--secondary));
            color: white;
        }
 
        .drawer-actions .btn-secondary {
            background: var(--card-bg);
            color: var(--text);
            border: 1px solid var(--border);
        }
 
        .drawer-actions .btn-secondary:hover {
            background: var(--primary);
            color: white;
            border-color: var(--primary);
        }
 
        /* 右侧历史抽屉 */
        .history {
            position: fixed;
            right: 0;
            top: 60px;
            bottom: 0;
            width: 380px;
            background: var(--card-bg);
            backdrop-filter: blur(20px);
            box-shadow: 0 0 30px var(--shadow);
            transform: translateX(100%);
            transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
            z-index: 1001;
            overflow-y: auto;
            border-left: 1px solid var(--border);
        }
 
        .history.open {
            transform: translateX(0);
            box-shadow: 0 0 40px var(--shadow), 0 0 80px rgba(0, 0, 0, 0.1);
        }
 
        .history-header {
            padding: 25px 20px;
            border-bottom: 1px solid var(--border);
            display: flex;
            justify-content: space-between;
            align-items: center;
            background: linear-gradient(135deg, var(--secondary), var(--accent));
            color: white;
            position: sticky;
            top: 0;
            z-index: 1002;
            backdrop-filter: blur(10px);
        }
 
        .history-header h3 {
            font-size: 20px;
            font-weight: 600;
            margin: 0;
            letter-spacing: 0.5px;
        }
 
        #historyList {
            padding: 25px 20px;
            background: linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.05) 100%);
        }
 
        .history-item {
            padding: 20px;
            margin-bottom: 15px;
            border: 1px solid var(--border);
            border-radius: 12px;
            background: var(--card-bg);
            box-shadow: 0 2px 8px var(--shadow);
            transition: all 0.3s ease;
            position: relative;
            overflow: hidden;
        }
 
        .history-item:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px var(--shadow);
            border-color: var(--primary);
        }
 
        .history-item::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            width: 4px;
            height: 100%;
            background: linear-gradient(135deg, var(--primary), var(--accent));
            border-radius: 2px;
        }
 
        .history-item-content {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            gap: 10px;
        }
 
        .history-name {
            font-size: 16px;
            font-weight: 600;
            color: var(--text);
            margin-bottom: 5px;
        }
 
        .history-mode {
            font-size: 12px;
            color: var(--primary);
            background: rgba(var(--primary-rgb, 255, 107, 107), 0.1);
            padding: 2px 8px;
            border-radius: 10px;
            margin-bottom: 5px;
            display: inline-block;
        }
 
        .history-time {
            font-size: 12px;
            color: var(--text-light);
            font-family: monospace;
            white-space: nowrap;
        }
 
        /* 主舞台 */
        .main {
            flex: 1;
            margin: 60px 0 180px 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 20px;
            transition: all 0.3s ease;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            z-index: 10;
            pointer-events: none;
        }
         
        .main .card {
            pointer-events: auto;
        }
 
        .card {
            background: var(--card-bg);
            backdrop-filter: blur(10px);
            border-radius: 20px;
            padding: 80px;
            text-align: center;
            box-shadow: 0 15px 40px var(--shadow);
            max-width: 600px;
            width: 90%;
            position: relative;
            overflow: hidden;
            transform: scale(1.1);
        }
 
        .card__name {
            font-size: 56px;
            font-weight: bold;
            margin-bottom: 30px;
            min-height: 70px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.3s ease;
            word-wrap: break-word;
            word-break: break-all;
            line-height: 1.2;
            max-height: 200px;
            overflow: hidden;
        }
         
        .card__name.long-text {
            font-size: 36px;
        }
         
        .card__name.very-long-text {
            font-size: 24px;
        }
         
        .card__name.extra-long-text {
            font-size: 18px;
        }
 
        .progress-bar {
            height: 4px;
            background: var(--border);
            border-radius: 2px;
            overflow: hidden;
            margin: 20px 0;
        }
 
        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, var(--primary), var(--accent));
            transition: width 0.3s ease;
        }
 
        .timer-display {
            font-size: 36px;
            font-weight: bold;
            color: var(--primary);
            margin: 20px 0;
        }
 
        /* 控制面板 */
        .controls {
            position: fixed;
            bottom: 0;
            left: 0;
            right: 0;
            background: var(--card-bg);
            backdrop-filter: blur(10px);
            padding: 20px;
            box-shadow: 0 -2px 10px var(--shadow);
        }
 
        .control-row {
            display: flex;
            gap: 15px;
            align-items: center;
            justify-content: center;
            flex-wrap: wrap;
            margin-bottom: 15px;
        }
 
        .mode-tabs {
            display: flex;
            background: var(--card-bg);
            border: 1px solid var(--border);
            border-radius: 25px;
            padding: 5px;
        }
 
        .mode-tab {
            padding: 10px 20px;
            border: none;
            background: none;
            color: var(--text);
            cursor: pointer;
            border-radius: 20px;
            transition: all 0.3s ease;
        }
 
        .mode-tab.active {
            background: var(--primary);
            color: white;
        }
 
        .btn {
            padding: 12px 24px;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            font-size: 16px;
            transition: all 0.3s ease;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }
 
        .btn-export:hover {
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
        }
 
        .btn-clear:hover {
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3);
        }
 
        .btn-close:hover {
            background: var(--border);
            transform: scale(1.1);
        }
 
        .btn-primary {
            background: var(--primary);
            color: white;
        }
 
        .btn-secondary {
            background: var(--text-light);
            color: white;
        }
 
        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px var(--shadow);
        }
 
        .slider-container {
            display: flex;
            align-items: center;
            gap: 10px;
            min-width: 200px;
        }
 
        .slider {
            flex: 1;
            height: 4px;
            background: var(--border);
            border-radius: 2px;
            outline: none;
            -webkit-appearance: none;
        }
 
        .slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 20px;
            height: 20px;
            background: var(--primary);
            border-radius: 50%;
            cursor: pointer;
        }
         
        /* 移动端触摸优化 */
        @media (max-width: 768px) {
            * {
                -webkit-tap-highlight-color: transparent;
                -webkit-touch-callout: none;
                -webkit-user-select: none;
                user-select: none;
            }
             
            body {
                -webkit-overflow-scrolling: touch;
                overscroll-behavior: contain;
            }
             
            .slider::-webkit-slider-thumb {
                width: 24px;
                height: 24px;
            }
             
            .btn, .mode-tab {
                min-height: 44px;
                touch-action: manipulation;
                -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
            }
             
            .drawer-toggle {
                min-width: 44px;
                min-height: 44px;
                touch-action: manipulation;
                -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
            }
             
            .voice-toggle, .theme-selector {
                min-height: 36px;
                touch-action: manipulation;
                -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
            }
             
            .theme-btn {
                min-width: 36px;
                min-height: 36px;
                touch-action: manipulation;
                -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
            }
             
            .card {
                -webkit-tap-highlight-color: rgba(0, 0, 0, 0.05);
            }
             
            .slider {
                touch-action: pan-x;
            }
             
            /* 移动端动画优化 */
            .card, .btn, .mode-tab {
                will-change: transform;
            }
             
            /* 防止页面缩放 */
            input, textarea, select {
                font-size: 16px !important;
            }
        }
 
        /* 弹幕效果 */
        .barrage {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 1001;
        }
 
        .barrage-item {
            position: absolute;
            background: var(--primary);
            color: white;
            padding: 15px 25px;
            border-radius: 25px;
            font-size: 18px;
            font-weight: bold;
            box-shadow: 0 4px 12px var(--shadow);
            animation: flyIn 0.8s ease-out;
        }
 
        @keyframes flyIn {
            from {
                transform: translateX(100vw) translateY(-50px);
                opacity: 0;
            }
            to {
                transform: translateX(calc(100vw - 200px)) translateY(0);
                opacity: 1;
            }
        }
 
        /* 3D 翻转卡片 */
        .card-3d {
            perspective: 1000px;
        }
 
        .card-inner {
            position: relative;
            width: 100%;
            height: 200px;
            transition: transform 1.2s;
            transform-style: preserve-3d;
        }
 
        .card-3d.flipping .card-inner {
            transform: rotateY(180deg);
        }
 
        .card-face {
            position: absolute;
            width: 100%;
            height: 100%;
            backface-visibility: hidden;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 36px;
            font-weight: bold;
        }
 
        .card-back {
            transform: rotateY(180deg);
            background: var(--accent);
            color: white;
        }
 
        /* 粒子背景 */
        #particles-canvas {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 1;
        }
 
        /* 抽屉切换按钮 */
        .drawer-toggle {
            position: fixed;
            top: 50%;
            transform: translateY(-50%);
            background: var(--primary);
            color: white;
            border: none;
            padding: 15px 12px;
            cursor: pointer;
            z-index: 998;
            transition: all 0.3s ease;
            font-size: 18px;
            font-weight: bold;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 45px;
            height: 45px;
            box-shadow: 0 2px 10px var(--shadow);
        }
 
        .drawer-toggle:hover {
            transform: translateY(-50%) scale(1.1);
            box-shadow: 0 4px 15px var(--shadow);
        }
 
        .drawer-toggle.left {
            left: 0;
            border-radius: 0 25px 25px 0;
        }
 
        .drawer-toggle.right {
            right: 0;
            border-radius: 25px 0 0 25px;
        }
 
        .drawer-toggle .icon {
            display: inline-block;
            line-height: 1;
            text-align: center;
        }
 
        /* 移动端优化增强版 */
        @media (max-width: 768px) {
            :root {
                --mobile-padding: 12px;
                --mobile-radius: 12px;
                --mobile-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
            }
             
            body {
                font-size: 14px;
                -webkit-text-size-adjust: 100%;
                -webkit-font-smoothing: antialiased;
                -moz-osx-font-smoothing: grayscale;
                overscroll-behavior-y: contain;
                touch-action: manipulation;
            }
             
            /* 移动端安全区域适配 */
            .header {
                padding: max(12px, env(safe-area-inset-top)) var(--mobile-padding);
                height: auto;
                min-height: 60px;
                flex-wrap: wrap;
                gap: 8px;
                background: rgba(255, 255, 255, 0.95);
                backdrop-filter: blur(10px);
                border-radius: 0 0 var(--mobile-radius) var(--mobile-radius);
            }
             
            .logo {
                font-size: 16px;
                font-weight: 600;
            }
             
            .header-controls {
                gap: 8px;
            }
             
            .voice-toggle {
                padding: 6px 10px;
                font-size: 12px;
                border-radius: 16px;
            }
             
            .theme-selector {
                padding: 3px;
                border-radius: 20px;
            }
             
            .theme-btn {
                width: 24px;
                height: 24px;
                border-radius: 50%;
                transition: all 0.2s ease;
            }
             
            .theme-btn:active {
                transform: scale(0.9);
            }
             
            /* 抽屉全屏优化 */
            .drawer, .history {
                width: 100%;
                border-radius: 0;
                top: 0;
                bottom: 0;
                padding-top: env(safe-area-inset-top);
                padding-bottom: env(safe-area-inset-bottom);
                background: rgba(255, 255, 255, 0.98);
                backdrop-filter: blur(20px);
                z-index: 1001; /* 确保在最上层 */
            }
             
            .drawer-header, .history-header {
                position: sticky;
                top: 0;
                background: rgba(255, 255, 255, 0.98);
                backdrop-filter: blur(20px);
                z-index: 1002;
                padding: max(15px, env(safe-area-inset-top)) var(--mobile-padding) 15px;
                display: flex;
                justify-content: space-between;
                align-items: center;
                border-bottom: 1px solid var(--border);
            }
             
            .drawer-header h3, .history-header h3 {
                margin: 0;
                font-size: 18px;
                font-weight: 600;
            }
             
            .drawer-header .btn, .history-header .btn {
                width: auto;
                min-width: 36px;
                height: 36px;
                padding: 8px;
                font-size: 20px;
                font-weight: bold;
                border-radius: 50%;
                background: rgba(0, 0, 0, 0.05);
                color: var(--text);
                display: flex;
                align-items: center;
                justify-content: center;
                margin: 0;
            }
             
            .drawer-header, .history-header {
                padding: max(20px, env(safe-area-inset-top)) var(--mobile-padding) 15px;
                font-size: 16px;
                font-weight: 600;
                border-bottom: 1px solid var(--border);
            }
             
            .drawer-content, .history-content {
                padding: 0 var(--mobile-padding) max(20px, env(safe-area-inset-bottom));
            }
             
            .student-input {
                width: 100%;
                min-height: 180px;
                padding: 12px;
                border: 1px solid var(--border);
                border-radius: var(--mobile-radius);
                background: var(--card-bg);
                color: var(--text);
                font-size: 16px;
                line-height: 1.4;
                resize: vertical;
            }
             
            .main {
                padding: var(--mobile-padding);
                margin: 80px 0 220px;
                padding-bottom: calc(200px + env(safe-area-inset-bottom));
            }
             
            /* 卡片移动端优化 */
            .card {
                padding: 30px 20px;
                margin: 0 auto;
                max-width: 95%;
                border-radius: var(--mobile-radius);
                box-shadow: var(--mobile-shadow);
                background: rgba(255, 255, 255, 0.9);
                backdrop-filter: blur(8px);
                transition: all 0.3s ease;
            }
             
            .card:hover {
                transform: translateY(-1px);
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            }
             
            .card__name {
                font-size: 28px;
                font-weight: 600;
                margin-bottom: 20px;
                line-height: 1.3;
                word-break: break-word;
                hyphens: auto;
            }
             
            .card__name.long-text {
                font-size: 22px;
            }
             
            .card__name.very-long-text {
                font-size: 18px;
            }
             
            .card__name.extra-long-text {
                font-size: 16px;
            }
             
            .timer-display {
                font-size: 72px !important;
                margin: 30px 0 !important;
                font-weight: 800 !important;
                font-family: 'Courier New', 'Consolas', 'Monaco', monospace !important;
                letter-spacing: 4px !important;
                text-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important;
                line-height: 1 !important;
                color: var(--primary) !important;
                text-align: center !important;
                min-height: 100px !important;
                display: flex;
                align-items: center;
                justify-content: center;
            }
             
            /* 控制面板移动端优化 */
            .controls {
                position: fixed;
                bottom: 0;
                left: 0;
                right: 0;
                z-index: 999;
                max-height: 50vh;
                overflow-y: auto;
                background: rgba(255, 255, 255, 0.98);
                backdrop-filter: blur(20px);
                box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
                padding: max(15px, env(safe-area-inset-bottom)) var(--mobile-padding);
                border-radius: var(--mobile-radius) var(--mobile-radius) 0 0;
            }
             
            .control-row {
                flex-direction: column;
                align-items: stretch;
                gap: 10px;
                margin-bottom: 10px;
            }
             
            .mode-tabs {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
                gap: 6px;
                padding: 8px;
                background: rgba(0, 0, 0, 0.03);
                border-radius: var(--mobile-radius);
                border: none;
                margin-bottom: 15px;
            }
             
            .mode-tab {
                padding: 10px 8px;
                font-size: 12px;
                min-width: auto;
                text-align: center;
                border-radius: 8px;
                font-weight: 500;
                transition: all 0.2s ease;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
                touch-action: manipulation;
            }
             
            .mode-tab.active {
                transform: none;
                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
            }
             
            .btn {
                padding: 14px 20px;
                font-size: 15px;
                width: 100%;
                justify-content: center;
                border-radius: var(--mobile-radius);
                font-weight: 600;
                transition: all 0.15s ease;
                min-height: 48px;
                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
                touch-action: manipulation;
            }
             
            .btn:active {
                transform: scale(0.97);
                box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
            }
             
            .slider-container {
                flex-direction: column;
                align-items: stretch;
                min-width: auto;
                gap: 8px;
                background: rgba(0, 0, 0, 0.02);
                padding: 12px;
                border-radius: var(--mobile-radius);
            }
             
            .slider-container label {
                text-align: center;
                font-size: 13px;
                font-weight: 500;
                color: var(--text-light);
            }
             
            .slider {
                height: 4px;
                border-radius: 2px;
                background: var(--border);
                touch-action: pan-x;
            }
             
            .slider::-webkit-slider-thumb {
                width: 20px;
                height: 20px;
                border-radius: 50%;
                background: var(--primary);
                cursor: pointer;
            }
             
            /* 抽屉切换按钮移动端优化 */
            .drawer-toggle {
                width: 40px;
                height: 40px;
                padding: 10px;
                font-size: 16px;
                border-radius: 50%;
                background: rgba(255, 255, 255, 0.9);
                color: var(--primary);
                box-shadow: var(--mobile-shadow);
                backdrop-filter: blur(8px);
                touch-action: manipulation;
            }
             
            .drawer-toggle.left {
                left: var(--mobile-padding);
                border-radius: 50%;
            }
             
            .drawer-toggle.right {
                right: var(--mobile-padding);
                border-radius: 50%;
            }
             
            /* 弹幕效果移动端优化 */
            .barrage-item {
                font-size: 14px;
                padding: 8px 16px;
                border-radius: 16px;
                box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
                font-weight: 500;
            }
             
            @keyframes flyIn {
                from {
                    transform: translateX(100vw) translateY(-30px);
                    opacity: 0;
                }
                to {
                    transform: translateX(calc(100vw - 120px)) translateY(0);
                    opacity: 1;
                }
            }
             
            /* 3D卡片移动端优化 */
            .card-3d .card-inner {
                height: 120px;
            }
             
            .card-face {
                font-size: 20px;
                font-weight: 600;
            }
             
            /* 防止双击缩放 */
            .btn, .mode-tab, .drawer-toggle {
                touch-action: manipulation;
                -webkit-tap-highlight-color: transparent;
            }
             
            /* 滚动条优化 */
            .drawer, .history {
                scrollbar-width: thin;
                scrollbar-color: var(--primary) transparent;
            }
             
            .drawer::-webkit-scrollbar, .history::-webkit-scrollbar {
                width: 4px;
            }
             
            .drawer::-webkit-scrollbar-thumb, .history::-webkit-scrollbar-thumb {
                background-color: var(--primary);
                border-radius: 2px;
            }
             
            /* 横屏模式优化 */
            @media (max-height: 500px) and (orientation: landscape) {
                .header {
                    min-height: 50px;
                    padding: 8px var(--mobile-padding);
                }
                 
                .controls {
                    padding: 8px var(--mobile-padding);
                }
                 
                .card {
                    padding: 20px 15px;
                    max-width: 80%;
                }
                 
                .card__name {
                    font-size: 22px;
                    margin-bottom: 15px;
                }
                 
                .timer-display {
                    font-size: 100px !important;
                    margin: 30px 0 !important;
                    letter-spacing: 6px !important;
                    min-height: 120px !important;
                    font-weight: 800 !important;
                    font-family: 'Courier New', 'Consolas', 'Monaco', monospace !important;
                    text-align: center !important;
                    color: var(--primary) !important;
                }
            }
        }
         
        /* iPhone安全区域适配 */
        @Supports (padding-top: env(safe-area-inset-top)) {
            :root {
                --safe-top: env(safe-area-inset-top);
                --safe-bottom: env(safe-area-inset-bottom);
                --safe-left: env(safe-area-inset-left);
                --safe-right: env(safe-area-inset-right);
            }
             
            .header {
                padding-top: calc(12px + var(--safe-top));
            }
             
            .drawer, .history {
                padding-top: var(--safe-top);
                padding-bottom: var(--safe-bottom);
                z-index: 1001;
            }
             
            .drawer-header, .history-header {
                position: sticky;
                top: 0;
                background: rgba(255, 255, 255, 0.98);
                backdrop-filter: blur(20px);
                z-index: 1002;
                padding: max(12px, env(safe-area-inset-top)) var(--mobile-padding) 12px;
                display: flex;
                justify-content: space-between;
                align-items: center;
                border-bottom: 1px solid var(--border);
            }
             
            .drawer-header h3, .history-header h3 {
                margin: 0;
                font-size: 16px;
                font-weight: 600;
            }
             
            .drawer-header .btn, .history-header .btn {
                width: auto;
                min-width: 32px;
                height: 32px;
                padding: 6px;
                font-size: 18px;
                font-weight: bold;
                border-radius: 50%;
                background: rgba(0, 0, 0, 0.05);
                color: var(--text);
                display: flex;
                align-items: center;
                justify-content: center;
                margin: 0;
            }
             
            .controls {
                padding-bottom: calc(15px + var(--safe-bottom));
            }
             
            .main {
                padding-top: calc(var(--safe-top) + 10px);
                padding-bottom: calc(var(--safe-bottom) + 10px);
            }
        }
 
        /* 中等屏幕优化 */
        @media (max-width: 768px) and (min-width: 481px) {
            .card {
                padding: 100px 40px;
                min-height: 400px;
                height: auto;
                max-height: 75vh;
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                border-radius: 28px;
                box-shadow: 
                    0 12px 40px rgba(0, 0, 0, 0.15),
                    0 4px 12px rgba(0, 0, 0, 0.1);
            }
             
            .card__name {
                font-size: 48px;
                line-height: 1.5;
                font-weight: 800;
                margin-bottom: 0;
                min-height: 120px;
                background: linear-gradient(135deg, var(--primary), var(--secondary));
                background-clip: text;
                -webkit-background-clip: text;
                -webkit-text-fill-color: transparent;
                text-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
                letter-spacing: 2px;
            }
             
            .timer-display {
                font-size: 80px !important;
                margin: 25px 0 !important;
                letter-spacing: 3px !important;
                min-height: 100px !important;
                font-weight: 800 !important;
                font-family: 'Courier New', 'Consolas', 'Monaco', monospace !important;
                text-align: center !important;
                color: var(--primary) !important;
                word-break: break-all;
                overflow-wrap: break-word;
            }
        }
         
        /* 超小屏手机优化 */
        @media (max-width: 480px) {
            :root {
                --mobile-padding: 10px;
                --mobile-radius: 10px;
            }
             
            body {
                font-size: 13px;
                -webkit-text-size-adjust: 100%;
            }
             
            .main {
                padding: var(--mobile-padding);
                margin: 0;
                padding-bottom: calc(180px + env(safe-area-inset-bottom));
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                height: 100vh;
            }
             
            .controls {
                position: fixed;
                bottom: 0;
                left: 0;
                right: 0;
                z-index: 999;
                max-height: 50vh;
                overflow-y: auto;
                background: var(--card-bg);
                backdrop-filter: blur(20px);
                box-shadow: 0 -4px 20px var(--shadow);
                padding: max(12px, env(safe-area-inset-bottom)) var(--mobile-padding);
                border-radius: var(--mobile-radius) var(--mobile-radius) 0 0;
                border-top: 1px solid var(--border);
                pointer-events: auto;
            }
             
            .mode-tabs {
                position: relative;
                z-index: 1000;
                pointer-events: auto;
            }
             
            .mode-tab {
                position: relative;
                z-index: 1001;
                pointer-events: auto;
            }
             
            .header {
                padding: max(10px, env(safe-area-inset-top)) var(--mobile-padding);
                min-height: 55px;
                gap: 6px;
            }
             
            .logo {
                font-size: 15px;
                font-weight: 600;
            }
             
            .card {
                padding: 80px 25px;
                margin: 0 auto;
                max-width: 92%;
                border-radius: 24px;
                box-shadow: 
                    0 8px 32px rgba(0, 0, 0, 0.12),
                    0 2px 8px rgba(0, 0, 0, 0.08),
                    inset 0 1px 0 rgba(255, 255, 255, 0.1);
                min-height: 280px;
                height: auto;
                max-height: 70vh;
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                background: rgba(255, 255, 255, 0.95);
                backdrop-filter: blur(20px);
                border: 1px solid rgba(255, 255, 255, 0.2);
                position: relative;
                overflow: hidden;
            }
             
            .card::before {
                content: '';
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                height: 1px;
                background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
            }
             
            .card__name {
                font-size: 36px;
                line-height: 1.4;
                font-weight: 800;
                margin-bottom: 0;
                display: flex;
                align-items: center;
                justify-content: center;
                text-align: center;
                width: 100%;
                min-height: 100px;
                background: linear-gradient(135deg, var(--primary), var(--secondary));
                background-clip: text;
                -webkit-background-clip: text;
                -webkit-text-fill-color: transparent;
                text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
                letter-spacing: 1px;
                word-break: break-word;
                hyphens: auto;
                padding: 0 10px;
            }
             
            .card__name.long-text {
                font-size: 20px;
            }
             
            .card__name.very-long-text {
                font-size: 17px;
            }
             
            .card__name.extra-long-text {
                font-size: 14px;
            }
             
            .controls {
                padding: max(12px, env(safe-area-inset-bottom)) var(--mobile-padding);
                border-radius: var(--mobile-radius) var(--mobile-radius) 0 0;
                position: fixed;
                bottom: 0;
                left: 0;
                right: 0;
                z-index: 999;
                max-height: 40vh;
                overflow-y: auto;
                background: rgba(255, 255, 255, 0.98);
                backdrop-filter: blur(20px);
                box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
            }
             
            .mode-tabs {
                display: grid;
                grid-template-columns: repeat(2, 1fr);
                gap: 4px;
                padding: 6px;
                border-radius: var(--mobile-radius);
                margin-bottom: 8px;
            }
             
            .mode-tab {
                padding: 6px 4px;
                font-size: 10px;
                min-width: auto;
                border-radius: 6px;
                font-weight: 500;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
                min-height: 36px;
                display: flex;
                align-items: center;
                justify-content: center;
            }
             
            .btn {
                padding: 10px 12px;
                font-size: 13px;
                border-radius: var(--mobile-radius);
                min-height: 42px;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
            }
             
            .control-buttons {
                display: flex;
                gap: 8px;
                justify-content: center;
                flex-wrap: wrap;
            }
             
            .control-buttons .btn {
                flex: 1;
                min-width: 0;
                max-width: 120px;
            }
             
            .slider-container {
                padding: 10px;
                border-radius: var(--mobile-radius);
                gap: 6px;
            }
             
            .slider-container label {
                font-size: 12px;
            }
             
            .voice-toggle, .theme-selector {
                padding: 5px 8px;
                font-size: 11px;
                border-radius: 14px;
            }
             
            .theme-btn {
                width: 20px;
                height: 20px;
            }
             
            .drawer-toggle {
                width: 36px;
                height: 36px;
                padding: 8px;
                font-size: 12px;
                border-radius: 50%;
            }
             
            .drawer-toggle.left {
                left: var(--mobile-padding);
            }
             
            .drawer-toggle.right {
                right: var(--mobile-padding);
            }
             
            .timer-display {
                font-size: clamp(40px, 8vw, 56px);
                margin: 15px 0;
                font-weight: 800;
                letter-spacing: 1px;
                min-height: auto;
                max-height: 120px;
                line-height: 1.2;
                display: flex;
                align-items: center;
                justify-content: center;
                text-align: center;
                word-break: break-all;
                overflow-wrap: break-word;
                padding: 0 10px;
                box-sizing: border-box;
                overflow: hidden;
            }
             
            /* 超小屏手机 */
            @media (max-width: 375px) {
                .card {
                    padding: 60px 15px;
                    max-width: 95%;
                    border-radius: 20px;
                }
                 
                .card__name {
                    font-size: 28px;
                    line-height: 1.3;
                    min-height: 80px;
                }
                 
                .timer-display {
                    font-size: clamp(32px, 7vw, 44px);
                    letter-spacing: 0.5px;
                    margin: 12px 0;
                    max-height: 100px;
                    padding: 0 5px;
                }
            }
             
            /* 极小屏手机 */
            @media (max-width: 320px) {
                .card {
                    padding: 50px 12px;
                }
                 
                .card__name {
                    font-size: 24px;
                }
                 
                .timer-display {
                    font-size: clamp(28px, 6vw, 38px);
                    margin: 10px 0;
                    max-height: 90px;
                    letter-spacing: 0;
                }
            }
             
            .barrage-item {
                font-size: 13px;
                padding: 8px 14px;
                border-radius: 14px;
            }
             
            .mode-options {
                padding: 8px 6px;
                border-radius: var(--mobile-radius);
                display: flex;
                flex-direction: column;
                gap: 6px;
            }
             
            .mode-options .btn {
                width: 100%;
                max-width: none;
                margin: 2px 0;
                font-size: 12px;
                padding: 8px 10px;
            }
             
            /* 手机端抽屉优化 */
            .drawer {
                width: 100%;
                top: 0;
                border-radius: 0;
                box-shadow: 0 0 50px var(--shadow);
                z-index: 1001;
            }
 
            .history {
                width: 100%;
                top: 0;
                border-radius: 0;
                box-shadow: 0 0 50px var(--shadow);
                z-index: 1001;
            }
 
            .drawer-header, .history-header {
                padding: max(15px, env(safe-area-inset-top)) var(--mobile-padding) 15px;
                font-size: 18px;
                min-height: 60px;
                border-radius: 0;
            }
 
            .drawer-header h3, .history-header h3 {
                font-size: 18px;
            }
 
            .drawer-content, #historyList {
                padding: 20px var(--mobile-padding);
            }
 
            .student-input {
                font-size: 16px;
                padding: 12px;
                min-height: 180px;
                border-radius: 10px;
            }
 
            .drawer-actions {
                flex-direction: column;
                gap: 10px;
                margin-top: 15px;
            }
 
            .drawer-actions .btn {
                width: 100%;
                padding: 15px;
                font-size: 16px;
                border-radius: 12px;
                min-height: 48px;
            }
 
            .history-item {
                padding: 15px;
                margin-bottom: 12px;
                border-radius: 10px;
            }
 
            .history-item-content {
                flex-direction: column;
                align-items: flex-start;
                gap: 8px;
            }
 
            .history-name {
                font-size: 15px;
            }
 
            .history-time {
                font-size: 11px;
            }
 
            /* 手机端控制面板优化 */
            .controls {
                padding: max(12px, env(safe-area-inset-bottom)) var(--mobile-padding);
                max-height: 45vh;
            }
 
            .mode-tabs {
                display: grid;
                grid-template-columns: repeat(3, 1fr);
                gap: 4px;
                padding: 6px;
                margin-bottom: 12px;
            }
 
            .mode-tab {
                padding: 8px 4px;
                font-size: 11px;
                min-height: 36px;
                display: flex;
                align-items: center;
                justify-content: center;
            }
 
            .mode-options {
                padding: 8px 4px;
            }
 
            .mode-options > div {
                display: flex;
                flex-wrap: wrap;
                gap: 6px;
                align-items: center;
                justify-content: center;
            }
 
            .mode-options .btn {
                flex: 1;
                min-width: 0;
                padding: 10px 8px;
                font-size: 13px;
                min-height: 40px;
                margin: 2px;
            }
 
            .mode-options .btn.btn-primary {
                flex: 1.5;
            }
 
            .slider-container {
                flex-direction: row;
                flex-wrap: wrap;
                align-items: center;
                justify-content: space-between;
                padding: 8px 12px;
                margin: 4px 0;
            }
 
            .slider-container label {
                font-size: 12px;
                margin: 0;
                flex: 0 0 auto;
            }
 
            .slider-container .slider {
                flex: 1;
                margin: 0 8px;
                min-width: 60px;
            }
 
            .slider-container span {
                font-size: 11px;
                flex: 0 0 auto;
            }
 
            .control-row:last-child {
                margin-bottom: 0;
            }
 
            .control-row:last-child .slider-container {
                flex: 1;
                margin: 0;
            }
 
            .control-row:last-child .btn {
                flex: 0 0 auto;
                width: auto;
                min-width: 80px;
                padding: 8px 12px;
                font-size: 12px;
                min-height: 36px;
            }
 
            .control-row:last-child > div {
                display: flex;
                gap: 6px;
                align-items: center;
            }
             
            /* 横屏超小屏优化 */
            @media (max-height: 450px) and (orientation: landscape) {
                .header {
                    min-height: 45px;
                    padding: 6px var(--mobile-padding);
                }
                 
                .card {
                    padding: 15px 10px;
                    max-width: 85%;
                }
                 
                .card__name {
                    font-size: 18px;
                    margin-bottom: 10px;
                }
                 
                .controls {
                    padding: 8px var(--mobile-padding);
                }
                 
                .mode-tabs {
                    grid-template-columns: repeat(4, 1fr);
                }
                 
                .btn {
                    padding: 8px 12px;
                    font-size: 12px;
                    min-height: 36px;
                }
            }
        }
 
        /* 无障碍 */
        .sr-only {
            position: absolute;
            width: 1px;
            height: 1px;
            padding: 0;
            margin: -1px;
            overflow: hidden;
            clip: rect(0, 0, 0, 0);
            white-space: nowrap;
            border: 0;
        }
 
        /* Toast 提示 */
        .toast {
            position: fixed;
            top: 80px;
            left: 50%;
            transform: translateX(-50%);
            background: var(--text);
            color: white;
            padding: 15px 25px;
            border-radius: 25px;
            box-shadow: 0 4px 12px var(--shadow);
            z-index: 2000;
            animation: toastIn 0.3s ease;
        }
 
        @keyframes toastIn {
            from {
                transform: translateX(-50%) translateY(-20px);
                opacity: 0;
            }
            to {
                transform: translateX(-50%) translateY(0);
                opacity: 1;
            }
        }
    </style>
</head>
<body>
    <!-- 粒子背景 -->
    <canvas id="particles-canvas"></canvas>
     
    <!-- 顶部栏 -->
    <header class="header">
        <div class="logo">&#127908; 语音点名器 v2.0</div>
        <div class="header-controls">
            <div class="voice-toggle">
                <span id="voiceIcon">&#128266;</span>
                <span id="voiceText">语音开启</span>
            </div>
            <div class="theme-selector">
                <button class="theme-btn theme-btn--macaron active" title="马卡龙主题" style="background: linear-gradient(135deg, #ff6b6b, #ffd93d);"></button>
                <button class="theme-btn theme-btn--geek" title="极客蓝主题" style="background: linear-gradient(135deg, #0a0a0a, #1a1a2e);"></button>
                <button class="theme-btn theme-btn--purple" title="梦幻紫主题" style="background: linear-gradient(135deg, #667eea, #764ba2);"></button>
                <button class="theme-btn theme-btn--warm" title="温暖红主题" style="background: linear-gradient(135deg, #ff6b6b, #ff5252);"></button>
                <button class="theme-btn theme-btn--nature" title="自然绿主题" style="background: linear-gradient(135deg, #4caf50, #388e3c);"></button>
            </div>
        </div>
    </header>
 
    <!-- 左侧名单抽屉 -->
    <div class="drawer" id="studentDrawer">
        <div class="drawer-header">
            <h3>学生名单</h3>
            <button class="btn btn-secondary">×</button>
        </div>
        <div class="drawer-content">
            <textarea class="student-input" id="studentInput" placeholder="每行一个学生姓名
统一使用txt格式,每行一个学生姓名
支持中文编码,自动处理乱码
拖拽文件到此处也可导入
 
&#128203; 导入模板示例:
张三
李四
王五
赵六"></textarea>
            <div class="drawer-actions">
                <button class="btn btn-primary">保存名单</button>
                <button class="btn btn-import" style="background: linear-gradient(135deg, #4caf50, #45a049); color: white; border: none; border-radius: 20px; padding: 8px 16px; font-size: 14px; cursor: pointer; display: flex; align-items: center; gap: 4px;">
                    &#128229; 导入
                </button>
                <input type="file" id="importFile" accept=".csv,.txt" style="display: none;">
                <button class="btn btn-secondary">清空</button>
                <button class="btn btn-secondary">导出TXT</button>
                <button class="btn btn-secondary" style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; border: none; border-radius: 20px; padding: 8px 16px; font-size: 14px; cursor: pointer; display: flex; align-items: center; gap: 4px;">
                    &#128203; 模板
                </button>
            </div>
            <div style="margin-top: 15px;">
                <small>当前学生:<span id="studentCount">0</span>人</small>
            </div>
        </div>
    </div>
 
    <!-- 右侧历史抽屉 -->
    <div class="history" id="historyDrawer">
        <div class="drawer-header">
            <h3>历史记录</h3>
            <div style="display: flex; gap: 8px; align-items: center;">
                <button class="btn btn-export" style="font-size: 13px; padding: 8px 14px; background: linear-gradient(135deg, var(--primary), var(--secondary)); color: white; border: none; border-radius: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: all 0.3s ease; min-height: 32px; display: flex; align-items: center; gap: 4px;">
                    &#128202; 导出CSV
                </button>
                <button class="btn btn-clear" style="font-size: 13px; padding: 8px 14px; background: linear-gradient(135deg, #ff6b6b, #ff8e53); color: white; border: none; border-radius: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: all 0.3s ease; min-height: 32px; display: flex; align-items: center; gap: 4px;">
                    &#128465;&#65039; 清空
                </button>
                <button class="btn btn-close" style="font-size: 18px; padding: 4px 8px; background: none; color: var(--text-light); border: none; border-radius: 50%; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease;">×</button>
            </div>
        </div>
        <div id="historyList" style="padding: 20px;"></div>
    </div>
 
    <!-- 主舞台 -->
    <main class="main">
        <div class="card" id="mainCard">
            <div id="displayArea">
                <div class="card__name" id="displayName">准备开始</div>
                <div class="progress-bar" id="progressBar" style="display: none;">
                    <div class="progress-fill" id="progressFill"></div>
                </div>
                <div class="timer-display" id="timerDisplay" style="display: none; font-size: 120px; font-weight: 800; font-family: 'Courier New', monospace; letter-spacing: 8px; color: var(--primary);">00:30</div>
            </div>
             
            <!-- 3D 翻转卡片(隐藏) -->
            <div class="card-3d" id="card3d" style="display: none;">
                <div class="card-inner">
                    <div class="card-face card-front">准备开始</div>
                    <div class="card-face card-back" id="cardBack">姓名</div>
                </div>
            </div>
        </div>
    </main>
 
    <!-- 弹幕容器 -->
    <div class="barrage" id="barrageContainer"></div>
 
    <!-- 控制面板 -->
    <div class="controls">
        <div class="control-row">
            <div class="mode-tabs">
                <button class="mode-tab active" data-mode="question">上课提问</button>
                <button class="mode-tab" data-mode="sequence">顺序点名</button>
                <button class="mode-tab" data-mode="random">随机点名</button>
                <button class="mode-tab" data-mode="quick">快速连抽</button>
                <button class="mode-tab" data-mode="timer">计时器</button>
            </div>
        </div>
         
        <div class="control-row" id="controlOptions">
            <!-- 上课提问模式选项 -->
            <div id="questionOptions" class="mode-options">
                <button class="btn btn-primary">随机提问</button>
                <label>
                    <input type="checkbox" id="enableQuestionParticles" checked> 粒子背景
                </label>
            </div>
 
            <!-- 顺序模式选项 -->
            <div id="sequenceOptions" class="mode-options" style="display: none;">
                <div class="slider-container">
                    <label>间隔时间:</label>
                    <input type="range" class="slider" id="intervalSlider" min="1" max="60" value="1">
                    <span id="intervalValue">1</span>秒
                </div>
                <div style="display: flex; gap: 6px; flex-wrap: wrap;">
                    <button class="btn btn-primary">开始</button>
                    <button class="btn btn-secondary">暂停</button>
                    <button class="btn btn-secondary">下一位</button>
                    <button class="btn btn-secondary">上一位</button>
                </div>
            </div>
 
            <!-- 随机模式选项 -->
            <div id="randomOptions" class="mode-options" style="display: none;">
                <div style="display: flex; gap: 6px; flex-wrap: wrap; align-items: center;">
                    <button class="btn btn-primary">开始</button>
                    <label style="display: flex; align-items: center; font-size: 12px; margin: 0;">
                        <input type="checkbox" id="enable3d"> 3D效果
                    </label>
                    <label style="display: flex; align-items: center; font-size: 12px; margin: 0;">
                        <input type="checkbox" id="enableParticles" checked> 粒子
                    </label>
                </div>
            </div>
 
            <!-- 连抽模式选项 -->
            <div id="quickOptions" class="mode-options" style="display: none;">
                <div class="slider-container">
                    <label>人数:</label>
                    <input type="range" class="slider" id="quickCountSlider" min="1" max="10" value="3">
                    <span id="quickCountValue">3</span>人
                </div>
                <div style="display: flex; gap: 6px; flex-wrap: wrap;">
                    <button class="btn btn-primary">开始</button>
                    <button class="btn btn-secondary">重置</button>
                </div>
            </div>
 
            <!-- 计时器模式选项 -->
            <div id="timerOptions" class="mode-options" style="display: none;">
                <div class="slider-container">
                    <label>时间:</label>
                    <select class="btn" id="timerSelect" style="flex: 1; min-width: 0; font-size: 12px; padding: 6px 8px;">
                        <option value="30">30秒</option>
                        <option value="60">1分钟</option>
                        <option value="90">90秒</option>
                        <option value="120">2分钟</option>
                        <option value="custom">自定义</option>
                    </select>
                    <input type="number" id="customTimer" placeholder="秒" style="display: none; width: 60px; font-size: 12px; padding: 6px;" min="1" max="3600">
                </div>
                <div style="display: flex; gap: 6px; flex-wrap: wrap;">
                    <button class="btn btn-primary" id="startTimerBtn">开始</button>
                    <button class="btn btn-secondary" id="pauseTimerBtn" style="display: none;">暂停</button>
                    <button class="btn btn-secondary">重置</button>
                </div>
            </div>
        </div>
 
        <div class="control-row">
            <div style="display: flex; gap: 6px; align-items: center; flex-wrap: wrap;">
                <div class="slider-container" style="flex: 1; margin: 0; min-width: 120px;">
                    <label style="font-size: 12px; margin: 0;">语速</label>
                    <input type="range" class="slider" id="speechRateSlider" min="0.5" max="2" step="0.1" value="1" style="margin: 0 4px;">
                    <span id="speechRateValue" style="font-size: 11px;">1.0×</span>
                </div>
                <button class="btn btn-secondary" style="flex: 0 0 auto; min-width: 60px; padding: 8px 10px; font-size: 12px;">测试</button>
                <button class="btn btn-secondary" style="flex: 0 0 auto; min-width: 60px; padding: 8px 10px; font-size: 12px;">重置</button>
            </div>
        </div>
    </div>
 
    <!-- 抽屉切换按钮 -->
    <button class="drawer-toggle left" aria-label="打开学生名单">
        <span class="icon">&#9776;</span>
    </button>
    <button class="drawer-toggle right" aria-label="打开历史记录">
        <span class="icon">&#128202;</span>
    </button>
 
    <script>
        // 全局状态管理
        const app = {
            students: [],
            currentIndex: 0,
            history: [],
            weights: {},
            isRunning: false,
            currentMode: 'question',
            intervalId: null,
            timerId: null,
            remainingTime: 0,
            speechEnabled: true,
            speechRate: 1,
            voiceMode: {
                sequence: { interval: 1 },
                random: { enable3d: true, enableParticles: true },
                quick: { count: 3 },
                timer: { duration: 30 }
            }
        };
 
        // 初始化
        document.addEventListener('DOMContentLoaded', () => {
            loadData();
            initEventListeners();
            initParticles();
            // 初始化默认模式为上课提问,确保手机端正确显示
            switchMode('question');
            updateDisplay();
        });
 
        // 数据持久化
        function loadData() {
            const savedStudents = localStorage.getItem('students');
            if (savedStudents) {
                app.students = JSON.parse(savedStudents);
            }
 
            const savedHistory = localStorage.getItem('history');
            if (savedHistory) {
                app.history = JSON.parse(savedHistory);
            }
 
            const savedWeights = localStorage.getItem('weights');
            if (savedWeights) {
                app.weights = JSON.parse(savedWeights);
            }
 
            const savedSettings = localStorage.getItem('settings');
            if (savedSettings) {
                const settings = JSON.parse(savedSettings);
                app.speechEnabled = settings.speechEnabled ?? true;
                app.speechRate = settings.speechRate ?? 1;
                app.voiceMode = { ...app.voiceMode, ...settings.voiceMode };
            }
 
            updateStudentCount();
            updateHistoryList();
        }
 
        function saveData() {
            localStorage.setItem('students', JSON.stringify(app.students));
            localStorage.setItem('history', JSON.stringify(app.history));
            localStorage.setItem('weights', JSON.stringify(app.weights));
            localStorage.setItem('settings', JSON.stringify({
                speechEnabled: app.speechEnabled,
                speechRate: app.speechRate,
                voiceMode: app.voiceMode
            }));
        }
 
        // 学生名单管理
        function saveStudents() {
            const input = document.getElementById('studentInput').value;
            const lines = input.split('\n').map(line => line.trim()).filter(line => line);
            app.students = [...new Set(lines)];
             
            // 初始化权重
            app.students.forEach(name => {
                if (!app.weights[name]) {
                    app.weights[name] = 1;
                }
            });
             
            updateStudentCount();
            saveData();
            showToast('名单已保存');
        }
 
        function clearStudents() {
            document.getElementById('studentInput').value = '';
            app.students = [];
            app.weights = {};
            updateStudentCount();
            saveData();
            showToast('名单已清空');
        }
 
        function exportStudents() {
            // 导出为纯名字的格式
            const txt = app.students.join('\n');
            downloadFile(txt, '学生名单.txt', 'text/plain');
        }
 
        function downloadTemplate() {
            const template = `# 学生名单导入模板
# 统一使用txt格式,每行一个学生姓名
# 每行必须是一个有效的学生姓名
 
# 示例:纯名字格式(推荐)
张三
李四
王五
赵六
孙七
周八
吴九
郑十
钱十一
孙十二
 
# 注意:
# 1. 每行一个学生姓名
# 2. 不要有空行
# 3. 不要有特殊字符
# 4. 支持中文姓名`;
            downloadFile(template, '学生名单模板.txt', 'text/plain');
        }
 
        function importStudents() {
            document.getElementById('importFile').click();
        }
 
        function handleFileImport(event) {
            const file = event.target.files[0];
            if (!file) return;
 
            const reader = new FileReader();
            reader.onload = function(e) {
                let content = e.target.result;
                 
                // 处理可能的编码问题
                try {
                    // 如果检测到乱码,尝试重新解码
                    if (content.indexOf('&#65533;') !== -1 || content.indexOf('\uFFFD') !== -1) {
                        // 使用GB2312编码重新读取(针对中文Windows系统)
                        const decoder = new TextDecoder('gb2312');
                        const bytes = new Uint8Array(e.target.result);
                        content = decoder.decode(bytes);
                    }
                } catch (error) {
                    console.warn('编码处理失败,使用原始内容');
                }
 
                // 智能编码检测和处理
                const lines = content
                    .replace(/\r\n/g, '\n')  // 统一换行符
                    .replace(/\r/g, '\n')   // 处理Mac换行符
                    .split('\n')
                    .map(line => {
                        // 移除BOM标记和空白字符
                        return line.replace(/^\uFEFF/, '').trim();
                    })
                    .filter(line => line && !line.startsWith('#'));
                 
                if (lines.length > 0) {
                    console.log('开始处理导入数据,共', lines.length, '行');
                     
                    // 处理纯名字格式
                    const names = [];
                    for (let line of lines) {
                        line = line.trim();
                        console.log('处理行:', line);
                         
                        if (!line || line.startsWith('#')) {
                            console.log('跳过空行或注释:', line);
                            continue;
                        }
                         
                        // 直接使用整行作为姓名
                        let name = line;
                         
                        // 移除可能的学号前缀,但保留中文姓名
                        const hasChinese = /[\u4e00-\u9fa5]/.test(line);
                        if (hasChinese) {
                            // 如果包含中文,直接使用整行
                            name = line;
                        } else {
                            // 如果没有中文,移除可能的数字前缀
                            name = line.replace(/^[0-9A-Za-z]+/, '').trim();
                            if (!name) name = line;
                        }
                         
                        if (name && name.length > 0) {
                            names.push(name);
                            console.log('添加姓名:', name);
                        }
                    }
 
                    console.log('解析完成,共', names.length, '个姓名');
                     
                    if (names.length === 0) {
                        console.log('未找到有效姓名');
                        showToast('未找到有效的学生姓名');
                        return;
                    }
 
                    // 合并到现有名单
                    const newStudents = [...new Set([...app.students, ...names])];
                    const addedCount = newStudents.length - app.students.length;
                    app.students = newStudents;
                     
                    console.log('导入前学生数:', app.students.length - addedCount);
                    console.log('新增学生数:', addedCount);
                    console.log('导入后学生数:', app.students.length);
                     
                    // 初始化权重
                    app.weights = {};
                    app.students.forEach(name => {
                        app.weights[name] = 1;
                    });
 
                    updateStudentCount();
                    saveData();
                    showToast(`成功导入 ${addedCount} 个新学生,共 ${names.length} 个`);
                } else {
                    console.log('文件为空');
                    showToast('文件为空或格式不正确');
                }
            };
             
            reader.onerror = function() {
                showToast('文件读取失败,请检查文件格式');
            };
 
            // 使用UTF-8编码读取
            reader.readAsText(file, 'UTF-8');
 
            // 清空文件输入框,允许重复导入同一文件
            event.target.value = '';
        }
 
        function updateStudentCount() {
            document.getElementById('studentCount').textContent = app.students.length;
            document.getElementById('studentInput').value = app.students.join('\n');
        }
 
        // 历史记录
        function addHistory(name, mode) {
            const record = {
                name,
                mode,
                time: new Date().toLocaleString('zh-CN'),
                timestamp: Date.now()
            };
            app.history.unshift(record);
            if (app.history.length > 100) {
                app.history = app.history.slice(0, 100);
            }
            updateHistoryList();
            saveData();
        }
 
        function updateHistoryList() {
            const container = document.getElementById('historyList');
            container.innerHTML = '';
             
            app.history.forEach(record => {
                const item = document.createElement('div');
                item.className = 'history-item';
                item.innerHTML = `
                    <div>
                        <div>${record.name}</div>
                        <div class="history-time">${record.time}</div>
                    </div>
                    <small>${record.mode}</small>
                `;
                container.appendChild(item);
            });
        }
 
        function clearHistory() {
            app.history = [];
            updateHistoryList();
            saveData();
            showToast('历史已清空');
        }
 
        function exportHistory() {
            const csv = app.history.map(h => `${h.time},${h.name},${h.mode}`).join('\n');
            downloadFile('时间,姓名,模式\n' + csv, 'history.csv', 'text/csv');
        }
 
        // 语音功能
        function speak(text, onEnd = null) {
            if (!app.speechEnabled) return;
             
            const utterance = new SpeechSynthesisUtterance(text);
            utterance.rate = app.speechRate;
            utterance.lang = navigator.language.startsWith('zh') ? 'zh-CN' : 'en-US';
             
            if (onEnd) {
                utterance.onend = onEnd;
            }
             
            try {
                speechSynthesis.speak(utterance);
            } catch (error) {
                showToast('语音播放失败,请检查浏览器设置');
                // 文本闪烁提示
                const display = document.getElementById('displayName');
                display.style.animation = 'pulse 0.5s ease 3';
            }
        }
 
        function testSpeech() {
            speak('测试语音');
        }
 
        function toggleVoice() {
            app.speechEnabled = !app.speechEnabled;
            const icon = document.getElementById('voiceIcon');
            const text = document.getElementById('voiceText');
             
            if (app.speechEnabled) {
                icon.textContent = '&#128266;';
                text.textContent = '语音开启';
            } else {
                icon.textContent = '&#128263;';
                text.textContent = '语音关闭';
            }
             
            saveData();
        }
 
        // 点名功能
        function switchMode(mode) {
            console.log('切换到模式:', mode);
            app.currentMode = mode;
             
            // 更新标签页
            document.querySelectorAll('.mode-tab').forEach(tab => {
                tab.classList.toggle('active', tab.dataset.mode === mode);
            });
             
            // 显示对应选项
            document.querySelectorAll('.mode-options').forEach(options => {
                options.style.display = 'none';
            });
             
            const optionsMap = {
                sequence: 'sequenceOptions',
                random: 'randomOptions',
                quick: 'quickOptions',
                question: 'questionOptions',
                timer: 'timerOptions'
            };
             
            if (optionsMap[mode]) {
                document.getElementById(optionsMap[mode]).style.display = 'block';
            }
             
            // 强制重置所有显示状态 - 修复计时器显示问题
            if (mode === 'timer') {
                // 计时器模式:显示计时器,隐藏姓名显示
                document.getElementById('timerDisplay').style.display = 'block';
                document.getElementById('displayName').style.display = 'none';
            } else {
                // 其他所有模式:隐藏计时器,显示姓名显示
                document.getElementById('timerDisplay').style.display = 'none';
                document.getElementById('displayName').style.display = 'block';
                 
                // 强制停止计时器
                if (app.isRunning) {
                    app.isRunning = false;
                    if (app.timerId) {
                        clearTimeout(app.timerId);
                        app.timerId = null;
                    }
                }
                 
                // 重置计时器状态
                app.remainingTime = 0;
                 
                // 重置显示
                updateDisplay('准备开始');
            }
        }
 
        // 顺序点名
        function startSequence() {
            if (app.students.length === 0) {
                showToast('请先添加学生名单');
                toggleDrawer();
                return;
            }
             
            app.isRunning = true;
            const interval = parseInt(document.getElementById('intervalSlider').value);
             
            function next() {
                if (!app.isRunning) return;
                 
                const name = app.students[app.currentIndex];
                updateDisplay(name);
                addHistory(name, '顺序点名');
                speak(name, () => {
                    // 检查是否到达最后一个学生
                    if (app.currentIndex >= app.students.length - 1) {
                        // 到达最后一个学生,自动停止
                        app.isRunning = false;
                        updateDisplay('点名完成');
                        showToast('顺序点名已完成');
                        return;
                    }
                     
                    // 继续下一个学生
                    app.currentIndex = app.currentIndex + 1;
                    if (app.isRunning) {
                        setTimeout(next, interval * 1000);
                    }
                });
            }
             
            next();
        }
 
        function pauseSequence() {
            app.isRunning = false;
            updateDisplay('已暂停');
        }
 
        function nextStudent() {
            if (app.students.length === 0) return;
            app.currentIndex = (app.currentIndex + 1) % app.students.length;
            updateDisplay(app.students[app.currentIndex]);
        }
 
        function prevStudent() {
            if (app.students.length === 0) return;
            app.currentIndex = (app.currentIndex - 1 + app.students.length) % app.students.length;
            updateDisplay(app.students[app.currentIndex]);
        }
 
        // 随机点名
        function startRandom() {
            if (app.students.length === 0) {
                showToast('请先添加学生名单');
                toggleDrawer();
                return;
            }
             
            const candidates = app.students.filter(name => app.weights[name] > 0);
            if (candidates.length === 0) {
                showToast('所有学生都已点到');
                return;
            }
             
            app.isRunning = true;
             
            // 权重选择
            const totalWeight = candidates.reduce((sum, name) => sum + app.weights[name], 0);
            let random = Math.random() * totalWeight;
            let selected = candidates[0];
             
            for (const name of candidates) {
                random -= app.weights[name];
                if (random <= 0) {
                    selected = name;
                    break;
                }
            }
             
            // 3D动画
            const enable3d = document.getElementById('enable3d').checked;
            if (enable3d) {
                document.getElementById('mainCard').style.display = 'none';
                document.getElementById('card3d').style.display = 'block';
                document.querySelector('.card-3d').classList.add('flipping');
                 
                setTimeout(() => {
                    document.getElementById('cardBack').textContent = selected;
                }, 600);
                 
                setTimeout(() => {
                    document.querySelector('.card-3d').classList.remove('flipping');
                    setTimeout(() => {
                        document.getElementById('mainCard').style.display = 'block';
                        document.getElementById('card3d').style.display = 'none';
                        finishRandom(selected);
                    }, 300);
                }, 1200);
            } else {
                finishRandom(selected);
            }
             
            if (document.getElementById('enableParticles').checked) {
                createParticles();
            }
        }
 
        function finishRandom(name) {
            updateDisplay(name);
            addHistory(name, '随机点名');
            app.weights[name] = 1 / (getCallCount(name) + 1);
            speak(name, () => {
                app.isRunning = false;
            });
        }
 
        // 上课提问
        function askQuestion() {
            if (app.students.length === 0) {
                showToast('请先添加学生名单');
                toggleDrawer();
                return;
            }
             
            if (app.isRunning) {
                return;
            }
             
            app.isRunning = true;
             
            // 动态闪现效果
            let flashCount = 0;
            const maxFlashes = 20; // 2秒内闪现20次,每次100ms
             
            const flashInterval = setInterval(() => {
                // 随机显示一个学生名字
                const randomIndex = Math.floor(Math.random() * app.students.length);
                const randomStudent = app.students[randomIndex];
                updateDisplay(randomStudent);
                 
                flashCount++;
                 
                // 2秒后停止闪现并确定最终结果
                if (flashCount >= maxFlashes) {
                    clearInterval(flashInterval);
                     
                    // 最终随机选择一个学生
                    const finalIndex = Math.floor(Math.random() * app.students.length);
                    const selectedStudent = app.students[finalIndex];
                     
                    // 显示最终选中的学生
                    updateDisplay(selectedStudent);
                    addHistory(selectedStudent, '上课提问');
                     
                    // 播报学生姓名
                    speak(selectedStudent, () => {
                        app.isRunning = false;
                    });
                }
            }, 100); // 每100ms切换一次
             
            // 粒子效果
            if (document.getElementById('enableQuestionParticles').checked) {
                createParticles();
            }
        }
 
        // 快速连抽
        function startQuickDraw() {
            if (app.students.length === 0) {
                showToast('请先添加学生名单');
                toggleDrawer();
                return;
            }
             
            if (app.isRunning) return;
             
            const count = parseInt(document.getElementById('quickCountSlider').value);
            const candidates = app.students.filter(name => app.weights[name] > 0);
             
            if (candidates.length < count) {
                showToast(`可选学生不足 ${count} 人`);
                return;
            }
             
            app.isRunning = true;
             
            // 开始动态闪现效果
            let flashCount = 0;
            const maxFlashes = 20; // 2秒内闪现20次
            const flashInterval = 100; // 每100毫秒闪现一次
             
            const flashTimer = setInterval(() => {
                // 随机显示一些名字进行闪现
                const flashNames = [];
                for (let i = 0; i < count; i++) {
                    const randomName = candidates[Math.floor(Math.random() * candidates.length)];
                    flashNames.push(randomName);
                }
                updateDisplay(flashNames.join('、'));
                 
                flashCount++;
                if (flashCount >= maxFlashes) {
                    clearInterval(flashTimer);
                     
                    // 闪现结束,进行最终选择
                    const selected = selectMultipleStudents(candidates, count);
                     
                    // 显示所有选中的名字
                    const allNames = selected.join('、');
                    updateDisplay(allNames);
                     
                    // 逐个播报和记录
                    selected.forEach((name, index) => {
                        setTimeout(() => {
                            addHistory(name, '快速连抽');
                            app.weights[name] = 1 / (getCallCount(name) + 1);
                            speak(name);
                             
                            // 如果是最后一个,结束运行状态
                            if (index === selected.length - 1) {
                                setTimeout(() => {
                                    app.isRunning = false;
                                }, 1000);
                            }
                        }, index * 800);
                    });
                }
            }, flashInterval);
        }
 
        function selectMultipleStudents(candidates, count) {
            const selected = [];
            const tempCandidates = [...candidates];
             
            for (let i = 0; i < count && tempCandidates.length > 0; i++) {
                const totalWeight = tempCandidates.reduce((sum, name) => sum + app.weights[name], 0);
                let random = Math.random() * totalWeight;
                let selectedIndex = 0;
                 
                for (let j = 0; j < tempCandidates.length; j++) {
                    random -= app.weights[tempCandidates[j]];
                    if (random <= 0) {
                        selectedIndex = j;
                        break;
                    }
                }
                 
                selected.push(tempCandidates[selectedIndex]);
                tempCandidates.splice(selectedIndex, 1);
            }
             
            return selected;
        }
 
        function resetWeights() {
            if (app.isRunning) {
                showToast('请等待当前操作完成后再重置权重');
                return;
            }
            app.students.forEach(name => {
                app.weights[name] = 1;
            });
            saveData();
            showToast('权重已重置');
        }
 
        function getCallCount(name) {
            return app.history.filter(h => h.name === name).length;
        }
 
        // 计时器
        function startTimer() {
            const select = document.getElementById('timerSelect');
            let duration = parseInt(select.value);
             
            if (select.value === 'custom') {
                duration = parseInt(document.getElementById('customTimer').value);
                if (!duration || duration < 1) {
                    showToast('请输入有效的时间');
                    return;
                }
            }
             
            // 如果是从暂停状态继续,不重置时间
            if (!app.isRunning && app.remainingTime <= 0) {
                app.remainingTime = duration;
            }
             
            app.isRunning = true;
             
            // 更新按钮显示
            document.getElementById('startTimerBtn').style.display = 'none';
            document.getElementById('pauseTimerBtn').style.display = 'inline-block';
             
            document.getElementById('timerDisplay').style.display = 'block';
            document.getElementById('displayName').style.display = 'none';
             
            function updateTimer() {
                if (!app.isRunning) return;
                 
                const minutes = Math.floor(app.remainingTime / 60);
                const seconds = app.remainingTime % 60;
                const timeText = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
                 
                // 先更新显示,确保视觉和语音同步
                document.getElementById('timerDisplay').textContent = timeText;
                 
                // 语音播报逻辑 - 调整播报时机,确保准确性
                if (app.speechEnabled) {
                    const currentTime = app.remainingTime;
                     
                    // 精确播报时机:在当前时间点的下一秒开始播报
                    setTimeout(() => {
                        // 最后10秒逐秒播报
                        if (currentTime <= 10 && currentTime > 0) {
                            speak(`${currentTime}`);
                        }
                        // 每10秒播报(最后30秒内,不包括逐秒播报的区间)
                        else if (currentTime <= 30 && currentTime % 10 === 0 && currentTime > 10) {
                            speak(`${currentTime}秒`);
                        }
                        // 每分钟播报(超过1分钟时)
                        else if (currentTime > 60 && currentTime % 60 === 0) {
                            speak(`${minutes}分钟${seconds > 0 ? seconds + '秒' : ''}剩余`);
                        }
                    }, 100); // 延迟100ms确保显示更新完成
                }
                 
                if (app.remainingTime <= 0) {
                    setTimeout(() => {
                        speak('时间到!');
                    }, 100);
                    pauseTimer();
                    return;
                }
                 
                app.remainingTime--;
                app.timerId = setTimeout(updateTimer, 1000);
            }
             
            updateTimer();
        }
 
        function pauseTimer() {
            app.isRunning = false;
            if (app.timerId) {
                clearTimeout(app.timerId);
                app.timerId = null;
            }
             
            // 如果还有剩余时间,显示暂停状态
            if (app.remainingTime > 0) {
                const minutes = Math.floor(app.remainingTime / 60);
                const seconds = app.remainingTime % 60;
                const timeText = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
                 
                // 保持计时器显示,但添加暂停标识
                document.getElementById('timerDisplay').textContent = timeText + ' (已暂停)';
                document.getElementById('timerDisplay').style.display = 'block';
                document.getElementById('displayName').style.display = 'none';
                 
                // 更新按钮状态
                document.getElementById('startTimerBtn').style.display = 'inline-block';
                document.getElementById('pauseTimerBtn').style.display = 'none';
            } else {
                // 时间到了才显示计时结束
                document.getElementById('timerDisplay').style.display = 'none';
                document.getElementById('displayName').style.display = 'block';
                updateDisplay('计时结束');
                 
                // 重置按钮状态
                document.getElementById('startTimerBtn').style.display = 'inline-block';
                document.getElementById('pauseTimerBtn').style.display = 'none';
            }
        }
 
        function togglePauseTimer() {
            if (app.isRunning) {
                pauseTimer();
                if (app.speechEnabled) {
                    speak('计时已暂停');
                }
            } else {
                // 继续计时
                if (app.speechEnabled) {
                    speak('继续计时');
                }
                startTimer();
            }
        }
 
        function resetTimer() {
            app.isRunning = false;
            if (app.timerId) {
                clearTimeout(app.timerId);
                app.timerId = null;
            }
             
            // 重置显示
            document.getElementById('timerDisplay').style.display = 'none';
            document.getElementById('displayName').style.display = 'block';
            updateDisplay('准备开始');
             
            // 重置按钮状态
            document.getElementById('startTimerBtn').style.display = 'inline-block';
            document.getElementById('pauseTimerBtn').style.display = 'none';
             
            // 语音播报
            if (app.speechEnabled) {
                speak('计时器已重置');
            }
        }
 
        // UI 更新
        function updateDisplay(text) {
            const displayElement = document.getElementById('displayName');
            const displayText = text || '准备开始';
            displayElement.textContent = displayText;
             
            // 移除所有字体大小类
            displayElement.classList.remove('long-text', 'very-long-text', 'extra-long-text');
             
            // 根据文本长度添加相应的字体大小类
            const textLength = displayText.length;
            if (textLength > 30) {
                displayElement.classList.add('extra-long-text');
            } else if (textLength > 20) {
                displayElement.classList.add('very-long-text');
            } else if (textLength > 10) {
                displayElement.classList.add('long-text');
            }
        }
 
        // 抽屉控制
        function toggleDrawer() {
            document.getElementById('studentDrawer').classList.toggle('open');
        }
 
        function closeDrawer() {
            document.getElementById('studentDrawer').classList.remove('open');
        }
 
        function toggleHistory() {
            document.getElementById('historyDrawer').classList.toggle('open');
        }
 
        function closeHistory() {
            document.getElementById('historyDrawer').classList.remove('open');
        }
 
        // 主题切换
        function switchTheme(theme) {
            document.documentElement.setAttribute('data-theme', theme);
             
            document.querySelectorAll('.theme-btn').forEach(btn => {
                btn.classList.remove('active');
            });
             
            event.target.classList.add('active');
            saveData();
        }
 
        // 动画效果
        function createBarrage(text) {
            const item = document.createElement('div');
            item.className = 'barrage-item';
            item.textContent = text;
             
            const top = Math.random() * (window.innerHeight - 200) + 100;
            item.style.top = `${top}px`;
             
            document.getElementById('barrageContainer').appendChild(item);
             
            setTimeout(() => {
                item.remove();
            }, 3000);
        }
 
        function createParticles() {
            const canvas = document.getElementById('particles-canvas');
            const ctx = canvas.getContext('2d');
             
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
             
            const particles = [];
            const particleCount = 50;
             
            for (let i = 0; i < particleCount; i++) {
                particles.push({
                    x: Math.random() * canvas.width,
                    y: Math.random() * canvas.height,
                    vx: (Math.random() - 0.5) * 2,
                    vy: (Math.random() - 0.5) * 2,
                    size: Math.random() * 3 + 1,
                    color: `hsl(${Math.random() * 360}, 70%, 50%)`
                });
            }
             
            function animate() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                 
                particles.forEach(p => {
                    p.x += p.vx;
                    p.y += p.vy;
                     
                    if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
                    if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
                     
                    ctx.beginPath();
                    ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
                    ctx.fillStyle = p.color;
                    ctx.fill();
                });
                 
                if (app.isRunning) {
                    requestAnimationFrame(animate);
                } else {
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                }
            }
             
            animate();
        }
 
        function initParticles() {
            const canvas = document.getElementById('particles-canvas');
            const ctx = canvas.getContext('2d');
             
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
             
            // 简单的背景粒子
            function drawBackground() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                 
                for (let i = 0; i < 20; i++) {
                    ctx.beginPath();
                    ctx.arc(
                        Math.random() * canvas.width,
                        Math.random() * canvas.height,
                        Math.random() * 2 + 1,
                        0,
                        Math.PI * 2
                    );
                    ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
                    ctx.fill();
                }
            }
             
            drawBackground();
        }
 
        // 事件监听器
        function initEventListeners() {
            // 滑块事件
            document.getElementById('intervalSlider').addEventListener('input', (e) => {
                document.getElementById('intervalValue').textContent = e.target.value;
                app.voiceMode.sequence.interval = parseInt(e.target.value);
            });
 
            document.getElementById('quickCountSlider').addEventListener('input', (e) => {
                document.getElementById('quickCountValue').textContent = e.target.value;
                app.voiceMode.quick.count = parseInt(e.target.value);
            });
 
            document.getElementById('speechRateSlider').addEventListener('input', (e) => {
                document.getElementById('speechRateValue').textContent = e.target.value;
                app.speechRate = parseFloat(e.target.value);
                saveData();
            });
 
            document.getElementById('timerSelect').addEventListener('change', (e) => {
                const customInput = document.getElementById('customTimer');
                customInput.style.display = e.target.value === 'custom' ? 'inline-block' : 'none';
            });
 
            // 手机端触摸支持 - 防止触摸事件被阻止
            document.querySelectorAll('.mode-tab').forEach(tab => {
                tab.addEventListener('touchstart', (e) => {
                    e.preventDefault();
                    const mode = e.target.dataset.mode;
                    switchMode(mode);
                });
            });
 
            // 文件拖拽
            const studentInput = document.getElementById('studentInput');
            studentInput.addEventListener('dragover', (e) => {
                e.preventDefault();
                studentInput.style.borderColor = 'var(--primary)';
            });
 
            studentInput.addEventListener('dragleave', () => {
                studentInput.style.borderColor = 'var(--border)';
            });
 
            studentInput.addEventListener('drop', (e) => {
                e.preventDefault();
                studentInput.style.borderColor = 'var(--border)';
                 
                const file = e.dataTransfer.files[0];
                if (file && file.type === 'text/csv') {
                    const reader = new FileReader();
                    reader.onload = (e) => {
                        studentInput.value = e.target.result.split('\n').join('\n');
                    };
                    reader.readAsText(file);
                }
            });
 
            // 键盘快捷键
            document.addEventListener('keydown', (e) => {
                if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
                 
                switch(e.key) {
                    case ' ':
                        e.preventDefault();
                        handleStartPause();
                        break;
                    case 'n':
                    case 'N':
                        nextStudent();
                        break;
                    case 'r':
                    case 'R':
                        if (app.currentMode === 'random') startRandom();
                        break;
                    case 'b':
                    case 'B':
                        if (app.currentMode === 'quick') startQuickDraw();
                        break;
                    case 't':
                    case 'T':
                        // 只在非手机端启用T键切换计时器
                        if (window.innerWidth > 768) {
                            switchMode('timer');
                        }
                        break;
                    case 'c':
                    case 'C':
                        if (confirm('确定要清空历史记录吗?')) {
                            clearHistory();
                        }
                        break;
                }
            });
 
            // Konami 彩蛋
            let konamiCode = [];
            const konamiSequence = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'KeyB', 'KeyA'];
             
            document.addEventListener('keydown', (e) => {
                konamiCode.push(e.code);
                if (konamiCode.length > konamiSequence.length) {
                    konamiCode = konamiCode.slice(1);
                }
                 
                if (konamiCode.join('') === konamiSequence.join('')) {
                    triggerEasterEgg();
                    konamiCode = [];
                }
            });
        }
 
        function handleStartPause() {
            switch(app.currentMode) {
                case 'sequence':
                    if (app.isRunning) {
                        pauseSequence();
                    } else {
                        startSequence();
                    }
                    break;
                case 'random':
                    if (!app.isRunning) startRandom();
                    break;
                case 'quick':
                    if (!app.isRunning) startQuickDraw();
                    break;
                case 'timer':
                    if (app.isRunning) {
                        pauseTimer();
                    } else {
                        startTimer();
                    }
                    break;
            }
        }
 
        function triggerEasterEgg() {
            showToast('&#127752; 彩蛋触发!彩虹粒子启动!');
             
            // 创建彩虹粒子
            const colors = ['#ff0000', '#ff7f00', '#ffff00', '#00ff00', '#0000ff', '#4b0082', '#9400d3'];
            const canvas = document.getElementById('particles-canvas');
            const ctx = canvas.getContext('2d');
             
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
             
            let frame = 0;
            function rainbowEffect() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                 
                for (let i = 0; i < app.students.length; i++) {
                    const x = (canvas.width / app.students.length) * i;
                    const y = canvas.height / 2 + Math.sin(frame * 0.1 + i) * 100;
                     
                    ctx.font = '24px Arial';
                    ctx.fillStyle = colors[i % colors.length];
                    ctx.fillText(app.students[i], x, y);
                }
                 
                frame++;
                if (frame < 300) {
                    requestAnimationFrame(rainbowEffect);
                } else {
                    initParticles();
                }
            }
             
            rainbowEffect();
             
            // 控制台 ASCII Art
            console.log(`
╔═╗┌─┐┌┐ ┌─┐┬ ┬
║  ├┤ ├┴┐│ │└┬┘
╚═╝└─┘└─┘└─┘ ┴ 
            `);
        }
 
        // 工具函数
        function showToast(message) {
            const toast = document.createElement('div');
            toast.className = 'toast';
            toast.textContent = message;
            document.body.appendChild(toast);
             
            setTimeout(() => {
                toast.remove();
            }, 3000);
        }
 
        function downloadFile(content, filename, mimeType) {
            const blob = new Blob([content], { type: mimeType });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            a.click();
            URL.revokeObjectURL(url);
        }
 
        function resetAll() {
            if (confirm('确定要重置所有数据吗?')) {
                app.students = [];
                app.history = [];
                app.weights = {};
                app.currentIndex = 0;
                app.isRunning = false;
                 
                if (app.intervalId) clearInterval(app.intervalId);
                if (app.timerId) clearTimeout(app.timerId);
                 
                updateDisplay('准备开始');
                updateStudentCount();
                updateHistoryList();
                saveData();
                showToast('已重置全部数据');
            }
        }
    </script>
     
    <!-- 倾企企服版权所有 - 加密版权信息 -->
    <div id="copyright" style="position: fixed; bottom: 2px; right: 5px; font-size: 8px; color: #ccc; user-select: none; pointer-events: none; opacity: 0.6; z-index: 9999;" contenteditable="false" draggable="false">
        倾企企服版权所有
    </div>
     
    <script>
        // 版权信息保护脚本
        (function() {
            const copyrightDiv = document.getElementById('copyright');
             
            // 防止右键菜单
            copyrightDiv.addEventListener('contextmenu', function(e) {
                e.preventDefault();
                return false;
            });
             
            // 防止选择文字
            copyrightDiv.addEventListener('selectstart', function(e) {
                e.preventDefault();
                return false;
            });
             
            // 防止复制
            copyrightDiv.addEventListener('copy', function(e) {
                e.preventDefault();
                return false;
            });
             
            // 防止拖拽
            copyrightDiv.addEventListener('dragstart', function(e) {
                e.preventDefault();
                return false;
            });
             
            // 使用CSS进一步防止编辑
            copyrightDiv.style.setProperty('-webkit-user-select', 'none', 'important');
            copyrightDiv.style.setProperty('-moz-user-select', 'none', 'important');
            copyrightDiv.style.setProperty('-ms-user-select', 'none', 'important');
        })();
    </script>
</body>
</html>

 

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐