<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>婚礼司仪数字人 · 大屏展示</title>
    <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='0.9em' font-size='90'>🤵</text></svg>">
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700;900&display=swap');

        * { margin: 0; padding: 0; box-sizing: border-box; }

        :root {
            --gold: #d4a853;
            --gold-light: #f0d68a;
            --red: #b64a2a;
            --dark: #0a0a14;
            --dark2: #12121e;
            --text: #f0e6d3;
            --text-dim: #8a8078;
        }

        body {
            background: var(--dark);
            color: var(--text);
            font-family: 'Noto Serif SC', 'SimSun', serif;
            overflow: hidden;
            height: 100vh;
            height: 100dvh;
            width: 100vw;
            cursor: none;
        }

        body.controls-visible { cursor: default; }

        .screen-bg {
            position: fixed;
            inset: 0;
            z-index: 0;
            background: radial-gradient(ellipse at 50% 80%, #1a1028 0%, #0a0a14 70%);
        }

        .particles {
            position: fixed;
            inset: 0;
            z-index: 1;
            pointer-events: none;
            overflow: hidden;
        }

        .particle {
            position: absolute;
            width: 3px;
            height: 3px;
            background: var(--gold);
            border-radius: 50%;
            opacity: 0;
            animation: floatUp linear infinite;
        }

        @keyframes floatUp {
            0% { opacity: 0; transform: translateY(100vh) scale(0); }
            10% { opacity: 0.8; }
            90% { opacity: 0.3; }
            100% { opacity: 0; transform: translateY(-10vh) scale(1); }
        }

        .main-layout {
            position: relative;
            z-index: 10;
            display: flex;
            height: 100vh;
            height: 100dvh;
            width: 100vw;
        }

        .avatar-section {
            flex: 0 0 55%;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            position: relative;
        }

        .avatar-container {
            position: relative;
            width: 420px;
            height: 580px;
        }

        .avatar-glow {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 500px;
            height: 500px;
            border-radius: 50%;
            background: radial-gradient(circle, rgba(212,168,83,0.15) 0%, transparent 70%);
            animation: glowPulse 4s ease-in-out infinite;
        }

        .avatar-glow.speaking {
            background: radial-gradient(circle, rgba(212,168,83,0.3) 0%, transparent 70%);
            animation: glowPulseSpeaking 1.5s ease-in-out infinite;
        }

        @keyframes glowPulse {
            0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.6; }
            50% { transform: translate(-50%, -50%) scale(1.05); opacity: 1; }
        }

        @keyframes glowPulseSpeaking {
            0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.8; }
            50% { transform: translate(-50%, -50%) scale(1.1); opacity: 1; }
        }

        .avatar-platform {
            position: absolute;
            bottom: 0;
            left: 50%;
            transform: translateX(-50%);
            width: 300px;
            height: 20px;
            background: radial-gradient(ellipse, rgba(212,168,83,0.3) 0%, transparent 70%);
            border-radius: 50%;
        }

        .digital-human {
            position: relative;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .dh-body {
            position: relative;
            width: 320px;
            height: 480px;
        }

        .dh-silhouette {
            position: absolute;
            bottom: 0;
            left: 50%;
            transform: translateX(-50%);
            width: 280px;
            height: 460px;
        }

        .dh-silhouette svg {
            width: 100%;
            height: 100%;
            filter: drop-shadow(0 0 30px rgba(212,168,83,0.2));
        }

        .dh-head {
            position: absolute;
            top: 0;
            left: 50%;
            transform: translateX(-50%);
            width: 160px;
            height: 160px;
            transition: transform 0.3s ease;
        }

        .dh-head.speaking {
            animation: headBob 3s ease-in-out infinite;
        }

        @keyframes headBob {
            0%, 100% { transform: translateX(-50%) rotate(0deg); }
            25% { transform: translateX(-50%) rotate(1deg) translateY(-2px); }
            75% { transform: translateX(-50%) rotate(-1deg) translateY(-1px); }
        }

        .dh-face {
            position: relative;
            width: 160px;
            height: 160px;
        }

        .dh-face svg {
            width: 100%;
            height: 100%;
        }

        .dh-mouth {
            transition: all 0.08s ease;
        }

        .dh-eyes-blink {
            animation: blink 4s ease-in-out infinite;
        }

        @keyframes blink {
            0%, 95%, 100% { transform: scaleY(1); }
            97% { transform: scaleY(0.1); }
        }

        .dh-torso {
            position: absolute;
            top: 140px;
            left: 50%;
            transform: translateX(-50%);
            width: 260px;
            height: 320px;
        }

        .dh-torso.speaking {
            animation: torsoSway 5s ease-in-out infinite;
        }

        @keyframes torsoSway {
            0%, 100% { transform: translateX(-50%) rotate(0deg); }
            33% { transform: translateX(-50%) rotate(0.5deg); }
            66% { transform: translateX(-50%) rotate(-0.5deg); }
        }

        .dh-hands {
            position: absolute;
            bottom: 60px;
            width: 100%;
        }

        .dh-hand {
            position: absolute;
            width: 50px;
            height: 50px;
        }

        .dh-hand.left { left: 10px; }
        .dh-hand.right { right: 10px; }

        .dh-hand.gesturing {
            animation: gestureLeft 2s ease-in-out;
        }

        .dh-hand.right.gesturing {
            animation: gestureRight 2s ease-in-out;
        }

        @keyframes gestureLeft {
            0%, 100% { transform: translateX(0) rotate(0deg); }
            30% { transform: translateX(30px) rotate(-10deg); }
            60% { transform: translateX(15px) rotate(-5deg); }
        }

        @keyframes gestureRight {
            0%, 100% { transform: translateX(0) rotate(0deg); }
            30% { transform: translateX(-30px) rotate(10deg); }
            60% { transform: translateX(-15px) rotate(5deg); }
        }

        .content-section {
            flex: 1;
            display: flex;
            flex-direction: column;
            justify-content: center;
            padding: 60px 80px 60px 40px;
            position: relative;
        }

        .phase-label {
            font-size: 18px;
            color: var(--gold);
            letter-spacing: 8px;
            text-transform: uppercase;
            margin-bottom: 16px;
            opacity: 0;
            transform: translateY(20px);
            transition: all 0.6s ease;
        }

        .phase-label.visible {
            opacity: 1;
            transform: translateY(0);
        }

        .script-display {
            position: relative;
            max-height: 50vh;
            overflow-y: auto;
            scrollbar-width: none;
        }

        .script-display::-webkit-scrollbar { display: none; }

        .script-text {
            font-size: 28px;
            line-height: 1.9;
            color: var(--text);
            letter-spacing: 2px;
            text-shadow: 0 2px 10px rgba(0,0,0,0.5);
        }

        .script-text .highlight {
            color: var(--gold-light);
            font-weight: 600;
        }

        .emotion-badge {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            margin-top: 24px;
            padding: 8px 20px;
            border: 1px solid rgba(212,168,83,0.3);
            border-radius: 30px;
            font-size: 14px;
            color: var(--gold);
            background: rgba(212,168,83,0.08);
            opacity: 0;
            transition: opacity 0.5s;
        }

        .emotion-badge.visible { opacity: 1; }

        .emotion-badge .dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: var(--gold);
            animation: dotPulse 2s infinite;
        }

        @keyframes dotPulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.3; }
        }

        .bottom-bar {
            position: fixed;
            bottom: 0;
            left: 0;
            right: 0;
            z-index: 20;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 16px 40px;
            background: linear-gradient(transparent, rgba(10,10,20,0.9));
            opacity: 0;
            transition: opacity 0.3s;
        }

        body.controls-visible .bottom-bar { opacity: 1; }

        .phase-nav {
            display: flex;
            gap: 8px;
        }

        .phase-nav-btn {
            padding: 8px 18px;
            border: 1px solid rgba(212,168,83,0.3);
            border-radius: 20px;
            background: rgba(212,168,83,0.08);
            color: var(--gold);
            font-size: 13px;
            cursor: pointer;
            transition: all 0.2s;
            font-family: inherit;
        }

        .phase-nav-btn:hover { background: rgba(212,168,83,0.2); border-color: var(--gold); }
        .phase-nav-btn.active { background: var(--gold); color: var(--dark); border-color: var(--gold); font-weight: 600; }

        .screen-controls {
            display: flex;
            gap: 12px;
            align-items: center;
        }

        .ctrl-btn {
            width: 40px;
            height: 40px;
            border: 1px solid rgba(212,168,83,0.3);
            border-radius: 50%;
            background: rgba(212,168,83,0.08);
            color: var(--gold);
            font-size: 16px;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .ctrl-btn:hover { background: rgba(212,168,83,0.25); border-color: var(--gold); }
        .ctrl-btn.speaking-btn { background: var(--gold); color: var(--dark); animation: speakPulse 1.5s infinite; }

        @keyframes speakPulse {
            0%, 100% { box-shadow: 0 0 0 0 rgba(212,168,83,0.4); }
            50% { box-shadow: 0 0 0 10px rgba(212,168,83,0); }
        }

        .voice-select-small {
            padding: 6px 12px;
            border: 1px solid rgba(212,168,83,0.3);
            border-radius: 20px;
            background: rgba(212,168,83,0.08);
            color: var(--gold);
            font-size: 12px;
            cursor: pointer;
            font-family: inherit;
            outline: none;
        }

        .voice-select-small option { background: var(--dark2); color: var(--text); }

        .watermark {
            position: fixed;
            top: 24px;
            right: 40px;
            z-index: 15;
            font-size: 13px;
            color: rgba(212,168,83,0.3);
            letter-spacing: 3px;
            opacity: 0;
            transition: opacity 0.3s;
        }

        body.controls-visible .watermark { opacity: 1; }

        .loading-overlay {
            position: fixed;
            inset: 0;
            z-index: 100;
            background: var(--dark);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            transition: opacity 0.8s;
        }

        .loading-overlay.hidden { opacity: 0; pointer-events: none; }

        .loading-ring {
            width: 60px;
            height: 60px;
            border: 2px solid rgba(212,168,83,0.2);
            border-top-color: var(--gold);
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }

        @keyframes spin { to { transform: rotate(360deg); } }

        .loading-text {
            margin-top: 20px;
            color: var(--gold);
            font-size: 16px;
            letter-spacing: 4px;
        }

        .audio-wave {
            position: absolute;
            bottom: 80px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 4px;
            align-items: flex-end;
            height: 40px;
            opacity: 0;
            transition: opacity 0.3s;
        }

        .audio-wave.active { opacity: 1; }

        .wave-bar {
            width: 4px;
            background: var(--gold);
            border-radius: 2px;
            transition: height 0.1s ease;
            height: 4px;
        }

        .tts-status-toast {
            position: fixed;
            top: 24px;
            left: 50%;
            transform: translateX(-50%) translateY(-60px);
            z-index: 200;
            padding: 10px 24px;
            border-radius: 30px;
            font-size: 14px;
            letter-spacing: 1px;
            transition: transform 0.4s ease;
            pointer-events: none;
        }

        .tts-status-toast.show { transform: translateX(-50%) translateY(0); }
        .tts-status-toast.loading { background: rgba(212,168,83,0.2); color: var(--gold); border: 1px solid rgba(212,168,83,0.3); }
        .tts-status-toast.error { background: rgba(200,50,50,0.2); color: #ff6b6b; border: 1px solid rgba(200,50,50,0.3); }
        .tts-status-toast.success { background: rgba(50,200,100,0.2); color: #6bffb8; border: 1px solid rgba(50,200,100,0.3); }

        .idle-text {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            text-align: center;
            color: var(--text-dim);
            font-size: 20px;
            letter-spacing: 4px;
            animation: fadeInOut 3s ease-in-out infinite;
        }

        @keyframes fadeInOut {
            0%, 100% { opacity: 0.3; }
            50% { opacity: 0.7; }
        }

        .video-overlay {
            position: fixed;
            inset: 0;
            z-index: 1000;
            background: rgba(0,0,0,0.95);
            display: none;
            align-items: center;
            justify-content: center;
        }

        .video-overlay.active {
            display: flex;
            animation: fadeIn 0.3s ease;
        }

        @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

        .video-container {
            position: relative;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .video-container video {
            max-width: 100%;
            max-height: 100%;
            object-fit: contain;
        }

        .video-controls-overlay {
            position: absolute;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 12px;
            opacity: 0;
            transition: opacity 0.3s;
        }

        .video-overlay:hover .video-controls-overlay {
            opacity: 1;
        }

        .video-ctrl-btn {
            width: 48px;
            height: 48px;
            border: 1px solid rgba(212,168,83,0.5);
            border-radius: 50%;
            background: rgba(212,168,83,0.1);
            color: var(--gold);
            font-size: 20px;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .video-ctrl-btn:hover {
            background: var(--gold);
            color: var(--dark);
        }

        .video-close-btn {
            position: absolute;
            top: 30px;
            right: 30px;
            width: 50px;
            height: 50px;
            border: 1px solid rgba(212,168,83,0.5);
            border-radius: 50%;
            background: rgba(212,168,83,0.1);
            color: var(--gold);
            font-size: 24px;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0;
        }

        .video-overlay:hover .video-close-btn {
            opacity: 1;
        }

        .video-close-btn:hover {
            background: rgba(200,50,50,0.8);
            border-color: #ff6b6b;
            color: #fff;
        }

        .video-progress {
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: rgba(212,168,83,0.2);
            cursor: pointer;
        }

        .video-progress-bar {
            height: 100%;
            background: var(--gold);
            transition: width 0.1s;
        }

        .video-time {
            position: absolute;
            bottom: 10px;
            right: 20px;
            font-size: 14px;
            color: var(--gold);
            opacity: 0.7;
        }

        @keyframes fadeInOut {
            0%, 100% { opacity: 0.3; }
            50% { opacity: 0.7; }
        }

        @media (max-width: 1200px) {
            .main-layout { flex-direction: column; }
            .avatar-section { flex: 0 0 45vh; }
            .avatar-container { width: 280px; height: 400px; }
            .content-section { padding: 20px 40px; }
            .script-text { font-size: 22px; }
        }

        @media (max-width: 768px) {
            .avatar-section { flex: 0 0 35vh; }
            .avatar-container { width: 200px; height: 300px; }
            .content-section { padding: 16px 24px; }
            .script-text { font-size: 18px; }
            .bottom-bar { padding: 12px 16px; flex-wrap: wrap; gap: 8px; }
        }

        .screen-volume-control {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .screen-vol-btn {
            font-size: 16px !important;
            min-width: 40px;
        }
        .screen-volume-slider {
            width: 100px;
            height: 24px;
            display: flex;
            align-items: center;
            cursor: pointer;
        }
        .screen-vol-track {
            width: 100%;
            height: 4px;
            background: rgba(212,168,83,0.3);
            border-radius: 2px;
            position: relative;
        }
        .screen-vol-filled {
            position: absolute;
            top: 0;
            left: 0;
            height: 100%;
            background: var(--gold);
            border-radius: 2px;
            width: 100%;
            transition: width 0.05s;
        }
        .screen-vol-indicator {
            display: flex;
            align-items: flex-end;
            gap: 2px;
            height: 16px;
        }
        .sv-bar {
            display: block;
            width: 3px;
            background: rgba(212,168,83,0.3);
            border-radius: 1px;
        }
        .sv-1 { height: 4px; }
        .sv-2 { height: 7px; }
        .sv-3 { height: 10px; }
        .sv-4 { height: 13px; }
        .sv-5 { height: 16px; }
        .sv-bar.active { background: var(--gold); }
        .sv-bar.muted { background: rgba(200,50,50,0.5); }
    </style>
</head>
<body>

<div class="screen-bg"></div>

<div class="particles" id="particles"></div>

<div class="loading-overlay" id="loadingOverlay">
    <div class="loading-ring"></div>
    <div class="loading-text">婚礼司仪数字人</div>
</div>

<div class="tts-status-toast" id="ttsToast"></div>

<div class="watermark">婚礼主持 · 喜乐</div>

<div class="main-layout">
    <div class="avatar-section">
        <div class="avatar-container">
            <div class="avatar-glow" id="avatarGlow"></div>
            <div class="digital-human">
                <div class="dh-body">
                    <div class="dh-head" id="dhHead">
                        <div class="dh-face">
                            <svg viewBox="0 0 200 220" xmlns="http://www.w3.org/2000/svg">
                                <defs>
                                    <linearGradient id="skinGrad" x1="0%" y1="0%" x2="0%" y2="100%">
                                        <stop offset="0%" style="stop-color:#f5d0a9"/>
                                        <stop offset="100%" style="stop-color:#e8b88a"/>
                                    </linearGradient>
                                    <linearGradient id="hairGrad" x1="0%" y1="0%" x2="0%" y2="100%">
                                        <stop offset="0%" style="stop-color:#1a1a1a"/>
                                        <stop offset="100%" style="stop-color:#2d2d2d"/>
                                    </linearGradient>
                                    <linearGradient id="suitGrad" x1="0%" y1="0%" x2="0%" y2="100%">
                                        <stop offset="0%" style="stop-color:#1a1a2e"/>
                                        <stop offset="100%" style="stop-color:#0f0f1a"/>
                                    </linearGradient>
                                    <linearGradient id="shirtGrad" x1="0%" y1="0%" x2="0%" y2="100%">
                                        <stop offset="0%" style="stop-color:#f8f8f8"/>
                                        <stop offset="100%" style="stop-color:#e8e8e8"/>
                                    </linearGradient>
                                    <radialGradient id="cheekGrad" cx="50%" cy="50%" r="50%">
                                        <stop offset="0%" style="stop-color:rgba(220,150,130,0.3)"/>
                                        <stop offset="100%" style="stop-color:rgba(220,150,130,0)"/>
                                    </radialGradient>
                                </defs>

                                <ellipse cx="100" cy="130" rx="55" ry="65" fill="url(#skinGrad)"/>

                                <ellipse cx="100" cy="68" rx="62" ry="42" fill="url(#hairGrad)"/>
                                <path d="M38 80 Q38 50 100 45 Q162 50 162 80 L162 90 Q130 75 100 75 Q70 75 38 90 Z" fill="url(#hairGrad)"/>

                                <g class="dh-eyes-blink" id="dhEyes">
                                    <ellipse cx="78" cy="125" rx="8" ry="6" fill="#2c1810"/>
                                    <ellipse cx="122" cy="125" rx="8" ry="6" fill="#2c1810"/>
                                    <ellipse cx="80" cy="123" rx="3" ry="2.5" fill="#fff" opacity="0.7"/>
                                    <ellipse cx="124" cy="123" rx="3" ry="2.5" fill="#fff" opacity="0.7"/>

                                    <path d="M65 118 Q78 113 91 118" stroke="#1a1a1a" stroke-width="2" fill="none"/>
                                    <path d="M109 118 Q122 113 135 118" stroke="#1a1a1a" stroke-width="2" fill="none"/>
                                </g>

                                <ellipse cx="65" cy="138" rx="10" ry="6" fill="url(#cheekGrad)"/>
                                <ellipse cx="135" cy="138" rx="10" ry="6" fill="url(#cheekGrad)"/>

                                <path d="M92 140 Q100 142 108 140" stroke="#c4956a" stroke-width="1.5" fill="none"/>

                                <g class="dh-mouth" id="dhMouth">
                                    <path id="mouthPath" d="M88 155 Q100 162 112 155" stroke="#b5453a" stroke-width="2.5" fill="none" stroke-linecap="round"/>
                                </g>

                                <path d="M42 155 Q42 175 55 190 L145 190 Q158 175 158 155 Q158 200 100 210 Q42 200 42 155" fill="url(#skinGrad)"/>

                                <path d="M55 190 L100 210 L145 190 Q160 220 170 240 L30 240 Q40 220 55 190" fill="url(#suitGrad)"/>

                                <path d="M88 190 L100 240 L112 190" fill="url(#shirtGrad)"/>

                                <path d="M100 195 L95 225 L100 240 L105 225 Z" fill="#b64a2a"/>

                                <rect x="97" y="188" width="6" height="8" rx="1" fill="#d4a853"/>

                                <path d="M30 240 Q30 260 100 270 Q170 260 170 240" fill="url(#suitGrad)"/>

                                <path d="M30 240 L10 300 L40 300 L50 260" fill="url(#suitGrad)"/>
                                <path d="M170 240 L190 300 L160 300 L150 260" fill="url(#suitGrad)"/>
                            </svg>
                        </div>
                    </div>
                </div>
            </div>
            <div class="avatar-platform"></div>
            <div class="audio-wave" id="audioWave"></div>
        </div>
    </div>

    <div class="content-section">
        <div class="phase-label" id="phaseLabel">开场白</div>
        <div class="script-display" id="scriptDisplay">
            <div class="script-text" id="scriptText">
                <div class="idle-text">选择婚礼环节开始</div>
            </div>
        </div>
        <div class="emotion-badge" id="emotionBadge">
            <span class="dot"></span>
            <span id="emotionText">温暖</span>
        </div>
    </div>
</div>

<div class="video-overlay" id="videoOverlay">
    <div class="video-container">
        <video id="screenVideo" autoplay muted playsinline webkit-playsinline x5-playsinline></video>
        <button class="video-close-btn" id="btnCloseVideo">✕</button>
        <div class="video-controls-overlay">
            <button class="video-ctrl-btn" id="btnVideoPlay">▶</button>
            <button class="video-ctrl-btn" id="btnVideoPause">⏸</button>
            <button class="video-ctrl-btn" id="btnVideoRestart">↻</button>
            <div class="screen-volume-control">
                <button class="video-ctrl-btn screen-vol-btn" id="btnScreenMute" title="静音">🔊</button>
                <div class="screen-volume-slider" id="screenVolumeSlider">
                    <div class="screen-vol-track">
                        <div class="screen-vol-filled" id="screenVolFilled"></div>
                    </div>
                </div>
                <div class="screen-vol-indicator" id="screenVolIndicator">
                    <span class="sv-bar sv-1"></span>
                    <span class="sv-bar sv-2"></span>
                    <span class="sv-bar sv-3"></span>
                    <span class="sv-bar sv-4"></span>
                    <span class="sv-bar sv-5"></span>
                </div>
            </div>
        </div>
        <div class="video-progress" id="videoProgress">
            <div class="video-progress-bar" id="videoProgressBar"></div>
        </div>
        <div class="video-time" id="videoTime">00:00 / 00:00</div>
    </div>
</div>

<div class="bottom-bar">
    <div class="phase-nav" id="phaseNav">
        <button class="phase-nav-btn" data-phase="opening" data-emotion="warm">🎊 开场白</button>
        <button class="phase-nav-btn" data-phase="bride_entrance" data-emotion="romantic">👰 新娘入场</button>
        <button class="phase-nav-btn" data-phase="vow" data-emotion="romantic">💕 誓言</button>
        <button class="phase-nav-btn" data-phase="ring" data-emotion="solemn">💍 戒指</button>
        <button class="phase-nav-btn" data-phase="parent_tea" data-emotion="warm">🍵 敬茶</button>
        <button class="phase-nav-btn" data-phase="toast" data-emotion="joyful">🥂 祝酒</button>
        <button class="phase-nav-btn" data-phase="game" data-emotion="joyful">🎮 互动</button>
        <button class="phase-nav-btn" data-phase="closing" data-emotion="warm">🎉 礼成</button>
    </div>
    <div class="screen-controls">
        <select class="voice-select-small" id="ttsVoice">
            <option value="charles" selected>🎙️ 磁性男声</option>
            <option value="alex">🎵 沉稳男声</option>
            <option value="benjamin">🔊 低沉男声</option>
            <option value="david">🎉 欢快男声</option>
            <option value="browser">🌐 浏览器语音</option>
        </select>
        <button class="ctrl-btn" id="btnSpeak" title="朗读">🔊</button>
        <button class="ctrl-btn" id="btnStop" title="停止" style="display:none;">⏹</button>
        <button class="ctrl-btn" id="btnFullscreen" title="全屏">⛶</button>
    </div>
</div>

<script>
(function() {
    var currentPhase = null;
    var currentEmotion = 'warm';
    var isSpeaking = false;
    var currentAudio = null;
    var audioContext = null;
    var analyser = null;
    var animFrameId = null;
    var browserSynth = window.speechSynthesis;
    var browserVoices = [];
    var controlsTimer = null;
    var currentVideo = null;
    var videoOverlay = null;
    var syncCheckInterval = null;
    var screenVolume = 1.0;
    var screenPrevVolume = 1.0;
    var screenIsMuted = false;
    var screenVolDragging = false;

    var emotionLabels = {
        warm: '温暖', excited: '热烈', romantic: '浪漫',
        solemn: '庄重', joyful: '欢快', quiet: '安静'
    };

    var emotionSpeedMap = {
        warm: 0.95, excited: 1.15, romantic: 0.85,
        solemn: 0.9, joyful: 1.1, quiet: 0.8
    };

    var phaseNames = {
        opening: '开场白', bride_entrance: '新娘入场', vow: '誓言环节',
        ring: '交换戒指', parent_tea: '敬茶改口', toast: '祝酒环节',
        game: '互动游戏', closing: '圆满礼成'
    };

    function init() {
        createParticles();
        createWaveBars();
        setupControls();
        setupVideoControls();
        loadBrowserVoices();
        startSyncCheck();

        window.addEventListener('message', onMessageReceived);

        setTimeout(function() {
            document.getElementById('loadingOverlay').classList.add('hidden');
        }, 1500);
    }

    function createParticles() {
        var container = document.getElementById('particles');
        for (var i = 0; i < 30; i++) {
            var p = document.createElement('div');
            p.className = 'particle';
            p.style.left = Math.random() * 100 + '%';
            p.style.animationDuration = (8 + Math.random() * 12) + 's';
            p.style.animationDelay = Math.random() * 10 + 's';
            p.style.width = (2 + Math.random() * 3) + 'px';
            p.style.height = p.style.width;
            container.appendChild(p);
        }
    }

    function createWaveBars() {
        var wave = document.getElementById('audioWave');
        for (var i = 0; i < 24; i++) {
            var bar = document.createElement('div');
            bar.className = 'wave-bar';
            wave.appendChild(bar);
        }
    }

    function setupControls() {
        document.addEventListener('mousemove', showControls);
        document.addEventListener('touchstart', showControls);
        document.addEventListener('click', showControls);

        document.querySelectorAll('.phase-nav-btn').forEach(function(btn) {
            btn.addEventListener('click', function() {
                if (isSpeaking) stopSpeaking();
                startPhase(this.dataset.phase, this.dataset.emotion);
                document.querySelectorAll('.phase-nav-btn').forEach(function(b) { b.classList.remove('active'); });
                this.classList.add('active');
            });
        });

        document.getElementById('btnSpeak').addEventListener('click', function() {
            var text = document.getElementById('scriptText').innerText;
            if (text && text !== '选择婚礼环节开始') speak(text);
        });

        document.getElementById('btnStop').addEventListener('click', stopSpeaking);

        document.getElementById('btnFullscreen').addEventListener('click', toggleFullscreen);
    }

    function setupVideoControls() {
        videoOverlay = document.getElementById('videoOverlay');
        currentVideo = document.getElementById('screenVideo');

        document.getElementById('btnCloseVideo').addEventListener('click', closeVideo);
        document.getElementById('btnVideoPlay').addEventListener('click', playVideo);
        document.getElementById('btnVideoPause').addEventListener('click', pauseVideo);
        document.getElementById('btnVideoRestart').addEventListener('click', restartVideo);
        document.getElementById('videoProgress').addEventListener('click', seekVideo);

        currentVideo.addEventListener('timeupdate', updateVideoProgress);
        currentVideo.addEventListener('ended', onVideoEnded);
        currentVideo.addEventListener('loadedmetadata', updateVideoDuration);

        setupScreenVolume();
    }

    function setupScreenVolume() {
        var btnScreenMute = document.getElementById('btnScreenMute');
        var slider = document.getElementById('screenVolumeSlider');
        var filled = document.getElementById('screenVolFilled');
        var indicator = document.getElementById('screenVolIndicator');

        btnScreenMute.addEventListener('click', function() {
            if (screenIsMuted) {
                screenIsMuted = false;
                screenVolume = screenPrevVolume;
            } else {
                screenIsMuted = true;
                screenPrevVolume = screenVolume > 0 ? screenVolume : 0.5;
                screenVolume = 0;
            }
            if (currentVideo) currentVideo.volume = screenVolume;
            updateScreenVolUI();
        });

        slider.addEventListener('mousedown', function(e) {
            screenVolDragging = true;
            updateScreenVolFromEvent(e);
        });
        slider.addEventListener('touchstart', function(e) {
            screenVolDragging = true;
            updateScreenVolFromEvent(e);
        }, { passive: true });
        document.addEventListener('mousemove', function(e) {
            if (!screenVolDragging) return;
            updateScreenVolFromEvent(e);
        });
        document.addEventListener('touchmove', function(e) {
            if (!screenVolDragging) return;
            updateScreenVolFromEvent(e);
        }, { passive: true });
        document.addEventListener('mouseup', function() { screenVolDragging = false; });
        document.addEventListener('touchend', function() { screenVolDragging = false; });
    }

    function updateScreenVolFromEvent(e) {
        var slider = document.getElementById('screenVolumeSlider');
        var track = slider.querySelector('.screen-vol-track');
        var rect = track.getBoundingClientRect();
        var clientX = e.touches ? e.touches[0].clientX : e.clientX;
        var percent = (clientX - rect.left) / rect.width;
        percent = Math.max(0, Math.min(1, percent));
        screenVolume = percent;
        screenIsMuted = percent === 0;
        if (percent > 0) screenPrevVolume = percent;
        if (currentVideo) currentVideo.volume = screenVolume;
        updateScreenVolUI();
    }

    function updateScreenVolUI() {
        var filled = document.getElementById('screenVolFilled');
        var btnScreenMute = document.getElementById('btnScreenMute');
        var indicator = document.getElementById('screenVolIndicator');

        filled.style.width = (screenVolume * 100) + '%';

        if (screenIsMuted || screenVolume === 0) {
            btnScreenMute.textContent = '🔇';
            btnScreenMute.title = '取消静音';
        } else if (screenVolume < 0.5) {
            btnScreenMute.textContent = '🔉';
            btnScreenMute.title = '静音';
        } else {
            btnScreenMute.textContent = '🔊';
            btnScreenMute.title = '静音';
        }

        var bars = indicator.querySelectorAll('.sv-bar');
        var activeBars = 0;
        if (screenVolume === 0 || screenIsMuted) activeBars = 0;
        else if (screenVolume <= 0.2) activeBars = 1;
        else if (screenVolume <= 0.4) activeBars = 2;
        else if (screenVolume <= 0.6) activeBars = 3;
        else if (screenVolume <= 0.8) activeBars = 4;
        else activeBars = 5;

        bars.forEach(function(bar, index) {
            bar.classList.remove('active', 'muted');
            if (screenIsMuted || screenVolume === 0) bar.classList.add('muted');
            else if (index < activeBars) bar.classList.add('active');
        });
    }

    function startSyncCheck() {
        syncCheckInterval = setInterval(function() {
            checkVideoSync();
            checkReminder();
        }, 200);
    }

    var lastSyncTime = 0;
    var lastSyncUrl = '';
    var lastVideoId = '';
    var lastReminderTimestamp = 0;

    function checkReminder() {
        var reminderData = localStorage.getItem('checkpointReminder');
        if (reminderData) {
            try {
                var data = JSON.parse(reminderData);
                if (data.timestamp && data.timestamp !== lastReminderTimestamp) {
                    lastReminderTimestamp = data.timestamp;
                    handleReminder(data);
                }
                localStorage.removeItem('checkpointReminder');
            } catch(e) {}
        }
    }

    function handleReminder(data) {
        if (!data) return;
        var msg = data.message || '';
        var type = data.reminderType || 'before';
        var isTest = data.isTest || false;

        if (data.requireVoice && msg) {
            var scriptEl = document.getElementById('scriptText');
            if (scriptEl) {
                var prefix = isTest ? '🧪 [测试播报] ' : '🔔 ';
                scriptEl.innerHTML = prefix + msg.replace(/\n/g, '<br>');
            }
            speak(msg);
        }

        var emoji = type === 'overdue' ? '⚠️' : type === 'switch' ? '🔄' : '⏰';
        var label = isTest ? '🧪 测试' : emoji;
        showTtsToast(isTest ? 'success' : (type === 'overdue' ? 'error' : 'success'), label + ' ' + (data.phaseName || ''));
    }

    function onMessageReceived(e) {
        try {
            if (e.data && e.data.type === 'checkpointReminder') {
                if (e.data.payload) {
                    handleReminder(e.data.payload);
                }
                return;
            }
            if (e.data && e.data.type === 'videoPlay') {
                if (e.data.payload && e.data.payload.url) {
                    playVideoFromUrl(e.data.payload.url);
                    lastSyncUrl = e.data.payload.url;
                    lastVideoId = e.data.payload.id || '';
                    if (e.data.payload.currentTime !== undefined) {
                        setTimeout(function() {
                            if (currentVideo && !isNaN(e.data.payload.currentTime)) {
                                currentVideo.currentTime = e.data.payload.currentTime;
                            }
                        }, 500);
                    }
                }
            } else if (e.data && e.data.type === 'videoPause') {
                pauseVideo();
            } else if (e.data && e.data.type === 'videoStop') {
                closeVideo();
                lastSyncUrl = '';
                lastVideoId = '';
            } else if (e.data && e.data.type === 'phaseUpdate') {
                if (e.data.payload && e.data.payload.phase) {
                    startPhase(e.data.payload.phase, e.data.payload.emotion || 'warm');
                }
            }
        } catch(err) {
            console.error('postMessage handler error:', err);
        }
    }

    function checkVideoSync() {
        var videoUrl = localStorage.getItem('currentVideoUrl');
        var command = localStorage.getItem('videoPlayCommand');
        var syncTime = localStorage.getItem('syncTime');
        var syncId = localStorage.getItem('currentVideoId');
        
        if (videoUrl && syncId !== lastVideoId) {
            if (command === 'play') {
                localStorage.removeItem('videoPlayCommand');
                playVideoFromUrl(videoUrl);
                lastSyncUrl = videoUrl;
                lastVideoId = syncId;
            }
        } else if (videoUrl && command === 'play' && videoUrl !== lastSyncUrl) {
            localStorage.removeItem('videoPlayCommand');
            playVideoFromUrl(videoUrl);
            lastSyncUrl = videoUrl;
            lastVideoId = syncId;
        } else if (command === 'pause') {
            localStorage.removeItem('videoPlayCommand');
            pauseVideo();
        } else if (command === 'stop') {
            localStorage.removeItem('videoPlayCommand');
            closeVideo();
            lastSyncUrl = '';
            lastVideoId = '';
        }
        
        if (videoUrl && syncId && videoUrl === lastSyncUrl && currentVideo && !currentVideo.paused && !currentVideo.ended) {
            var currentTime = parseFloat(syncTime);
            if (!isNaN(currentTime) && Math.abs(currentVideo.currentTime - currentTime) > 0.5) {
                currentVideo.currentTime = currentTime;
            }
        }
    }

    function playVideoFromUrl(url) {
        if (!url) return;
        
        closeVideo();
        
        currentVideo.src = url;
        currentVideo.volume = screenVolume;
        videoOverlay.classList.add('active');
        document.body.classList.add('controls-visible');
        
        currentVideo.play().catch(function(e) {
            console.error('Video play error:', e);
        });
    }

    function playVideo() {
        if (currentVideo) {
            currentVideo.play();
        }
    }

    function pauseVideo() {
        if (currentVideo) {
            currentVideo.pause();
        }
    }

    function restartVideo() {
        if (currentVideo) {
            currentVideo.currentTime = 0;
            currentVideo.play();
        }
    }

    function closeVideo() {
        if (currentVideo) {
            currentVideo.pause();
            currentVideo.src = '';
        }
        videoOverlay.classList.remove('active');
    }

    function seekVideo(e) {
        if (!currentVideo) return;
        
        var rect = this.getBoundingClientRect();
        var percent = (e.clientX - rect.left) / rect.width;
        currentVideo.currentTime = percent * currentVideo.duration;
    }

    function updateVideoProgress() {
        if (!currentVideo || isNaN(currentVideo.duration)) return;
        
        var percent = (currentVideo.currentTime / currentVideo.duration) * 100;
        document.getElementById('videoProgressBar').style.width = percent + '%';
        
        updateVideoTime();
    }

    function updateVideoDuration() {
        updateVideoTime();
    }

    function updateVideoTime() {
        if (!currentVideo || isNaN(currentVideo.duration)) return;
        
        var current = formatTime(currentVideo.currentTime);
        var total = formatTime(currentVideo.duration);
        document.getElementById('videoTime').textContent = current + ' / ' + total;
    }

    function formatTime(seconds) {
        var mins = Math.floor(seconds / 60);
        var secs = Math.floor(seconds % 60);
        return mins.toString().padStart(2, '0') + ':' + secs.toString().padStart(2, '0');
    }

    function onVideoEnded() {
        closeVideo();
    }

    function showControls() {
        document.body.classList.add('controls-visible');
        clearTimeout(controlsTimer);
        controlsTimer = setTimeout(function() {
            document.body.classList.remove('controls-visible');
        }, 4000);
    }

    function loadBrowserVoices() {
        browserVoices = browserSynth ? browserSynth.getVoices() : [];
        if (browserSynth && browserSynth.onvoiceschanged !== undefined) {
            browserSynth.onvoiceschanged = function() { browserVoices = browserSynth.getVoices(); };
        }
        setTimeout(function() { browserVoices = browserSynth ? browserSynth.getVoices() : []; }, 1000);
    }

    function toggleFullscreen() {
        if (!document.fullscreenElement) {
            document.documentElement.requestFullscreen().catch(function() {});
        } else {
            document.exitFullscreen();
        }
    }

    function startPhase(phase, emotion) {
        currentPhase = phase;
        currentEmotion = emotion;
        updateEmotionBadge(emotion);

        var label = document.getElementById('phaseLabel');
        label.textContent = phaseNames[phase] || phase;
        label.classList.remove('visible');
        setTimeout(function() { label.classList.add('visible'); }, 50);

        document.getElementById('scriptText').innerHTML = '<div class="idle-text">⏳ 正在准备...</div>';

        apiCall('POST', 'digital-human/host/phase', {
            phase: phase,
            context: JSON.stringify({ groom_name: '新郎', bride_name: '新娘' })
        }, function(err, resp) {
            if (err || !resp.success || !resp.data) {
                document.getElementById('scriptText').innerHTML = '<div class="idle-text">❌ 生成失败，请重试</div>';
                return;
            }
            var data = resp.data;
            var html = (data.script || '').replace(/\n/g, '<br>');
            document.getElementById('scriptText').innerHTML = html;
            if (data.emotion) {
                currentEmotion = data.emotion;
                updateEmotionBadge(data.emotion);
            }
            speak(data.script);
        });
    }

    function updateEmotionBadge(emotion) {
        var badge = document.getElementById('emotionBadge');
        document.getElementById('emotionText').textContent = emotionLabels[emotion] || emotion;
        badge.classList.add('visible');
    }

    function speak(text) {
        if (!text) return;
        stopSpeaking();

        var voice = document.getElementById('ttsVoice').value;
        if (voice === 'browser') {
            speakBrowser(text);
        } else {
            speakSiliconflow(text, voice);
        }
    }

    function speakSiliconflow(text, voice) {
        var speed = 1.0;
        var finalSpeed = speed * (emotionSpeedMap[currentEmotion] || 1.0);

        showTtsToast('loading', '🎙️ 正在合成语音...');
        setSpeakingState(true);

        var xhr = new XMLHttpRequest();
        xhr.open('POST', 'api/tts_proxy.php', true);
        xhr.setRequestHeader('Content-Type', 'application/json');
        xhr.timeout = 60000;

        xhr.onload = function() {
            var resp;
            try { resp = JSON.parse(xhr.responseText); } catch(e) {
                showTtsToast('error', '❌ 响应解析失败');
                speakBrowser(text);
                return;
            }
            if (xhr.status === 200 && resp && resp.success) {
                showTtsToast('success', '✅ 语音就绪');
                playAudio(resp.audio_url);
            } else {
                showTtsToast('error', '❌ ' + ((resp && resp.message) || '合成失败'));
                speakBrowser(text);
            }
        };
        xhr.onerror = function() { showTtsToast('error', '❌ 网络错误'); speakBrowser(text); };
        xhr.ontimeout = function() { showTtsToast('error', '❌ 请求超时'); speakBrowser(text); };

        xhr.send(JSON.stringify({ text: text, voice: voice, speed: finalSpeed, emotion: currentEmotion }));
    }

    function playAudio(url) {
        if (currentAudio) { currentAudio.pause(); currentAudio = null; }

        currentAudio = new Audio(url);
        currentAudio.volume = 1.0;
        currentAudio.crossOrigin = 'anonymous';

        currentAudio.onplay = function() {
            setSpeakingState(true);
            setupAudioAnalyser(currentAudio);
        };
        currentAudio.onended = function() { setSpeakingState(false); };
        currentAudio.onerror = function() { setSpeakingState(false); };

        currentAudio.play().catch(function() { setSpeakingState(false); });
    }

    function setupAudioAnalyser(audioEl) {
        try {
            if (!audioContext) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
            }
            if (audioContext.state === 'suspended') audioContext.resume();

            var source = audioContext.createMediaElementSource(audioEl);
            analyser = audioContext.createAnalyser();
            analyser.fftSize = 64;
            source.connect(analyser);
            analyser.connect(audioContext.destination);

            updateWaveFromAnalyser();
        } catch(e) {
            updateWaveSimulated();
        }
    }

    function updateWaveFromAnalyser() {
        if (!isSpeaking || !analyser) return;

        var bars = document.querySelectorAll('.wave-bar');
        var data = new Uint8Array(analyser.frequencyBinCount);
        analyser.getByteFrequencyData(data);

        var mouthOpen = 0;
        for (var i = 0; i < Math.min(data.length, 12); i++) {
            mouthOpen += data[i];
        }
        mouthOpen = mouthOpen / (12 * 255);

        updateMouth(mouthOpen);

        for (var j = 0; j < bars.length; j++) {
            var idx = Math.floor(j * data.length / bars.length);
            var val = data[idx] / 255;
            bars[j].style.height = Math.max(4, val * 40) + 'px';
            bars[j].style.opacity = 0.4 + val * 0.6;
        }

        animFrameId = requestAnimationFrame(updateWaveFromAnalyser);
    }

    function updateWaveSimulated() {
        if (!isSpeaking) return;

        var bars = document.querySelectorAll('.wave-bar');
        for (var i = 0; i < bars.length; i++) {
            var h = 4 + Math.random() * 30;
            bars[i].style.height = h + 'px';
            bars[i].style.opacity = 0.5 + Math.random() * 0.5;
        }

        var mouthOpen = 0.3 + Math.random() * 0.5;
        updateMouth(mouthOpen);

        setTimeout(updateWaveSimulated, 100);
    }

    function updateMouth(openAmount) {
        var mouthPath = document.getElementById('mouthPath');
        var openY = 155 + openAmount * 12;
        var curveDepth = openAmount * 10;
        mouthPath.setAttribute('d',
            'M88 ' + openY + ' Q100 ' + (openY + curveDepth) + ' 112 ' + openY
        );

        if (openAmount > 0.3) {
            mouthPath.setAttribute('fill', 'rgba(80,20,20,0.6)');
            mouthPath.setAttribute('stroke-width', '1.5');
        } else {
            mouthPath.setAttribute('fill', 'none');
            mouthPath.setAttribute('stroke-width', '2.5');
        }
    }

    function speakBrowser(text) {
        if (!browserSynth) return;

        var cleanText = text.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '')
            .replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').trim();
        if (!cleanText) return;

        var utterance = new SpeechSynthesisUtterance(cleanText);
        var zhVoice = browserVoices.find(function(v) { return v.lang.indexOf('zh') > -1 && v.name.toLowerCase().indexOf('male') > -1; })
            || browserVoices.find(function(v) { return v.lang.indexOf('zh') > -1; });
        if (zhVoice) utterance.voice = zhVoice;

        utterance.rate = 1.0;
        utterance.pitch = 0.8;
        utterance.volume = 1.0;
        utterance.lang = 'zh-CN';

        utterance.onstart = function() {
            setSpeakingState(true);
            updateWaveSimulated();
        };
        utterance.onend = function() { setSpeakingState(false); };
        utterance.onerror = function() { setSpeakingState(false); };

        browserSynth.speak(utterance);
    }

    function stopSpeaking() {
        if (currentAudio) {
            currentAudio.pause();
            currentAudio.currentTime = 0;
            currentAudio = null;
        }
        if (browserSynth) browserSynth.cancel();
        if (animFrameId) cancelAnimationFrame(animFrameId);
        analyser = null;
        setSpeakingState(false);
        resetMouth();
    }

    function setSpeakingState(speaking) {
        isSpeaking = speaking;
        var head = document.getElementById('dhHead');
        var glow = document.getElementById('avatarGlow');
        var wave = document.getElementById('audioWave');
        var btnSpeak = document.getElementById('btnSpeak');
        var btnStop = document.getElementById('btnStop');

        if (speaking) {
            head.classList.add('speaking');
            glow.classList.add('speaking');
            wave.classList.add('active');
            btnSpeak.classList.add('speaking-btn');
            btnSpeak.style.display = 'none';
            btnStop.style.display = 'flex';
        } else {
            head.classList.remove('speaking');
            glow.classList.remove('speaking');
            wave.classList.remove('active');
            btnSpeak.classList.remove('speaking-btn');
            btnSpeak.style.display = 'flex';
            btnStop.style.display = 'none';

            var bars = document.querySelectorAll('.wave-bar');
            bars.forEach(function(b) { b.style.height = '4px'; b.style.opacity = '0.3'; });
        }
    }

    function resetMouth() {
        var mouthPath = document.getElementById('mouthPath');
        mouthPath.setAttribute('d', 'M88 155 Q100 162 112 155');
        mouthPath.setAttribute('fill', 'none');
        mouthPath.setAttribute('stroke-width', '2.5');
    }

    function showTtsToast(type, msg) {
        var toast = document.getElementById('ttsToast');
        toast.className = 'tts-status-toast ' + type + ' show';
        toast.textContent = msg;
        if (type === 'success') {
            setTimeout(function() { toast.classList.remove('show'); }, 2000);
        } else if (type === 'error') {
            setTimeout(function() { toast.classList.remove('show'); }, 4000);
        }
    }

    function apiCall(method, path, params, callback) {
        var xhr = new XMLHttpRequest();
        var url = 'api/api_router.php/' + path;
        if (method === 'GET' && params) {
            var qs = Object.keys(params).map(function(k) { return k + '=' + encodeURIComponent(params[k]); }).join('&');
            url += '?' + qs;
        }
        xhr.open(method, url, true);
        xhr.timeout = 60000;
        xhr.onload = function() {
            if (xhr.status === 200) {
                try { callback(null, JSON.parse(xhr.responseText)); }
                catch(e) { callback(e); }
            } else { callback(new Error('HTTP ' + xhr.status)); }
        };
        xhr.onerror = function() { callback(new Error('网络错误')); };
        xhr.ontimeout = function() { callback(new Error('请求超时')); };

        if (method === 'POST' && params) {
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            var body = Object.keys(params).map(function(k) { return k + '=' + encodeURIComponent(params[k]); }).join('&');
            xhr.send(body);
        } else {
            xhr.send();
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();
</script>

</body>
</html>
