小K网牛逼 发表于 2025-12-26 00:00:05

【HTML】美食大转盘 - 点餐助手



增加导入txt文本功能,增加一键清空功能,将所有代码进行注释,方便大家修改,瞎弄的,博大家一笑!!!
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>美食大转盘 - 点餐助手</title>
    <style>
      /*
      ============================================
      样式表说明
      ============================================
      给维护者:
      - 使用CSS变量确保颜色一致性
      - 采用响应式设计,移动端优先
      - 使用backdrop-filter实现毛玻璃效果
      给学习者:
      - 使用clamp()实现响应式字体
      - aspect-ratio保持元素比例
      - CSS Grid创建两栏布局
      ============================================
      */
         
      :root {
            --primary-color: #f97316;
            --secondary-color: #db2777;
            --accent-color: #7c3aed;
            --success-color: #10b981;
            --warning-color: #f59e0b;
            --danger-color: #ef4444;
            --text-light: rgba(255, 255, 255, 0.9);
            --text-lighter: rgba(255, 255, 255, 0.7);
            --bg-overlay: rgba(255, 255, 255, 0.1);
            --bg-overlay-dark: rgba(0, 0, 0, 0.2);
            --border-light: rgba(255, 255, 255, 0.2);
            --shadow-color: rgba(0, 0, 0, 0.1);
      }
         
      * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
      }
   
      body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
            min-height: 100vh;
            /* NOTE: 美食主题渐变背景色 */
            background: linear-gradient(135deg,
                #ff6b6b 0%,
                #ff9a3c 25%,
                #f6d365 50%,
                #84fab0 75%,
                #8fd3f4 100%);
            background-attachment: fixed;
            padding: 1rem;
            color: white;
            position: relative;
            overflow-x: hidden;
      }
         
      /* 背景装饰元素 */
      body::before {
            content: '';
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background:
                radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
                radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
                radial-gradient(circle at 40% 40%, rgba(255, 107, 107, 0.15) 0%, transparent 50%),
                radial-gradient(circle at 60% 60%, rgba(255, 154, 60, 0.15) 0%, transparent 50%);
            z-index: -1;
      }
   
      .container {
            max-width: 1200px;
            margin: 0 auto;
            position: relative;
      }
   
      .header {
            text-align: center;
            margin-bottom: 2rem;
            position: relative;
      }
         
      .header::after {
            content: '';
            position: absolute;
            bottom: -10px;
            left: 50%;
            transform: translateX(-50%);
            width: 150px;
            height: 4px;
            background: linear-gradient(90deg, transparent, var(--secondary-color), transparent);
            border-radius: 2px;
      }
   
      .header h1 {
            /* OPTIMIZE: clamp()实现平滑的字体响应式变化 */
            font-size: clamp(1.8rem, 6vw, 3rem);
            font-weight: 900;
            color: #fff;
            margin: 0.5rem 0;
            text-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
            letter-spacing: 1px;
            background: linear-gradient(135deg, #fff 0%, #fcd34d 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
      }
   
      .header p {
            color: var(--text-light);
            font-size: 1.2rem;
            font-weight: 500;
            margin-top: 0.5rem;
            max-width: 600px;
            margin-left: auto;
            margin-right: auto;
            line-height: 1.6;
      }

      .header .emoji-bounce {
            display: inline-block;
            animation: bounce 2s infinite;
            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
      }
         
      .subtitle {
            font-size: 1rem;
            color: var(--text-lighter);
            margin-top: 0.5rem;
            font-style: italic;
      }
   
      .main-grid {
            display: grid;
            /* NOTE: 两栏布局,左侧固定宽度,右侧自适应 */
            grid-template-columns: 350px 1fr;
            gap: 2rem;
            align-items: start;
      }

      /* WARN: 移动端适配 - 900px以下变为单栏布局 */
      @media (max-width: 900px) {
            .main-grid {
                grid-template-columns: 1fr;
            }
            .list-panel { order: 2; }
            .wheel-panel { order: 1; }
      }
   
      .panel {
            /* NOTE: 毛玻璃效果面板 */
            background: var(--bg-overlay);
            backdrop-filter: blur(20px) saturate(180%);
            border-radius: 1.5rem;
            padding: 1.8rem;
            border: 1px solid var(--border-light);
            box-shadow:
                0 8px 32px var(--shadow-color),
                inset 0 1px 0 rgba(255, 255, 255, 0.2);
            transition: transform 0.3s ease, box-shadow 0.3s ease;
            position: relative;
            overflow: hidden;
      }
         
      .panel::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--accent-color));
            border-radius: 1.5rem 1.5rem 0 0;
      }
         
      .panel:hover {
            transform: translateY(-5px);
            box-shadow:
                0 12px 40px rgba(0, 0, 0, 0.15),
                inset 0 1px 0 rgba(255, 255, 255, 0.3);
      }
   
      .panel-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 0.5rem;
            margin-bottom: 1.8rem;
            padding-bottom: 1.2rem;
            border-bottom: 1px solid var(--border-light);
      }
   
      .panel-header h2 {
            font-size: 1.6rem;
            font-weight: 800;
            color: #fff;
            display: flex;
            align-items: center;
            gap: 0.75rem;
      }
   
      .icon {
            color: #fcd34d;
            font-size: 2rem;
            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
      }
         
      .count-badge {
            font-size: 0.9rem;
            font-weight: 700;
            color: #fff;
            background: linear-gradient(135deg, var(--success-color), #0d9668);
            padding: 0.4rem 0.8rem;
            border-radius: 2rem;
            box-shadow: 0 4px 8px rgba(16, 185, 129, 0.3);
      }
   
      .input-group {
            margin-bottom: 1.5rem;
      }
   
      .input-label {
            color: var(--text-light);
            margin-bottom: 0.75rem;
            display: block;
            font-weight: 600;
            font-size: 1.1rem;
            display: flex;
            align-items: center;
            gap: 0.5rem;
      }
         
      .input-label .label-icon {
            font-size: 1.2rem;
      }
   
      .input-row {
            display: flex;
            gap: 0.75rem;
      }
   
      input, textarea {
            background: rgba(255, 255, 255, 0.15);
            border: 2px solid var(--border-light);
            border-radius: 0.75rem;
            padding: 1rem 1.25rem;
            color: white;
            outline: none;
            transition: all 0.3s;
            width: 100%;
            font-size: 1rem;
            font-weight: 500;
      }
         
      input::placeholder, textarea::placeholder {
            color: rgba(255, 255, 255, 0.5);
            font-weight: 400;
      }
   
      input:focus, textarea:focus {
            border-color: #fcd34d;
            background: rgba(255, 255, 255, 0.25);
            box-shadow: 0 0 0 4px rgba(252, 211, 77, 0.2);
      }
   
      .input-row input {
            flex: 1;
      }
   
      .btn {
            padding: 1rem 1.5rem;
            border: none;
            border-radius: 0.75rem;
            font-weight: 700;
            cursor: pointer;
            transition: all 0.3s;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 0.75rem;
            text-decoration: none;
            user-select: none;
            white-space: nowrap;
            font-size: 1rem;
            letter-spacing: 0.5px;
            position: relative;
            overflow: hidden;
      }
         
      .btn::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 100%;
            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
            transition: left 0.5s;
      }
         
      .btn:hover::before {
            left: 100%;
      }
   
      .btn:disabled {
            opacity: 0.6;
            cursor: not-allowed;
            transform: none !important;
      }
   
      .btn-primary {
            background: linear-gradient(135deg, var(--primary-color), #ea580c);
            color: white;
            box-shadow: 0 6px 16px rgba(249, 115, 22, 0.4);
      }
   
      .btn-primary:hover:not(:disabled) {
            background: linear-gradient(135deg, #ea580c, #c2410c);
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(249, 115, 22, 0.5);
      }
   
      .btn-success {
            background: linear-gradient(135deg, var(--success-color), #0d9668);
            color: white;
            box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4);
      }

      .btn-success:hover:not(:disabled) {
            background: linear-gradient(135deg, #0d9668, #047857);
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(16, 185, 129, 0.5);
      }
         
      .btn-secondary {
            background: rgba(255, 255, 255, 0.2);
            color: white;
            border: 2px solid var(--border-light);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
      }
   
      .btn-secondary:hover:not(:disabled) {
            background: rgba(255, 255, 255, 0.3);
            transform: translateY(-2px);
            box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
      }
         
      .btn-danger {
            background: linear-gradient(135deg, var(--danger-color), #b91c1c);
            color: white;
            box-shadow: 0 6px 16px rgba(239, 68, 68, 0.4);
      }
         
      .btn-danger:hover:not(:disabled) {
            background: linear-gradient(135deg, #b91c1c, #991b1b);
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(239, 68, 68, 0.5);
      }
         
      .btn-warning {
            background: linear-gradient(135deg, var(--warning-color), #d97706);
            color: white;
            box-shadow: 0 6px 16px rgba(245, 158, 11, 0.4);
      }
         
      .btn-warning:hover:not(:disabled) {
            background: linear-gradient(135deg, #d97706, #b45309);
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(245, 158, 11, 0.5);
      }
   
      .punishment-list {
            max-height: 400px;
            overflow-y: auto;
            padding-right: 5px;
            margin-bottom: 1.5rem;
      }
         
      .punishment-list::-webkit-scrollbar {
            width: 8px;
      }
      .punishment-list::-webkit-scrollbar-track {
            background: rgba(255, 255, 255, 0.05);
            border-radius: 4px;
      }
      .punishment-list::-webkit-scrollbar-thumb {
            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
            border-radius: 4px;
      }
   
      .punishment-item {
            background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
            padding: 1.25rem;
            border-radius: 1rem;
            margin-bottom: 0.75rem;
            display: flex;
            justify-content: space-between;
            align-items: center;
            transition: all 0.3s;
            color: white;
            border-left: 4px solid transparent;
            border: 1px solid var(--border-light);
            position: relative;
            overflow: hidden;
      }
         
      .punishment-item::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            width: 4px;
            height: 100%;
            background: linear-gradient(180deg, var(--primary-color), var(--secondary-color));
            opacity: 0;
            transition: opacity 0.3s;
      }
   
      .punishment-item:hover {
            background: linear-gradient(135deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.1));
            transform: translateX(8px);
            box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
      }
         
      .punishment-item:hover::before {
            opacity: 1;
      }
   
      .punishment-item.active {
            background: linear-gradient(135deg, rgba(252, 211, 77, 0.25), rgba(245, 158, 11, 0.15));
            border-left-color: #fcd34d;
            font-weight: bold;
            box-shadow: 0 4px 12px rgba(252, 211, 77, 0.2);
      }
         
      .punishment-item .food-icon {
            font-size: 1.5rem;
            margin-right: 0.75rem;
      }
   
      .punishment-item .remove-btn {
            background: rgba(0, 0, 0, 0.2);
            border: none;
            color: #fca5a5;
            cursor: pointer;
            font-size: 1.3rem;
            width: 32px;
            height: 32px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
            flex-shrink: 0;
      }
   
      .punishment-item .remove-btn:hover {
            background: #ef4444;
            color: white;
            transform: rotate(90deg);
      }
   
      .wheel-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            width: 100%;
      }
   
      .wheel-wrapper {
            position: relative;
            margin-bottom: 2.5rem;
            filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.3));
            width: 100%;
            /* OPTIMIZE: 使用aspect-ratio保持正方形 */
            max-width: 500px;
            aspect-ratio: 1/1;
      }
   
      .wheel-pointer {
            position: absolute;
            top: -15px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 20;
            width: 50px;
            height: 50px;
            background: linear-gradient(135deg, var(--danger-color), #b91c1c);
            border: 5px solid #fff;
            border-radius: 50%;
            box-shadow: 0 8px 20px rgba(0,0,0,0.4);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.5rem;
            animation: pulsePointer 2s infinite;
      }
         
      .wheel-pointer::after {
            content: '&#128071;';
            position: absolute;
            top: 100%;
            left: 50%;
            transform: translateX(-50%);
            font-size: 1.8rem;
            filter: drop-shadow(0 4px 6px rgba(0,0,0,0.3));
      }
   
      .wheel-svg {
            width: 100%;
            height: 100%;
            transition-property: transform;
            /* NOTE: 自定义贝塞尔曲线实现"缓慢停止"效果 */
            transition-timing-function: cubic-bezier(0.15, 0, 0.20, 1);
      }
   
      .control-buttons {
            display: flex;
            gap: 1.5rem;
            margin-bottom: 2.5rem;
            flex-wrap: wrap;
            justify-content: center;
      }
   
      .result-display {
            background: linear-gradient(135deg, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.5));
            backdrop-filter: blur(20px);
            border: 3px solid #fcd34d;
            color: #fcd34d;
            padding: 2rem 3rem;
            border-radius: 1.5rem;
            text-align: center;
            font-weight: bold;
            box-shadow:
                0 15px 35px rgba(0, 0, 0, 0.3),
                inset 0 1px 0 rgba(255, 255, 255, 0.2);
            min-width: 320px;
            transform: scale(0.9);
            opacity: 0;
            transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
            position: relative;
            overflow: hidden;
      }
         
      .result-display::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: linear-gradient(90deg, var(--primary-color), #fcd34d, var(--secondary-color));
      }

      .result-display.show {
            transform: scale(1);
            opacity: 1;
      }
   
      .result-display h3 {
            font-size: 1.6rem;
            margin-bottom: 1rem;
            color: white;
            text-shadow: 0 2px 4px rgba(0,0,0,0.3);
      }
         
      .result-display .punishment-text {
            font-size: 2rem;
            font-weight: 900;
            word-break: break-word;
            line-height: 1.4;
            background: linear-gradient(135deg, #fcd34d, #f59e0b);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      }
         
      .result-emoji {
            font-size: 3rem;
            margin-bottom: 1rem;
            display: block;
            filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
      }
   
      .celebration-modal {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.85);
            backdrop-filter: blur(10px);
            z-index: 50;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
      }
   
      .celebration-content {
            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
            border-radius: 2rem;
            padding: 3rem;
            max-width: 36rem;
            width: 100%;
            text-align: center;
            position: relative;
            overflow: hidden;
            box-shadow:
                0 30px 60px rgba(0, 0, 0, 0.5),
                inset 0 1px 0 rgba(255, 255, 255, 0.3);
            border: 4px solid rgba(255,255,255,0.2);
      }
   
      .celebration-bg {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.2) 0%, transparent 50%),
                     radial-gradient(circle at 70% 70%, rgba(255,255,255,0.15) 0%, transparent 50%);
            animation: pulseBg 3s infinite;
      }
   
      .celebration-close {
            position: absolute;
            top: 1.5rem;
            right: 1.5rem;
            width: 3rem;
            height: 3rem;
            background: rgba(0, 0, 0, 0.3);
            border: 2px solid rgba(255, 255, 255, 0.3);
            border-radius: 50%;
            color: white;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.8rem;
            transition: all 0.3s;
            font-weight: bold;
      }
   
      .celebration-close:hover {
            background: rgba(255, 255, 255, 0.2);
            transform: rotate(90deg);
      }
   
      .celebration-inner {
            position: relative;
            z-index: 10;
      }
   
      .celebration-emoji {
            font-size: 6rem;
            margin-bottom: 1rem;
            display: block;
            filter: drop-shadow(0 6px 12px rgba(0,0,0,0.4));
            animation: bounce 2s infinite;
      }
   
      .celebration-title {
            font-size: 2.8rem;
            font-weight: 900;
            color: #fff;
            margin-bottom: 1.5rem;
            text-transform: uppercase;
            letter-spacing: 2px;
            text-shadow: 0 4px 8px rgba(0,0,0,0.3);
      }
   
      .celebration-result-box {
            background: rgba(0, 0, 0, 0.3);
            backdrop-filter: blur(10px);
            border-radius: 1.5rem;
            padding: 2.5rem;
            margin-bottom: 2.5rem;
            border: 2px dashed rgba(255,255,255,0.3);
      }
   
      .celebration-result-label {
            font-size: 1.4rem;
            color: rgba(255,255,255,0.9);
            margin-bottom: 1rem;
            font-weight: 600;
      }

      .celebration-result-text {
            font-size: 2.4rem;
            font-weight: 900;
            color: #fcd34d;
            line-height: 1.3;
            text-shadow: 0 3px 6px rgba(0,0,0,0.3);
      }
   
      .celebration-btn {
            background: white;
            color: var(--secondary-color);
            border: none;
            padding: 1.25rem 4rem;
            border-radius: 3rem;
            font-weight: 900;
            font-size: 1.4rem;
            cursor: pointer;
            transition: all 0.3s;
            box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
            position: relative;
            overflow: hidden;
      }
   
      .celebration-btn:hover {
            background: #f3f4f6;
            transform: translateY(-3px) scale(1.05);
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
      }
   
      .empty-state {
            padding: 2.5rem;
            border-radius: 1.5rem;
            background: rgba(255, 255, 255, 0.05);
            color: rgba(255, 255, 255, 0.6);
            text-align: center;
            font-style: italic;
            border: 2px dashed rgba(255, 255, 255, 0.2);
            font-size: 1.1rem;
      }
         
      .file-import-note {
            color: var(--text-lighter);
            font-size: 0.9rem;
            margin-top: 0.5rem;
            display: flex;
            align-items: center;
            gap: 0.5rem;
      }
         
      .clear-all-section {
            display: flex;
            justify-content: flex-end;
            margin-top: 1.5rem;
            padding-top: 1.5rem;
            border-top: 1px solid var(--border-light);
      }
         
      /* NOTE: 一键清空确认弹窗样式 */
      .confirm-clear-modal {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.85);
            backdrop-filter: blur(10px);
            z-index: 100;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
      }
         
      .confirm-clear-content {
            background: linear-gradient(135deg, var(--danger-color), #b91c1c);
            border-radius: 2rem;
            padding: 3rem;
            max-width: 32rem;
            width: 100%;
            text-align: center;
            position: relative;
            overflow: hidden;
            box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
            border: 4px solid rgba(255,255,255,0.2);
      }
         
      .confirm-clear-icon {
            font-size: 4.5rem;
            margin-bottom: 1.5rem;
            display: block;
            filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
            animation: shake 0.5s ease-in-out;
      }
         
      .confirm-clear-title {
            font-size: 2rem;
            font-weight: 900;
            color: #fff;
            margin-bottom: 1.5rem;
            text-shadow: 0 3px 6px rgba(0,0,0,0.3);
      }
         
      .confirm-clear-message {
            font-size: 1.2rem;
            color: rgba(255, 255, 255, 0.9);
            margin-bottom: 2.5rem;
            line-height: 1.6;
      }
         
      .confirm-clear-count {
            font-weight: 900;
            color: #fcd34d;
            font-size: 1.5rem;
            text-shadow: 0 2px 4px rgba(0,0,0,0.3);
      }
         
      .confirm-clear-buttons {
            display: flex;
            gap: 1.5rem;
            justify-content: center;
      }
         
      .confirm-clear-btn {
            padding: 1.2rem 2.5rem;
            border: none;
            border-radius: 1rem;
            font-weight: 800;
            font-size: 1.2rem;
            cursor: pointer;
            transition: all 0.3s;
            min-width: 140px;
            position: relative;
            overflow: hidden;
      }
         
      .confirm-clear-btn.confirm {
            background: #10b981;
            color: white;
            box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4);
      }
         
      .confirm-clear-btn.confirm:hover {
            background: #0d9668;
            transform: translateY(-3px);
            box-shadow: 0 12px 25px rgba(16, 185, 129, 0.5);
      }
         
      .confirm-clear-btn.cancel {
            background: rgba(255, 255, 255, 0.2);
            color: white;
            border: 2px solid rgba(255, 255, 255, 0.3);
            box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
      }
         
      .confirm-clear-btn.cancel:hover {
            background: rgba(255, 255, 255, 0.3);
            transform: translateY(-3px);
            box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
      }
   
      @keyframes pulse {
            0%, 100% { transform: scale(1); }
            50% { transform: scale(1.05); }
      }
         
      @keyframes pulsePointer {
            0%, 100% { transform: translateX(-50%) scale(1); }
            50% { transform: translateX(-50%) scale(1.1); }
      }

      @keyframes pulseBg {
            0%, 100% { opacity: 0.5; }
            50% { opacity: 1; }
      }
   
      @keyframes bounce {
            0%, 20%, 53%, 80%, 100% { transform: translateY(0); }
            40%, 43% { transform: translateY(-20px); }
            70% { transform: translateY(-10px); }
            90% { transform: translateY(-5px); }
      }

      @keyframes modalPop {
            0% { transform: scale(0.8) translateY(50px); opacity: 0; }
            70% { transform: scale(1.05); }
            100% { transform: scale(1) translateY(0); opacity: 1; }
      }
         
      @keyframes shake {
            0%, 100% { transform: translateX(0); }
            10%, 30%, 50%, 70%, 90% { transform: translateX(-8px); }
            20%, 40%, 60%, 80% { transform: translateX(8px); }
      }
         
      @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
      }
         
      @keyframes float {
            0%, 100% { transform: translateY(0); }
            50% { transform: translateY(-20px); }
      }
   
      .hidden {
            display: none;
      }

      .modal-enter {
            animation: modalPop 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
      }
         
      .shake {
            animation: shake 0.5s ease-in-out;
      }
         
      /* TODO: 添加暗色模式支持 */
      /* OPTIMIZE: 将动画关键帧提取到单独部分以便维护 */
         
      /* 美食图标样式 */
      .food-emoji {
            display: inline-block;
            animation: float 3s ease-in-out infinite;
      }
         
      /* 响应式调整 */
      @media (max-width: 768px) {
            .panel {
                padding: 1.5rem;
            }
            
            .header h1 {
                font-size: clamp(1.5rem, 5vw, 2.5rem);
            }
            
            .header p {
                font-size: 1rem;
            }
            
            .control-buttons {
                flex-direction: column;
                align-items: center;
            }
            
            .btn {
                width: 100%;
                max-width: 300px;
            }
            
            .celebration-content {
                padding: 2rem;
            }
            
            .celebration-title {
                font-size: 2rem;
            }
            
            .celebration-result-text {
                font-size: 1.8rem;
            }
      }
    </style>
