🎮ArcadeLab

情绪泡泡龙——ABC消消乐大作战

by LaserPirate42
1256 lines49.2 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>情绪泡泡龙——ABC消消乐大作战</title>
    <style>
        :root {
            --bg: #fdf6f0;
            --card-bg: #fffaf5;
            --text: #5a4a3a;
            --text-light: #7a6a5a;
            --accent: #e8a87c;
            --golden: #f2c94c;
            --golden-light: #f9e4a0;
            --golden-dark: #d4a830;
            --gray-bubble: #c4b5d4;
            --gray-bubble-dark: #a895bc;
            --gray-emotion: #d4b5b5;
            --gray-emotion-dark: #bc9595;
            --positive-b: #f5d78c;
            --positive-b-dark: #e8c35e;
            --positive-c: #f9e4a0;
            --positive-c-dark: #f0d060;
            --danger: #e07b7b;
            --success: #6bbf6b;
            --info: #6b9fbf;
            --shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
            --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
            --radius: 18px;
            --radius-sm: 10px;
            --font: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;
        }

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

        body {
            font-family: var(--font);
            background: linear-gradient(135deg, #fef5ed 0%, #fde9df 30%, #fdf0f5 60%, #fef8f3 100%);
            background-attachment: fixed;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 10px;
            -webkit-tap-highlight-color: transparent;
            user-select: none;
            -webkit-user-select: none;
            -webkit-touch-callout: none;
        }

        .game-container {
            width: 100%;
            max-width: 520px;
            background: var(--card-bg);
            border-radius: 24px;
            box-shadow: var(--shadow-lg);
            padding: 14px 16px 16px;
            display: flex;
            flex-direction: column;
            gap: 10px;
            position: relative;
            overflow: hidden;
        }

        /* 顶部装饰条 */
        .game-container::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 6px;
            background: linear-gradient(90deg, #f9c5a7, #f7d98c, #a7d4c5, #c4b5d4, #f9c5a7);
            border-radius: 24px 24px 0 0;
        }

        /* 头部信息栏 */
        .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 8px;
            padding: 6px 8px;
            background: #fffefb;
            border-radius: var(--radius-sm);
            box-shadow: var(--shadow);
            margin-top: 2px;
        }
        .header-item {
            display: flex;
            align-items: center;
            gap: 5px;
            font-size: 0.9rem;
            font-weight: 600;
            color: var(--text);
            white-space: nowrap;
        }
        .header-item .icon {
            font-size: 1.3rem;
        }
        .header-item .value {
            color: #d4843a;
            font-weight: 700;
            font-size: 1rem;
            min-width: 28px;
            text-align: center;
        }
        .timer-warning {
            color: #e05a5a !important;
            animation: pulse-warning 0.6s ease-in-out infinite;
        }
        @keyframes pulse-warning {
            0%,
            100% {
                transform: scale(1);
            }
            50% {
                transform: scale(1.15);
            }
        }

        /* 事件展示区 */
        .event-display {
            background: linear-gradient(135deg, #fffef9, #fef9f2);
            border: 2px dashed #e8d5c4;
            border-radius: var(--radius-sm);
            padding: 10px 14px;
            text-align: center;
            position: relative;
            transition: all 0.3s;
        }
        .event-label {
            font-size: 0.75rem;
            color: #b8957a;
            text-transform: uppercase;
            letter-spacing: 2px;
            margin-bottom: 3px;
        }
        .event-text {
            font-size: 1.05rem;
            font-weight: 700;
            color: #5a3e2b;
            line-height: 1.4;
        }
        .event-badge {
            display: inline-block;
            background: #f9e4a0;
            color: #8b6914;
            font-size: 0.7rem;
            padding: 3px 10px;
            border-radius: 20px;
            font-weight: 600;
            letter-spacing: 1px;
        }

        /* 泡泡网格 */
        .bubble-grid {
            display: grid;
            grid-template-columns: repeat(6, 1fr);
            grid-template-rows: repeat(5, 1fr);
            gap: 7px;
            padding: 6px;
            background: #fefcf9;
            border-radius: var(--radius);
            box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.04);
            min-height: 280px;
            position: relative;
        }
        @media (max-width: 400px) {
            .bubble-grid {
                gap: 5px;
                padding: 4px;
            }
            .bubble {
                font-size: 0.65rem !important;
                padding: 4px 2px !important;
                min-height: 48px !important;
            }
            .game-container {
                padding: 10px 8px 12px;
            }
            .header-item {
                font-size: 0.75rem;
            }
            .event-text {
                font-size: 0.9rem;
            }
        }
        @media (min-width: 401px) and (max-width: 480px) {
            .bubble {
                font-size: 0.7rem !important;
                padding: 5px 3px !important;
                min-height: 52px !important;
            }
        }

        /* 泡泡样式 */
        .bubble {
            display: flex;
            align-items: center;
            justify-content: center;
            text-align: center;
            border-radius: 50%;
            cursor: pointer;
            font-weight: 600;
            font-size: 0.72rem;
            line-height: 1.25;
            padding: 6px 4px;
            transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.3s;
            position: relative;
            word-break: break-all;
            min-height: 56px;
            aspect-ratio: 1 / 1;
            box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1), inset 0 2px 0 rgba(255, 255, 255, 0.5);
            letter-spacing: 0.3px;
            color: #3a2a1a;
        }
        .bubble:active {
            transform: scale(0.9);
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
        }
        .bubble.selected {
            transform: scale(1.08);
            box-shadow: 0 0 0 5px rgba(100, 160, 220, 0.5), 0 6px 20px rgba(0, 0, 0, 0.2) !important;
            z-index: 5;
            animation: gentle-bounce 0.5s ease-in-out infinite;
        }
        @keyframes gentle-bounce {
            0%,
            100% {
                transform: scale(1.08);
            }
            50% {
                transform: scale(1.14);
            }
        }
        .bubble.hint-glow {
            animation: hint-pulse 0.7s ease-in-out infinite !important;
            box-shadow: 0 0 0 6px rgba(255, 180, 50, 0.7), 0 6px 22px rgba(255, 150, 30, 0.4) !important;
            z-index: 4;
        }
        @keyframes hint-pulse {
            0%,
            100% {
                box-shadow: 0 0 0 6px rgba(255, 180, 50, 0.7), 0 6px 22px rgba(255, 150, 30, 0.4);
            }
            50% {
                box-shadow: 0 0 0 12px rgba(255, 180, 50, 0.2), 0 8px 28px rgba(255, 150, 30, 0.6);
            }
        }

        /* 灰色泡泡 - 不合理信念 */
        .bubble.belief-negative {
            background: linear-gradient(145deg, #d5c8e8, #bfaed6);
            box-shadow: 0 3px 8px rgba(150, 120, 170, 0.3), inset 0 2px 0 rgba(255, 255, 255, 0.4);
        }
        /* 灰色泡泡 - 负性情绪 */
        .bubble.emotion-negative {
            background: linear-gradient(145deg, #e4cdcd, #d4b5b5);
            box-shadow: 0 3px 8px rgba(170, 130, 130, 0.3), inset 0 2px 0 rgba(255, 255, 255, 0.4);
        }
        /* 金色泡泡 - 合理信念 */
        .bubble.belief-positive {
            background: linear-gradient(145deg, #f8df90, #f0c94c);
            box-shadow: 0 3px 10px rgba(200, 150, 40, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.5);
            color: #5a3a0a;
        }
        /* 金色泡泡 - 正性情绪 */
        .bubble.emotion-positive {
            background: linear-gradient(145deg, #fceba8, #f7d96a);
            box-shadow: 0 3px 10px rgba(200, 150, 30, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.5);
            color: #5a3a0a;
        }

        /* 动画:爆破 */
        .bubble.bursting {
            animation: burst-out 0.5s ease-out forwards;
        }
        @keyframes burst-out {
            0% {
                transform: scale(1);
                opacity: 1;
                filter: blur(0);
            }
            30% {
                transform: scale(1.3);
                opacity: 0.7;
                filter: blur(1px);
            }
            100% {
                transform: scale(0);
                opacity: 0;
                filter: blur(4px);
            }
        }
        /* 动画:收集金光 */
        .bubble.collecting {
            animation: collect-gold 0.6s ease-out forwards;
        }
        @keyframes collect-gold {
            0% {
                transform: scale(1);
                opacity: 1;
                box-shadow: 0 0 0 0 rgba(255, 200, 50, 0.8);
            }
            50% {
                transform: scale(1.4);
                opacity: 0.9;
                box-shadow: 0 0 30px 20px rgba(255, 200, 50, 0.6);
            }
            100% {
                transform: scale(0);
                opacity: 0;
                box-shadow: 0 0 60px 40px rgba(255, 200, 50, 0);
            }
        }
        /* 动画:掉落 */
        .bubble.dropping {
            animation: drop-in 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
        }
        @keyframes drop-in {
            0% {
                transform: translateY(-80px) scale(0.3);
                opacity: 0;
            }
            60% {
                transform: translateY(8px) scale(1.06);
                opacity: 1;
            }
            80% {
                transform: translateY(-3px) scale(0.97);
            }
            100% {
                transform: translateY(0) scale(1);
                opacity: 1;
            }
        }
        /* 抖动 */
        .bubble.shaking {
            animation: shake 0.45s ease-in-out;
        }
        @keyframes shake {
            0%,
            100% {
                transform: translateX(0);
            }
            15% {
                transform: translateX(-8px);
            }
            30% {
                transform: translateX(8px);
            }
            45% {
                transform: translateX(-6px);
            }
            60% {
                transform: translateX(6px);
            }
            75% {
                transform: translateX(-3px);
            }
            90% {
                transform: translateX(3px);
            }
        }

        /* 浮动提示文字 */
        .floating-tip {
            position: fixed;
            pointer-events: none;
            z-index: 100;
            font-weight: 700;
            font-size: 0.95rem;
            color: #5a3a0a;
            background: #fef9e8;
            border: 2px solid #f0c94c;
            border-radius: 20px;
            padding: 8px 16px;
            box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
            animation: float-up 2.5s ease-out forwards;
            white-space: nowrap;
            text-align: center;
        }
        @keyframes float-up {
            0% {
                opacity: 0;
                transform: translateY(0) scale(0.6);
            }
            20% {
                opacity: 1;
                transform: translateY(-20px) scale(1.05);
            }
            70% {
                opacity: 0.9;
                transform: translateY(-60px) scale(1);
            }
            100% {
                opacity: 0;
                transform: translateY(-90px) scale(0.8);
            }
        }

        /* 按钮 */
        .btn-row {
            display: flex;
            gap: 10px;
            justify-content: center;
            flex-wrap: wrap;
        }
        .btn {
            padding: 10px 18px;
            border-radius: 25px;
            border: none;
            font-size: 0.9rem;
            font-weight: 700;
            cursor: pointer;
            letter-spacing: 0.5px;
            transition: all 0.2s;
            font-family: var(--font);
            box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            gap: 6px;
        }
        .btn:active {
            transform: scale(0.94);
            box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
        }
        .btn-hint {
            background: #fff3d6;
            color: #8b6914;
            border: 2px solid #f0c94c;
        }
        .btn-hint:disabled {
            opacity: 0.4;
            cursor: not-allowed;
            pointer-events: none;
        }
        .btn-reset {
            background: #f5f0eb;
            color: #7a6a5a;
            border: 2px solid #d5c8b5;
        }

        /* 结算弹窗 */
        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.55);
            z-index: 200;
            display: flex;
            align-items: center;
            justify-content: center;
            animation: fade-in 0.3s ease;
        }
        @keyframes fade-in {
            from {
                opacity: 0;
            }
            to {
                opacity: 1;
            }
        }
        .modal {
            background: #fffefb;
            border-radius: 24px;
            padding: 24px 20px;
            max-width: 440px;
            width: 90%;
            box-shadow: 0 20px 50px rgba(0, 0, 0, 0.25);
            text-align: center;
            max-height: 85vh;
            overflow-y: auto;
            animation: slide-up 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
        }
        @keyframes slide-up {
            from {
                transform: translateY(60px);
                opacity: 0;
            }
            to {
                transform: translateY(0);
                opacity: 1;
            }
        }
        .modal h2 {
            font-size: 1.5rem;
            color: #5a3e2b;
            margin-bottom: 8px;
        }
        .modal .title-badge {
            display: inline-block;
            font-size: 1.1rem;
            font-weight: 700;
            padding: 8px 20px;
            border-radius: 25px;
            margin: 8px 0 14px;
            letter-spacing: 1px;
        }
        .badge-diamond {
            background: #e8f8ff;
            color: #2b6f9a;
            border: 2px solid #6bc4e8;
        }
        .badge-gold {
            background: #fff8e8;
            color: #8b6914;
            border: 2px solid #f0c94c;
        }
        .badge-bronze {
            background: #fdf5f0;
            color: #8b5a3a;
            border: 2px solid #d4a080;
        }
        .modal .score-display {
            font-size: 3rem;
            font-weight: 900;
            color: #d4843a;
            margin: 6px 0;
        }
        .modal .stars-display {
            font-size: 2rem;
            letter-spacing: 4px;
            margin: 4px 0 10px;
        }
        .modal .quotes-list {
            text-align: left;
            background: #fefcf8;
            border-radius: 14px;
            padding: 14px;
            margin: 10px 0;
            border: 1px solid #f0e8d8;
        }
        .modal .quotes-list p {
            padding: 6px 0;
            border-bottom: 1px dotted #e8d8c4;
            font-size: 0.85rem;
            color: #5a4a3a;
            line-height: 1.5;
        }
        .modal .quotes-list p:last-child {
            border-bottom: none;
        }
        .modal .btn-close {
            margin-top: 12px;
            background: #f0c94c;
            color: #5a3a0a;
            font-size: 1rem;
            padding: 12px 30px;
            border-radius: 25px;
            border: none;
            font-weight: 700;
            cursor: pointer;
            box-shadow: 0 4px 14px rgba(200, 150, 30, 0.3);
            font-family: var(--font);
            letter-spacing: 0.5px;
            transition: all 0.2s;
        }
        .modal .btn-close:active {
            transform: scale(0.94);
        }

        /* 响应式 */
        @media (min-width: 481px) {
            .bubble {
                font-size: 0.78rem;
                min-height: 62px;
            }
            .bubble-grid {
                gap: 8px;
            }
        }
    </style>
</head>
<body>

    <div class="game-container" id="gameContainer">
        <!-- 头部信息栏 -->
        <div class="header">
            <div class="header-item"><span class="icon">🎯</span>关卡:<span class="value" id="levelDisplay">1/5</span></div>
            <div class="header-item"><span class="icon">⭐</span>星星:<span class="value" id="starDisplay">0</span></div>
            <div class="header-item"><span class="icon">💯</span>分数:<span class="value" id="scoreDisplay">0</span></div>
            <div class="header-item"><span class="icon">⏱️</span>时间:<span class="value" id="timerDisplay">90</span>秒</div>
        </div>

        <!-- 事件展示区 -->
        <div class="event-display" id="eventDisplay">
            <div class="event-label">📋 当前事件 A</div>
            <div class="event-text" id="eventText">小组合作作业,我的建议没被采纳。</div>
        </div>

        <!-- 泡泡网格 -->
        <div class="bubble-grid" id="bubbleGrid"></div>

        <!-- 按钮行 -->
        <div class="btn-row">
            <button class="btn btn-hint" id="btnHint" title="每关限用1次">💡 提示</button>
            <button class="btn btn-reset" id="btnReset">🔄 重置本关</button>
        </div>
    </div>

    <!-- 结算弹窗(隐藏) -->
    <div class="modal-overlay" id="modalOverlay" style="display:none;">
        <div class="modal" id="modalContent"></div>
    </div>

    <script>
        (function() {
            // ==================== 游戏数据 ====================
            const eventsData = [{
                id: 0,
                eventA: '小组合作作业,我的建议没被采纳。',
                beliefNegative: '他们肯定看不起我,觉得我蠢。',
                emotionNegative: '委屈😭、愤怒😡',
                beliefPositive: '方案有取舍很正常,不代表我不好。',
                emotionPositive: '平静😊、有动力💪',
                encouragement: '太棒了!你的想法很有价值,不被采纳只是方案选择不同而已✨',
            }, {
                id: 1,
                eventA: '手机被老师暂时保管了。',
                beliefNegative: '老师故意针对我,让我丢脸!',
                emotionNegative: '暴怒😡、想顶撞',
                beliefPositive: '规则对大家都一样,老师是在帮我。',
                emotionPositive: '释然😌、专心听课',
                encouragement: '没错!规则面前人人平等,专注听课才是对自己负责💪',
            }, {
                id: 2,
                eventA: '好朋友突然不回我消息。',
                beliefNegative: '他肯定讨厌我了。',
                emotionNegative: '焦虑😰、难过😢',
                beliefPositive: '他可能在忙,我可以等等再问。',
                emotionPositive: '理解🤗、耐心',
                encouragement: '理解万岁!朋友可能只是暂时忙碌,耐心是友谊的试金石🤗',
            }, {
                id: 3,
                eventA: '考试没考好。',
                beliefNegative: '我太笨了,永远学不好。',
                emotionNegative: '沮丧😞、想放弃',
                beliefPositive: '这次没考好说明有提升空间。',
                emotionPositive: '反思🤔、制定计划✍️',
                encouragement: '成长型思维!考试是检验学习的机会,不是定义能力的标签✍️',
            }, {
                id: 4,
                eventA: '被同学当众开了玩笑。',
                beliefNegative: '他在羞辱我,所有人都笑我。',
                emotionNegative: '羞耻😳、愤怒😡',
                beliefPositive: '他只是开玩笑,没有恶意。',
                emotionPositive: '一笑了之😄、大度',
                encouragement: '大度是一种力量!一笑了之,你的心胸比玩笑更大😄',
            }];

            // 全局金句
            const globalQuotes = [
                '💡 想法一变,心情就亮了!',
                '🌈 事件无法改变,但信念可以选择。',
                '🧠 做情绪的主人,从转念开始。',
                '🫧 灰色泡泡不可怕,转念就能变金色!',
                '🎨 你的想法,决定你的心情颜色。',
                '🌟 换个角度看世界,世界也会温柔待你。',
            ];

            // ==================== 游戏状态 ====================
            let currentLevel = 0; // 0-4
            let score = 0;
            let stars = 0;
            let timeLeft = 90;
            let timerInterval = null;
            let hintUsedThisLevel = false;
            let selectedBubble = null;
            let isProcessing = false;
            let collectedGoldenPairs = 0; // 当前关卡已收集的金色配对数量
            let goldenPairsNeeded = 1; // 当前关卡需要收集的金色配对数量(初始1对,转化后可能增加)
            let currentEventGoldenBubbles = []; // 追踪当前事件的金色泡泡ID
            let allCollectedEncouragements = []; // 收集到的所有鼓励语

            // ==================== DOM引用 ====================
            const bubbleGrid = document.getElementById('bubbleGrid');
            const eventText = document.getElementById('eventText');
            const levelDisplay = document.getElementById('levelDisplay');
            const starDisplay = document.getElementById('starDisplay');
            const scoreDisplay = document.getElementById('scoreDisplay');
            const timerDisplay = document.getElementById('timerDisplay');
            const btnHint = document.getElementById('btnHint');
            const btnReset = document.getElementById('btnReset');
            const modalOverlay = document.getElementById('modalOverlay');
            const modalContent = document.getElementById('modalContent');

            // ==================== 泡泡数据结构 ====================
            // 每个泡泡: { id, eventId, type:'belief'|'emotion', chain:'negative'|'positive', text, htmlId }
            let allBubbles = [];

            function generateBubbleId() {
                return 'bub_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
            }

            function createBubbleData(eventId, type, chain, text) {
                return {
                    id: generateBubbleId(),
                    eventId: eventId,
                    type: type,
                    chain: chain,
                    text: text,
                };
            }

            // ==================== 构建当前关卡的泡泡池 ====================
            function buildBubblePoolForLevel(levelIndex) {
                const pool = [];
                const currentEvent = eventsData[levelIndex];
                // 当前事件的4个泡泡(必须出现)
                pool.push(createBubbleData(levelIndex, 'belief', 'negative', currentEvent.beliefNegative));
                pool.push(createBubbleData(levelIndex, 'emotion', 'negative', currentEvent.emotionNegative));
                pool.push(createBubbleData(levelIndex, 'belief', 'positive', currentEvent.beliefPositive));
                pool.push(createBubbleData(levelIndex, 'emotion', 'positive', currentEvent.emotionPositive));
                // 从其他事件中随机选取填充泡泡
                const otherEvents = eventsData.filter((_, i) => i !== levelIndex);
                const fillerPool = [];
                otherEvents.forEach(ev => {
                    fillerPool.push(createBubbleData(ev.id, 'belief', 'negative', ev.beliefNegative));
                    fillerPool.push(createBubbleData(ev.id, 'emotion', 'negative', ev.emotionNegative));
                    fillerPool.push(createBubbleData(ev.id, 'belief', 'positive', ev.beliefPositive));
                    fillerPool.push(createBubbleData(ev.id, 'emotion', 'positive', ev.emotionPositive));
                });
                // 随机打乱填充池
                shuffleArray(fillerPool);
                // 需要填充到30个(5×6网格)
                const needed = 30 - pool.length;
                for (let i = 0; i < needed; i++) {
                    pool.push({ ...fillerPool[i % fillerPool.length], id: generateBubbleId() });
                }
                // 打乱整个池
                shuffleArray(pool);
                return pool;
            }

            function shuffleArray(arr) {
                for (let i = arr.length - 1; i > 0; i--) {
                    const j = Math.floor(Math.random() * (i + 1));
                    [arr[i], arr[j]] = [arr[j], arr[i]];
                }
            }

            // ==================== 渲染泡泡网格 ====================
            function renderGrid() {
                bubbleGrid.innerHTML = '';
                allBubbles.forEach((bubble, index) => {
                    const el = document.createElement('div');
                    el.className = 'bubble';
                    el.setAttribute('data-index', index);
                    el.setAttribute('data-id', bubble.id);
                    el.textContent = bubble.text;
                    // 根据类型和链设置样式类
                    if (bubble.chain === 'negative') {
                        if (bubble.type === 'belief') {
                            el.classList.add('belief-negative');
                        } else {
                            el.classList.add('emotion-negative');
                        }
                    } else {
                        if (bubble.type === 'belief') {
                            el.classList.add('belief-positive');
                        } else {
                            el.classList.add('emotion-positive');
                        }
                    }
                    el.addEventListener('click', () => onBubbleClick(index, el));
                    // 触摸事件防止延迟
                    el.addEventListener('touchend', (e) => {
                        e.preventDefault();
                        onBubbleClick(index, el);
                    });
                    bubbleGrid.appendChild(el);
                    bubble.htmlEl = el;
                    bubble.gridIndex = index;
                });
                // 追踪当前事件的金色泡泡
                updateGoldenBubbleTracking();
            }

            function updateGoldenBubbleTracking() {
                currentEventGoldenBubbles = allBubbles
                    .filter(b => b.eventId === currentLevel && b.chain === 'positive')
                    .map(b => b.id);
                // 计算需要收集的金色配对数量(= 金色信念数量,因为每个金色信念需要一个金色情绪配对)
                const goldenBeliefs = allBubbles.filter(b => b.eventId === currentLevel && b.chain === 'positive' && b
                    .type === 'belief').length;
                goldenPairsNeeded = goldenBeliefs;
                collectedGoldenPairs = 0;
            }

            function refreshBubbleElements() {
                const els = bubbleGrid.querySelectorAll('.bubble');
                els.forEach(el => {
                    const index = parseInt(el.getAttribute('data-index'));
                    if (index < allBubbles.length) {
                        allBubbles[index].htmlEl = el;
                        allBubbles[index].gridIndex = index;
                    }
                });
            }

            // ==================== 泡泡点击处理 ====================
            function onBubbleClick(index, el) {
                if (isProcessing) return;
                if (el.classList.contains('bursting') || el.classList.contains('collecting') || el.classList
                    .contains('dropping')) return;
                if (!el.parentNode) return; // 已被移除

                const bubble = allBubbles[index];
                if (!bubble) return;

                // 如果已经选中了这个泡泡,取消选中
                if (selectedBubble && selectedBubble.id === bubble.id) {
                    deselectBubble();
                    return;
                }

                // 如果没有已选中的泡泡,选中当前泡泡
                if (!selectedBubble) {
                    selectBubble(bubble, el);
                    return;
                }

                // 已有选中的泡泡,进行配对判断
                const firstBubble = selectedBubble;
                const firstEl = firstBubble.htmlEl;
                const secondBubble = bubble;
                const secondEl = el;

                // 检查是否是有效配对:同一事件、一个信念一个情绪、同一条链
                if (firstBubble.eventId === secondBubble.eventId &&
                    firstBubble.type !== secondBubble.type &&
                    firstBubble.chain === secondBubble.chain) {

                    // 有效配对!
                    isProcessing = true;
                    deselectBubbleSilent();

                    if (firstBubble.chain === 'negative') {
                        // 灰色配对 → 爆破转化
                        handleNegativePair(firstBubble, secondBubble, firstEl, secondEl);
                    } else {
                        // 金色配对 → 收集得分
                        handlePositivePair(firstBubble, secondBubble, firstEl, secondEl);
                    }
                } else {
                    // 无效配对 → 抖动
                    handleInvalidPair(firstEl, secondEl);
                    deselectBubbleSilent();
                }
            }

            function selectBubble(bubble, el) {
                deselectBubbleSilent();
                selectedBubble = bubble;
                el.classList.add('selected');
            }

            function deselectBubble() {
                if (selectedBubble && selectedBubble.htmlEl) {
                    selectedBubble.htmlEl.classList.remove('selected');
                }
                selectedBubble = null;
            }

            function deselectBubbleSilent() {
                if (selectedBubble && selectedBubble.htmlEl) {
                    selectedBubble.htmlEl.classList.remove('selected');
                }
                selectedBubble = null;
            }

            // ==================== 配对处理 ====================
            function handleNegativePair(bub1, bub2, el1, el2) {
                // 爆破动画
                el1.classList.add('bursting');
                el2.classList.add('bursting');
                // 弹出提示
                const rect1 = el1.getBoundingClientRect();
                const rect2 = el2.getBoundingClientRect();
                const midX = (rect1.left + rect1.right + rect2.left + rect2.right) / 4;
                const midY = (rect1.top + rect1.bottom + rect2.top + rect2.bottom) / 4;
                showFloatingTip(midX, midY, '💡 试试换个想法,心情会不一样哦!');

                // 获取当前事件的正面泡泡文本
                const currentEvent = eventsData[currentLevel];
                const newBeliefText = currentEvent.beliefPositive;
                const newEmotionText = currentEvent.emotionPositive;

                // 延迟移除旧泡泡并添加新金色泡泡
                setTimeout(() => {
                    // 移除旧泡泡
                    removeBubbleById(bub1.id);
                    removeBubbleById(bub2.id);

                    // 创建新的金色泡泡
                    const newBeliefBubble = createBubbleData(currentLevel, 'belief', 'positive', newBeliefText);
                    const newEmotionBubble = createBubbleData(currentLevel, 'emotion', 'positive', newEmotionText);

                    // 插入到网格中(替换被移除的位置)
                    // 找到空位或使用原来的位置
                    const emptyIndices = [];
                    allBubbles.forEach((b, i) => {
                        if (b === null || b === undefined) emptyIndices.push(i);
                    });
                    // 清理null
                    allBubbles = allBubbles.filter(b => b !== null && b !== undefined);
                    // 添加新泡泡
                    allBubbles.push(newBeliefBubble);
                    allBubbles.push(newEmotionBubble);
                    // 如果总数超过30,移除一些非当前事件的干扰泡泡
                    while (allBubbles.length > 30) {
                        const removeIndex = allBubbles.findIndex(b => b.eventId !== currentLevel && b.chain ===
                            'negative');
                        if (removeIndex >= 0) {
                            allBubbles.splice(removeIndex, 1);
                        } else {
                            const ri = allBubbles.findIndex(b => b.eventId !== currentLevel);
                            if (ri >= 0) allBubbles.splice(ri, 1);
                            else break;
                        }
                    }
                    // 重新渲染
                    renderGrid();
                    // 给新泡泡添加掉落动画
                    setTimeout(() => {
                        const newBubs = allBubbles.filter(b => b.eventId === currentLevel && b.chain ===
                            'positive');
                        newBubs.forEach(b => {
                            if (b.htmlEl && !b.htmlEl.classList.contains('dropping') && !b.htmlEl.classList
                                .contains('collecting')) {
                                // 检查是否是新加入的(没有旧位置)
                                b.htmlEl.classList.add('dropping');
                            }
                        });
                        updateGoldenBubbleTracking();
                        checkLevelComplete();
                    }, 80);
                    isProcessing = false;
                }, 500);
            }

            function handlePositivePair(bub1, bub2, el1, el2) {
                // 收集动画
                el1.classList.add('collecting');
                el2.classList.add('collecting');
                // 弹出鼓励语
                const currentEvent = eventsData[currentLevel];
                const rect1 = el1.getBoundingClientRect();
                const rect2 = el2.getBoundingClientRect();
                const midX = (rect1.left + rect1.right + rect2.left + rect2.right) / 4;
                const midY = (rect1.top + rect1.bottom + rect2.top + rect2.bottom) / 4;
                showFloatingTip(midX, midY, currentEvent.encouragement);

                // 加分
                score += 10;
                stars += 1;
                collectedGoldenPairs += 1;
                // 收集鼓励语
                if (!allCollectedEncouragements.includes(currentEvent.encouragement)) {
                    allCollectedEncouragements.push(currentEvent.encouragement);
                }
                updateUI();

                // 延迟移除
                setTimeout(() => {
                    removeBubbleById(bub1.id);
                    removeBubbleById(bub2.id);
                    allBubbles = allBubbles.filter(b => b !== null && b !== undefined);
                    renderGrid();
                    updateGoldenBubbleTracking();
                    checkLevelComplete();
                    isProcessing = false;
                }, 550);
            }

            function handleInvalidPair(el1, el2) {
                el1.classList.add('shaking');
                el2.classList.add('shaking');
                setTimeout(() => {
                    el1.classList.remove('shaking');
                    el2.classList.remove('shaking');
                }, 450);
            }

            function removeBubbleById(id) {
                const index = allBubbles.findIndex(b => b && b.id === id);
                if (index >= 0) {
                    allBubbles[index] = null;
                }
            }

            // ==================== 浮动提示 ====================
            function showFloatingTip(x, y, text) {
                const tip = document.createElement('div');
                tip.className = 'floating-tip';
                tip.textContent = text;
                tip.style.left = x + 'px';
                tip.style.top = y + 'px';
                tip.style.transform = 'translate(-50%, -50%)';
                document.body.appendChild(tip);
                setTimeout(() => {
                    if (tip.parentNode) tip.parentNode.removeChild(tip);
                }, 2600);
            }

            // ==================== 检查关卡完成 ====================
            function checkLevelComplete() {
                // 更新追踪
                updateGoldenBubbleTracking();
                // 检查当前事件是否还有金色泡泡
                const remainingGolden = allBubbles.filter(b => b && b.eventId === currentLevel && b.chain === 'positive');
                if (remainingGolden.length === 0 && collectedGoldenPairs >= goldenPairsNeeded) {
                    // 当前关卡完成
                    clearInterval(timerInterval);
                    if (currentLevel < 4) {
                        // 进入下一关
                        setTimeout(() => {
                            currentLevel++;
                            startLevel(currentLevel);
                        }, 800);
                    } else {
                        // 全部通关
                        setTimeout(() => {
                            showFinalModal();
                        }, 600);
                    }
                }
            }

            // ==================== 计时器 ====================
            function startTimer() {
                clearInterval(timerInterval);
                timeLeft = 90;
                updateTimerDisplay();
                timerInterval = setInterval(() => {
                    timeLeft--;
                    updateTimerDisplay();
                    if (timeLeft <= 0) {
                        clearInterval(timerInterval);
                        handleTimeUp();
                    }
                }, 1000);
            }

            function updateTimerDisplay() {
                timerDisplay.textContent = timeLeft;
                if (timeLeft <= 15) {
                    timerDisplay.classList.add('timer-warning');
                } else {
                    timerDisplay.classList.remove('timer-warning');
                }
            }

            function handleTimeUp() {
                isProcessing = true;
                clearInterval(timerInterval);
                // 显示提示并重置当前关卡
                const gridRect = bubbleGrid.getBoundingClientRect();
                const cx = gridRect.left + gridRect.width / 2;
                const cy = gridRect.top + gridRect.height / 2;
                showFloatingTip(cx, cy, '⏰ 时间到!重新挑战本关吧~');
                setTimeout(() => {
                    isProcessing = false;
                    startLevel(currentLevel);
                }, 1500);
            }

            // ==================== 开始关卡 ====================
            function startLevel(levelIndex) {
                currentLevel = levelIndex;
                clearInterval(timerInterval);
                isProcessing = false;
                selectedBubble = null;
                hintUsedThisLevel = false;
                collectedGoldenPairs = 0;
                goldenPairsNeeded = 1;
                btnHint.disabled = false;
                btnHint.textContent = '💡 提示';
                const currentEvent = eventsData[levelIndex];
                eventText.textContent = currentEvent.eventA;
                levelDisplay.textContent = (levelIndex + 1) + '/5';
                updateUI();
                allBubbles = buildBubblePoolForLevel(levelIndex);
                renderGrid();
                updateGoldenBubbleTracking();
                updateTimerDisplay();
                timerDisplay.classList.remove('timer-warning');
                startTimer();
                // 滚动到游戏区域
                document.getElementById('gameContainer').scrollIntoView({ behavior: 'smooth' });
            }

            function updateUI() {
                scoreDisplay.textContent = score;
                starDisplay.textContent = stars;
                levelDisplay.textContent = (currentLevel + 1) + '/5';
            }

            // ==================== 提示按钮 ====================
            btnHint.addEventListener('click', () => {
                if (hintUsedThisLevel || isProcessing) return;
                hintUsedThisLevel = true;
                btnHint.disabled = true;
                btnHint.textContent = '💡 已使用';
                // 找到当前事件的一个金色配对并高亮
                const goldenBelief = allBubbles.find(b => b && b.eventId === currentLevel && b.chain === 'positive' &&
                    b.type === 'belief');
                const goldenEmotion = allBubbles.find(b => b && b.eventId === currentLevel && b.chain === 'positive' &&
                    b.type === 'emotion');
                if (goldenBelief && goldenBelief.htmlEl) {
                    goldenBelief.htmlEl.classList.add('hint-glow');
                }
                if (goldenEmotion && goldenEmotion.htmlEl) {
                    goldenEmotion.htmlEl.classList.add('hint-glow');
                }
                // 3秒后取消高亮
                setTimeout(() => {
                    if (goldenBelief && goldenBelief.htmlEl) goldenBelief.htmlEl.classList.remove('hint-glow');
                    if (goldenEmotion && goldenEmotion.htmlEl) goldenEmotion.htmlEl.classList.remove('hint-glow');
                }, 3000);
            });

            // ==================== 重置按钮 ====================
            btnReset.addEventListener('click', () => {
                if (isProcessing) return;
                clearInterval(timerInterval);
                isProcessing = false;
                selectedBubble = null;
                hintUsedThisLevel = false;
                collectedGoldenPairs = 0;
                goldenPairsNeeded = 1;
                btnHint.disabled = false;
                btnHint.textContent = '💡 提示';
                allBubbles = buildBubblePoolForLevel(currentLevel);
                renderGrid();
                updateGoldenBubbleTracking();
                timeLeft = 90;
                updateTimerDisplay();
                timerDisplay.classList.remove('timer-warning');
                updateUI();
                startTimer();
            });

            // ==================== 通关结算 ====================
            function showFinalModal() {
                clearInterval(timerInterval);
                isProcessing = true;
                let titleBadgeClass = '';
                let titleName = '';
                if (score >= 80) {
                    titleBadgeClass = 'badge-diamond';
                    titleName = '💎 钻石心灵大师';
                } else if (score >= 50) {
                    titleBadgeClass = 'badge-gold';
                    titleName = '🥇 黄金炼心师';
                } else {
                    titleBadgeClass = 'badge-bronze';
                    titleName = '🥉 青铜净化师';
                }

                // 收集所有鼓励语和全局金句
                const allQuotes = [...allCollectedEncouragements, ...globalQuotes];
                // 去重
                const uniqueQuotes = [...new Set(allQuotes)];

                modalContent.innerHTML = `
                    <h2>🎉 恭喜通关!</h2>
                    <div class="title-badge ${titleBadgeClass}">${titleName}</div>
                    <div class="score-display">${score}</div>
                    <div style="color:#7a6a5a;">总分</div>
                    <div class="stars-display">${'⭐'.repeat(Math.min(stars, 20))}</div>
                    <div style="color:#7a6a5a;font-size:0.85rem;">共获得 ${stars} 颗星星</div>
                    <div class="quotes-list">
                        <strong style="color:#5a3e2b;">📝 你的心灵金句合集:</strong>
                        ${uniqueQuotes.map(q => `<p>${q}</p>`).join('')}
                    </div>
                    <p style="font-size:0.8rem;color:#b8957a;margin-top:8px;">
                        记住:<strong>事件A只是间接原因,信念B才是情绪C的直接原因</strong>。<br>
                        做情绪的主人,从调整想法开始!🌈
                    </p>
                    <button class="btn-close" id="btnCloseModal">🔄 重新挑战</button>
                `;
                modalOverlay.style.display = 'flex';
                document.getElementById('btnCloseModal').addEventListener('click', () => {
                    modalOverlay.style.display = 'none';
                    resetGame();
                });
            }

            function resetGame() {
                currentLevel = 0;
                score = 0;
                stars = 0;
                timeLeft = 90;
                collectedGoldenPairs = 0;
                goldenPairsNeeded = 1;
                allCollectedEncouragements = [];
                isProcessing = false;
                selectedBubble = null;
                hintUsedThisLevel = false;
                btnHint.disabled = false;
                btnHint.textContent = '💡 提示';
                clearInterval(timerInterval);
                updateUI();
                updateTimerDisplay();
                timerDisplay.classList.remove('timer-warning');
                allBubbles = buildBubblePoolForLevel(0);
                renderGrid();
                updateGoldenBubbleTracking();
                startTimer();
                eventText.textContent = eventsData[0].eventA;
                document.getElementById('gameContainer').scrollIntoView({ behavior: 'smooth' });
            }

            // 点击弹窗遮罩关闭
            modalOverlay.addEventListener('click', function(e) {
                if (e.target === modalOverlay) {
                    modalOverlay.style.display = 'none';
                    resetGame();
                }
            });

            // ==================== 初始化游戏 ====================
            function initGame() {
                currentLevel = 0;
                score = 0;
                stars = 0;
                allCollectedEncouragements = [];
                allBubbles = buildBubblePoolForLevel(0);
                eventText.textContent = eventsData[0].eventA;
                updateUI();
                renderGrid();
                updateGoldenBubbleTracking();
                startTimer();
            }

            // 启动
            initGame();
            console.log('🫧 情绪泡泡龙——ABC消消乐大作战 已就绪!');
            console.log('📋 知识点:事件A只是间接原因,信念B才是情绪C的直接原因');
            console.log('🎮 玩法:点击两个同色系泡泡配对,灰色=不合理信念+负性情绪(爆破转化),金色=合理信念+正性情绪(收集得分)');
            console.log('💡 每关限用1次提示,共5关,90秒/关,加油!');
        })();
    </script>
</body>
</html>

Game Source: 情绪泡泡龙——ABC消消乐大作战

Creator: LaserPirate42

Libraries: none

Complexity: complex (1256 lines, 49.2 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: abc-laserpirate42" to link back to the original. Then publish at arcadelab.ai/publish.