PageSourceSearch

https://www.hamienet.com/assets/TaikoGame-D89Exv0S.js

js hamienet.com collected 2026-10-02 05:03:33 UTC 258,469 bytes, 5,359 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js";
2import { S as SouthparkCharacter, B as BASE_ANIMALS, a as SOUTHPARK_INSTRUMENTS } from "./SouthparkInstruments-C-w5kAUh.js";
3import { g as getAnimalVariant, a as getVariantFilterId, r as renderSvgVariantFilter } from "./SouthparkVariants-DYvo6xjJ.js";
4import { a as api } from "./index-w5lXls15.js";
5import { G as GlobeFlag } from "./GlobeFlag-V1uaxVEK.js";
6import { u as useTranslation } from "./vendor-i18n-BC7ZMXWQ.js";
7import { eA as ChartNoAxesColumn, cz as Trophy, bV as Play, c4 as RotateCcw, ba as Info, ff as Smartphone, cG as Keyboard, bG as CircleQuestionMark, aX as ArrowLeft, b9 as Settings, cB as ChevronUp, cC as ChevronDown, c7 as Shuffle, b6 as LoaderCircle } from "./vendor-D0P4XgNj.js";
8const DIFFICULTY_CONFIG = {
9  kantan: {
10    label: "Kantan (Easy)",
11    icon: "🌸",
12    scoreMultiplier: 1,
13    minInterval: 0.35,
14    maxNotesPerSec: 2.5,
15    subdivision: 2,
16    allowBalloons: false,
17    allowDrumrolls: true,
18    donKaAlternationLimit: 2
19  },
20  futsuu: {
21    label: "Futsuu (Normal)",
22    icon: "🎋",
23    scoreMultiplier: 1.5,
24    minInterval: 0.22,
25    maxNotesPerSec: 4.5,
26    subdivision: 4,
27    allowBalloons: true,
28    allowDrumrolls: true,
29    donKaAlternationLimit: 4
30  },
31  muzukashii: {
32    label: "Muzukashii (Hard)",
33    icon: "👺",
34    scoreMultiplier: 2.2,
35    minInterval: 0.12,
36    maxNotesPerSec: 8.5,
37    subdivision: 8,
38    allowBalloons: true,
39    allowDrumrolls: true,
40    donKaAlternationLimit: 8
41  },
42  oni: {
43    label: "Oni (Extreme)",
44    icon: "👹",
45    scoreMultiplier: 3,
46    minInterval: 0.05,
47    maxNotesPerSec: 18,
48    subdivision: 16,
49    allowBalloons: true,
50    allowDrumrolls: true,
51    donKaAlternationLimit: 999
52  }
53};
54const GM_DON_PITCHES = /* @__PURE__ */ new Set([35, 36, 41, 43, 45, 47, 48, 50, 61, 64]);
55const GM_KA_PITCHES = /* @__PURE__ */ new Set([37, 38, 39, 40, 42, 44, 46, 49, 51, 52, 53, 54, 55, 56, 57, 58, 59, 69, 70]);
56function generateTaikoChart(pianoRollData, melodyData, processedInstruments, midiMetadata, difficulty = "futsuu", duration = 180) {
57  const tempo = Math.max(40, Math.min(300, Number(midiMetadata == null ? void 0 : midiMetadata.tempo) || 120));
58  const rawNotes = [];
59  let chosenSource = "auto";
60  const hasPercussion = processedInstruments == null ? void 0 : processedInstruments.some(
61    (inst) => inst.midiChannel === 9 && (inst.notesCount === void 0 || inst.notesCount > 0)
62  );
63  const drumNotes = pianoRollData && hasPercussion ? filterDrumNotes(pianoRollData, processedInstruments) : [];
64  if (drumNotes.length >= 10) {
65    chosenSource = "drum";
66    mapDrumNotes(drumNotes, rawNotes);
67  } else if (melodyData && melodyData.length >= 8) {
68    chosenSource = "melody";
69    mapMelodyNotes(melodyData, rawNotes, tempo);
70  } else if (pianoRollData && pianoRollData.length >= 8) {
71    chosenSource = "melody";
72    mapMelodyNotes(pianoRollData, rawNotes, tempo);
73  } else {
74    chosenSource = "auto";
75    mapAutoGroove(tempo, duration, rawNotes);
76  }
77  rawNotes.sort((a, b) => a.t - b.t);
78  const config = DIFFICULTY_CONFIG[difficulty];
79  const quantizedNotes = quantizeAndFilter(rawNotes, difficulty, tempo, config);
80  let donCount = 0;
81  let kaCount = 0;
82  let drumrollCount = 0;
83  let balloonCount = 0;
84  const finalNotes = quantizedNotes.map((n, idx) => {
85    if (n.type === "don" || n.type === "big_don") donCount++;
86    else if (n.type === "ka" || n.type === "big_ka") kaCount++;
87    else if (n.type === "drumroll") drumrollCount++;
88    else if (n.type === "balloon") balloonCount++;
89    return {
90      id: idx + 1,
91      t: Math.round(n.t * 1e3) / 1e3,
92      type: n.type,
93      duration: Math.round(n.duration * 1e3) / 1e3,
94      source: chosenSource,
95      velocity: n.velocity,
96      hitsRequired: n.type === "balloon" ? Math.max(5, Math.min(25, Math.floor(n.duration * 6))) : void 0,
97      hitsCount: 0
98    };
99  });
100  return {
101    notes: finalNotes,
102    meta: {
103      source: chosenSource,
104      difficulty,
105      totalNotes: finalNotes.length,
106      donCount,
107      kaCount,
108      drumrollCount,
109      balloonCount,
110      bpm: tempo,
111      scoreMultiplier: config.scoreMultiplier
112    }
113  };
114}
115function filterDrumNotes(data, processedInstruments) {
116  const musicalInstruments = (processedInstruments == null ? void 0 : processedInstruments.filter((inst) => inst.midiChannel !== -1)) || [];
117  return data.filter((note) => {
118    let inst = musicalInstruments[note.trackIdx];
119    if (!inst || inst.midiChannel !== 9) {
120      inst = processedInstruments == null ? void 0 : processedInstruments.find((i) => i.trackId === note.trackIdx);
121    }
122    if (!inst || inst.midiChannel !== 9) {
123      inst = processedInstruments == null ? void 0 : processedInstruments.find((i) => i.originalName === note.i || i.displayName === note.i || i.name === note.i);
124    }
125    if (inst) {
126      return inst.midiChannel === 9;
127    }
128    const nameLower = (note.i || "").toLowerCase();
129    return nameLower.includes("drum") || nameLower.includes("perc") || nameLower.includes("kit") || nameLower.includes("cymbal");
130  });
131}
132function mapDrumNotes(drumNotes, output) {
133  const clusters = /* @__PURE__ */ new Map();
134  for (const note of drumNotes) {
135    const key = Math.round(note.t * 40) / 40;
136    let group = clusters.get(key);
137    if (!group) {
138      group = [];
139      clusters.set(key, group);
140    }
141    group.push(note);
142  }
143  for (const [, notes] of clusters.entries()) {
144    const time = notes[0].t;
145    const maxVel = Math.max(...notes.map((n) => n.v || 80));
146    const hasBass = notes.some((n) => GM_DON_PITCHES.has(n.n));
147    const hasCymbalOrSnare = notes.some((n) => GM_KA_PITCHES.has(n.n));
148    if (hasBass && hasCymbalOrSnare) {
149      output.push({
150        t: time,
151        type: "big_don",
152        duration: 0.1,
153        velocity: maxVel
154      });
155    } else if (hasBass) {
156      output.push({
157        t: time,
158        type: maxVel > 110 ? "big_don" : "don",
159        duration: 0.1,
160        velocity: maxVel
161      });
162    } else if (hasCymbalOrSnare) {
163      output.push({
164        t: time,
165        type: maxVel > 110 ? "big_ka" : "ka",
166        duration: 0.1,
167        velocity: maxVel
168      });
169    } else {
170      output.push({
171        t: time,
172        type: notes[0].n % 2 === 0 ? "don" : "ka",
173        duration: 0.1,
174        velocity: maxVel
175      });
176    }
177  }
178}
179function mapMelodyNotes(melodyNotes, output, bpm) {
180  if (!melodyNotes || melodyNotes.length === 0) return;
181  let minP = 127;
182  let maxP = 0;
183  let sumP = 0;
184  for (const note of melodyNotes) {
185    if (note.n < minP) minP = note.n;
186    if (note.n > maxP) maxP = note.n;
187    sumP += note.n;
188  }
189  const avgP = sumP / melodyNotes.length;
190  const midThreshold = minP + (maxP - minP) * 0.52;
191  const beatDuration = 60 / bpm;
192  for (const note of melodyNotes) {
193    const vel = note.v || 80;
194    const dur = note.d || 0.2;
195    if (dur >= beatDuration * 2 && dur >= 0.8) {
196      output.push({
197        t: note.t,
198        type: dur >= beatDuration * 3.5 ? "balloon" : "drumroll",
199        duration: Math.min(dur, 4),
200        velocity: vel
201      });
202      continue;
203    }
204    const beatOffset = note.t % beatDuration / beatDuration;
205    const isOnBeat = beatOffset < 0.12 || beatOffset > 0.88;
206    let noteType = "don";
207    if (note.n > midThreshold || !isOnBeat && note.n >= avgP) {
208      noteType = vel > 105 ? "big_ka" : "ka";
209    } else {
210      noteType = isOnBeat && vel > 100 ? "big_don" : "don";
211    }
212    output.push({
213      t: note.t,
214      type: noteType,
215      duration: 0.1,
216      velocity: vel
217    });
218  }
219}
220function mapAutoGroove(bpm, duration, output) {
221  const beatSec = 60 / bpm;
222  const totalBeats = Math.floor(duration / beatSec);
223  for (let b = 2; b < totalBeats - 2; b++) {
224    const t = b * beatSec;
225    const measureBeat = b % 4;
226    const measureNum = Math.floor(b / 4);
227    if (measureBeat === 0) {
228      output.push({ t, type: "big_don", duration: 0.1, velocity: 100 });
229    } else if (measureBeat === 2) {
230      output.push({ t, type: "don", duration: 0.1, velocity: 85 });
231    } else if (measureBeat === 1 || measureBeat === 3) {
232      if (measureNum % 4 === 3 && measureBeat === 3) {
233        output.push({ t, type: "don", duration: 0.1, velocity: 90 });
234        output.push({ t: t + beatSec * 0.5, type: "don", duration: 0.1, velocity: 90 });
235      } else {
236        output.push({ t, type: "ka", duration: 0.1, velocity: 80 });
237      }
238    }
239    if (measureNum % 8 === 7 && measureBeat === 2) {
240      output.push({
241        t,
242        type: "drumroll",
243        duration: beatSec * 2,
244        velocity: 95
245      });
246      b += 1;
247    }
248  }
249}
250function quantizeAndFilter(notes, difficulty, bpm, config, source) {
251  const beatSec = 60 / bpm;
252  const step = beatSec / (config.subdivision / 2);
253  const result = [];
254  let lastEndT = -999;
255  let consecutiveSameType = 0;
256  let lastType = null;
257  for (let i = 0; i < notes.length; i++) {
258    const note = notes[i];
259    if (note.t < lastEndT + config.minInterval) {
260      continue;
261    }
262    let qTime = Math.round(note.t / step) * step;
263    if (qTime < 0) qTime = 0;
264    if (qTime <= lastEndT) {
265      qTime = lastEndT + config.minInterval;
266    }
267    let finalType = note.type;
268    if (difficulty === "kantan") {
269      if (finalType === "balloon") finalType = "drumroll";
270      if (finalType === "ka" && Math.random() > 0.25) finalType = "don";
271      if (finalType === "big_ka") finalType = "big_don";
272    } else if (difficulty === "futsuu") {
273      if (finalType === lastType) {
274        consecutiveSameType++;
275        if (consecutiveSameType > config.donKaAlternationLimit) {
276          finalType = finalType === "don" || finalType === "big_don" ? "ka" : "don";
277          consecutiveSameType = 1;
278        }
279      } else {
280        consecutiveSameType = 1;
281      }
282    }
283    if (!config.allowBalloons && finalType === "balloon") {
284      finalType = "drumroll";
285    }
286    if (!config.allowDrumrolls && finalType === "drumroll") {
287      finalType = "big_don";
288    }
289    result.push({
290      t: qTime,
291      type: finalType,
292      duration: note.duration,
293      velocity: note.velocity
294    });
295    const noteDur = finalType === "drumroll" || finalType === "balloon" ? note.duration || 0.8 : 0;
296    lastEndT = qTime + noteDur;
297    lastType = finalType;
298  }
299  return result;
300}
301const HSF3_KEY = new TextEncoder().encode("HamieNET_HSF3_Secret_2026_Key!");
302function deobfuscateHsf3Buffer(buffer) {
303  const bytes = new Uint8Array(buffer);
304  const obfLen = Math.min(bytes.length, 4096);
305  const keyLen = HSF3_KEY.length;
306  for (let i = 0; i < obfLen; i++) {
307    bytes[i] ^= HSF3_KEY[i % keyLen];
308  }
309  return buffer;
310}
311class TaikoAudioEngine {
312  constructor() {
313    this.ctx = null;
314    this.masterGain = null;
315    this.audioOffsetMs = 0;
316    this.donBuffer = null;
317    this.kaBuffer = null;
318    this.isHsf3Loading = false;
319    this.hsf3Loaded = false;
320    if (typeof window !== "undefined") {
321      const savedOffset = localStorage.getItem("taiko_audio_offset");
322      if (savedOffset !== null) {
323        this.audioOffsetMs = Number(savedOffset) || 0;
324      }
325      setTimeout(() => this.loadHsf3Soundfont().catch(() => {
326      }), 100);
327    }
328  }
329  async loadHsf3Soundfont(url = "/storage/soundfonts/GeneralUser-GS_HSF3Bank/0_116.hsf3") {
330    if (this.hsf3Loaded || this.isHsf3Loading) return;
331    this.isHsf3Loading = true;
332    this.initContext();
333    try {
334      const res = await fetch(url);
335      if (!res.ok) throw new Error(`HTTP ${res.status}`);
336      let buf = await res.arrayBuffer();
337      buf = deobfuscateHsf3Buffer(buf);
338      const u8 = new Uint8Array(buf);
339      let firstOgg = -1;
340      for (let i = 0; i < Math.min(u8.length - 4, 25e3); i++) {
341        if (u8[i] === 79 && u8[i + 1] === 103 && u8[i + 2] === 103 && u8[i + 3] === 83) {
342          firstOgg = i;
343          break;
344        }
345      }
346      if (firstOgg !== -1 && this.ctx) {
347        const view = new DataView(buf);
348        const smplSize = view.getUint32(firstOgg - 4, true);
349        const oggData = buf.slice(firstOgg, firstOgg + smplSize);
350        const decoded = await this.ctx.decodeAudioData(oggData);
351        this.donBuffer = decoded;
352        this.kaBuffer = decoded;
353        this.hsf3Loaded = true;
354      }
355    } catch (err) {
356    } finally {
357      this.isHsf3Loading = false;
358    }
359  }
360  initContext() {
361    if (!this.ctx) {
362      const AudioCtx = window.AudioContext || window.webkitAudioContext;
363      if (AudioCtx) {
364        this.ctx = new AudioCtx();
365        this.masterGain = this.ctx.createGain();
366        this.masterGain.gain.setValueAtTime(0.85, this.ctx.currentTime);
367        this.masterGain.connect(this.ctx.destination);
368      }
369    }
370    if (this.ctx && this.ctx.state === "suspended") {
371      this.ctx.resume();
372    }
373  }
374  getOffsetMs() {
375    return this.audioOffsetMs;
376  }
377  setOffsetMs(offset) {
378    this.audioOffsetMs = Math.max(-250, Math.min(250, offset));
379    if (typeof window !== "undefined") {
380      localStorage.setItem("taiko_audio_offset", String(this.audioOffsetMs));
381    }
382  }
383  /**
384   * Play authentic Wadaiko Center strike (DON)
385   */
386  playDon(isBig = false, volume = 1) {
387    this.initContext();
388    if (!this.ctx || !this.masterGain) return;
389    const now = this.ctx.currentTime;
390    const vol = isBig ? volume * 1.35 : volume;
391    if (this.donBuffer) {
392      const source = this.ctx.createBufferSource();
393      source.buffer = this.donBuffer;
394      source.playbackRate.setValueAtTime(isBig ? 0.9 : 1.02, now);
395      const gain = this.ctx.createGain();
396      gain.gain.setValueAtTime(vol * 1.15, now);
397      gain.gain.exponentialRampToValueAtTime(1e-3, now + Math.min(0.65, this.donBuffer.duration));
398      source.connect(gain);
399      gain.connect(this.masterGain);
400      source.start(now);
401      if (isBig) {
402        const subOsc2 = this.ctx.createOscillator();
403        const subGain2 = this.ctx.createGain();
404        subOsc2.type = "triangle";
405        subOsc2.frequency.setValueAtTime(75, now);
406        subOsc2.frequency.exponentialRampToValueAtTime(32, now + 0.22);
407        subGain2.gain.setValueAtTime(vol * 0.7, now);
408        subGain2.gain.exponentialRampToValueAtTime(1e-3, now + 0.22);
409        subOsc2.connect(subGain2);
410        subGain2.connect(this.masterGain);
411        subOsc2.start(now);
412        subOsc2.stop(now + 0.22);
413      }
414      return;
415    }
416    const osc = this.ctx.createOscillator();
417    const oscGain = this.ctx.createGain();
418    const startFreq = isBig ? 130 : 160;
419    const endFreq = isBig ? 45 : 55;
420    const duration = isBig ? 0.35 : 0.22;
421    osc.type = "sine";
422    osc.frequency.setValueAtTime(startFreq, now);
423    osc.frequency.exponentialRampToValueAtTime(endFreq, now + duration);
424    oscGain.gain.setValueAtTime(vol * 0.9, now);
425    oscGain.gain.exponentialRampToValueAtTime(1e-3, now + duration);
426    osc.connect(oscGain);
427    oscGain.connect(this.masterGain);
428    osc.start(now);
429    osc.stop(now + duration);
430    const subOsc = this.ctx.createOscillator();
431    const subGain = this.ctx.createGain();
432    subOsc.type = "triangle";
433    subOsc.frequency.setValueAtTime(isBig ? 65 : 85, now);
434    subOsc.frequency.exponentialRampToValueAtTime(35, now + 0.18);
435    subGain.gain.setValueAtTime(vol * 0.6, now);
436    subGain.gain.exponentialRampToValueAtTime(1e-3, now + 0.18);
437    subOsc.connect(subGain);
438    subGain.connect(this.masterGain);
439    subOsc.start(now);
440    subOsc.stop(now + 0.18);
441    this.playNoiseTransient(now, 600, 1400, 0.035, vol * 0.4);
442  }
443  /**
444   * Play sharp Wadaiko Rim strike (KA / KATSU)
445   */
446  playKa(isBig = false, volume = 1) {
447    this.initContext();
448    if (!this.ctx || !this.masterGain) return;
449    const now = this.ctx.currentTime;
450    const vol = isBig ? volume * 1.3 : volume;
451    if (this.kaBuffer) {
452      const source = this.ctx.createBufferSource();
453      source.buffer = this.kaBuffer;
454      source.playbackRate.setValueAtTime(isBig ? 1.65 : 2.05, now);
455      const filter = this.ctx.createBiquadFilter();
456      filter.type = "highpass";
457      filter.frequency.setValueAtTime(850, now);
458      const gain = this.ctx.createGain();
459      gain.gain.setValueAtTime(vol * 1.05, now);
460      gain.gain.exponentialRampToValueAtTime(1e-3, now + 0.16);
461      source.connect(filter);
462      filter.connect(gain);
463      gain.connect(this.masterGain);
464      source.start(now);
465      source.stop(now + 0.18);
466      this.playNoiseTransient(now, 2400, 6e3, 0.032, vol * 0.45);
467      return;
468    }
469    const duration = isBig ? 0.14 : 0.09;
470    const osc1 = this.ctx.createOscillator();
471    const oscGain1 = this.ctx.createGain();
472    osc1.type = "triangle";
473    osc1.frequency.setValueAtTime(isBig ? 620 : 850, now);
474    osc1.frequency.exponentialRampToValueAtTime(350, now + duration);
475    oscGain1.gain.setValueAtTime(vol * 0.7, now);
476    oscGain1.gain.exponentialRampToValueAtTime(1e-3, now + duration);
477    osc1.connect(oscGain1);
478    oscGain1.connect(this.masterGain);
479    osc1.start(now);
480    osc1.stop(now + duration);
481    this.playNoiseTransient(now, 2200, 5e3, 0.045, vol * 0.65);
482  }
483  /**
484   * Noise transient filter helper for attack snap
485   */
486  playNoiseTransient(time, lowFreq, highFreq, duration, gainAmount) {
487    if (!this.ctx || !this.masterGain) return;
488    const bufferSize = Math.floor(this.ctx.sampleRate * duration);
489    const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
490    const data = buffer.getChannelData(0);
491    for (let i = 0; i < bufferSize; i++) {
492      data[i] = Math.random() * 2 - 1;
493    }
494    const noise = this.ctx.createBufferSource();
495    noise.buffer = buffer;
496    const filter = this.ctx.createBiquadFilter();
497    filter.type = "bandpass";
498    filter.frequency.setValueAtTime((lowFreq + highFreq) / 2, time);
499    filter.Q.setValueAtTime(1.8, time);
500    const gainNode = this.ctx.createGain();
501    gainNode.gain.setValueAtTime(gainAmount, time);
502    gainNode.gain.exponentialRampToValueAtTime(1e-3, time + duration);
503    noise.connect(filter);
504    filter.connect(gainNode);
505    gainNode.connect(this.masterGain);
506    noise.start(time);
507    noise.stop(time + duration);
508  }
509  /**
510   * Celebratory balloon pop sound effect
511   */
512  playBalloonPop() {
513    this.initContext();
514    if (!this.ctx || !this.masterGain) return;
515    const now = this.ctx.currentTime;
516    this.playDon(true, 1.2);
517    this.playNoiseTransient(now, 400, 3e3, 0.15, 0.8);
518  }
519}
520const taikoAudio = new TaikoAudioEngine();
521const DEFAULT_PLAYER_KEYS = {
522  1: {
523    leftKa: ["KeyD"],
524    leftDon: ["KeyF"],
525    rightDon: ["KeyJ"],
526    rightKa: ["KeyK"]
527  },
528  2: {
529    leftKa: ["ArrowLeft"],
530    leftDon: ["ArrowDown"],
531    rightDon: ["ArrowRight", "Numpad2"],
532    rightKa: ["ArrowUp", "Numpad3"]
533  },
534  3: {
535    leftKa: ["KeyC"],
536    leftDon: ["KeyV"],
537    rightDon: ["KeyB"],
538    rightKa: ["KeyN"]
539  },
540  4: {
541    leftKa: ["Numpad7", "Digit7"],
542    leftDon: ["Numpad8", "Digit8"],
543    rightDon: ["Numpad9", "Digit9"],
544    rightKa: ["NumpadAdd", "Digit0"]
545  }
546};
547class TaikoInputManager {
548  constructor() {
549    this.activePlayers = 1;
550    this.onHitCallback = null;
551    this.keyStates = /* @__PURE__ */ new Map();
552    this.lastHitTimes = /* @__PURE__ */ new Map();
553    this.isListening = false;
554    this.boundKeyDown = this.handleKeyDown.bind(this);
555    this.boundKeyUp = this.handleKeyUp.bind(this);
556  }
557  setActivePlayers(count) {
558    this.activePlayers = Math.max(1, Math.min(2, count));
559  }
560  setOnHit(callback) {
561    this.onHitCallback = callback;
562  }
563  isKeyActive(playerId, subKey) {
564    const config = DEFAULT_PLAYER_KEYS[playerId];
565    if (!config) return false;
566    const codes = config[subKey];
567    return codes ? codes.some((code) => this.keyStates.get(`${playerId}_${code}`) === true) : false;
568  }
569  start() {
570    if (typeof window === "undefined" || this.isListening) return;
571    window.addEventListener("keydown", this.boundKeyDown);
572    window.addEventListener("keyup", this.boundKeyUp);
573    this.isListening = true;
574  }
575  stop() {
576    if (typeof window === "undefined" || !this.isListening) return;
577    window.removeEventListener("keydown", this.boundKeyDown);
578    window.removeEventListener("keyup", this.boundKeyUp);
579    this.keyStates.clear();
580    this.lastHitTimes.clear();
581    this.isListening = false;
582  }
583  /**
584   * Programmatic trigger for Touch screens or Gamepads
585   */
586  triggerHit(playerId, action, subKey) {
587    if (this.onHitCallback) {
588      this.onHitCallback(playerId, action, action.startsWith("big_"), subKey);
589    }
590  }
591  handleKeyDown(e) {
592    var _a, _b, _c, _d;
593    const target = e.target;
594    if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)) {
595      return;
596    }
597    if (e.repeat) return;
598    const code = e.code;
599    const now = performance.now();
600    for (let p = 1; p <= this.activePlayers; p++) {
601      const config = DEFAULT_PLAYER_KEYS[p];
602      if (!config) continue;
603      const isLeftKa = config.leftKa.includes(code);
604      const isLeftDon = config.leftDon.includes(code);
605      const isRightDon = config.rightDon.includes(code);
606      const isRightKa = config.rightKa.includes(code);
607      if (!isLeftKa && !isLeftDon && !isRightDon && !isRightKa) continue;
608      if (code.startsWith("Arrow") || code.startsWith("Numpad")) {
609        e.preventDefault();
610      }
611      this.keyStates.set(`${p}_${code}`, true);
612      if (isLeftDon || isRightDon) {
613        const subKey = isLeftDon ? "leftDon" : "rightDon";
614        const otherKey = isLeftDon ? config.rightDon[0] : config.leftDon[0];
615        const otherTime = this.lastHitTimes.get(`${p}_${otherKey}`) || 0;
616        const isDual = now - otherTime < 75;
617        this.lastHitTimes.set(`${p}_${code}`, now);
618        if (isDual) {
619          (_a = this.onHitCallback) == null ? void 0 : _a.call(this, p, "big_don", true, subKey);
620        } else {
621          (_b = this.onHitCallback) == null ? void 0 : _b.call(this, p, "don", false, subKey);
622        }
623        return;
624      }
625      if (isLeftKa || isRightKa) {
626        const subKey = isLeftKa ? "leftKa" : "rightKa";
627        const otherKey = isLeftKa ? config.rightKa[0] : config.leftKa[0];
628        const otherTime = this.lastHitTimes.get(`${p}_${otherKey}`) || 0;
629        const isDual = now - otherTime < 75;
630        this.lastHitTimes.set(`${p}_${code}`, now);
631        if (isDual) {
632          (_c = this.onHitCallback) == null ? void 0 : _c.call(this, p, "big_ka", true, subKey);
633        } else {
634          (_d = this.onHitCallback) == null ? void 0 : _d.call(this, p, "ka", false, subKey);
635        }
636        return;
637      }
638    }
639  }
640  handleKeyUp(e) {
641    const code = e.code;
642    for (let p = 1; p <= this.activePlayers; p++) {
643      this.keyStates.set(`${p}_${code}`, false);
644    }
645  }
646}
647const TaikoHamsterMascot = ({
648  combo,
649  isHit,
650  lastHitType,
651  lastSubKey,
652  strikeTick,
653  soulGauge = 0,
654  playerColor = "#ef4444",
655  scale = 1,
656  playerId = 1,
657  currentTime = 0
658}) => {
659  const isFever = soulGauge >= 100;
660  const isGaugeFull = soulGauge >= 80;
661  const costumeId = playerId === 2 ? "taiko_headband_blue" : "taiko_headband";
662  const hamsterVariant = reactExports.useMemo(() => getAnimalVariant("hamster", 0), []);
663  const variantFilterId = reactExports.useMemo(() => getVariantFilterId("hamster", 0), []);
664  const taikoInst = reactExports.useMemo(() => {
665    return SOUTHPARK_INSTRUMENTS[116] || {
666      program: 116,
667      name: "Taiko Drum",
668      holdStyle: "drumming",
669      scale: 1,
670      offsetX: 0,
671      offsetY: 0
672    };
673  }, []);
674  const [leftStrike, setLeftStrike] = reactExports.useState({ active: false, target: "don" });
675  const [rightStrike, setRightStrike] = reactExports.useState({ active: false, target: "don" });
676  const [jumpY, setJumpY] = reactExports.useState(0);
677  const [jumpScale, setJumpScale] = reactExports.useState(1);
678  const altArmRef = reactExports.useRef("right");
679  const lastTickRef = reactExports.useRef(strikeTick ?? 0);
680  const timerLeftRef = reactExports.useRef(null);
681  const timerRightRef = reactExports.useRef(null);
682  const timerJumpRef = reactExports.useRef(null);
683  reactExports.useEffect(() => {
684    return () => {
685      if (timerLeftRef.current) clearTimeout(timerLeftRef.current);
686      if (timerRightRef.current) clearTimeout(timerRightRef.current);
687      if (timerJumpRef.current) clearTimeout(timerJumpRef.current);
688    };
689  }, []);
690  reactExports.useEffect(() => {
691    const isNewTick = strikeTick !== void 0 && strikeTick !== lastTickRef.current;
692    if (strikeTick !== void 0) {
693      lastTickRef.current = strikeTick;
694    }
695    if (!isNewTick && !isHit) return;
696    const isBig = lastHitType === "big_don" || lastHitType === "big_ka";
697    const targetType = lastHitType === "ka" || lastHitType === "big_ka" || lastSubKey === "leftKa" || lastSubKey === "rightKa" ? "ka" : "don";
698    let hitLeft = false;
699    let hitRight = false;
700    if (isBig) {
701      hitLeft = true;
702      hitRight = true;
703    } else if (lastSubKey === "leftDon" || lastSubKey === "leftKa") {
704      hitLeft = true;
705    } else if (lastSubKey === "rightDon" || lastSubKey === "rightKa") {
706      hitRight = true;
707    } else if (isHit) {
708      if (altArmRef.current === "left") {
709        hitLeft = true;
710        altArmRef.current = "right";
711      } else {
712        hitRight = true;
713        altArmRef.current = "left";
714      }
715    }
716    if (hitLeft) {
717      setLeftStrike({ active: true, target: targetType });
718      if (timerLeftRef.current) clearTimeout(timerLeftRef.current);
719      timerLeftRef.current = setTimeout(() => {
720        setLeftStrike((prev) => ({ ...prev, active: false }));
721      }, 180);
722    }
723    if (hitRight) {
724      setRightStrike({ active: true, target: targetType });
725      if (timerRightRef.current) clearTimeout(timerRightRef.current);
726      timerRightRef.current = setTimeout(() => {
727        setRightStrike((prev) => ({ ...prev, active: false }));
728      }, 180);
729    }
730    if (hitLeft || hitRight || isBig) {
731      if (timerJumpRef.current) clearTimeout(timerJumpRef.current);
732      if (isBig) {
733        setJumpY(-14);
734        setJumpScale(1.06);
735      } else {
736        setJumpY(-5);
737        setJumpScale(1.02);
738      }
739      timerJumpRef.current = setTimeout(() => {
740        setJumpY(0);
741        setJumpScale(1);
742      }, isBig ? 200 : 140);
743    }
744  }, [strikeTick, isHit, lastHitType, lastSubKey]);
745  const manualLimbAngles = reactExports.useMemo(() => {
746    const groove = Math.sin((currentTime || 0) * 8) * 1.5;
747    let shoulderL = 18 + groove;
748    let relElbowL = -48 - groove;
749    let wristL = -5;
750    let stickL = 145;
751    let shoulderR = -18 - groove;
752    let relElbowR = 48 + groove;
753    let wristR = 5;
754    let stickR = -145;
755    if (leftStrike.active) {
756      if (leftStrike.target === "don") {
757        shoulderL = 10;
758        relElbowL = -20;
759        wristL = 12;
760        stickL = 45;
761      } else {
762        shoulderL = 8;
763        relElbowL = -18;
764        wristL = -8;
765        stickL = -10;
766      }
767    }
768    if (rightStrike.active) {
769      if (rightStrike.target === "don") {
770        shoulderR = -10;
771        relElbowR = 20;
772        wristR = -12;
773        stickR = -45;
774      } else {
775        shoulderR = -8;
776        relElbowR = 18;
777        wristR = 8;
778        stickR = 10;
779      }
780    }
781    return {
782      shoulderL,
783      relElbowL,
784      wristL,
785      stickL,
786      shoulderR,
787      relElbowR,
788      wristR,
789      stickR
790    };
791  }, [leftStrike, rightStrike, currentTime]);
792  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
793    "div",
794    {
795      className: "relative flex items-center justify-center select-none pointer-events-none",
796      style: {
797        width: 120,
798        height: 140,
799        transform: `scale(${scale * jumpScale}) translateY(${jumpY}px)`,
800        transition: "transform 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275)"
801      },
802      children: [
803        (isFever || isGaugeFull) && /* @__PURE__ */ jsxRuntimeExports.jsx(
804          "div",
805          {
806            className: "absolute inset-0 rounded-full animate-pulse opacity-40 blur-xl pointer-events-none",
807            style: { backgroundColor: isGaugeFull ? "#fbbf24" : playerColor }
808          }
809        ),
810        /* @__PURE__ */ jsxRuntimeExports.jsxs(
811          "svg",
812          {
813            viewBox: "-70 -110 140 200",
814            className: "w-full h-full overflow-visible drop-shadow-md",
815            children: [
816              /* @__PURE__ */ jsxRuntimeExports.jsxs("defs", { children: [
817                /* @__PURE__ */ jsxRuntimeExports.jsxs("linearGradient", { id: "miniTaikoBarrelGrad", x1: "0", y1: "0", x2: "1", y2: "0", children: [
818                  /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "0%", stopColor: "#7f1d1d" }),
819                  /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "35%", stopColor: "#b91c1c" }),
820                  /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "70%", stopColor: "#991b1b" }),
821                  /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "100%", stopColor: "#7f1d1d" })
822                ] }),
823                hamsterVariant && /* @__PURE__ */ jsxRuntimeExports.jsx("g", { dangerouslySetInnerHTML: { __html: renderSvgVariantFilter(variantFilterId, hamsterVariant.hueShift, hamsterVariant.saturate, hamsterVariant.brightness) } })
824              ] }),
825              /* @__PURE__ */ jsxRuntimeExports.jsx(
826                SouthparkCharacter,
827                {
828                  animal: BASE_ANIMALS.hamster,
829                  instrument: taikoInst,
830                  isActive: leftStrike.active || rightStrike.active,
831                  velocity: 127,
832                  currentTime,
833                  isPlaying: true,
834                  physicsMode: "snappy",
835                  showInstrument: false,
836                  costumes: [costumeId],
837                  manualLimbAngles,
838                  variant: hamsterVariant,
839                  variantFilterId,
840                  hideArms: true
841                }
842              ),
843              /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { id: "hamster-mini-taiko", transform: "translate(0, 52)", children: [
844                /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "-24", y1: "4", x2: "-32", y2: "30", stroke: "#3b1d11", strokeWidth: "4.5", strokeLinecap: "round" }),
845                /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "24", y1: "4", x2: "32", y2: "30", stroke: "#3b1d11", strokeWidth: "4.5", strokeLinecap: "round" }),
846                /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "-29", y1: "22", x2: "29", y2: "22", stroke: "#522513", strokeWidth: "3", strokeLinecap: "round" }),
847                /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "-4", y: "8", width: "8", height: "22", rx: "2", fill: "#451a03", stroke: "#2c1409", strokeWidth: "1" }),
848                /* @__PURE__ */ jsxRuntimeExports.jsx(
849                  "path",
850                  {
851                    d: "M -27 0 C -33 12, -31 20, -25 26 L 25 26 C 31 20, 33 12, 27 0 Z",
852                    fill: "url(#miniTaikoBarrelGrad)",
853                    stroke: "#2c1409",
854                    strokeWidth: "1.8"
855                  }
856                ),
857                /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M -26 18 C -16 22, 16 22, 26 18", fill: "none", stroke: "#1c1917", strokeWidth: "2.5" }),
858                [-18, -9, 0, 9, 18].map((x) => /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: x, cy: 19 + (x === 0 ? 1 : 0), r: "1.4", fill: "#fbbf24", stroke: "#78350f", strokeWidth: "0.5" }, x)),
859                /* @__PURE__ */ jsxRuntimeExports.jsx(
860                  "ellipse",
861                  {
862                    cx: "0",
863                    cy: "0",
864                    rx: "27",
865                    ry: "12",
866                    fill: "#78350f",
867                    stroke: "#2c1409",
868                    strokeWidth: "1.5"
869                  }
870                ),
871                leftStrike.active && leftStrike.target === "ka" && /* @__PURE__ */ jsxRuntimeExports.jsx(
872                  "path",
873                  {
874                    d: "M -26.5 0 C -26.5 -7, -10 -11.5, 0 -11.5",
875                    fill: "none",
876                    stroke: "#38bdf8",
877                    strokeWidth: "4.5",
878                    strokeLinecap: "round",
879                    style: { filter: "drop-shadow(0 0 6px #0284c7)" }
880                  }
881                ),
882                rightStrike.active && rightStrike.target === "ka" && /* @__PURE__ */ jsxRuntimeExports.jsx(
883                  "path",
884                  {
885                    d: "M 0 -11.5 C 10 -11.5, 26.5 -7, 26.5 0",
886                    fill: "none",
887                    stroke: "#38bdf8",
888                    strokeWidth: "4.5",
889                    strokeLinecap: "round",
890                    style: { filter: "drop-shadow(0 0 6px #0284c7)" }
891                  }
892                ),
893                /* @__PURE__ */ jsxRuntimeExports.jsx(
894                  "ellipse",
895                  {
896                    cx: "0",
897                    cy: "0",
898                    rx: "23",
899                    ry: "9.5",
900                    fill: "#fef3c7",
901                    stroke: "#b45309",
902                    strokeWidth: "1.2"
903                  }
904                ),
905                leftStrike.active && leftStrike.target === "don" && /* @__PURE__ */ jsxRuntimeExports.jsx(
906                  "path",
907                  {
908                    d: "M 0 -9.5 C -14 -9.5, -23 -4.5, -23 0 C -23 4.5, -14 9.5, 0 9.5 Z",
909                    fill: "#ef4444",
910                    fillOpacity: "0.9",
911                    style: { filter: "drop-shadow(0 0 8px #ef4444) drop-shadow(0 0 14px #f97316)" }
912                  }
913                ),
914                rightStrike.active && rightStrike.target === "don" && /* @__PURE__ */ jsxRuntimeExports.jsx(
915                  "path",
916                  {
917                    d: "M 0 -9.5 C 14 -9.5, 23 -4.5, 23 0 C 23 4.5, 14 9.5, 0 9.5 Z",
918                    fill: "#ef4444",
919                    fillOpacity: "0.9",
920                    style: { filter: "drop-shadow(0 0 8px #ef4444) drop-shadow(0 0 14px #f97316)" }
921                  }
922                ),
923                leftStrike.active && rightStrike.active && leftStrike.target === "don" && /* @__PURE__ */ jsxRuntimeExports.jsx(
924                  "ellipse",
925                  {
926                    cx: "0",
927                    cy: "0",
928                    rx: "16",
929                    ry: "7",
930                    fill: "#fef08a",
931                    fillOpacity: "0.95",
932                    style: { filter: "drop-shadow(0 0 10px #fbbf24)" }
933                  }
934                ),
935                /* @__PURE__ */ jsxRuntimeExports.jsx(
936                  "text",
937                  {
938                    x: "0",
939                    y: "1.5",
940                    textAnchor: "middle",
941                    dominantBaseline: "central",
942                    fontSize: "9",
943                    fontWeight: "900",
944                    fontFamily: "sans-serif",
945                    fill: leftStrike.active || rightStrike.active ? "#ffffff" : "#b45309",
946                    fillOpacity: "0.9",
947                    style: { pointerEvents: "none" },
948                    children: "太"
949                  }
950                ),
951                /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { opacity: "0.75", fontSize: "6.5", fontWeight: "bold", fontFamily: "monospace", textAnchor: "middle", children: [
952                  /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: "-18", y: "-0.5", fill: leftStrike.active && leftStrike.target === "ka" ? "#ffffff" : "#0369a1", children: "D" }),
953                  /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: "-8", y: "1", fill: leftStrike.active && leftStrike.target === "don" ? "#ffffff" : "#b91c1c", children: "F" }),
954                  /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: "8", y: "1", fill: rightStrike.active && rightStrike.target === "don" ? "#ffffff" : "#b91c1c", children: "J" }),
955                  /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: "18", y: "-0.5", fill: rightStrike.active && rightStrike.target === "ka" ? "#ffffff" : "#0369a1", children: "K" })
956                ] }),
957                leftStrike.active && /* @__PURE__ */ jsxRuntimeExports.jsx(
958                  "circle",
959                  {
960                    cx: leftStrike.target === "ka" ? -22 : -10,
961                    cy: leftStrike.target === "ka" ? -2 : 0,
962                    r: "7",
963                    fill: "none",
964                    stroke: leftStrike.target === "ka" ? "#38bdf8" : "#fbbf24",
965                    strokeWidth: "2.2",
966                    style: { filter: leftStrike.target === "ka" ? "drop-shadow(0 0 5px #0284c7)" : "drop-shadow(0 0 5px #ea580c)" }
967                  }
968                ),
969                rightStrike.active && /* @__PURE__ */ jsxRuntimeExports.jsx(
970                  "circle",
971                  {
972                    cx: rightStrike.target === "ka" ? 22 : 10,
973                    cy: rightStrike.target === "ka" ? -2 : 0,
974                    r: "7",
975                    fill: "none",
976                    stroke: rightStrike.target === "ka" ? "#38bdf8" : "#fbbf24",
977                    strokeWidth: "2.2",
978                    style: { filter: rightStrike.target === "ka" ? "drop-shadow(0 0 5px #0284c7)" : "drop-shadow(0 0 5px #ea580c)" }
979                  }
980                ),
981                leftStrike.active && rightStrike.active && /* @__PURE__ */ jsxRuntimeExports.jsx(
982                  "ellipse",
983                  {
984                    cx: "0",
985                    cy: "0",
986                    rx: "25",
987                    ry: "11",
988                    fill: "none",
989                    stroke: leftStrike.target === "ka" ? "#38bdf8" : "#fbbf24",
990                    strokeWidth: "2.5",
991                    style: { filter: "drop-shadow(0 0 8px #f59e0b)" }
992                  }
993                )
994              ] }),
995              /* @__PURE__ */ jsxRuntimeExports.jsx(
996                SouthparkCharacter,
997                {
998                  animal: BASE_ANIMALS.hamster,
999                  instrument: taikoInst,
1000                  isActive: leftStrike.active || rightStrike.active,
1001                  velocity: 127,
1002                  currentTime,
1003                  isPlaying: true,
1004                  physicsMode: "snappy",
1005                  showInstrument: false,
1006                  costumes: [costumeId],
1007                  manualLimbAngles,
1008                  variant: hamsterVariant,
1009                  variantFilterId,
1010                  renderArmsOnly: true
1011                }
1012              )
1013            ]
1014          }
1015        )
1016      ]
1017    }
1018  );
1019};
1020const TAIKO_LOCALES = {
1021  en: {
1022    gameTitle: "Taiko no Hamie",
1023    gameSubtitle: "RHYTHM DRUM VISUALIZER",
1024    start: "START GAME",
1025    ready: "READY",
1026    loadingAudio: "Loading Audio...",
1027    lobby: "Lobby",
1028    players: "PLAYERS",
1029    difficulty: "DIFFICULTY",
1030    stageTheme: "STAGE THEME",
1031    speed: "SPEED",
1032    speedSlow: "0.8x (Slow)",
1033    speedNormal: "1.0x (Normal)",
1034    speedFast: "1.2x (Swift)",
1035    speedVeryFast: "1.5x (Fast)",
1036    mode: "GAME MODE",
1037    competitive: "Battle (Competitive)",
1038    cooperative: "Team (Co-op)",
1039    keyboardControls: "KEYBOARD CONTROLS",
1040    keyTip: "Press both Don keys simultaneously for Big Don!",
1041    don: "DON",
1042    ka: "KA",
1043    drumroll: "ROLL",
1044    balloon: "POP!",
1045    perfect: "PERFECT!",
1046    good: "GOOD",
1047    miss: "MISS",
1048    combo: "COMBO",
1049    score: "Score",
1050    maxCombo: "Max Combo",
1051    accuracy: "Accuracy",
1052    source: "Source",
1053    sourceDrums: "MIDI Drums",
1054    sourceMelody: "Melody Rhythm",
1055    sourceBeatGrid: "Groove Grid",
1056    resultsTitle: "STAGE CLEAR!",
1057    clearCrown: "CLEAR",
1058    silverCrown: "FULL COMBO",
1059    goldCrown: "ALL PERFECT",
1060    retry: "PLAY AGAIN",
1061    shareScore: "SHARE SCORE",
1062    kantan: "EASY",
1063    futsuu: "NORMAL",
1064    muzukashii: "HARD",
1065    oni: "EXTREME",
1066    themeMatsuri: "🏮 Festival Lanterns",
1067    themeSakura: "🌸 Sakura Fuji",
1068    themeThunder: "⚡ Thunder Torii",
1069    themeNeon: "🌆 Neo-Tokyo Neon",
1070    themeRandom: "🎲 Random (Shuffle)",
1071    themeBamboo: "🎋 Bamboo Moon",
1072    themeAutumnTemple: "🍁 Autumn Pagoda",
1073    themeOceanSunset: "🌊 Ukiyo-e Wave",
1074    themeDojo: "🏯 Taiko Dojo",
1075    themeSnowVillage: "❄️ Snow Village",
1076    themeShintoShrine: "⛩️ Thousand Torii",
1077    themeSpaceGalaxy: "🌌 Cosmic Nebula",
1078    themeRetroArcade: "🕹️ Retro Arcade",
1079    themeDragonVolcano: "🐉 Dragon Volcano",
1080    themeCastleNight: "🏯 Moonlight Castle",
1081    themeZenGarden: "🪨 Kyoto Zen Garden",
1082    themeCarnival: "🎪 Taiko Carnival",
1083    p1Name: "Player 1",
1084    p2Name: "Player 2",
1085    p3Name: "Player 3",
1086    p4Name: "Player 4",
1087    p1Solo: "1P Solo",
1088    p2Duo: "2P Duo (Versus)",
1089    restart: "Restart",
1090    play: "Play",
1091    pause: "Pause",
1092    setup: "Setup",
1093    topTaikoMasters: "TOP TAIKO MASTERS",
1094    leaderboard: "Leaderboard",
1095    noHighscoresYet: "No highscores yet!",
1096    beFirstToSetRecord: "Be the first Taiko Master to set a record!",
1097    emptySlot: "-",
1098    submittingScore: "Submitting score...",
1099    points: "pts",
1100    winner: "WINNER",
1101    teamTotalScore: "Team Total Score",
1102    teamMaxCombo: "Team Max Combo",
1103    greatLabel: "良 (Great)",
1104    goodLabel: "可 (Good)",
1105    missLabel: "不可 (Miss)",
1106    rollsLabel: "連打 (Rolls)",
1107    competitiveBattle: "Competitive Battle",
1108    cooperativeTeam: "Cooperative Team",
1109    pressFOrJNow: "Press F or J now!",
1110    donCenter: "DON (Center)",
1111    tapDrumheadCenter: "Tap drumhead center",
1112    pressFAndJNow: "Press F + J now!",
1113    bigDonBothHands: "BIG DON (Both Hands)",
1114    strikeCenterBothHands: "Strike center with both hands!",
1115    pressDAndKNow: "Press D + K now!",
1116    bigKaBothRims: "BIG KA (Both Rims)",
1117    strikeRimsBothHands: "Strike rims with both hands!",
1118    pressDOrKNow: "Press D or K now!",
1119    kaRim: "KA (Rim)",
1120    tapDrumRimEdge: "Tap drum rim edge",
1121    mashFAndJFast: "Mash F & J fast!",
1122    drumrollTitle: "DRUMROLL",
1123    alternateRapidStrikes: "Alternate rapid strikes!",
1124    touchDrum: "Touch Drum",
1125    touchDrumPrompt: "Tap drum pads to play!",
1126    tabletRecommended: "Tablet or keyboard recommended for 2-Player mode",
1127    touchKaLeft: "Left Ka",
1128    touchDonLeft: "Left Don",
1129    touchDonRight: "Right Don",
1130    touchKaRight: "Right Ka",
1131    improvementSupport: "Improvement Support",
1132    section: "Section",
1133    practiceSection: "Practice this section",
1134    coachFullCombo: "Donderful! Flawless rhythm from start to finish! You are a true Taiko Master!",
1135    coachCleared: "Stage Cleared! Section {{sec}} had a few tricky notes. Practice that phrase to aim for a Full Combo!",
1136    coachFailed: "Donmai! Don't give up! Focus on Section {{sec}} where the tempo shifts and you'll clear it next time!",
1137    coachCoop: "Great teamwork! Both drummers played in sync!",
1138    tamashii: "Soul",
1139    clearThreshold: "Clear 80%",
1140    drumrolls: "Drumrolls",
1141    performanceTab: "Performance Report",
1142    leaderboardTab: "Top Masters"
1143  },
1144  ja: {
1145    gameTitle: "太鼓のハミー",
1146    gameSubtitle: "太鼓リズムビジュアライザー",
1147    start: "スタート (演奏開始)",
1148    ready: "準備",
1149    loadingAudio: "音声を読み込み中...",
1150    lobby: "ロビー",
1151    players: "参加人数",
1152    difficulty: "難易度",
1153    stageTheme: "ステージ背景",
1154    speed: "ばいそく (速度)",
1155    speedSlow: "0.8倍 (ゆっくり)",
1156    speedNormal: "1.0倍 (標準)",
1157    speedFast: "1.2倍 (はやい)",
1158    speedVeryFast: "1.5倍 (超はやい)",
1159    mode: "対戦モード",
1160    competitive: "対戦 (バトル)",
1161    cooperative: "協力 (みんなで)",
1162    keyboardControls: "操作方法 (キーボード)",
1163    keyTip: "ドンを2キー同時押しで大音符を叩けます!",
1164    don: "ドン",
1165    ka: "カッ",
1166    drumroll: "連打",
1167    balloon: "風船",
1168    perfect: "良",
1169    good: "可",
1170    miss: "不可",
1171    combo: "コンボ",
1172    score: "スコア",
1173    maxCombo: "最大コンボ",
1174    accuracy: "精度",
1175    source: "譜面音源",
1176    sourceDrums: "GM ドラムトラック",
1177    sourceMelody: "メロディリズム抽出",
1178    sourceBeatGrid: "オートビートグリッド",
1179    resultsTitle: "演奏結果発表",
1180    clearCrown: "クリア達成",
1181    silverCrown: "フルコンボ",
1182    goldCrown: "ドンダフル (全良)",
1183    retry: "もう一回遊ぶ",
1184    shareScore: "スコアを共有",
1185    kantan: "かんたん",
1186    futsuu: "ふつう",
1187    muzukashii: "むずかしい",
1188    oni: "おに",
1189    themeMatsuri: "🏮 夏祭り花火",
1190    themeSakura: "🌸 桜富士川",
1191    themeThunder: "⚡ 雷鳥居山",
1192    themeNeon: "🌆 ネオ東京",
1193    themeRandom: "🎲 ランダム (おまかせ)",
1194    themeBamboo: "🎋 竹林の月夜",
1195    themeAutumnTemple: "🍁 紅葉の五重塔",
1196    themeOceanSunset: "🌊 浮世絵波濤",
1197    themeDojo: "🏯 太鼓大道場",
1198    themeSnowVillage: "❄️ 白川郷雪景色",
1199    themeShintoShrine: "⛩️ 伏見千本鳥居",
1200    themeSpaceGalaxy: "🌌 銀河ステーション",
1201    themeRetroArcade: "🕹️ レトロゲーセン",
1202    themeDragonVolcano: "🐉 火竜の霊峰",
1203    themeCastleNight: "🏯 姫路月下桜",
1204    themeZenGarden: "🪨 京都枯山水",
1205    themeCarnival: "🎪 黄金太鼓カーニバル",
1206    p1Name: "1P",
1207    p2Name: "2P",
1208    p3Name: "3P",
1209    p4Name: "4P",
1210    p1Solo: "1人プレイ",
1211    p2Duo: "2人プレイ (対戦)",
1212    restart: "リスタート",
1213    play: "再生",
1214    pause: "一時停止",
1215    setup: "設定",
1216    topTaikoMasters: "太鼓達人ランキング",
1217    leaderboard: "リーダーボード",
1218    noHighscoresYet: "ハイスコアはまだありません!",
1219    beFirstToSetRecord: "最初の太鼓達人として記録を残そう!",
1220    emptySlot: "-",
1221    submittingScore: "スコア送信中...",
1222    points: "点",
1223    winner: "勝者",
1224    teamTotalScore: "チーム合計スコア",
1225    teamMaxCombo: "チーム最大コンボ",
1226    greatLabel: "良 (Great)",
1227    goodLabel: "可 (Good)",
1228    missLabel: "不可 (Miss)",
1229    rollsLabel: "連打 (Rolls)",
1230    competitiveBattle: "対戦バトル",
1231    cooperativeTeam: "協力チーム",
1232    pressFOrJNow: "F または J を叩いて!",
1233    donCenter: "ドン (面)",
1234    tapDrumheadCenter: "太鼓の面を叩く",
1235    pressFAndJNow: "F + J 同時押し!",
1236    bigDonBothHands: "大ドン (両手)",
1237    strikeCenterBothHands: "両手で太鼓の面を叩く!",
1238    pressDAndKNow: "D + K 同時押し!",
1239    bigKaBothRims: "大カッ (両手)",
1240    strikeRimsBothHands: "両手で太鼓のフチを叩く!",
1241    pressDOrKNow: "D または K を叩いて!",
1242    kaRim: "カッ (フチ)",
1243    tapDrumRimEdge: "太鼓のフチを叩く",
1244    mashFAndJFast: "F と J を連打!",
1245    drumrollTitle: "連打",
1246    alternateRapidStrikes: "素早く交互に叩く!",
1247    touchDrum: "タッチ太鼓",
1248    touchDrumPrompt: "太鼓を叩いて演奏!",
1249    tabletRecommended: "2人プレイはタブレットまたはキーボード推奨",
1250    touchKaLeft: "左 カッ",
1251    touchDonLeft: "左 ドン",
1252    touchDonRight: "右 ドン",
1253    touchKaRight: "右 カッ",
1254    improvementSupport: "演奏リポート",
1255    section: "区間",
1256    practiceSection: "この区間を練習",
1257    coachFullCombo: "ドンダフルコンボ!全良の神業だドン!真の太鼓の達人だ!",
1258    coachCleared: "クリア成功だドン!第{{sec}}区間に少し惜しい打音があったドン。復習してフルコンボを目指そう!",
1259    coachFailed: "どんまい!諦めたらダメだドン!第{{sec}}区間のリズムを練習すれば次は必ずクリアできるドン!",
1260    coachCoop: "息ぴったりの演奏だドン!二人の心が一つになったドン!",
1261    tamashii: "魂",
1262    clearThreshold: "クリア 80%",
1263    drumrolls: "連打数",
1264    performanceTab: "演奏リポート",
1265    leaderboardTab: "ランキング"
1266  },
1267  "zh-CN": {
1268    gameTitle: "太鼓之哈米",
1269    gameSubtitle: "节奏太鼓律动游戏",
1270    start: "开始演奏",
1271    ready: "准备",
1272    loadingAudio: "正在加载音频...",
1273    lobby: "游戏大厅",
1274    players: "玩家人数",
1275    difficulty: "难度选择",
1276    stageTheme: "舞台主题",
1277    speed: "音符流速",
1278    speedSlow: "0.8x (慢速)",
1279    speedNormal: "1.0x (标准)",
1280    speedFast: "1.2x (稍快)",
1281    speedVeryFast: "1.5x (极速)",
1282    mode: "游戏模式",
1283    competitive: "对抗竞技",
1284    cooperative: "协作合奏",
1285    keyboardControls: "键盘操作说明",
1286    keyTip: "双手同时按下咚键可触发大音符!",
1287    don: "咚",
1288    ka: "咔",
1289    drumroll: "连打",
1290    balloon: "气球",
1291    perfect: "良",
1292    good: "可",
1293    miss: "å·®",
1294    combo: "连击",
1295    score: "得分",
1296    maxCombo: "最高连击",
1297    accuracy: "精准度",
1298    source: "谱面来源",
1299    sourceDrums: "鼓点音轨",
1300    sourceMelody: "旋律节奏",
1301    sourceBeatGrid: "拍号节拍",
1302    resultsTitle: "演奏完成!",
1303    clearCrown: "通关",
1304    silverCrown: "全连击",
1305    goldCrown: "全部完美",
1306    retry: "再次游玩",
1307    shareScore: "分享成绩",
1308    kantan: "简单",
1309    futsuu: "普通",
1310    muzukashii: "困难",
1311    oni: "魔王",
1312    themeMatsuri: "🏮 夏日祭花火",
1313    themeSakura: "🌸 富士樱花河",
1314    themeThunder: "⚡ 雷鸣鸟居峰",
1315    themeNeon: "🌆 赛博霓虹城",
1316    themeRandom: "🎲 随机舞台 (轮播)",
1317    themeBamboo: "🎋 竹林月夜",
1318    themeAutumnTemple: "🍁 红叶古刹",
1319    themeOceanSunset: "🌊 浮世绘巨浪",
1320    themeDojo: "🏯 太鼓演武场",
1321    themeSnowVillage: "❄️ 白川乡雪景",
1322    themeShintoShrine: "⛩️ 伏见千本鸟居",
1323    themeSpaceGalaxy: "🌌 银河太空站",
1324    themeRetroArcade: "🕹️ 怀旧游戏厅",
1325    themeDragonVolcano: "🐉 火龙熔岩峰",
1326    themeCastleNight: "🏯 月下樱花城",
1327    themeZenGarden: "🪨 京都枯山水",
1328    themeCarnival: "🎪 黄金太鼓狂欢节",
1329    p1Name: "玩家 1",
1330    p2Name: "玩家 2",
1331    p3Name: "玩家 3",
1332    p4Name: "玩家 4",
1333    p1Solo: "单人演奏 (1P)",
1334    p2Duo: "双人对战 (2P)",
1335    restart: "重新开始",
1336    play: "播放",
1337    pause: "暂停",
1338    setup: "设置",
1339    topTaikoMasters: "太鼓大师排行榜",
1340    leaderboard: "排行榜",
1341    noHighscoresYet: "暂无高分记录!",
1342    beFirstToSetRecord: "成为首位创下纪录的太鼓大师吧!",
1343    emptySlot: "-",
1344    submittingScore: "正在提交得分...",
1345    points: "分",
1346    winner: "获胜者",
1347    teamTotalScore: "团队总分",
1348    teamMaxCombo: "团队最高连击",
1349    greatLabel: "良 (Great)",
1350    goodLabel: "可 (Good)",
1351    missLabel: "不可 (Miss)",
1352    rollsLabel: "连打 (Rolls)",
1353    competitiveBattle: "对战模式",
1354    cooperativeTeam: "合作团队",
1355    pressFOrJNow: "立即按 F 或 J!",
1356    donCenter: "咚 (鼓面)",
1357    tapDrumheadCenter: "轻击鼓面中心",
1358    pressFAndJNow: "同时按 F + J!",
1359    bigDonBothHands: "大咚 (双手)",
1360    strikeCenterBothHands: "双手同时击打鼓面!",
1361    pressDAndKNow: "同时按 D + K!",
1362    bigKaBothRims: "大咔 (双手)",
1363    strikeRimsBothHands: "双手同时击打鼓边!",
1364    pressDOrKNow: "立即按 D 或 K!",
1365    kaRim: "咔 (鼓边)",
1366    tapDrumRimEdge: "轻击鼓边边缘",
1367    mashFAndJFast: "快速连打 F 与 J!",
1368    drumrollTitle: "连打",
1369    alternateRapidStrikes: "双手快速交替连击!",
1370    touchDrum: "触摸鼓",
1371    touchDrumPrompt: "点击鼓面开始演奏!",
1372    tabletRecommended: "双人模式建议使用平板或键盘",
1373    touchKaLeft: "左 咔",
1374    touchDonLeft: "左 咚",
1375    touchDonRight: "右 咚",
1376    touchKaRight: "右 咔",
1377    improvementSupport: "演奏分析报告",
1378    section: "区间",
1379    practiceSection: "练习此区间",
1380    coachFullCombo: "太神啦!全曲无失误全连击!你就是真正的太鼓达人!",
1381    coachCleared: "通关成功!第{{sec}}区间有几处失手,针对练习即可冲击全连!",
1382    coachFailed: "别灰心!第{{sec}}区间的节奏稍有难度,针对练习下次一定能过关!",
1383    coachCoop: "默契绝佳!两位鼓手的配合天衣无缝!",
1384    tamashii: "魂",
1385    clearThreshold: "通关 80%",
1386    drumrolls: "连打数",
1387    performanceTab: "演奏报告",
1388    leaderboardTab: "高手榜"
1389  },
1390  "zh-TW": {
1391    gameTitle: "太鼓之哈米",
1392    gameSubtitle: "節奏太鼓律動遊戲",
1393    start: "開始演奏",
1394    ready: "準備",
1395    loadingAudio: "正在載入音訊...",
1396    lobby: "遊戲大廳",
1397    players: "玩家人數",
1398    difficulty: "難度選擇",
1399    stageTheme: "舞台主題",
1400    speed: "音符流速",
1401    speedSlow: "0.8x (慢速)",
1402    speedNormal: "1.0x (標準)",
1403    speedFast: "1.2x (稍快)",
1404    speedVeryFast: "1.5x (極速)",
1405    mode: "遊戲模式",
1406    competitive: "對戰模式",
1407    cooperative: "合作演奏",
1408    keyboardControls: "鍵盤操作指南",
1409    keyTip: "同時按下兩個「咚」按鍵可敲擊大音符!",
1410    don: "咚",
1411    ka: "咔",
1412    drumroll: "連打",
1413    balloon: "氣球",
1414    perfect: "良",
1415    good: "可",
1416    miss: "不可",
1417    combo: "連段",
1418    score: "得分",
1419    maxCombo: "最大連段",
1420    accuracy: "精準度",
1421    source: "譜面來源",
1422    sourceDrums: "GM 鼓組軌道",
1423    sourceMelody: "旋律節奏提取",
1424    sourceBeatGrid: "自動節拍律動",
1425    resultsTitle: "演奏成績結算",
1426    clearCrown: "通關達成",
1427    silverCrown: "全連段達成",
1428    goldCrown: "咚噠全良",
1429    retry: "再次遊玩",
1430    shareScore: "分享成績",
1431    kantan: "簡單",
1432    futsuu: "普通",
1433    muzukashii: "困難",
1434    oni: "魔王",
1435    themeMatsuri: "🏮 夏日祭花火",
1436    themeSakura: "🌸 富士櫻花河",
1437    themeThunder: "⚡ 雷鳴鳥居峰",
1438    themeNeon: "🌆 賽博霓虹城",
1439    themeRandom: "🎲 隨機舞台 (輪播)",
1440    themeBamboo: "🎋 竹林月夜",
1441    themeAutumnTemple: "🍁 紅葉古剎",
1442    themeOceanSunset: "🌊 浮世繪巨浪",
1443    themeDojo: "🏯 太鼓演武場",
1444    themeSnowVillage: "❄️ 白川鄉雪景",
1445    themeShintoShrine: "⛩️ 伏見千本鳥居",
1446    themeSpaceGalaxy: "🌌 銀河太空站",
1447    themeRetroArcade: "🕹️ 懷舊遊戲廳",
1448    themeDragonVolcano: "🐉 火龍熔岩峰",
1449    themeCastleNight: "🏯 月下櫻花城",
1450    themeZenGarden: "🪨 京都枯山水",
1451    themeCarnival: "🎪 黃金太鼓狂歡節",
1452    p1Name: "玩家 1",
1453    p2Name: "玩家 2",
1454    p3Name: "玩家 3",
1455    p4Name: "玩家 4",
1456    p1Solo: "單人演奏 (1P)",
1457    p2Duo: "雙人對戰 (2P)",
1458    restart: "重新開始",
1459    play: "播放",
1460    pause: "暫停",
1461    setup: "設定",
1462    topTaikoMasters: "太鼓大師排行榜",
1463    leaderboard: "排行榜",
1464    noHighscoresYet: "暫無高分紀錄!",
1465    beFirstToSetRecord: "成為首位創下紀錄的太鼓大師吧!",
1466    emptySlot: "-",
1467    submittingScore: "正在提交得分...",
1468    points: "分",
1469    winner: "獲勝者",
1470    teamTotalScore: "團隊總分",
1471    teamMaxCombo: "團隊最高連擊",
1472    greatLabel: "良 (Great)",
1473    goodLabel: "可 (Good)",
1474    missLabel: "不可 (Miss)",
1475    rollsLabel: "連打 (Rolls)",
1476    competitiveBattle: "對戰模式",
1477    cooperativeTeam: "合作團隊",
1478    pressFOrJNow: "立即按 F 或 J!",
1479    donCenter: "咚 (鼓面)",
1480    tapDrumheadCenter: "敲擊鼓面中心",
1481    pressFAndJNow: "同時按 F + J!",
1482    bigDonBothHands: "大咚 (雙手)",
1483    strikeCenterBothHands: "雙手同時擊打鼓面!",
1484    pressDAndKNow: "同時按 D + K!",
1485    bigKaBothRims: "大咔 (雙手)",
1486    strikeRimsBothHands: "雙手同時擊打鼓邊!",
1487    pressDOrKNow: "立即按 D 或 K!",
1488    kaRim: "咔 (鼓邊)",
1489    tapDrumRimEdge: "敲擊鼓邊邊緣",
1490    mashFAndJFast: "快速連打 F 與 J!",
1491    drumrollTitle: "連打",
1492    alternateRapidStrikes: "雙手快速交替連擊!",
1493    touchDrum: "觸控鼓",
1494    touchDrumPrompt: "點擊鼓面開始演奏!",
1495    tabletRecommended: "雙人模式建議使用平板或鍵盤",
1496    touchKaLeft: "左 咔",
1497    touchDonLeft: "左 咚",
1498    touchDonRight: "右 咚",
1499    touchKaRight: "右 咔",
1500    improvementSupport: "演奏分析報告",
1501    section: "區間",
1502    practiceSection: "練習此區間",
1503    coachFullCombo: "太神啦!全曲無失誤全連擊!你就是真正的太鼓達人!",
1504    coachCleared: "過關成功!第{{sec}}區間有幾處稍不注意的敲擊,多加練習即可衝擊全連!",
1505    coachFailed: "別氣餒!第{{sec}}區間的節奏稍有難度,針對練習下次一定能順利過關!",
1506    coachCoop: "默契絕佳!兩位鼓手的配合天衣無縫!",
1507    tamashii: "魂",
1508    clearThreshold: "通關 80%",
1509    drumrolls: "連打數",
1510    performanceTab: "演奏報告",
1511    leaderboardTab: "高手榜"
1512  },
1513  ko: {
1514    gameTitle: "타이코 노 하미",
1515    gameSubtitle: "리듬 북 시각화 게임",
1516    start: "연주 시작",
1517    ready: "준비",
1518    loadingAudio: "오디오 로딩 중...",
1519    lobby: "로비",
1520    players: "플레이어 수",
1521    difficulty: "난이도",
1522    stageTheme: "스테이지 배경",
1523    speed: "속도 조절",
1524    speedSlow: "0.8x (느림)",
1525    speedNormal: "1.0x (보통)",
1526    speedFast: "1.2x (빠름)",
1527    speedVeryFast: "1.5x (매우 빠름)",
1528    mode: "게임 모드",
1529    competitive: "배틀 (경쟁)",
1530    cooperative: "팀플레이 (협동)",
1531    keyboardControls: "키보드 조작법",
1532    keyTip: "양손의 동 키를 동시에 누르면 큰 음표 타격!",
1533    don: "쿵(동)",
1534    ka: "딱(카)",
1535    drumroll: "연타",
1536    balloon: "풍선!",
1537    perfect: "얼씨구(良)",
1538    good: "좋다(可)",
1539    miss: "에구(不可)",
1540    combo: "연속 성공",
1541    score: "점수",
1542    maxCombo: "최대 콤보",
1543    accuracy: "정확도",
1544    source: "채보 출처",
1545    sourceDrums: "드럼 트랙",
1546    sourceMelody: "멜로디 리듬",
1547    sourceBeatGrid: "박자 그리드",
1548    resultsTitle: "연주 완료!",
1549    clearCrown: "클리어",
1550    silverCrown: "풀 콤보",
1551    goldCrown: "전량(퍼펙트)",
1552    retry: "다시 하기",
1553    shareScore: "점수 공유",
1554    kantan: "초급",
1555    futsuu: "보통",
1556    muzukashii: "어려움",
1557    oni: "귀신",
1558    themeMatsuri: "🏮 축제 불꽃놀이",
1559    themeSakura: "🌸 벚꽃 후지강",
1560    themeThunder: "⚡ 번개 토리이",
1561    themeNeon: "🌆 네오 도쿄",
1562    themeRandom: "🎲 랜덤 배경 (셔플)",
1563    themeBamboo: "🎋 대나무 달밤",
1564    themeAutumnTemple: "🍁 단풍 오층탑",
1565    themeOceanSunset: "🌊 우키요에 파도",
1566    themeDojo: "🏯 태고 도장",
1567    themeSnowVillage: "❄️ 설국 마을",
1568    themeShintoShrine: "⛩️ 붉은 토리이 길",
1569    themeSpaceGalaxy: "🌌 은하 우주정거장",
1570    themeRetroArcade: "🕹️ 레트로 오락실",
1571    themeDragonVolcano: "🐉 화룡 영봉",
1572    themeCastleNight: "🏯 달빛 벚꽃성",
1573    themeZenGarden: "🪨 교토 선원 정원",
1574    themeCarnival: "🎪 골든 태고 축제",
1575    p1Name: "플레이어 1",
1576    p2Name: "플레이어 2",
1577    p3Name: "플레이어 3",
1578    p4Name: "플레이어 4",
1579    restart: "다시 시작",
1580    play: "재생",
1581    pause: "일시정지",
1582    setup: "설정",
1583    topTaikoMasters: "태고 마스터 순위표",
1584    leaderboard: "순위표",
1585    noHighscoresYet: "아직 최고 기록이 없습니다!",
1586    beFirstToSetRecord: "첫 번째 태고 마스터로 기록을 남겨보세요!",
1587    emptySlot: "-",
1588    submittingScore: "점수 등록 중...",
1589    points: "점",
1590    winner: "우승자",
1591    teamTotalScore: "팀 총점",
1592    teamMaxCombo: "팀 최대 콤보",
1593    greatLabel: "얼씨구 (Great)",
1594    goodLabel: "좋다 (Good)",
1595    missLabel: "에구 (Miss)",
1596    rollsLabel: "연타 (Rolls)",
1597    competitiveBattle: "대전 배틀",
1598    cooperativeTeam: "협동 팀",
1599    pressFOrJNow: "지금 F 또는 J를 누르세요!",
1600    donCenter: "쿵 (북면)",
1601    tapDrumheadCenter: "북면 중앙 치기",
1602    pressFAndJNow: "F + J 동시에 누르기!",
1603    bigDonBothHands: "대 쿵 (양손)",
1604    strikeCenterBothHands: "양손으로 북면 중앙 치기!",
1605    pressDAndKNow: "D + K 동시에 누르기!",
1606    bigKaBothRims: "대 딱 (양손)",
1607    strikeRimsBothHands: "양손으로 북 테두리 치기!",
1608    pressDOrKNow: "지금 D 또는 K를 누르세요!",
1609    kaRim: "딱 (테두리)",
1610    tapDrumRimEdge: "북 테두리 치기",
1611    mashFAndJFast: "F와 J를 빠르게 연타!",
1612    drumrollTitle: "연타",
1613    alternateRapidStrikes: "양손으로 빠르게 번갈아 치기!",
1614    touchDrum: "터치 북",
1615    touchDrumPrompt: "북을 터치하여 연주하세요!",
1616    tabletRecommended: "2인 플레이는 태블릿 또는 키보드를 권장합니다",
1617    touchKaLeft: "왼쪽 딱",
1618    touchDonLeft: "왼쪽 쿵",
1619    touchDonRight: "오른쪽 쿵",
1620    touchKaRight: "오른쪽 딱",
1621    improvementSupport: "연주 리포트",
1622    section: "구간",
1623    practiceSection: "이 구간 연습하기",
1624    coachFullCombo: "대단해요! 처음부터 끝까지 완벽한 풀콤보! 진정한 태고의 달인입니다!",
1625    coachCleared: "클리어 성공! 제{{sec}}구간에 아쉬운 노트가 있었어요. 복습해서 풀콤보에 도전해보세요!",
1626    coachFailed: "아쉬워요! 포기하지 마세요! 제{{sec}}구간 리듬을 연습하면 다음엔 꼭 클리어할 수 있어요!",
1627    coachCoop: "환상의 호흡! 두 사람의 리듬이 완벽하게 하나가 되었어요!",
1628    tamashii: "혼",
1629    clearThreshold: "클리어 80%",
1630    drumrolls: "연타 수",
1631    performanceTab: "연주 리포트",
1632    leaderboardTab: "랭킹"
1633  },
1634  es: {
1635    gameTitle: "Taiko no Hamie",
1636    gameSubtitle: "VISUALIZADOR DE RITMO TAIKO",
1637    start: "COMENZAR PARTIDA",
1638    ready: "LISTO",
1639    loadingAudio: "Cargando audio...",
1640    lobby: "Sala",
1641    players: "JUGADORES",
1642    difficulty: "DIFICULTAD",
1643    stageTheme: "ESCENARIO",
1644    speed: "VELOCIDAD",
1645    speedSlow: "0.8x (Lento)",
1646    speedNormal: "1.0x (Normal)",
1647    speedFast: "1.2x (Rápido)",
1648    speedVeryFast: "1.5x (Muy rápido)",
1649    mode: "MODO DE JUEGO",
1650    competitive: "Batalla (Competitivo)",
1651    cooperative: "Equipo (Cooperativo)",
1652    keyboardControls: "CONTROLES DE TECLADO",
1653    keyTip: "¡Pulsa ambas teclas Don al mismo tiempo para el Gran Don!",
1654    don: "DON",
1655    ka: "KA",
1656    drumroll: "REDOMBLE",
1657    balloon: "¡GLOBO!",
1658    perfect: "¡PERFECTO!",
1659    good: "BIEN",
1660    miss: "FALLO",
1661    combo: "COMBO",
1662    score: "Puntuación",
1663    maxCombo: "Combo Máx",
1664    accuracy: "Precisión",
1665    source: "Origen",
1666    sourceDrums: "Batería MIDI",
1667    sourceMelody: "Ritmo Melódico",
1668    sourceBeatGrid: "Rejilla de Compás",
1669    resultsTitle: "¡ESCENARIO SUPERADO!",
1670    clearCrown: "SUPERADO",
1671    silverCrown: "COMBO COMPLETO",
1672    goldCrown: "TODO PERFECTO",
1673    retry: "JUGAR DE NUEVO",
1674    shareScore: "COMPARTIR RESULTADO",
1675    kantan: "FÁCIL",
1676    futsuu: "NORMAL",
1677    muzukashii: "DIFÍCIL",
1678    oni: "EXTREMO",
1679    themeMatsuri: "🏮 Fiesta de Farolillos",
1680    themeSakura: "🌸 Río de Cerezos",
1681    themeThunder: "⚡ Monte del Trueno",
1682    themeNeon: "🌆 Neón Neo-Tokio",
1683    themeRandom: "🎲 Aleatorio (Ruleta)",
1684    themeBamboo: "🎋 Bambú bajo la Luna",
1685    themeAutumnTemple: "🍁 Pagoda de Otoño",
1686    themeOceanSunset: "🌊 Gran Ola Ukiyo-e",
1687    themeDojo: "🏯 Gran Dojo Taiko",
1688    themeSnowVillage: "❄️ Aldea Nevada",
1689    themeShintoShrine: "⛩️ Mil Torii Sagrados",
1690    themeSpaceGalaxy: "🌌 Nebulosa Cósmica",
1691    themeRetroArcade: "🕹️ Salón Arcade Retro",
1692    themeDragonVolcano: "🐉 Volcán del Dragón",
1693    themeCastleNight: "🏯 Castillo de Luna y Cerezos",
1694    themeZenGarden: "🪨 Jardín Zen de Kioto",
1695    themeCarnival: "🎪 Carnaval Taiko Dorado",
1696    p1Name: "Jugador 1",
1697    p2Name: "Jugador 2",
1698    p3Name: "Jugador 3",
1699    p4Name: "Jugador 4",
1700    restart: "Reiniciar",
1701    play: "Reproducir",
1702    pause: "Pausar",
1703    setup: "Ajustes",
1704    topTaikoMasters: "MEJORES MAESTROS TAIKO",
1705    leaderboard: "Clasificación",
1706    noHighscoresYet: "¡Sin puntuaciones aún!",
1707    beFirstToSetRecord: "¡Sé el primer maestro Taiko en establecer un récord!",
1708    emptySlot: "-",
1709    submittingScore: "Enviando puntuación...",
1710    points: "pts",
1711    winner: "GANADOR",
1712    teamTotalScore: "Puntuación Total del Equipo",
1713    teamMaxCombo: "Combo Máximo del Equipo",
1714    greatLabel: "良 (Excelente)",
1715    goodLabel: "可 (Bien)",
1716    missLabel: "不可 (Fallo)",
1717    rollsLabel: "連打 (Redobles)",
1718    competitiveBattle: "Batalla Competitiva",
1719    cooperativeTeam: "Equipo Cooperativo",
1720    pressFOrJNow: "¡Pulsa F o J ahora!",
1721    donCenter: "DON (Centro)",
1722    tapDrumheadCenter: "Golpea el centro del parche",
1723    pressFAndJNow: "¡Pulsa F + J ahora!",
1724    bigDonBothHands: "GRAN DON (Dos manos)",
1725    strikeCenterBothHands: "¡Golpea el centro con ambas manos!",
1726    pressDAndKNow: "¡Pulsa D + K ahora!",
1727    bigKaBothRims: "GRAN KA (Dos manos)",
1728    strikeRimsBothHands: "¡Golpea el borde con ambas manos!",
1729    pressDOrKNow: "¡Pulsa D o K ahora!",
1730    kaRim: "KA (Borde)",
1731    tapDrumRimEdge: "Golpea el borde del tambor",
1732    mashFAndJFast: "¡Repite F y J rápido!",
1733    drumrollTitle: "REDOBLE",
1734    alternateRapidStrikes: "¡Golpes rápidos alternados!",
1735    touchDrum: "Tambor Táctil",
1736    touchDrumPrompt: "¡Toca los tambores para tocar!",
1737    tabletRecommended: "Se recomienda tableta o teclado para 2 jugadores",
1738    touchKaLeft: "Ka Izq",
1739    touchDonLeft: "Don Izq",
1740    touchDonRight: "Don Der",
1741    touchKaRight: "Ka Der",
1742    improvementSupport: "Informe de Rendimiento",
1743    section: "Sección",
1744    practiceSection: "Practicar esta sección",
1745    coachFullCombo: "¡Increíble! ¡Combo completo impecable de principio a fin! ¡Eres un verdadero Maestro Taiko!",
1746    coachCleared: "¡Nivel superado! La sección {{sec}} tuvo algunas notas difíciles. ¡Practícala para lograr el Combo Completo!",
1747    coachFailed: "¡Ánimo! ¡No te rindas! ¡Concéntrate en la sección {{sec}} y lo lograrás la próxima vez!",
1748    coachCoop: "¡Gran trabajo en equipo! ¡Ambos tocaron en perfecta armonía!",
1749    tamashii: "Alma",
1750    clearThreshold: "Superado 80%",
1751    drumrolls: "Redobles",
1752    performanceTab: "Informe",
1753    leaderboardTab: "Clasificación"
1754  },
1755  fr: {
1756    gameTitle: "Taiko no Hamie",
1757    gameSubtitle: "VISUALISEUR DE RYTHME TAIKO",
1758    start: "COMMENCER",
1759    ready: "PRÊT",
1760    loadingAudio: "Chargement de l'audio...",
1761    lobby: "Salon",
1762    players: "JOUEURS",
1763    difficulty: "DIFFICULTÉ",
1764    stageTheme: "THÈME DU DÉCOR",
1765    speed: "VITESSE",
1766    speedSlow: "0.8x (Lent)",
1767    speedNormal: "1.0x (Normal)",
1768    speedFast: "1.2x (Rapide)",
1769    speedVeryFast: "1.5x (Très rapide)",
1770    mode: "MODE DE JEU",
1771    competitive: "Bataille (Compétitif)",
1772    cooperative: "Équipe (Coopératif)",
1773    keyboardControls: "CONTRÔLES DU CLAVIER",
1774    keyTip: "Appuyez sur les deux touches Don en même temps pour le Gros Don !",
1775    don: "DON",
1776    ka: "KA",
1777    drumroll: "ROULEMENT",
1778    balloon: "BALLON !",
1779    perfect: "PARFAIT !",
1780    good: "BIEN",
1781    miss: "MANQUÉ",
1782    combo: "COMBO",
1783    score: "Score",
1784    maxCombo: "Combo Max",
1785    accuracy: "Précision",
1786    source: "Source",
1787    sourceDrums: "Batterie MIDI",
1788    sourceMelody: "Rythme mélodique",
1789    sourceBeatGrid: "Grille de tempo",
1790    resultsTitle: "MORCEAU TERMINÉ !",
1791    clearCrown: "RÉUSSI",
1792    silverCrown: "COMBO COMPLET",
1793    goldCrown: "TOUT PARFAIT",
1794    retry: "REJOUER",
1795    shareScore: "PARTAGER LE SCORE",
1796    kantan: "FACILE",
1797    futsuu: "NORMAL",
1798    muzukashii: "DIFFICILE",
1799    oni: "EXTRÊME",
1800    themeMatsuri: "🏮 Fête des Lanternes",
1801    themeSakura: "🌸 Rivière des Cerisiers",
1802    themeThunder: "⚡ Montagne du Tonnerre",
1803    themeNeon: "🌆 Néo-Tokyo Néon",
1804    themeRandom: "🎲 Aléatoire (Mélange)",
1805    themeBamboo: "🎋 Forêt de Bambous Lunaires",
1806    themeAutumnTemple: "🍁 Pagode d'Automne",
1807    themeOceanSunset: "🌊 Grande Vague Ukiyo-e",
1808    themeDojo: "🏯 Grand Dojo Taiko",
1809    themeSnowVillage: "❄️ Village Sous la Neige",
1810    themeShintoShrine: "⛩️ Allée des Mille Torii",
1811    themeSpaceGalaxy: "🌌 Nébuleuse Cosmique",
1812    themeRetroArcade: "🕹️ Arcade Rétro",
1813    themeDragonVolcano: "🐉 Volcan du Dragon",
1814    themeCastleNight: "🏯 Château au Clair de Lune",
1815    themeZenGarden: "🪨 Jardin Zen de Kyoto",
1816    themeCarnival: "🎪 Festival Carnaval Taiko",
1817    p1Name: "Joueur 1",
1818    p2Name: "Joueur 2",
1819    p3Name: "Joueur 3",
1820    p4Name: "Joueur 4",
1821    restart: "Recommencer",
1822    play: "Lecture",
1823    pause: "Pause",
1824    setup: "Configuration",
1825    topTaikoMasters: "TOP MAÎTRES TAIKO",
1826    leaderboard: "Classement",
1827    noHighscoresYet: "Aucun record pour le moment !",
1828    beFirstToSetRecord: "Soyez le premier maître Taiko à établir un record !",
1829    emptySlot: "-",
1830    submittingScore: "Envoi du score...",
1831    points: "pts",
1832    winner: "GAGNANT",
1833    teamTotalScore: "Score Total de l'Équipe",
1834    teamMaxCombo: "Combo Max de l'Équipe",
1835    greatLabel: "良 (Parfait)",
1836    goodLabel: "可 (Bien)",
1837    missLabel: "不可 (Manqué)",
1838    rollsLabel: "連打 (Roulements)",
1839    competitiveBattle: "Bataille Compétitive",
1840    cooperativeTeam: "Équipe Coopérative",
1841    pressFOrJNow: "Appuyez sur F ou J !",
1842    donCenter: "DON (Centre)",
1843    tapDrumheadCenter: "Frappez le centre du tambour",
1844    pressFAndJNow: "Appuyez sur F + J !",
1845    bigDonBothHands: "GRAND DON (Deux mains)",
1846    strikeCenterBothHands: "Frappez le centre à deux mains !",
1847    pressDAndKNow: "Appuyez sur D + K !",
1848    bigKaBothRims: "GRAND KA (Deux mains)",
1849    strikeRimsBothHands: "Frappez le rebord à deux mains !",
1850    pressDOrKNow: "Appuyez sur D ou K !",
1851    kaRim: "KA (Rebord)",
1852    tapDrumRimEdge: "Frappez le rebord du tambour",
1853    mashFAndJFast: "Mitraillez F et J vite !",
1854    drumrollTitle: "ROULEMENT",
1855    alternateRapidStrikes: "Frappes rapides alternées !",
1856    touchDrum: "Tambour Tactile",
1857    touchDrumPrompt: "Touchez le tambour pour jouer !",
1858    tabletRecommended: "Tablette ou clavier recommandé pour le mode 2 joueurs",
1859    touchKaLeft: "Ka Gauche",
1860    touchDonLeft: "Don Gauche",
1861    touchDonRight: "Don Droit",
1862    touchKaRight: "Ka Droit",
1863    improvementSupport: "Rapport de Performance",
1864    section: "Section",
1865    practiceSection: "S'entraîner sur cette section",
1866    coachFullCombo: "Incroyable ! Combo complet parfait du début à la fin ! Vous êtes un véritable Maître Taiko !",
1867    coachCleared: "Morceau réussi ! La section {{sec}} comportait quelques notes délicates. Entraînez-vous pour le Combo Complet !",
1868    coachFailed: "Courage ! N'abandonnez pas ! Révisez la section {{sec}} et vous réussirez la prochaine fois !",
1869    coachCoop: "Superbe travail d'équipe ! Les deux joueurs étaient parfaitement synchronisés !",
1870    tamashii: "Âme",
1871    clearThreshold: "Succès 80%",
1872    drumrolls: "Roulements",
1873    performanceTab: "Rapport",
1874    leaderboardTab: "Classement"
1875  },
1876  de: {
1877    gameTitle: "Taiko no Hamie",
1878    gameSubtitle: "TAIKO-RHYTHMUS-VISUALISIERER",
1879    start: "SPIEL STARTEN",
1880    ready: "BEREIT",
1881    loadingAudio: "Audio wird geladen...",
1882    lobby: "Lobby",
1883    players: "SPIELER",
1884    difficulty: "SCHWIERIGKEIT",
1885    stageTheme: "BÜHNENTHEMA",
1886    speed: "GESCHWINDIGKEIT",
1887    speedSlow: "0.8x (Langsam)",
1888    speedNormal: "1.0x (Normal)",
1889    speedFast: "1.2x (Flott)",
1890    speedVeryFast: "1.5x (Schnell)",
1891    mode: "SPIELMODUS",
1892    competitive: "Wettkampf (Duell)",
1893    cooperative: "Team (Koop)",
1894    keyboardControls: "TASTATUR-STEUERUNG",
1895    keyTip: "Drücke beide Don-Tasten gleichzeitig für den Großen Don!",
1896    don: "DON",
1897    ka: "KA",
1898    drumroll: "WIRBEL",
1899    balloon: "BALLON!",
1900    perfect: "PERFEKT!",
1901    good: "GUT",
1902    miss: "FEHLER",
1903    combo: "COMBO",
1904    score: "Punkte",
1905    maxCombo: "Max Combo",
1906    accuracy: "Genauigkeit",
1907    source: "Quelle",
1908    sourceDrums: "MIDI-Schlagzeug",
1909    sourceMelody: "Melodie-Rhythmus",
1910    sourceBeatGrid: "Takt-Raster",
1911    resultsTitle: "LEVEL GESCHAFFT!",
1912    clearCrown: "GESCHAFFT",
1913    silverCrown: "FULL COMBO",
1914    goldCrown: "ALLES PERFEKT",
1915    retry: "ERNEUT SPIELEN",
1916    shareScore: "PUNKTE TEILEN",
1917    kantan: "LEICHT",
1918    futsuu: "NORMAL",
1919    muzukashii: "SCHWER",
1920    oni: "EXTREM",
1921    themeMatsuri: "🏮 Laternenfest",
1922    themeSakura: "🌸 Kirschblüten-Fluss",
1923    themeThunder: "⚡ Donnerberg",
1924    themeNeon: "🌆 Neo-Tokio Neon",
1925    themeRandom: "🎲 Zufällig (Mix)",
1926    themeBamboo: "🎋 Bambuswald im Mondlicht",
1927    themeAutumnTemple: "🍁 Herbst-Pagode",
1928    themeOceanSunset: "🌊 Ukiyo-e Riesenwelle",
1929    themeDojo: "🏯 Taiko-Großdojo",
1930    themeSnowVillage: "❄️ Verschneites Dorf",
1931    themeShintoShrine: "⛩️ Tausend Torii-Tore",
1932    themeSpaceGalaxy: "🌌 Kosmische Raumstation",
1933    themeRetroArcade: "🕹️ Retro-Spielhalle",
1934    themeDragonVolcano: "🐉 Drachen-Vulkan",
1935    themeCastleNight: "🏯 Mondschein-Burg",
1936    themeZenGarden: "🪨 Kyoto Zen-Garten",
1937    themeCarnival: "🎪 Goldenes Taiko-Festival",
1938    p1Name: "Spieler 1",
1939    p2Name: "Spieler 2",
1940    p3Name: "Spieler 3",
1941    p4Name: "Spieler 4",
1942    restart: "Neustart",
1943    play: "Wiedergabe",
1944    pause: "Pause",
1945    setup: "Setup",
1946    topTaikoMasters: "TOP TAIKO-MEISTER",
1947    leaderboard: "Bestenliste",
1948    noHighscoresYet: "Noch keine Rekorde vorhanden!",
1949    beFirstToSetRecord: "Sei der erste Taiko-Meister, der einen Rekord aufstellt!",
1950    emptySlot: "-",
1951    submittingScore: "Punktzahl wird übermittelt...",
1952    points: "Pkt.",
1953    winner: "SIEGER",
1954    teamTotalScore: "Gesamtpunktzahl des Teams",
1955    teamMaxCombo: "Maximaler Team-Combo",
1956    greatLabel: "良 (Perfekt)",
1957    goodLabel: "可 (Gut)",
1958    missLabel: "不可 (Verfehlt)",
1959    rollsLabel: "連打 (Trommelwirbel)",
1960    competitiveBattle: "Wettkampf-Schlacht",
1961    cooperativeTeam: "Kooperatives Team",
1962    pressFOrJNow: "Jetzt F oder J drücken!",
1963    donCenter: "DON (Mitte)",
1964    tapDrumheadCenter: "Mitte des Trommelfells treffen",
1965    pressFAndJNow: "Jetzt F + J drücken!",
1966    bigDonBothHands: "GROSSER DON (Beide Hände)",
1967    strikeCenterBothHands: "Mit beiden Händen in die Mitte schlagen!",
1968    pressDAndKNow: "Jetzt D + K drücken!",
1969    bigKaBothRims: "GROSSER KA (Beide Hände)",
1970    strikeRimsBothHands: "Mit beiden Händen auf den Rand schlagen!",
1971    pressDOrKNow: "Jetzt D oder K drücken!",
1972    kaRim: "KA (Rand)",
1973    tapDrumRimEdge: "Auf den Trommelrand schlagen",
1974    mashFAndJFast: "F & J schnell hämmern!",
1975    drumrollTitle: "WIRBEL",
1976    alternateRapidStrikes: "Schnelle abwechselnde Schläge!",
1977    touchDrum: "Touch-Trommel",
1978    touchDrumPrompt: "Trommelpads berühren zum Spielen!",
1979    tabletRecommended: "Tablet oder Tastatur für 2 Spieler empfohlen",
1980    touchKaLeft: "Links Ka",
1981    touchDonLeft: "Links Don",
1982    touchDonRight: "Rechts Don",
1983    touchKaRight: "Rechts Ka",
1984    improvementSupport: "Leistungsbericht",
1985    section: "Abschnitt",
1986    practiceSection: "Diesen Abschnitt üben",
1987    coachFullCombo: "Unglaublich! Perfekte Full Combo von Anfang bis Ende! Ein wahrer Taiko-Meister!",
1988    coachCleared: "Level geschafft! In Abschnitt {{sec}} gab es ein paar knifflige Noten. Übe diesen Teil für die Full Combo!",
1989    coachFailed: "Kopf hoch! Nicht aufgeben! Konzentriere dich auf Abschnitt {{sec}}, dann klappt es beim nächsten Mal!",
1990    coachCoop: "Großartiges Teamwork! Beide Spieler waren perfekt im Takt!",
1991    tamashii: "Seele",
1992    clearThreshold: "Geschafft 80%",
1993    drumrolls: "Wirbel",
1994    performanceTab: "Bericht",
1995    leaderboardTab: "Bestenliste"
1996  },
1997  it: {
1998    gameTitle: "Taiko no Hamie",
1999    gameSubtitle: "VISUALIZZATORE DI RITMO TAIKO",
2000    start: "INIZIA GIOCO",
2001    ready: "PRONTO",
2002    loadingAudio: "Caricamento audio...",
2003    lobby: "Atrio",
2004    players: "GIOCATORI",
2005    difficulty: "DIFFICOLTÀ",
2006    stageTheme: "TEMA DEL PALCO",
2007    speed: "VELOCITÀ",
2008    speedSlow: "0.8x (Lento)",
2009    speedNormal: "1.0x (Normale)",
2010    speedFast: "1.2x (Veloce)",
2011    speedVeryFast: "1.5x (Molto veloce)",
2012    mode: "MODALITÀ DI GIOCO",
2013    competitive: "Battaglia (Competitivo)",
2014    cooperative: "Squadra (Cooperativo)",
2015    keyboardControls: "CONTROLLI DA TASTIERA",
2016    keyTip: "Premi entrambi i tasti Don contemporaneamente per il Grande Don!",
2017    don: "DON",
2018    ka: "KA",
2019    drumroll: "RULLO",
2020    balloon: "PALLONCINO!",
2021    perfect: "PERFETTO!",
2022    good: "BUONO",
2023    miss: "MANCATO",
2024    combo: "COMBO",
2025    score: "Punteggio",
2026    maxCombo: "Combo Max",
2027    accuracy: "Precisione",
2028    source: "Sorgente",
2029    sourceDrums: "Batteria MIDI",
2030    sourceMelody: "Ritmo Melodico",
2031    sourceBeatGrid: "Griglia di Battuta",
2032    resultsTitle: "BRANO COMPLETATO!",
2033    clearCrown: "SUPERATO",
2034    silverCrown: "FULL COMBO",
2035    goldCrown: "TUTTO PERFETTO",
2036    retry: "GIOCA ANCORA",
2037    shareScore: "CONDIVIDI PUNTEGGIO",
2038    kantan: "FACILE",
2039    futsuu: "NORMALE",
2040    muzukashii: "DIFFICILE",
2041    oni: "ESTREMO",
2042    themeMatsuri: "🏮 Festival delle Lanterne",
2043    themeSakura: "🌸 Fiume dei Ciliegi",
2044    themeThunder: "⚡ Monte del Tuono",
2045    themeNeon: "🌆 Neo-Tokyo Neon",
2046    themeRandom: "🎲 Casuale (Mix)",
2047    themeBamboo: "🎋 Foresta di Bambù e Luna",
2048    themeAutumnTemple: "🍁 Pagoda d'Autunno",
2049    themeOceanSunset: "🌊 Onda Ukiyo-e al Tramonto",
2050    themeDojo: "🏯 Grande Dojo Taiko",
2051    themeSnowVillage: "❄️ Villaggio Innevato",
2052    themeShintoShrine: "⛩️ Sentiero dei Mille Torii",
2053    themeSpaceGalaxy: "🌌 Stazione Spaziale Cosmica",
2054    themeRetroArcade: "🕹️ Sala Giochi Vintage",
2055    themeDragonVolcano: "🐉 Vulcano del Drago",
2056    themeCastleNight: "🏯 Castello al Chiaro di Luna",
2057    themeZenGarden: "🪨 Giardino Zen di Kyoto",
2058    themeCarnival: "🎪 Carnevale d'Oro Taiko",
2059    p1Name: "Giocatore 1",
2060    p2Name: "Giocatore 2",
2061    p3Name: "Giocatore 3",
2062    p4Name: "Giocatore 4",
2063    restart: "Riavvia",
2064    play: "Riproduci",
2065    pause: "Pausa",
2066    setup: "Configura",
2067    topTaikoMasters: "TOP MAESTRI TAIKO",
2068    leaderboard: "Classifica",
2069    noHighscoresYet: "Nessun record registrato!",
2070    beFirstToSetRecord: "Sii il primo Maestro Taiko a segnare un record!",
2071    emptySlot: "-",
2072    submittingScore: "Invio punteggio...",
2073    points: "pt",
2074    winner: "VINCITORE",
2075    teamTotalScore: "Punteggio Totale Squadra",
2076    teamMaxCombo: "Combo Massimo Squadra",
2077    greatLabel: "良 (Ottimo)",
2078    goodLabel: "可 (Buono)",
2079    missLabel: "不可 (Mancato)",
2080    rollsLabel: "連打 (Rulli)",
2081    competitiveBattle: "Sfida Competitiva",
2082    cooperativeTeam: "Squadra Cooperativa",
2083    pressFOrJNow: "Premi F o J adesso!",
2084    donCenter: "DON (Centro)",
2085    tapDrumheadCenter: "Colpisci il centro del tamburo",
2086    pressFAndJNow: "Premi F + J adesso!",
2087    bigDonBothHands: "GRAN DON (Due mani)",
2088    strikeCenterBothHands: "Colpisci il centro con entrambe le mani!",
2089    pressDAndKNow: "Premi D + K adesso!",
2090    bigKaBothRims: "GRAN KA (Due mani)",
2091    strikeRimsBothHands: "Colpisci il bordo con entrambe le mani!",
2092    pressDOrKNow: "Premi D o K adesso!",
2093    kaRim: "KA (Bordo)",
2094    tapDrumRimEdge: "Colpisci il bordo del tamburo",
2095    mashFAndJFast: "Premi F e J velocemente!",
2096    drumrollTitle: "RULLO",
2097    alternateRapidStrikes: "Colpi rapidi alternati!",
2098    touchDrum: "Tamburo Touch",
2099    touchDrumPrompt: "Tocca i tamburi per suonare!",
2100    tabletRecommended: "Tablet o tastiera consigliati per la modalità a 2 giocatori",
2101    touchKaLeft: "Ka Sinistro",
2102    touchDonLeft: "Don Sinistro",
2103    touchDonRight: "Don Destro",
2104    touchKaRight: "Ka Destro",
2105    improvementSupport: "Report Prestazioni",
2106    section: "Sezione",
2107    practiceSection: "Esercitati su questa sezione",
2108    coachFullCombo: "Incredibile! Combo completa impeccabile dall'inizio alla fine! Sei un vero Maestro Taiko!",
2109    coachCleared: "Brano superato! La sezione {{sec}} conteneva note difficili. Esercitati per la Combo Completa!",
2110    coachFailed: "Coraggio! Non mollare! Rivedi la sezione {{sec}} e la prossima volta ce la farai!",
2111    coachCoop: "Ottimo lavoro di squadra! I due tamburi erano perfettamente all'unisono!",
2112    tamashii: "Anima",
2113    clearThreshold: "Completato 80%",
2114    drumrolls: "Rulli",
2115    performanceTab: "Report",
2116    leaderboardTab: "Classifica"
2117  },
2118  pt: {
2119    gameTitle: "Taiko no Hamie",
2120    gameSubtitle: "VISUALIZADOR DE RITMO TAIKO",
2121    start: "INICIAR JOGO",
2122    ready: "PRONTO",
2123    loadingAudio: "Carregando áudio...",
2124    lobby: "Saguão",
2125    players: "JOGADORES",
2126    difficulty: "DIFICULDADE",
2127    stageTheme: "TEMA DO CENÁRIO",
2128    speed: "VELOCIDADE",
2129    speedSlow: "0.8x (Lento)",
2130    speedNormal: "1.0x (Normal)",
2131    speedFast: "1.2x (Rápido)",
2132    speedVeryFast: "1.5x (Muito Rápido)",
2133    mode: "MODO DE JUEGO",
2134    competitive: "Batalha (Competitivo)",
2135    cooperative: "Equipe (Cooperativo)",
2136    keyboardControls: "CONTROLES DO TECLADO",
2137    keyTip: "Pressione as duas teclas Don ao mesmo tempo para o Grande Don!",
2138    don: "DON",
2139    ka: "KA",
2140    drumroll: "RUFLO",
2141    balloon: "BALÃO!",
2142    perfect: "PERFEITO!",
2143    good: "BOM",
2144    miss: "ERRO",
2145    combo: "COMBO",
2146    score: "Pontuação",
2147    maxCombo: "Combo Máx",
2148    accuracy: "Precisão",
2149    source: "Origem",
2150    sourceDrums: "Bateria MIDI",
2151    sourceMelody: "Ritmo Melódico",
2152    sourceBeatGrid: "Grade de Batida",
2153    resultsTitle: "MÚSICA CONCLUÍDA!",
2154    clearCrown: "CONCLUÍDO",
2155    silverCrown: "COMBO COMPLETO",
2156    goldCrown: "TUDO PERFEITO",
2157    retry: "JOGAR NOVAMENTE",
2158    shareScore: "COMPARTILHAR PONTUAÇÃO",
2159    kantan: "FÁCIL",
2160    futsuu: "NORMAL",
2161    muzukashii: "DIFÍCIL",
2162    oni: "EXTREMO",
2163    themeMatsuri: "🏮 Festa das Lanternas",
2164    themeSakura: "🌸 Rio das Cerejeiras",
2165    themeThunder: "⚡ Montanha do Trovão",
2166    themeNeon: "🌆 Neo-Tóquio Neon",
2167    themeRandom: "🎲 Aleatório (Sorteio)",
2168    themeBamboo: "🎋 Bosque de Bambu ao Luar",
2169    themeAutumnTemple: "🍁 Pagode de Outono",
2170    themeOceanSunset: "🌊 Grande Onda Ukiyo-e",
2171    themeDojo: "🏯 Grande Dojô Taiko",
2172    themeSnowVillage: "❄️ Vila Coberta de Neve",
2173    themeShintoShrine: "⛩️ Caminho dos Mil Torii",
2174    themeSpaceGalaxy: "🌌 Estação Espacial Cósmica",
2175    themeRetroArcade: "🕹️ Fliperama Retrô",
2176    themeDragonVolcano: "🐉 Vulcão do Dragão Dourado",
2177    themeCastleNight: "🏯 Castelo ao Luar",
2178    themeZenGarden: "🪨 Jardim Zen de Quioto",
2179    themeCarnival: "🎪 Grande Carnaval Taiko",
2180    p1Name: "Jogador 1",
2181    p2Name: "Jogador 2",
2182    p3Name: "Jogador 3",
2183    p4Name: "Jogador 4",
2184    restart: "Reiniciar",
2185    play: "Reproduzir",
2186    pause: "Pausar",
2187    setup: "Configurações",
2188    topTaikoMasters: "MELHORES MESTRES TAIKO",
2189    leaderboard: "Classificação",
2190    noHighscoresYet: "Nenhum recorde registrado!",
2191    beFirstToSetRecord: "Seja o primeiro Mestre Taiko a bater o recorde!",
2192    emptySlot: "-",
2193    submittingScore: "Enviando pontuação...",
2194    points: "pts",
2195    winner: "VENCEDOR",
2196    teamTotalScore: "Pontuação Total da Equipe",
2197    teamMaxCombo: "Combo Máximo da Equipe",
2198    greatLabel: "良 (Excelente)",
2199    goodLabel: "可 (Bom)",
2200    missLabel: "不可 (Erro)",
2201    rollsLabel: "連打 (Rufos)",
2202    competitiveBattle: "Batalha Competitiva",
2203    cooperativeTeam: "Equipe Cooperativa",
2204    pressFOrJNow: "Pressione F ou J agora!",
2205    donCenter: "DON (Centro)",
2206    tapDrumheadCenter: "Toque no centro da pele",
2207    pressFAndJNow: "Pressione F + J agora!",
2208    bigDonBothHands: "GRANDE DON (Ambas as mãos)",
2209    strikeCenterBothHands: "Bata no centro com as duas mãos!",
2210    pressDAndKNow: "Pressione D + K agora!",
2211    bigKaBothRims: "GRANDE KA (Ambas as mãos)",
2212    strikeRimsBothHands: "Bata no aro com as duas mãos!",
2213    pressDOrKNow: "Pressione D ou K agora!",
2214    kaRim: "KA (Aro)",
2215    tapDrumRimEdge: "Toque no aro do tambor",
2216    mashFAndJFast: "Aperte F e J rápido!",
2217    drumrollTitle: "RUFO",
2218    alternateRapidStrikes: "Batidas rápidas alternadas!",
2219    touchDrum: "Tambor Tátil",
2220    touchDrumPrompt: "Toque no tambor para jogar!",
2221    tabletRecommended: "Tablet ou teclado recomendado para 2 jogadores",
2222    touchKaLeft: "Ka Esq",
2223    touchDonLeft: "Don Esq",
2224    touchDonRight: "Don Dir",
2225    touchKaRight: "Ka Dir",
2226    improvementSupport: "Relatório de Desempenho",
2227    section: "Seção",
2228    practiceSection: "Praticar esta seção",
2229    coachFullCombo: "Incrível! Combo completo impecável do início ao fim! Você é um verdadeiro Mestre Taiko!",
2230    coachCleared: "Música concluída! A seção {{sec}} teve algumas notas difíceis. Pratique para alcançar o Combo Completo!",
2231    coachFailed: "Ânimo! Não desista! Foque na seção {{sec}} e você passará na próxima vez!",
2232    coachCoop: "Ótimo trabalho em equipe! Ambos tocaram em perfeita harmonia!",
2233    tamashii: "Alma",
2234    clearThreshold: "Concluído 80%",
2235    drumrolls: "Rufos",
2236    performanceTab: "Relatório",
2237    leaderboardTab: "Placar"
2238  },
2239  ru: {
2240    gameTitle: "Тайко но Хами",
2241    gameSubtitle: "РИТМ-ВИЗУАЛИЗАТОР БАРАБАНОВ",
2242    start: "НАЧАТЬ ИГРУ",
2243    ready: "ГОТОВ",
2244    loadingAudio: "Загрузка аудио...",
2245    lobby: "Лобби",
2246    players: "ИГРОКИ",
2247    difficulty: "СЛОЖНОСТЬ",
2248    stageTheme: "ТЕМА СЦЕНЫ",
2249    speed: "СКОРОСТЬ",
2250    speedSlow: "0.8x (Медленно)",
2251    speedNormal: "1.0x (Обычно)",
2252    speedFast: "1.2x (Быстро)",
2253    speedVeryFast: "1.5x (Очень быстро)",
2254    mode: "РЕЖИМ ИГРЫ",
2255    competitive: "Битва (Соревнование)",
2256    cooperative: "Команда (Кооп)",
2257    keyboardControls: "УПРАВЛЕНИЕ КЛАВИАТУРОЙ",
2258    keyTip: "Нажмите обе клавиши Дон одновременно для Большого Дона!",
2259    don: "ДОН",
2260    ka: "КА",
2261    drumroll: "ДРОБЬ",
2262    balloon: "ШАРИК!",
2263    perfect: "ОТЛИЧНО!",
2264    good: "ХОРОШО",
2265    miss: "ПРОМАХ",
2266    combo: "КОМБО",
2267    score: "Счет",
2268    maxCombo: "Макс. комбо",
2269    accuracy: "Точность",
2270    source: "Источник",
2271    sourceDrums: "MIDI-барабаны",
2272    sourceMelody: "Ритм мелодии",
2273    sourceBeatGrid: "Сетка тактов",
2274    resultsTitle: "ТРЕК ПРОЙДЕН!",
2275    clearCrown: "ПРОЙДЕНО",
2276    silverCrown: "ПОЛНОЕ КОМБО",
2277    goldCrown: "ВСЁ ИДЕАЛЬНО",
2278    retry: "ИГРАТЬ СНОВА",
2279    shareScore: "ПОДЕЛИТЬСЯ СЧЕТОМ",
2280    kantan: "ЛЕГКО",
2281    futsuu: "НОРМАЛЬНО",
2282    muzukashii: "СЛОЖНО",
2283    oni: "ОНИ (ЭКСТРИМ)",
2284    themeMatsuri: "🏮 Фестиваль фонариков",
2285    themeSakura: "🌸 Сакура и Фудзи",
2286    themeThunder: "⚡ Громовая гора",
2287    themeNeon: "🌆 Нео-Токио Неон",
2288    themeRandom: "🎲 Случайный выбор",
2289    themeBamboo: "🎋 Бамбуковая роща под луной",
2290    themeAutumnTemple: "🍁 Осенняя пагода",
2291    themeOceanSunset: "🌊 Волна Укиё-э на закате",
2292    themeDojo: "🏯 Великое додзё тайко",
2293    themeSnowVillage: "❄️ Заснеженная деревня",
2294    themeShintoShrine: "⛩️ Аллея тысячи тории",
2295    themeSpaceGalaxy: "🌌 Космическая туманность",
2296    themeRetroArcade: "🕹️ Ретро-игровой зал",
2297    themeDragonVolcano: "🐉 Вулканический пик дракона",
2298    themeCastleNight: "🏯 Лунный замок самураев",
2299    themeZenGarden: "🪨 Дзен-сад камней в Киото",
2300    themeCarnival: "🎪 Золотой карнавал тайко",
2301    p1Name: "Игрок 1",
2302    p2Name: "Игрок 2",
2303    p3Name: "Игрок 3",
2304    p4Name: "Игрок 4",
2305    restart: "Перезапуск",
2306    play: "Играть",
2307    pause: "Пауза",
2308    setup: "Настройки",
2309    topTaikoMasters: "ТАБЛИЦА ЛИДЕРОВ ТАЙКО",
2310    leaderboard: "Таблица лидеров",
2311    noHighscoresYet: "Рекордов пока нет!",
2312    beFirstToSetRecord: "Станьте первым мастером Тайко с рекордом!",
2313    emptySlot: "-",
2314    submittingScore: "Отправка результата...",
2315    points: "очков",
2316    winner: "ПОБЕДИТЕЛЬ",
2317    teamTotalScore: "Общий счет команды",
2318    teamMaxCombo: "Макс. комбо команды",
2319    greatLabel: "良 (Отлично)",
2320    goodLabel: "可 (Хорошо)",
2321    missLabel: "不可 (Мимо)",
2322    rollsLabel: "連打 (Дробь)",
2323    competitiveBattle: "Битва (Соревнование)",
2324    cooperativeTeam: "Команда (Кооператив)",
2325    pressFOrJNow: "Нажмите F или J сейчас!",
2326    donCenter: "ДОН (Центр)",
2327    tapDrumheadCenter: "Удар по центру барабана",
2328    pressFAndJNow: "Нажмите F + J вместе!",
2329    bigDonBothHands: "БОЛЬШОЙ ДОН (Две руки)",
2330    strikeCenterBothHands: "Удар по центру двумя руками!",
2331    pressDAndKNow: "Нажмите D + K вместе!",
2332    bigKaBothRims: "БОЛЬШОЙ КА (Две руки)",
2333    strikeRimsBothHands: "Удар по ободу двумя руками!",
2334    pressDOrKNow: "Нажмите D или K сейчас!",
2335    kaRim: "КА (Обод)",
2336    tapDrumRimEdge: "Удар по ободу барабана",
2337    mashFAndJFast: "Быстро жмите F и J!",
2338    drumrollTitle: "ДРОБЬ",
2339    alternateRapidStrikes: "Быстрые чередующиеся удары!",
2340    touchDrum: "Сенсорный барабан",
2341    touchDrumPrompt: "Нажимайте на барабан для игры!",
2342    tabletRecommended: "Для режима на 2 игрока рекомендуется планшет или клавиатура",
2343    touchKaLeft: "Ка Слева",
2344    touchDonLeft: "Дон Слева",
2345    touchDonRight: "Дон Справа",
2346    touchKaRight: "Ка Справа",
2347    improvementSupport: "Отчет об игре",
2348    section: "Секция",
2349    practiceSection: "Тренировать эту секцию",
2350    coachFullCombo: "Невероятно! Безупречное полное комбо от начала до конца! Настоящий мастер Тайко!",
2351    coachCleared: "Трек пройден! В секции {{sec}} были сложные ноты. Потренируйтесь для полного комбо!",
2352    coachFailed: "Не унывайте! Не сдавайтесь! Потренируйте секцию {{sec}}, и в следующий раз всё получится!",
2353    coachCoop: "Отличная командная работа! Оба барабанщика играли в унисон!",
2354    tamashii: "Душа",
2355    clearThreshold: "Пройдено 80%",
2356    drumrolls: "Дроби",
2357    performanceTab: "Отчет",
2358    leaderboardTab: "Таблица лидеров"
2359  },
2360  id: {
2361    gameTitle: "Taiko no Hamie",
2362    gameSubtitle: "VISUALISASI RITME GENDANG TAIKO",
2363    start: "MULAI MAIN",
2364    ready: "SIAP",
2365    loadingAudio: "Memuat audio...",
2366    lobby: "Lobi",
2367    players: "PEMAIN",
2368    difficulty: "TINGKAT KESULITAN",
2369    stageTheme: "TEMA PANGGUNG",
2370    speed: "KECEPATAN",
2371    speedSlow: "0.8x (Lambat)",
2372    speedNormal: "1.0x (Normal)",
2373    speedFast: "1.2x (Cepat)",
2374    speedVeryFast: "1.5x (Sangat Cepat)",
2375    mode: "MODE PERMAINAN",
2376    competitive: "Pertarungan (Kompetitif)",
2377    cooperative: "Tim (Kerja Sama)",
2378    keyboardControls: "KONTROL KEYBOARD",
2379    keyTip: "Tekan kedua tombol Don secara bersamaan untuk Don Besar!",
2380    don: "DON",
2381    ka: "KA",
2382    drumroll: "GULUNGAN",
2383    balloon: "BALON!",
2384    perfect: "SEMPURNA!",
2385    good: "BAGUS",
2386    miss: "LEWAT",
2387    combo: "KOMBO",
2388    score: "Skor",
2389    maxCombo: "Kombo Maks",
2390    accuracy: "Akurasi",
2391    source: "Sumber",
2392    sourceDrums: "Drum MIDI",
2393    sourceMelody: "Ritme Melodi",
2394    sourceBeatGrid: "Kisi Ketukan",
2395    resultsTitle: "LAGU SELESAI!",
2396    clearCrown: "SELESAI",
2397    silverCrown: "KOMBO LENGKAP",
2398    goldCrown: "SEMUA SEMPURNA",
2399    retry: "MAIN LAGI",
2400    shareScore: "BAGIKAN SKOR",
2401    kantan: "MUDAH",
2402    futsuu: "NORMAL",
2403    muzukashii: "SULIT",
2404    oni: "EKSTREM",
2405    themeMatsuri: "🏮 Festival Lentera",
2406    themeSakura: "🌸 Sungai Sakura",
2407    themeThunder: "⚡ Gunung Petir",
2408    themeNeon: "🌆 Neon Neo-Tokyo",
2409    themeRandom: "🎲 Acak (Kocok)",
2410    themeBamboo: "🎋 Hutan Bambu Bulan Purnama",
2411    themeAutumnTemple: "🍁 Pagoda Musim Gugur",
2412    themeOceanSunset: "🌊 Ombak Besar Ukiyo-e",
2413    themeDojo: "🏯 Dojo Agung Taiko",
2414    themeSnowVillage: "❄️ Desa Salju Shirakawa",
2415    themeShintoShrine: "⛩️ Ribuan Gerbang Torii",
2416    themeSpaceGalaxy: "🌌 Stasiun Luar Angkasa",
2417    themeRetroArcade: "🕹️ Game Center Retro",
2418    themeDragonVolcano: "🐉 Gunung Api Naga Emas",
2419    themeCastleNight: "🏯 Istana Rembulan Sakura",
2420    themeZenGarden: "🪨 Taman Batu Zen Kyoto",
2421    themeCarnival: "🎪 Karnaval Akbar Taiko",
2422    p1Name: "Pemain 1",
2423    p2Name: "Pemain 2",
2424    p3Name: "Pemain 3",
2425    p4Name: "Pemain 4",
2426    restart: "Mulai Ulang",
2427    play: "Putar",
2428    pause: "Jeda",
2429    setup: "Pengaturan",
2430    topTaikoMasters: "MASTER TAIKO TERATAS",
2431    leaderboard: "Papan Peringkat",
2432    noHighscoresYet: "Belum ada rekor skor!",
2433    beFirstToSetRecord: "Jadilah Master Taiko pertama yang mencetak rekor!",
2434    emptySlot: "-",
2435    submittingScore: "Mengirimkan skor...",
2436    points: "poin",
2437    winner: "PEMENANG",
2438    teamTotalScore: "Total Skor Tim",
2439    teamMaxCombo: "Kombo Maksimal Tim",
2440    greatLabel: "良 (Luar Biasa)",
2441    goodLabel: "可 (Bagus)",
2442    missLabel: "不可 (Meleset)",
2443    rollsLabel: "連打 (Ketukan Cepat)",
2444    competitiveBattle: "Pertarungan Kompetitif",
2445    cooperativeTeam: "Tim Kooperatif",
2446    pressFOrJNow: "Tekan F atau J sekarang!",
2447    donCenter: "DON (Tengah)",
2448    tapDrumheadCenter: "Ketuk bagian tengah drum",
2449    pressFAndJNow: "Tekan F + J sekarang!",
2450    bigDonBothHands: "DON BESAR (Kedua Tangan)",
2451    strikeCenterBothHands: "Pukul bagian tengah dengan kedua tangan!",
2452    pressDAndKNow: "Tekan D + K sekarang!",
2453    bigKaBothRims: "KA BESAR (Kedua Tangan)",
2454    strikeRimsBothHands: "Pukul bagian pinggir dengan kedua tangan!",
2455    pressDOrKNow: "Tekan D atau K sekarang!",
2456    kaRim: "KA (Pinggir)",
2457    tapDrumRimEdge: "Ketuk pinggiran drum",
2458    mashFAndJFast: "Pukul F & J cepat!",
2459    drumrollTitle: "KETUKAN CEPAT",
2460    alternateRapidStrikes: "Pukulan cepat bergantian!",
2461    touchDrum: "Drum Sentuh",
2462    touchDrumPrompt: "Ketuk drum untuk bermain!",
2463    tabletRecommended: "Tablet atau keyboard disarankan untuk 2 pemain",
2464    touchKaLeft: "Ka Kiri",
2465    touchDonLeft: "Don Kiri",
2466    touchDonRight: "Don Kanan",
2467    touchKaRight: "Ka Kanan",
2468    improvementSupport: "Laporan Performa",
2469    section: "Bagian",
2470    practiceSection: "Latih bagian ini",
2471    coachFullCombo: "Luar biasa! Full combo sempurna dari awal hingga akhir! Kamu adalah Master Taiko sejati!",
2472    coachCleared: "Lagu selesai! Bagian {{sec}} memiliki beberapa nada sulit. Berlatihlah untuk meraih Full Combo!",
2473    coachFailed: "Semangat! Jangan menyerah! Fokus pada Bagian {{sec}} dan kamu pasti bisa melewatinya!",
2474    coachCoop: "Kerja sama yang luar biasa! Kedua pemain bermain dengan ritme yang selaras!",
2475    tamashii: "Jiwa",
2476    clearThreshold: "Selesai 80%",
2477    drumrolls: "Roll drum",
2478    performanceTab: "Laporan",
2479    leaderboardTab: "Papan Peringkat"
2480  },
2481  ar: {
2482    gameTitle: "تايكو نو هامي",
2483    gameSubtitle: "مؤثر إيقاع طبول تايكو",
2484    start: "ابدأ العزف",
2485    ready: "استعد",
2486    loadingAudio: "جارٍ تحميل الصوت...",
2487    lobby: "الردهة",
2488    players: "اللاعبون",
2489    difficulty: "مستوى الصعوبة",
2490    stageTheme: "خلفية المسرح",
2491    speed: "سرعة النغمات",
2492    speedSlow: "0.8x (بطيء)",
2493    speedNormal: "1.0x (عادي)",
2494    speedFast: "1.2x (سريع)",
2495    speedVeryFast: "1.5x (سريع جداً)",
2496    mode: "نمط اللعب",
2497    competitive: "نزال (تنافسي)",
2498    cooperative: "فريق (تعاوني)",
2499    keyboardControls: "إرشادات لوحة المفاتيح",
2500    keyTip: "اضغط على زري دون معاً لضرب نغمة دون الكبيرة!",
2501    don: "دون",
2502    ka: "كا",
2503    drumroll: "دحرجة",
2504    balloon: "بالون!",
2505    perfect: "ممتاز!",
2506    good: "جيد",
2507    miss: "فائت",
2508    combo: "متتالي",
2509    score: "النقاط",
2510    maxCombo: "أعلى تتابع",
2511    accuracy: "الدقة",
2512    source: "المصدر",
2513    sourceDrums: "طبول MIDI",
2514    sourceMelody: "إيقاع اللحن",
2515    sourceBeatGrid: "شبكة الإيقاع",
2516    resultsTitle: "تم إكمال العزف!",
2517    clearCrown: "إكمال",
2518    silverCrown: "تتابع كامل",
2519    goldCrown: "كل النغمات ممتازة",
2520    retry: "العب مجدداً",
2521    shareScore: "مشاركة النتيجة",
2522    kantan: "سهل",
2523    futsuu: "عادي",
2524    muzukashii: "صعب",
2525    oni: "أسطوري",
2526    themeMatsuri: "🏮 مهرجان الفوانيس",
2527    themeSakura: "🌸 نهر أزهار الكرز",
2528    themeThunder: "⚡ قمة البرق",
2529    themeNeon: "🌆 نيو طوكيو نيون",
2530    themeRandom: "🎲 عشوائي (تبديل)",
2531    themeBamboo: "🎋 غابة الخيزران تحت ضوء القمر",
2532    themeAutumnTemple: "🍁 معبد خريفي عتيق",
2533    themeOceanSunset: "🌊 موجة أوكيو-إيه عند الغروب",
2534    themeDojo: "🏯 دوجو التايكو العظيم",
2535    themeSnowVillage: "❄️ قرية ثلجية هادئة",
2536    themeShintoShrine: "⛩️ ممر بوابات توري الألف",
2537    themeSpaceGalaxy: "🌌 محطة فضاء كونية",
2538    themeRetroArcade: "🕹️ صالة ألعاب كلاسيكية",
2539    themeDragonVolcano: "🐉 بركان التنين الذهبي",
2540    themeCastleNight: "🏯 قلعة تحت ضوء القمر والكرز",
2541    themeZenGarden: "🪨 حديقة زن في كيوتو",
2542    themeCarnival: "🎪 مهرجان التايكو الذهبي",
2543    p1Name: "اللاعب 1",
2544    p2Name: "اللاعب 2",
2545    p3Name: "اللاعب 3",
2546    p4Name: "اللاعب 4",
2547    restart: "إعادة البدء",
2548    play: "تشغيل",
2549    pause: "إيقاف مؤقت",
2550    setup: "إعداد",
2551    topTaikoMasters: "أفضل أساتذة التايكو",
2552    leaderboard: "لوحة الصدارة",
2553    noHighscoresYet: "لا توجد أرقام قياسية بعد!",
2554    beFirstToSetRecord: "كن أول أستاذ تايكو يسجل رقماً قياسياً!",
2555    emptySlot: "-",
2556    submittingScore: "جارٍ إرسال النتيجة...",
2557    points: "نقطة",
2558    winner: "الفائز",
2559    teamTotalScore: "إجمالي نقاط الفريق",
2560    teamMaxCombo: "أعلى تتابع للفريق",
2561    greatLabel: "良 (ممتاز)",
2562    goodLabel: "可 (جيد)",
2563    missLabel: "不可 (فائت)",
2564    rollsLabel: "連打 (دحرجة)",
2565    competitiveBattle: "معركة تنافسية",
2566    cooperativeTeam: "فريق تعاوني",
2567    pressFOrJNow: "اضغط F أو J الآن!",
2568    donCenter: "دون (المركز)",
2569    tapDrumheadCenter: "اضرب مركز الطبل",
2570    pressFAndJNow: "اضغط F + J معاً!",
2571    bigDonBothHands: "دون كبير (باليدين)",
2572    strikeCenterBothHands: "اضرب المركز بكلتا اليدين!",
2573    pressDAndKNow: "اضغط D + K معاً!",
2574    bigKaBothRims: "كا كبير (باليدين)",
2575    strikeRimsBothHands: "اضرب الحافة بكلتا اليدين!",
2576    pressDOrKNow: "اضغط D أو K الآن!",
2577    kaRim: "كا (الحافة)",
2578    tapDrumRimEdge: "اضرب حافة الطبل",
2579    mashFAndJFast: "انقر F و J بسرعة!",
2580    drumrollTitle: "دحرجة الطبل",
2581    alternateRapidStrikes: "ضربات سريعة متناوبة!",
2582    touchDrum: "طبلة اللمس",
2583    touchDrumPrompt: "المس الطبلة للعزف!",
2584    tabletRecommended: "يوصى بجهاز لوحي أو لوحة مفاتيح لوضع اللاعبين",
2585    touchKaLeft: "كا يسار",
2586    touchDonLeft: "دون يسار",
2587    touchDonRight: "دون يمين",
2588    touchKaRight: "كا يمين",
2589    improvementSupport: "تقرير الأداء",
2590    section: "القسم",
2591    practiceSection: "تدرّب على هذا القسم",
2592    coachFullCombo: "مذهل! عزف متكامل دون أي خطأ من البداية حتى النهاية! أنت أستاذ التايكو الحقيقي!",
2593    coachCleared: "تم اجتياز المعزوفة! تضمن القسم {{sec}} بعض النوتات الصعبة. تدرّب عليه لتحقيق العزف المتكامل!",
2594    coachFailed: "لا بأس! لا تستسلم! ركّز على القسم {{sec}} وستجتازه في المرة القادمة!",
2595    coachCoop: "عمل جماعي رائع! عزف كلا العازفين بإيقاع وتناغم تام!",
2596    tamashii: "الروح",
2597    clearThreshold: "اجتياز 80%",
2598    drumrolls: "القرع المتكرر",
2599    performanceTab: "تقرير الأداء",
2600    leaderboardTab: "قائمة المتصدرين"
2601  }
2602};
2603function getTaikoLocale(currentLang) {
2604  if (!currentLang) return TAIKO_LOCALES.en;
2605  if (TAIKO_LOCALES[currentLang]) return TAIKO_LOCALES[currentLang];
2606  const prefix = currentLang.split("-")[0];
2607  if (TAIKO_LOCALES[prefix]) return TAIKO_LOCALES[prefix];
2608  return TAIKO_LOCALES.en;
2609}
2610const TaikoResultsScreen = ({
2611  trackId,
2612  userId,
2613  userName,
2614  results,
2615  isCooperative,
2616  difficulty,
2617  chartSource,
2618  onPlayAgain,
2619  onBackToLobby,
2620  onPracticeSection
2621}) => {
2622  const { i18n } = useTranslation();
2623  const locale = reactExports.useMemo(() => getTaikoLocale(i18n.language), [i18n.language]);
2624  const [, startTransition] = reactExports.useTransition();
2625  const [leaderboard, setLeaderboard] = reactExports.useState([]);
2626  const [isSubmitting, setIsSubmitting] = reactExports.useState(false);
2627  const [hasSubmitted, setHasSubmitted] = reactExports.useState(false);
2628  const submissionLockRef = reactExports.useRef(false);
2629  const [activeTab, setActiveTab] = reactExports.useState("report");
2630  const [selectedSection, setSelectedSection] = reactExports.useState(null);
2631  const resultsContainerRef = reactExports.useRef(null);
2632  const [containerDimensions, setContainerDimensions] = reactExports.useState({ width: 800, height: 450 });
2633  reactExports.useEffect(() => {
2634    const updateDims = () => {
2635      if (resultsContainerRef.current) {
2636        const rect = resultsContainerRef.current.getBoundingClientRect();
2637        if (rect.width > 0 && rect.height > 0) {
2638          setContainerDimensions({ width: rect.width, height: rect.height });
2639        }
2640      }
2641    };
2642    updateDims();
2643    window.addEventListener("resize", updateDims);
2644    let ro = null;
2645    if (typeof ResizeObserver !== "undefined" && resultsContainerRef.current) {
2646      ro = new ResizeObserver(() => updateDims());
2647      ro.observe(resultsContainerRef.current);
2648    }
2649    return () => {
2650      window.removeEventListener("resize", updateDims);
2651      if (ro) ro.disconnect();
2652    };
2653  }, []);
2654  const resultsScale = reactExports.useMemo(() => {
2655    const isMobile = containerDimensions.width < 768;
2656    const targetW = isMobile ? 480 : 880;
2657    const targetH = isMobile ? 540 : 500;
2658    const availW = Math.max(120, containerDimensions.width - 16);
2659    const availH = Math.max(120, containerDimensions.height - 16);
2660    return Math.min(1, availW / targetW, availH / targetH);
2661  }, [containerDimensions.width, containerDimensions.height]);
2662  const bestPlayer = reactExports.useMemo(() => [...results].sort((a, b) => b.score - a.score)[0] || results[0], [results]);
2663  const player2 = reactExports.useMemo(() => results.find((r) => r.playerId === 2), [results]);
2664  reactExports.useMemo(() => results.reduce((acc, r) => acc + r.score, 0), [results]);
2665  reactExports.useMemo(() => Math.max(...results.map((r) => r.maxCombo)), [results]);
2666  reactExports.useEffect(() => {
2667    if (!trackId) return;
2668    api.getHighscores(trackId, "taiko", userId).then((res) => {
2669      const list = Array.isArray(res) ? res : res && res.highscores ? res.highscores : [];
2670      setLeaderboard(list);
2671    }).catch(() => {
2672    });
2673    if (!submissionLockRef.current && !hasSubmitted && bestPlayer && bestPlayer.score > 0) {
2674      submissionLockRef.current = true;
2675      setIsSubmitting(true);
2676      api.postHighscore(trackId, {
2677        userId,
2678        userName: userName || (results.length > 1 ? `Player ${bestPlayer.playerId}` : "Taiko Master"),
2679        score: bestPlayer.score,
2680        accuracy: bestPlayer.accuracy,
2681        maxCombo: bestPlayer.maxCombo,
2682        gameKey: "taiko",
2683        metadata: {
2684          difficulty,
2685          chartSource,
2686          perfect: bestPlayer.perfectCount,
2687          good: bestPlayer.goodCount,
2688          miss: bestPlayer.missCount,
2689          drumrollHits: bestPlayer.drumrollHits,
2690          cleared: bestPlayer.cleared,
2691          isCooperative,
2692          playerCount: results.length
2693        }
2694      }).then(() => {
2695        setHasSubmitted(true);
2696        return api.getHighscores(trackId, "taiko", userId);
2697      }).then((res) => {
2698        const list = Array.isArray(res) ? res : res && res.highscores ? res.highscores : [];
2699        startTransition(() => {
2700          setLeaderboard(list);
2701        });
2702      }).catch(() => {
2703      }).finally(() => setIsSubmitting(false));
2704    }
2705  }, [trackId, userId, userName, bestPlayer, difficulty, chartSource, isCooperative, results.length, hasSubmitted]);
2706  const uniqueLeaderboard = reactExports.useMemo(() => {
2707    const seen = /* @__PURE__ */ new Set();
2708    const list = [];
2709    for (const item of leaderboard) {
2710      const key = item.userId ? `u_${item.userId}` : item.userName ? `n_${item.userName}` : `id_${item.id}`;
2711      if (!seen.has(key)) {
2712        seen.add(key);
2713        list.push(item);
2714      }
2715    }
2716    return list;
2717  }, [leaderboard]);
2718  const podiumSlots = [0, 1, 2].map((idx) => ({
2719    rank: idx + 1,
2720    medal: idx === 0 ? "🥇" : idx === 1 ? "🥈" : "🥉",
2721    item: uniqueLeaderboard[idx] || null
2722  }));
2723  const sections = reactExports.useMemo(() => {
2724    if ((bestPlayer == null ? void 0 : bestPlayer.sections) && bestPlayer.sections.length === 5) {
2725      return bestPlayer.sections;
2726    }
2727    const pAcc = (bestPlayer == null ? void 0 : bestPlayer.accuracy) || 85;
2728    const pPer = (bestPlayer == null ? void 0 : bestPlayer.perfectCount) || 30;
2729    const pGood = (bestPlayer == null ? void 0 : bestPlayer.goodCount) || 10;
2730    const pMiss = (bestPlayer == null ? void 0 : bestPlayer.missCount) || 2;
2731    return [0, 1, 2, 3, 4].map((idx) => ({
2732      sectionIndex: idx,
2733      startTime: idx * 15,
2734      endTime: (idx + 1) * 15,
2735      perfectCount: Math.max(1, Math.round(pPer / 5)),
2736      goodCount: Math.round(pGood / 5),
2737      missCount: idx === 2 ? Math.max(1, pMiss) : 0,
2738      totalNotes: Math.max(1, Math.round((pPer + pGood + pMiss) / 5)),
2739      accuracy: idx === 2 ? Math.max(50, pAcc - 15) : Math.min(100, pAcc + 5),
2740      cleared: idx !== 2 || pAcc >= 70,
2741      crown: idx !== 2 && pMiss === 0 ? "gold" : pAcc >= 70 ? "silver" : "none"
2742    }));
2743  }, [bestPlayer]);
2744  const worstSection = reactExports.useMemo(() => {
2745    let maxMiss = -1;
2746    let worstIdx = 0;
2747    sections.forEach((s, idx) => {
2748      if (s.missCount > maxMiss) {
2749        maxMiss = s.missCount;
2750        worstIdx = idx;
2751      }
2752    });
2753    return { index: worstIdx + 1, misses: maxMiss, stat: sections[worstIdx] };
2754  }, [sections]);
2755  const coachMessage = reactExports.useMemo(() => {
2756    if (bestPlayer.missCount === 0 && bestPlayer.perfectCount > 0) {
2757      return {
2758        title: "⭐ DONDERFUL FULL COMBO! ⭐",
2759        text: locale.coachFullCombo,
2760        status: "perfect"
2761      };
2762    }
2763    if (bestPlayer.cleared) {
2764      const secText2 = String(worstSection.index);
2765      const body2 = locale.coachCleared.replace("{{sec}}", secText2);
2766      return {
2767        title: "🎉 STAGE CLEARED! 🎉",
2768        text: body2,
2769        status: "cleared"
2770      };
2771    }
2772    const secText = String(worstSection.index);
2773    const body = locale.coachFailed.replace("{{sec}}", secText);
2774    return {
2775      title: "💪 DONMAI! TRY AGAIN! 💪",
2776      text: body,
2777      status: "failed"
2778    };
2779  }, [bestPlayer, worstSection, locale]);
2780  const difficultyEmblem = reactExports.useMemo(() => {
2781    switch (difficulty.toLowerCase()) {
2782      case "kantan":
2783        return { icon: "🌱", label: "Easy", color: "bg-emerald-600 text-emerald-100 border-emerald-400" };
2784      case "muzukashii":
2785      case "hard":
2786        return { icon: "🌲", label: "Hard", color: "bg-emerald-800 text-emerald-100 border-emerald-500" };
2787      case "oni":
2788        return { icon: "👹", label: "Oni", color: "bg-purple-900 text-purple-100 border-purple-500" };
2789      case "futsuu":
2790      default:
2791        return { icon: "🌸", label: "Normal", color: "bg-rose-700 text-rose-100 border-rose-400" };
2792    }
2793  }, [difficulty]);
2794  const renderSoulGauge = (gauge) => {
2795    const totalSegments = 26;
2796    const filledSegments = Math.round(gauge / 100 * totalSegments);
2797    const isFullSoul = gauge >= 99.5;
2798    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 w-full max-w-md", children: [
2799      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex-1 h-6 sm:h-7 bg-zinc-950/90 rounded-md p-1 border border-white/20 flex items-center gap-0.5 shadow-inner overflow-hidden", children: [
2800        /* @__PURE__ */ jsxRuntimeExports.jsxs(
2801          "div",
2802          {
2803            className: "absolute top-0 bottom-0 z-20 pointer-events-none flex flex-col items-center justify-between",
2804            style: { left: "80%" },
2805            children: [
2806              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0.5 h-full bg-yellow-300 shadow-[0_0_8px_#fde047]" }),
2807              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute -top-0.5 text-[8px] font-black uppercase text-yellow-300 tracking-tighter bg-black/80 px-1 rounded -translate-y-full whitespace-nowrap", children: locale.clearThreshold })
2808            ]
2809          }
2810        ),
2811        Array.from({ length: totalSegments }).map((_, idx) => {
2812          const isFilled = idx < filledSegments;
2813          const isClearZone = idx >= Math.floor(totalSegments * 0.8);
2814          let segColor = "bg-zinc-800/60";
2815          if (isFilled) {
2816            if (!isClearZone) {
2817              segColor = "bg-gradient-to-t from-amber-600 to-yellow-400 shadow-[0_0_4px_rgba(245,158,11,0.5)]";
2818            } else {
2819              const rainbowColors = [
2820                "from-emerald-500 to-teal-400",
2821                "from-teal-400 to-cyan-400",
2822                "from-sky-400 to-blue-500",
2823                "from-indigo-500 to-purple-500",
2824                "from-fuchsia-500 to-pink-500",
2825                "from-pink-500 to-rose-500"
2826              ];
2827              const colorIdx = Math.min(rainbowColors.length - 1, idx - Math.floor(totalSegments * 0.8));
2828              segColor = `bg-gradient-to-t ${rainbowColors[colorIdx]} shadow-[0_0_8px_rgba(56,189,248,0.7)]`;
2829            }
2830          }
2831          return /* @__PURE__ */ jsxRuntimeExports.jsx(
2832            "div",
2833            {
2834              className: `flex-1 h-full rounded-xs transform -skew-x-12 transition-all ${segColor}`
2835            },
2836            idx
2837          );
2838        })
2839      ] }),
2840      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 flex-shrink-0", children: [
2841        /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-black text-white/90 font-mono", children: [
2842          Math.round(gauge),
2843          "% ▽"
2844        ] }),
2845        /* @__PURE__ */ jsxRuntimeExports.jsx(
2846          "div",
2847          {
2848            className: `w-7 h-7 sm:w-8 sm:h-8 rounded-full flex items-center justify-center font-black text-sm transition-all ${isFullSoul ? "bg-gradient-to-br from-pink-500 via-rose-500 to-amber-400 text-white shadow-[0_0_20px_#ec4899] border-2 border-pink-200 animate-pulse scale-110" : gauge >= 80 ? "bg-gradient-to-br from-emerald-600 to-teal-500 text-white shadow-[0_0_10px_#10b981] border border-emerald-300" : "bg-zinc-800 text-zinc-500 border border-zinc-700"}`,
2849            title: locale.tamashii,
2850            children: locale.tamashii
2851          }
2852        )
2853      ] })
2854    ] });
2855  };
2856  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: resultsContainerRef, className: "absolute inset-0 z-50 bg-black/90 backdrop-blur-md flex items-center justify-center p-1 sm:p-2 text-white select-none overflow-hidden pointer-events-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2857    "div",
2858    {
2859      style: {
2860        transform: `scale(${resultsScale})`,
2861        transformOrigin: "center center"
2862      },
2863      className: "w-full max-w-4xl bg-zinc-950/90 border border-white/15 rounded-3xl p-3 sm:p-4 flex flex-col shadow-2xl backdrop-blur-xl shrink-0",
2864      children: [
2865        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2 pb-2 border-b border-white/10 flex-shrink-0", children: [
2866          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
2867            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xl", children: "🥁" }),
2868            /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-lg sm:text-2xl font-black tracking-wide bg-gradient-to-r from-amber-300 via-orange-400 to-rose-400 bg-clip-text text-transparent uppercase drop-shadow-sm", children: locale.improvementSupport })
2869          ] }),
2870          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-zinc-900 border border-white/10 rounded-xl p-0.5", children: [
2871            /* @__PURE__ */ jsxRuntimeExports.jsxs(
2872              "button",
2873              {
2874                type: "button",
2875                onClick: () => setActiveTab("report"),
2876                className: `px-3 py-1 rounded-lg text-xs font-black transition-all flex items-center gap-1.5 cursor-pointer ${activeTab === "report" ? "bg-gradient-to-r from-amber-500 to-orange-500 text-white shadow-md" : "text-zinc-400 hover:text-white"}`,
2877                children: [
2878                  /* @__PURE__ */ jsxRuntimeExports.jsx(ChartNoAxesColumn, { size: 13 }),
2879                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: locale.performanceTab })
2880                ]
2881              }
2882            ),
2883            /* @__PURE__ */ jsxRuntimeExports.jsxs(
2884              "button",
2885              {
2886                type: "button",
2887                onClick: () => setActiveTab("leaderboard"),
2888                className: `px-3 py-1 rounded-lg text-xs font-black transition-all flex items-center gap-1.5 cursor-pointer ${activeTab === "leaderboard" ? "bg-gradient-to-r from-amber-500 to-orange-500 text-white shadow-md" : "text-zinc-400 hover:text-white"}`,
2889                children: [
2890                  /* @__PURE__ */ jsxRuntimeExports.jsx(Trophy, { size: 13 }),
2891                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: locale.leaderboardTab })
2892                ]
2893              }
2894            )
2895          ] })
2896        ] }),
2897        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 py-2 border-b border-white/10 flex-shrink-0", children: [
2898          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2.5 sm:gap-4 flex-wrap", children: [
2899            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-1.5 px-2.5 py-1 rounded-xl border ${difficultyEmblem.color} shadow-sm`, children: [
2900              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base sm:text-lg", children: difficultyEmblem.icon }),
2901              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-black uppercase tracking-wider", children: difficultyEmblem.label })
2902            ] }),
2903            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900/90 border border-white/20 rounded-2xl px-3.5 py-1 shadow-inner flex flex-col items-center justify-center min-w-[120px]", children: [
2904              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-black tracking-widest text-zinc-400 uppercase", children: locale.score }),
2905              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xl sm:text-2xl font-black text-amber-300 drop-shadow-[0_2px_4px_rgba(0,0,0,0.8)]", children: bestPlayer.score.toLocaleString() })
2906            ] }),
2907            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [
2908              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-zinc-400 uppercase", children: locale.maxCombo }),
2909              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base sm:text-xl font-black text-emerald-400 font-mono", children: bestPlayer.maxCombo })
2910            ] }),
2911            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [
2912              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-zinc-400 uppercase", children: locale.drumrolls }),
2913              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base sm:text-xl font-black text-amber-400 font-mono", children: bestPlayer.drumrollHits })
2914            ] })
2915          ] }),
2916          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full sm:w-auto flex-1 flex justify-end min-w-[240px]", children: renderSoulGauge(bestPlayer.soulGauge) })
2917        ] }),
2918        activeTab === "report" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col justify-between py-2 min-h-0 overflow-y-auto", children: [
2919          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900/70 border border-white/10 rounded-2xl p-2.5 sm:p-3 flex flex-col shadow-lg mb-2", children: [
2920            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-xs font-bold text-zinc-300 mb-1 px-1", children: [
2921              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
2922                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-amber-400 font-black", children: [
2923                  locale.section,
2924                  ":"
2925                ] }),
2926                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: selectedSection !== null ? `${locale.section} ${selectedSection + 1}` : "1 - 5" })
2927              ] }),
2928              selectedSection !== null && onPracticeSection && /* @__PURE__ */ jsxRuntimeExports.jsxs(
2929                "button",
2930                {
2931                  type: "button",
2932                  onClick: () => onPracticeSection(sections[selectedSection].startTime, sections[selectedSection].endTime),
2933                  className: "bg-gradient-to-r from-amber-500 to-yellow-400 text-black text-[11px] font-black px-3 py-0.5 rounded-full shadow hover:scale-105 transition-all flex items-center gap-1 cursor-pointer",
2934                  children: [
2935                    /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 10, fill: "currentColor" }),
2936                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
2937                      locale.practiceSection,
2938                      " ",
2939                      selectedSection + 1
2940                    ] })
2941                  ]
2942                }
2943              )
2944            ] }),
2945            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative h-28 sm:h-32 bg-black/50 border border-white/10 rounded-xl p-2 flex items-end justify-around overflow-hidden", children: [
2946              /* @__PURE__ */ jsxRuntimeExports.jsxs(
2947                "div",
2948                {
2949                  className: "absolute left-0 right-0 z-10 pointer-events-none flex items-center",
2950                  style: { bottom: "75%" },
2951                  children: [
2952                    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full border-b border-dashed border-yellow-400/80 shadow-[0_0_6px_#facc15]" }),
2953                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute left-1 -top-3 text-[8px] font-black uppercase text-yellow-300 bg-black/80 px-1 rounded", children: "Clear 80%" })
2954                  ]
2955                }
2956              ),
2957              sections.map((sec, idx) => {
2958                const isSelected = selectedSection === idx;
2959                const isFC = sec.crown === "gold";
2960                const isCleared = sec.crown === "silver" || isFC;
2961                return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2962                  "div",
2963                  {
2964                    onClick: () => setSelectedSection((prev) => prev === idx ? null : idx),
2965                    className: `relative flex-1 flex flex-col items-center justify-end h-full cursor-pointer rounded-lg transition-colors p-1 ${isSelected ? "bg-amber-400/15 ring-2 ring-amber-400" : "hover:bg-white/5"}`,
2966                    children: [
2967                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex items-center justify-center text-4xl sm:text-5xl font-black text-white/[0.04] pointer-events-none select-none", children: idx + 1 }),
2968                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1 z-20 text-sm sm:text-base", children: isFC ? "👑" : isCleared ? "🥈" : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "opacity-30 text-xs", children: "-" }) }),
2969                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 sm:w-12 bg-zinc-800/80 rounded-t-lg h-full flex items-end justify-center overflow-hidden border border-white/10 shadow-inner", children: results.length > 1 && player2 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full flex items-end h-full gap-0.5 px-0.5", children: [
2970                        /* @__PURE__ */ jsxRuntimeExports.jsx(
2971                          "div",
2972                          {
2973                            className: "flex-1 bg-gradient-to-t from-red-600 via-rose-500 to-amber-300 rounded-t-sm transition-all duration-700 shadow-md",
2974                            style: { height: `${Math.max(8, sec.accuracy)}%` },
2975                            title: `P1: ${sec.accuracy}%`
2976                          }
2977                        ),
2978                        /* @__PURE__ */ jsxRuntimeExports.jsx(
2979                          "div",
2980                          {
2981                            className: "flex-1 bg-gradient-to-t from-blue-600 via-sky-500 to-teal-300 rounded-t-sm transition-all duration-700 shadow-md",
2982                            style: { height: `${Math.max(8, player2.sections && player2.sections[idx] ? player2.sections[idx].accuracy : sec.accuracy)}%` },
2983                            title: `P2: ${player2.sections && player2.sections[idx] ? player2.sections[idx].accuracy : sec.accuracy}%`
2984                          }
2985                        )
2986                      ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
2987                        "div",
2988                        {
2989                          className: "w-full bg-gradient-to-t from-emerald-500 via-lime-400 to-yellow-300 rounded-t-md transition-all duration-700 shadow-[0_0_12px_rgba(163,230,53,0.4)]",
2990                          style: { height: `${Math.max(8, sec.accuracy)}%` }
2991                        }
2992                      ) })
2993                    ]
2994                  },
2995                  idx
2996                );
2997              })
2998            ] }),
2999            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 bg-black/40 rounded-xl p-1.5 border border-white/5", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-center text-xs", children: [
3000              /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "text-[10px] text-zinc-400 font-bold border-b border-white/10", children: [
3001                /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "py-1 text-left px-2", children: locale.difficulty }),
3002                sections.map((_, idx) => /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "py-1", children: idx + 1 }, idx))
3003              ] }) }),
3004              /* @__PURE__ */ jsxRuntimeExports.jsxs("tbody", { className: "divide-y divide-white/5 font-mono font-bold", children: [
3005                /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
3006                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 text-left px-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block bg-gradient-to-r from-amber-500 to-orange-500 text-white font-black text-[10px] px-2 py-0.5 rounded shadow-sm", children: locale.greatLabel }) }),
3007                  sections.map((sec, idx) => /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 text-amber-300", children: sec.perfectCount }, idx))
3008                ] }),
3009                /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
3010                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 text-left px-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block bg-gradient-to-r from-slate-500 to-zinc-600 text-white font-black text-[10px] px-2 py-0.5 rounded shadow-sm", children: locale.goodLabel }) }),
3011                  sections.map((sec, idx) => /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 text-sky-300", children: sec.goodCount }, idx))
3012                ] }),
3013                /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
3014                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 text-left px-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block bg-gradient-to-r from-sky-600 to-blue-600 text-white font-black text-[10px] px-2 py-0.5 rounded shadow-sm", children: locale.missLabel }) }),
3015                  sections.map((sec, idx) => /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: `py-1 ${sec.missCount > 0 ? "text-rose-400 font-black" : "text-zinc-500"}`, children: sec.missCount }, idx))
3016                ] })
3017              ] })
3018            ] }) })
3019          ] }),
3020          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 bg-zinc-900/60 border border-white/10 rounded-2xl p-2.5 sm:p-3 shadow-md", children: [
3021            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-shrink-0 flex flex-col items-center justify-center", children: [
3022              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 sm:w-14 sm:h-14 rounded-full bg-gradient-to-b from-amber-400 to-orange-500 border-2 border-amber-300 p-0.5 shadow-md flex items-center justify-center overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 100 100", className: "w-full h-full", children: [
3023                /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "25", cy: "25", r: "15", fill: "#d97706" }),
3024                /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "25", cy: "25", r: "9", fill: "#fbcfe8" }),
3025                /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "75", cy: "25", r: "15", fill: "#d97706" }),
3026                /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "75", cy: "25", r: "9", fill: "#fbcfe8" }),
3027                /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "50", cy: "55", rx: "38", ry: "34", fill: "#f59e0b" }),
3028                /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "50", cy: "66", rx: "22", ry: "16", fill: "#fef3c7" }),
3029                /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "36", cy: "50", r: "5", fill: "#1e293b" }),
3030                /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "38", cy: "48", r: "2", fill: "#ffffff" }),
3031                /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "64", cy: "50", r: "5", fill: "#1e293b" }),
3032                /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "66", cy: "48", r: "2", fill: "#ffffff" }),
3033                /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "28", cy: "60", rx: "6", ry: "3", fill: "#fda4af" }),
3034                /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "72", cy: "60", rx: "6", ry: "3", fill: "#fda4af" }),
3035                /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "47,60 53,60 50,64", fill: "#e11d48" }),
3036                /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M 46 66 Q 50 70 54 66", stroke: "#1e293b", strokeWidth: "2", fill: "none" }),
3037                /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "16", y: "32", width: "68", height: "12", rx: "3", fill: "#dc2626" }),
3038                /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: "50", y: "42", textAnchor: "middle", fill: "#ffffff", fontSize: "9", fontWeight: "900", children: "太" })
3039              ] }) }),
3040              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-black text-amber-300 mt-0.5", children: "HAMIE" })
3041            ] }),
3042            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 bg-amber-50 dark:bg-zinc-900 border-2 border-amber-400/90 rounded-2xl p-2.5 sm:p-3 text-zinc-900 dark:text-zinc-100 shadow-md relative", children: [
3043              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute -left-2 top-1/2 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-r-8 border-r-amber-400" }),
3044              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-black text-amber-600 dark:text-amber-400 tracking-wide uppercase mb-0.5", children: coachMessage.title }),
3045              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-bold leading-relaxed text-zinc-800 dark:text-zinc-200", children: coachMessage.text })
3046            ] })
3047          ] })
3048        ] }) : (
3049          /* Leaderboard Tab Content */
3050          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 py-3 min-h-0 flex flex-col justify-between overflow-y-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900/60 border border-white/10 rounded-2xl p-3 sm:p-4", children: [
3051            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-xs font-black text-amber-300 mb-3 px-1", children: [
3052              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5 text-sm", children: [
3053                /* @__P
3053URE__ */ jsxRuntimeExports.jsx(Trophy, { size: 16 }),
3054                " ",
3055                locale.topTaikoMasters
3056              ] }),
3057              isSubmitting && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 text-xs animate-pulse", children: locale.submittingScore })
3058            ] }),
3059            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: podiumSlots.map(({ rank, medal, item }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3060              "div",
3061              {
3062                className: `flex items-center justify-between p-2.5 sm:p-3 rounded-xl border text-xs sm:text-sm ${item ? rank === 1 ? "bg-amber-500/10 border-amber-500/30 text-amber-200" : rank === 2 ? "bg-slate-400/10 border-slate-400/30 text-slate-200" : "bg-amber-700/10 border-amber-700/30 text-amber-300" : "bg-white/[0.02] border-white/5 text-zinc-500"}`,
3063                children: [
3064                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [
3065                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xl", children: medal }),
3066                    item ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "truncate font-bold text-zinc-200 flex items-center gap-1.5", children: [
3067                      item.countryCode && /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: item.countryCode, size: 13 }),
3068                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: item.userName || "Anonymous" })
3069                    ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-500", children: "-" })
3070                  ] }),
3071                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: item ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono font-black text-amber-300 text-sm sm:text-base", children: [
3072                    Number(item.score).toLocaleString(),
3073                    " ",
3074                    locale.points
3075                  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-zinc-500", children: "-" }) })
3076                ]
3077              },
3078              rank
3079            )) }),
3080            uniqueLeaderboard.length > 3 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 pt-2 border-t border-white/5 space-y-1.5", children: uniqueLeaderboard.slice(3, 5).map((item, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-xs text-zinc-400 px-2 py-1 bg-white/[0.02] rounded-lg", children: [
3081              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 truncate max-w-[160px]", children: [
3082                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-600 font-bold", children: [
3083                  idx + 4,
3084                  "."
3085                ] }),
3086                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: item.userName })
3087              ] }),
3088              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-bold", children: Number(item.score).toLocaleString() })
3089            ] }, idx)) })
3090          ] }) })
3091        ),
3092        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-center gap-3 pt-2 border-t border-white/10 flex-shrink-0", children: [
3093          /* @__PURE__ */ jsxRuntimeExports.jsxs(
3094            "button",
3095            {
3096              onClick: onPlayAgain,
3097              className: "flex items-center gap-2 bg-gradient-to-r from-red-600 to-rose-600 hover:from-red-500 hover:to-rose-500 text-white font-black px-6 py-2 rounded-xl shadow-lg transition-all transform hover:scale-105 text-xs sm:text-sm cursor-pointer",
3098              children: [
3099                /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 15 }),
3100                " ",
3101                locale.retry
3102              ]
3103            }
3104          ),
3105          /* @__PURE__ */ jsxRuntimeExports.jsx(
3106            "button",
3107            {
3108              onClick: onBackToLobby,
3109              className: "flex items-center gap-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 font-bold px-5 py-2 rounded-xl border border-white/10 transition-all text-xs sm:text-sm cursor-pointer",
3110              children: locale.lobby
3111            }
3112          )
3113        ] })
3114      ]
3115    }
3116  ) });
3117};
3118const TaikoTouchControls = ({
3119  playerCount,
3120  onHit,
3121  locale
3122}) => {
3123  const [pressedKeys, setPressedKeys] = reactExports.useState(/* @__PURE__ */ new Set());
3124  const touchMapRef = reactExports.useRef(/* @__PURE__ */ new Map());
3125  const triggerVibrate = reactExports.useCallback(() => {
3126    try {
3127      if (typeof navigator !== "undefined" && navigator.vibrate) {
3128        navigator.vibrate(12);
3129      }
3130    } catch (e) {
3131    }
3132  }, []);
3133  const handlePointerDown = reactExports.useCallback((e, keyId, playerId, action, subKey) => {
3134    e.preventDefault();
3135    e.stopPropagation();
3136    touchMapRef.current.set(e.pointerId, keyId);
3137    setPressedKeys((prev) => {
3138      const next = new Set(prev);
3139      next.add(keyId);
3140      return next;
3141    });
3142    triggerVibrate();
3143    onHit(playerId, action, subKey);
3144  }, [onHit, triggerVibrate]);
3145  const handlePointerUp = reactExports.useCallback((e) => {
3146    e.preventDefault();
3147    e.stopPropagation();
3148    const keyId = touchMapRef.current.get(e.pointerId);
3149    if (keyId) {
3150      touchMapRef.current.delete(e.pointerId);
3151      setPressedKeys((prev) => {
3152        const next = new Set(prev);
3153        next.delete(keyId);
3154        return next;
3155      });
3156    }
3157  }, []);
3158  const handlePointerCancel = reactExports.useCallback((e) => {
3159    const keyId = touchMapRef.current.get(e.pointerId);
3160    if (keyId) {
3161      touchMapRef.current.delete(e.pointerId);
3162      setPressedKeys((prev) => {
3163        const next = new Set(prev);
3164        next.delete(keyId);
3165        return next;
3166      });
3167    }
3168  }, []);
3169  if (playerCount === 1) {
3170    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full bg-zinc-950/85 backdrop-blur-md border-t border-white/10 px-2 py-2 sm:py-3 select-none touch-none", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-xl mx-auto flex items-center justify-between gap-1.5 sm:gap-3", children: [
3171      /* @__PURE__ */ jsxRuntimeExports.jsxs(
3172        "button",
3173        {
3174          type: "button",
3175          "data-taiko-player": "1",
3176          "data-taiko-key": "ka",
3177          onPointerDown: (e) => handlePointerDown(e, "p1_ka_left", 1, "ka", "leftKa"),
3178          onPointerUp: handlePointerUp,
3179          onPointerCancel: handlePointerCancel,
3180          className: `flex-1 flex flex-col items-center justify-center h-16 sm:h-20 rounded-2xl border-2 transition-transform active:scale-95 cursor-pointer ${pressedKeys.has("p1_ka_left") ? "bg-sky-500 border-sky-300 shadow-[0_0_20px_rgba(56,189,248,0.7)] translate-y-1 text-white" : "bg-gradient-to-b from-sky-600 to-sky-900 border-sky-400/60 shadow-lg text-sky-100"}`,
3181          children: [
3182            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs sm:text-sm font-black tracking-wider uppercase", children: locale.ka }),
3183            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] sm:text-[10px] opacity-75 font-semibold mt-0.5", children: locale.touchKaLeft })
3184          ]
3185        }
3186      ),
3187      /* @__PURE__ */ jsxRuntimeExports.jsxs(
3188        "button",
3189        {
3190          type: "button",
3191          "data-taiko-player": "1",
3192          "data-taiko-key": "don",
3193          onPointerDown: (e) => handlePointerDown(e, "p1_don_left", 1, "don", "leftDon"),
3194          onPointerUp: handlePointerUp,
3195          onPointerCancel: handlePointerCancel,
3196          className: `flex-[1.2] flex flex-col items-center justify-center h-16 sm:h-20 rounded-2xl border-2 transition-transform active:scale-95 cursor-pointer ${pressedKeys.has("p1_don_left") ? "bg-red-500 border-amber-300 shadow-[0_0_20px_rgba(239,68,68,0.7)] translate-y-1 text-white" : "bg-gradient-to-b from-red-600 to-red-900 border-red-400/60 shadow-lg text-red-100"}`,
3197          children: [
3198            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base sm:text-lg font-black tracking-wider uppercase", children: locale.don }),
3199            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] sm:text-[10px] opacity-75 font-semibold mt-0.5", children: locale.touchDonLeft })
3200          ]
3201        }
3202      ),
3203      /* @__PURE__ */ jsxRuntimeExports.jsxs(
3204        "button",
3205        {
3206          type: "button",
3207          "data-taiko-player": "1",
3208          "data-taiko-key": "don",
3209          onPointerDown: (e) => handlePointerDown(e, "p1_don_right", 1, "don", "rightDon"),
3210          onPointerUp: handlePointerUp,
3211          onPointerCancel: handlePointerCancel,
3212          className: `flex-[1.2] flex flex-col items-center justify-center h-16 sm:h-20 rounded-2xl border-2 transition-transform active:scale-95 cursor-pointer ${pressedKeys.has("p1_don_right") ? "bg-red-500 border-amber-300 shadow-[0_0_20px_rgba(239,68,68,0.7)] translate-y-1 text-white" : "bg-gradient-to-b from-red-600 to-red-900 border-red-400/60 shadow-lg text-red-100"}`,
3213          children: [
3214            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base sm:text-lg font-black tracking-wider uppercase", children: locale.don }),
3215            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] sm:text-[10px] opacity-75 font-semibold mt-0.5", children: locale.touchDonRight })
3216          ]
3217        }
3218      ),
3219      /* @__PURE__ */ jsxRuntimeExports.jsxs(
3220        "button",
3221        {
3222          type: "button",
3223          "data-taiko-player": "1",
3224          "data-taiko-key": "ka",
3225          onPointerDown: (e) => handlePointerDown(e, "p1_ka_right", 1, "ka", "rightKa"),
3226          onPointerUp: handlePointerUp,
3227          onPointerCancel: handlePointerCancel,
3228          className: `flex-1 flex flex-col items-center justify-center h-16 sm:h-20 rounded-2xl border-2 transition-transform active:scale-95 cursor-pointer ${pressedKeys.has("p1_ka_right") ? "bg-sky-500 border-sky-300 shadow-[0_0_20px_rgba(56,189,248,0.7)] translate-y-1 text-white" : "bg-gradient-to-b from-sky-600 to-sky-900 border-sky-400/60 shadow-lg text-sky-100"}`,
3229          children: [
3230            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs sm:text-sm font-black tracking-wider uppercase", children: locale.ka }),
3231            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] sm:text-[10px] opacity-75 font-semibold mt-0.5", children: locale.touchKaRight })
3232          ]
3233        }
3234      )
3235    ] }) });
3236  }
3237  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full bg-zinc-950/85 backdrop-blur-md border-t border-white/10 px-2 py-2 sm:py-3 select-none touch-none", children: [
3238    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex md:hidden items-center justify-center gap-1.5 text-[10px] font-bold text-amber-300/90 mb-1.5", children: [
3239      /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { size: 12, className: "flex-shrink-0 text-amber-400" }),
3240      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: locale.tabletRecommended })
3241    ] }),
3242    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-4xl mx-auto grid grid-cols-2 gap-2 sm:gap-6", children: [
3243      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 bg-red-950/20 border border-red-500/20 rounded-2xl p-1.5 sm:p-2", children: [
3244        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between px-1", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-black uppercase tracking-wider text-red-400 flex items-center gap-1", children: [
3245          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-2 h-2 rounded-full bg-red-500 shadow-[0_0_6px_#ef4444]" }),
3246          locale.p1Name || "Player 1"
3247        ] }) }),
3248        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2", children: [
3249          /* @__PURE__ */ jsxRuntimeExports.jsx(
3250            "button",
3251            {
3252              type: "button",
3253              "data-taiko-player": "1",
3254              "data-taiko-key": "ka",
3255              onPointerDown: (e) => handlePointerDown(e, "p1_ka", 1, "ka", "leftKa"),
3256              onPointerUp: handlePointerUp,
3257              onPointerCancel: handlePointerCancel,
3258              className: `flex-1 flex flex-col items-center justify-center h-14 sm:h-18 rounded-xl border-2 transition-transform active:scale-95 cursor-pointer ${pressedKeys.has("p1_ka") ? "bg-sky-500 border-sky-300 shadow-[0_0_15px_rgba(56,189,248,0.7)] translate-y-1 text-white" : "bg-gradient-to-b from-sky-600 to-sky-900 border-sky-400/60 shadow-md text-sky-100"}`,
3259              children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs sm:text-sm font-black tracking-wider uppercase", children: locale.ka })
3260            }
3261          ),
3262          /* @__PURE__ */ jsxRuntimeExports.jsx(
3263            "button",
3264            {
3265              type: "button",
3266              "data-taiko-player": "1",
3267              "data-taiko-key": "don",
3268              onPointerDown: (e) => handlePointerDown(e, "p1_don", 1, "don", "leftDon"),
3269              onPointerUp: handlePointerUp,
3270              onPointerCancel: handlePointerCancel,
3271              className: `flex-[1.2] flex flex-col items-center justify-center h-14 sm:h-18 rounded-xl border-2 transition-transform active:scale-95 cursor-pointer ${pressedKeys.has("p1_don") ? "bg-red-500 border-amber-300 shadow-[0_0_15px_rgba(239,68,68,0.7)] translate-y-1 text-white" : "bg-gradient-to-b from-red-600 to-red-900 border-red-400/60 shadow-md text-red-100"}`,
3272              children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm sm:text-base font-black tracking-wider uppercase", children: locale.don })
3273            }
3274          )
3275        ] })
3276      ] }),
3277      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 bg-blue-950/20 border border-blue-500/20 rounded-2xl p-1.5 sm:p-2", children: [
3278        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between px-1", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-black uppercase tracking-wider text-blue-400 flex items-center gap-1", children: [
3279          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-2 h-2 rounded-full bg-blue-500 shadow-[0_0_6px_#3b82f6]" }),
3280          locale.p2Name || "Player 2"
3281        ] }) }),
3282        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2", children: [
3283          /* @__PURE__ */ jsxRuntimeExports.jsx(
3284            "button",
3285            {
3286              type: "button",
3287              "data-taiko-player": "2",
3288              "data-taiko-key": "don",
3289              onPointerDown: (e) => handlePointerDown(e, "p2_don", 2, "don", "leftDon"),
3290              onPointerUp: handlePointerUp,
3291              onPointerCancel: handlePointerCancel,
3292              className: `flex-[1.2] flex flex-col items-center justify-center h-14 sm:h-18 rounded-xl border-2 transition-transform active:scale-95 cursor-pointer ${pressedKeys.has("p2_don") ? "bg-red-500 border-amber-300 shadow-[0_0_15px_rgba(239,68,68,0.7)] translate-y-1 text-white" : "bg-gradient-to-b from-red-600 to-red-900 border-red-400/60 shadow-md text-red-100"}`,
3293              children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm sm:text-base font-black tracking-wider uppercase", children: locale.don })
3294            }
3295          ),
3296          /* @__PURE__ */ jsxRuntimeExports.jsx(
3297            "button",
3298            {
3299              type: "button",
3300              "data-taiko-player": "2",
3301              "data-taiko-key": "ka",
3302              onPointerDown: (e) => handlePointerDown(e, "p2_ka", 2, "ka", "leftKa"),
3303              onPointerUp: handlePointerUp,
3304              onPointerCancel: handlePointerCancel,
3305              className: `flex-1 flex flex-col items-center justify-center h-14 sm:h-18 rounded-xl border-2 transition-transform active:scale-95 cursor-pointer ${pressedKeys.has("p2_ka") ? "bg-sky-500 border-sky-300 shadow-[0_0_15px_rgba(56,189,248,0.7)] translate-y-1 text-white" : "bg-gradient-to-b from-sky-600 to-sky-900 border-sky-400/60 shadow-md text-sky-100"}`,
3306              children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs sm:text-sm font-black tracking-wider uppercase", children: locale.ka })
3307            }
3308          )
3309        ] })
3310      ] })
3311    ] })
3312  ] });
3313};
3314const createDefaultSectionHits = () => [
3315  { perfect: 0, good: 0, miss: 0 },
3316  { perfect: 0, good: 0, miss: 0 },
3317  { perfect: 0, good: 0, miss: 0 },
3318  { perfect: 0, good: 0, miss: 0 },
3319  { perfect: 0, good: 0, miss: 0 }
3320];
3321const PLAYER_COLORS = ["#ef4444", "#3b82f6", "#10b981", "#f59e0b"];
3322const getTrackLayout = (height, width, count) => {
3323  let laneH = 112;
3324  let startY = Math.floor(height * 0.38);
3325  if (count === 1) {
3326    laneH = Math.min(116, Math.max(50, Math.floor(height * 0.29)));
3327    startY = Math.floor(height * 0.35);
3328  } else {
3329    laneH = Math.min(94, Math.max(40, Math.floor(height * 0.46 / 2)));
3330    startY = Math.max(16, Math.floor(height * 0.2));
3331  }
3332  const hitZoneX = Math.max(140, Math.min(270, width * 0.2));
3333  const ribbonH = Math.max(26, laneH - 24);
3334  const hitRadius = Math.min(26, Math.max(14, Math.round((ribbonH - 8) * 0.4)));
3335  const drumRadius = hitRadius * 1.15;
3336  const drumCenterX = Math.max(40, hitZoneX - hitRadius - drumRadius - 16);
3337  return { laneH, startY, hitZoneX, hitRadius, drumRadius, drumCenterX };
3338};
3339function drawKeycap(ctx, x, y, w, h, label, isPressed, theme = "don") {
3340  ctx.save();
3341  const r = Math.min(4, Math.round(w * 0.2));
3342  const pressOffset = isPressed ? 2 : 0;
3343  const drawY = y + pressOffset;
3344  const bevelH = isPressed ? 1 : 3;
3345  const roundRectPath = (rx, ry, rw, rh, radius) => {
3346    ctx.beginPath();
3347    if (typeof ctx.roundRect === "function") {
3348      ctx.roundRect(rx, ry, rw, rh, radius);
3349    } else {
3350      ctx.moveTo(rx + radius, ry);
3351      ctx.arcTo(rx + rw, ry, rx + rw, ry + rh, radius);
3352      ctx.arcTo(rx + rw, ry + rh, rx, ry + rh, radius);
3353      ctx.arcTo(rx, ry + rh, rx, ry, radius);
3354      ctx.arcTo(rx, ry, rx + rw, ry, radius);
3355    }
3356  };
3357  ctx.fillStyle = theme === "don" ? isPressed ? "#7f1d1d" : "#450a0a" : isPressed ? "#1e3a8a" : "#0f172a";
3358  roundRectPath(x - w / 2, drawY - h / 2, w, h + bevelH, r);
3359  ctx.fill();
3360  const topGrad = ctx.createLinearGradient(0, drawY - h / 2, 0, drawY + h / 2);
3361  if (theme === "don") {
3362    if (isPressed) {
3363      topGrad.addColorStop(0, "#fef08a");
3364      topGrad.addColorStop(1, "#fde047");
3365    } else {
3366      topGrad.addColorStop(0, "#ffffff");
3367      topGrad.addColorStop(1, "#f3f4f6");
3368    }
3369  } else {
3370    if (isPressed) {
3371      topGrad.addColorStop(0, "#bae6fd");
3372      topGrad.addColorStop(1, "#7dd3fc");
3373    } else {
3374      topGrad.addColorStop(0, "#ffffff");
3375      topGrad.addColorStop(1, "#e2e8f0");
3376    }
3377  }
3378  ctx.fillStyle = topGrad;
3379  roundRectPath(x - w / 2, drawY - h / 2, w, h, r);
3380  ctx.fill();
3381  ctx.strokeStyle = isPressed ? theme === "don" ? "#eab308" : "#38bdf8" : "rgba(0, 0, 0, 0.35)";
3382  ctx.lineWidth = 1;
3383  ctx.stroke();
3384  ctx.fillStyle = isPressed ? theme === "don" ? "#b45309" : "#0369a1" : theme === "don" ? "#991b1b" : "#1e3a8a";
3385  ctx.font = `900 ${Math.max(9, Math.round(h * 0.56))}px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace`;
3386  ctx.textAlign = "center";
3387  ctx.textBaseline = "middle";
3388  ctx.fillText(label, x, drawY);
3389  ctx.restore();
3390}
3391function drawHamieNoteFace(ctx, x, y, r, type) {
3392  const isDon = type === "don" || type === "big_don";
3393  const isBig = type.startsWith("big_");
3394  ctx.save();
3395  const earRadius = r * 0.28;
3396  const earDist = r * 0.78;
3397  const earAngles = [-Math.PI * 0.72, -Math.PI * 0.28];
3398  for (const angle of earAngles) {
3399    const ex = x + Math.cos(angle) * earDist;
3400    const ey = y + Math.sin(angle) * earDist;
3401    ctx.beginPath();
3402    ctx.arc(ex, ey, earRadius, 0, Math.PI * 2);
3403    ctx.fillStyle = isDon ? "#dc2626" : "#1d4ed8";
3404    ctx.fill();
3405    ctx.lineWidth = 1.5;
3406    ctx.strokeStyle = "#ffffff";
3407    ctx.stroke();
3408    ctx.beginPath();
3409    ctx.arc(ex, ey, earRadius * 0.58, 0, Math.PI * 2);
3410    ctx.fillStyle = isDon ? "#fca5a5" : "#93c5fd";
3411    ctx.fill();
3412  }
3413  ctx.beginPath();
3414  ctx.arc(x, y, r, 0, Math.PI * 2);
3415  ctx.fillStyle = isDon ? "#ef4444" : "#2563eb";
3416  ctx.fill();
3417  ctx.lineWidth = isBig ? 4 : 2.5;
3418  ctx.strokeStyle = "#ffffff";
3419  ctx.stroke();
3420  const muzzleY = y + r * 0.2;
3421  const muzzleW = r * 0.92;
3422  const muzzleH = r * 0.54;
3423  ctx.beginPath();
3424  ctx.ellipse(x, muzzleY, muzzleW * 0.5, muzzleH * 0.5, 0, 0, Math.PI * 2);
3425  ctx.fillStyle = isDon ? "#ffffff" : "#f0f9ff";
3426  ctx.fill();
3427  const cheekY = y + r * 0.18;
3428  const cheekDist = r * 0.44;
3429  const cheekRadius = r * 0.16;
3430  ctx.beginPath();
3431  ctx.arc(x - cheekDist, cheekY, cheekRadius, 0, Math.PI * 2);
3432  ctx.arc(x + cheekDist, cheekY, cheekRadius, 0, Math.PI * 2);
3433  ctx.fillStyle = isDon ? "rgba(244, 63, 94, 0.85)" : "rgba(56, 189, 248, 0.85)";
3434  ctx.fill();
3435  const eyeY = y - r * 0.1;
3436  const eyeDist = r * 0.32;
3437  const eyeR = r * 0.15;
3438  if (isDon) {
3439    ctx.beginPath();
3440    ctx.arc(x - eyeDist, eyeY, eyeR, 0, Math.PI * 2);
3441    ctx.fillStyle = "#18181b";
3442    ctx.fill();
3443    ctx.beginPath();
3444    ctx.arc(x - eyeDist - eyeR * 0.3, eyeY - eyeR * 0.3, eyeR * 0.42, 0, Math.PI * 2);
3445    ctx.fillStyle = "#ffffff";
3446    ctx.fill();
3447    ctx.beginPath();
3448    ctx.arc(x + eyeDist, eyeY, eyeR, 0, Math.PI * 2);
3449    ctx.fillStyle = "#18181b";
3450    ctx.fill();
3451    ctx.beginPath();
3452    ctx.arc(x + eyeDist - eyeR * 0.3, eyeY - eyeR * 0.3, eyeR * 0.42, 0, Math.PI * 2);
3453    ctx.fillStyle = "#ffffff";
3454    ctx.fill();
3455  } else {
3456    ctx.lineWidth = Math.max(2, r * 0.11);
3457    ctx.strokeStyle = "#0f172a";
3458    ctx.lineCap = "round";
3459    ctx.beginPath();
3460    ctx.arc(x - eyeDist, eyeY + eyeR * 0.35, eyeR, Math.PI * 1.15, Math.PI * 1.85);
3461    ctx.stroke();
3462    ctx.beginPath();
3463    ctx.arc(x + eyeDist, eyeY + eyeR * 0.35, eyeR, Math.PI * 1.15, Math.PI * 1.85);
3464    ctx.stroke();
3465  }
3466  const noseY = y + r * 0.1;
3467  ctx.beginPath();
3468  ctx.arc(x, noseY, r * 0.08, 0, Math.PI * 2);
3469  ctx.fillStyle = isDon ? "#e11d48" : "#0284c7";
3470  ctx.fill();
3471  ctx.lineWidth = Math.max(1.5, r * 0.075);
3472  ctx.strokeStyle = isDon ? "#881337" : "#0f172a";
3473  ctx.lineCap = "round";
3474  const mouthY = noseY + r * 0.06;
3475  const mouthW = r * 0.14;
3476  ctx.beginPath();
3477  ctx.arc(x - mouthW * 0.5, mouthY, mouthW * 0.5, 0, Math.PI * 0.9);
3478  ctx.stroke();
3479  ctx.beginPath();
3480  ctx.arc(x + mouthW * 0.5, mouthY, mouthW * 0.5, Math.PI * 0.1, Math.PI);
3481  ctx.stroke();
3482  ctx.lineWidth = 1;
3483  ctx.strokeStyle = "rgba(0, 0, 0, 0.3)";
3484  ctx.beginPath();
3485  ctx.moveTo(x - r * 0.44, y + r * 0.1);
3486  ctx.lineTo(x - r * 0.76, y + r * 0.05);
3487  ctx.moveTo(x - r * 0.44, y + r * 0.2);
3488  ctx.lineTo(x - r * 0.74, y + r * 0.26);
3489  ctx.moveTo(x + r * 0.44, y + r * 0.1);
3490  ctx.lineTo(x + r * 0.76, y + r * 0.05);
3491  ctx.moveTo(x + r * 0.44, y + r * 0.2);
3492  ctx.lineTo(x + r * 0.74, y + r * 0.26);
3493  ctx.stroke();
3494  ctx.restore();
3495}
3496function drawNoteLabelBelow(ctx, x, centerY, r, label, isDon, isBig) {
3497  ctx.save();
3498  const labelY = centerY + r + (isBig ? 10 : 8);
3499  const fontSize = Math.max(9, Math.round(r * 0.42));
3500  ctx.font = `900 ${fontSize}px ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif`;
3501  ctx.textAlign = "center";
3502  ctx.textBaseline = "middle";
3503  const textMetrics = ctx.measureText(label);
3504  const pillW = textMetrics.width + 10;
3505  const pillH = fontSize + 4;
3506  const pillR = Math.round(pillH * 0.45);
3507  ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
3508  ctx.strokeStyle = isDon ? "#f87171" : "#60a5fa";
3509  ctx.lineWidth = 1.2;
3510  ctx.beginPath();
3511  if (typeof ctx.roundRect === "function") {
3512    ctx.roundRect(x - pillW / 2, labelY - pillH / 2, pillW, pillH, pillR);
3513  } else {
3514    ctx.rect(x - pillW / 2, labelY - pillH / 2, pillW, pillH);
3515  }
3516  ctx.fill();
3517  ctx.stroke();
3518  ctx.fillStyle = isDon ? "#fca5a5" : "#93c5fd";
3519  ctx.fillText(label, x, labelY);
3520  ctx.restore();
3521}
3522function drawSoulGauge(ctx, x, y, w, h, soulPercent) {
3523  ctx.save();
3524  const radius = 5;
3525  ctx.beginPath();
3526  if (typeof ctx.roundRect === "function") {
3527    ctx.roundRect(x - 2, y - 2, w + 4, h + 4, radius + 2);
3528  } else {
3529    ctx.rect(x - 2, y - 2, w + 4, h + 4);
3530  }
3531  ctx.fillStyle = "#090a10";
3532  ctx.fill();
3533  ctx.lineWidth = 2;
3534  ctx.strokeStyle = "#000000";
3535  ctx.stroke();
3536  const segWidth = 9;
3537  const segGap = 2;
3538  const slant = 4;
3539  const totalSegs = Math.max(12, Math.floor((w - slant) / (segWidth + segGap)));
3540  const actualStep = (w - slant) / totalSegs;
3541  const activeSegCount = Math.round(Math.min(100, Math.max(0, soulPercent)) / 100 * totalSegs);
3542  const clearThresholdSeg = Math.round(totalSegs * 0.8);
3543  ctx.save();
3544  ctx.beginPath();
3545  if (typeof ctx.roundRect === "function") {
3546    ctx.roundRect(x, y, w, h, radius);
3547  } else {
3548    ctx.rect(x, y, w, h);
3549  }
3550  ctx.clip();
3551  ctx.fillStyle = "#06080e";
3552  ctx.fillRect(x, y, w, h);
3553  for (let i = 0; i < totalSegs; i++) {
3554    const segX = x + i * actualStep;
3555    const isActive = i < activeSegCount;
3556    const isPastClear = i >= clearThresholdSeg;
3557    ctx.beginPath();
3558    ctx.moveTo(segX + slant, y);
3559    ctx.lineTo(segX + segWidth + slant, y);
3560    ctx.lineTo(segX + segWidth, y + h);
3561    ctx.lineTo(segX, y + h);
3562    ctx.closePath();
3563    if (isActive) {
3564      if (isPastClear) {
3565        const goldGrad = ctx.createLinearGradient(segX, y, segX, y + h);
3566        goldGrad.addColorStop(0, "#fef08a");
3567        goldGrad.addColorStop(0.3, "#f59e0b");
3568        goldGrad.addColorStop(0.7, "#ea580c");
3569        goldGrad.addColorStop(1, "#b91c1c");
3570        ctx.fillStyle = goldGrad;
3571      } else {
3572        const greenGrad = ctx.createLinearGradient(segX, y, segX, y + h);
3573        greenGrad.addColorStop(0, "#6ee7b7");
3574        greenGrad.addColorStop(0.3, "#2dd4bf");
3575        greenGrad.addColorStop(0.65, "#059669");
3576        greenGrad.addColorStop(1, "#064e3b");
3577        ctx.fillStyle = greenGrad;
3578      }
3579      ctx.fill();
3580      ctx.save();
3581      ctx.beginPath();
3582      ctx.moveTo(segX + slant, y);
3583      ctx.lineTo(segX + segWidth + slant, y);
3584      ctx.lineTo(segX + segWidth + slant * 0.5, y + h * 0.48);
3585      ctx.lineTo(segX + slant * 0.5, y + h * 0.48);
3586      ctx.closePath();
3587      const glossGrad = ctx.createLinearGradient(segX, y, segX, y + h * 0.48);
3588      glossGrad.addColorStop(0, "rgba(255, 255, 255, 0.85)");
3589      glossGrad.addColorStop(0.4, "rgba(255, 255, 255, 0.45)");
3590      glossGrad.addColorStop(1, "rgba(255, 255, 255, 0.05)");
3591      ctx.fillStyle = glossGrad;
3592      ctx.fill();
3593      ctx.restore();
3594    } else {
3595      ctx.fillStyle = "rgba(255, 255, 255, 0.04)";
3596      ctx.fill();
3597    }
3598    ctx.lineWidth = 1;
3599    ctx.strokeStyle = "#06080e";
3600    ctx.stroke();
3601  }
3602  const clearX = x + clearThresholdSeg * actualStep;
3603  ctx.beginPath();
3604  ctx.moveTo(clearX + slant, y - 2);
3605  ctx.lineTo(clearX, y + h + 2);
3606  ctx.lineWidth = 2.5;
3607  ctx.strokeStyle = "#ffffff";
3608  ctx.stroke();
3609  ctx.restore();
3610  const isCleared = soulPercent >= 80;
3611  ctx.save();
3612  ctx.font = "900 9px ui-sans-serif, system-ui, sans-serif";
3613  ctx.textAlign = "center";
3614  ctx.textBaseline = "bottom";
3615  ctx.fillStyle = isCleared ? "#fbbf24" : "rgba(255, 255, 255, 0.75)";
3616  ctx.shadowColor = isCleared ? "#f59e0b" : "transparent";
3617  ctx.shadowBlur = isCleared ? 8 : 0;
3618  ctx.fillText("CLEAR 80%", clearX + 6, y - 2);
3619  ctx.restore();
3620  ctx.restore();
3621}
3622const TaikoGame = ({
3623  data,
3624  melodyData,
3625  processedInstruments,
3626  midiMetadata,
3627  currentTime,
3628  isPlaying,
3629  duration,
3630  tempo,
3631  timeSignature,
3632  trackId,
3633  userId,
3634  userName,
3635  onPlayPause,
3636  onSeek,
3637  onGameModeMute,
3638  playbackRate = 1,
3639  theme,
3640  isLoadingAudio,
3641  isAudioLoaded
3642}) => {
3643  const { t, i18n } = useTranslation();
3644  const locale = reactExports.useMemo(() => getTaikoLocale(i18n.language), [i18n.language]);
3645  const localeRef = reactExports.useRef(locale);
3646  localeRef.current = locale;
3647  const [gameState, setGameState] = reactExports.useState("lobby");
3648  const gameStateRef = reactExports.useRef("lobby");
3649  gameStateRef.current = gameState;
3650  const ALL_STAGE_THEME_IDS = [
3651    "matsuri",
3652    "sakura",
3653    "thunder",
3654    "neon",
3655    "bamboo",
3656    "autumn_temple",
3657    "ocean_sunset",
3658    "dojo",
3659    "snow_village",
3660    "shinto_shrine",
3661    "space_galaxy",
3662    "retro_arcade",
3663    "dragon_volcano",
3664    "castle_night",
3665    "zen_garden",
3666    "carnival"
3667  ];
3668  const [playerCount, setPlayerCount] = reactExports.useState(1);
3669  const [isCooperative, setIsCooperative] = reactExports.useState(false);
3670  const [difficulty, setDifficulty] = reactExports.useState("futsuu");
3671  const [selectedBackground, setSelectedBackground] = reactExports.useState("random");
3672  const [resolvedRandomBgId, setResolvedRandomBgId] = reactExports.useState(() => {
3673    return ALL_STAGE_THEME_IDS[Math.floor(Math.random() * ALL_STAGE_THEME_IDS.length)];
3674  });
3675  const rollRandomBackground = reactExports.useCallback(() => {
3676    setResolvedRandomBgId((prev) => {
3677      const others = ALL_STAGE_THEME_IDS.filter((id) => id !== prev);
3678      return others[Math.floor(Math.random() * others.length)] || ALL_STAGE_THEME_IDS[0];
3679    });
3680  }, []);
3681  reactExports.useEffect(() => {
3682    if (selectedBackground === "random") {
3683      rollRandomBackground();
3684    }
3685  }, [trackId]);
3686  const [scrollSpeedMultiplier, setScrollSpeedMultiplier] = reactExports.useState(1);
3687  const [lobbyView, setLobbyView] = reactExports.useState("main");
3688  const [showMoreSettings, setShowMoreSettings] = reactExports.useState(false);
3689  const [countdownNumber, setCountdownNumber] = reactExports.useState(3);
3690  const [showStartFlash, setShowStartFlash] = reactExports.useState(false);
3691  const [showKeyHelp, setShowKeyHelp] = reactExports.useState(false);
3692  const [showTouchControls, setShowTouchControls] = reactExports.useState(() => {
3693    if (typeof window === "undefined") return false;
3694    return "ontouchstart" in window || navigator.maxTouchPoints > 0;
3695  });
3696  const [showPcKeys, setShowPcKeys] = reactExports.useState(() => {
3697    if (typeof window === "undefined") return true;
3698    try {
3699      const saved = localStorage.getItem("taiko_show_pc_keys");
3700      return saved === null ? true : saved === "true";
3701    } catch {
3702      return true;
3703    }
3704  });
3705  const showPcKeysRef = reactExports.useRef(showPcKeys);
3706  reactExports.useEffect(() => {
3707    showPcKeysRef.current = showPcKeys;
3708    try {
3709      localStorage.setItem("taiko_show_pc_keys", String(showPcKeys));
3710    } catch {
3711    }
3712  }, [showPcKeys]);
3713  reactExports.useEffect(() => {
3714    if (typeof window !== "undefined") {
3715      window.__toggleTaikoTouch = () => setShowTouchControls((prev) => !prev);
3716      window.__setTaikoTouch = (v) => setShowTouchControls(v);
3717      window.__startTaikoGame = () => {
3718        setGameState("playing");
3719        gameStateRef.current = "playing";
3720      };
3721      window.__setTaikoPlayers = (p) => {
3722        setPlayerCount(p);
3723      };
3724    }
3725  }, []);
3726  const [canvasSize, setCanvasSize] = reactExports.useState({ width: 800, height: 450 });
3727  const [containerSize, setContainerSize] = reactExports.useState({ width: 800, height: 450 });
3728  const lobbyScale = reactExports.useMemo(() => {
3729    const isMobile = containerSize.width < 768;
3730    const targetW = isMobile ? 380 : lobbyView === "leaderboard" ? 580 : 480;
3731    const targetH = isMobile ? lobbyView === "leaderboard" ? 440 : showMoreSettings ? 490 : 330 : lobbyView === "leaderboard" ? 440 : showMoreSettings ? 480 : 330;
3732    const availW = Math.max(120, containerSize.width - 16);
3733    const availH = Math.max(120, containerSize.height - 16);
3734    return Math.min(1, availW / targetW, availH / targetH);
3735  }, [containerSize.width, containerSize.height, lobbyView, showMoreSettings]);
3736  const trackLayout = reactExports.useMemo(() => {
3737    return getTrackLayout(canvasSize.height, canvasSize.width, playerCount);
3738  }, [canvasSize.height, canvasSize.width, playerCount]);
3739  const rootContainerRef = reactExports.useRef(null);
3740  const canvasWrapperRef = reactExports.useRef(null);
3741  const canvasRef = reactExports.useRef(null);
3742  const lastTimeRef = reactExports.useRef(currentTime);
3743  const lastTickRef = reactExports.useRef(performance.now());
3744  const isPlayingRef = reactExports.useRef(isPlaying);
3745  isPlayingRef.current = isPlaying;
3746  const countdownStartTimeRef = reactExports.useRef(0);
3747  const countdownTotalDurationSecRef = reactExports.useRef(0);
3748  const startFlashTimeoutRef = reactExports.useRef(null);
3749  const particlesRef = reactExports.useRef([]);
3750  const shockwavesRef = reactExports.useRef([]);
3751  const judgementsRef = reactExports.useRef([]);
3752  const [chart, setChart] = reactExports.useState(null);
3753  const chartNotesRef = reactExports.useRef([]);
3754  const hitNotesPerPlayerRef = reactExports.useRef(/* @__PURE__ */ new Map());
3755  const missedNotesPerPlayerRef = reactExports.useRef(/* @__PURE__ */ new Map());
3756  const inputManagerRef = reactExports.useRef(new TaikoInputManager());
3757  const lastDonKeyRef = reactExports.useRef(/* @__PURE__ */ new Map());
3758  const durationRef = reactExports.useRef(duration);
3759  durationRef.current = duration;
3760  const livePlayerStatesRef = reactExports.useRef([
3761    {
3762      id: 1,
3763      score: 0,
3764      combo: 0,
3765      maxCombo: 0,
3766      perfectCount: 0,
3767      goodCount: 0,
3768      missCount: 0,
3769      drumrollHits: 0,
3770      soulGauge: 0,
3771      lastHitType: null,
3772      lastSubKey: null,
3773      isHitting: false,
3774      hitDrumhead: false,
3775      hitRim: false,
3776      sectionHits: createDefaultSectionHits()
3777    }
3778  ]);
3779  const [mascotStats, setMascotStats] = reactExports.useState({
3780    combo: 0,
3781    isHitting: false,
3782    lastHitType: null,
3783    lastSubKey: null,
3784    strikeTick: 0,
3785    soulGauge: 0
3786  });
3787  const [p2MascotStats, setP2MascotStats] = reactExports.useState({
3788    combo: 0,
3789    isHitting: false,
3790    lastHitType: null,
3791    lastSubKey: null,
3792    strikeTick: 0,
3793    soulGauge: 0
3794  });
3795  const BACKGROUNDS = reactExports.useMemo(() => [
3796    { id: "matsuri", label: locale.themeMatsuri, url: "/assets/visualizers/taiko/matsuri.jpg" },
3797    { id: "sakura", label: locale.themeSakura, url: "/assets/visualizers/taiko/sakura.jpg" },
3798    { id: "thunder", label: locale.themeThunder, url: "/assets/visualizers/taiko/thunder.jpg" },
3799    { id: "neon", label: locale.themeNeon, url: "/assets/visualizers/taiko/neon.jpg" },
3800    { id: "bamboo", label: locale.themeBamboo || "🎋 Bamboo Moon", url: "/assets/visualizers/taiko/bamboo.jpg" },
3801    { id: "autumn_temple", label: locale.themeAutumnTemple || "🍁 Autumn Pagoda", url: "/assets/visualizers/taiko/autumn_temple.jpg" },
3802    { id: "ocean_sunset", label: locale.themeOceanSunset || "🌊 Ukiyo-e Wave", url: "/assets/visualizers/taiko/ocean_sunset.jpg" },
3803    { id: "dojo", label: locale.themeDojo || "🏯 Taiko Dojo", url: "/assets/visualizers/taiko/dojo.jpg" },
3804    { id: "snow_village", label: locale.themeSnowVillage || "❄️ Snow Village", url: "/assets/visualizers/taiko/snow_village.jpg" },
3805    { id: "shinto_shrine", label: locale.themeShintoShrine || "⛩️ Thousand Torii", url: "/assets/visualizers/taiko/shinto_shrine.jpg" },
3806    { id: "space_galaxy", label: locale.themeSpaceGalaxy || "🌌 Cosmic Nebula", url: "/assets/visualizers/taiko/sp
3806ace_galaxy.jpg" },
3807    { id: "retro_arcade", label: locale.themeRetroArcade || "🕹️ Retro Arcade", url: "/assets/visualizers/taiko/retro_arcade.jpg" },
3808    { id: "dragon_volcano", label: locale.themeDragonVolcano || "🐉 Dragon Volcano", url: "/assets/visualizers/taiko/dragon_volcano.jpg" },
3809    { id: "castle_night", label: locale.themeCastleNight || "🏯 Moonlight Castle", url: "/assets/visualizers/taiko/castle_night.jpg" },
3810    { id: "zen_garden", label: locale.themeZenGarden || "🪨 Kyoto Zen Garden", url: "/assets/visualizers/taiko/zen_garden.jpg" },
3811    { id: "carnival", label: locale.themeCarnival || "🎪 Taiko Carnival", url: "/assets/visualizers/taiko/carnival.jpg" }
3812  ], [locale]);
3813  const [leaderboard, setLeaderboard] = reactExports.useState([]);
3814  const playStartTimeRef = reactExports.useRef(0);
3815  const consecutiveMissesRef = reactExports.useRef(0);
3816  const consecutiveHitsRef = reactExports.useRef(0);
3817  const isBeginnerModeRef = reactExports.useRef(false);
3818  const lastBubbleUpdateRef = reactExports.useRef(0);
3819  const [showBeginnerBubble, setShowBeginnerBubble] = reactExports.useState(false);
3820  const [beginnerHint, setBeginnerHint] = reactExports.useState(null);
3821  reactExports.useEffect(() => {
3822    if (trackId) {
3823      api.getHighscores(trackId, "taiko", userId).then((res) => {
3824        const rawList = Array.isArray(res) ? res : res && res.highscores ? res.highscores : [];
3825        const seen = /* @__PURE__ */ new Set();
3826        const list = [];
3827        for (const item of rawList) {
3828          const key = item.userId ? `u_${item.userId}` : item.userName ? `n_${item.userName}` : `id_${item.id}`;
3829          if (!seen.has(key)) {
3830            seen.add(key);
3831            list.push(item);
3832          }
3833        }
3834        setLeaderboard(list);
3835      }).catch(() => {
3836      });
3837    }
3838  }, [trackId, userId, gameState]);
3839  reactExports.useEffect(() => {
3840    const gen = generateTaikoChart(
3841      data,
3842      melodyData,
3843      processedInstruments,
3844      midiMetadata,
3845      difficulty,
3846      duration
3847    );
3848    setChart(gen);
3849    chartNotesRef.current = gen.notes;
3850  }, [data, melodyData, processedInstruments, midiMetadata, difficulty, duration]);
3851  reactExports.useEffect(() => {
3852    if (gameStateRef.current === "countdown") {
3853      if (currentTime === 0) {
3854        lastTimeRef.current = 0;
3855        lastTickRef.current = performance.now();
3856      }
3857      return;
3858    }
3859    lastTimeRef.current = currentTime;
3860    lastTickRef.current = performance.now();
3861  }, [currentTime]);
3862  reactExports.useEffect(() => {
3863    if (gameStateRef.current === "countdown") return;
3864    if (isPlaying) {
3865      lastTickRef.current = performance.now();
3866    } else {
3867      const elapsed = (performance.now() - lastTickRef.current) / 1e3;
3868      lastTimeRef.current += elapsed * playbackRate;
3869    }
3870  }, [isPlaying, playbackRate]);
3871  reactExports.useEffect(() => {
3872    const newStates = [];
3873    for (let p = 1; p <= playerCount; p++) {
3874      newStates.push({
3875        id: p,
3876        score: 0,
3877        combo: 0,
3878        maxCombo: 0,
3879        perfectCount: 0,
3880        goodCount: 0,
3881        missCount: 0,
3882        drumrollHits: 0,
3883        soulGauge: 0,
3884        lastHitType: null,
3885        lastSubKey: null,
3886        isHitting: false,
3887        hitDrumhead: false,
3888        hitRim: false,
3889        sectionHits: createDefaultSectionHits()
3890      });
3891    }
3892    livePlayerStatesRef.current = newStates;
3893    inputManagerRef.current.setActivePlayers(playerCount);
3894    hitNotesPerPlayerRef.current = /* @__PURE__ */ new Map();
3895    missedNotesPerPlayerRef.current = /* @__PURE__ */ new Map();
3896    for (let p = 1; p <= playerCount; p++) {
3897      hitNotesPerPlayerRef.current.set(p, /* @__PURE__ */ new Set());
3898      missedNotesPerPlayerRef.current.set(p, /* @__PURE__ */ new Set());
3899    }
3900  }, [playerCount]);
3901  reactExports.useEffect(() => {
3902    const im = inputManagerRef.current;
3903    im.setOnHit((pId, action, isSimultaneous, subKey) => {
3904      handlePlayerHit(pId, action, isSimultaneous, subKey);
3905    });
3906    if (gameState === "playing" || gameState === "lobby" || gameState === "countdown") {
3907      im.start();
3908    } else {
3909      im.stop();
3910    }
3911    return () => im.stop();
3912  }, [gameState]);
3913  const handlePlayerHit = reactExports.useCallback((pId, action, isSimultaneous, subKey) => {
3914    const isDon = action === "don" || action === "big_don";
3915    const isBig = action.startsWith("big_");
3916    if (isDon) {
3917      taikoAudio.playDon(isBig);
3918    } else {
3919      taikoAudio.playKa(isBig);
3920    }
3921    const pState = livePlayerStatesRef.current[pId - 1];
3922    if (pState) {
3923      pState.isHitting = true;
3924      pState.lastHitType = action;
3925      pState.lastSubKey = subKey;
3926      pState.hitDrumhead = isDon;
3927      pState.hitRim = !isDon;
3928      setTimeout(() => {
3929        if (livePlayerStatesRef.current[pId - 1]) {
3930          livePlayerStatesRef.current[pId - 1].isHitting = false;
3931          livePlayerStatesRef.current[pId - 1].hitDrumhead = false;
3932          livePlayerStatesRef.current[pId - 1].hitRim = false;
3933          livePlayerStatesRef.current[pId - 1].lastSubKey = null;
3934        }
3935      }, 100);
3936    }
3937    if (pId === 1) {
3938      setMascotStats((prev) => ({
3939        combo: (pState == null ? void 0 : pState.combo) || 0,
3940        isHitting: true,
3941        lastHitType: action,
3942        lastSubKey: subKey || null,
3943        strikeTick: prev.strikeTick + 1,
3944        soulGauge: (pState == null ? void 0 : pState.soulGauge) || 0
3945      }));
3946      setTimeout(() => {
3947        setMascotStats((prev) => ({ ...prev, isHitting: false }));
3948      }, 180);
3949    } else if (pId === 2) {
3950      setP2MascotStats((prev) => ({
3951        combo: (pState == null ? void 0 : pState.combo) || 0,
3952        isHitting: true,
3953        lastHitType: action,
3954        lastSubKey: subKey || null,
3955        strikeTick: prev.strikeTick + 1,
3956        soulGauge: (pState == null ? void 0 : pState.soulGauge) || 0
3957      }));
3958      setTimeout(() => {
3959        setP2MascotStats((prev) => ({ ...prev, isHitting: false }));
3960      }, 180);
3961    }
3962    if (gameState !== "playing") return;
3963    const now = performance.now();
3964    const currentT = isPlayingRef.current ? lastTimeRef.current + (now - lastTickRef.current) / 1e3 * playbackRate : lastTimeRef.current;
3965    const offsetSec = taikoAudio.getOffsetMs() / 1e3;
3966    const adjustedT = currentT - offsetSec;
3967    const playerHits = hitNotesPerPlayerRef.current.get(pId) || /* @__PURE__ */ new Set();
3968    const playerMisses = missedNotesPerPlayerRef.current.get(pId) || /* @__PURE__ */ new Set();
3969    const notes = chartNotesRef.current;
3970    let candidateNote = null;
3971    let minDiff = 999;
3972    for (const note of notes) {
3973      if (playerHits.has(note.id) || playerMisses.has(note.id)) continue;
3974      if ((note.type === "drumroll" || note.type === "balloon") && adjustedT >= note.t - 0.12 && adjustedT <= note.t + note.duration + 0.1) {
3975        candidateNote = note;
3976        minDiff = 0;
3977        break;
3978      }
3979      const diff = Math.abs(note.t - adjustedT);
3980      if (diff < minDiff && diff <= 0.17) {
3981        minDiff = diff;
3982        candidateNote = note;
3983      }
3984    }
3985    const canvas = canvasRef.current;
3986    const dpr = window.devicePixelRatio || 1;
3987    const width = canvas ? canvas.width / dpr : 800;
3988    const height = canvas ? canvas.height / dpr : 450;
3989    const { laneH, startY, hitZoneX } = getTrackLayout(height, width, playerCount);
3990    const laneY = startY + (pId - 1) * laneH;
3991    const ribbonTop = laneY + 12;
3992    const ribbonHeight = laneH - 14;
3993    const hitX = hitZoneX;
3994    const hitY = ribbonTop + ribbonHeight * 0.5;
3995    shockwavesRef.current.push({
3996      x: hitX,
3997      y: hitY,
3998      radius: 20,
3999      maxRadius: isBig ? 65 : 45,
4000      color: isBig ? "#f59e0b" : isDon ? "#ef4444" : "#3b82f6",
4001      lineWidth: isBig ? 5 : 3,
4002      alpha: 1
4003    });
4004    if (!candidateNote || !pState) return;
4005    if (candidateNote.type === "drumroll" || candidateNote.type === "balloon") {
4006      candidateNote.hitsCount = (candidateNote.hitsCount || 0) + 1;
4007      const pts = 30 * DIFFICULTY_CONFIG[difficulty].scoreMultiplier;
4008      pState.score += Math.round(pts);
4009      pState.drumrollHits += 1;
4010      pState.soulGauge = Math.min(100, pState.soulGauge + 0.6);
4011      if (candidateNote.type === "balloon" && candidateNote.hitsRequired && candidateNote.hitsCount >= candidateNote.hitsRequired) {
4012        taikoAudio.playBalloonPop();
4013        playerHits.add(candidateNote.id);
4014        spawnJudgement(locale.balloon, "#fbbf24", hitX, hitY, "+5000");
4015        pState.score += 5e3;
4016        spawnParticles(hitX, hitY, "#fbbf24", 24);
4017      } else {
4018        spawnParticles(hitX, hitY, "#f59e0b", 4);
4019      }
4020      if (pId === 1) {
4021        setMascotStats((prev) => ({
4022          ...prev,
4023          combo: pState.combo,
4024          soulGauge: pState.soulGauge
4025        }));
4026      }
4027      return;
4028    }
4029    const isNoteDon = candidateNote.type === "don" || candidateNote.type === "big_don";
4030    const isNoteKa = candidateNote.type === "ka" || candidateNote.type === "big_ka";
4031    if (isDon && !isNoteDon || !isDon && !isNoteKa) {
4032      playerMisses.add(candidateNote.id);
4033      pState.combo = 0;
4034      pState.missCount += 1;
4035      pState.soulGauge = Math.max(0, pState.soulGauge - 2.5);
4036      if (pState.sectionHits) {
4037        const songDur = Math.max(10, durationRef.current || 60);
4038        const secIdx = Math.min(4, Math.max(0, Math.floor(candidateNote.t / (songDur / 5))));
4039        if (pState.sectionHits[secIdx]) {
4040          pState.sectionHits[secIdx].miss += 1;
4041        }
4042      }
4043      spawnJudgement(locale.miss, "#f43f5e", hitX, hitY);
4044      if (pId === 1) {
4045        consecutiveMissesRef.current += 1;
4046        consecutiveHitsRef.current = 0;
4047        if (now - playStartTimeRef.current < 15e3 && consecutiveMissesRef.current >= 2) {
4048          isBeginnerModeRef.current = true;
4049          setShowBeginnerBubble(true);
4050        } else if (consecutiveMissesRef.current >= 4) {
4051          isBeginnerModeRef.current = true;
4052          setShowBeginnerBubble(true);
4053        }
4054        setMascotStats((prev) => ({ ...prev, combo: 0, soulGauge: pState.soulGauge }));
4055      }
4056      return;
4057    }
4058    const isPerfect = minDiff <= 0.055;
4059    const isGood = minDiff <= 0.135;
4060    playerHits.add(candidateNote.id);
4061    const isBigNote = candidateNote.type === "big_don" || candidateNote.type === "big_ka";
4062    const diffMult = DIFFICULTY_CONFIG[difficulty].scoreMultiplier;
4063    let basePts = isPerfect ? 100 : 50;
4064    let stickingBonus = false;
4065    if (isDon && subKey) {
4066      const lastDon = lastDonKeyRef.current.get(pId);
4067      if (lastDon && now - lastDon.time < 260 && (difficulty === "muzukashii" || difficulty === "oni")) {
4068        if (lastDon.subKey !== subKey) {
4069          stickingBonus = true;
4070        }
4071      }
4072      lastDonKeyRef.current.set(pId, { subKey, time: now });
4073    }
4074    let isDualBonus = false;
4075    if (isBigNote) {
4076      if (isSimultaneous) {
4077        basePts = Math.round(basePts * 2.5);
4078        isDualBonus = true;
4079      } else {
4080        basePts = Math.round(basePts * 1.2);
4081      }
4082    } else if (stickingBonus) {
4083      basePts = Math.round(basePts * 1.25);
4084    }
4085    pState.combo += 1;
4086    pState.maxCombo = Math.max(pState.maxCombo, pState.combo);
4087    if (isPerfect) {
4088      pState.perfectCount += 1;
4089      pState.soulGauge = Math.min(100, pState.soulGauge + (isDualBonus || stickingBonus ? 2.2 : 1.6));
4090    } else if (isGood) {
4091      pState.goodCount += 1;
4092      pState.soulGauge = Math.min(100, pState.soulGauge + 0.8);
4093    }
4094    if (pState.sectionHits) {
4095      const songDur = Math.max(10, durationRef.current || 60);
4096      const secIdx = Math.min(4, Math.max(0, Math.floor(candidateNote.t / (songDur / 5))));
4097      if (pState.sectionHits[secIdx]) {
4098        if (isPerfect) pState.sectionHits[secIdx].perfect += 1;
4099        else if (isGood) pState.sectionHits[secIdx].good += 1;
4100      }
4101    }
4102    const comboMult = Math.min(4, 1 + Math.floor(pState.combo / 15) * 0.5);
4103    const addedScore = Math.round(basePts * comboMult * diffMult);
4104    pState.score += addedScore;
4105    let judgeText = isPerfect ? locale.perfect : locale.good;
4106    if (isDualBonus) {
4107      judgeText = `${judgeText} (DUAL!)`;
4108    } else if (stickingBonus) {
4109      judgeText = `${judgeText} (STICKING!)`;
4110    }
4111    const judgeColor = isDualBonus ? "#fbbf24" : isPerfect ? "#fbbf24" : "#38bdf8";
4112    spawnJudgement(judgeText, judgeColor, hitX, hitY, `+${addedScore}`);
4113    spawnParticles(hitX, hitY, judgeColor, isDualBonus ? 28 : isPerfect ? 16 : 8);
4114    if (pId === 1) {
4115      consecutiveHitsRef.current += 1;
4116      consecutiveMissesRef.current = 0;
4117      if (consecutiveHitsRef.current >= 4 && isBeginnerModeRef.current) {
4118        isBeginnerModeRef.current = false;
4119        setShowBeginnerBubble(false);
4120        setBeginnerHint(null);
4121      }
4122      setMascotStats((prev) => ({
4123        ...prev,
4124        combo: pState.combo,
4125        soulGauge: pState.soulGauge
4126      }));
4127    }
4128  }, [difficulty, gameState, playbackRate, locale, playerCount]);
4129  const spawnJudgement = (text, color, x, y, points) => {
4130    judgementsRef.current.push({
4131      text,
4132      color,
4133      x,
4134      y: y - 24,
4135      alpha: 1,
4136      scale: 1.25,
4137      points
4138    });
4139  };
4140  const spawnParticles = (x, y, color, count = 12) => {
4141    for (let i = 0; i < count; i++) {
4142      const angle = Math.random() * Math.PI * 2;
4143      const speed = 2 + Math.random() * 5;
4144      particlesRef.current.push({
4145        x,
4146        y,
4147        vx: Math.cos(angle) * speed,
4148        vy: Math.sin(angle) * speed,
4149        color,
4150        size: 3 + Math.random() * 4,
4151        life: 1,
4152        decay: 0.035 + Math.random() * 0.02
4153      });
4154    }
4155  };
4156  const countdownIntervalRef = reactExports.useRef(null);
4157  const [waitingForAudio, setWaitingForAudio] = reactExports.useState(false);
4158  const isAudioReady = isAudioLoaded !== false && !isLoadingAudio;
4159  const startCountdownSequence = reactExports.useCallback((customStartTime) => {
4160    if (!chart || chart.notes.length === 0) return;
4161    const startAt = typeof customStartTime === "number" ? Math.max(0, customStartTime) : 0;
4162    if (onSeek) {
4163      onSeek(startAt);
4164    }
4165    lastTimeRef.current = startAt;
4166    lastTickRef.current = performance.now();
4167    if (isPlaying && onPlayPause) {
4168      onPlayPause(false);
4169    }
4170    for (let p = 1; p <= playerCount; p++) {
4171      const pState = livePlayerStatesRef.current[p - 1];
4172      if (pState) {
4173        pState.score = 0;
4174        pState.combo = 0;
4175        pState.maxCombo = 0;
4176        pState.perfectCount = 0;
4177        pState.goodCount = 0;
4178        pState.missCount = 0;
4179        pState.drumrollHits = 0;
4180        pState.soulGauge = 0;
4181        pState.lastHitType = null;
4182        pState.lastSubKey = null;
4183        pState.isHitting = false;
4184        pState.hitDrumhead = false;
4185        pState.hitRim = false;
4186        pState.sectionHits = createDefaultSectionHits();
4187      }
4188    }
4189    hitNotesPerPlayerRef.current = /* @__PURE__ */ new Map([[1, /* @__P
4189URE__ */ new Set()], [2, /* @__PURE__ */ new Set()]]);
4190    missedNotesPerPlayerRef.current = /* @__PURE__ */ new Map([[1, /* @__PURE__ */ new Set()], [2, /* @__PURE__ */ new Set()]]);
4191    chartNotesRef.current.forEach((n) => {
4192      n.hitsCount = 0;
4193    });
4194    playStartTimeRef.current = performance.now();
4195    consecutiveMissesRef.current = 0;
4196    consecutiveHitsRef.current = 0;
4197    isBeginnerModeRef.current = false;
4198    setShowBeginnerBubble(false);
4199    setBeginnerHint(null);
4200    const bpm = chart.meta.bpm || tempo || 120;
4201    const beatSec = 60 / bpm;
4202    const beatDurationMs = Math.max(550, Math.min(850, Math.round(beatSec * 1e3)));
4203    const totalCountdownMs = 3 * beatDurationMs;
4204    const totalCountdownSec = totalCountdownMs / 1e3;
4205    countdownStartTimeRef.current = performance.now();
4206    countdownTotalDurationSecRef.current = totalCountdownSec;
4207    setGameState("countdown");
4208    gameStateRef.current = "countdown";
4209    setCountdownNumber(3);
4210    taikoAudio.playKa(false, 0.9);
4211    let count = 3;
4212    if (countdownIntervalRef.current) clearInterval(countdownIntervalRef.current);
4213    countdownIntervalRef.current = setInterval(() => {
4214      count--;
4215      if (count > 0) {
4216        setCountdownNumber(count);
4217        taikoAudio.playKa(false, 0.9);
4218      } else if (count === 0) {
4219        if (countdownIntervalRef.current) clearInterval(countdownIntervalRef.current);
4220        countdownIntervalRef.current = null;
4221        setCountdownNumber(locale.start);
4222        setShowStartFlash(true);
4223        taikoAudio.playDon(true, 1.4);
4224        setGameState("playing");
4225        gameStateRef.current = "playing";
4226        if (onPlayPause) {
4227          onPlayPause(true);
4228        }
4229        lastTimeRef.current = startAt;
4230        lastTickRef.current = performance.now();
4231        playStartTimeRef.current = performance.now();
4232        consecutiveMissesRef.current = 0;
4233        consecutiveHitsRef.current = 0;
4234        isBeginnerModeRef.current = false;
4235        setShowBeginnerBubble(false);
4236        setBeginnerHint(null);
4237        if (startFlashTimeoutRef.current) clearTimeout(startFlashTimeoutRef.current);
4238        startFlashTimeoutRef.current = setTimeout(() => {
4239          setShowStartFlash(false);
4240        }, 700);
4241      }
4242    }, beatDurationMs);
4243  }, [chart, onSeek, isPlaying, onPlayPause, playerCount, tempo, locale.start]);
4244  const handleStartGame = reactExports.useCallback((customStartTime) => {
4245    if (!chart || chart.notes.length === 0) return;
4246    if (!isAudioReady) {
4247      setWaitingForAudio(true);
4248      return;
4249    }
4250    if (selectedBackground === "random") {
4251      rollRandomBackground();
4252    }
4253    const startAt = typeof customStartTime === "number" ? customStartTime : void 0;
4254    startCountdownSequence(startAt);
4255  }, [chart, isAudioReady, startCountdownSequence, selectedBackground, rollRandomBackground]);
4256  reactExports.useEffect(() => {
4257    if (waitingForAudio && isAudioReady) {
4258      setWaitingForAudio(false);
4259      startCountdownSequence();
4260    }
4261  }, [waitingForAudio, isAudioReady, startCountdownSequence]);
4262  reactExports.useEffect(() => {
4263    return () => {
4264      if (countdownIntervalRef.current) {
4265        clearInterval(countdownIntervalRef.current);
4266        countdownIntervalRef.current = null;
4267      }
4268      if (startFlashTimeoutRef.current) {
4269        clearTimeout(startFlashTimeoutRef.current);
4270        startFlashTimeoutRef.current = null;
4271      }
4272    };
4273  }, []);
4274  reactExports.useEffect(() => {
4275    const handleGlobalKey = (e) => {
4276      const target = e.target;
4277      if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)) return;
4278      if (e.code === "Space" || e.code === "Enter" && gameState === "lobby") {
4279        e.preventDefault();
4280        e.stopPropagation();
4281        if (e.code === "Space" && gameState === "playing") {
4282          if (onPlayPause) onPlayPause(!isPlaying);
4283        } else if (gameState === "lobby") {
4284          handleStartGame();
4285        }
4286      }
4287    };
4288    window.addEventListener("keydown", handleGlobalKey);
4289    return () => window.removeEventListener("keydown", handleGlobalKey);
4290  }, [gameState, isPlaying, onPlayPause, handleStartGame]);
4291  reactExports.useEffect(() => {
4292    if (gameState !== "playing") return;
4293    const notes = chartNotesRef.current;
4294    const lastNote = notes && notes.length > 0 ? notes[notes.length - 1] : null;
4295    const lastNoteEndTime = lastNote ? lastNote.t + (lastNote.duration || 0) : 0;
4296    const notesFinished = lastNoteEndTime > 0 ? currentTime >= lastNoteEndTime + 1.2 : true;
4297    const audioFinished = duration > 5 && (currentTime >= duration - 0.2 || !isPlaying && currentTime >= lastNoteEndTime);
4298    if (notesFinished && audioFinished) {
4299      setGameState("results");
4300    }
4301  }, [gameState, currentTime, duration, isPlaying]);
4302  reactExports.useEffect(() => {
4303    window.__triggerTaikoResults = () => {
4304      setGameState("results");
4305    };
4306    return () => {
4307      delete window.__triggerTaikoResults;
4308    };
4309  }, []);
4310  reactExports.useEffect(() => {
4311    const resizeCanvas = () => {
4312      const root = rootContainerRef.current;
4313      if (root) {
4314        const rootRect = root.getBoundingClientRect();
4315        if (rootRect.width > 0 && rootRect.height > 0) {
4316          setContainerSize({ width: rootRect.width, height: rootRect.height });
4317        }
4318      }
4319      const wrapper = canvasWrapperRef.current;
4320      const canvas = canvasRef.current;
4321      if (!wrapper || !canvas) return;
4322      const rect = wrapper.getBoundingClientRect();
4323      const dpr = window.devicePixelRatio || 1;
4324      canvas.width = Math.round(rect.width * dpr);
4325      canvas.height = Math.round(rect.height * dpr);
4326      canvas.style.width = `${rect.width}px`;
4327      canvas.style.height = `${rect.height}px`;
4328      setCanvasSize({ width: rect.width, height: rect.height });
4329    };
4330    resizeCanvas();
4331    window.addEventListener("resize", resizeCanvas);
4332    let ro = null;
4333    if (typeof ResizeObserver !== "undefined" && rootContainerRef.current) {
4334      ro = new ResizeObserver(() => resizeCanvas());
4335      ro.observe(rootContainerRef.current);
4336    }
4337    return () => {
4338      window.removeEventListener("resize", resizeCanvas);
4339      if (ro) ro.disconnect();
4340    };
4341  }, []);
4342  reactExports.useEffect(() => {
4343    const canvas = canvasRef.current;
4344    if (!canvas) return;
4345    const ctx = canvas.getContext("2d");
4346    if (!ctx) return;
4347    let animationFrameId;
4348    const render = () => {
4349      const dpr = window.devicePixelRatio || 1;
4350      const width = canvas.width / dpr;
4351      const height = canvas.height / dpr;
4352      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
4353      const now = performance.now();
4354      let visualTime = lastTimeRef.current;
4355      if (gameStateRef.current === "countdown") {
4356        const elapsedSec = (now - countdownStartTimeRef.current) / 1e3;
4357        visualTime = Math.min(0, -countdownTotalDurationSecRef.current + elapsedSec * playbackRate);
4358      } else if (isPlayingRef.current) {
4359        visualTime = lastTimeRef.current + (now - lastTickRef.current) / 1e3 * playbackRate;
4360      }
4361      const offsetSec = taikoAudio.getOffsetMs() / 1e3;
4362      visualTime -= offsetSec;
4363      ctx.clearRect(0, 0, width, height);
4364      const { laneH, startY, hitZoneX, hitRadius, drumRadius, drumCenterX } = getTrackLayout(height, width, playerCount);
4365      const scrollSpeed = width * 0.28 * scrollSpeedMultiplier;
4366      const notes = chartNotesRef.current;
4367      const bpm = (chart == null ? void 0 : chart.meta.bpm) || 120;
4368      const beatSec = 60 / bpm;
4369      const measureSec = beatSec * 4;
4370      for (let p = 1; p <= playerCount; p++) {
4371        const laneY = startY + (p - 1) * laneH;
4372        const ribbonTop = laneY + 22;
4373        const ribbonHeight = laneH - 24;
4374        const centerY = ribbonTop + ribbonHeight * 0.5;
4375        const pColor = PLAYER_COLORS[p - 1];
4376        const playerHits = hitNotesPerPlayerRef.current.get(p);
4377        const playerMisses = missedNotesPerPlayerRef.current.get(p);
4378        const pState = livePlayerStatesRef.current[p - 1];
4379        const trackGrad = ctx.createLinearGradient(0, ribbonTop, 0, ribbonTop + ribbonHeight);
4380        trackGrad.addColorStop(0, "#1c1e2b");
4381        trackGrad.addColorStop(0.5, "#12131c");
4382        trackGrad.addColorStop(1, "#0b0c13");
4383        ctx.fillStyle = trackGrad;
4384        ctx.fillRect(0, ribbonTop, width, ribbonHeight);
4385        ctx.fillStyle = "#f59e0b";
4386        ctx.fillRect(0, ribbonTop, width, 2.5);
4387        ctx.fillStyle = "#d97706";
4388        ctx.fillRect(0, ribbonTop + ribbonHeight - 2, width, 2);
4389        const gaugeY = laneY + 2;
4390        const gaugeH = 16;
4391        const gaugeX = hitZoneX - 10;
4392        const gaugeW = width - gaugeX - 16;
4393        drawSoulGauge(ctx, gaugeX, gaugeY, gaugeW, gaugeH, (pState == null ? void 0 : pState.soulGauge) || 0);
4394        if (measureSec > 0.5) {
4395          const startMeasure = Math.floor((visualTime - 1) / measureSec);
4396          const endMeasure = Math.ceil((visualTime + width / scrollSpeed + 1) / measureSec);
4397          ctx.lineWidth = 1.5;
4398          for (let m = startMeasure; m <= endMeasure; m++) {
4399            const mTime = m * measureSec;
4400            const mX = hitZoneX + (mTime - visualTime) * scrollSpeed;
4401            if (mX >= hitZoneX && mX <= width) {
4402              ctx.strokeStyle = "rgba(255, 255, 255, 0.35)";
4403              ctx.beginPath();
4404              ctx.moveTo(mX, ribbonTop);
4405              ctx.lineTo(mX, ribbonTop + ribbonHeight);
4406              ctx.stroke();
4407            }
4408          }
4409        }
4410        ctx.save();
4411        ctx.beginPath();
4412        ctx.rect(hitZoneX - hitRadius - 4, ribbonTop, width - (hitZoneX - hitRadius - 4), ribbonHeight);
4413        ctx.clip();
4414        for (let i = 0; i < notes.length; i++) {
4415          const note = notes[i];
4416          const timeDiff = note.t - visualTime;
4417          const noteX = hitZoneX + timeDiff * scrollSpeed;
4418          if (timeDiff < -0.18 && !(playerHits == null ? void 0 : playerHits.has(note.id)) && !(playerMisses == null ? void 0 : playerMisses.has(note.id))) {
4419            if (note.type !== "drumroll" && note.type !== "balloon") {
4420              playerMisses == null ? void 0 : playerMisses.add(note.id);
4421              if (pState) {
4422                pState.combo = 0;
4423                pState.missCount += 1;
4424                pState.soulGauge = Math.max(0, pState.soulGauge - 2.5);
4425                if (pState.sectionHits) {
4426                  const songDur = Math.max(10, durationRef.current || 60);
4427                  const secIdx = Math.min(4, Math.max(0, Math.floor(note.t / (songDur / 5))));
4428                  if (pState.sectionHits[secIdx]) {
4429                    pState.sectionHits[secIdx].miss += 1;
4430                  }
4431                }
4432              }
4433              spawnJudgement(locale.miss, "#f43f5e", hitZoneX, centerY);
4434              if (p === 1) {
4435                consecutiveMissesRef.current += 1;
4436                consecutiveHitsRef.current = 0;
4437                const curNow = performance.now();
4438                if (curNow - playStartTimeRef.current < 15e3 && consecutiveMissesRef.current >= 2) {
4439                  isBeginnerModeRef.current = true;
4440                  setShowBeginnerBubble(true);
4441                } else if (consecutiveMissesRef.current >= 4) {
4442                  isBeginnerModeRef.current = true;
4443                  setShowBeginnerBubble(true);
4444                }
4445              }
4446            }
4447          }
4448          const isRoll = note.type === "drumroll" || note.type === "balloon";
4449          const rollWidth = isRoll ? Math.max(60, note.duration * scrollSpeed) : 0;
4450          const isBig = note.type.startsWith("big_");
4451          const r = isBig ? hitRadius * 1.25 : hitRadius * 0.9;
4452          const noteTailX = isRoll ? noteX + rollWidth : noteX + r;
4453          if (noteTailX < hitZoneX - hitRadius - 10 || noteX > width + 120) continue;
4454          if (playerHits == null ? void 0 : playerHits.has(note.id)) continue;
4455          if (isRoll) {
4456            ctx.fillStyle = note.type === "balloon" ? "#f59e0b" : "#fbbf24";
4457            ctx.strokeStyle = "#78350f";
4458            ctx.lineWidth = 3;
4459            ctx.beginPath();
4460            ctx.roundRect(noteX, centerY - r * 0.65, rollWidth, r * 1.3, r * 0.65);
4461            ctx.fill();
4462            ctx.stroke();
4463            const visibleStart = Math.max(noteX, hitZoneX - hitRadius - 4);
4464            const visibleEnd = Math.min(noteX + rollWidth, width);
4465            if (visibleEnd - visibleStart > 40) {
4466              const textX = (visibleStart + visibleEnd) * 0.5;
4467              ctx.fillStyle = "#78350f";
4468              ctx.font = "bold 12px sans-serif";
4469              ctx.textAlign = "center";
4470              ctx.textBaseline = "middle";
4471              const hitsText = note.hitsCount ? ` ×${note.hitsCount}` : "";
4472              const label = note.type === "balloon" ? `${locale.balloon}${note.hitsRequired ? ` (${Math.max(0, note.hitsRequired - (note.hitsCount || 0))})` : ""}` : `${locale.drumroll}${hitsText}`;
4473              ctx.fillText(label, textX, centerY);
4474            }
4475          } else if (note.type === "don" || note.type === "big_don" || note.type === "ka" || note.type === "big_ka") {
4476            drawHamieNoteFace(ctx, noteX, centerY, r, note.type);
4477            if (showPcKeysRef.current) {
4478              const isDon = note.type.includes("don");
4479              const keyY = centerY + r + (isBig ? 10 : 8);
4480              const kw = isBig ? 15 : 13;
4481              const kh = isBig ? 15 : 13;
4482              const spacing = isBig ? 9 : 8;
4483              if (p === 1) {
4484                if (isDon) {
4485                  drawKeycap(ctx, noteX - spacing, keyY, kw, kh, "F", false, "don");
4486                  drawKeycap(ctx, noteX + spacing, keyY, kw, kh, "J", false, "don");
4487                } else {
4488                  drawKeycap(ctx, noteX - spacing, keyY, kw, kh, "D", false, "ka");
4489                  drawKeycap(ctx, noteX + spacing, keyY, kw, kh, "K", false, "ka");
4490                }
4491              } else {
4492                if (isDon) {
4493                  drawKeycap(ctx, noteX - spacing, keyY, kw, kh, "↓", false, "don");
4494                  drawKeycap(ctx, noteX + spacing, keyY, kw, kh, "→", false, "don");
4495                } else {
4496                  drawKeycap(ctx, noteX - spacing, keyY, kw, kh, "←", false, "ka");
4497                  drawKeycap(ctx, noteX + spacing, keyY, kw, kh, "↑", false, "ka");
4498                }
4499              }
4500            } else {
4501              const noteLabel = note.type === "big_don" ? `BIG ${locale.don}` : note.type === "big_ka" ? `BIG ${locale.ka}` : note.type === "don" ? locale.don : locale.ka;
4502              drawNoteLabelBelow(ctx, noteX, centerY, r, noteLabel, note.type.includes("don"), isBig);
4503            }
4504          }
4505        }
4506        ctx.restore();
4507        if (isBeginnerModeRef.current && p === 1) {
4508          const upcoming = notes.find((n) => !(playerHits == null ? void 0 : playerHits.has(n.id)) && !(playerMisses == null ? void 0 : playerMisses.has(n.id)) && n.t - visualTime >= -0.08 && n.t - visualTime <= 1.4);
4509          if (upcoming) {
4510            const isUpcomingDon = upcoming.type === "don" || upcoming.type === "big_don";
4511            const isUpcomingKa = upcoming.type === "ka" || upcoming.type === "big_ka";
4512            const isUpcomingBig = upcoming.type.startsWith("big_");
4513            const isUpcomingRoll = upcoming.type === "drumroll" || upcoming.type === "balloon";
4514            let text = localeRef.current.pressFOrJNow;
4515            let badge = localeRef.current.donCenter;
4516            let sub = localeRef.current.tapDrumheadCenter;
4517            let color = "#ef4444";
4518            if (isUpcomingBig && isUpcomingDon) {
4519              text = localeRef.current.pressFAndJNow;
4520              badge = localeRef.current.bigDonBothHands;
4521              sub = localeRef.current.strikeCenterBothHands;
4522              color = "#f59e0b";
4523            } else if (isUpcomingBig && isUpcomingKa) {
4524              text = localeRef.current.pressDAndKNow;
4525              badge = localeRef.current.bigKaBothRims;
4526              sub = localeRef.current.strikeRimsBothHands;
4527              color = "#0ea5e9";
4528            } else if (isUpcomingKa) {
4529              text = localeRef.current.pressDOrKNow;
4530              badge = localeRef.current.kaRim;
4531              sub = localeRef.current.tapDrumRimEdge;
4532              color = "#3b82f6";
4533            } else if (isUpcomingRoll) {
4534              text = localeRef.current.mashFAndJFast;
4535              badge = localeRef.current.drumrollTitle;
4536              sub = localeRef.current.alternateRapidStrikes;
4537              color = "#eab308";
4538            }
4539            if (now - lastBubbleUpdateRef.current > 70) {
4540              lastBubbleUpdateRef.current = now;
4541              setBeginnerHint({ text, badge, sub, color, noteId: upcoming.id });
4542            }
4543          }
4544        }
4545        ctx.lineWidth = 3;
4546        ctx.strokeStyle = "rgba(255, 255, 255, 0.75)";
4547        ctx.beginPath();
4548        ctx.arc(hitZoneX, centerY, hitRadius, 0, Math.PI * 2);
4549        ctx.stroke();
4550        ctx.lineWidth = 2;
4551        ctx.strokeStyle = "rgba(255, 255, 255, 0.4)";
4552        ctx.beginPath();
4553        ctx.arc(hitZoneX, centerY, hitRadius * 0.65, 0, Math.PI * 2);
4554        ctx.stroke();
4555        ctx.fillStyle = "#141622";
4556        ctx.fillRect(0, ribbonTop, hitZoneX - hitRadius - 4, ribbonHeight);
4557        ctx.fillStyle = "#f59e0b";
4558        ctx.fillRect(hitZoneX - hitRadius - 5, ribbonTop, 2, ribbonHeight);
4559        ctx.fillStyle = "#78350f";
4560        ctx.beginPath();
4561        ctx.arc(drumCenterX, centerY, drumRadius + 4, 0, Math.PI * 2);
4562        ctx.fill();
4563        ctx.fillStyle = (pState == null ? void 0 : pState.hitRim) ? "#60a5fa" : "#2563eb";
4564        ctx.beginPath();
4565        ctx.arc(drumCenterX, centerY, drumRadius, 0, Math.PI * 2);
4566        ctx.fill();
4567        ctx.fillStyle = (pState == null ? void 0 : pState.hitDrumhead) ? "#fca5a5" : "#fef3c7";
4568        ctx.beginPath();
4569        ctx.arc(drumCenterX, centerY, drumRadius * 0.72, 0, Math.PI * 2);
4570        ctx.fill();
4571        ctx.fillStyle = (pState == null ? void 0 : pState.hitDrumhead) ? "#dc2626" : "#b45309";
4572        ctx.font = `900 ${Math.round(drumRadius * 0.38)}px sans-serif`;
4573        ctx.textAlign = "center";
4574        ctx.textBaseline = "middle";
4575        ctx.fillText("太", drumCenterX, centerY - Math.round(drumRadius * 0.18));
4576        const im = inputManagerRef.current;
4577        const isLeftKaActive = im.isKeyActive(p, "leftKa") || (pState == null ? void 0 : pState.hitRim) && (pState == null ? void 0 : pState.lastSubKey) === "leftKa";
4578        const isRightKaActive = im.isKeyActive(p, "rightKa") || (pState == null ? void 0 : pState.hitRim) && (pState == null ? void 0 : pState.lastSubKey) === "rightKa";
4579        const isLeftDonActive = im.isKeyActive(p, "leftDon") || (pState == null ? void 0 : pState.hitDrumhead) && (pState == null ? void 0 : pState.lastSubKey) === "leftDon";
4580        const isRightDonActive = im.isKeyActive(p, "rightDon") || (pState == null ? void 0 : pState.hitDrumhead) && (pState == null ? void 0 : pState.lastSubKey) === "rightDon";
4581        const donKw = Math.max(16, Math.round(drumRadius * 0.36));
4582        const donKh = Math.max(16, Math.round(drumRadius * 0.36));
4583        const donY = centerY + Math.round(drumRadius * 0.28);
4584        const donSpacing = Math.round(drumRadius * 0.24);
4585        const kaKw = Math.max(15, Math.round(drumRadius * 0.32));
4586        const kaKh = Math.max(15, Math.round(drumRadius * 0.32));
4587        const kaLeftX = drumCenterX - Math.round(drumRadius * 0.84);
4588        const kaRightX = drumCenterX + Math.round(drumRadius * 0.84);
4589        if (p === 1) {
4590          drawKeycap(ctx, kaLeftX, centerY, kaKw, kaKh, "D", !!isLeftKaActive, "ka");
4591          drawKeycap(ctx, kaRightX, centerY, kaKw, kaKh, "K", !!isRightKaActive, "ka");
4592          drawKeycap(ctx, drumCenterX - donSpacing, donY, donKw, donKh, "F", !!isLeftDonActive, "don");
4593          drawKeycap(ctx, drumCenterX + donSpacing, donY, donKw, donKh, "J", !!isRightDonActive, "don");
4594        } else {
4595          drawKeycap(ctx, kaLeftX, centerY, kaKw, kaKh, "←", !!isLeftKaActive, "ka");
4596          drawKeycap(ctx, kaRightX, centerY, kaKw, kaKh, "↑", !!isRightKaActive, "ka");
4597          drawKeycap(ctx, drumCenterX - donSpacing, donY, donKw, donKh, "↓", !!isLeftDonActive, "don");
4598          drawKeycap(ctx, drumCenterX + donSpacing, donY, donKw, donKh, "→", !!isRightDonActive, "don");
4599        }
4600        ctx.fillStyle = "#f87171";
4601        ctx.font = "bold 9px sans-serif";
4602        ctx.textAlign = "center";
4603        ctx.fillText("DON", drumCenterX, ribbonTop + 8);
4604        ctx.fillStyle = "#60a5fa";
4605        ctx.font = "bold 9px sans-serif";
4606        ctx.fillText("KA", kaLeftX, ribbonTop + 8);
4607        ctx.fillText("KA", kaRightX, ribbonTop + 8);
4608        const statsX = Math.max(14, drumCenterX - drumRadius - 42);
4609        ctx.fillStyle = pColor;
4610        ctx.font = "bold 12px sans-serif";
4611        ctx.textAlign = "left";
4612        ctx.textBaseline = "top";
4613        ctx.fillText(`P${p}`, statsX, ribbonTop + 6);
4614        ctx.fillStyle = "#ffffff";
4615        ctx.font = "bold 14px monospace";
4616        ctx.fillText(`${((pState == null ? void 0 : pState.score) || 0).toLocaleString()}`, statsX, ribbonTop + 22);
4617        if (pState && pState.combo >= 5) {
4618          ctx.fillStyle = pState.combo >= 30 ? "#fbbf24" : "#fde047";
4619          ctx.font = "900 12px sans-serif";
4620          ctx.fillText(`${pState.combo} ${locale.combo}!`, statsX, ribbonTop + ribbonHeight - 18);
4621        }
4622      }
4623      for (let i = shockwavesRef.current.length - 1; i >= 0; i--) {
4624        const sw = shockwavesRef.current[i];
4625        sw.radius += 3.5;
4626        sw.alpha -= 0.06;
4627        if (sw.alpha <= 0 || sw.radius >= sw.maxRadius) {
4628          shockwavesRef.current.splice(i, 1);
4629          continue;
4630        }
4631        ctx.save();
4632        ctx.globalAlpha = Math.max(0, sw.alpha);
4633        ctx.strokeStyle = sw.color;
4634        ctx.lineWidth = sw.lineWidth;
4635        ctx.beginPath();
4636        ctx.arc(sw.x, sw.y, sw.radius, 0, Math.PI * 2);
4637        ctx.stroke();
4638        ctx.restore();
4639      }
4640      for (let i = particlesRef.current.length - 1; i >= 0; i--) {
4641        const pt = particlesRef.current[i];
4642        pt.x += pt.vx;
4643        pt.y += pt.vy;
4644        pt.life -= pt.decay;
4645        if (pt.life <= 0) {
4646          particlesRef.current.splice(i, 1);
4647          continue;
4648        }
4649        ctx.save();
4650        ctx.globalAlpha = Math.max(0, pt.life);
4651        ctx.fillStyle = pt.color;
4652        ctx.beginPath();
4653        ctx.arc(pt.x, pt.y, pt.size * pt.life, 0, Math.PI * 2);
4654        ctx.fill();
4655        ctx.restore();
4656      }
4657      for (let i = judgementsRef.current.length - 1; i >= 0; i--) {
4658        const j = judgementsRef.current[i];
4659        j.y -= 0.9;
4660        j.alpha -= 0.035;
4661        if (j.alpha <= 0) {
4662          judgementsRef.current.splice(i, 1);
4663          continue;
4664        }
4665        ctx.save();
4666        ctx.globalAlpha = Math.max(0, j.alpha);
4667        ctx.font = `900 18px sans-serif`;
4668        ctx.textAlign = "center";
4669        ctx.textBaseline = "middle";
4670        ctx.strokeStyle = "#000000";
4671        ctx.lineWidth = 4;
4672        ctx.strokeText(j.text, j.x, j.y);
4673        ctx.fillStyle = j.color;
4674        ctx.fillText(j.text, j.x, j.y);
4675        if (j.points) {
4676          ctx.font = "bold 11px monospace";
4677          ctx.fillStyle = "#ffffff";
4678          ctx.strokeText(j.points, j.x, j.y + 16);
4679          ctx.fillText(j.points, j.x, j.y + 16);
4680        }
4681        ctx.restore();
4682      }
4683      animationFrameId = requestAnimationFrame(render);
4684    };
4685    animationFrameId = requestAnimationFrame(render);
4686    return () => cancelAnimationFrame(animationFrameId);
4687  }, [playerCount, playbackRate, scrollSpeedMultiplier, locale, chart]);
4688  const activeThemeId = selectedBackground === "random" ? resolvedRandomBgId : selectedBackground;
4689  const activeTheme = reactExports.useMemo(() => {
4690    return BACKGROUNDS.find((b) => b.id === activeThemeId) || BACKGROUNDS[0];
4691  }, [activeThemeId, BACKGROUNDS]);
4692  const activeBgUrl = activeTheme.url;
4693  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
4694    "div",
4695    {
4696      ref: rootContainerRef,
4697      className: "relative w-full h-full select-none overflow-hidden flex flex-col bg-zinc-950 font-sans",
4698      children: [
4699        /* @__PURE__ */ jsxRuntimeExports.jsx(
4700          "div",
4701          {
4702            className: "absolute inset-0 bg-cover bg-center transition-all duration-700 pointer-events-none opacity-90",
4703            style: { backgroundImage: `url(${activeBgUrl})` }
4704          }
4705        ),
4706        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 left-0 right-0 h-16 bg-gradient-to-b from-black/50 to-transparent pointer-events-none" }),
4707        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative z-20 flex items-center justify-between px-4 py-2 border-b border-white/15 backdrop-blur-md bg-black/40", children: [
4708          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2.5", children: [
4709            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 h-8 rounded-xl bg-gradient-to-br from-red-600 to-rose-700 border border-amber-400/80 flex items-center justify-center font-black text-white text-sm shadow-md", children: "太" }),
4710            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
4711              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-amber-300 font-black tracking-wide", children: locale.gameTitle }),
4712              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 text-[10px]", children: [
4713                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-bold text-zinc-200", children: [
4714                  DIFFICULTY_CONFIG[difficulty].icon,
4715                  " ",
4716                  DIFFICULTY_CONFIG[difficulty].label
4717                ] }),
4718                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-white/10 px-1.5 py-0.5 rounded text-zinc-400", children: (chart == null ? void 0 : chart.meta.source) === "drum" ? locale.sourceDrums : (chart == null ? void 0 : chart.meta.source) === "melody" ? locale.sourceMelody : locale.sourceBeatGrid })
4719              ] })
4720            ] })
4721          ] }),
4722          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "hidden sm:flex items-center gap-2 text-xs font-bold text-zinc-300 bg-white/5 px-3 py-1 rounded-full border border-white/10", children: [
4723            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-amber-400 font-black", children: [
4724              playerCount,
4725              "P"
4726            ] }),
4727            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500", children: "•" }),
4728            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
4729              scrollSpeedMultiplier,
4730              "x ",
4731              locale.speed
4732            ] })
4733          ] }),
4734          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
4735            /* @__PURE__ */ jsxRuntimeExports.jsx(
4736              "button",
4737              {
4738                onClick: () => setShowTouchControls(!showTouchControls),
4739                className: `p-1.5 rounded-lg border transition-all ${showTouchControls ? "bg-amber-500/20 text-amber-300 border-amber-400/40 shadow-sm" : "bg-zinc-800/80 hover:bg-zinc-700 text-zinc-400 border-white/10"}`,
4740                title: locale.touchDrum,
4741                children: /* @__PURE__ */ jsxRuntimeExports.jsx(Smartphone, { size: 15 })
4742              }
4743            ),
4744            /* @__PURE__ */ jsxRuntimeExports.jsxs(
4745              "button",
4746              {
4747                onClick: () => setShowPcKeys(!showPcKeys),
4748                className: `px-2 py-1 rounded-lg border transition-all flex items-center gap-1.5 cursor-pointer ${showPcKeys ? "bg-amber-500/20 text-amber-300 border-amber-400/50 shadow-[0_0_10px_rgba(245,158,11,0.25)]" : "bg-zinc-800/80 hover:bg-zinc-700 text-zinc-400 border-white/10"}`,
4749                title: showPcKeys ? "Turn off PC beat keycap hints" : "Turn on PC beat keycap hints",
4750                children: [
4751                  /* @__PURE__ */ jsxRuntimeExports.jsx(Keyboard, { size: 14 }),
4752                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-black hidden sm:inline", children: showPcKeys ? "KEYS: ON" : "KEYS: OFF" })
4753                ]
4754              }
4755            ),
4756            /* @__PURE__ */ jsxRuntimeExports.jsx(
4757              "button",
4758              {
4759                onClick: () => setShowKeyHelp(!showKeyHelp),
4760                className: "p-1.5 rounded-lg bg-zinc-800/80 hover:bg-zinc-700 text-zinc-300 border border-white/10 transition-colors",
4761                title: locale.keyboardControls,
4762                children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { size: 15 })
4763              }
4764            ),
4765            (gameState === "playing" || gameState === "countdown") && /* @__PURE__ */ jsxRuntimeExports.jsxs(
4766              "button",
4767              {
4768                onClick: handleStartGame,
4769                className: "flex items-center gap-1 text-xs bg-gradient-to-r from-amber-600 to-red-600 hover:from-amber-500 hover:to-red-500 text-white px-2.5 py-1.5 rounded-lg border border-amber-400/40 font-bold shadow-md transition-all active:scale-95",
4770                title: "Restart Game",
4771                children: [
4772                  /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 13 }),
4773                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Restart" })
4774                ]
4775              }
4776            ),
4777            gameState === "playing" && /* @__PURE__ */ jsxRuntimeExports.jsx(
4778              "button",
4779              {
4780                onClick: () => setGameState("lobby"),
4781                className: "text-xs bg-zinc-800 hover:bg-zinc-700 text-zinc-200 px-3 py-1.5 rounded-lg border border-white/10 font-bold",
4782                children: locale.lobby
4783              }
4784            )
4785          ] })
4786        ] }),
4787        showKeyHelp && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute top-14 right-4 z-40 bg-zinc-900/95 border border-white/20 rounded-2xl p-4 shadow-2xl backdrop-blur-md max-w-sm text-xs text-white", children: [
4788          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-black text-amber-300 text-sm mb-2 flex items-center justify-between", children: [
4789            /* @__P
4789URE__ */ jsxRuntimeExports.jsxs("span", { children: [
4790              "⌨️ ",
4791              locale.keyboardControls
4792            ] }),
4793            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setShowKeyHelp(false), className: "text-zinc-500 hover:text-white", children: "✕" })
4794          ] }),
4795          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
4796            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-black/50 p-2 rounded-lg border border-white/5", children: [
4797              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-red-400", children: locale.p1Name }),
4798              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
4799                locale.don,
4800                " (Center): ",
4801                /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "bg-zinc-800 px-1.5 py-0.5 rounded text-white font-mono", children: "F" }),
4802                " / ",
4803                /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "bg-zinc-800 px-1.5 py-0.5 rounded text-white font-mono", children: "J" })
4804              ] }),
4805              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
4806                locale.ka,
4807                " (Rim): ",
4808                /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "bg-zinc-800 px-1.5 py-0.5 rounded text-white font-mono", children: "D" }),
4809                " / ",
4810                /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "bg-zinc-800 px-1.5 py-0.5 rounded text-white font-mono", children: "K" })
4811              ] })
4812            ] }),
4813            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-black/50 p-2 rounded-lg border border-white/5", children: [
4814              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-blue-400", children: locale.p2Name }),
4815              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
4816                locale.don,
4817                " (Center): ",
4818                /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "bg-zinc-800 px-1.5 py-0.5 rounded text-white font-mono", children: "↓" }),
4819                " / ",
4820                /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "bg-zinc-800 px-1.5 py-0.5 rounded text-white font-mono", children: "→" })
4821              ] }),
4822              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
4823                locale.ka,
4824                " (Rim): ",
4825                /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "bg-zinc-800 px-1.5 py-0.5 rounded text-white font-mono", children: "←" }),
4826                " / ",
4827                /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "bg-zinc-800 px-1.5 py-0.5 rounded text-white font-mono", children: "↑" })
4828              ] })
4829            ] }),
4830            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-black/50 p-2 rounded-lg border border-white/5 flex items-center justify-between text-zinc-300", children: [
4831              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Pause / Resume:" }),
4832              /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "bg-zinc-800 px-2 py-0.5 rounded text-white font-mono", children: "Space" })
4833            ] })
4834          ] }),
4835          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 text-[10px] text-zinc-400 italic", children: locale.keyTip })
4836        ] }),
4837        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: canvasWrapperRef, className: "relative flex-1 w-full h-full overflow-hidden", children: [
4838          /* @__PURE__ */ jsxRuntimeExports.jsx(
4839            "canvas",
4840            {
4841              ref: canvasRef,
4842              className: "absolute inset-0 block pointer-events-auto"
4843            }
4844          ),
4845          gameState !== "results" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
4846            "div",
4847            {
4848              className: "absolute pointer-events-none z-10 flex items-end select-none",
4849              style: {
4850                left: Math.max(16, Math.round(trackLayout.drumCenterX - 60)),
4851                top: Math.max(0, trackLayout.startY - 132)
4852              },
4853              children: [
4854                /* @__PURE__ */ jsxRuntimeExports.jsx(
4855                  TaikoHamsterMascot,
4856                  {
4857                    combo: mascotStats.combo,
4858                    isHit: mascotStats.isHitting,
4859                    lastHitType: mascotStats.lastHitType,
4860                    lastSubKey: mascotStats.lastSubKey,
4861                    strikeTick: mascotStats.strikeTick,
4862                    soulGauge: mascotStats.soulGauge,
4863                    playerColor: PLAYER_COLORS[0],
4864                    playerId: 1,
4865                    currentTime,
4866                    scale: trackLayout.startY < 140 ? 0.88 : 1.05
4867                  }
4868                ),
4869                playerCount >= 2 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4870                  TaikoHamsterMascot,
4871                  {
4872                    combo: p2MascotStats.combo,
4873                    isHit: p2MascotStats.isHitting,
4874                    lastHitType: p2MascotStats.lastHitType,
4875                    lastSubKey: p2MascotStats.lastSubKey,
4876                    strikeTick: p2MascotStats.strikeTick,
4877                    soulGauge: p2MascotStats.soulGauge,
4878                    playerColor: PLAYER_COLORS[1],
4879                    playerId: 2,
4880                    currentTime,
4881                    scale: trackLayout.startY < 140 ? 0.88 : 1.05
4882                  }
4883                ) })
4884              ]
4885            }
4886          ),
4887          showBeginnerBubble && beginnerHint && gameState === "playing" && /* @__PURE__ */ jsxRuntimeExports.jsx(
4888            "div",
4889            {
4890              className: "absolute z-30 pointer-events-auto transition-all animate-bounce select-none",
4891              style: {
4892                left: Math.max(160, Math.round(trackLayout.hitZoneX + 25)),
4893                top: Math.max(8, Math.round(trackLayout.startY - 72))
4894              },
4895              children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative bg-gradient-to-b from-amber-50 to-yellow-100 border-2 border-zinc-950 rounded-2xl px-3 py-1.5 shadow-[0_6px_20px_rgba(0,0,0,0.5)] text-zinc-950 max-w-[210px] text-center", children: [
4896                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute -bottom-2.5 left-7 w-0 h-0 border-x-[7px] border-x-transparent border-t-[10px] border-t-zinc-950", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute -top-[12px] -left-[5px] w-0 h-0 border-x-[5px] border-x-transparent border-t-[8px] border-t-yellow-100" }) }),
4897                /* @__PURE__ */ jsxRuntimeExports.jsx(
4898                  "button",
4899                  {
4900                    onClick: () => {
4901                      isBeginnerModeRef.current = false;
4902                      setShowBeginnerBubble(false);
4903                    },
4904                    className: "absolute top-1 right-1.5 text-zinc-400 hover:text-zinc-800 text-[10px] font-black",
4905                    title: "Dismiss hint",
4906                    children: "✕"
4907                  }
4908                ),
4909                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center gap-1 mb-0.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-black uppercase px-1.5 py-0.2 rounded-full text-white", style: { backgroundColor: beginnerHint.color }, children: beginnerHint.badge }) }),
4910                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-black tracking-tight text-zinc-950 leading-tight", children: beginnerHint.text }),
4911                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[9px] text-zinc-600 font-bold mt-0.5", children: beginnerHint.sub })
4912              ] })
4913            }
4914          ),
4915          showTouchControls && (gameState === "playing" || gameState === "countdown") && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-0 left-0 right-0 z-20", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4916            TaikoTouchControls,
4917            {
4918              playerCount,
4919              onHit: (pId, action, subKey) => {
4920                inputManagerRef.current.triggerHit(pId, action, subKey);
4921              },
4922              locale
4923            }
4924          ) }),
4925          gameState !== "results" && !showTouchControls && /* @__PURE__ */ jsxRuntimeExports.jsxs(
4926            "div",
4927            {
4928              style: {
4929                transform: containerSize.width < 620 ? `scale(${Math.min(1, Math.max(0.68, (containerSize.width - 16) / 620))})` : void 0,
4930                transformOrigin: "bottom center"
4931              },
4932              className: "absolute bottom-1.5 left-1/2 -translate-x-1/2 z-20 pointer-events-none flex items-center flex-nowrap whitespace-nowrap gap-1.5 sm:gap-2 px-2.5 sm:px-3 py-0.5 rounded-full bg-black/85 backdrop-blur-md border border-white/20 text-[10px] sm:text-[11px] font-bold text-zinc-300 shadow-xl select-none max-w-[96vw] overflow-hidden",
4933              children: [
4934                playerCount === 2 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
4935                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
4936                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.2 rounded bg-red-950 text-red-300 font-mono text-[9px] font-black border border-red-500/40 border-b-2 border-b-red-800 shadow-sm", children: "1P" }),
4937                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-red-400 font-black", children: [
4938                      locale.don,
4939                      ":"
4940                    ] }),
4941                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-amber-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[17px] text-center", children: "F" }),
4942                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-amber-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[17px] text-center", children: "J" }),
4943                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400 font-black ml-0.5", children: [
4944                      locale.ka,
4945                      ":"
4946                    ] }),
4947                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-sky-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[17px] text-center", children: "D" }),
4948                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-sky-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[17px] text-center", children: "K" })
4949                  ] }),
4950                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "|" }),
4951                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
4952                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.2 rounded bg-blue-950 text-blue-300 font-mono text-[9px] font-black border border-blue-500/40 border-b-2 border-b-blue-800 shadow-sm", children: "2P" }),
4953                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-red-400 font-black", children: [
4954                      locale.don,
4955                      ":"
4956                    ] }),
4957                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-amber-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[17px] text-center", children: "↓" }),
4958                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-amber-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[17px] text-center", children: "→" }),
4959                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400 font-black ml-0.5", children: [
4960                      locale.ka,
4961                      ":"
4962                    ] }),
4963                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-sky-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[17px] text-center", children: "←" }),
4964                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-sky-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[17px] text-center", children: "↑" })
4965                  ] })
4966                ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
4967                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
4968                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-2.5 h-2.5 rounded-full bg-red-500 shadow-[0_0_6px_#ef4444]" }),
4969                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-red-400 font-black", children: [
4970                      locale.don,
4971                      ":"
4972                    ] }),
4973                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-amber-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[18px] text-center", children: "F" }),
4974                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-amber-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[18px] text-center", children: "J" })
4975                  ] }),
4976                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "|" }),
4977                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
4978                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-2.5 h-2.5 rounded-full bg-blue-500 shadow-[0_0_6px_#3b82f6]" }),
4979                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-400 font-black", children: [
4980                      locale.ka,
4981                      ":"
4982                    ] }),
4983                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-sky-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[18px] text-center", children: "D" }),
4984                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-sky-200 font-mono text-[10px] font-black border border-white/20 border-b-2 border-b-zinc-600 shadow-sm min-w-[18px] text-center", children: "K" })
4985                  ] })
4986                ] }),
4987                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "|" }),
4988                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-zinc-400", children: [
4989                  /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-800 text-zinc-300 font-mono text-[10px] font-bold border border-white/10 border-b-2 border-b-zinc-600 shadow-sm", children: "Space" }),
4990                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: isPlaying ? locale.pause : locale.play })
4991                ] }),
4992                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "|" }),
4993                /* @__PURE__ */ jsxRuntimeExports.jsxs(
4994                  "button",
4995                  {
4996                    type: "button",
4997                    onClick: (e) => {
4998                      e.stopPropagation();
4999                      setShowPcKeys((prev) => !prev);
5000                    },
5001                    className: `pointer-events-auto flex items-center gap-1 px-2 py-0.5 rounded-full border text-[10px] font-black transition-all cursor-pointer ${showPcKeys ? "bg-amber-500/30 border-amber-400/70 text-amber-300 shadow-[0_0_8px_rgba(245,158,11,0.3)]" : "bg-zinc-800 border-white/20 text-zinc-400 hover:text-white"}`,
5002                    title: showPcKeys ? "Hide Visual Keycaps on Beats" : "Show Visual Keycaps on Beats",
5003                    children: [
5004                      /* @__PURE__ */ jsxRuntimeExports.jsx(Keyboard, { size: 11 }),
5005                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: showPcKeys ? "KEYS: ON" : "KEYS: OFF" })
5006                    ]
5007                  }
5008                )
5009              ]
5010            }
5011          ),
5012          (gameState === "countdown" || showStartFlash) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-[14%] sm:top-[16%] left-1/2 -translate-x-1/2 z-30 flex flex-col items-center pointer-events-none select-none", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-6 py-2 rounded-2xl bg-zinc-950/85 border-2 border-amber-400/90 shadow-[0_0_35px_rgba(245,158,11,0.5)] text-center", children: [
5013            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] sm:text-[11px] font-black tracking-widest text-amber-300 uppercase mb-0.5", children: locale.ready || "READY..." }),
5014            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-4xl sm:text-5xl font-black text-transparent bg-clip-text bg-gradient-to-r from-amber-300 via-rose-400 to-red-500 drop-shadow-[0_3px_10px_rgba(0,0,0,0.8)] animate-pulse", children: countdownNumber })
5015          ] }) }),
5016          gameState === "lobby" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center p-1 sm:p-2 bg-black/60 backdrop-blur-sm overflow-hidden pointer-events-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
5017            "div",
5018            {
5019              style: {
5020                transform: `scale(${lobbyScale})`,
5021                transformOrigin: "center center"
5022              },
5023              className: `w-full ${lobbyView === "leaderboard" ? "max-w-md sm:max-w-lg md:max-w-xl" : "max-w-md sm:max-w-lg"} bg-zinc-900/95 border border-white/15 rounded-2xl p-4 sm:p-5 shadow-2xl space-y-3.5 shrink-0`,
5024              children: [
5025                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between border-b border-white/10 pb-2.5", children: [
5026                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-left", children: [
5027                    /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-base sm:text-xl font-black tracking-wide text-transparent bg-clip-text bg-gradient-to-r from-amber-400 via-rose-400 to-red-500", children: locale.gameTitle }),
5028                    /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] sm:text-xs text-zinc-400 font-bold uppercase tracking-wider", children: locale.gameSubtitle })
5029                  ] }),
5030                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: lobbyView === "leaderboard" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
5031                    "button",
5032                    {
5033                      type: "button",
5034                      onClick: () => setLobbyView("main"),
5035                      className: "px-3 py-1.5 rounded-xl font-bold transition-all flex items-center gap-1.5 bg-zinc-800 hover:bg-zinc-700 border border-white/10 text-xs text-zinc-200 shadow-sm cursor-pointer",
5036                      children: [
5037                        /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { size: 14 }),
5038                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("common.back", "Back") })
5039                      ]
5040                    }
5041                  ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
5042                    "button",
5043                    {
5044                      type: "button",
5045                      onClick: () => setLobbyView("leaderboard"),
5046                      className: "px-2.5 sm:px-3 py-1.5 rounded-xl font-bold transition-all flex items-center gap-1.5 bg-zinc-800/80 hover:bg-zinc-700 border border-white/10 text-xs text-amber-400 hover:text-amber-300 shadow-sm cursor-pointer",
5047                      title: locale.leaderboard,
5048                      children: [
5049                        /* @__PURE__ */ jsxRuntimeExports.jsx(Trophy, { size: 14 }),
5050                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: locale.leaderboard }),
5051                        leaderboard.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-amber-400 text-black text-[9px] font-black px-1.5 py-0.5 rounded-full", children: leaderboard.length })
5052                      ]
5053                    }
5054                  ) })
5055                ] }),
5056                lobbyView === "main" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
5057                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
5058                    /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] text-zinc-400 font-bold block mb-1", children: locale.players }),
5059                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
5060                      /* @__PURE__ */ jsxRuntimeExports.jsx(
5061                        "button",
5062                        {
5063                          type: "button",
5064                          onClick: () => setPlayerCount(1),
5065                          className: `py-2 rounded-xl text-xs font-black border transition-all cursor-pointer ${playerCount === 1 ? "bg-red-600 border-red-400 text-white shadow-md scale-[1.01]" : "bg-zinc-800/80 border-white/10 text-zinc-400 hover:bg-zinc-700"}`,
5066                          children: locale.p1Solo || "1P Solo"
5067                        }
5068                      ),
5069                      /* @__PURE__ */ jsxRuntimeExports.jsx(
5070                        "button",
5071                        {
5072                          type: "button",
5073                          onClick: () => setPlayerCount(2),
5074                          className: `py-2 rounded-xl text-xs font-black border transition-all cursor-pointer ${playerCount === 2 ? "bg-blue-600 border-blue-400 text-white shadow-md scale-[1.01]" : "bg-zinc-800/80 border-white/10 text-zinc-400 hover:bg-zinc-700"}`,
5075                          children: locale.p2Duo || "2P Duo"
5076                        }
5077                      )
5078                    ] })
5079                  ] }),
5080                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
5081                    /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] text-zinc-400 font-bold block mb-1", children: locale.difficulty }),
5082                    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-4 gap-1.5", children: ["kantan", "futsuu", "muzukashii", "oni"].map((d) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
5083                      "button",
5084                      {
5085                        type: "button",
5086                        onClick: () => setDifficulty(d),
5087                        className: `py-2 px-1 rounded-xl text-xs font-black border transition-all flex flex-col sm:flex-row items-center justify-center gap-0.5 sm:gap-1 cursor-pointer ${difficulty === d ? "bg-gradient-to-b from-amber-500 to-red-600 border-amber-300 text-white shadow-md scale-[1.02]" : "bg-zinc-800/80 border-white/10 text-zinc-400 hover:bg-zinc-700 hover:text-white"}`,
5088                        children: [
5089                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: DIFFICULTY_CONFIG[d].icon }),
5090                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "uppercase tracking-tight text-[10px] sm:text-xs", children: d === "kantan" ? locale.kantan : d === "futsuu" ? locale.futsuu : d === "muzukashii" ? locale.muzukashii : locale.oni })
5091                        ]
5092                      },
5093                      d
5094                    )) })
5095                  ] }),
5096                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
5097                    /* @__PURE__ */ jsxRuntimeExports.jsxs(
5098                      "button",
5099                      {
5100                        type: "button",
5101                        onClick: () => setShowMoreSettings(!showMoreSettings),
5102                        className: "w-full py-1.5 px-3 rounded-xl text-xs font-bold text-zinc-400 hover:text-zinc-200 bg-zinc-800/50 hover:bg-zinc-800 border border-white/10 flex items-center justify-between transition-colors cursor-pointer",
5103                        children: [
5104                          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5", children: [
5105                            /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 13, className: showMoreSettings ? "text-amber-400 rotate-45 transition-transform" : "text-zinc-400" }),
5106                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: locale.setup || "More Settings" })
5107                          ] }),
5108                          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5 text-[10px] sm:text-[11px] text-zinc-500 font-medium", children: [
5109                            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
5110                              scrollSpeedMultiplier,
5111                              "x · ",
5112                              selectedBackground === "random" ? `🎲 ${activeTheme.label}` : activeTheme.label
5113                            ] }),
5114                            showMoreSettings ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 })
5115                          ] })
5116                        ]
5117                      }
5118                    ),
5119                    showMoreSettings && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 bg-black/40 border border-white/10 rounded-xl p-2.5 sm:p-3 space-y-2.5 animate-in fade-in slide-in-from-top-1 duration-150", children: [
5120                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
5121                        /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] sm:text-[11px] text-zinc-400 font-bold block mb-1", children: locale.speed }),
5122                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-4 gap-1", children: [0.8, 1, 1.2, 1.5].map((s) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
5123                          "button",
5124                          {
5125                            type: "button",
5126                            onClick: () => setScrollSpeedMultiplier(s),
5127                            className: `py-1 rounded-lg text-xs font-black border transition-all cursor-pointer ${scrollSpeedMultiplier === s ? "bg-amber-600 border-amber-300 text-white shadow-md" : "bg-zinc-800/80 border-white/10 text-zinc-400 hover:bg-zinc-700"}`,
5128                            children: [
5129                              s,
5130                              "x"
5131                            ]
5132                          },
5133                          s
5134                        )) })
5135                      ] }),
5136                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
5137                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-[10px] sm:text-[11px] text-zinc-400 font-bold mb-1", children: [
5138                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: locale.stageTheme }),
5139                          selectedBackground === "random" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
5140                            "button",
5141                            {
5142                              type: "button",
5143                              onClick: rollRandomBackground,
5144                              className: "flex items-center gap-1 text-[10px] text-amber-300 hover:text-amber-200 cursor-pointer bg-white/5 hover:bg-white/10 px-1.5 py-0.5 rounded border border-white/10 transition-colors",
5145                              title: "Shuffle to another random stage",
5146                              children: [
5147                                /* @__PURE__ */ jsxRuntimeExports.jsx(Shuffle, { size: 10 }),
5148                                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Shuffle" })
5149                              ]
5150                            }
5151                          )
5152                        ] }),
5153                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1.5", children: [
5154                          /* @__PURE__ */ jsxRuntimeExports.jsxs(
5155                            "button",
5156                            {
5157                              type: "button",
5158                              onClick: () => {
5159                                setSelectedBackground("random");
5160                                rollRandomBackground();
5161                              },
5162                              className: `w-full py-1 px-2.5 rounded-lg text-xs font-bold border transition-all flex items-center justify-between cursor-pointer ${selectedBackground === "random" ? "bg-amber-500/20 border-amber-400 text-amber-300 shadow-sm" : "bg-zinc-800/60 border-white/10 text-zinc-400 hover:bg-zinc-700 hover:text-white"}`,
5163                              children: [
5164                                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex items-center gap-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: locale.themeRandom || "🎲 Random (Shuffle)" }) }),
5165                                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-zinc-400 truncate max-w-[140px]", children: activeTheme.label })
5166                              ]
5167                            }
5168                          ),
5169                          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-2 gap-1.5 max-h-[125px] overflow-y-auto custom-scrollbar pr-1", children: BACKGROUNDS.map((bg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
5170                            "button",
5171                            {
5172                              type: "button",
5173                              onClick: () => setSelectedBackground(bg.id),
5174                              className: `py-1 px-2 rounded-lg text-xs font-bold border truncate transition-all text-left cursor-pointer ${selectedBackground === bg.id ? "bg-zinc-700/90 border-amber-400 text-amber-300 shadow" : "bg-zinc-800/60 border-white/10 text-zinc-400 hover:bg-zinc-700 hover:text-white"}`,
5175                              children: bg.label
5176                            },
5177                            bg.id
5178                          )) })
5179                        ] })
5180                      ] }),
5181                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
5182                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-[10px] sm:text-[11px] text-zinc-400 font-bold mb-1", children: [
5183                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: locale.keyboardHints || "PC Beat Keycap Hints" }),
5184                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500 font-normal", children: "D, F, J, K" })
5185                        ] }),
5186                        /* @__PURE__ */ jsxRuntimeExports.jsxs(
5187                          "button",
5188                          {
5189                            type: "button",
5190                            onClick: () => setShowPcKeys(!showPcKeys),
5191                            className: `w-full py-1.5 px-2.5 rounded-lg text-xs font-bold border transition-all flex items-center justify-between cursor-pointer ${showPcKeys ? "bg-amber-500/20 border-amber-400 text-amber-300 shadow-sm" : "bg-zinc-800/60 border-white/10 text-zinc-400 hover:bg-zinc-700 hover:text-white"}`,
5192                            children: [
5193                              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5", children: [
5194                                /* @__PURE__ */ jsxRuntimeExports.jsx(Keyboard, { size: 13 }),
5195                                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: showPcKeys ? "SHOW KEYCAPS ON BEATS" : "HIDE KEYCAPS (CLASSIC)" })
5196                              ] }),
5197                              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[9px] font-black px-1.5 py-0.5 rounded ${showPcKeys ? "bg-amber-400 text-black" : "bg-zinc-700 text-zinc-300"}`, children: showPcKeys ? "ON" : "OFF" })
5198                            ]
5199                          }
5200                        )
5201                      ] })
5202                    ] })
5203                  ] }),
5204                  /* @__PURE__ */ jsxRuntimeExports.jsx(
5205                    "button",
5206                    {
5207                      type: "button",
5208                      onClick: handleStartGame,
5209                      disabled: waitingForAudio,
5210                      className: `w-full mt-1 py-2.5 ${waitingForAudio ? "bg-zinc-700/80 border border-zinc-600 text-zinc-300 cursor-wait" : "bg-gradient-to-r from-red-600 via-rose-600 to-amber-600 hover:from-red-500 hover:to-amber-500 text-white transform active:scale-[0.98]"} font-black text-sm rounded-xl shadow-xl transition-all flex items-center justify-center gap-2 cursor-pointer`,
5211                      children: waitingForAudio ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5212                        /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 16, className: "animate-spin text-amber-400" }),
5213                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: locale.loadingAudio || "Loading Audio..." })
5214                      ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5215                        /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 16, fill: "white" }),
5216                        " ",
5217                        locale.start
5218                      ] })
5219                    }
5220                  )
5221                ] }) : (
5222                  /* View 2: Highscores & Leaderboard Podium */
5223                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
5224                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-xs font-black text-amber-400 px-1", children: [
5225                      /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5", children: [
5226                        /* @__PURE__ */ jsxRuntimeExports.jsx(Trophy, { size: 14 }),
5227                        " ",
5228                        locale.topTaikoMasters
5229                      ] }),
5230                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-400 font-normal", children: locale.leaderboard })
5231                    ] }),
5232                    leaderboard.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center text-zinc-500 text-xs bg-black/40 rounded-xl border border-white/10", children: [
5233                      /* @__PURE__ */ jsxRuntimeExports.jsx(Trophy, { size: 28, className: "text-zinc-600 mb-2 opacity-50" }),
5234                      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-bold text-zinc-400", children: locale.noHighscoresYet }),
5235                      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-zinc-500 mt-1", children: locale.beFirstToSetRecord })
5236                    ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 bg-black/40 rounded-xl border border-white/10 p-3", children: [
5237                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-3 gap-1.5 items-end mb-1 pt-2", children: [
5238                        leaderboard[1] ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center bg-zinc-800/60 border border-slate-400/40 rounded-xl p-1.5 text-center", children: [
5239                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base", children: "🥈" }),
5240                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-slate-200 truncate w-full", title: leaderboard[1].userName, children: leaderboard[1].userName || "Player 2" }),
5241                          leaderboard[1].countryCode && /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: leaderboard[1].countryCode, size: 11 }),
5242                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono font-black text-emerald-400 mt-0.5", children: Number(leaderboard[1].score).toLocaleString() })
5243                        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center bg-zinc-800/30 border border-white/5 rounded-xl p-1.5 text-center text-zinc-500", children: [
5244                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base", children: "🥈" }),
5245                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-zinc-500 truncate w-full", children: "-" }),
5246                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-zinc-600 mt-0.5", children: "-" })
5247                        ] }),
5248                        leaderboard[0] ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center bg-gradient-to-t from-amber-500/20 to-zinc-800/80 border border-amber-400/60 rounded-xl p-2 text-center shadow-[0_0_15px_rgba(245,158,11,0.2)] -mt-3", children: [
5249                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-2xl", children: "🥇" }),
5250                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-black text-amber-200 truncate w-full", title: leaderboard[0].userName, children: leaderboard[0].userName || "Taiko Master" }),
5251                          leaderboard[0].countryCode && /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: leaderboard[0].countryCode, size: 12 }),
5252                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono font-black text-amber-300 mt-0.5", children: Number(leaderboard[0].score).toLocaleString() })
5253                        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center bg-zinc-800/30 border border-white/5 rounded-xl p-2 text-center text-zinc-500 -mt-3", children: [
5254                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-2xl", children: "🥇" }),
5255                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-bold text-zinc-500 truncate w-full", children: "-" }),
5256                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono text-zinc-600 mt-0.5", children: "-" })
5257                        ] }),
5258                        leaderboard[2] ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center bg-zinc-800/60 border border-amber-700/40 rounded-xl p-1.5 text-center", children: [
5259                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base", children: "🥉" }),
5260                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-amber-300 truncate w-full", title: leaderboard[2].userName, children: leaderboard[2].userName || "Player 3" }),
5261                          leaderboard[2].countryCode && /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: leaderboard[2].countryCode, size: 11 }),
5262                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono font-black text-emerald-400 mt-0.5", children: Number(leaderboard[2].score).toLocaleString() })
5263                        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center bg-zinc-800/30 border border-white/5 rounded-xl p-1.5 text-center text-zinc-500", children: [
5264                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base", children: "🥉" }),
5265                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-zinc-500 truncate w-full", children: "-" }),
5266                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-zinc-600 mt-0.5", children: "-" })
5267                        ] })
5268                      ] }),
5269                      leaderboard.length > 3 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-y-auto custom-scrollbar space-y-1 max-h-[140px] pr-1", children: leaderboard.slice(3, 8).map((item, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-2 py-1 bg-white/5 rounded-lg text-xs", children: [
5270                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 min-w-0", children: [
5271                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-500 text-[10px] w-3", children: idx + 4 }),
5272                          /* @__P
5272URE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-200 truncate max-w-[120px]", children: item.userName }),
5273                          item.countryCode && /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: item.countryCode, size: 10 })
5274                        ] }),
5275                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-emerald-400 font-bold text-[10px]", children: Number(item.score).toLocaleString() })
5276                      ] }, idx)) })
5277                    ] }),
5278                    /* @__PURE__ */ jsxRuntimeExports.jsxs(
5279                      "button",
5280                      {
5281                        type: "button",
5282                        onClick: () => setLobbyView("main"),
5283                        className: "w-full py-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 font-bold text-xs rounded-xl border border-white/10 transition-colors flex items-center justify-center gap-1.5 cursor-pointer",
5284                        children: [
5285                          /* @__P
5285URE__ */ jsxRuntimeExports.jsx(ArrowLeft, { size: 14 }),
5286                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("common.back", "Back to Game Setup") })
5287                        ]
5288                      }
5289                    )
5290                  ] })
5291                )
5292              ]
5293            }
5294          ) }),
5295          gameState === "results" && /* @__PURE__ */ jsxRuntimeExports.jsx(
5296            TaikoResultsScreen,
5297            {
5298              trackId,
5299              userId,
5300              userName,
5301              results: livePlayerStatesRef.current.map((p) => {
5302                const totalHits = p.perfectCount + p.goodCount + p.missCount;
5303                const acc = totalHits > 0 ? Math.round((p.perfectCount + p.goodCount * 0.5) / totalHits * 100) : 0;
5304                const songDur = Math.max(10, durationRef.current || 60);
5305                const secDur = songDur / 5;
5306                const sections = (p.sectionHits || createDefaultSectionHits()).map((s, idx) => {
5307                  const sTotal = s.perfect + s.good + s.miss;
5308                  const sAcc = sTotal > 0 ? Math.round((s.perfect + s.good * 0.5) / sTotal * 100) : 100;
5309                  const cleared = sAcc >= 70;
5310                  const crown = s.miss === 0 && sTotal > 0 ? "gold" : cleared ? "silver" : "none";
5311                  return {
5312                    sectionIndex: idx,
5313                    startTime: idx * secDur,
5314                    endTime: (idx + 1) * secDur,
5315                    perfectCount: s.perfect,
5316                    goodCount: s.good,
5317                    missCount: s.miss,
5318                    totalNotes: sTotal,
5319                    accuracy: sAcc,
5320                    cleared,
5321                    crown
5322                  };
5323                });
5324                return {
5325                  playerId: p.id,
5326                  score: p.score,
5327                  accuracy: acc,
5328                  maxCombo: p.maxCombo,
5329                  perfectCount: p.perfectCount,
5330                  goodCount: p.goodCount,
5331                  missCount: p.missCount,
5332                  drumrollHits: p.drumrollHits,
5333                  cleared: p.soulGauge >= 80,
5334                  soulGauge: p.soulGauge,
5335                  sections
5336                };
5337              }),
5338              isCooperative,
5339              difficulty,
5340              chartSource: (chart == null ? void 0 : chart.meta.source) || "auto",
5341              onPlayAgain: () => {
5342                setGameState("countdown");
5343                handleStartGame();
5344              },
5345              onBackToLobby: () => setGameState("lobby"),
5346              onPracticeSection: (startTime) => {
5347                setGameState("countdown");
5348                handleStartGame(Math.max(0, startTime - 1));
5349              }
5350            }
5351          )
5352        ] })
5353      ]
5354    }
5355  );
5356};
5357export {
5358  TaikoGame as T
5359};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.