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