</head>
<body>
    <div class="container">
      <!-- 头部标题 -->
      <div class="header">
            <div style="display: flex; align-items: center; justify-content: center; gap: 1.5rem; margin-bottom: 1rem;">
                <span class="emoji-bounce food-emoji">&#127829;</span>
                <h1>美食大转盘 - 点餐助手</h1>
                <span class="emoji-bounce food-emoji">&#127828;</span>
            </div>
            <p>还在为"吃什么"发愁吗?让转盘帮你决定今天的美食!</p>
            <div class="subtitle">添加你喜欢的菜品,让选择变得简单有趣</div>
      </div>
   
      <div class="main-grid">
            <!--
            ============================================
            菜品项目库面板
            给使用者:
            - 在此添加/删除菜品项目
            - 支持导入txt文件批量添加
            - 项目列表会保存在浏览器本地
            ============================================
            -->
            <div class="panel list-panel">
                <div class="panel-header">
                  <div style="display: flex; align-items: center; gap: 0.75rem;">
                        <span class="icon">&#127869;️</span>
                        <h2>我的美食菜单</h2>
                        <div class="count-badge" id="punishmentCount">0项</div>
                  </div>
                  <button class="btn btn-danger" id="clearAllPunishmentsBtn" style="padding: 0.8rem 1.2rem;">
                        <span>&#128465;️</span>
                        一键清空
                  </button>
                </div>
                  
                <div class="input-group">
                  <label class="input-label">
                        <span class="label-icon">➕</span>
                        添加新菜品:
                  </label>
                  <div class="input-row">
                        <input type="text" id="punishmentInput" placeholder="例如:麻辣香锅、披萨、寿司..." autocomplete="off">
                        <button class="btn btn-success" id="addPunishmentBtn">
                            <span>&#127860;</span>
                            添加
                        </button>
                  </div>
                </div>
               
                <!-- NOTE: 文件导入功能 -->
                <div class="input-group">
                  <label class="input-label">
                        <span class="label-icon">&#128193;</span>
                        导入菜品列表(TXT文件):
                  </label>
                  <div class="input-row">
                        <input type="file" id="importFile" accept=".txt" style="flex: 1; padding: 0.75rem;">
                        <button class="btn btn-secondary" id="importBtn">
                            <span>&#128229;</span>
                            导入
                        </button>
                  </div>
                  <div class="file-import-note">
                        <span>&#128221;</span>
                        每行一个菜品,支持批量导入
                  </div>
                </div>
   
                <div class="punishment-list" id="punishmentList">
                  <!-- 菜品项目动态加载 -->
                </div>
               
                <div class="clear-all-section">
                  <button class="btn btn-warning" id="resetToDefaultBtn" style="padding: 0.8rem 1.5rem;">
                        <span>&#128260;</span>
                        恢复默认菜单
                  </button>
                </div>
            </div>
   
            <!--
            ============================================
            转盘控制面板
            给使用者:
            - 点击"开始点餐"旋转转盘
            - 点击"重置"将转盘归零
            - 结果会在下方和弹窗显示
            ============================================
            -->
            <div class="panel wheel-panel" style="display: flex; flex-direction: column; align-items: center; justify-content: center;">
                <div class="wheel-container">
                  <div class="wheel-wrapper">
                        <!-- 转盘指针 -->
                        <div class="wheel-pointer">&#128071;</div>
                        <!-- SVG转盘 -->
                        <svg id="wheelSvg" class="wheel-svg" viewBox="-250 -250 500 500">
                            <defs>
                              <radialGradient id="centerGradient">
                                    <stop offset="0%" stop-color="#fcd34d" />
                                    <stop offset="100%" stop-color="#f59e0b" />
                              </radialGradient>
                              <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
                                    <feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="rgba(0,0,0,0.4)"/>
                              </filter>
                              <linearGradient id="wheelGradient" x1="0%" y1="0%" x2="100%" y2="100%">
                                    <stop offset="0%" style="stop-color:#ff6b6b;stop-opacity:1" />
                                    <stop offset="25%" style="stop-color:#ff9a3c;stop-opacity:1" />
                                    <stop offset="50%" style="stop-color:#f6d365;stop-opacity:1" />
                                    <stop offset="75%" style="stop-color:#84fab0;stop-opacity:1" />
                                    <stop offset="100%" style="stop-color:#8fd3f4;stop-opacity:1" />
                              </linearGradient>
                            </defs>
                            <!-- 转盘扇形区域(动态生成) -->
                            <g id="wheelSections"></g>
                            <!-- 转盘中心装饰 -->
                            <circle cx="0" cy="0" r="50" fill="url(#centerGradient)" stroke="#fff" stroke-width="6" filter="url(#shadow)"/>
                            <circle cx="0" cy="0" r="35" fill="none" stroke="#fff" stroke-width="3" stroke-dasharray="4 4"/>
                            <text x="0" y="5" fill="#fff" font-size="18" font-weight="bold" text-anchor="middle" dominant-baseline="middle">点餐</text>
                        </svg>
                  </div>
   
                  <!-- 控制按钮区域 -->
                  <div class="control-buttons">
                        <button class="btn btn-primary" id="startBtn" style="min-width: 180px; font-size: 1.3rem; padding: 1.2rem 2rem;">
                            <span>&#127919;</span>
                            <span id="startBtnText">开始点餐</span>
                        </button>
                        <button class="btn btn-secondary" id="resetBtn" style="padding: 1.2rem 2rem;">
                            <span>&#128260;</span>
                            重置转盘
                        </button>
                  </div>
   
                  <!-- 结果展示区域 -->
                  <div id="resultDisplay" class="result-display">
                        <div class="result-emoji">&#129300;</div>
                        <h3>今日推荐</h3>
                        <div class="punishment-text" id="winnerPunishment">等待选择...</div>
                  </div>
                </div>
            </div>
      </div>
    </div>

    <!--
    ============================================
    庆祝弹窗
    给使用者:
    - 抽取结果后会显示此弹窗
    - 点击"确定"或关闭按钮关闭弹窗
    ============================================
    -->
    <div id="celebrationModal" class="celebration-modal hidden">
      <div class="celebration-content">
            <div class="celebration-bg"></div>
            <button class="celebration-close" id="closeCelebrationBtn">×</button>
            <div class="celebration-inner">
                <span class="celebration-emoji">&#127881;</span>
                <h2 class="celebration-title">今日美食已选定!</h2>
                  
                <div class="celebration-result-box">
                  <div class="celebration-result-label">转盘推荐:</div>
                  <div class="celebration-result-text" id="celebrationPunishment"></div>
                </div>
   
                <button class="celebration-btn" id="confirmCelebrationBtn">
                  <span>&#128523;</span>
                  太棒了,就吃这个!
                </button>
            </div>
      </div>
    </div>
   
    <!--
    ============================================
    一键清空确认弹窗
    给使用者:
    - 确认是否清空所有菜品项目
    - 提供取消选项防止误操作
    ============================================
    -->
    <div id="confirmClearModal" class="confirm-clear-modal hidden">
      <div class="confirm-clear-content modal-enter">
            <span class="confirm-clear-icon">⚠️</span>
            <h2 class="confirm-clear-title">确认清空菜单</h2>
            <div class="confirm-clear-message">
                您确定要清空所有 <span class="confirm-clear-count" id="clearCount">0</span> 个菜品吗?<br>
                <strong>此操作无法撤销!</strong> 所有自定义和导入的菜品将被永久删除。
            </div>
            <div class="confirm-clear-buttons">
                <button class="confirm-clear-btn cancel" id="cancelClearBtn">
                  <span>❌</span>
                  取消
                </button>
                <button class="confirm-clear-btn confirm" id="confirmClearBtn">
                  <span>✅</span>
                  确定清空
                </button>
            </div>
      </div>
    </div>
   
    <script>
      /*
      ============================================
      美食大转盘 - JavaScript核心逻辑
      给维护者:
      - 使用LocalStorage持久化数据
      - 转盘算法基于角度计算
      - 事件委托处理动态列表
      给学习者:
      - 使用SVG动态生成转盘
      - 三角函数计算扇形区域
      - 贝塞尔曲线实现缓动效果
      ============================================
      */
         
      // ========== 全局变量声明 ==========
         
      // NOTE: 默认菜品列表 - 当本地存储为空时使用
      const defaultPunishments = [
            '&#127829; 披萨',
            '&#127828; 汉堡',
            '&#127836; 拉面',
            '&#127843; 寿司',
            '&#129368; 麻辣香锅',
            '&#127858; 火锅',
            '&#129367; 沙拉',
            '&#127835; 咖喱饭',
            '&#127857; 便当',
            '&#129386; 三明治',
            '&#127837; 意大利面',
            '&#127790; 墨西哥卷饼',
            '&#129375; 饺子',
            '&#127844; 炸虾',
            '&#127846; 冰淇淋'
      ];
         
      // WARN: 从LocalStorage加载数据,失败时使用默认值
      let punishments = JSON.parse(localStorage.getItem('wheelPunishments')) || [...defaultPunishments];
         
      // 转盘状态控制变量
      let isSpinning = false;      // 是否正在旋转
      let currentRotation = 0;   // 当前旋转角度
      let currentPunishment = '';// 当前选中的菜品
   
      // NOTE: 转盘扇形颜色列表 - 美食主题渐变色
      const colors = [
            '#FF6B6B', '#FF9A3C', '#F6D365', '#84FAB0', '#8FD3F4',
            '#A78BFA', '#F472B6', '#60A5FA', '#34D399', '#FBBF24',
            '#EF4444', '#F59E0B', '#10B981', '#3B82F6', '#8B5CF6'
      ];
         
      // ========== 工具函数 ==========
         
      /**
         * 更新菜品项目计数显示
         */
      function updatePunishmentCount() {
            const countElement = document.getElementById('punishmentCount');
            const count = punishments.length;
            countElement.textContent = `${count}项`;
            
            // 根据数量调整颜色
            if (count === 0) {
                countElement.style.background = 'linear-gradient(135deg, #ef4444, #b91c1c)';
            } else if (count <= 5) {
                countElement.style.background = 'linear-gradient(135deg, #f59e0b, #d97706)';
            } else if (count <= 10) {
                countElement.style.background = 'linear-gradient(135deg, #10b981, #0d9668)';
            } else {
                countElement.style.background = 'linear-gradient(135deg, #8b5cf6, #7c3aed)';
            }
      }
         
      /**
         * 保存菜品列表到LocalStorage
         * @description 持久化数据,页面刷新后不丢失
         */
      function savePunishments() {
            localStorage.setItem('wheelPunishments', JSON.stringify(punishments));
      }
         
      /**
         * 格式化日期时间
         * @Param {Date} date - 日期对象
         * @returns {string} 格式化后的日期字符串
         */
      function formatDateTime(date) {
            return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
      }
         
      /**
         * 截断文本,添加省略号
         * @param {string} text - 原始文本
         * @param {number} maxLength - 最大长度
         * @returns {string} 截断后的文本
         */
      function truncateText(text, maxLength = 100) {
            if (text.length <= maxLength) return text;
            return text.substring(0, maxLength) + '...';
      }
         
      /**
         * 显示一键清空确认弹窗
         */
      function showClearConfirmation() {
            if (isSpinning) {
                alert('转盘旋转时不能清空菜品!');
                return;
            }
            
            if (punishments.length === 0) {
                alert('当前没有菜品可清空!');
                return;
            }
            
            // 更新清空数量显示
            document.getElementById('clearCount').textContent = punishments.length;
            
            // 显示确认弹窗
            const modal = document.getElementById('confirmClearModal');
            modal.classList.remove('hidden');
            
            // 添加抖动效果以强调危险操作
            const content = modal.querySelector('.confirm-clear-content');
            content.classList.remove('shake');
            void content.offsetWidth; // 触发重排
            content.classList.add('shake');
      }
         
      /**
         * 隐藏一键清空确认弹窗
         */
      function hideClearConfirmation() {
            document.getElementById('confirmClearModal').classList.add('hidden');
      }
         
      /**
         * 执行一键清空操作
         * @description 清空所有菜品项目
         */
      function clearAllPunishments() {
            if (isSpinning) {
                alert('转盘旋转时不能清空菜品!');
                return;
            }
            
            // 记录清空前的数量
            const previousCount = punishments.length;
            
            // 清空菜品列表
            punishments = [];
            
            // 更新UI
            updatePunishmentList();
            updatePunishmentCount();
            updateWheel();
            
            // 保存到LocalStorage
            savePunishments();
            
            // 重置转盘状态
            currentRotation = 0;
            currentPunishment = '';
            const wheelSvg = document.getElementById('wheelSvg');
            wheelSvg.style.transitionDuration = '0ms';
            wheelSvg.style.transform = 'rotate(0deg)';
            
            // 隐藏结果
            const resultDisplay = document.getElementById('resultDisplay');
            resultDisplay.classList.remove('show');
            
            // 隐藏庆祝弹窗
            closeCelebration();
            
            // 显示操作反馈
            alert(`已清空 ${previousCount} 个菜品!`);
            
            // 隐藏确认弹窗
            hideClearConfirmation();
      }
         
      /**
         * 恢复默认菜单
         */
      function resetToDefaultMenu() {
            if (isSpinning) {
                alert('转盘旋转时不能恢复默认菜单!');
                return;
            }
            
            if (!confirm('确定要恢复默认菜单吗?当前所有菜品将被替换为默认菜品。')) {
                return;
            }
            
            // 恢复默认菜品列表
            punishments = [...defaultPunishments];
            
            // 更新UI
            updatePunishmentList();
            updatePunishmentCount();
            updateWheel();
            
            // 保存到LocalStorage
            savePunishments();
            
            alert('已恢复默认菜单!');
      }
         
      /**
         * 导入TXT文件并解析菜品列表
         * @description 每行一个菜品,自动过滤空行和重复项
         * @param {File} file - 用户选择的TXT文件
         */
      function importPunishmentsFromTxt(file) {
            const reader = new FileReader();
            
            reader.onload = function(e) {
                try {
                  const content = e.target.result;
                  // 按换行符分割,过滤空行和首尾空格
                  const newPunishments = content.split(/\r?\n/)
                        .map(item => item.trim())
                        .filter(item => item.length > 0);
                     
                  if (newPunishments.length === 0) {
                        alert('文件为空或格式不正确!');
                        return;
                  }
                     
                  // 合并并去重
                  const uniqueNewPunishments = newPunishments.filter(
                        item => !punishments.includes(item)
                  );
                     
                  if (uniqueNewPunishments.length === 0) {
                        alert('所有菜品已存在!');
                        return;
                  }
                     
                  // 添加到现有列表
                  punishments.push(...uniqueNewPunishments);
                     
                  // 更新UI并保存
                  updatePunishmentList();
                  updatePunishmentCount();
                  updateWheel();
                  savePunishments();
                     
                  alert(`成功导入 ${uniqueNewPunishments.length} 个新菜品!${newPunishments.length - uniqueNewPunishments.length > 0 ? `有 ${newPunishments.length - uniqueNewPunishments.length} 个重复菜品已跳过。` : ''}`);
                     
                } catch (error) {
                  console.error('导入文件时出错:', error);
                  alert('文件解析失败,请检查文件格式!');
                }
            };
            
            reader.onerror = function() {
                alert('读取文件失败!');
            };
            
            reader.readAsText(file);
      }

      // ========== DOM加载完成事件 ==========
      document.addEventListener('DOMContentLoaded', function() {
            // 初始化UI
            updatePunishmentList();
            updatePunishmentCount();
            updateWheel();
               
            // 输入框回车事件
            const input = document.getElementById('punishmentInput');
            input.addEventListener('keypress', function(e) {
                if (e.key === 'Enter') {
                  addPunishment();
                }
            });
            
            // 绑定按钮事件
            document.getElementById('addPunishmentBtn').addEventListener('click', addPunishment);
            document.getElementById('importBtn').addEventListener('click', function() {
                const fileInput = document.getElementById('importFile');
                if (fileInput.files.length > 0) {
                  importPunishmentsFromTxt(fileInput.files);
                  fileInput.value = ''; // 清空文件选择
                } else {
                  alert('请先选择文件!');
                }
            });
            
            document.getElementById('startBtn').addEventListener('click', startGame);
            document.getElementById('resetBtn').addEventListener('click', resetWheel);
            document.getElementById('resetToDefaultBtn').addEventListener('click', resetToDefaultMenu);
            document.getElementById('closeCelebrationBtn').addEventListener('click', closeCelebration);
            document.getElementById('confirmCelebrationBtn').addEventListener('click', closeCelebration);
            
            // 一键清空相关事件
            document.getElementById('clearAllPunishmentsBtn').addEventListener('click', showClearConfirmation);
            document.getElementById('cancelClearBtn').addEventListener('click', hideClearConfirmation);
            document.getElementById('confirmClearBtn').addEventListener('click', clearAllPunishments);
            
            // 点击确认弹窗外部关闭弹窗
            document.getElementById('confirmClearModal').addEventListener('click', function(e) {
                if (e.target === this) {
                  hideClearConfirmation();
                }
            });
            
            // 使用事件委托处理动态生成的删除按钮
            document.getElementById('punishmentList').addEventListener('click', function(e) {
                if (e.target.classList.contains('remove-btn')) {
                  const item = e.target.closest('.punishment-item');
                  const index = Array.from(this.children).indexOf(item);
                  if (index !== -1) {
                        removePunishment(index);
                  }
                }
            });
      });
         
      // ========== 核心功能函数 ==========
         
      /**
         * 添加新菜品项目
         * @description 验证输入并添加到列表,过滤重复项
         */
      function addPunishment() {
            const input = document.getElementById('punishmentInput');
            const name = input.value.trim();
            
            // 输入验证
            if (!name) {
                alert('请输入菜品名称!');
                return;
            }
            
            if (name.length > 30) {
                alert('菜品名称过长,请控制在30字以内!');
                return;
            }
               
            if (!punishments.includes(name)) {
                punishments.push(name);
                input.value = '';
                input.focus();
                updatePunishmentList();
                updatePunishmentCount();
                updateWheel();
                savePunishments();
            } else {
                alert('该菜品已存在!');
            }
      }
   
      /**
         * 删除菜品项目
         * @param {number} index - 要删除的项目索引
         * @description 转盘旋转时禁止删除
         */
      function removePunishment(index) {
            if (isSpinning) {
                alert('转盘旋转时不能删除菜品!');
                return;
            }
            
            if (confirm('确定要删除这个菜品吗?')) {
                punishments.splice(index, 1);
                updatePunishmentList();
                updatePunishmentCount();
                updateWheel();
                savePunishments();
            }
      }
   
      /**
         * 更新菜品列表UI
         * @description 动态生成列表项,处理空状态
         */
      function updatePunishmentList() {
            const list = document.getElementById('punishmentList');
               
            // 处理空列表状态
            if (punishments.length === 0) {
                list.innerHTML = '<div class="empty-state">菜品菜单是空的,请先添加菜品!</div>';
                return;
            }
               
            // 生成列表HTML
            list.innerHTML = punishments.map((punishment, index) => {
                // 提取emoji和文本
                const emojiMatch = punishment.match(/^([\u{1F300}-\u{1F9FF}]|\u{1F1E6}-\u{1F1FF}|\u{2600}-\u{26FF}|\u{2700}-\u{27BF})/u);
                const emoji = emojiMatch ? emojiMatch : '&#127869;️';
                const text = emojiMatch ? punishment.substring(emojiMatch.length).trim() : punishment;
               
                return `
                <div class="punishment-item ${currentPunishment === punishment ? 'active' : ''}">
                  <div style="display: flex; align-items: center;">
                        <span class="food-emoji" style="font-size: 1.8rem; margin-right: 0.75rem;">${emoji}</span>
                        <span>${text}</span>
                  </div>
                  <button class="remove-btn" title="删除此项">×</button>
                </div>
                `;
            }).join('');
      }
   
      /**
         * 更新转盘SVG图形
         * @description 动态计算扇形区域,处理文本显示
         * @算法说明:
         * 1. 计算每个扇形的角度
         * 2. 使用极坐标计算弧线路径
         * 3. 根据项目数量调整字体大小
         */
      function updateWheel() {
            const sectionsGroup = document.getElementById('wheelSections');
            sectionsGroup.innerHTML = '';
               
            // 空转盘状态
            if (punishments.length === 0) {
                const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
                path.setAttribute('d', 'M 0 0 L 240 0 A 240 240 0 1 1 -240 0 Z');
                path.setAttribute('fill', 'rgba(255,255,255,0.2)');
                path.setAttribute('stroke', '#fff');
                path.setAttribute('stroke-width', '4');
                  
                const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
                text.setAttribute('x', '0');
                text.setAttribute('y', '0');
                text.setAttribute('fill', '#fff');
                text.setAttribute('font-size', '32');
                text.setAttribute('font-weight', 'bold');
                text.setAttribute('text-anchor', 'middle');
                text.setAttribute('dominant-baseline', 'middle');
                text.textContent = '请添加菜品';
                  
                sectionsGroup.appendChild(path);
                sectionsGroup.appendChild(text);
                return;
            }
               
            // 计算每个扇形的角度
            const sectionAngle = 360 / punishments.length;
            const radius = 240; // 转盘半径
   
            // 生成每个扇形
            punishments.forEach((punishment, index) => {
                // 计算起始和结束角度(弧度制)
                const startAngle = index * sectionAngle;
                const endAngle = (index + 1) * sectionAngle;
                  
                const startAngleRad = (startAngle * Math.PI) / 180;
                const endAngleRad = (endAngle * Math.PI) / 180;
                  
                // 判断是否为大圆弧(角度大于180度)
                const largeArcFlag = sectionAngle > 180 ? 1 : 0;
                  
                // 计算弧线起点和终点坐标
                const x1 = Math.cos(startAngleRad) * radius;
                const y1 = Math.sin(startAngleRad) * radius;
                const x2 = Math.cos(endAngleRad) * radius;
                const y2 = Math.sin(endAngleRad) * radius;
                  
                // 构建扇形路径数据
                const pathData = `M 0 0 L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2} Z`;
                  
                // 创建扇形路径元素
                const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
                path.setAttribute('d', pathData);
                path.setAttribute('fill', colors);
                path.setAttribute('stroke', '#fff');
                path.setAttribute('stroke-width', '4');
                path.setAttribute('filter', 'url(#shadow)');
                  
                // 计算文本位置(扇形中间)
                const midAngle = (startAngle + endAngle) / 2;
                const textRadius = radius * 0.65; // 文本位置半径
                const textX = Math.cos((midAngle * Math.PI) / 180) * textRadius;
                const textY = Math.sin((midAngle * Math.PI) / 180) * textRadius;
                  
                // 根据项目数量调整字体大小
                let fontSize = 22;
                if (punishments.length > 15) fontSize = 14;
                else if (punishments.length > 10) fontSize = 16;
                else if (punishments.length > 8) fontSize = 18;
                  
                // 提取emoji和文本
                const emojiMatch = punishment.match(/^([\u{1F300}-\u{1F9FF}]|\u{1F1E6}-\u{1F1FF}|\u{2600}-\u{26FF}|\u{2700}-\u{27BF})/u);
                const emoji = emojiMatch ? emojiMatch : '';
                const text = emojiMatch ? punishment.substring(emojiMatch.length).trim() : punishment;
               
                // 创建文本元素
                const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
                textElement.setAttribute('x', textX);
                textElement.setAttribute('y', textY);
                textElement.setAttribute('fill', '#fff');
                textElement.setAttribute('font-size', fontSize);
                textElement.setAttribute('font-weight', 'bold');
                textElement.setAttribute('text-anchor', 'middle');
                textElement.setAttribute('dominant-baseline', 'middle');
                textElement.setAttribute('transform', `rotate(${midAngle}, ${textX}, ${textY})`);
               
                // 文本截断处理(长文本显示省略号)
                const maxChars = 6;
                let displayText = text;
                if (text.length > maxChars) {
                  displayText = text.substring(0, maxChars) + '..';
                }
               
                // 如果有emoji,添加到文本前面
                const displayContent = emoji ? `${emoji} ${displayText}` : displayText;
                textElement.textContent = displayContent;
               
                // 添加到SVG
                sectionsGroup.appendChild(path);
                sectionsGroup.appendChild(textElement);
            });
      }
   
      /**
         * 开始转盘游戏
         * @description 控制转盘旋转并计算最终结果
         * @算法说明:
         * 1. 生成随机旋转角度和时间
         * 2. 使用CSS transition实现动画
         * 3. 根据指针角度计算选中扇形
         */
      function startGame() {
            // 前置检查
            if (punishments.length === 0) {
                alert('请先添加菜品!');
                return;
            }
   
            if (isSpinning) return;
   
            // 设置旋转状态
            isSpinning = true;
            currentPunishment = '';
               
            // 更新按钮状态
            const startBtn = document.getElementById('startBtn');
            const startBtnText = document.getElementById('startBtnText');
            startBtn.disabled = true;
            startBtnText.textContent = '点餐中...';
               
            // 隐藏上一个结果
            const resultDisplay = document.getElementById('resultDisplay');
            resultDisplay.classList.remove('show');
            
            // 生成随机动画参数
            const randomDuration = 4000 + Math.random() * 4000; // 4-8秒
            
            // 计算最终旋转角度(多圈旋转 + 随机偏移)
            const randomDegreeOffset = Math.floor(Math.random() * 360);
            const randomSpins = 5 + Math.floor(Math.random() * 5); // 5-10圈
            const finalRotation = currentRotation + (randomSpins * 360) + randomDegreeOffset;
            
            // 更新当前角度
            currentRotation = finalRotation;
            
            // 应用旋转动画
            const wheelSvg = document.getElementById('wheelSvg');
            wheelSvg.style.transitionDuration = `${randomDuration}ms`;
            wheelSvg.style.transform = `rotate(${currentRotation}deg)`;
            
            // 动画结束后计算结果
            setTimeout(() => {
                isSpinning = false;
               
                // 转盘指针固定角度(正上方为270度)
                const POINTER_ANGLE = 270;
               
                // 计算实际旋转角度(归一化到0-360度)
                const actualRotation = currentRotation % 360;
               
                // 计算指针指向的角度
                let winningAngle = (POINTER_ANGLE - actualRotation);
               
                // 角度归一化处理
                winningAngle = winningAngle % 360;
                if (winningAngle < 0) {
                  winningAngle += 360;
                }
               
                // 根据角度计算选中扇形的索引
                const sectionAngle = 360 / punishments.length;
                const winningIndex = Math.floor(winningAngle / sectionAngle);
               
                // 获取选中的菜品(边界情况处理)
                currentPunishment = punishments || punishments;
               
                // 恢复按钮状态
                startBtn.disabled = false;
                startBtnText.textContent = '再次点餐';
               
                // 显示结果
                const resultEmoji = document.querySelector('#resultDisplay .result-emoji');
                resultEmoji.textContent = '&#127881;';
                document.getElementById('winnerPunishment').textContent = currentPunishment;
                resultDisplay.classList.add('show');
               
                // 更新列表高亮
                updatePunishmentList();
               
                // 显示庆祝弹窗
                showCelebration();
            }, randomDuration);
      }
   
      /**
         * 重置转盘
         * @description 将转盘归零,清除结果
         */
      function resetWheel() {
            if (isSpinning) {
                alert('转盘旋转时不能重置!');
                return;
            }
            
            if (!confirm('确定要重置转盘吗?')) return;
               
            currentRotation = 0;
            currentPunishment = '';
               
            // 立即重置转盘(无动画)
            const wheelSvg = document.getElementById('wheelSvg');
            wheelSvg.style.transitionDuration = '0ms';
            wheelSvg.style.transform = 'rotate(0deg)';
               
            // 隐藏结果
            const resultDisplay = document.getElementById('resultDisplay');
            const resultEmoji = document.querySelector('#resultDisplay .result-emoji');
            resultEmoji.textContent = '&#129300;';
            resultDisplay.classList.remove('show');
            
            // 更新UI
            updatePunishmentList();
            closeCelebration();
      }
   
      /**
         * 显示庆祝弹窗
         * @description 展示选中的菜品,带有动画效果
         */
      function showCelebration() {
            const modal = document.getElementById('celebrationModal');
            document.getElementById('celebrationPunishment').textContent = currentPunishment;
            
            // 显示并添加动画
            modal.classList.remove('hidden');
            const content = modal.querySelector('.celebration-content');
            content.classList.remove('modal-enter');
            void content.offsetWidth; // 触发重排以重新播放动画
            content.classList.add('modal-enter');
      }
   
      /**
         * 关闭庆祝弹窗
         */
      function closeCelebration() {
            document.getElementById('celebrationModal').classList.add('hidden');
      }
         
      // TODO: 添加导出菜品列表功能
      // TODO: 添加转盘音效支持
      // TODO: 添加点餐历史记录功能
      // OPTIMIZE: 考虑使用Vue/React重构以提高可维护性
    </script>
</body>
</html>

power1489 发表于 2026-1-27 16:34:48

源码在哪?
页: [1]
查看完整版本: 【HTML】美食大转盘 - 点餐助手