🎮ArcadeLab

情绪泡泡龙——ABC消消乐大作战(优化版)

by LaserPirate42
1002 lines37.4 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;
            --golden: #f2c94c;
            --gray-bubble: #c4b5d4;
            --gray-emotion: #d4b5b5;
            --positive-b: #f5d78c;
            --positive-c: #f9e4a0;
            --shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
            --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
            --radius: 18px;
            --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%);
            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: 480px;
            background: var(--card-bg);
            border-radius: 24px;
            box-shadow: var(--shadow-lg);
            padding: 14px 14px 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: 6px;
            padding: 6px 8px;
            background: #fffefb;
            border-radius: 12px;
            box-shadow: var(--shadow);
            margin-top: 2px;
        }

        .header-item {
            display: flex;
            align-items: center;
            gap: 4px;
            font-size: 0.85rem;
            font-weight: 600;
            color: var(--text);
            white-space: nowrap;
        }

        .header-item .icon {
            font-size: 1.2rem;
        }

        .header-item .value {
            color: #d4843a;
            font-weight: 700;
            font-size: 0.95rem;
            min-width: 24px;
            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: 12px;
            padding: 8px 12px;
            text-align: center;
        }

        .event-label {
            font-size: 0.7rem;
            color: #b8957a;
            letter-spacing: 1px;
            margin-bottom: 2px;
        }

        .event-text {
            font-size: 1rem;
            font-weight: 700;
            color: #5a3e2b;
            line-height: 1.4;
        }

        .guide-text {
            font-size: 0.75rem;
            color: #a08060;
            margin-top: 4px;
        }

        /* 4x4 网格 */
        .bubble-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            grid-template-rows: repeat(4, 1fr);
            gap: 8px;
            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: 6px;
                padding: 4px;
            }
            .bubble {
                font-size: 0.7rem !important;
                min-height: 58px !important;
            }
            .game-container {
                padding: 10px 8px 12px;
            }
            .event-text {
                font-size: 0.9rem;
            }
        }

        .bubble {
            display: flex;
            align-items: center;
            justify-content: center;
            text-align: center;
            border-radius: 50%;
            cursor: pointer;
            font-weight: 600;
            font-size: 0.8rem;
            line-height: 1.25;
            padding: 6px 3px;
            transition: transform 0.15s ease, box-shadow 0.15s ease;
            position: relative;
            word-break: break-all;
            min-height: 64px;
            aspect-ratio: 1 / 1;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12), inset 0 2px 0 rgba(255, 255, 255, 0.5);
            letter-spacing: 0.3px;
            color: #3a2a1a;
        }

        .bubble:active {
            transform: scale(0.9);
        }

        .bubble.selected {
            transform: scale(1.08);
            box-shadow: 0 0 0 5px rgba(100, 160, 220, 0.6), 0 6px 20px rgba(0, 0, 0, 0.25) !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.8), 0 6px 22px rgba(255, 150, 30, 0.5) !important;
            z-index: 4;
        }

        @keyframes hint-pulse {
            0%,
            100% {
                box-shadow: 0 0 0 6px rgba(255, 180, 50, 0.8), 0 6px 22px rgba(255, 150, 30, 0.5);
            }
            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 4px 10px rgba(150, 120, 170, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.4);
        }

        /* 灰色泡泡 - 负性情绪 */
        .bubble.emotion-negative {
            background: linear-gradient(145deg, #e4cdcd, #d4b5b5);
            box-shadow: 0 4px 10px rgba(170, 130, 130, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.4);
        }

        /* 金色泡泡 - 合理信念 */
        .bubble.belief-positive {
            background: linear-gradient(145deg, #f8df90, #f0c94c);
            box-shadow: 0 4px 12px rgba(200, 150, 40, 0.4), inset 0 2px 0 rgba(255, 255, 255, 0.5);
            color: #5a3a0a;
        }

        /* 金色泡泡 - 正性情绪 */
        .bubble.emotion-positive {
            background: linear-gradient(145deg, #fceba8, #f7d96a);
            box-shadow: 0 4px 12px rgba(200, 150, 30, 0.4), 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;
            }
            30% {
                transform: scale(1.3);
                opacity: 0.7;
            }
            100% {
                transform: scale(0);
                opacity: 0;
            }
        }

        .bubble.collecting {
            animation: collect-gold 0.6s ease-out forwards;
        }

        @keyframes collect-gold {
            0% {
                transform: scale(1);
                opacity: 1;
            }
            50% {
                transform: scale(1.4);
                opacity: 0.9;
                box-shadow: 0 0 30px 20px rgba(255, 200, 50, 0.7);
            }
            100% {
                transform: scale(0);
                opacity: 0;
            }
        }

        .bubble.dropping {
            animation: drop-in 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
        }

        @keyframes drop-in {
            0% {
                transform: translateY(-60px) scale(0.3);
                opacity: 0;
            }
            60% {
                transform: translateY(6px) scale(1.05);
                opacity: 1;
            }
            100% {
                transform: translateY(0) scale(1);
                opacity: 1;
            }
        }

        .bubble.shaking {
            animation: shake 0.4s ease-in-out;
        }

        @keyframes shake {
            0%,
            100% {
                transform: translateX(0);
            }
            25% {
                transform: translateX(-6px);
            }
            50% {
                transform: translateX(6px);
            }
            75% {
                transform: translateX(-3px);
            }
        }

        .floating-tip {
            position: fixed;
            pointer-events: none;
            z-index: 100;
            font-weight: 700;
            font-size: 0.9rem;
            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);
            }
            80% {
                opacity: 0.8;
                transform: translateY(-50px) scale(1);
            }
            100% {
                opacity: 0;
                transform: translateY(-80px) 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);
        }

        .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: 420px;
            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.4rem;
            color: #5a3e2b;
            margin-bottom: 8px;
        }

        .title-badge {
            display: inline-block;
            font-size: 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;
        }

        .score-display {
            font-size: 2.5rem;
            font-weight: 900;
            color: #d4843a;
        }

        .stars-display {
            font-size: 1.8rem;
            letter-spacing: 4px;
            margin: 4px 0 10px;
        }

        .quotes-list {
            text-align: left;
            background: #fefcf8;
            border-radius: 14px;
            padding: 14px;
            margin: 10px 0;
            border: 1px solid #f0e8d8;
        }

        .quotes-list p {
            padding: 6px 0;
            border-bottom: 1px dotted #e8d8c4;
            font-size: 0.8rem;
            color: #5a4a3a;
            line-height: 1.5;
        }

        .quotes-list p:last-child {
            border-bottom: none;
        }

        .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);
        }
    </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">120</span>秒</div>
        </div>

        <div class="event-display" id="eventDisplay">
            <div class="event-label">📋 当前事件 A</div>
            <div class="event-text" id="eventText">小组合作作业,我的建议没被采纳。</div>
            <div class="guide-text" id="guideText">👆 先找到两个<strong>灰色泡泡</strong>配对,再看会出现什么神奇变化!</div>
        </div>

        <div class="bubble-grid" id="bubbleGrid"></div>

        <div class="btn-row">
            <button class="btn btn-hint" id="btnHint">💡 提示(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;
            let score = 0;
            let stars = 0;
            let timeLeft = 120;
            let timerInterval = null;
            let hintUsed = false;
            let selectedBubble = null;
            let isProcessing = false;
            let collectedGoldenPairs = 0;
            let goldenPairsNeeded = 1;
            let allBubbles = [];
            let allEncouragements = [];

            // DOM
            const bubbleGrid = document.getElementById('bubbleGrid');
            const eventText = document.getElementById('eventText');
            const guideText = document.getElementById('guideText');
            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');

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

            function createBubble(eventId, type, chain, text) {
                return { id: generateId(), eventId, type, chain, text, htmlEl: null, gridIndex: -1 };
            }

            function buildBubblePool(level) {
                const current = eventsData[level];
                const pool = [];
                // 当前事件的4个泡泡 (2灰+2金)
                pool.push(createBubble(level, 'belief', 'negative', current.beliefNegative));
                pool.push(createBubble(level, 'emotion', 'negative', current.emotionNegative));
                pool.push(createBubble(level, 'belief', 'positive', current.beliefPositive));
                pool.push(createBubble(level, 'emotion', 'positive', current.emotionPositive));

                // 干扰项:从其他事件中取部分泡泡,但总泡泡数控制在12-14个,网格为4x4=16,剩余用重复当前事件的金色泡泡填充(降低难度)
                const otherEvents = eventsData.filter((_, i) => i !== level);
                const interference = [];
                otherEvents.forEach(ev => {
                    interference.push(createBubble(ev.id, 'belief', 'negative', ev.beliefNegative));
                    interference.push(createBubble(ev.id, 'emotion', 'negative', ev.emotionNegative));
                });
                // 打乱并取前6个作为干扰
                shuffleArray(interference);
                const selectedInterference = interference.slice(0, 6);
                pool.push(...selectedInterference);

                // 如果总数不到16,用当前事件的金色泡泡复制填充(让学生更容易看到正确选项)
                while (pool.length < 16) {
                    pool.push(createBubble(level, 'belief', 'positive', current.beliefPositive));
                    if (pool.length < 16) pool.push(createBubble(level, 'emotion', 'positive', current.emotionPositive));
                }
                // 确保恰好16个
                const finalPool = pool.slice(0, 16);
                shuffleArray(finalPool);
                return finalPool;
            }

            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((b, index) => {
                    const el = document.createElement('div');
                    el.className = 'bubble';
                    el.setAttribute('data-index', index);
                    el.textContent = b.text;
                    if (b.chain === 'negative') {
                        el.classList.add(b.type === 'belief' ? 'belief-negative' : 'emotion-negative');
                    } else {
                        el.classList.add(b.type === 'belief' ? 'belief-positive' : 'emotion-positive');
                    }
                    el.addEventListener('click', () => onBubbleClick(index, el));
                    el.addEventListener('touchend', (e) => {
                        e.preventDefault();
                        onBubbleClick(index, el);
                    });
                    bubbleGrid.appendChild(el);
                    b.htmlEl = el;
                    b.gridIndex = index;
                });
                updateGoldenTracking();
            }

            function updateGoldenTracking() {
                const goldenBeliefs = allBubbles.filter(b => b.eventId === currentLevel && b.chain === 'positive' && b
                    .type === 'belief').length;
                goldenPairsNeeded = goldenBeliefs;
                collectedGoldenPairs = 0;
            }

            function onBubbleClick(index, el) {
                if (isProcessing) return;
                if (el.classList.contains('bursting') || el.classList.contains('collecting') || el.classList.contains(
                        'dropping')) return;
                const bubble = allBubbles[index];
                if (!bubble) return;

                if (selectedBubble && selectedBubble.id === bubble.id) {
                    deselectBubble();
                    return;
                }

                if (!selectedBubble) {
                    selectBubble(bubble, el);
                    return;
                }

                const first = selectedBubble;
                const firstEl = first.htmlEl;
                const second = bubble;
                const secondEl = el;

                if (first.eventId === second.eventId && first.type !== second.type && first.chain === second.chain) {
                    isProcessing = true;
                    deselectBubbleSilent();
                    if (first.chain === 'negative') {
                        handleNegativePair(first, second, firstEl, secondEl);
                    } else {
                        handlePositivePair(first, second, firstEl, secondEl);
                    }
                } else {
                    firstEl.classList.add('shaking');
                    secondEl.classList.add('shaking');
                    deselectBubbleSilent();
                    setTimeout(() => {
                        firstEl.classList.remove('shaking');
                        secondEl.classList.remove('shaking');
                    }, 400);
                }
            }

            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(b1, b2, el1, el2) {
                el1.classList.add('bursting');
                el2.classList.add('bursting');
                const midX = (el1.getBoundingClientRect().left + el2.getBoundingClientRect().right) / 2;
                const midY = (el1.getBoundingClientRect().top + el2.getBoundingClientRect().bottom) / 2;
                showFloatingTip(midX, midY, '💡 试试换个想法,心情会不一样哦!');

                const current = eventsData[currentLevel];
                setTimeout(() => {
                    removeBubbleById(b1.id);
                    removeBubbleById(b2.id);
                    const newB = createBubble(currentLevel, 'belief', 'positive', current.beliefPositive);
                    const newE = createBubble(currentLevel, 'emotion', 'positive', current.emotionPositive);
                    allBubbles = allBubbles.filter(b => b !== null);
                    allBubbles.push(newB);
                    allBubbles.push(newE);
                    // 保持总数不超过16
                    while (allBubbles.length > 16) {
                        const idx = allBubbles.findIndex(b => b.eventId !== currentLevel && b.chain === 'negative');
                        if (idx >= 0) allBubbles.splice(idx, 1);
                        else {
                            const ri = allBubbles.findIndex(b => b.eventId !== currentLevel);
                            if (ri >= 0) allBubbles.splice(ri, 1);
                            else break;
                        }
                    }
                    renderGrid();
                    setTimeout(() => {
                        allBubbles.forEach(b => {
                            if (b.htmlEl && b.eventId === currentLevel && b.chain === 'positive' && !b.htmlEl
                                .classList.contains('collecting')) {
                                b.htmlEl.classList.add('dropping');
                            }
                        });
                        updateGoldenTracking();
                        checkLevelComplete();
                        isProcessing = false;
                    }, 80);
                }, 500);
            }

            function handlePositivePair(b1, b2, el1, el2) {
                el1.classList.add('collecting');
                el2.classList.add('collecting');
                const current = eventsData[currentLevel];
                const midX = (el1.getBoundingClientRect().left + el2.getBoundingClientRect().right) / 2;
                const midY = (el1.getBoundingClientRect().top + el2.getBoundingClientRect().bottom) / 2;
                showFloatingTip(midX, midY, current.encouragement);
                score += 10;
                stars += 1;
                collectedGoldenPairs++;
                if (!allEncouragements.includes(current.encouragement)) allEncouragements.push(current.encouragement);
                updateUI();
                setTimeout(() => {
                    removeBubbleById(b1.id);
                    removeBubbleById(b2.id);
                    allBubbles = allBubbles.filter(b => b !== null);
                    renderGrid();
                    updateGoldenTracking();
                    checkLevelComplete();
                    isProcessing = false;
                }, 550);
            }

            function removeBubbleById(id) {
                const idx = allBubbles.findIndex(b => b && b.id === id);
                if (idx >= 0) allBubbles[idx] = 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.remove(); }, 2600);
            }

            function checkLevelComplete() {
                updateGoldenTracking();
                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); }, 700);
                    } else {
                        setTimeout(() => showFinalModal(), 600);
                    }
                }
            }

            function startTimer() {
                clearInterval(timerInterval);
                timeLeft = 120;
                updateTimerDisplay();
                timerInterval = setInterval(() => {
                    timeLeft--;
                    updateTimerDisplay();
                    if (timeLeft <= 0) {
                        clearInterval(timerInterval);
                        handleTimeUp();
                    }
                }, 1000);
            }

            function updateTimerDisplay() {
                timerDisplay.textContent = timeLeft;
                timerDisplay.classList.toggle('timer-warning', timeLeft <= 20);
            }

            function handleTimeUp() {
                isProcessing = true;
                clearInterval(timerInterval);
                const rect = bubbleGrid.getBoundingClientRect();
                showFloatingTip(rect.left + rect.width / 2, rect.top + rect.height / 2, '⏰ 时间到!重新挑战本关吧~');
                setTimeout(() => { isProcessing = false;
                    startLevel(currentLevel); }, 1500);
            }

            function startLevel(level) {
                currentLevel = level;
                clearInterval(timerInterval);
                isProcessing = false;
                selectedBubble = null;
                hintUsed = false;
                collectedGoldenPairs = 0;
                btnHint.disabled = false;
                btnHint.textContent = '💡 提示(1次)';
                const ev = eventsData[level];
                eventText.textContent = ev.eventA;
                guideText.innerHTML = '👆 先找到两个<strong>灰色泡泡</strong>配对,再看会出现什么神奇变化!';
                levelDisplay.textContent = (level + 1) + '/5';
                updateUI();
                allBubbles = buildBubblePool(level);
                renderGrid();
                updateGoldenTracking();
                timerDisplay.classList.remove('timer-warning');
                updateTimerDisplay();
                startTimer();
            }

            function updateUI() {
                scoreDisplay.textContent = score;
                starDisplay.textContent = stars;
            }

            btnHint.addEventListener('click', () => {
                if (hintUsed || isProcessing) return;
                hintUsed = true;
                btnHint.disabled = true;
                btnHint.textContent = '💡 已使用';
                const gb = allBubbles.find(b => b && b.eventId === currentLevel && b.chain === 'positive' && b.type ===
                    'belief');
                const ge = allBubbles.find(b => b && b.eventId === currentLevel && b.chain === 'positive' && b.type ===
                    'emotion');
                if (gb && gb.htmlEl) gb.htmlEl.classList.add('hint-glow');
                if (ge && ge.htmlEl) ge.htmlEl.classList.add('hint-glow');
                setTimeout(() => {
                    if (gb && gb.htmlEl) gb.htmlEl.classList.remove('hint-glow');
                    if (ge && ge.htmlEl) ge.htmlEl.classList.remove('hint-glow');
                }, 3000);
            });

            btnReset.addEventListener('click', () => {
                if (isProcessing) return;
                startLevel(currentLevel);
            });

            function showFinalModal() {
                clearInterval(timerInterval);
                isProcessing = true;
                let badgeClass, titleName;
                if (score >= 80) { badgeClass = 'badge-diamond';
                    titleName = '💎 钻石心灵大师'; } else if (score >= 50) { badgeClass = 'badge-gold';
                    titleName = '🥇 黄金炼心师'; } else { badgeClass = 'badge-bronze';
                    titleName = '🥉 青铜净化师'; }
                const quotes = [...new Set([...allEncouragements, ...globalQuotes])];
                modalContent.innerHTML = `
                    <h2>🎉 通关成功!</h2>
                    <div class="title-badge ${badgeClass}">${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>📝 心灵金句合集:</strong>
                        ${quotes.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;
                allEncouragements = [];
                isProcessing = false;
                selectedBubble = null;
                hintUsed = false;
                btnHint.disabled = false;
                btnHint.textContent = '💡 提示(1次)';
                clearInterval(timerInterval);
                updateUI();
                startLevel(0);
                modalOverlay.style.display = 'none';
            }

            modalOverlay.addEventListener('click', (e) => {
                if (e.target === modalOverlay) resetGame();
            });

            function initGame() {
                resetGame();
            }

            initGame();
            console.log('🫧 情绪泡泡龙优化版已就绪!网格4x4,更容易找到配对。');
        })();
    </script>
</body>
</html>

Game Source: 情绪泡泡龙——ABC消消乐大作战(优化版)

Creator: LaserPirate42

Libraries: none

Complexity: complex (1002 lines, 37.4 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-mpcrtsj2" to link back to the original. Then publish at arcadelab.ai/publish.