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>