SOURCE

console 命令行工具 X clear

                    
>
console
<!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>
*{
    box-sizing:border-box;
    font-family:"Microsoft Yahei",sans-serif;
    margin:0;
    padding:0;
}
body{
    background: linear-gradient(140deg,#e8f7ff,#fff9e8,#ffeef0);
    min-height:100vh;
    overflow:hidden;
}
.wrap{
    width:100vw;
    height:100vh;
    position:relative;
}
.top-bar{
    position:absolute;
    top:12px;
    left:50%;
    transform:translateX(-50%);
    width:92%;
    max-width:1000px;
    display:flex;
    justify-content:space-between;
    align-items:center;
    background:#fff;
    padding:12px 20px;
    border-radius:16px;
    box-shadow:0 3px 14px #00000024;
    z-index:20;
}
.team-info{
    font-size:20px;
    font-weight:bold;
    display:flex;
    align-items:center;
    gap:8px;
}
.red-text{color:#d93025;}
.blue-text{color:#0277bd;}
.score-num{
    padding:3px 12px;
    border-radius:10px;
    border:2px solid currentColor;
    min-width:46px;
    text-align:center;
}
.timer{
    font-size:28px;
    font-weight:bold;
    color:#ff5722;
}
.track-wrap{
    position:absolute;
    top:75px;
    left:50%;
    transform:translateX(-50%);
    width:90%;
    height:56px;
    display:flex;
    align-items:center;
    gap:12px;
    z-index:18;
}
.track{
    flex:1;
    height:38px;
    border-radius:20px;
    position:relative;
    overflow:hidden;
}
.track-red{
    background:rgba(255,180,180,0.55);
    border:2px solid #d93025;
}
.track-blue{
    background:rgba(180,220,255,0.55);
    border:2px solid #0277bd;
}
.bear{
    position:absolute;
    left:4px;
    top:50%;
    transform:translateY(-50%);
    font-size:32px;
    transition:left 0.45s ease-out;
}
.flag{
    position:absolute;
    right:6px;
    top:50%;
    transform:translateY(-50%);
    font-size:26px;
}
.q-card{
    position:absolute;
    top:145px;
    left:50%;
    transform:translateX(-50%);
    background:#ffffff;
    padding:10px 24px;
    border-radius:14px;
    box-shadow:0 3px 12px #0003;
    text-align:center;
    z-index:20;
}
.q-title{
    font-size:16px;
    margin-bottom:4px;
    color:#444;
}
.q-desc{
    font-size:18px;
    font-weight:bold;
}
.area{
    position:absolute;
    top:210px;
    bottom:90px;
    width:50%;
}
.area-left{
    left:0;
    background:rgba(255,215,215,0.35);
    border-right:2px dashed #ffb8b8;
}
.area-right{
    right:0;
    background:rgba(215,238,255,0.35);
    border-left:2px dashed #b2d8ff;
}
.area-label{
    position:absolute;
    top:8px;
    font-size:16px;
    font-weight:bold;
    padding:5px 12px;
    border-radius:9px;
    color:#fff;
    z-index:5;
}
.area-left .area-label{
    left:10px;
    background:#d93025;
}
.area-right .area-label{
    right:10px;
    background:#0277bd;
}
.option-item{
    position:absolute;
    padding:8px 14px;
    line-height:1.4;
    text-align:center;
    border-radius:16px;
    background:#34495e;
    color:#fff;
    font-size:14px;
    font-weight:bold;
    cursor:pointer;
    box-shadow:0 3px 10px #0003;
    user-select:none;
    z-index:10;
    transition:0.15s;
    white-space:nowrap;
}
.option-item:hover{
    transform:scale(1.03);
}
.option-item.right{
    background:#27ae60;
    animation:pop 0.45s ease;
}
.option-item.wrong{
    background:#e74c3c;
    animation:shake 0.4s ease;
}
@keyframes shake{
    0%,100%{transform:translateX(0);}
    20%{transform:translateX(-6px);}
    40%{transform:translateX(6px);}
    60%{transform:translateX(-6px);}
    80%{transform:translateX(6px);}
}
@keyframes pop{
    0%{transform:scale(1);}
    50%{transform:scale(1.2);}
    100%{transform:scale(1);}
}
.confetti{
    position:absolute;
    width:10px;height:10px;
    border-radius:2px;
    opacity:0;
    z-index:30;
    animation:fall 1.3s forwards;
}
@keyframes fall{
    0%{transform:translateY(-20px) rotate(0deg);opacity:1;}
    100%{transform:translateY(320px) rotate(400deg);opacity:0;}
}
.win-confetti{
    position:fixed;
    width:14px;height:14px;
    border-radius:3px;
    opacity:0;
    z-index:40;
    animation:bigFall 2.2s forwards;
}
@keyframes bigFall{
    0%{transform:translateY(-30px) rotate(0deg);opacity:1;}
    100%{transform:translateY(100vh) rotate(720deg);opacity:0;}
}
.feedback{
    position:absolute;
    top:52%;
    left:50%;
    transform:translate(-50%,-50%);
    font-size:26px;
    font-weight:bold;
    padding:12px 26px;
    border-radius:14px;
    color:white;
    z-index:25;
    display:none;
}
.feedback.ok{background:#27ae60;}
.feedback.no{background:#c0392b;}
.win-modal{
    position:fixed;
    inset:0;
    background:rgba(0,0,0,0.45);
    display:none;
    align-items:center;
    justify-content:center;
    z-index:50;
}
.win-box{
    background:#fff;
    padding:30px 45px;
    border-radius:20px;
    text-align:center;
}
.win-box h2{
    font-size:34px;
    margin-bottom:16px;
}
.win-box button{
    border:none;
    padding:10px 22px;
    border-radius:10px;
    background:#2980b9;
    color:#fff;
    font-size:16px;
    cursor:pointer;
}
.ctrl-bar{
    position:absolute;
    bottom:16px;
    left:50%;
    transform:translateX(-50%);
    display:flex;
    gap:12px;
    z-index:20;
}
.ctrl-bar button{
    border:none;
    padding:10px 20px;
    border-radius:10px;
    font-size:16px;
    font-weight:bold;
    color:#fff;
    cursor:pointer;
    box-shadow:0 2px 8px #0003;
}
#startBtn{background:#27ae60;}
#nextBtn{background:#2980b9;}
#resetBtn{background:#7f8c8d;}
button:disabled{opacity:0.5;cursor:not-allowed;}
</style>
</head>
<body>
<div class="wrap">
    <div class="top-bar">
        <div class="team-info red-text">🔴红队 <span class="score-num" id="redScore">0</span></div>
        <div class="timer" id="timer">25</div>
        <div class="team-info blue-text"><span class="score-num" id="blueScore">0</span> 蓝队🔵</div>
    </div>

    <div class="track-wrap">
        <div class="track track-red">
            <div class="bear" id="bearRed">🐻</div>
            <div class="flag">🏁</div>
        </div>
        <div class="track track-blue">
            <div class="bear" id="bearBlue">🐻</div>
            <div class="flag">🏁</div>
        </div>
    </div>

    <div class="q-card">
        <div class="q-title">选择题</div>
        <div class="q-desc" id="qText">点击开始游戏</div>
    </div>

    <div class="area area-left">
        <div class="area-label">红队答题区</div>
    </div>
    <div class="area area-right">
        <div class="area-label">蓝队答题区</div>
    </div>

    <div class="feedback" id="feedbackBox"></div>

    <div class="ctrl-bar">
        <button id="startBtn">开始游戏</button>
        <button id="nextBtn" disabled>下一题</button>
        <button id="resetBtn" onclick="location.reload()">重新开局</button>
    </div>
</div>

<div class="win-modal" id="winModal">
    <div class="win-box">
        <h2 id="winText"></h2>
        <button onclick="closeWin()">再来一局</button>
    </div>
</div>

<script>
const questionList = [
    {
        q:"无脊椎动物的分类",
        answerSet:new Set(["刺胞动物","线虫动物","环节动物","扁形动物","节肢动物","棘皮动物","软体动物"]),
        options:["刺胞动物","线虫动物","环节动物","扁形动物","节肢动物","棘皮动物","软体动物","两栖动物","爬行动物"]
    },
    {
        q:"下列属于刺胞动物的是",
        answerSet:new Set(["海葵"]),
        options:["海葵","蛔虫","蛲虫","螃蟹","蜗牛","沙蚕","海尾蛇"]
    },
    {
        q:"节肢动物的特点有哪些?",
        answerSet:new Set(["体表有坚硬的外骨骼","身体和附肢都分节"]),
        options:["体表有坚硬的外骨骼","身体和附肢都分节","身体由许多形态相似的体节组成","体表有角质层"]
    },
    {
        q:"刺胞动物的特点有哪些?",
        answerSet:new Set(["身体呈辐射对称","有口无肛门"]),
        options:["身体呈辐射对称","身体呈两侧对称","有口无肛门","有口有肛门","身体呈圆柱形","身体由许多相似的体节组成"]
    },
    {
        q:"软体动物都有哪些?",
        answerSet:new Set(["蜗牛","章鱼","鲍鱼"]),
        options:["蜗牛","章鱼","鲍鱼","水母","海胆","海星","血吸虫"]
    },
    {
        q:"环节动物有哪些特征?",
        answerSet:new Set(["身体由许多相似的体节组成","身体呈圆筒形","多靠刚毛或疣足辅助运动"]),
        options:["身体呈圆柱形","体表有角质层","身体由许多相似的体节组成","身体呈圆筒形","多靠刚毛或疣足辅助运动"]
    }
];

const totalQuestion = 6;
const stepMax = 8;
// 0.6cm ≈ 23px(96DPI标准屏幕换算),直接设置选项间隔
const gapCm06 = 23;

let qIndex = 0;
let redStep = 0;
let blueStep = 0;
let timeLeft = 25;
let timerId = null;
let gameActive = false;
let redPicked = new Set();
let bluePicked = new Set();

const qText = document.getElementById("qText");
const redScoreEl = document.getElementById("redScore");
const blueScoreEl = document.getElementById("blueScore");
const timerEl = document.getElementById("timer");
const feedbackBox = document.getElementById("feedbackBox");
const startBtn = document.getElementById("startBtn");
const nextBtn = document.getElementById("nextBtn");
const leftArea = document.querySelector(".area-left");
const rightArea = document.querySelector(".area-right");
const bearRed = document.getElementById("bearRed");
const bearBlue = document.getElementById("bearBlue");
const winModal = document.getElementById("winModal");
const winText = document.getElementById("winText");

startBtn.onclick = startGame;
nextBtn.onclick = nextQuestion;

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

function startGame(){
    qIndex = 0;
    redStep = 0;
    blueStep = 0;
    updateBearPos();
    updateScore();
    nextQuestion();
}

function nextQuestion(){
    clearInterval(timerId);
    feedbackBox.style.display = "none";
    nextBtn.disabled = true;
    gameActive = false;
    redPicked.clear();
    bluePicked.clear();
    leftArea.querySelectorAll(".option-item").forEach(d=>d.remove());
    rightArea.querySelectorAll(".option-item").forEach(d=>d.remove());

    if(qIndex >= totalQuestion){
        checkFinalWin();
        return;
    }

    const cur = questionList[qIndex];
    qText.innerText = cur.q;

    const leftOpts = shuffle(cur.options);
    const rightOpts = shuffle(cur.options);

    spawnOptions(leftArea, leftOpts, cur, "red");
    spawnOptions(rightArea, rightOpts, cur, "blue");

    startCountdown();
    gameActive = true;
}

function spawnOptions(wrap, optList, question, side){
    const areaW = wrap.clientWidth;
    const areaH = wrap.clientHeight;
    const placedBoxes = [];
    const targetYBase = areaH - 50;

    optList.forEach((text, idx)=>{
        const div = document.createElement("div");
        div.className = "option-item";
        div.innerText = text;
        wrap.appendChild(div);
        div.style.visibility = "hidden";
        const w = div.offsetWidth;
        const h = div.offsetHeight;

        let ok = false;
        let tryCount = 0;
        let targetX;
        while(!ok && tryCount < 400){
            targetX = 15 + Math.random()*(areaW - w - 30);
            ok = true;
            for(let b of placedBoxes){
                // 严格隔开 0.6cm 的空隙
                if( !(targetX + w + gapCm06 < b.x || targetX > b.x + b.w + gapCm06) ){
                    ok = false;
                    break;
                }
            }
            tryCount++;
        }
        placedBoxes.push({x:targetX,y:targetYBase,w:w,h:h});

        div.style.left = targetX + "px";
        div.style.top = "-60px";
        div.style.visibility = "visible";

        const delay = idx * 700 + (side === "blue" ? 250 : 0);
        setTimeout(()=>{
            if(!gameActive) return;
            startFall(div, targetYBase, text, question, side);
        }, delay);
    });
}

function startFall(el, targetY, text, question, side){
    let y = -60;
    const fallSpeed = 0.75;
    const fallTimer = setInterval(()=>{
        if(!gameActive){
            clearInterval(fallTimer);
            el.remove();
            return;
        }
        y += fallSpeed;
        el.style.top = y + "px";
        if(y >= targetY){
            clearInterval(fallTimer);
            // 落到底部之后停留片刻消失,中途不会消失
            setTimeout(()=>{
                el.remove();
            },1100);
        }
    },30);

    el.onclick = ()=>{
        if(!gameActive) return;
        const pickedSet = side === "red" ? redPicked : bluePicked;
        if(pickedSet.has(text)) return;

        pickedSet.add(text);
        if(question.answerSet.has(text)){
            el.classList.add("right");
            playSuccessSound();
            createConfetti(side === "red" ? leftArea : rightArea);
            if(side === "red") redStep++;
            else blueStep++;
            updateBearPos();
            updateScore();
            checkBearWin();
        }else{
            el.classList.add("wrong");
            playErrorSound();
        }
    }
}

function updateBearPos(){
    const trackW = document.querySelector(".track-red").clientWidth - 40;
    const redPercent = redStep / stepMax;
    const bluePercent = blueStep / stepMax;
    bearRed.style.left = (redPercent * trackW + 4) + "px";
    bearBlue.style.left = (bluePercent * trackW + 4) + "px";
}

function checkBearWin(){
    if(redStep >= stepMax){
        showWin("🔴红队小熊率先到达终点!红队获胜🎉");
    }else if(blueStep >= stepMax){
        showWin("🔵蓝队小熊率先到达终点!蓝队获胜🎉");
    }
}

function startCountdown(){
    timeLeft = 25;
    timerEl.innerText = timeLeft;
    clearInterval(timerId);
    timerId = setInterval(()=>{
        timeLeft--;
        timerEl.innerText = timeLeft;
        if(timeLeft <= 0){
            clearInterval(timerId);
            if(gameActive){
                gameActive = false;
                showFeedback("⏰时间结束!",false);
                setTimeout(()=>{
                    qIndex++;
                    nextBtn.disabled = false;
                },700);
            }
        }
    },1000);
}

function checkFinalWin(){
    if(redStep>blueStep){
        showWin("🔴红队总分更高,红队获胜!");
    }else if(blueStep>redStep){
        showWin("🔵蓝队总分更高,蓝队获胜!");
    }else{
        showWin("⚖️两队平分,平局!");
    }
}

function showWin(text){
    winText.innerText = text;
    winModal.style.display = "flex";
    for(let i=0;i<80;i++){
        const c = document.createElement("div");
        c.className = "win-confetti";
        c.style.left = Math.random()*100+"%";
        c.style.background = ["#e74c3c","#3498db","#f1c40f","#2ecc71","#9b59b6"][Math.floor(Math.random()*5)];
        document.body.appendChild(c);
        setTimeout(()=>c.remove(), 2200);
    }
}
function closeWin(){
    winModal.style.display = "none";
}

function showFeedback(txt, isOk){
    feedbackBox.innerText = txt;
    feedbackBox.className = "feedback " + (isOk?"ok":"no");
    feedbackBox.style.display = "block";
}
function updateScore(){
    redScoreEl.innerText = redStep;
    blueScoreEl.innerText = blueStep;
}

function createConfetti(container){
    const colorArr = ["#e74c3c","#3498db","#f1c40f","#2ecc71","#9b59b6"];
    for(let i=0;i<25;i++){
        const c = document.createElement("div");
        c.className = "confetti";
        c.style.left = (Math.random()*container.clientWidth)+"px";
        c.style.background = colorArr[Math.floor(Math.random()*colorArr.length)];
        container.appendChild(c);
        setTimeout(()=>c.remove(),1300);
    }
}

function playSuccessSound(){
    const ac = new AudioContext();
    const osc = ac.createOscillator();
    const gain = ac.createGain();
    osc.connect(gain);
    gain.connect(ac.destination);
    osc.type = "sine";
    gain.gain.setValueAtTime(0,ac.currentTime);
    gain.gain.linearRampToValueAtTime(0.3,ac.currentTime+0.05);
    osc.frequency.setValueAtTime(523,ac.currentTime);
    osc.frequency.setValueAtTime(659,ac.currentTime+0.12);
    osc.frequency.setValueAtTime(784,ac.currentTime+0.24);
    gain.gain.exponentialRampToValueAtTime(0.01,ac.currentTime+0.45);
    osc.start();
    osc.stop(ac.currentTime+0.45);
}
function playErrorSound(){
    const ac = new AudioContext();
    const osc = ac.createOscillator();
    const gain = ac.createGain();
    osc.connect(gain);
    gain.connect(ac.destination);
    osc.type = "sawtooth";
    gain.gain.setValueAtTime(0,ac.currentTime);
    gain.gain.linearRampToValueAtTime(0.28,ac.currentTime+0.05);
    osc.frequency.setValueAtTime(220,ac.currentTime);
    osc.frequency.setValueAtTime(140,ac.currentTime+0.18);
    gain.gain.exponentialRampToValueAtTime(0.01,ac.currentTime+0.35);
    osc.start();
    osc.stop(ac.currentTime+0.35);
}
</script>
</body>
</html>