Código · 交通工具与动词搭配

CC BY 4.0 · acredita a @Sauyeon · v1

← Detalles del juego

Tómalo, remezcla y enseña con él

Copia el código de abajo en ChatGPT, Claude, Gemini, Grok —la herramienta que quieras—, cámbialo para tu clase y publica tu remezcla aquí con crédito.

⬇ Descargar HTML ⬇ Descargar ZIP
Inicio de indicación de edición con IA
Edit this classroom game for interactive whiteboards. Keep large touch targets and whole-class play. Tracking, logins, personal-data collection, and remote API calls are prohibited. Preserve its single-HTML structure. Preserve the educational goal. Original creator: @Sauyeon on ClassBoard Games (CC BY 4.0).
game-5.html · 25.3 KB
<!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>
:root{
  --bg:#f4f8f6; --panel:#ffffff; --green:#4f8f75; --green2:#eaf5ef;
  --blue:#4f7fa8; --blue2:#edf5fb; --orange:#e78a4a; --red:#d85b5b;
  --ink:#17221d; --muted:#65736c; --line:#dbe6df; --shadow:0 8px 24px rgba(34,68,51,.08);
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0; font-family:"Malgun Gothic","Noto Sans KR","Noto Sans SC","Microsoft YaHei",sans-serif;
  background:linear-gradient(180deg,#f7fbf9 0%,#f1f6f4 100%);
  color:var(--ink); min-height:100vh; overflow-y:auto;
}
button,input{font:inherit}
button{cursor:pointer}
.app{width:min(1500px,98vw); margin:0 auto; padding:8px 10px 24px}
.topbar{
  display:grid; grid-template-columns:1fr auto auto; gap:10px; align-items:center;
  background:rgba(255,255,255,.94); border:2px solid var(--line); border-radius:18px;
  box-shadow:var(--shadow); padding:8px 12px; position:sticky; top:4px; z-index:30;
}
.title h1{margin:0; font-size:clamp(24px,2.2vw,40px); color:#2e6f55; line-height:1}
.title .rule{margin-top:4px; font-size:clamp(13px,1.08vw,18px); color:var(--muted); font-weight:800}
.stats{display:flex; gap:8px; flex-wrap:wrap; justify-content:flex-end}
.stat{
  background:#f7faf8; border:2px solid var(--line); border-radius:13px; padding:5px 10px;
  min-width:92px; text-align:center; font-weight:900; font-size:clamp(13px,1vw,17px)
}
.stat strong{display:block; font-size:clamp(21px,1.8vw,31px)}
.timer{color:#b84a4a; background:#fff4f3; border-color:#f0c8c6}
.toolbar{display:flex; gap:6px; flex-wrap:wrap; justify-content:flex-end}
.btn{
  border:none; border-radius:12px; padding:8px 12px; font-weight:900;
  background:#edf2ef; color:#24342c; box-shadow:0 3px 9px rgba(0,0,0,.05);
}
.btn.primary{background:var(--green); color:white}
.btn.blue{background:var(--blue); color:white}
.btn.warn{background:#f1b15d; color:#412d14}
.btn.red{background:#d96b6b; color:white}
.btn:active{transform:translateY(1px)}
.modebar{
  display:flex; gap:8px; align-items:center; justify-content:center; flex-wrap:wrap;
  padding:8px 0 6px;
}
.modebtn{
  border:2px solid var(--line); background:white; color:#34473d; border-radius:999px;
  padding:7px 16px; font-weight:900; font-size:clamp(14px,1.05vw,18px)
}
.modebtn.active{background:#2f7157;color:white;border-color:#2f7157}
.board{display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:2px}
.zone{
  min-height:220px; border-radius:18px; padding:8px; border:3px dashed;
  display:flex; flex-direction:column; background:white;
}
.zone.zuo{border-color:#86b99e;background:#f5fbf7}
.zone.qi{border-color:#8eb8d9;background:#f6fbff}
.zone.dragover{transform:scale(1.008); box-shadow:0 0 0 5px rgba(82,145,111,.12)}
.zonehead{
  display:flex; align-items:baseline; justify-content:center; gap:8px; margin-bottom:5px;
  font-weight:900
}
.zonehead .han{font-size:clamp(34px,3.2vw,52px)}
.zonehead .py{font-size:clamp(17px,1.5vw,24px); color:var(--orange)}
.zonehead .kr{font-size:clamp(14px,1.15vw,19px); color:var(--muted)}
.zuo .han{color:#34765a}.qi .han{color:#3f77a5}
.matched{
  display:flex; flex-wrap:wrap; gap:7px; justify-content:center; align-content:flex-start;
  min-height:150px
}
.chip{
  display:flex; align-items:center; gap:7px; background:white; border:2px solid var(--line);
  border-radius:14px; padding:6px 9px; box-shadow:0 3px 9px rgba(0,0,0,.05);
  min-width:150px; justify-content:center; animation:pop .22s ease-out
}
.chip.zuo{border-color:#9cc8b1;background:#edf7f1}
.chip.qi{border-color:#9dc2df;background:#edf6fc}
.chip .icon{font-size:28px}
.chip .text{line-height:1.05;text-align:left}
.chip .py{font-size:14px;color:#d46e38;font-weight:900}

.chip .zh{font-size:24px;font-weight:1000;white-space:nowrap;display:flex;align-items:center;gap:2px}
.chip.zuo .verb{color:#2e7155;}
.chip.qi .verb{color:#376f9a;}
.chip .noun{color:#1f2924;}

.chip .kr{font-size:13px;color:#59665f;font-weight:800}
.source{
  margin-top:10px; background:white; border:2px solid var(--line); border-radius:18px;
  box-shadow:var(--shadow); padding:8px 10px;
}
.sourcehead{
  display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:6px;
  font-weight:900; color:#3d4d45; font-size:clamp(14px,1.1vw,18px)
}

.cards{
  display:grid; grid-template-columns:repeat(3, 1fr); gap:10px;
}
.card{
  border:2px solid #b9ccd2; border-radius:15px; background:#fbfdfc; padding:10px 12px;
  min-height:78px; display:flex; align-items:center; justify-content:center; gap:10px;
  box-shadow:0 3px 8px rgba(0,0,0,.05); cursor:grab; transition:.15s; font-weight:1000;
}
.card:hover{transform:translateY(-2px)}
.card.selected{outline:4px solid #f2b35f; border-color:#d98a22}
.card.disabled{opacity:.42; pointer-events:none}
.card .icon{font-size:36px}
.card .txt{line-height:1.1;text-align:left}
.card .py{font-size:15px;color:#d46e38;font-weight:1000}
.card .zh{font-size:26px;font-weight:1000;white-space:nowrap}
.card .kr{font-size:14px;color:#5d6963;font-weight:1000}

.relay{
  margin-top:10px; display:none; background:white; border:2px solid var(--line); border-radius:18px;
  box-shadow:var(--shadow); padding:9px 12px; text-align:center
}
.relay.active{display:block}
.relay-label{font-weight:900;color:#5d6a63;font-size:15px}
.relay-card{
  margin:5px auto 7px; width:min(520px,96%); min-height:112px; border:3px solid #b8d5c5;
  background:#f7fcf9; border-radius:20px; display:flex; align-items:center; justify-content:center; gap:18px;
}
.relay-card .icon{font-size:52px}
.relay-card .py{font-size:20px;color:#d46e38;font-weight:1000}
.relay-card .zh{font-size:40px;font-weight:1000}
.relay-card .kr{font-size:17px;color:#627069;font-weight:1000}
.relay-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px;width:min(720px,96%);margin:0 auto}
.answerbtn{
  border:none;border-radius:16px;padding:12px 8px;font-weight:1000;font-size:clamp(24px,2.1vw,36px)
}
.answerbtn.zuo{background:#dff1e7;color:#2e7155;border:3px solid #88b99e}
.answerbtn.qi{background:#e4f1fa;color:#376f9a;border:3px solid #8fb8d8}
.feedback{
  min-height:42px;display:flex;align-items:center;justify-content:center;
  font-size:clamp(22px,2vw,32px);font-weight:1000;margin-top:3px
}
.feedback.good{color:#2f7a58}.feedback.bad{color:#c44f4f}
.footerbar{display:flex;justify-content:center;gap:8px;flex-wrap:wrap;margin-top:10px}
.hidden{display:none!important}
.countdown{
  position:fixed;inset:0;background:rgba(18,35,27,.76);display:none;align-items:center;justify-content:center;
  z-index:1000;color:white;font-weight:1000;font-size:min(26vw,260px);text-shadow:0 10px 30px rgba(0,0,0,.35)
}
.countdown.show{display:flex}
.finishbar{
  display:none;margin-top:10px;background:#fff8e8;border:2px solid #ecd29b;border-radius:16px;
  padding:10px 14px;text-align:center;font-weight:900;font-size:clamp(17px,1.3vw,22px)
}
.finishbar.show{display:block}
.finishbar strong{color:#a34b3f}
.editor{
  position:fixed;right:10px;top:80px;width:min(540px,94vw);max-height:82vh;overflow:auto;
  z-index:200;background:white;border:2px solid var(--line);border-radius:18px;box-shadow:0 16px 45px rgba(0,0,0,.18);
  padding:12px;display:none
}
.editor.show{display:block}
.editor h2{margin:0 0 4px;font-size:24px}
.editor p{margin:0 0 9px;color:#66736c;font-weight:700;font-size:14px}
.editgrid{display:grid;grid-template-columns:44px 1fr 1fr 1fr 72px;gap:5px;align-items:center}
.editgrid input{
  width:100%;border:1.5px solid #cfdcd5;border-radius:9px;padding:6px 7px;font-weight:800
}
.editgrid .head{font-size:12px;color:#66736c;text-align:center;font-weight:900}
.editor-actions{display:flex;gap:7px;justify-content:flex-end;margin-top:10px;flex-wrap:wrap}
.note{font-size:12px;color:#748078;margin-top:7px}
.hide-pinyin .py{display:none!important}
.hide-korean .kr{display:none!important}

.result-overlay{
  position:fixed; inset:0; background:rgba(18,35,27,.82); backdrop-filter:blur(6px);
  z-index:1200; display:none; align-items:center; justify-content:center; padding:15px;
}
.result-overlay.show{display:flex}
.result-inner{
  background:linear-gradient(180deg,#ffffff 0%,#f4f9f6 100%);
  border-radius:24px; padding:24px; width:min(860px,96vw); max-height:90vh; overflow-y:auto;
  box-shadow:0 20px 50px rgba(0,0,0,.3); text-align:center;
}
.result-title{
  font-size:clamp(28px,3.5vw,48px); font-weight:1000; color:#2f7157; margin-bottom:4px;
}
.result-label{
  font-size:clamp(16px,1.5vw,22px); font-weight:900; color:#5b6962; margin-bottom:2px;
}
.result-time{
  font-size:clamp(48px,8vw,90px); line-height:1; font-weight:1000; color:#b84a4a;
  letter-spacing:.02em; margin-bottom:15px;
}
.result-review{
  background:white; border:2px solid var(--line); border-radius:18px; padding:12px 14px; margin-bottom:18px;
}
.result-review-title{
  font-size:16px; font-weight:1000; color:#3d4d45; margin-bottom:10px; text-align:left;
}
.result-grid{
  display:grid; grid-template-columns:1fr 1fr; gap:10px;
}
.result-col{
  border-radius:14px; padding:8px; display:flex; flex-direction:column; gap:6px; min-height:100px;
}
.result-col.zuo{background:#f0f7f3; border:1px solid #a3d0b8}
.result-col.qi{background:#f0f6fa; border:1px solid #a4c7e5}
.result-col-head{font-weight:1000; font-size:18px; margin-bottom:4px}
.result-col.zuo .result-col-head{color:#2e7155}
.result-col.qi .result-col-head{color:#376f9a}

.result-close{
  font-size:clamp(16px,1.4vw,22px); padding:10px 28px; width:100%; max-width:320px;
}

@keyframes pop{0%{transform:scale(.85);opacity:.4}100%{transform:scale(1);opacity:1}}
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-9px)}75%{transform:translateX(9px)}}
@media(max-width:850px){
  .cards{grid-template-columns:repeat(2, 1fr)}
  .result-grid{grid-template-columns:1fr}
}
@media(max-width:760px){
  .board{grid-template-columns:1fr}
  .topbar{position:static;grid-template-columns:1fr}.stats,.toolbar{justify-content:center}
  .chip{min-width:135px}
}
</style>
</head>
<body>
<div class="app">
  <header class="topbar">
    <div class="title">
      <h1>🚦 交通工具接力|교통수단 릴레이</h1>
      <div class="rule">拖到“坐 / 骑”|“坐 / 骑”에 넣기 ❌ 错误 +10秒|오답 +10초</div>
    </div>
    <div class="stats">
      <div class="stat timer">⏱ 计时|시간<strong id="timer">00:00</strong></div>    </div>
    <div class="toolbar">
      <button class="btn primary" id="startBtn">▶ 开始|시작</button>      <button class="btn warn" id="editBtn">✏ 编辑|편집</button>
      <button class="btn red" id="resetBtn">↻ 重置|초기화</button>
    </div>
  </header>

  <div class="modebar">
    <button class="modebtn active" data-mode="practice">练习|연습</button>
    <button class="modebtn" data-mode="relay">接力赛|릴레이</button>
  </div>

  <section class="board">
    <div class="zone zuo" data-type="坐">
      <div class="zonehead"><span class="han">坐</span><span class="py">zuò</span><span class="kr">앉아서 타다</span></div>
      <div class="matched" id="zuoList"></div>
    </div>
    <div class="zone qi" data-type="骑">
      <div class="zonehead"><span class="han">骑</span><span class="py">qí</span><span class="kr">걸쳐서 타다</span></div>
      <div class="matched" id="qiList"></div>
    </div>
  </section>

  <section class="source" id="practiceArea">
    <div class="sourcehead">
      <span>选择或拖拽|선택 또는 드래그</span>
      <span id="remainText">剩余 6|남음 6</span>
    </div>
    <div class="cards" id="cards"></div>
  </section>

  <section class="relay" id="relayArea">
    <div class="relay-label">当前题目|현재 문제</div>
    <div class="relay-card" id="relayCard"></div>
    <div class="relay-actions">
      <button class="answerbtn zuo" data-answer="坐">坐 <span class="py">zuò</span><br><span class="kr">앉아서 타다</span></button>
      <button class="answerbtn qi" data-answer="骑">骑 <span class="py">qí</span><br><span class="kr">걸쳐서 타다</span></button>
    </div>
    <div class="feedback" id="feedback"></div>
  </section>

  <div class="finishbar" id="finishbar">
    <button class="btn primary" id="readBtn">🔊 一起读|같이 읽기</button>
  </div>

  <div class="footerbar">
    <button class="btn" id="pinyinBtn">拼音:开|병음: 켜짐</button>
    <button class="btn" id="koreanBtn">韩语:开|한국어: 켜짐</button>
  </div>
</div>

<div class="result-overlay" id="resultOverlay">
  <div class="result-inner">
    <div class="result-title">🎉 完成|완료</div>
    <div class="result-label">最终用时|최종 시간</div>
    <div class="result-time" id="resultTime">00:00</div>
    
    <div class="result-review">
      <div class="result-review-title">📌 搭配成果巩固|짝맞추기 결과</div>
      <div class="result-grid">
        <div class="result-col zuo">
          <div class="result-col-head">坐 zuò</div>
          <div id="resultZuoList"></div>
        </div>
        <div class="result-col qi">
          <div class="result-col-head">骑 qí</div>
          <div id="resultQiList"></div>
        </div>
      </div>
    </div>

    <button class="btn primary result-close" id="resultClose">确定|확인</button>
  </div>
</div>

<div class="countdown" id="countdown"></div>

<div class="editor" id="editor">
  <h2>编辑词语|단어 편집</h2>
  <p>可修改图标、中文、拼音、韩语和“坐/骑”。修改后点击“应用”。</p>
  <div class="editgrid" id="editGrid"></div>
  <div class="editor-actions">
    <button class="btn" id="restoreBtn">恢复默认|기본값</button>
    <button class="btn" id="closeEditBtn">关闭|닫기</button>
    <button class="btn primary" id="applyEditBtn">应用|적용</button>
  </div>
  <div class="note">内容会保存在当前浏览器|내용은 현재 브라우저에 저장됩니다.</div>
</div>

<script>
const DEFAULT_VOCAB = [
  {icon:'✈️',zh:'飞机',py:'fēijī',kr:'비행기',type:'坐'},
  {icon:'🚆',zh:'火车',py:'huǒchē',kr:'기차',type:'坐'},
  {icon:'🚇',zh:'地铁',py:'dìtiě',kr:'지하철',type:'坐'},
  {icon:'🚢',zh:'船',py:'chuán',kr:'배',type:'坐'},
  {icon:'🚌',zh:'公交车',py:'gōngjiāochē',kr:'버스',type:'坐'},
  {icon:'🚕',zh:'出租车',py:'chūzūchē',kr:'택시',type:'坐'},
  {icon:'🚗',zh:'汽车',py:'qìchē',kr:'자동차',type:'坐'},
  {icon:'🏍️',zh:'摩托车',py:'mótuōchē',kr:'오토바이',type:'骑'},
  {icon:'🚲',zh:'自行车',py:'zìxíngchē',kr:'자전거',type:'骑'},
  {icon:'🐎',zh:'马',py:'mǎ',kr:'말',type:'骑'}
];
let vocab = JSON.parse(localStorage.getItem('transportRelayVocab')||'null') || structuredClone(DEFAULT_VOCAB);
let activeVocab = []; 
let mode='practice', started=false, sec=0, timer=null, ok=0, bad=0, selected=null;
let pending=[], relayIndex=0, readingTimer=null;
const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)];

function esc(s){return String(s).replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]))}
function shuffle(a){for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]]}return a}
function timeText(){
  return String(Math.floor(sec/60)).padStart(2,'0')+':'+String(sec%60).padStart(2,'0');
}
function updateStats(){
  $('#timer').textContent=timeText();
  $('#remainText').textContent=`剩余 ${Math.max(activeVocab.length-ok,0)}|남음 ${Math.max(activeVocab.length-ok,0)}`;
}
function beep(kind){
  try{
    const ctx=new (window.AudioContext||window.webkitAudioContext)();
    const o=ctx.createOscillator(),g=ctx.createGain();o.connect(g);g.connect(ctx.destination);
    o.frequency.value=kind==='ok'?720:kind==='bad'?170:520; o.type=kind==='bad'?'sawtooth':'sine';
    g.gain.setValueAtTime(.13,ctx.currentTime);g.gain.exponentialRampToValueAtTime(.001,ctx.currentTime+.18);
    o.start();o.stop(ctx.currentTime+.19);
  }catch(e){}
}
function startTimer(){clearInterval(timer);timer=setInterval(()=>{sec++;updateStats()},1000)}

// 修改:确保每次抽取的 6 个词中至少有 2 个“骑”
function pickActiveVocab(){
  const qiList = shuffle(vocab.filter(v => v.type === '骑'));
  const zuoList = shuffle(vocab.filter(v => v.type === '坐'));
  
  // 强制取至少2个“骑”
  const selectedQi = qiList.slice(0, Math.max(2, Math.min(qiList.length, 2)));
  
  // 剩余需要从其它词语中补齐
  const remainingPool = shuffle([...qiList.slice(selectedQi.length), ...zuoList]);
  const needCount = 6 - selectedQi.length;
  const selectedOthers = remainingPool.slice(0, needCount);
  
  // 打乱后返回6个词
  return shuffle([...selectedQi, ...selectedOthers]);
}

function resetGame(){
  clearInterval(timer);clearInterval(readingTimer); started=false;sec=0;ok=0;bad=0;selected=null;
  $('#startBtn').disabled=false; $('#startBtn').textContent='▶ 开始|시작';
  $('#zuoList').innerHTML=''; $('#qiList').innerHTML=''; $('#finishbar').classList.remove('show');
  $('#resultOverlay').classList.remove('show');
  $('#feedback').textContent=''; $('#feedback').className='feedback';
  
  activeVocab = pickActiveVocab();
  pending = activeVocab.map((v,i)=>({...v,id:i}));
  
  relayIndex=0;
  renderPractice();renderRelay();updateStats();
}

function cardHTML(v){return `<span class="icon">${esc(v.icon)}</span><div class="txt"><div class="py">${esc(v.py)}</div><div class="zh">${esc(v.zh)}</div><div class="kr">${esc(v.kr)}</div></div>`}
function renderPractice(){
  $('#cards').innerHTML='';
  pending.forEach(v=>{
    const el=document.createElement('div');el.className='card'+(started?'':' disabled');el.draggable=started;el.dataset.id=v.id;
    el.innerHTML=cardHTML(v);
    el.addEventListener('click',()=>selectCard(el,v));
    el.addEventListener('dragstart',e=>{if(!started)return;e.dataTransfer.setData('text/plain',String(v.id))});
    $('#cards').appendChild(el);
  });
}
function renderRelay(){
  const v=pending[relayIndex];
  if(!v){$('#relayCard').innerHTML='<div style="font-weight:900;font-size:28px">完成|완료</div>';return}
  $('#relayCard').innerHTML=cardHTML(v);
}
function selectCard(el,v){
  if(!started)return;
  $$('.card').forEach(x=>x.classList.remove('selected'));
  selected={el,v};el.classList.add('selected');
}

function addMatched(v){
  const chip=document.createElement('div');chip.className='chip '+(v.type==='坐'?'zuo':'qi');
  chip.dataset.zh=v.zh;
  chip.innerHTML=`<span class="icon">${esc(v.icon)}</span><div class="text"><div class="py">${v.type==='坐'?'zuò':'qí'} ${esc(v.py)}</div><div class="zh"><span class="verb">${v.type}</span><span class="noun">${esc(v.zh)}</span></div><div class="kr">${esc(v.kr)}</div></div>`;
  (v.type==='坐'?$('#zuoList'):$('#qiList')).appendChild(chip);
}

function correct(v){
  beep('ok');ok++;addMatched(v);
  pending=pending.filter(x=>x.id!==v.id);
  updateStats();
  if(ok===activeVocab.length){finish();return true}
  return false;
}

function wrong(el){
  beep('bad');bad++;sec+=10;updateStats();
  if(el){el.style.animation='shake .32s';setTimeout(()=>el.style.animation='',330)}
}

function handlePracticeAnswer(v,zoneType,el){
  if(v.type===zoneType){
    correct(v); renderPractice(); selected=null;
  }else wrong(el);
}
$$('.zone').forEach(z=>{
  z.addEventListener('dragover',e=>{e.preventDefault();if(started&&mode==='practice')z.classList.add('dragover')});
  z.addEventListener('dragleave',()=>z.classList.remove('dragover'));
  z.addEventListener('drop',e=>{
    e.preventDefault();z.classList.remove('dragover');if(!started||mode!=='practice')return;
    const id=Number(e.dataTransfer.getData('text/plain'));const v=pending.find(x=>x.id===id);
    const el=[...$('#cards').children].find(x=>Number(x.dataset.id)===id); if(v)handlePracticeAnswer(v,z.dataset.type,el);
  });
  z.addEventListener('click',()=>{
    if(started&&mode==='practice'&&selected)handlePracticeAnswer(selected.v,z.dataset.type,selected.el)
  });
});
$$('.answerbtn').forEach(b=>b.addEventListener('click',()=>{
  if(!started||mode!=='relay')return;
  const v=pending[relayIndex]; if(!v)return;
  if(v.type===b.dataset.answer){
    $('#feedback').textContent=`✅ ${v.type}${v.zh}`;$('#feedback').className='feedback good';
    const done=correct(v);
    if(!done){relayIndex=0;setTimeout(()=>{$('#feedback').textContent='';renderRelay()},420)}
  }else{
    wrong(b);$('#feedback').textContent='❌ 再试一次|다시!';$('#feedback').className='feedback bad';
  }
}));

function finish(){
  clearInterval(timer);started=false;$('#startBtn').disabled=false;$('#startBtn').textContent='▶ 再玩|다시';
  $('#resultTime').textContent=timeText();
  
  const zuoMatched = activeVocab.filter(v=>v.type==='坐');
  const qiMatched = activeVocab.filter(v=>v.type==='骑');
  
  const createChipHTML = v => `
    <div class="chip ${v.type==='坐'?'zuo':'qi'}" style="margin:2px 0;">
      <span class="icon">${esc(v.icon)}</span>
      <div class="text">
        <div class="py">${v.type==='坐'?'zuò':'qí'} ${esc(v.py)}</div>
        <div class="zh"><span class="verb">${v.type}</span><span class="noun">${esc(v.zh)}</span></div>
        <div class="kr">${esc(v.kr)}</div>
      </div>
    </div>`;

  $('#resultZuoList').innerHTML = zuoMatched.map(createChipHTML).join('') || '<div style="color:#888;font-size:12px;">无</div>';
  $('#resultQiList').innerHTML = qiMatched.map(createChipHTML).join('') || '<div style="color:#888;font-size:12px;">无</div>';

  $('#resultOverlay').classList.add('show');
  $('#feedback').textContent='🎉 完成|완료';$('#feedback').className='feedback good';
  renderPractice();renderRelay();
}

async function countdownStart(){
  if(started)return;
  const c=$('#countdown');c.classList.add('show');
  for(const t of ['3','2','1','GO!']){c.textContent=t;beep('go');await new Promise(r=>setTimeout(r,t==='GO!'?450:650))}
  c.classList.remove('show');started=true;$('#startBtn').disabled=true;startTimer();renderPractice();
}
$('#startBtn').addEventListener('click',()=>{if(ok===activeVocab.length)resetGame();countdownStart()});
$('#resetBtn').addEventListener('click',resetGame);
$('#resultClose').addEventListener('click',()=>{ $('#resultOverlay').classList.remove('show'); $('#finishbar').classList.add('show'); window.scrollTo({top:document.body.scrollHeight,behavior:'smooth'}); });
$$('.modebtn').forEach(b=>b.addEventListener('click',()=>{
  if(started)return alert('请先重置|먼저 초기화하세요');
  mode=b.dataset.mode;$$('.modebtn').forEach(x=>x.classList.toggle('active',x===b));
  $('#practiceArea').classList.toggle('hidden',mode!=='practice');$('#relayArea').classList.toggle('active',mode==='relay');
  resetGame();
}));
$('#pinyinBtn').addEventListener('click',()=>{
  const on=document.body.classList.toggle('hide-pinyin');
  $('#pinyinBtn').textContent=on?'拼音:关|병음: 꺼짐':'拼音:开|병음: 켜짐';
});
$('#koreanBtn').addEventListener('click',()=>{
  const on=document.body.classList.toggle('hide-korean');
  $('#koreanBtn').textContent=on?'韩语:关|한국어: 꺼짐':'韩语:开|한국어: 켜짐';
});
$('#readBtn').addEventListener('click',()=>{
  clearInterval(readingTimer);
  const chips=$$('.chip');let i=0;
  chips.forEach(c=>{c.style.outline='';c.style.transform=''});
  if(!chips.length)return;
  const step=()=>{
    chips.forEach(c=>{c.style.outline='';c.style.transform=''});
    if(i>=chips.length){clearInterval(readingTimer);return}
    chips[i].style.outline='5px solid #f2b35f';chips[i].style.transform='scale(1.06)';
    chips[i].scrollIntoView({behavior:'smooth',block:'center'});i++;
  };
  step();readingTimer=setInterval(step,1200);
});

/* editor */
function renderEditor(){
  const g=$('#editGrid');
  g.innerHTML='<div class="head">图标</div><div class="head">中文</div><div class="head">拼音</div><div class="head">韩语</div><div class="head">坐/骑</div>';
  vocab.forEach((v,i)=>{
    [['icon',v.icon],['zh',v.zh],['py',v.py],['kr',v.kr],['type',v.type]].forEach(([k,val])=>{
      const input=document.createElement('input');input.value=val;input.dataset.i=i;input.dataset.k=k;g.appendChild(input);
    });
  });
}
$('#editBtn').addEventListener('click',()=>{if(started)return alert('请先重置|먼저 초기화하세요');renderEditor();$('#editor').classList.add('show')});
$('#closeEditBtn').addEventListener('click',()=>$('#editor').classList.remove('show'));
$('#applyEditBtn').addEventListener('click',()=>{
  const next=structuredClone(vocab);
  $$('#editGrid input').forEach(inp=>next[Number(inp.dataset.i)][inp.dataset.k]=inp.value.trim());
  if(next.some(v=>!v.zh||!v.py||!v.kr||!['坐','骑'].includes(v.type))){
    return alert('请检查内容。“坐/骑”必须填写:坐 或 骑。|내용을 확인하세요.');
  }
  vocab=next;localStorage.setItem('transportRelayVocab',JSON.stringify(vocab));$('#editor').classList.remove('show');resetGame();
});
$('#restoreBtn').addEventListener('click',()=>{
  vocab=structuredClone(DEFAULT_VOCAB);localStorage.removeItem('transportRelayVocab');renderEditor();resetGame();
});
resetGame();
</script>
</body>
</html>