PageSourceSearch

https://www.hamienet.com/assets/Taiko-BWRRYy3Y.js

js hamienet.com collected 2026-09-24 19:00:05 UTC 229,994 bytes, 4,894 lines download raw bytes

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

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.