1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js"; 2import { B as BLANK_SOLO_STAFF_HUMDRUM } from "./index--Kd7xwwe.js"; 3import { cs as BookOpen, bI as Star, cm as ArrowRight, cy as Trophy, cY as ChevronLeft, bE as ChevronRight, cj as Layers, bF as CircleQuestionMark, bU as Play, a_ as CircleCheck, bZ as Pause, bT as Volume2, bW as VolumeX, bi as Sparkles, du as GripHorizontal } from "./vendor-dVNaLJx_.js"; 4import "./vendor-react-three-MDLA5jog.js"; 5import "./vendor-i18n-R-bMoYp_.js"; 6import "./vendor-audio-DH3w0BzW.js"; 7import "./index-D0Bmh-UX.js"; 8const SNAPSHOT_CHAPTER_1 = `!!!verovio: breaks encoded 9!!!COM: Robert Schumann 10!!!OTL: Widmung 11!!!ODT: 1840 12!!!OMD: Innig, lebhaft. 13**kern **dynam **kern **kern **text 14*clefF4 * *clefG2 *clefG2 * 15*k[b-e-a-d-] * *k[b-e-a-d-] *k[b-e-a-d-] * 16*M3/2 *M3/2 *M3/2 *M3/2 * 17=1 =1 =1 =1 =1 181.r . 1.r 1.r . 19=2 =2 =2 =2 =2 201.r . 1.r 1.r . 21=3 =3 =3 =3 =3 221.r . 1.r 1.r . 23=4 =4 =4 =4 =4 241.r . 1.r 1.r . 25=5 =5 =5 =5 =5 261.r . 1.r 1.r . 27=6 =6 =6 =6 =6 281.r . 1.r 1.r . 29=7 =7 =7 =7 =7 301.r . 1.r 1.r . 31=8 =8 =8 =8 =8 321.r . 1.r 1.r . 33=9 =9 =9 =9 =9 341.r . 1.r 1.r . 35=10 =10 =10 =10 =10 361.r . 1.r 1.r . 37=11 =11 =11 =11 =11 381.r . 1.r 1.r . 39=12 =12 =12 =12 =12 401.r . 1.r 1.r . 41=13 =13 =13 =13 =13 421.r . 1.r 1.r . 43=14 =14 =14 =14 =14 441.r . 1.r 1.r . 45=15 =15 =15 =15 =15 461.r . 1.r 1.r . 47=16 =16 =16 =16 =16 481.r . 1.r 1.r . 49=17 =17 =17 =17 =17 501.r . 1.r 1.r . 51=18 =18 =18 =18 =18 521.r . 1.r 1.r . 53== == == == == 54*- *- *- *- *-`; 55let SNAPSHOT_CHAPTER_8; 56const WIDMUNG_KRN_RAW = `!!!verovio: breaks encoded 57!!!COM: Robert Schumann 58!!!OTL: Widmung 59!!!ODT: 1840 60!!!OMD: Innig, lebhaft. 61**kern **dynam **kern **kern **text 62*clefF4 * *clefG2 *clefG2 * 63*k[b-e-a-d-] * *k[b-e-a-d-] *k[b-e-a-d-] * 64*M3/2 *M3/2 *M3/2 *M3/2 * 65=1 =1 =1 =1 =1 66*^ * * * * 67* *ped * * * * 681AAA- 1AA- (8E-L\\ mf (8cL/ 1.r . 69. 8A-\\ < 8e-/ . . 70. 8.c\\ . 8.a-/ . . 71. 16e-J\\ . 16ccJ/ . . 72. 8e-L\\ . 8ccL/ . . 73. 8c\\ . 8a-/ . . 74* *Xped * * * * 75. 8A-\\ [ 8e-/ . . 76* *ped * * * * 77. 8E-J)\\ . 8cJ)/ . . 782AAA-\\ 2AA-\\ (8E-L\\ . (8cL/ . . 79. 8A-\\ . 8e-/ . . 80* *Xped * * * * 81. 8E-\\ . 8B-/ . . 82. 8A-J)\\ . 8e-J)/ . . 83=2 =2 =2 =2 =2 = 84* *ped * * * * 851AAA- 1AA- (8E-L\\ . (8cL/ 4cc Du 86. 8A-\\ < 8e-/ . . 87. 8.c\\ . 8.a-/ 8cc mei- 88. . . . 8cc ne 89. 16e-J\\ . 16ccJ/ . . 90. 8e-L\\ . 8ccL/ 4.ee- See- 91. 8c\\ . 8a-/ . . 92* *Xped * * * * 93. 8A-\\ [ 8e-/ . . 94* *ped * * * * 95. 8E-J)\\ . 8cJ)/ 8a- le, 962AAA-\\ 2AA-\\ (8E-L\\ . (8cL/ 4a- du 97. 8A-\\ . 8e-/ . . 98* *Xped * * * * 99. 8E-\\ . 8B-/ 4b- mein 100. 8A-J)\\ . 8e-J)/ . . 101=3 =3 =3 =3 =3 = 102* *ped * * * * 1031AAA- 1AA- (8E-L\\ . (8cL/ 2cc Herz, 104. 8A-\\ < 8e-/ . . 105. 8.c\\ . 8.a-/ . . 106. 16e-J\\ . 16ccJ/ . . 107. 8e-L\\ . 8ccL/ 2r . 108. 8c\\ . 8a-/ . . 109* *Xped * * * * 110. 8A-\\ [ 8e-/ . . 111* *ped * * * * 112. 8E-J)\\ . 8cJ)/ . . 1132AAA-\\ 2AA-\\ (8E-L\\ . (8cL/ 4a- du 114. 8A-\\ . 8e-/ . . 115* *Xped * * * * 116. 8E-\\ . 8c/ 8a- mei- 117. 8cJ)\\ . 8A-J)/ 8a- ne 118=4 =4 =4 =4 =4 = 119* *ped * * * * 120. . . . qgg . 1211DDD- 1DD- (8A-L\\ . (8fL/ 2ff[ Wonnâ, 122. 8d-\\ . 8a-/ . . 123. 8.f\\ . 8.dd-/ . . 124. 16a-J\\ . 16ffJ/ . . 125. 8a-L\\ . 8ffL/ 4ff] . 126. 8A-\\ . 8f/ . . 127. 8g\\ . 8ee-/ 4ee- o 128. 8A-J)\\ . 8fJ)/ . . 129* *Xped * * * * 1302r (8fL\\ . (8dd-L/ 4dd- du 131. 8A-\\ . 8f/ . . 132. 8e-\\ . 8cc/ 4cc mein 133. 8A-J)\\ . 8fJ)/ . . 134!!LO:LB:g=original 135=5 =5 =5 =5 =5 = 1361DDD- 1DD- (8A-L\\ . (2b-L/ 2b- Schmerz, 137. 8d-\\ . . . . 138. 8.f-\\ . . . . 139. 16b-J\\ . . . . 140. 8b-L\\ . 2r 2r . 141. 8g\\ . . . . 142. 8D-\\ . . . . 143. 8AA-J)\\ . . . . 1442DD-\\ 2EE-\\ (8GGL\\ . 4b-^/ 4b-^ du 145. 8BB-\\ . . . . 146. 4r . 8g/ 8g mei- 147. . . 8e-J/ 8e- ne 148=6 =6 =6 =6 =6 = 1491CC 1E- 8A-\\ . 1a-L 2a-[ Welt, 150. 8cJ)\\ . . . . 151. (8.e-L\\ . . . . 152. 16a-\\ . . . . 153. 8a-\\ . . 4a-] . 154. 8e-J\\ . . . . 155. 8cL\\ . . 4a- in 156. 8A-\\ . . . . 157* *ped * * * * 1582BBB-\\ 8g\\ . 2dd-/ 4.dd- der 159. 8e-J)\\ . . . . 160. (8d-L\\ . . . . 161* *Xped * * * * 162. 8G\\ . . 8dd- ich 163=7 =7 =7 =7 =7 = 164* *ped * * * * 1654AAA-\\ 8g\\ . 2dd-/ 2dd- le- 166. 8e-J)\\ . . . . 1674r (8d-L\\ . . . . 168. 8G\\ . . . . 1694AA-\\ 8a-\\ . 4ccJ)/ 4cc be, 170. 8e-J\\ . . . . 171* *Xped * * * * 1724r 8cL\\ . 4r 4cc mein 173. 8A-\\ . . . . 1742GG- 2AA- 4c\\ . (4ee-^L/ 4.ee-^ Him- 175. 4r . 8cc/ . . 176. . . 8a-/ 8a- mel 177=8 =8 =8 =8 =8 = 1781FF 1AA- 8D-J)\\ . 2dd-[J)/ 2dd-[ du, 179. (8FL\\ . . . . 180. 8.A-\\ . . . . 181. 16d-\\ . . . . 182. 8d-J)\\ . (4dd-L/ 4dd-] . 183. (8A-L\\ . . . . 184. 8F\\ . 4dd-/ 4dd- da- 185. 8D-\\ . . . . 186* *ped * * * * 1872EE-\\ 8cJ\\ . 4gg-/ 4.gg- rein 188. 8A-L\\ . . . . 189. 8G-\\ . 4gg-J/ . . 190* *Xped * * * * 191. 8C\\ . . 8gg- ich 192!!LO:LB:g=original 193=9 =9 =9 =9 =9 = 194* *ped * * * * 1954DD-\\ 8cJ)\\ . 2gg-L/ 2gg- schwe- 196. (8A-L\\ . . . . 1972r 8G-\\ . . . . 198. 8C\\ . . . . 199. 4DJ)\\ . 4ff/ 4ff be, 200* *Xped * * * * 2012DD-\\ (4AAL\\ . 4ee-/ 4ee- o 202. 4BB\\ . 4dd-J)/ 4dd- du 2034DD-\\ 4D\\ . (4ccL/ 4cc mein 204=10 =10 =10 =10 =10 = 2051DD- 8BB-J\\ . 2b-[/ 2b- Grab, 206. 8DL\\ . . . . 207. 8.F-\\ . . . . 208. 16G\\ . . . . 209. 8GJ)\\ . 4b-/ 4r . 210. (8F-L\\ . . . . 211. 8D\\ . 4b-J)/ 4b- in 212. 8BB-\\ . . . . 2132DD-\\ 8GGJ)\\ . (4ccL/ 4cc das 214. (8BB-L\\ . . . . 215. 8F-\\ . 4dd-/ 4dd- hin- 216. 8G\\ . . . . 217=11 =11 =11 =11 =11 = 2181CC 8AA-J\\ . 2ee-/ 4ee- ab 219. 8E-L\\ . . . . 220. 8.G-\\ . . 4a- ich 221. 16A-\\ . . . . 222. 8A-J)\\ . 2a-[J/ 2a-[ e- 223. (8G-L\\ . . . . 224. 8E-\\ . . . . 225. 8AA-\\ . . . . 2264DD-\\ 8DJ)\\ . 4a-L/ 4a-] . 227. (8A-L\\ . . . . 2284BBB-\\ 8BB\\ . 4dd-/ 4dd- wig 229. 8D\\ . . . . 230=12 =12 =12 =12 =12 = 2312EE-\\ 8AA-J\\ . 2cc/ 4cc mei- 232. 8CL\\ . . . . 233. 8E-\\ . . 4cc nen 234. 8A-\\ . . . . 235. . . . qcc . 2361EE- 8AA-J)\\ . 2b-[J)/ 2b-[ Kum- 237. (8BB-L\\ . . . . 238. 8.D-\\ . . . . 239. 16E\\ . . . . 240. 8EJ)\\ . (4b-L/ 4b-] . 241. (8D-L\\ . . . . 242. 8GG\\ . 4cc/ 4cc mer 243. 8EE\\ . . . . 244=13 =13 =13 =13 =13 = 2451AAA- 8E-J\\ . 1.a- 2a- gab! 246. 8A-L\\ . . . . 247. 8.C\\ . . . . 248. 16E-\\ . . . . 249. 8E-J)\\ . . 2r . 250. (8CL\\ . . . . 251. 8AA-\\ . . . . 252. 8EE-\\ . . . . 2532EE- 2r > . 2r . 254. . [ . . . 255!!LO:LB:g=original 256==|| ==|| ==|| ==|| ==|| ==|| 257=14 =14 =14 =14 =14 =14 258*k[f#c#g#d#] *k[f#c#g#d#] * *k[f#c#g#d#] *k[f#c#g#d#] * 259* *ped * * * * 2601EE (6EL\\ p 6eJ)/ 1g# Du 261. 6E\\ . (6eL/ . . 262. 6EJ)\\ . 6e/ . . 263. (6EL\\ . 6eJ)/ . . 264. 6E\\ . (6eL/ . . 265. 6EJ)\\ . 6e/ . . 266* *Xped * * * * 2672EE\\ (6DL\\ . 6eJ)/ 4f# bist 268. 6D\\ . (6eL/ . . 269. . . . 4g# die 270. 6DJ)\\ . 6e/ . . 271=15 =15 =15 =15 =15 =15 272* *ped * * * * 2731EE (6CL\\ . 6aJ)/ 1a Ruhâ, 274. 6C\\ . (6aL/ . . 275. 6CJ)\\ . 6a/ . . 276. (6CL\\ . 6aJ)/ . . 277. 6C\\ . (6aL/ . . 278. 6CJ)\\ . 6a/ . . 279* *Xped * * * * 2802EE\\ (6BBL\\ . 6gJ)/ 2g# du 281. 6BB\\ . (6gL/ . . 282. 6BBJ)\\ . 6g/ . . 283=16 =16 =16 =16 =16 =16 284* *ped * * * *
2851EE (6AAL\\ . 6fJ)/ 1f#[ bist 286. 6AA\\ . (6fL/ . . 287. 6AAJ)\\ . 6f/ . . 288. (6AAL\\ . 6fJ)/ . . 289. 6AA\\ . (6fL/ . . 290. 6AAJ)\\ . 6f/ . . 291* *Xped * * * * 2922EE\\ (6BBL\\ . 6fJ)/ 4f#] . 293. 6BB\\ . (6fL/ . . 294. . . . 4g# der 295. 6BBJ)\\ . 6f/ . . 296=17 =17 =17 =17 =17 =17 297* *ped * * * * 2981r (2EL\\ . 6eJ)/ 2b Frie- 299. . . (6eL/ . . 300. . . 6e/ . . 301. 2r . 6BJ)/ 2e den, 302. . . (6BL/ . . 303. . . 6B/ . . 304* *Xped * * * * 3052r 2BB\\ . 6BJ)/ 4e du 306. . . (6BL/ . . 307. . . . 4f# bist 308. . . 6B/ . . 309=18 =18 =18 =18 =18 =18 3104r 4r . 4r 4g# vom 311*v *v * * * * 312== == == == == 313*- *- *- *- *- 314!!!hamienet: systemIndents=0_350`; 315SNAPSHOT_CHAPTER_8 = WIDMUNG_KRN_RAW; 316const setupChapterSteps = [ 317 { 318 id: "setup-welcome", 319 instruction: "Welcome to the Robert Schumann - Widmung transcription tutorial! Click the Setup Wizard card.", 320 mode: "both", 321 action: { type: "click", selector: '[data-testid="start-setup-wizard-card"]' }, 322 validation: { type: "element-exists", value: ".setup-wizard-modal" }, 323 cursorTarget: { selector: '[data-testid="start-setup-wizard-card"]' }, 324 interactionMilestone: { 325 promptText: 'Your turn! Click the "Setup Wizard" card to begin configuring the score.', 326 targetSelector: '[data-testid="start-setup-wizard-card"]', 327 successMessage: "Excellent! The Score Setup Wizard is now open. ð" 328 }, 329 durationMs: 2200 330 }, 331 { 332 id: "setup-title", 333 instruction: `Let's configure Widmung's initial metadata. Fill in Title: "Widmung".`, 334 mode: "both", 335 action: { type: "click", selector: '[data-testid="wizard-title-input"]' }, 336 cursorTarget: { selector: '[data-testid="wizard-title-input"]' }, 337 durationMs: 1800 338 }, 339 { 340 id: "setup-composer", 341 instruction: 'Now fill in Composer: "Robert Schumann".', 342 mode: "both", 343 action: { type: "click", selector: '[data-testid="wizard-composer-input"]' }, 344 cursorTarget: { selector: '[data-testid="wizard-composer-input"]' }, 345 durationMs: 1800 346 }, 347 { 348 id: "setup-next-1", 349 instruction: "Click Next to choose the instruments for the score.", 350 mode: "both", 351 action: { type: "click", selector: '[data-testid="wizard-next-btn"]' }, 352 cursorTarget: { selector: '[data-testid="wizard-next-btn"]' }, 353 durationMs: 1800 354 }, 355 { 356 id: "setup-next-2", 357 instruction: "Piano is selected by default. Click Next to set the key and time signatures.", 358 mode: "both", 359 action: { type: "click", selector: '[data-testid="wizard-next-btn"]' }, 360 cursorTarget: { selector: '[data-testid="wizard-next-btn"]' }, 361 durationMs: 1800 362 }, 363 { 364 id: "setup-sig", 365 instruction: "Set the key signature to A-flat Major (4 flats) and the meter to 3/2 time.", 366 mode: "both", 367 action: { type: "click", selector: '[data-testid="wizard-key-Ab"]' }, 368 cursorTarget: { selector: '[data-testid="wizard-key-Ab"]' }, 369 durationMs: 2500 370 }, 371 { 372 id: "setup-generate", 373 instruction: 'Excellent! Click "Finish & Create" to create the blank Grand Staff staves.', 374 mode: "both", 375 action: { type: "wizard-fill", title: "Widmung", composer: "Robert Schumann", keySig: "A-flat Major", timeSig: "3/2", tempo: 120, tempoText: "Innig, lebhaft." }, 376 validation: { type: "wizard-completed", value: "" }, 377 cursorTarget: { selector: '[data-testid="wizard-finish-btn"]' }, 378 interactionMilestone: { 379 promptText: 'Your turn! Click "Finish & Create" to create your new score layout.', 380 targetSelector: '[data-testid="wizard-finish-btn"]', 381 triggerSelector: '[data-testid="wizard-finish-btn"]', 382 successMessage: "Fantastic! The blank score is now generated. ð¹" 383 }, 384 durationMs: 3e3 385 } 386]; 387const generateStepsProgrammatically = () => { 388 var _a; 389 const krnLines = WIDMUNG_KRN_RAW.split("\n").map((l) => l.trim()).filter((l) => l.length > 0); 390 const parsedLines = krnLines.map((line) => { 391 if (line.startsWith("!") || line.startsWith("*") || line.startsWith("=")) { 392 return { isMeta: true, raw: line, cols: [] }; 393 } 394 return { isMeta: false, raw: line, cols: line.split(" ") }; 395 }); 396 const activeGrid = parsedLines.map((line) => { 397 if (line.isMeta) return [...line.cols]; 398 return line.cols.map(() => "."); 399 }); 400 const compileHumdrumSnapshot = () => { 401 return parsedLines.map((line, lineIdx) => { 402 if (line.isMeta) return line.raw; 403 return activeGrid[lineIdx].join(" "); 404 }).join("\n") + "\n"; 405 }; 406 const generatedSteps = []; 407 let noteIndex = 1; 408 let currentMeasure = 1; 409 let currentBeat = 1;
410 parsedLines.forEach((line, lineIdx) => { 411 if (line.isMeta) { 412 if (line.raw.startsWith("=")) { 413 const m = parseInt(line.raw.replace(/[^0-9]/g, ""), 10); 414 if (!isNaN(m)) { 415 currentMeasure = m; 416 currentBeat = 1; 417 } 418 } 419 return; 420 } 421 const lineBeat = currentBeat; 422 currentBeat = currentBeat % 3 + 1; 423 const cols = line.cols; 424 const bassToken = cols[0]; 425 if (bassToken && bassToken !== "." && !bassToken.startsWith("!")) { 426 activeGrid[lineIdx][0] = bassToken; 427 const isRest = bassToken.includes("r"); 428 const durMatch = bassToken.match(/^(\d+)/); 429 durMatch ? durMatch[1] : "1"; 430 generatedSteps.push({ 431 id: `gen-step-${noteIndex++}`, 432 instruction: isRest ? `Add bass rest in lower staff.` : `Place bass note ${bassToken} in lower staff.`, 433 mode: "both", 434 action: { type: "set-humdrum", content: compileHumdrumSnapshot() }, 435 cursorTarget: { selector: `.measure-${currentMeasure} .beat-1-target` }, 436 durationMs: isRest ? 600 : 900, 437 goalSnapshot: compileHumdrumSnapshot(), 438 position: { measure: currentMeasure, beat: lineBeat, tick: 0 } 439 }); 440 } 441 const accToken = cols[2]; 442 if (accToken && accToken !== "." && !accToken.startsWith("!")) { 443 activeGrid[lineIdx][2] = accToken; 444 const isRest = accToken.includes("r"); 445 const durMatch = accToken.match(/^(\d+)/); 446 durMatch ? durMatch[1] : "8"; 447 generatedSteps.push({ 448 id: `gen-step-${noteIndex++}`, 449 instruction: isRest ? `Add rest in accompaniment.` : `Place accompaniment note ${accToken} in upper Left Hand staff.`, 450 mode: "both", 451 action: { type: "set-humdrum", content: compileHumdrumSnapshot() }, 452 cursorTarget: { selector: `.measure-${currentMeasure} .beat-1-target` }, 453 durationMs: isRest ? 600 : 900, 454 goalSnapshot: compileHumdrumSnapshot(), 455 position: { measure: currentMeasure, beat: lineBeat, tick: 0 } 456 }); 457 } 458 const vocalToken = cols[3]; 459 const textToken = cols[4]; 460 if (vocalToken && vocalToken !== "." && !vocalToken.startsWith("!")) { 461 activeGrid[lineIdx][3] = vocalToken; 462 if (textToken && textToken !== ".") { 463 activeGrid[lineIdx][4] = textToken; 464 } 465 const isRest = vocalToken.includes("r"); 466 const durMatch = vocalToken.match(/^(\d+)/); 467 durMatch ? durMatch[1] : "4"; 468 let instr = isRest ? `Add rest in vocal line.` : `Place vocal melody note ${vocalToken} on Soprano staff.`; 469 if (textToken && textToken !== "." && textToken !== "r" && !textToken.startsWith("!")) { 470 instr += ` (Lyrics: "${textToken}")`; 471 } 472 generatedSteps.push({ 473 id: `gen-step-${noteIndex++}`, 474 instruction: instr, 475 mode: "both", 476 action: { type: "set-humdrum", content: compileHumdrumSnapshot() }, 477 cursorTarget: { selector: `.measure-${currentMeasure} .beat-1-target` }, 478 durationMs: isRest ? 600 : 900, 479 goalSnapshot: compileHumdrumSnapshot(), 480 position: { measure: currentMeasure, beat: lineBeat, tick: 0 } 481 }); 482 } 483 }); 484 const totalGenSteps = generatedSteps.length; 485 const stepsPerChapter = Math.max(15, Math.ceil(totalGenSteps / 7)); 486 const chapters = [ 487 { 488 id: "setup", 489 title: "Setting Up the Score", 490 description: "Use the Score Wizard to configure Widmung's initial grand staff layout, meter, and composer details.", 491 skillLearned: "Score Configuration", 492 goalSnapshot: SNAPSHOT_CHAPTER_1, 493 steps: setupChapterSteps 494 } 495 ]; 496 const chapterNames = [ 497 { id: "piano-bass", title: "Entering the Bass Foundation", desc: "Place whole and half note bass foundations on the lower piano staff." }, 498 { id: "piano-triplets", title: "Flowing Accompaniment Triplets", desc: "Type flowing arpeggiated accompaniment triplets into the LH upper voice." }, 499 { id: "vocal-melody", title: "Transcribing Vocal Melody", desc: "Configure Soprano Vocal clef staves and transcribe lead vocal lines." }, 500 { id: "ties-articulation", title: "Ties, Slurs & Pedal marks", desc: "Apply expression slurs, note ties, and damper pedal markings to staves." }, 501 { id: "lyrics-text", title: "Vocal Lyrics Alignment", desc: "Map vocal text syllables to note positions in lyrics tracks." }, 502 { id: "modulation", title: "Modulation & E Major key change", desc: "Apply a key signature change to E Major (4 sharps) with flat natural cancellations." }, 503 { id: "engraving-polish", title: "System Indents & Layout polish", desc: "Set Breitkopf Leipzig edition system indents and spacing properties." } 504 ]; 505 for (let cIdx = 0; cIdx < 7; cIdx++) { 506 const start = cIdx * stepsPerChapter; 507 const end = Math.min(totalGenSteps, (cIdx + 1) * stepsPerChapter); 508 const chapterSteps = generatedSteps.slice(start, end); 509 const info = chapterNames[cIdx]; 510 const stepsWithKeypadHops = []; 511 chapterSteps.forEach((step, sIdx) => { 512 if (sIdx % 4 === 0) { 513 const durKeys = ['[data-testid="keypad-btn-2-2"]', '[data-testid="keypad-btn-1-0"]', '[data-testid="keypad-btn-1-2"]']; 514 const randomKey = durKeys[sIdx % durKeys.length]; 515 stepsWithKeypadHops.push({ 516 id: `${step.id}-keypad`, 517 instruction: "Select duration on the Notation Keypad.", 518 mode: "both", 519 action: { type: "click", selector: randomKey }, 520 cursorTarget: { selector: randomKey }, 521 durationMs: 700, 522 goalSnapshot: step.goalSnapshot 523 }); 524 } 525 stepsWithKeypadHops.push(step); 526 }); 527 chapters.push({ 528 id: info.id, 529 title: info.title, 530 description: info.desc, 531 skillLearned: "Notation Engraving", 532 goalSnapshot: ((_a = chapterSteps[chapterSteps.length - 1]) == null ? void 0 : _a.goalSnapshot) || SNAPSHOT_CHAPTER_8, 533 steps: stepsWithKeypadHops 534 }); 535 } 536 chapters[chapters.length - 1].goalSnapshot = SNAPSHOT_CHAPTER_8; 537 const lastChapSteps = chapters[chapters.length - 1].steps; 538 if (lastChapSteps.length > 0) { 539 lastChapSteps[lastChapSteps.length - 1].goalSnapshot = SNAPSHOT_CHAPTER_8; 540 } 541 return { chapters }; 542}; 543const { chapters: tutorialChapters } = generateStepsProgrammatically(); 544const SIMPLE_GOAL_SNAPSHOT = `**kern 545*clefG2 546*k[] 547*M4/4 548=1 5494e 5504d 5514c 5524d 553=2 5544e 5554e 5562e 557=3 5584d 5594d 5602d 561=4 5624e 5634g 5642g 565== 566*-`; 567const simpleChapters = [ 568 { 569 id: "simple-setup", 570 title: "Setting Up Your Song", 571 description: "Let's open the Score Wizard and create a blank page to start writing our notes.", 572 skillLearned: "Score Configuration", 573 goalSnapshot: `**kern 574*clefG2 575*k[] 576*M4/4 577=1 5781r 579=2 5801r 581=3 5821r 583=4 5841r 585== 586*-`, 587 steps: [ 588 { 589 id: "simple-setup-welcome", 590 instruction: `Welcome to the music editor! Let's start by clicking the "Setup Wizard" card to create our song sheet.`, 591 kidFriendlyInstruction: `Welcome! Let's build our music sheet by clicking the "Setup Wizard" card.`, 592 mode: "both", 593 action: { type: "click", selector: '[data-testid="start-setup-wizard-card"]' }, 594 validation: { type: "element-exists", value: ".setup-wizard-modal" }, 595 cursorTarget: { selector: '[data-testid="start-setup-wizard-card"]' }, 596 interactionMilestone: { 597 promptText: 'Your turn! Click the "Setup Wizard" card to begin configuring the score.', 598 targetSelector: '[data-testid="start-setup-wizard-card"]', 599 successMessage: "Excellent! The Score Setup Wizard is now open. ð" 600 }, 601 durationMs: 2500 602 }, 603 { 604 id: "simple-setup-fill", 605 instruction: 'Type "Mary Had a Little Lamb" in the Title box, then click Next.', 606 kidFriendlyInstruction: 'Type "Mary Had a Little Lamb" in the Title box, then click Next.', 607 mode: "both", 608 action: { type: "click", selector: '[data-testid="wizard-next-btn"]' }, 609 cursorTarget: { selector: '[data-testid="wizard-title-input"]' }, 610 interactionMilestone: { 611 promptText: 'Your turn! Type "Mary Had a Little Lamb" in the Title box, then click Next.', 612 targetSelector: '[data-testid="wizard-title-input"]', 613 triggerSelector: '[data-testid="wizard-next-btn"]', 614 successMessage: "Great job! Score title configured. ð" 615 }, 616 durationMs: 3e3 617 }, 618 { 619 id: "simple-setup-instruments", 620 instruction: "Piano is pre-selected for our song layout. Click Next to set the key signature and meter.", 621 kidFriendlyInstruction: "Piano is pre-selected for our song layout. Click Next to set the key signature and meter.", 622 mode: "both", 623 action: { type: "click", selector: '[data-testid="wizard-next-btn"]' }, 624 cursorTarget: { selector: '[data-testid="wizard-next-btn"]' }, 625 interactionMilestone: { 626 promptText: "Your turn! Click Next to continue to Key & Time Signature.", 627 targetSelector: '[data-testid="wizard-next-btn"]', 628 triggerSelector: '[data-testid="wizard-next-btn"]', 629 successMessage: "Awesome! Next page opened. ðµ" 630 }, 631 durationMs: 2500 632 }, 633 { 634 id: "simple-setup-generate", 635 instruction: 'Perfect! Click "Finish & Create" to generate your fresh, clean blank staff.', 636 kidFriendlyInstruction: 'Awesome! Click "Finish & Create" to see your empty music sheet.', 637 mode: "both", 638 action: { 639 type: "wizard-fill", 640 title: "Mary Had a Little Lamb", 641 composer: "Traditional", 642 keySig: "C Major", 643 timeSig: "4/4", 644 tempo: 100, 645 tempoText: "Moderato" 646 }, 647 validation: { type: "wizard-completed", value: "" }, 648 cursorTarget: { selector: '[data-testid="wizard-finish-btn"]' }, 649 interactionMilestone: { 650 promptText: 'Your turn! Click "Finish & Create" to create your new score layout.', 651 targetSelector: '[data-testid="wizard-finish-btn"]', 652 triggerSelector: '[data-testid="wizard-finish-btn"]', 653 successMessage: "Fantastic! The blank score is now generated. ð¹" 654 }, 655 durationMs: 3e3, 656 goalSnapshot: `**kern 657*clefG2 658*k[] 659*M4/4 660=1 6611r 662=2 6631r 664=3 6651r 666=4 6671r 668== 669*-` 670 } 671 ] 672 }, 673 { 674 id: "simple-melody-1", 675 title: "Entering the First Notes", 676 description: 'We will enter the first part of the melody: "Ma-ry had a lit-tle lamb".', 677 skillLearned: "Entering Quarter Notes", 678 goalSnapshot: `**kern 679*clefG2 680*k[] 681*M4/4 682=1 6834e 6844d 6854c 6864d 687=2 6884e 6894e 6902e 691=3 6921r 693=4 6941r 695== 696*-`, 697 steps: [ 698 { 699 id: "simple-step-4", 700 instruction: "First, select the Quarter Note (1/4) duration button on the keypad.", 701 kidFriendlyInstruction: "Select the Quarter Note button (the one that looks like a filled black note with a stick).", 702 mode: "both", 703 action: { type: "click", selector: '[data-testid="keypad-btn-1-0"]' }, 704 cursorTarget: { selector: '[data-testid="keypad-btn-1-0"]' }, 705 interactionMilestone: { 706 promptText: "Your turn! Click the Quarter Note button on the keypad.", 707 targetSelector: '[data-testid="keypad-btn-1-0"]', 708 successMessage: "Great job! You selected the quarter note. ð" 709 }, 710 durationMs: 2500, 711 goalSnapshot: `**kern 712*clefG2 713*k[] 714*M4/4 715=1 7161r 717=2 7181r 719=3 7201r 721=4 7221r 723== 724*-` 725 }, 726 { 727 id: "simple-step-5", 728 instruction: 'Now place the first note, E4, on the staff. This represents the syllable "Ma-".', 729 kidFriendlyInstruction: "Let's place our first note, E (Ma-).", 730 mode: "both", 731 action: { 732 type: "set-humdrum", 733 content: `**kern 734*clefG2 735*k[] 736*M4/4 737=1 7384e 7392.r 740=2 7411r 742=3 7431r 744=4 7451r 746== 747*-` 748 }, 749 cursorTarget: { selector: ".measure-1 .beat-1-target" }, 750 interactionMilestone: { 751 promptText: "Your turn! Click Measure 1, Beat 1 on the staff to place the first note.", 752 targetSelector: ".measure-1 .beat-1-target", 753 successMessage: "Awesome! First note placed on Measure 1, Beat 1. ðµ" 754 }, 755 position: { measure: 1, beat: 1, tick: 0 }, 756 durationMs: 2e3, 757 goalSnapshot: `**kern 758*clefG2 759*k[] 760*M4/4 761=1 7624e 7632.r 764=2 7651r 766=3 7671r 768=4 7691r 770== 771*-` 772 }, 773 { 774 id: "simple-step-6", 775 instruction: 'Add the next note, D4, for "ry".', 776 kidFriendlyInstruction: "Add the next note, D (-ry).", 777 mode: "both", 778 action: { 779 type: "set-humdrum", 780 content: `**kern 781*clefG2 782*k[] 783*M4/4 784=1 7854e 7864d 7872r 788=2 7891r 790=3 7911r 792=4 7931r 794== 795*-` 796 }, 797 cursorTarget: { selector: "#verovio-svg" }, 798 durationMs: 1500, 799 goalSnapshot: `**kern 800*clefG2 801*k[] 802*M4/4 803=1 8044e 8054d 8062r 807=2 8081r 809=3 8101r 811=4 8121r 813== 814*-` 815 }, 816 { 817 id: "simple-step-7", 818 instruction: 'Add a C4 note for "had".', 819 kidFriendlyInstruction: 'Add a C note for "had".', 820 mode: "both", 821 action: { 822 type: "set-humdrum", 823 content: `**kern 824*clefG2 825*k[] 826*M4/4 827=1 8284e 8294d 8304c 8314r 832=2 8331r 834=3 8351r 836=4 8371r 838== 839*-` 840 }, 841 cursorTarget: { selector: "#verovio-svg" }, 842 durationMs: 1500, 843 goalSnapshot: `**kern 844*clefG2 845*k[] 846*M4/4 847=1 8484e 8494d 8504c 8514r 852=2 8531r 854=3 8551r 856=4 8571r 858== 859*-` 860 }, 861 { 862 id: "simple-step-8", 863 instruction: 'Add D4 again for "a". Now the first measure is full!', 864 kidFriendlyInstruction: 'Add D note for "a". The first bar is complete!', 865 mode: "both", 866 action: { 867 type: "set-humdrum", 868 content: `**kern 869*clefG2 870*k[] 871*M4/4 872=1 8734e 8744d 8754c 8764d 877=2 8781r 879=3 8801r 881=4 8821r 883== 884*-` 885 }, 886 cursorTarget: { selector: "#verovio-svg" }, 887 durationMs: 1500, 888 goalSnapshot: `**kern 889*clefG2 890*k[] 891*M4/4 892=1 8934e 8944d 8954c 8964d 897=2 8981r 899=3 9001r 901=4 9021r 903== 904*-` 905 }, 906 { 907 id: "simple-step-9", 908 instruction: `Let's start the second measure. Place an E4 note for the syllable "lit-".`, 909 kidFriendlyInstruction: "Start the second measure by placing an E note (lit-).", 910 mode: "both", 911 action: { 912 type: "set-humdrum", 913 content: `**kern 914*clefG2 915*k[] 916*M4/4 917=1 9184e 9194d 9204c 9214d 922=2 9234e 9242.r 925=3 9261r 927=4 9281r 929== 930*-` 931 }, 932 cursorTarget: { selector: "#verovio-svg" }, 933 durationMs: 1500, 934 goalSnapshot: `**kern 935*clefG2 936*k[] 937*M4/4 938=1 9394e 9404d 9414c 9424d 943=2 9444e 9452.r 946=3 9471r 948=4 9491r 950== 951*-` 952 }, 953 { 954 id: "simple-step-10", 955 instruction: 'Place another E4 note for "-tle".', 956 kidFriendlyInstruction: "Place another E note (-tle).", 957 mode: "both", 958 action: { 959 type: "set-humdrum", 960 content: `**kern 961*clefG2 962*k[] 963*M4/4 964=1 9654e 9664d 9674c 9684d 969=2 9704e 9714e 9722r 973=3 9741r 975=4 9761r 977== 978*-` 979 }, 980 cursorTarget: { selector: "#verovio-svg" }, 981 durationMs: 1500, 982 goalSnapshot: `**kern 983*clefG2 984*k[] 985*M4/4 986=1 9874e 9884d 9894c 9904d 991=2 9924e 9934e 9942r 995=3 9961r 997=4 9981r 999== 1000*-` 1001 }, 1002 { 1003 id: "simple-step-11", 1004 instruction: "Now, select the Half Note (1/2) duration button on the keypad. This note lasts twice as long!", 1005 kidFriendlyInstruction: "Select the Half Note button (the empty note head with a stick). It holds for two beats!", 1006 mode: "both", 1007 action: { type: "click", selector: '[data-testid="keypad-btn-1-1"]' }, 1008 cursorTarget: { selector: '[data-testid="keypad-btn-1-1"]' }, 1009 interactionMilestone: { 1010 promptText: "Your turn! Click the Half Note button on the keypad.", 1011 targetSelector: '[data-testid="keypad-btn-1-1"]', 1012 successMessage: "Splendid! You selected the half note. ð" 1013 }, 1014 durationMs: 2500, 1015 goalSnapshot: `**kern 1016*clefG2 1017*k[] 1018*M4/4 1019=1 10204e 10214d 10224c 10234d 1024=2 10254e 10264e 10272r 1028=3 10291r 1030=4 10311r 1032== 1033*-` 1034 }, 1035 { 1036 id: "simple-step-12", 1037 instruction: 'Place the half note E4 on the staff for the word "lamb".', 1038 kidFriendlyInstruction: 'Place the E half note for "lamb".', 1039 mode: "both", 1040 action: { 1041 type: "set-humdrum", 1042 content: `**kern 1043*clefG2 1044*k[] 1045*M4/4 1046=1 10474e 10484d 10494c 10504d 1051=2 10524e 10534e 10542e 1055=3 10561r 1057=4 10581r 1059== 1060*-` 1061 }, 1062 cursorTarget: { selector: "#verovio-svg" }, 1063 durationMs: 2e3, 1064 goalSnapshot: `**kern 1065*clefG2 1066*k[] 1067*M4/4 1068=1 10694e 10704d 10714c 10724d 1073=2 10744e 10754e 10762e 1077=3 10781r 1079=4 10801r 1081== 1082*-` 1083 } 1084 ] 1085 }, 1086 { 1087 id: "simple-melody-2", 1088 title: "Finishing Your Song", 1089 description: "We will enter the remaining notes and then play our so
1089ng!", 1090 skillLearned: "Playing Back a Song", 1091 goalSnapshot: SIMPLE_GOAL_SNAPSHOT, 1092 steps: [ 1093 { 1094 id: "simple-step-13", 1095 instruction: `Let's finish the next measure with three notes: D, D, and a half note D. ("Lit-tle lamb, lit-tle lamb")`, 1096 kidFriendlyInstruction: "Let's add three notes: D, D, and a longer D note for the next measure.", 1097 mode: "both", 1098 action: { 1099 type: "set-humdrum", 1100 content: `**kern 1101*clefG2 1102*k[] 1103*M4/4 1104=1 11054e 11064d 11074c 11084d 1109=2 11104e 11114e 11122e 1113=3 11144d 11154d 11162d 1117=4 11181r 1119== 1120*-` 1121 }, 1122 cursorTarget: { selector: "#verovio-svg" }, 1123 durationMs: 2500, 1124 goalSnapshot: `**kern 1125*clefG2 1126*k[] 1127*M4/4 1128=1 11294e 11304d 11314c 11324d 1133=2 11344e 11354e 11362e 1137=3 11384d 11394d 11402d 1141=4 11421r 1143== 1144*-` 1145 }, 1146 { 1147 id: "simple-step-14", 1148 instruction: 'Finally, enter the last measure: E, G, and a half note G. ("Its fleece was white as snow")', 1149 kidFriendlyInstruction: "Let's fill the last measure: E, G, and a longer G note to finish the song!", 1150 mode: "both", 1151 action: { 1152 type: "set-humdrum", 1153 content: SIMPLE_GOAL_SNAPSHOT 1154 }, 1155 cursorTarget: { selector: "#verovio-svg" }, 1156 durationMs: 2500, 1157 goalSnapshot: SIMPLE_GOAL_SNAPSHOT 1158 }, 1159 { 1160 id: "simple-step-play", 1161 instruction: "Congratulations! Your song is written. Click the Play button to hear how it sounds.", 1162 kidFriendlyInstruction: "Hooray! Your song is complete. Let's play it! Click the Play button.", 1163 mode: "both", 1164 action: { type: "click", selector: '[data-testid="tutorial-play-btn"]' }, 1165 cursorTarget: { selector: '[data-testid="tutorial-play-btn"]' }, 1166 interactionMilestone: { 1167 promptText: "Your turn! Click the Play button in the tutorial controls to listen to your song.", 1168 targetSelector: '[data-testid="tutorial-play-btn"]', 1169 successMessage: "Great job! Enjoy your music. ðµ" 1170 }, 1171 durationMs: 3e3, 1172 goalSnapshot: SIMPLE_GOAL_SNAPSHOT 1173 } 1174 ] 1175 }, 1176 { 1177 id: "simple-editor-controls", 1178 title: "Editor Controls & Workflow", 1179 description: "Learn playback controls, keyboard hotkeys, staff & measure management, and exporting options.", 1180 skillLearned: "Mastering Full Editor Controls", 1181 goalSnapshot: SIMPLE_GOAL_SNAPSHOT, 1182 steps: [ 1183 { 1184 id: "control-toolbar-overview", 1185 instruction: "1. Toolbar Overview: Configure Clefs, Key Signatures, Time Signatures, and Tempo BPM in the main menu toolbar.", 1186 kidFriendlyInstruction: "1. Toolbar Overview: Use the top toolbar to pick your Key, Meter, Clefs, and Tempo!", 1187 mode: "both", 1188 action: { type: "click", selector: '[data-testid="file-menu-btn"]' }, 1189 cursorTarget: { selector: '[data-testid="file-menu-btn"]' }, 1190 interactionMilestone: { 1191 promptText: "Inspect the top menu bar to explore key, meter, clef, and score setup options.", 1192 targetSelector: '[data-testid="file-menu-btn"]', 1193 triggerSelector: '[data-testid="file-menu-btn"]', 1194 successMessage: "Great! Toolbar overview completed. ð¼" 1195 }, 1196 durationMs: 3e3, 1197 goalSnapshot: SIMPLE_GOAL_SNAPSHOT 1198 }, 1199 { 1200 id: "control-playback-synth", 1201 instruction: "2. Playback & Synth Controls: Use the Play/Pause buttons and Sound Preset selector (Grand, E-Piano, Pad) to customize sound.", 1202 kidFriendlyInstruction: "2. Playback & Synth: Click Play/Pause and pick your favorite instrument sound preset!", 1203 mode: "both", 1204 action: { type: "click", selector: '[data-testid="playback-play-btn"]' }, 1205 cursorTarget: { selector: '[data-testid="synth-preset-group"]' }, 1206 interactionMilestone: { 1207 promptText: "Click Play or test switching instrument sound presets (Grand, E-Piano, Pad).", 1208 targetSelector: '[data-testid="synth-preset-group"]', 1209 triggerSelector: '[data-testid="playback-play-btn"]', 1210 successMessage: "Awesome! Playback & synth sound settings verified. ð¹" 1211 }, 1212 durationMs: 3e3, 1213 goalSnapshot: SIMPLE_GOAL_SNAPSHOT 1214 }, 1215 { 1216 id: "control-code-editor", 1217 instruction: "3. Code Editor Toggle: Switch between visual staff canvas and raw Humdrum score text syntax.", 1218 kidFriendlyInstruction: "3. Code Editor: Click the Code Editor button to see your music written as code!", 1219 mode: "both", 1220 action: { type: "click", selector: '[data-testid="toggle-code-editor-btn"]' }, 1221 cursorTarget: { selector: '[data-testid="toggle-code-editor-btn"]' }, 1222 interactionMilestone: { 1223 promptText: "Click the Code Editor button in the toolbar to view or hide the text syntax view.", 1224 targetSelector: '[data-testid="toggle-code-editor-btn"]', 1225 triggerSelector: '[data-testid="toggle-code-editor-btn"]', 1226 successMessage: "Splendid! Code editor toggle mastered. ð»" 1227 }, 1228 durationMs: 3e3, 1229 goalSnapshot: SIMPLE_GOAL_SNAPSHOT 1230 }, 1231 { 1232 id: "control-measure-voice", 1233 instruction: "4. Measure & Voice Management: Select Voice 1 (blue) or Voice 2 (orange) and add or remove staff measures.", 1234 kidFriendlyInstruction: "4. Measure & Voice: Switch between Voice 1 and Voice 2 to write polyphonic music!", 1235 mode: "both", 1236 action: { type: "click", selector: '[data-testid="voice-1-btn"]' }, 1237 cursorTarget: { selector: '[data-testid="voice-1-btn"]' }, 1238 interactionMilestone: { 1239 promptText: "Click Voice 1 or Voice 2 buttons to select your active polyphonic staff layer.", 1240 targetSelector: '[data-testid="voice-1-btn"]', 1241 triggerSelector: '[data-testid="voice-1-btn"]', 1242 successMessage: "Excellent! Voice & measure management verified. ð¶" 1243 }, 1244 durationMs: 3e3, 1245 goalSnapshot: SIMPLE_GOAL_SNAPSHOT 1246 }, 1247 { 1248 id: "control-export-options", 1249 instruction: "5. Saving & Exporting: Click the Export menu to download your sheet music as MIDI, MusicXML, or printable PDF!", 1250 kidFriendlyInstruction: "5. Saving & Exporting: Click Export to save your score as PDF, MIDI, or MusicXML!", 1251 mode: "both", 1252 action: { type: "click", selector: '[data-testid="export-menu-btn"]' }, 1253 cursorTarget: { selector: '[data-testid="export-menu-btn"]' }, 1254 interactionMilestone: { 1255 promptText: "Click the Export menu in the top toolbar to view MusicXML, MIDI, and PDF export options.", 1256 targetSelector: '[data-testid="export-menu-btn"]', 1257 triggerSelector: '[data-testid="export-menu-btn"]', 1258 successMessage: "Fantastic! You have mastered the Notation Editor workflow! ð" 1259 }, 1260 durationMs: 3e3, 1261 goalSnapshot: SIMPLE_GOAL_SNAPSHOT 1262 } 1263 ] 1264 } 1265]; 1266const translations = [ 1267 [/\btriplets\b/gi, "groups of three fast notes"], 1268 [/\bvocal lyrics alignment\b/gi, "matching syllables to notes"], 1269 [/\btime signature\b/gi, "beat pattern"], 1270 [/\bkey signature\b/gi, "sharps or flats for your song"], 1271 [/\bbarline\b/gi, "measure divider"], 1272 [/\bstaff\b/gi, "line group"], 1273 [/\bstaves\b/gi, "line groups"], 1274 [/\bledger line\b/gi, "extra line above or below the staff"], 1275 [/\bhumdrum\b/gi, "sheet music"], 1276 [/\bsoprano\b/gi, "Upper Line"], 1277 [/\bbass\b/gi, "low notes"], 1278 [/\bclef\b/gi, "music symbol"] 1279]; 1280function filterVocabulary(text, isSimpleMode) { 1281 if (!isSimpleMode || !text) return text; 1282 let filtered = text; 1283 for (const [pattern, replacement] of translations) { 1284 filtered = filtered.replace(pattern, replacement); 1285 } 1286 return filtered; 1287} 1288const typeIntoInput = (input, text) => { 1289 var _a; 1290 const nativeInputValueSetter = (_a = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")) == null ? void 0 : _a.set; 1291 if (nativeInputValueSetter) { 1292 nativeInputValueSetter.call(input, text); 1293 } else { 1294 input.value = text; 1295 } 1296 input.dispatchEvent(new Event("input", { bubbles: true })); 1297 input.dispatchEvent(new Event("change", { bubbles: true })); 1298}; 1299const normalizeHumdrum = (str) => { 1300 if (!str) return ""; 1301 return str.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("!")).map((line) => line.replace(/\s+/g, " ")).join("\n"); 1302}; 1303const useTutorialEngine = ({ 1304 setHumdrumContent, 1305 humdrumContent, 1306 editorRef, 1307 mode: propMode, 1308 playNotePreview, 1309 onExit, 1310 tutorialStarted, 1311 setTutorialStarted 1312}) => { 1313 console.log(`[TUTORIAL ENGINE] Initializing. tutorialStarted=${tutorialStarted}, mode=${propMode}`); 1314 const getSessionValue = (key, defaultVal) => { 1315 if (typeof window !== "undefined") { 1316 return sessionStorage.getItem(key) ?? defaultVal; 1317 } 1318 return defaultVal; 1319 }; 1320 const [lessonType, setLessonType] = reactExports.useState( 1321 () => getSessionValue("agy_tut_lesson_type", "advanced") 1322 ); 1323 const activeScript = lessonType === "simple" ? simpleChapters : tutorialChapters; 1324 const [currentChapterIndex, setCurrentChapterIndex] = reactExports.useState( 1325 () => parseInt(getSessionValue("agy_tut_chapter", "0"), 10) 1326 ); 1327 const [currentStepIndex, setCurrentStepIndex] = reactExports.useState( 1328 () => parseInt(getSessionValue("agy_tut_step", "0"), 10) 1329 ); 1330 const [isPlaying, setIsPlaying] = reactExports.useState(() => { 1331 const stored = getSessionValue("agy_tut_playing", ""); 1332 if (stored !== "") return stored === "true"; 1333 return false;
1334 }); 1335 const [speed, setSpeed] = reactExports.useState(() => { 1336 const stored = getSessionValue("agy_tut_speed", ""); 1337 if (stored !== "") return parseFloat(stored); 1338 return 1; 1339 }); 1340 const [useTts, setUseTts] = reactExports.useState( 1341 () => getSessionValue("agy_tut_tts", "true") === "true" 1342 // Voice not muted by default 1343 ); 1344 const [isCompleted, setIsCompleted] = reactExports.useState(false); 1345 const [autoPlayMode, setAutoPlayMode] = reactExports.useState(() => { 1346 const stored = getSessionValue("agy_tut_autoplay", ""); 1347 if (stored !== "") return stored; 1348 return "off"; 1349 }); 1350 const [stepByStepMode, setStepByStepMode] = reactExports.useState( 1351 () => getSessionValue("agy_tut_step_by_step", "false") === "true" 1352 ); 1353 const [mode, setModeInternal] = reactExports.useState(() => { 1354 const stored = getSessionValue("agy_tut_mode", ""); 1355 if (stored === "guided" || stored === "watch") return stored; 1356 if (propMode === "playback" || propMode === "watch") return "watch"; 1357 return "guided"; 1358 }); 1359 const [isShowMeActive, setIsShowMeActive] = reactExports.useState(false); 1360 const [isStuckHintVisible, setIsStuckHintVisible] = reactExports.useState(false); 1361 const setMode = reactExports.useCallback((newMode) => { 1362 setModeInternal(newMode); 1363 if (newMode === "watch") { 1364 setIsPlaying(true); 1365 } else { 1366 setIsPlaying(false); 1367 } 1368 setIsStuckHintVisible(false); 1369 }, []); 1370 const [isWaitingForInteraction, setIsWaitingForInteraction] = reactExports.useState(false); 1371 const [completedMilestones, setCompletedMilestones] = reactExports.useState(() => /* @__PURE__ */ new Set()); 1372 const [isSpeaking, setIsSpeaking] = reactExports.useState(false); 1373 const prevStartedRef = reactExports.useRef(tutorialStarted); 1374 reactExports.useEffect(() => { 1375 if (tutorialStarted && !prevStartedRef.current) { 1376 setCurrentChapterIndex(0); 1377 setCurrentStepIndex(0); 1378 setHumdrumContent(BLANK_SOLO_STAFF_HUMDRUM); 1379 } 1380 prevStartedRef.current = tutorialStarted; 1381 }, [tutorialStarted, setHumdrumContent]); 1382 reactExports.useEffect(() => { 1383 if (typeof window !== "undefined") { 1384 if (isCompleted) { 1385 sessionStorage.removeItem("agy_tut_chapter"); 1386 sessionStorage.removeItem("agy_tut_step"); 1387 sessionStorage.removeItem("agy_tut_playing"); 1388 sessionStorage.removeItem("agy_tut_speed"); 1389 sessionStorage.removeItem("agy_tut_tts"); 1390 sessionStorage.removeItem("agy_tut_autoplay"); 1391 sessionStorage.removeItem("agy_tut_started"); 1392 sessionStorage.removeItem("agy_tut_lesson_type"); 1393 sessionStorage.removeItem("agy_tut_step_by_step"); 1394 sessionStorage.removeItem("agy_tut_mode"); 1395 } else { 1396 sessionStorage.setItem("agy_tut_chapter", currentChapterIndex.toString()); 1397 sessionStorage.setItem("agy_tut_step", currentStepIndex.toString()); 1398 sessionStorage.setItem("agy_tut_playing", isPlaying.toString()); 1399 sessionStorage.setItem("agy_tut_speed", speed.toString()); 1400 sessionStorage.setItem("agy_tut_tts", useTts.toString()); 1401 sessionStorage.setItem("agy_tut_autoplay", autoPlayMode); 1402 sessionStorage.setItem("agy_tut_started", tutorialStarted.toString()); 1403 sessionStorage.setItem("agy_tut_lesson_type", lessonType); 1404 sessionStorage.setItem("agy_tut_step_by_step", stepByStepMode.toString()); 1405 sessionStorage.setItem("agy_tut_mode", mode); 1406 } 1407 } 1408 }, [ 1409 currentChapterIndex, 1410 currentStepIndex, 1411 isPlaying, 1412 speed, 1413 useTts, 1414 autoPlayMode, 1415 isCompleted, 1416 tutorialStarted, 1417 lessonType, 1418 stepByStepMode, 1419 mode 1420 ]); 1421 const handleExit = reactExports.useCallback(() => { 1422 if (typeof window !== "undefined") { 1423 sessionStorage.removeItem("agy_tut_chapter"); 1424 sessionStorage.removeItem("agy_tut_step"); 1425 sessionStorage.removeItem("agy_tut_playing"); 1426 sessionStorage.removeItem("agy_tut_speed"); 1427 sessionStorage.removeItem("agy_tut_tts"); 1428 sessionStorage.removeItem("agy_tut_autoplay"); 1429 sessionStorage.removeItem("agy_tut_started"); 1430 sessionStorage.removeItem("agy_tut_lesson_type"); 1431 sessionStorage.removeItem("agy_tut_step_by_step"); 1432 sessionStorage.removeItem("agy_tut_mode"); 1433 } 1434 if (onExit) onExit(); 1435 }, [onExit]); 1436 const playbackTimerRef = reactExports.useRef(null); 1437 const isPlayingRef = reactExports.useRef(isPlaying); 1438 isPlayingRef.current = isPlaying; 1439 const currentChapter = activeScript[currentChapterIndex] || activeScript[0]; 1440 const currentStep = currentChapter == null ? void 0 : currentChapter.steps[currentStepIndex]; 1441 const getStepInstruction = reactExports.useCallback((step) => { 1442 if (!step) return ""; 1443 const text = lessonType === "simple" && step.kidFriendlyInstruction ? step.kidFriendlyInstruction : step.instruction; 1444 return filterVocabulary(text, lessonType === "simple"); 1445 }, [lessonType]); 1446 const speakInstruction = reactExports.useCallback((text) => { 1447 if (!useTts || typeof window === "undefined" || !window.speechSynthesis) { 1448 setIsSpeaking(false); 1449 return; 1450 } 1451 try { 1452 window.speechSynthesis.cancel(); 1453 const filtered = filterVocabulary(text, lessonType === "simple"); 1454 const utterance = new SpeechSynthesisUtterance(filtered); 1455 utterance.rate = 1; 1456 utterance.onstart = () => setIsSpeaking(true); 1457 utterance.onend = () => setIsSpeaking(false); 1458 utterance.onerror = () => setIsSpeaking(false); 1459 window.speechSynthesis.speak(utterance); 1460 } catch (err) { 1461 console.error("Speech synthesis error:", err); 1462 setIsSpeaking(false); 1463 } 1464 }, [useTts, lessonType]); 1465 const lastExecutedStepRef = reactExports.useRef(null); 1466 const executeStepAction = reactExports.useCallback((step) => { 1467 var _a; 1468 if (!step) return; 1469 const stepKey = `${currentChapterIndex}-${currentStepIndex}`; 1470 console.log("[TUTORIAL ENGINE] executeStepAction executing stepKey:", stepKey, "actionType:", step.action.type, "selector:", step.action.selector || "none"); 1471 lastExecutedStepRef.current = stepKey; 1472 const act = step.action; 1473 const targetSel = act.selector || ((_a = step.cursorTarget) == null ? void 0 : _a.selector);
1474 if (targetSel) { 1475 try { 1476 const el = document.querySelector(targetSel); 1477 if (el) { 1478 el.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }); 1479 } 1480 } catch (e) { 1481 } 1482 } else { 1483 try { 1484 const el = document.querySelector(".selected-note"); 1485 if (el) { 1486 el.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }); 1487 } 1488 } catch (e) { 1489 } 1490 } 1491 switch (act.type) { 1492 case "set-humdrum": 1493 setHumdrumContent(act.content); 1494 requestAnimationFrame(() => { 1495 if (editorRef == null ? void 0 : editorRef.forceRender) { 1496 editorRef.forceRender(); 1497 } 1498 if (step.position && (editorRef == null ? void 0 : editorRef.setPlaybackPosition)) { 1499 editorRef.setPlaybackPosition(step.position); 1500 } 1501 }); 1502 break; 1503 case "click": { 1504 const attemptClick = (retriesLeft = 10) => { 1505 const el = document.querySelector(act.selector); 1506 if (el) { 1507 if (act.selector === '[data-testid="wizard-next-btn"]' || step.id === "simple-setup-fill" || step.id === "setup-next-1" || step.id === "setup-next-2") { 1508 const titleInput = document.querySelector('[data-testid="wizard-title-input"]'); 1509 const compInput = document.querySelector('[data-testid="wizard-composer-input"]'); 1510 if (titleInput && !titleInput.value) { 1511 typeIntoInput(titleInput, lessonType === "simple" ? "Mary Had a Little Lamb" : "Widmung"); 1512 } 1513 if (compInput && !compInput.value) { 1514 typeIntoInput(compInput, lessonType === "simple" ? "Traditional" : "Robert Schumann"); 1515 } 1516 setTimeout(() => { 1517 const btn = document.querySelector('[data-testid="wizard-next-btn"]') || el; 1518 if (btn) btn.click(); 1519 }, 100); 1520 return; 1521 } else if (act.selector === '[data-testid="wizard-title-input"]') { 1522 typeIntoInput(el, lessonType === "simple" ? "Mary Had a Little Lamb" : "Widmung"); 1523 } else if (act.selector === '[data-testid="wizard-composer-input"]') { 1524 typeIntoInput(el, lessonType === "simple" ? "Traditional" : "Robert Schumann"); 1525 } else if (act.selector === '[data-testid="wizard-key-Ab"]') { 1526 setTimeout(() => { 1527 const numInput = document.querySelector('[data-testid="wizard-time-num"]'); 1528 const denSelect = document.querySelector('[data-testid="wizard-time-den"]'); 1529 if (numInput) typeIntoInput(numInput, "3"); 1530 if (denSelect) { 1531 denSelect.value = "2"; 1532 denSelect.dispatchEvent(new Event("change", { bubbles: true })); 1533 } 1534 }, 100); 1535 } 1536 el.click(); 1537 } else if (retriesLeft > 0) { 1538 setTimeout(() => attemptClick(retriesLeft - 1), 100); 1539 } 1540 }; 1541 attemptClick(); 1542 break; 1543 } 1544 case "wizard-fill": 1545 const snap = step.goalSnapshot || currentChapter.goalSnapshot; 1546 setHumdrumContent(snap); 1547 if (editorRef == null ? void 0 : editorRef.closeWizard) { 1548 editorRef.closeWizard(); 1549 } 1550 if (editorRef == null ? void 0 : editorRef.setShowWelcome) { 1551 editorRef.setShowWelcome(false); 1552 } 1553 if (setTutorialStarted) { 1554 setTutorialStarted(true); 1555 } 1556 requestAnimationFrame(() => { 1557 if (editorRef == null ? void 0 : editorRef.forceRender) { 1558 editorRef.forceRender(); 1559 } 1560 if (step.position && (editorRef == null ? void 0 : editorRef.setPlaybackPosition)) { 1561 editorRef.setPlaybackPosition(step.position); 1562 } 1563 }); 1564 break; 1565 case "keypress": 1566 if (editorRef == null ? void 0 : editorRef.triggerKeyPress) { 1567 editorRef.triggerKeyPress(act.key, act); 1568 } 1569 break; 1570 } 1571 if (playNotePreview && step.instruction.toLowerCase().includes("note")) { 1572 playNotePreview([60]); 1573 } 1574 }, [setHumdrumContent, editorRef, playNotePreview, currentChapter, mode, currentChapterIndex, currentStepIndex, lessonType]); 1575 const handleNext = reactExports.useCallback(() => { 1576 if (isShowMeActive) { 1577 setIsShowMeActive(false); 1578 setMode("guided"); 1579 } 1580 if (currentStepIndex < currentChapter.steps.length - 1) { 1581 const nextStepIdx = currentStepIndex + 1; 1582 const nextStep = currentChapter.steps[nextStepIdx]; 1583 if (nextStep.interactionMilestone && mode !== "watch") { 1584 setIsWaitingForInteraction(true); 1585 setIsPlaying(false); 1586 setCurrentStepIndex(nextStepIdx); 1587 speakInstruction(getStepInstruction(nextStep)); 1588 return; 1589 } 1590 setCurrentStepIndex(nextStepIdx); 1591 if (typeof window !== "undefined") { 1592 sessionStorage.setItem("agy_tut_chapter", currentChapterIndex.toString()); 1593 sessionStorage.setItem("agy_tut_step", nextStepIdx.toString()); 1594 } 1595 speakInstruction(getStepInstruction(nextStep)); 1596 } else if (currentChapterIndex < activeScript.length - 1) { 1597 if (autoPlayMode === "chapter" && mode !== "watch") { 1598 setIsPlaying(false); 1599 speakInstruction("Chapter completed. Click next or play to continue."); 1600 return; 1601 } 1602 const nextChapterIdx = currentChapterIndex + 1; 1603 const nextStep = activeScript[nextChapterIdx].steps[0]; 1604 if (nextStep.interactionMilestone && mode !== "watch") { 1605 setIsWaitingForInteraction(true); 1606 setIsPlaying(false); 1607 setCurrentChapterIndex(nextChapterIdx); 1608 setCurrentStepIndex(0); 1609 setHumdrumContent(currentChapter.goalSnapshot); 1610 if (typeof window !== "undefined") { 1611 sessionStorage.setItem("agy_tut_chapter", nextChapterIdx.toString()); 1612 sessionStorage.setItem("agy_tut_step", "0"); 1613 } 1614 speakInstruction(getStepInstruction(nextStep)); 1615 return; 1616 } 1617 setCurrentChapterIndex(nextChapterIdx); 1618 setCurrentStepIndex(0); 1619 if (typeof window !== "undefined") { 1620 sessionStorage.setItem("agy_tut_chapter", nextChapterIdx.toString()); 1621 sessionStorage.setItem("agy_tut_step", "0"); 1622 } 1623 setHumdrumContent(currentChapter.goalSnapshot); 1624 speakInstruction(getStepInstruction(nextStep)); 1625 } else { 1626 setIsPlaying(false); 1627 setIsCompleted(true); 1628 speakInstruction(lessonType === "simple" ? "Congratulations! You completed your song." : "Congratulations! You have completed the Schumann Wi
1628dmung tutorial."); 1629 } 1630 }, [ 1631 currentChapterIndex, 1632 currentStepIndex, 1633 currentChapter, 1634 speakInstruction, 1635 setHumdrumContent, 1636 autoPlayMode, 1637 activeScript, 1638 getStepInstruction, 1639 lessonType, 1640 isShowMeActive, 1641 setMode, 1642 mode 1643 ]); 1644 const handlePrev = reactExports.useCallback(() => { 1645 setIsCompleted(false); 1646 setIsWaitingForInteraction(false); 1647 if (currentStepIndex > 0) { 1648 const prevStepIdx = currentStepIndex - 1; 1649 setCurrentStepIndex(prevStepIdx); 1650 const snap = prevStepIdx === 0 ? currentChapterIndex === 0 ? BLANK_SOLO_STAFF_HUMDRUM : activeScript[currentChapterIndex - 1].goalSnapshot : currentChapter.steps[prevStepIdx - 1].goalSnapshot || currentChapter.goalSnapshot; 1651 if (snap) setHumdrumContent(snap); 1652 speakInstruction(getStepInstruction(currentChapter.steps[prevStepIdx])); 1653 } else if (currentChapterIndex > 0) { 1654 const prevChapterIdx = currentChapterIndex - 1; 1655 const prevChapter = activeScript[prevChapterIdx]; 1656 setCurrentChapterIndex(prevChapterIdx); 1657 const lastStepIdx = prevChapter.steps.length - 1; 1658 setCurrentStepIndex(lastStepIdx); 1659 const snap = lastStepIdx === 0 ? prevChapterIdx === 0 ? BLANK_SOLO_STAFF_HUMDRUM : activeScript[prevChapterIdx - 1].goalSnapshot : prevChapter.steps[lastStepIdx - 1].goalSnapshot || prevChapter.goalSnapshot; 1660 if (snap) setHumdrumContent(snap); 1661 speakInstruction(getStepInstruction(prevChapter.steps[lastStepIdx])); 1662 } 1663 }, [currentChapterIndex, currentStepIndex, currentChapter, speakInstruction, setHumdrumContent, activeScript, getStepInstruction]); 1664 const jumpToChapter = reactExports.useCallback((chapterIndex) => { 1665 setIsCompleted(false); 1666 setIsWaitingForInteraction(false); 1667 setCurrentChapterIndex(chapterIndex); 1668 setCurrentStepIndex(0); 1669 const snap = chapterIndex === 0 ? BLANK_SOLO_STAFF_HUMDRUM : activeScript[chapterIndex - 1].goalSnapshot; 1670 setHumdrumContent(snap); 1671 const targetStep = activeScript[chapterIndex].steps[0]; 1672 if (targetStep.interactionMilestone) { 1673 setIsWaitingForInteraction(true); 1674 setIsPlaying(false); 1675 } 1676 speakInstruction(getStepInstruction(targetStep)); 1677 }, [speakInstruction, setHumdrumContent, activeScript, getStepInstruction]); 1678 const getGlobalStepCount = () => { 1679 return activeScript.reduce((acc, chap) => acc + chap.steps.length, 0); 1680 }; 1681 const getGlobalStepIndex = () => { 1682 let index = 0; 1683 for (let i = 0; i < currentChapterIndex; i++) { 1684 index += activeScript[i].steps.length; 1685 } 1686 return index + currentStepIndex; 1687 }; 1688 const jumpToGlobalStep = reactExports.useCallback((globalIndex) => { 1689 setIsCompleted(false); 1690 setIsWaitingForInteraction(false); 1691 let accumulated = 0; 1692 for (let c = 0; c < activeScript.length; c++) { 1693 const stepsInChapter = activeScript[c].steps.length; 1694 if (globalIndex < accumulated + stepsInChapter) { 1695 const stepIdx = globalIndex - accumulated; 1696 setCurrentChapterIndex(c); 1697 setCurrentStepIndex(stepIdx); 1698 const prevSnap = c === 0 && stepIdx === 0 ? BLANK_SOLO_STAFF_HUMDRUM : stepIdx === 0 ? activeScript[c - 1].goalSnapshot : activeScript[c].goalSnapshot; 1699 if (prevSnap) { 1700 setHumdrumContent(prevSnap); 1701 } 1702 const targetStep = activeScript[c].steps[stepIdx]; 1703 if (targetStep.interactionMilestone) { 1704 setIsWaitingForInteraction(true); 1705 setIsPlaying(false); 1706 } 1707 speakInstruction(getStepInstruction(targetStep)); 1708 break; 1709 } 1710 accumulated += stepsInChapter; 1711 } 1712 }, [speakInstruction, setHumdrumContent, activeScript, getStepInstruction]); 1713 const handleNextRef = reactExports.useRef(handleNext); 1714 reactExports.useEffect(() => { 1715 handleNextRef.current = handleNext; 1716 }, [handleNext]); 1717 const executeStepActionRef = reactExports.useRef(executeStepAction); 1718 reactExports.useEffect(() => { 1719 executeStepActionRef.current = executeStepAction; 1720 }, [executeStepAction]); 1721 const scrollTargetIntoView = reactExports.useCallback((selector) => { 1722 try { 1723 const el = document.querySelector(selector); 1724 if (el) { 1725 el.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }); 1726 } 1727 } catch (err) { 1728 console.warn("[TUTORIAL ENGINE] Failed to scroll target into view:", selector, err); 1729 } 1730 }, []); 1731 reactExports.useEffect(() => { 1732 if (!tutorialStarted || isWaitingForInteraction) return; 1733 const handleKeyDown = (e) => { 1734 var _a, _b; 1735 if (((_a = document.activeElement) == null ? void 0 : _a.tagName) === "INPUT" || ((_b = document.activeElement) == null ? void 0 : _b.tagName) === "TEXTAREA") { 1736 return; 1737 } 1738 if (e.key === " ") { 1739 e.preventDefault(); 1740 setIsPlaying((prev) => !prev); 1741 } else if (e.key === "ArrowLeft") { 1742 e.preventDefault(); 1743 handlePrev(); 1744 } else if (e.key === "ArrowRight") { 1745 e.preventDefault(); 1746 handleNext(); 1747 } 1748 }; 1749 window.addEventListener("keydown", handleKeyDown); 1750 return () => { 1751 window.removeEventListener("keydown", handleKeyDown); 1752 }; 1753 }, [tutorialStarted, isWaitingForInteraction, handleNext, handlePrev]); 1754 reactExports.useEffect(() => { 1755 if (playbackTimerRef.current) { 1756 clearTimeout(playbackTimerRef.current); 1757 playbackTimerRef.current = null; 1758 } 1759 if (tutorialStarted && isPlaying && currentStep && mode === "watch" && !isWaitingForInteraction && !stepByStepMode) { 1760 if (stepByStepMode && (currentStep.detailHtml || currentStep.durationMs > 3e3) && lastExecutedStepRef.current === `${currentChapterIndex}-${currentStepIndex}`) { 1761 setIsPlaying(false); 1762 return; 1763 }
1764 const delay = (currentStep.durationMs || 2e3) / speed; 1765 playbackTimerRef.current = setTimeout(() => { 1766 handleNextRef.current(); 1767 }, delay); 1768 } 1769 return () => { 1770 if (playbackTimerRef.current) { 1771 clearTimeout(playbackTimerRef.current); 1772 } 1773 }; 1774 }, [isPlaying, currentChapterIndex, currentStepIndex, speed, mode, tutorialStarted, isWaitingForInteraction, stepByStepMode, currentStep]); 1775 reactExports.useEffect(() => { 1776 if (tutorialStarted && isPlaying && currentStep && mode === "watch") { 1777 const stepKey = `${currentChapterIndex}-${currentStepIndex}`; 1778 console.log("[TUTORIAL ENGINE] playing useEffect fired. stepKey:", stepKey, "lastExecuted:", lastExecutedStepRef.current); 1779 if (lastExecutedStepRef.current !== stepKey) { 1780 console.log("[TUTORIAL ENGINE] stepKey does not match lastExecuted. Executing action..."); 1781 lastExecutedStepRef.current = stepKey; 1782 executeStepActionRef.current(currentStep); 1783 } 1784 } else if (!isPlaying) { 1785 console.log("[TUTORIAL ENGINE] paused. Clearing lastExecutedStepRef"); 1786 lastExecutedStepRef.current = null; 1787 } 1788 }, [isPlaying, currentStepIndex, currentChapterIndex, mode, tutorialStarted, currentStep]); 1789 reactExports.useEffect(() => { 1790 var _a, _b; 1791 if (tutorialStarted && currentStep) { 1792 speakInstruction(getStepInstruction(currentStep)); 1793 const activeMeasureIndex = ((_a = currentStep.position) == null ? void 0 : _a.measure) || 1; 1794 if (editorRef == null ? void 0 : editorRef.scrollToMeasure) { 1795 editorRef.scrollToMeasure(activeMeasureIndex, { behavior: "smooth", align: "center" }); 1796 } 1797 const targetSel = currentStep.action.selector || ((_b = currentStep.cursorTarget) == null ? void 0 : _b.selector); 1798 if (targetSel) { 1799 setTimeout(() => { 1800 scrollTargetIntoView(targetSel); 1801 }, 150); 1802 } else { 1803 setTimeout(() => { 1804 scrollTargetIntoView(".selected-note"); 1805 }, 150); 1806 } 1807 } 1808 }, [currentChapterIndex, currentStepIndex, speakInstruction, tutorialStarted, currentStep, scrollTargetIntoView, getStepInstruction, editorRef]); 1809 reactExports.useEffect(() => { 1810 return () => { 1811 if (typeof window !== "undefined" && window.speechSynthesis) { 1812 try { 1813 window.speechSynthesis.cancel(); 1814 } catch (e) { 1815 console.warn("Speech synthesis cleanup warning:", e); 1816 } 1817 } 1818 }; 1819 }, []); 1820 reactExports.useEffect(() => { 1821 console.log(`[TUTORIAL ENGINE] isWaitingForInteraction: target=${(currentStep == null ? void 0 : currentStep.interactionMilestone) ? "yes" : "no"}`); 1822 if (tutorialStarted && mode === "guided" && (currentStep == null ? void 0 : currentStep.interactionMilestone)) { 1823 setIsWaitingForInteraction(true); 1824 } else { 1825 setIsWaitingForInteraction(false); 1826 } 1827 }, [tutorialStarted, mode, currentStep, currentStepIndex, currentChapterIndex]); 1828 reactExports.useEffect(() => { 1829 if (!tutorialStarted || isCompleted) { 1830 setIsStuckHintVisible(false); 1831 return; 1832 } 1833 setIsStuckHintVisible(true); 1834 }, [tutorialStarted, isCompleted]); 1835 const showMeThisStep = reactExports.useCallback(() => { 1836 setIsShowMeActive(true); 1837 if (currentStep) { 1838 executeStepActionRef.current(currentStep); 1839 setTimeout(() => { 1840 setIsShowMeActive(false); 1841 setModeInternal("guided"); 1842 handleNext(); 1843 }, 300); 1844 } else { 1845 setModeInternal("watch"); 1846 setIsPlaying(true); 1847 } 1848 }, [currentStep, handleNext]); 1849 const skipStep = reactExports.useCallback(() => { 1850 if (currentStep) { 1851 executeStepActionRef.current(currentStep); 1852 } 1853 handleNext(); 1854 }, [currentStep, handleNext]); 1855 reactExports.useEffect(() => { 1856 if (!tutorialStarted || isCompleted || mode !== "guided" || !(currentStep == null ? void 0 : currentStep.validation)) { 1857 return; 1858 } 1859 const validation = currentStep.validation; 1860 const interval = setInterval(() => { 1861 let isValSatisfied = false; 1862 if (validation.type === "element-exists") { 1863 isValSatisfied = document.querySelector(validation.value) !== null; 1864 } else if (validation.type === "wizard-completed") { 1865 isValSatisfied = !document.querySelector(".setup-wizard-modal"); 1866 } 1867 if (isValSatisfied) { 1868 clearInterval(interval); 1869 console.log(`[TUTORIAL ENGINE] Validation satisfied for step ${currentStep.id}, advancing.`); 1870 handleNext(); 1871 } 1872 }, 250); 1873 return () => clearInterval(interval); 1874 }, [tutorialStarted, isCompleted, mode, currentStep, handleNext]); 1875 reactExports.useEffect(() => { 1876 if (!tutorialStarted || isCompleted || mode !== "guided" || isWaitingForInteraction || (currentStep == null ? void 0 : currentStep.interactionMilestone)) { 1877 return; 1878 } 1879 const act = currentStep == null ? void 0 : currentStep.action; 1880 if ((act == null ? void 0 : act.type) !== "click" || !act.selector) { 1881 return; 1882 } 1883 const handleGlobalClick = (e) => { 1884 const targetEl = document.querySelector(act.selector); 1885 if (targetEl && (targetEl === e.target || targetEl.contains(e.target))) { 1886 setTimeout(() => { 1887 handleNext(); 1888 }, 100); 1889 } 1890 }; 1891 document.addEventListener("click", handleGlobalClick, true); 1892 return () => { 1893 document.removeEventListener("click", handleGlobalClick, true); 1894 }; 1895 }, [tutorialStarted, isCompleted, mode, isWaitingForInteraction, currentStep, handleNext]); 1896 reactExports.useEffect(() => { 1897 if (!tutorialStarted || isCompleted || mode !== "guided" || isWaitingForInteraction || !currentStep || !humdrumContent) { 1898 return; 1899 } 1900 if (currentStepIndex === 0 && currentChapterIndex === 0) { 1901 return; 1902 } 1903 if (currentStep.action.type === "click" || currentStep.action.type === "wait") { 1904 return; 1905 } 1906 const targetSnapshot = currentStep.goalSnapshot || (currentStep.action.type === "set-humdrum" ? currentStep.action.content : void 0); 1907 if (!targetSnapshot) { 1908 return; 1909 } 1910 const normalizedCurrent = normalizeHumdrum(humdrumContent);
1911 const normalizedTarget = normalizeHumdrum(targetSnapshot); 1912 if (normalizedCurrent === normalizedTarget) { 1913 handleNext(); 1914 } 1915 }, [tutorialStarted, isCompleted, mode, isWaitingForInteraction, currentStep, currentStepIndex, currentChapterIndex, humdrumContent, handleNext]); 1916 return { 1917 currentChapterIndex, 1918 currentStepIndex, 1919 currentChapter, 1920 currentStep, 1921 isPlaying, 1922 setIsPlaying, 1923 speed, 1924 setSpeed, 1925 useTts, 1926 setUseTts, 1927 isCompleted, 1928 handleNext, 1929 handlePrev, 1930 jumpToChapter, 1931 jumpToGlobalStep, 1932 getGlobalStepIndex, 1933 getGlobalStepCount, 1934 autoPlayMode, 1935 setAutoPlayMode, 1936 tutorialStarted, 1937 setTutorialStarted, 1938 onExit: handleExit, 1939 lessonType, 1940 setLessonType, 1941 stepByStepMode, 1942 setStepByStepMode, 1943 isWaitingForInteraction, 1944 setIsWaitingForInteraction, 1945 completedMilestones, 1946 mode, 1947 setMode, 1948 isStuckHintVisible, 1949 setIsStuckHintVisible, 1950 showMeThisStep, 1951 skipStep, 1952 isShowMeActive, 1953 isSpeaking 1954 }; 1955}; 1956const TutorialPanel = ({ 1957 currentChapterIndex, 1958 currentStepIndex, 1959 isPlaying, 1960 setIsPlaying, 1961 speed, 1962 setSpeed, 1963 useTts, 1964 setUseTts, 1965 isCompleted, 1966 handleNext, 1967 handlePrev, 1968 jumpToChapter, 1969 jumpToGlobalStep, 1970 getGlobalStepIndex, 1971 getGlobalStepCount, 1972 onExit, 1973 theme = "dark", 1974 autoPlayMode, 1975 setAutoPlayMode, 1976 mode, 1977 setMode, 1978 isStuckHintVisible, 1979 showMeThisStep, 1980 skipStep, 1981 tutorialStarted, 1982 setTutorialStarted, 1983 lessonType, 1984 setLessonType, 1985 stepByStepMode, 1986 setStepByStepMode, 1987 completedMilestones, 1988 isSpeaking = false 1989}) => { 1990 const [activeTab, setActiveTab] = reactExports.useState("steps"); 1991 const [isCollapsed, setIsCollapsed] = reactExports.useState(false); 1992 const [showAdvanced, setShowAdvanced] = reactExports.useState(false); 1993 const [isAnimatingToSide, setIsAnimatingToSide] = reactExports.useState(false); 1994 const [isPlaybackScrubberExpanded, setIsPlaybackScrubberExpanded] = reactExports.useState(false); 1995 const getStepInstruction = (step) => { 1996 if (!step) return ""; 1997 const text = lessonType === "simple" && step.kidFriendlyInstruction ? step.kidFriendlyInstruction : step.instruction; 1998 return filterVocabulary(text, lessonType === "simple"); 1999 }; 2000 const startLesson = (type) => { 2001 setLessonType(type); 2002 setIsAnimatingToSide(true); 2003 setTimeout(() => { 2004 setTutorialStarted(true); 2005 setIsAnimatingToSide(false); 2006 }, 700); 2007 }; 2008 const activeScript = lessonType === "simple" ? simpleChapters : tutorialChapters; 2009 const currentChapter = activeScript[currentChapterIndex] || activeScript[0]; 2010 const currentStep = currentChapter == null ? void 0 : currentChapter.steps[currentStepIndex]; 2011 const totalSteps = getGlobalStepCount(); 2012 const globalStepIdx = getGlobalStepIndex(); 2013 const progressPercent = totalSteps > 1 ? globalStepIdx / (totalSteps - 1) * 100 : 0; 2014 const getChapterTicks = () => { 2015 let acc = 0; 2016 return activeScript.map((ch, idx) => { 2017 const pos = totalSteps > 1 ? acc / (totalSteps - 1) * 100 : 0; 2018 acc += ch.steps.length; 2019 return { id: ch.id, title: ch.title, pos }; 2020 }); 2021 }; 2022 const ticks = getChapterTicks(); 2023 if (!tutorialStarted || isAnimatingToSide) { 2024 return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2025 /* @__PURE__ */ jsxRuntimeExports.jsx( 2026 "div", 2027 { 2028 className: `fixed inset-0 bg-black/75 backdrop-blur-[4px] z-[190] transition-opacity duration-700 ${isAnimatingToSide ? "opacity-0 pointer-events-none" : "opacity-100"}` 2029 } 2030 ), 2031 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2032 "div", 2033 { 2034 className: isAnimatingToSide ? "fixed right-0 top-0 bottom-0 h-screen w-80 md:w-96 bg-[var(--daw-elevated)] border-l border-[var(--daw-border-strong)] shadow-2xl z-[200] transition-all duration-700 ease-in-out flex flex-col overflow-hidden" : "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[450px] max-w-[95vw] bg-[var(--daw-elevated)] border border-[var(--daw-border-strong)] rounded-2xl shadow-2xl p-6 z-[200] transition-all duration-700 ease-in-out flex flex-col space-y-4", 2035 children: [ 2036 !isAnimatingToSide && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center pb-2.5 border-b border-[var(--daw-border-strong)]", children: [
2037 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm font-bold text-[var(--daw-text)] flex items-center gap-2", children: [ 2038 /* @__PURE__ */ jsxRuntimeExports.jsx(BookOpen, { className: "w-4 h-4 text-blue-500" }), 2039 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Notation Editor Tutorial" }) 2040 ] }), 2041 /* @__PURE__ */ jsxRuntimeExports.jsx( 2042 "button", 2043 { 2044 onClick: onExit, 2045 className: "text-xs font-semibold px-2.5 py-1 rounded bg-[var(--tutorial-exit-bg)] border border-[var(--tutorial-exit-border)] hover:bg-[var(--tutorial-exit-hover-bg)] text-[var(--tutorial-exit-text)] transition cursor-pointer", 2046 children: "Exit" 2047 } 2048 ) 2049 ] }), 2050 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `transition-opacity duration-300 flex-1 flex flex-col justify-center ${isAnimatingToSide ? "opacity-0" : "opacity-100"}`, children: [
2051 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-extrabold text-lg text-[var(--daw-text)] text-center tracking-wide mt-2", children: "ð Choose Your Lesson" }), 2052 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-[var(--daw-text-secondary)] text-center leading-relaxed max-w-xs mx-auto mt-2 mb-6", children: "Start with the basics or jump into an advanced masterwork. You can switch between practicing (Guided) and watching (Watch) at any time." }), 2053 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [ 2054 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2055 "button", 2056 { 2057 onClick: () => startLesson("simple"), 2058 className: "w-full text-left p-4 rounded-xl border border-[var(--tutorial-simple-border)] bg-[var(--tutorial-simple-bg)] hover:bg-[var(--tutorial-simple-hover-bg)] transition cursor-pointer flex flex-col justify-between space-y-2 group", 2059 "data-testid": "choose-simple-lesson-btn", 2060 children: [
2061 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 2062 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-bold text-[var(--tutorial-simple-text)] uppercase tracking-widest flex items-center gap-1", children: [ 2063 /* @__PURE__ */ jsxRuntimeExports.jsx(Star, { className: "w-3.5 h-3.5 fill-current text-[var(--tutorial-simple-text)]" }), 2064 " Simple Lesson" 2065 ] }), 2066 /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRight, { className: "w-4 h-4 text-[var(--tutorial-simple-text)] transition transform group-hover:translate-x-1" }) 2067 ] }), 2068 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-extrabold text-sm text-[var(--daw-text)]", children: "Mary Had a Little Lamb" }), 2069 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-[var(--daw-text-secondary)] leading-normal", children: "A beginner-friendly C Major introduction to building simple tunes. Replaces technical musical jargon with easy definitions and challenges." }) 2070 ] 2071 } 2072 ),
2073 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2074 "button", 2075 { 2076 onClick: () => startLesson("advanced"), 2077 className: "w-full text-left p-4 rounded-xl border border-[var(--tutorial-advanced-border)] bg-[var(--tutorial-advanced-bg)] hover:bg-[var(--tutorial-advanced-hover-bg)] transition cursor-pointer flex flex-col justify-between space-y-2 group", 2078 "data-testid": "choose-advanced-lesson-btn", 2079 children: [ 2080 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 2081 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-bold text-[var(--tutorial-advanced-text)] uppercase tracking-widest flex items-center gap-1", children: [ 2082 /* @__PURE__ */ jsxRuntimeExports.jsx(Trophy, { className: "w-3.5 h-3.5" }), 2083 " Classic / Advanced Mode" 2084 ] }),
2085 /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRight, { className: "w-4 h-4 text-[var(--tutorial-advanced-text)] transition transform group-hover:translate-x-1" }) 2086 ] }), 2087 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-extrabold text-sm text-[var(--daw-text)]", children: "Schumann's Widmung" }), 2088 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-[var(--daw-text-secondary)] leading-normal", children: "Full 228-step transcription workflow of Robert Schumann's love song. Learn key modulation, ties, indents, and full engraving details." }) 2089 ] 2090 } 2091 ) 2092 ] }) 2093 ] }) 2094 ] 2095 } 2096 ) 2097 ] }); 2098 } 2099 return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 2100 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2101 "div", 2102 { 2103 className: `relative h-full z-[150] flex transition-all duration-300 border-l border-[var(--daw-border-strong)] ${isCollapsed ? "w-10" : "w-80 md:w-96"}`, 2104 children: [
2105 /* @__PURE__ */ jsxRuntimeExports.jsx( 2106 "button", 2107 { 2108 onClick: () => setIsCollapsed(!isCollapsed), 2109 className: "w-10 bg-[var(--daw-surface)] border-l border-[var(--daw-border-strong)] flex items-center justify-center text-[var(--daw-text-secondary)] hover:text-[var(--daw-text)] cursor-pointer h-16 self-center rounded-l-xl -ml-px shadow-2xl", 2110 children: isCollapsed ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronLeft, { className: "w-5 h-5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "w-5 h-5" }) 2111 } 2112 ), 2113 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2114 "div", 2115 { 2116 className: `flex-1 flex flex-col min-h-0 border-l border-[var(--daw-border-strong)] shadow-2xl ${isCollapsed ? "hidden" : "flex"}`, 2117 style: { backgroundColor: "var(--daw-bg)", color: "var(--daw-text)" }, 2118 children: [ 2119 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-b border-[var(--daw-border-strong)] flex items-center justify-between", style: { backgroundColor: "var(--daw-header)" }, children: [
2120 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-2.5", children: [ 2121 /* @__PURE__ */ jsxRuntimeExports.jsx(BookOpen, { className: "w-5 h-5 text-blue-500" }), 2122 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-sm tracking-wide text-[var(--daw-text)] uppercase", children: lessonType === "simple" ? "ð Simple Lesson" : "Guided Tutorial" }) 2123 ] }), 2124 /* @__PURE__ */ jsxRuntimeExports.jsx( 2125 "button", 2126 { 2127 onClick: onExit, 2128 className: "text-xs font-semibold px-2.5 py-1 rounded bg-[var(--tutorial-exit-bg,theme(colors.red.600))] border border-[var(--tutorial-exit-border,theme(colors.red.700))] hover:bg-[var(--tutorial-exit-hover-bg,theme(colors.red.500))] text-white transition cursor-pointer", 2129 children: "Exit" 2130 } 2131 ) 2132 ] }),
2133 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex p-1 border-b border-[var(--daw-border-strong)]", style: { backgroundColor: "var(--daw-elevated)" }, children: [ 2134 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2135 "button", 2136 { 2137 onClick: () => setActiveTab("steps"), 2138 className: `flex-1 py-2 text-xs font-bold rounded-lg transition-all flex items-center justify-center space-x-2 ${activeTab === "steps" ? "bg-[var(--daw-surface)] text-[var(--daw-text)] shadow-md font-extrabold border border-[var(--daw-border)]" : "text-[var(--daw-text-secondary)] hover:text-[var(--daw-text)]"}`, 2139 children: [ 2140 /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { className: "w-3.5 h-3.5" }), 2141 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Steps" }) 2142 ] 2143 } 2144 ), 2145 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2146 "button", 2147 { 2148 onClick: () => setActiveTab("original"), 2149 className: `flex-1 py-2 text-xs font-bold rounded-lg transition-all flex items-center justify-center space-x-2 ${activeTab === "original" ? "bg-[var(--daw-surface)] text-[var(--daw-text)] shadow-md font-extrabold border border-[var(--daw-border)]" : "text-[var(--daw-text-secondary)] hover:text-[var(--daw-text)]"}`, 2150 children: [
2151 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { className: "w-3.5 h-3.5" }), 2152 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: lessonType === "simple" ? "Helper Guide" : "Original Score" }) 2153 ] 2154 } 2155 ) 2156 ] }), 2157 activeTab === "steps" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-h-0 justify-between overflow-hidden", children: [ 2158 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto p-5 space-y-5", children: [ 2159 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2160 "div", 2161 { 2162 className: "p-4 rounded-xl border relative overflow-hidden", 2163 style: { 2164 backgroundColor: "var(--daw-surface)", 2165 borderColor: lessonType === "simple" ? "#10b98125" : "var(--daw-border-strong)" 2166 }, 2167 children: [ 2168 lessonType === "simple" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-2 top-2 text-emerald-500/25 rotate-12", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Star, { className: "w-12 h-12 fill-current" }) }),
2169 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: `text-[10px] font-bold uppercase tracking-widest block mb-1 ${lessonType === "simple" ? "text-emerald-600 dark:text-emerald-400" : "text-blue-600 dark:text-blue-400"}`, children: [ 2170 "Chapter ", 2171 currentChapterIndex + 1, 2172 " of ", 2173 activeScript.length 2174 ] }), 2175 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-extrabold text-base text-[var(--daw-text)] mb-1", children: currentChapter.title }), 2176 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-[var(--daw-text-secondary)] font-medium leading-relaxed", children: lessonType === "simple" ? filterVocabulary(currentChapter.description, true) : currentChapter.description }), 2177 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3.5 pt-3 border-t border-[var(--daw-border)] flex justify-between items-center text-[10px] text-[var(--daw-text-secondary)] font-bold uppercase", children: [
2178 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Skill Focus:" }), 2179 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[var(--daw-text)]", children: lessonType === "simple" ? filterVocabulary(currentChapter.skillLearned, true) : currentChapter.skillLearned }) 2180 ] }) 2181 ] 2182 } 2183 ), 2184 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2185 "div", 2186 { 2187 className: `p-4 rounded-xl border relative shadow-2xl transition-all duration-300 ${isSpeaking ? lessonType === "simple" ? theme === "light" || theme === "retro" ? "bg-emerald-50 border-emerald-500 ring-2 ring-emerald-500/20 scale-[1.02] shadow-[0_0_15px_rgba(16,185,129,0.2)] text-emerald-950" : "bg-emerald-950/25 border-emerald-450 ring-2 ring-emerald-500/20 scale-[1.02] shadow-[0_0_15px_rgba(16,185,129,0.3)] text-zinc-100" : theme === "light" || theme === "retro" ? "bg-indigo-50 border-indigo-500 ring-2 ring-indigo-500/20 scale-[1.02] shadow-[0_0_15px_rgba(99,102,241,0.2)] text-indigo-950" : "bg-[#1e1a38] border-indigo-400 ring-2 ring-indigo-500/20 scale-[1.02] shadow-[0_0_15px_rgba(99,102,241,0.3)] text-zinc-100" : lessonType === "simple" ? theme === "light" || theme === "retro" ? "bg-emerald-50/50 border-emerald-200 text-emerald-950" : "bg-emerald-950/15 border-emerald-500/30 text-zinc-100" : theme === "light" || theme === "retro" ? "bg-indigo-50/50 border-indigo-200 text-indigo-950" : "bg-[#161427] border-[#3b327b] text-zinc-100"}`, 2188 children: [
2189 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[9px] font-bold uppercase tracking-wider block mb-2 ${lessonType === "simple" ? "text-emerald-600 dark:text-emerald-400" : "text-indigo-600 dark:text-indigo-400"}`, children: isCompleted ? "Lesson Complete!" : `Step ${currentStepIndex + 1} of ${currentChapter.steps.length}` }), 2190 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs leading-relaxed font-bold text-[var(--daw-text)]", children: isCompleted ? lessonType === "simple" ? "Amazing! You finished Mary Had a Little Lamb! ð" : "Congratulations! You have completed the Schumann Widmung tutorial." : lessonType === "simple" && (currentStep == null ? void 0 : currentStep.kidFriendlyInstruction) ? filterVocabulary(currentStep.kidFriendlyInstruction, true) : currentStep == null ? void 0 : currentStep.instruction }), 2191 isCompleted && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 2192 "button", 2193 { 2194 onClick: onExit, 2195 className: "w-full py-2.5 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg text-xs font-bold transition shadow-md shadow-emerald-900/30 cursor-pointer text-center", 2196 children: "Finish & Start Editing" 2197 } 2198 ) }), 2199 !isCompleted && (currentStep == null ? void 0 : currentStep.detailHtml) && /* @__P
2199URE__ */ jsxRuntimeExports.jsx( 2200 "div", 2201 { 2202 className: "mt-3 text-[var(--daw-text-secondary)] text-[11px] leading-relaxed", 2203 dangerouslySetInnerHTML: { 2204 __html: lessonType === "simple" ? filterVocabulary(currentStep.detailHtml, true) : currentStep.detailHtml 2205 } 2206 } 2207 ) 2208 ] 2209 } 2210 ), 2211 !isCompleted && isStuckHintVisible && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `p-3.5 rounded-xl border flex flex-col space-y-2 animate-fade-in ${theme === "light" || theme === "retro" ? "bg-blue-50 border-blue-200" : "bg-blue-950/10 border-blue-500/20"}`, children: [ 2212 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start space-x-2", children: [ 2213 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { className: "w-4 h-4 text-blue-400 shrink-0 mt-0.5" }),
2214 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [ 2215 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-blue-400 uppercase tracking-wider block mb-0.5", children: mode === "watch" ? "Watching Step" : "Stuck?" }), 2216 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[var(--daw-text)] text-xs font-semibold leading-normal", children: mode === "watch" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 2217 "Watching this step... Click ", 2218 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-[var(--daw-text-secondary)] font-bold", children: "Skip" }), 2219 " to proceed, or switch to ", 2220 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-blue-500 font-bold", children: "Guided Mode" }), 2221 "." 2222 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 2223 "Click ",
2224 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-blue-500 font-bold", children: "Show Me" }), 2225 " to watch this step, or ", 2226 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-[var(--daw-text-secondary)] font-bold", children: "Skip" }), 2227 " to proceed." 2228 ] }) }) 2229 ] }) 2230 ] }), 2231 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex space-x-2 pt-1", children: [ 2232 mode === "watch" ? /* @__PURE__ */ jsxRuntimeExports.jsxs( 2233 "button", 2234 { 2235 onClick: () => setMode("guided"), 2236 className: "px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-[10px] font-bold transition cursor-pointer flex items-center gap-1.5 shadow-md shadow-blue-900/25 active:scale-95", 2237 children: [
2238 /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "w-3 h-3 fill-current" }), 2239 " Guided Mode" 2240 ] 2241 } 2242 ) : /* @__PURE__ */ jsxRuntimeExports.jsxs( 2243 "button", 2244 { 2245 onClick: showMeThisStep, 2246 className: "px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-[10px] font-bold transition cursor-pointer flex items-center gap-1.5 shadow-md shadow-blue-900/25 active:scale-95", 2247 children: [ 2248 /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "w-3 h-3 fill-current" }), 2249 " Show Me" 2250 ] 2251 } 2252 ), 2253 /* @__PURE__ */ jsxRuntimeExports.jsx( 2254 "button", 2255 { 2256 onClick: skipStep, 2257 className: "px-3 py-1.5 bg-[var(--daw-surface)] hover:bg-[var(--daw-elevated)] text-[var(--daw-text)] rounded-lg text-[10px] font-bold border border-[var(--daw-border-strong)] transition cursor-pointer active:scale-95", 2258 children: "Skip â" 2259 } 2260 ) 2261 ] }) 2262 ] }),
2263 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2.5 pt-2", children: [ 2264 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-bold text-[var(--daw-text-secondary)] uppercase tracking-wider block", children: "Chapters Navigation" }), 2265 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1.5 max-h-48 overflow-y-auto pr-1", children: activeScript.map((ch, idx) => { 2266 const isActive = idx === currentChapterIndex; 2267 const isPrevChapterCompleted = idx < currentChapterIndex; 2268 const isLight = theme === "light" || theme === "retro"; 2269 let bg = "var(--daw-surface)"; 2270 let border = "1px solid var(--daw-border-strong)"; 2271 let color = "var(--daw-text-secondary)"; 2272 if (isActive) { 2273 if (isLight) { 2274 bg = lessonType === "simple" ? "rgba(16, 185, 129, 0.15)" : "rgba(59, 130, 246, 0.15)"; 2275 border = lessonType === "simple" ? "1px solid rgba(16, 185, 129, 0.6)" : "1px solid rgba(59, 130, 246, 0.6)"; 2276 color = lessonType === "simple" ? "#047857" : "#1d4ed8"; 2277 } else { 2278 bg = lessonType === "simple" ? "rgba(16, 185, 129, 0.12)" : "rgba(59, 130, 246, 0.12)"; 2279 border = lessonType === "simple" ? "1px solid rgba(16, 185, 129, 0.4)" : "1px solid rgba(59, 130, 246, 0.4)"; 2280 color = lessonType === "simple" ? "#6ee7b7" : "#93c5fd"; 2281 } 2282 } else if (isPrevChapterCompleted) { 2283 if (isLight) { 2284 bg = "rgba(16, 185, 129, 0.1)"; 2285 border = "1px solid rgba(16, 185, 129, 0.4)"; 2286 color = "#059669"; 2287 } else { 2288 bg = "rgba(16, 185, 129, 0.08)"; 2289 border = "1px solid rgba(16, 185, 129, 0.25)"; 2290 color = "#34d399"; 2291 } 2292 } 2293 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 2294 "button", 2295 { 2296 onClick: () => jumpToChapter(idx), 2297 className: "w-full flex items-center justify-between p-2.5 rounded-lg text-[11px] font-semibold transition cursor-pointer", 2298 style: { backgroundColor: bg, border, color }, 2299 children: [
2300 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-2", children: [ 2301 isPrevChapterCompleted ? lessonType === "simple" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Star, { className: "w-3.5 h-3.5 text-yellow-450 fill-current" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { className: "w-3.5 h-3.5 text-emerald-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-3.5 h-3.5 rounded-full border text-[8px] flex items-center justify-center ${isActive ? lessonType === "simple" ? "border-emerald-500 text-emerald-555 font-bold" : "border-blue-500 text-blue-550 font-bold" : "border-[var(--daw-border-strong)] text-[var(--daw-text-secondary)]"}`, children: idx + 1 }), 2302 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate max-w-[200px] font-bold", children: ch.title }) 2303 ] }), 2304 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] opacity-75 uppercase font-bold", children: ch.skillLearned.split(" ")[0] }) 2305 ] 2306 }, 2307 ch.id 2308 ); 2309 }) }) 2310 ] }) 2311 ] }),
2312 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mx-2 mb-3 pb-0.5 shrink-0 relative z-10 border border-[var(--daw-border-strong)] bg-[var(--daw-elevated)] rounded-xl shadow-2xl flex flex-col overflow-hidden transition-all duration-300", children: [ 2313 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2314 "button", 2315 { 2316 onClick: () => setIsPlaybackScrubberExpanded(!isPlaybackScrubberExpanded), 2317 className: "p-3 w-full flex items-center justify-between text-xs font-bold text-[var(--daw-text-secondary)] hover:text-[var(--daw-text)] hover:bg-[var(--daw-surface)] transition cursor-pointer select-none text-left bg-transparent border-0 outline-none", 2318 children: [ 2319 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-2", children: [ 2320 /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "w-3.5 h-3.5 fill-current text-blue-500" }),
2321 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: isPlaybackScrubberExpanded ? "Hide Playback Controls" : "Show Playback Controls" }) 2322 ] }), 2323 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-2", children: [ 2324 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-[10px] text-[var(--daw-text-secondary)]", children: [ 2325 "Step ", 2326 globalStepIdx + 1, 2327 "/", 2328 totalSteps 2329 ] }), 2330 isPlaybackScrubberExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "w-4 h-4 rotate-90 transition-transform duration-200" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "w-4 h-4 -rotate-90 transition-transform duration-200" }) 2331 ] }) 2332 ] 2333 } 2334 ),
2335 isPlaybackScrubberExpanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-t border-[var(--daw-border-strong)] space-y-4", children: [ 2336 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [ 2337 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-[9px] font-bold text-[var(--daw-text-secondary)] uppercase select-none", children: [ 2338 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Playback Scrubber" }), 2339 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-[var(--daw-text-secondary)]", children: [ 2340 "Step ", 2341 globalStepIdx + 1, 2342 "/", 2343 totalSteps 2344 ] }) 2345 ] }), 2346 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative pt-1 flex items-center", children: [
2347 /* @__PURE__ */ jsxRuntimeExports.jsx( 2348 "input", 2349 { 2350 type: "range", 2351 min: "0", 2352 max: totalSteps - 1, 2353 value: globalStepIdx, 2354 onChange: (e) => jumpToGlobalStep(parseInt(e.target.value, 10)), 2355 className: "w-full accent-emerald-505 h-1 bg-[var(--daw-inset)] rounded-lg cursor-pointer appearance-none relative z-10" 2356 } 2357 ), 2358 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 right-0 top-[9px] h-1 bg-[var(--daw-inset)] rounded-lg pointer-events-none" }), 2359 /* @__PURE__ */ jsxRuntimeExports.jsx( 2360 "div", 2361 { 2362 className: `absolute left-0 top-[9px] h-1 rounded-lg pointer-events-none ${lessonType === "simple" ? "bg-emerald-500" : "bg-blue-500"}`, 2363 style: { width: `${progressPercent}%` } 2364 } 2365 ), 2366 ticks.map((tick) => { 2367 var _a; 2368 return /* @__PURE__ */ jsxRuntimeExports.jsx( 2369 "div", 2370 { 2371 className: `absolute w-1 h-2 -translate-x-1/2 pointer-events-none border-x border-[var(--daw-border-strong)] ${globalStepIdx >= ((_a = ticks.find((t) => t.id === tick.id)) == null ? void 0 : _a.pos) ? "bg-emerald-400 border-emerald-500" : "bg-[var(--daw-inset)] border-[var(--daw-border-strong)]"}`, 2372 style: { left: `${tick.pos}%` }, 2373 title: tick.title 2374 }, 2375 tick.id 2376 ); 2377 }) 2378 ] }) 2379 ] }),
2380 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 2381 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-2", children: [ 2382 /* @__PURE__ */ jsxRuntimeExports.jsx( 2383 "button", 2384 { 2385 onClick: () => handlePrev(), 2386 className: "flex items-center justify-center w-9 h-9 rounded-lg bg-[var(--daw-surface)] border border-[var(--daw-border-strong)] text-[var(--daw-text-secondary)] hover:text-[var(--daw-text)] cursor-pointer hover:bg-[var(--daw-elevated)] active:scale-95 transition", 2387 title: "Previous Step (â)", 2388 children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronLeft, { className: "w-5 h-5" }) 2389 } 2390 ), 2391 isCompleted ? /* @__PURE__ */ jsxRuntimeExports.jsx( 2392 "button", 2393 { 2394 onClick: () => jumpToGlobalStep(0), 2395 className: "flex items-center justify-center w-10 h-10 rounded-lg bg-blue-600 hover:bg-blue-500 text-white shadow transition cursor-pointer active:scale-95", 2396 title: "Restart Lesson", 2397 children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-5 h-5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67" }) }) 2398 } 2399 ) : /* @__PURE__ */ jsxRuntimeExports.jsx( 2400 "button", 2401 { 2402 onClick: () => setIsPlaying(!isPlaying), 2403 "data-testid": "tutorial-play-btn", 2404 className: `flex items-center justify-center w-10 h-10 rounded-lg text-white shadow transition cursor-pointer active:scale-95 ${isPlaying ? "bg-amber-600 hover:bg-amber-500 shadow-amber-600/30" : "bg-emerald-600 hover:bg-emerald-500 shadow-emerald-600/30"}`, 2405 title: isPlaying ? "Pause Lesson (Space)" : "Play/Watch Lesson (Space)",
2406 children: isPlaying ? /* @__PURE__ */ jsxRuntimeExports.jsx(Pause, { className: "w-5 h-5 fill-current" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "w-5 h-5 fill-current" }) 2407 } 2408 ), 2409 /* @__PURE__ */ jsxRuntimeExports.jsx( 2410 "button", 2411 { 2412 onClick: () => handleNext(), 2413 className: "flex items-center justify-center w-9 h-9 rounded-lg bg-[var(--daw-surface)] border border-[var(--daw-border-strong)] text-[var(--daw-text-secondary)] hover:text-[var(--daw-text)] cursor-pointer hover:bg-[var(--daw-elevated)] active:scale-95 transition", 2414 title: "Next Step (â)", 2415 children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "w-5 h-5" }) 2416 } 2417 ) 2418 ] }),
2419 /* @__PURE__ */ jsxRuntimeExports.jsx( 2420 "button", 2421 { 2422 onClick: () => setUseTts(!useTts), 2423 className: `flex items-center justify-center w-9 h-9 rounded-lg border transition cursor-pointer active:scale-95 ${useTts ? "bg-emerald-600/10 border-emerald-500/30 text-emerald-700 dark:text-emerald-400 hover:bg-emerald-600/20" : "bg-[var(--daw-surface)] border-[var(--daw-border-strong)] text-[var(--daw-text-secondary)] hover:text-[var(--daw-text)]"}`, 2424 title: useTts ? "Voice Narration is ON (Click to mute)" : "Voice Narration is MUTED (Click to unmute)", 2425 children: useTts ? /* @__PURE__ */ jsxRuntimeExports.jsx(Volume2, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(VolumeX, { className: "w-4 h-4" }) 2426 } 2427 ), 2428 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-[var(--daw-inset)] p-1 rounded-lg border border-[var(--daw-border-strong)]", children: [
2429 /* @__PURE__ */ jsxRuntimeExports.jsx( 2430 "button", 2431 { 2432 onClick: () => setMode("guided"), 2433 "data-testid": "mode-guided-btn", 2434 className: `px-3 py-1 rounded text-[10px] font-bold transition-all cursor-pointer ${mode === "guided" ? "bg-blue-600 text-white shadow-md" : "text-[var(--daw-text-secondary)] hover:text-[var(--daw-text)]"}`, 2435 title: "Guided Practice: Follow targets at your own pace", 2436 children: "ð¯ Guided" 2437 } 2438 ), 2439 /* @__PURE__ */ jsxRuntimeExports.jsx( 2440 "button", 2441 { 2442 onClick: () => setMode("watch"), 2443 "data-testid": "mode-watch-btn", 2444 className: `px-3 py-1 rounded text-[10px] font-bold transition-all cursor-pointer ${mode === "watch" ? "bg-indigo-600 text-white shadow-md" : "text-[var(--daw-text-secondary)] hover:text-[var(--daw-text)]"}`, 2445 title: "Watch Mode: Sit back and watch with auto-play and narration", 2446 children: "ð¬ Watch" 2447 } 2448 ) 2449 ] }) 2450 ] }),
2451 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [ 2452 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[9px] font-bold text-[var(--daw-text-secondary)] uppercase select-none", children: [ 2453 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Speed Limit" }), 2454 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-emerald-400", children: [ 2455 speed, 2456 "x" 2457 ] }) 2458 ] }), 2459 /* @__PURE__ */ jsxRuntimeExports.jsx( 2460 "input", 2461 { 2462 type: "range", 2463 min: "0", 2464 max: "3", 2465 value: speed === 0.5 ? 0 : speed === 1 ? 1 : speed === 2 ? 2 : 3, 2466 onChange: (e) => { 2467 const val = parseInt(e.target.value, 10); 2468 const speeds = [0.5, 1, 2, 8]; 2469 setSpeed(speeds[val]); 2470 }, 2471 className: "w-full accent-emerald-500 h-1 bg-[var(--daw-inset)] rounded-lg cursor-pointer" 2472 } 2473 ),
2474 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[8px] font-bold text-[var(--daw-text-secondary)] select-none", children: [ 2475 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: speed === 0.5 ? "text-emerald-400 font-extrabold" : "", children: "Slow (0.5x)" }), 2476 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: speed === 1 ? "text-emerald-400 font-extrabold" : "", children: "Normal (1x)" }), 2477 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: speed === 2 ? "text-emerald-400 font-extrabold" : "", children: "Fast (2x)" }), 2478 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: speed === 8 ? "text-emerald-400 font-extrabold" : "", children: "Showcase (8x)" }) 2479 ] }) 2480 ] }), 2481 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center text-[9px] text-[var(--daw-text-secondary)] font-bold border-t border-[var(--daw-border-strong)] pt-2 flex justify-center items-center gap-1 select-none", children: [
2482 /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1 bg-[var(--daw-inset)] rounded border border-[var(--daw-border-strong)] text-[var(--daw-text)]", children: "Space" }), 2483 " Play/Pause ·", 2484 /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1 bg-[var(--daw-inset)] rounded border border-[var(--daw-border-strong)] text-[var(--daw-text)]", children: "â" }), 2485 " / ", 2486 /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1 bg-[var(--daw-inset)] rounded border border-[var(--daw-border-strong)] text-[var(--daw-text)]", children: "â" }), 2487 " Step" 2488 ] }) 2489 ] }) 2490 ] }) 2491 ] }), 2492 activeTab === "original" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex flex-col p-5 overflow-y-auto space-y-4", children: lessonType === "simple" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2493 /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "font-extrabold text-xs text-[var(--daw-text-secondary)] uppercase tracking-widest flex items-center gap-1", children: [ 2494 /* @__PURE__ */ jsxRuntimeExports.jsx(Star, { className: "w-3.5 h-3.5 fill-current text-yellow-450" }), 2495 " Simple Song Guide" 2496 ] }), 2497 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] leading-relaxed text-[var(--daw-text-secondary)]", children: [ 2498 "Here is a visual map of the notes we are writing for ", 2499 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Mary Had a Little Lamb" }), 2500 ". Tap the notes or look at the sheet to help you place your notes!" 2501 ] }), 2502 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-emerald-200 dark:border-emerald-500/20 rounded-xl bg-emerald-50/40 dark:bg-emerald-950/10 p-4 space-y-3 shadow-sm dark:shadow-xl", children: [
2503 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-serif font-black text-sm text-center text-[var(--daw-text)] tracking-wide uppercase", children: "Mary Had a Little Lamb" }), 2504 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3.5 py-2 select-none", children: [ 2505 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-[var(--daw-border)] w-full h-px relative py-1", children: [ 2506 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-bold text-[var(--daw-text)] block mb-1", children: "Measure 1: Ma - ry had a" }), 2507 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono text-xs px-2 py-0.5 rounded font-bold border ${theme === "light" || theme === "retro" ? "bg-emerald-50 border-emerald-200 text-emerald-700" : "bg-zinc-900 border-zinc-800 text-emerald-400"}`, children: "E - D - C - D" }), 2508 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-[var(--daw-text-secondary)] block mt-1", children: "(Four black quarter notes)" }) 2509 ] }),
2510 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-[var(--daw-border)] w-full h-px relative py-1", children: [ 2511 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-bold text-[var(--daw-text)] block mb-1", children: "Measure 2: lit - tle lamb" }), 2512 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono text-xs px-2 py-0.5 rounded font-bold border ${theme === "light" || theme === "retro" ? "bg-emerald-50 border-emerald-200 text-emerald-700" : "bg-zinc-900 border-zinc-800 text-emerald-400"}`, children: "E - E - E (holds)" }), 2513 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-[var(--daw-text-secondary)] block mt-1", children: "(Two quarter notes, one half note)" }) 2514 ] }) 2515 ] }) 2516 ] }) 2517 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2518 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-extrabold text-xs text-[var(--daw-text)] uppercase tracking-widest", children: "Breitkopf & Härtel 1887 Edition" }), 2519 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] leading-relaxed text-[var(--daw-text-secondary)]", children: `This guided lesson transcribes Robert Schumann's "Widmung" arr. by Clara Schumann. Below is the original score sheet and a reference of the original layout goals (A-flat major, flat-cancellations, Breitkopf system indents).` }), 2520 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border border-[var(--daw-border-strong)] rounded-xl bg-[var(--daw-inset)] p-2 shadow-xl flex flex-col items-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 2521 "img", 2522 { 2523 src: "/assets/schumann_widmung_original.jpg", 2524 alt: "Schumann/Clara Schumann Widmung Breitkopf 1887 Original Score", 2525 className: "w-full h-auto rounded-lg object-contain border border-[var(--daw-border-strong)]" 2526 } 2527 ) }),
2528 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-[var(--daw-border-strong)] rounded-xl bg-[var(--daw-surface)] p-4 space-y-4 shadow-xl", children: [ 2529 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-end border-b border-[var(--daw-border-strong)] pb-2", children: [ 2530 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 2531 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-serif font-black text-sm text-[var(--daw-text)] tracking-wide uppercase", children: "Widmung." }), 2532 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[9px] italic text-[var(--daw-text-secondary)] font-medium", children: "Liederkreis von Friedrich Rückert, Op. 25" }) 2533 ] }), 2534 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-right", children: [ 2535 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-serif text-[var(--daw-text-secondary)] block", children: "ROBERT SCHUMANN" }),
2536 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] font-serif text-[var(--daw-text-secondary)] uppercase tracking-wider block", children: "Arr. Clara Schumann" }) 2537 ] }) 2538 ] }), 2539 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3.5 py-2 select-none", children: [ 2540 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1 opacity-85", children: [ 2541 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] font-bold text-[var(--daw-text-secondary)] uppercase tracking-wider block", children: "System 1: Indent (X = 350)" }), 2542 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-[var(--daw-border-strong)] w-full h-px relative", children: [ 2543 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-8 right-0 border-t border-zinc-500/30 h-px top-1" }),
2544 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-8 right-0 border-t border-zinc-500/30 h-px top-2" }), 2545 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-8 right-0 border-t border-zinc-500/30 h-px top-3" }), 2546 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-8 right-0 border-t border-zinc-500/30 h-px top-4" }), 2547 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-10 text-[9px] font-serif text-blue-500/90 top-0.5", children: "ââââ" }), 2548 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-20 text-[9px] font-bold text-emerald-500/90 top-0.5", children: "3/2" }) 2549 ] }) 2550 ] }), 2551 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1 opacity-85", children: [ 2552 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] font-bold text-[var(--daw-text-secondary)] uppercase tracking-wider block", children: "System 4: Key Change (Measure 14)" }),
2553 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-[var(--daw-border-strong)] w-full h-px relative", children: [ 2554 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 right-0 border-t border-zinc-500/30 h-px top-1" }), 2555 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 right-0 border-t border-zinc-500/30 h-px top-2" }), 2556 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 right-0 border-t border-zinc-500/30 h-px top-3" }), 2557 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-24 text-[9px] font-serif text-amber-500/95 top-0.5", children: "â®â®â®â®" }), 2558 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-14 text-[9px] font-serif text-orange-550/95 top-0.5", children: "â¯â¯â¯â¯" }), 2559 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-4 text-[9px] font-bold text-[var(--daw-text-secondary)] top-0.5", children: "||" }) 2560 ] }) 2561 ] }) 2562 ] }),
2563 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-[var(--daw-border-strong)] flex items-center justify-between text-[8px] text-[var(--daw-text-secondary)] font-bold uppercase tracking-wider", children: [ 2564 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Leipzig Edition (1887)" }), 2565 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Opus 25 Nr. 1" }) 2566 ] }) 2567 ] }) 2568 ] }) }), 2569 /* @__PURE__ */ jsxRuntimeExports.jsx("style", { dangerouslySetInnerHTML: { __html: ` 2570 @keyframes fadeIn { 2571 from { opacity: 0; transform: translateY(-4px); } 2572 to { opacity: 1; transform: translateY(0); } 2573 } 2574 .animate-fade-in { 2575 animation: fadeIn 0.2s ease-out forwards; 2576 } 2577 ` } }) 2578 ] 2579 } 2580 ) 2581 ] 2582 } 2583 ), 2584 isCollapsed && /* @__PURE__ */ jsxRuntimeExports.jsxs( 2585 "div", 2586 { 2587 className: "fixed bottom-6 left-1/2 transform -translate-x-1/2 z-[200] flex items-center space-x-4 bg-[var(--daw-elevated)] border border-[var(--daw-border-strong)] px-5 py-3 rounded-full shadow-2xl backdrop-blur-md animate-fade-in", 2588 style: { minWidth: "420px", maxWidth: "650px" }, 2589 children: [ 2590 isCompleted ? /* @__PURE__ */ jsxRuntimeExports.jsx( 2591 "button", 2592 { 2593 onClick: () => jumpToGlobalStep(0), 2594 className: "flex items-center justify-center w-8 h-8 rounded-full text-white bg-blue-600 hover:bg-blue-500 shadow shadow-blue-600/20 transition cursor-pointer shrink-0", 2595 title: "Restart Tutorial", 2596 children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67" }) }) 2597 } 2598 ) : /* @__PURE__ */ jsxRuntimeExports.jsx( 2599 "button", 2600 { 2601 onClick: () => { 2602 if (mode === "guided") { 2603 setMode("watch"); 2604 setIsPlaying(true); 2605 } else { 2606 setIsPlaying(!isPlaying); 2607 } 2608 }, 2609 className: `flex items-center justify-center w-8 h-8 rounded-full text-white shadow transition cursor-pointer shrink-0 ${mode === "guided" ? "bg-blue-600 hover:bg-blue-500 shadow-blue-600/20" : isPlaying ? "bg-amber-600 hover:bg-amber-500 shadow-amber-600/20" : "bg-emerald-600 hover:bg-emerald-500 shadow-emerald-600/20"}`, 2610 title: mode === "guided" ? "Switch to Watch Mode & Play" : isPlaying ? "Pause Playback" : "Start Playback",
2611 children: mode === "guided" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "w-3.5 h-3.5 fill-current" }) : isPlaying ? /* @__PURE__ */ jsxRuntimeExports.jsx(Pause, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "w-3.5 h-3.5 fill-current" }) 2612 } 2613 ), 2614 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [ 2615 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[9px] font-bold uppercase tracking-widest block mb-0.5 select-none ${isCompleted ? "text-emerald-600 dark:text-emerald-400" : mode === "guided" ? "text-emerald-600 dark:text-emerald-400" : "text-amber-600 dark:text-amber-400"}`, children: isCompleted ? "Lesson Complete!" : mode === "guided" ? `ð¯ Guided Lesson (${globalStepIdx + 1}/${totalSteps})` : `ð¬ Watch Mode (${globalStepIdx + 1}/${totalSteps})` }), 2616 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[var(--daw-text)] text-xs font-semibold truncate", title: isCompleted ? "Finished!" : getStepInstruction(currentStep), children: isCompleted ? "Congratulations! ð" : getStepInstruction(currentStep) }) 2617 ] }),
2618 /* @__PURE__ */ jsxRuntimeExports.jsx( 2619 "button", 2620 { 2621 onClick: () => setIsCollapsed(false), 2622 className: "text-xs text-[var(--daw-text-secondary)] hover:text-[var(--daw-text)] font-bold bg-[var(--daw-surface)] border border-[var(--daw-border-strong)] px-3 py-1.5 rounded-full transition cursor-pointer shrink-0 select-none", 2623 children: "Details" 2624 } 2625 ), 2626 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-4 border-l border-[var(--daw-border-strong)] shrink-0" }), 2627 /* @__PURE__ */ jsxRuntimeExports.jsx( 2628 "button", 2629 { 2630 onClick: onExit, 2631 className: "text-xs text-red-400 hover:text-red-300 font-bold bg-red-950/20 border border-red-900/40 px-3 py-1.5 rounded-full transition cursor-pointer shrink-0 select-none", 2632 children: isCompleted ? "Start Editing" : "Exit" 2633 } 2634 ) 2635 ] 2636 } 2637 ) 2638 ] }); 2639}; 2640const VirtualCursor = ({ 2641 targetSelector, 2642 clickTriggered = false, 2643 clickType = "left", 2644 speedMultiplier = 1 2645}) => { 2646 const [position, setPosition] = reactExports.useState({ x: -100, y: -100 }); 2647 const [isVisible, setIsVisible] = reactExports.useState(false); 2648 const [ripples, setRipples] = reactExports.useState([]); 2649 const cursorRef = reactExports.useRef(null); 2650 const rippleIdRef = reactExports.useRef(0); 2651 reactExports.useEffect(() => { 2652 if (!targetSelector) { 2653 setIsVisible(false); 2654 return; 2655 } 2656 const updatePosition = () => { 2657 try { 2658 const el = document.querySelector(targetSelector); 2659 if (el) { 2660 const rect = el.getBoundingClientRect(); 2661 const centerX = rect.left + rect.width / 2; 2662 const centerY = rect.top + rect.height / 2; 2663 setPosition({ x: centerX, y: centerY }); 2664 setIsVisible(true); 2665 } else { 2666 setIsVisible(false); 2667 } 2668 } catch (err) { 2669 setIsVisible(false); 2670 } 2671 }; 2672 updatePosition(); 2673 const interval = setInterval(updatePosition, 100); 2674 window.addEventListener("resize", updatePosition); 2675 window.addEventListener("scroll", updatePosition, true); 2676 const mutationObserver = new MutationObserver(() => updatePosition()); 2677 mutationObserver.observe(document.body, { childList: true, subtree: true, attributes: true }); 2678 const resizeObserver = new ResizeObserver(() => updatePosition()); 2679 resizeObserver.observe(document.body); 2680 return () => { 2681 clearInterval(interval); 2682 window.removeEventListener("resize", updatePosition); 2683 window.removeEventListener("scroll", updatePosition, true); 2684 mutationObserver.disconnect(); 2685 resizeObserver.disconnect(); 2686 }; 2687 }, [targetSelector]); 2688 reactExports.useEffect(() => { 2689 if (clickTriggered && isVisible) { 2690 const id = ++rippleIdRef.current; 2691 setRipples((prev) => [...prev, { id, x: position.x, y: position.y, type: clickType }]); 2692 const duration = Math.max(150, 800 / speedMultiplier); 2693 const timer = setTimeout(() => { 2694 setRipples((prev) => prev.filter((r) => r.id !== id)); 2695 }, duration); 2696 return () => clearTimeout(timer); 2697 } 2698 }, [clickTriggered, position, isVisible, clickType, speedMultiplier]); 2699 if (!isVisible) return null; 2700 const rippleDuration = Math.max(0.15, 0.8 / speedMultiplier); 2701 return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 2702 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 pointer-events-none z-[9999]", children: ripples.map((ripple) => /* @__PURE__ */ jsxRuntimeExports.jsx( 2703 "div", 2704 { 2705 className: `absolute rounded-full border-2 transform -translate-x-1/2 -translate-y-1/2 animate-ping ${ripple.type === "left" ? "border-emerald-500 bg-emerald-500/10" : "border-orange-500 bg-orange-500/10"}`, 2706 style: { 2707 left: ripple.x, 2708 top: ripple.y, 2709 width: "40px", 2710 height: "40px", 2711 animationDuration: `${rippleDuration}s` 2712 } 2713 }, 2714 ripple.id 2715 )) }),
2716 /* @__PURE__ */ jsxRuntimeExports.jsx( 2717 "div", 2718 { 2719 ref: cursorRef, 2720 className: "fixed pointer-events-none z-[10000] drop-shadow-[0_4px_8px_rgba(0,0,0,0.5)] select-none", 2721 style: { 2722 left: position.x, 2723 top: position.y, 2724 transform: clickTriggered ? "scale(0.85)" : "scale(1)", 2725 transition: `left ${350 / speedMultiplier}ms cubic-bezier(0.25, 1, 0.5, 1), top ${350 / speedMultiplier}ms cubic-bezier(0.25, 1, 0.5, 1), transform 150ms ease` 2726 }, 2727 children: /* @__PURE__ */ jsxRuntimeExports.jsxs( 2728 "svg", 2729 { 2730 width: "24", 2731 height: "24", 2732 viewBox: "0 0 24 24", 2733 fill: "none", 2734 xmlns: "http://www.w3.org/2000/svg", 2735 children: [ 2736 /* @__PURE__ */ jsxRuntimeExports.jsx( 2737 "path", 2738 { 2739 d: "M5.65376 12.3825L19.5602 4.90848C20.3541 4.48192 21.2337 5.25301 20.9163 6.10103L15.3674 20.9234C15.084 21.6806 14.0534 21.7822 13.626 21.0991L10.3628 15.8845L5.35127 13.9142C4.6984 13.6575 4.8703 12.8036 5.65376 12.3825Z", 2740 fill: "white", 2741 stroke: "black", 2742 strokeWidth: "1.5", 2743 strokeLinejoin: "round" 2744 } 2745 ),
2746 /* @__PURE__ */ jsxRuntimeExports.jsx( 2747 "circle", 2748 { 2749 cx: "7", 2750 cy: "13", 2751 r: "2", 2752 fill: clickType === "left" ? "#10b981" : "#f97316" 2753 } 2754 ) 2755 ] 2756 } 2757 ) 2758 } 2759 ) 2760 ] }); 2761}; 2762const TutorialHints = ({ 2763 targetSelector, 2764 isActive = false, 2765 speedMultiplier = 1 2766}) => { 2767 const [bounds, setBounds] = reactExports.useState(null); 2768 reactExports.useEffect(() => { 2769 if (!isActive || !targetSelector) { 2770 setBounds(null); 2771 return; 2772 } 2773 const updateBounds = () => { 2774 try { 2775 const el = document.querySelector(targetSelector); 2776 if (el) { 2777 const rect = el.getBoundingClientRect(); 2778 if (rect.width > 0 && rect.height > 0) { 2779 setBounds(rect); 2780 } else { 2781 setBounds(null); 2782 } 2783 } else { 2784 setBounds(null); 2785 } 2786 } catch (err) { 2787 setBounds(null); 2788 } 2789 }; 2790 updateBounds(); 2791 const interval = setInterval(updateBounds, 100); 2792 window.addEventListener("resize", updateBounds); 2793 window.addEventListener("scroll", updateBounds, true); 2794 const mutationObserver = new MutationObserver(() => updateBounds()); 2795 mutationObserver.observe(document.body, { childList: true, subtree: true, attributes: true }); 2796 const resizeObserver = new ResizeObserver(() => updateBounds()); 2797 resizeObserver.observe(document.body); 2798 return () => { 2799 clearInterval(interval); 2800 window.removeEventListener("resize", updateBounds); 2801 window.removeEventListener("scroll", updateBounds, true); 2802 mutationObserver.disconnect(); 2803 resizeObserver.disconnect(); 2804 }; 2805 }, [targetSelector, isActive]); 2806 if (!isActive || !bounds) return null; 2807 const pingDuration = Math.max(0.25, 1.5 / speedMultiplier); 2808 return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 2809 /* @__PURE__ */ jsxRuntimeExports.jsx( 2810 "div", 2811 { 2812 className: "fixed inset-0 pointer-events-auto z-[140] bg-zinc-950/60 mix-blend-multiply transition-opacity duration-300", 2813 style: { 2814 clipPath: `polygon( 2815 0% 0%, 2816 0% 100%, 2817 ${bounds.left}px 100%, 2818 ${bounds.left}px ${bounds.top}px, 2819 ${bounds.right}px ${bounds.top}px, 2820 ${bounds.right}px ${bounds.bottom}px, 2821 ${bounds.left}px ${bounds.bottom}px, 2822 ${bounds.left}px 100%, 2823 100% 100%, 2824 100% 0% 2825 )` 2826 } 2827 } 2828 ), 2829 /* @__PURE__ */ jsxRuntimeExports.jsx( 2830 "div", 2831 { 2832 className: "fixed z-[145] pointer-events-none border-2 border-blue-500 rounded-lg shadow-[0_0_15px_rgba(59,130,246,0.5)] animate-pulse", 2833 style: { 2834 left: bounds.left - 4, 2835 top: bounds.top - 4, 2836 width: bounds.width + 8, 2837 height: bounds.height + 8, 2838 transition: `all ${Math.max(50, 200 / speedMultiplier)}ms ease-out` 2839 }, 2840 children: /* @__PURE__ */ jsxRuntimeExports.jsx( 2841 "div", 2842 { 2843 className: "absolute inset-0 border border-blue-400 rounded-lg animate-ping opacity-60", 2844 style: { animationDuration: `${pingDuration}s` } 2845 } 2846 ) 2847 } 2848 ) 2849 ] }); 2850}; 2851const InteractionOverlay = ({ 2852 isActive, 2853 targetSelector, 2854 triggerSelector, 2855 promptText, 2856 successMessage, 2857 onComplete 2858}) => { 2859 const [bounds, setBounds] = reactExports.useState(null); 2860 const [triggerBounds, setTriggerBounds] = reactExports.useState(null); 2861 const [showSuccess, setShowSuccess] = reactExports.useState(false); 2862 const [dragPosition, setDragPosition] = reactExports.useState(null); 2863 const [isDragging, setIsDragging] = reactExports.useState(false); 2864 const dragStartRef = reactExports.useRef(null); 2865 const bannerRef = reactExports.useRef(null); 2866 reactExports.useEffect(() => { 2867 setDragPosition(null); 2868 setIsDragging(false); 2869 }, [promptText, targetSelector, triggerSelector]); 2870 const [isFallback, setIsFallback] = reactExports.useState(false); 2871 reactExports.useEffect(() => { 2872 setShowSuccess(false); 2873 if (!isActive) { 2874 setBounds(null); 2875 setTriggerBounds(null); 2876 setIsFallback(false); 2877 return; 2878 } 2879 const getFallbackBounds = () => { 2880 const width = 240; 2881 const height = 120; 2882 const left = Math.max(10, (window.innerWidth - width) / 2); 2883 const top = Math.max(10, (window.innerHeight - height) / 2); 2884 return new DOMRect(left, top, width, height); 2885 }; 2886 const updateBounds = () => {
2887 try { 2888 let el = targetSelector ? document.querySelector(targetSelector) : null; 2889 let isMissing = false; 2890 if (el) { 2891 const rect = el.getBoundingClientRect(); 2892 if (rect.width > 0 && rect.height > 0) { 2893 setBounds(rect); 2894 setIsFallback(false); 2895 } else { 2896 isMissing = true; 2897 } 2898 } else { 2899 isMissing = true; 2900 } 2901 if (isMissing) { 2902 if (targetSelector) { 2903 setBounds(getFallbackBounds()); 2904 setIsFallback(true); 2905 } else { 2906 setBounds(null); 2907 setIsFallback(false); 2908 } 2909 } 2910 if (triggerSelector) { 2911 const triggerEl = document.querySelector(triggerSelector); 2912 if (triggerEl) { 2913 const trRect = triggerEl.getBoundingClientRect(); 2914 if (trRect.width > 0 && trRect.height > 0) { 2915 setTriggerBounds(trRect); 2916 } else { 2917 setTriggerBounds(null); 2918 } 2919 } else { 2920 setTriggerBounds(null); 2921 } 2922 } else { 2923 setTriggerBounds(null); 2924 } 2925 } catch (err) { 2926 if (targetSelector) { 2927 setBounds(getFallbackBounds()); 2928 setIsFallback(true); 2929 } else { 2930 setBounds(null); 2931 } 2932 setTriggerBounds(null); 2933 } 2934 }; 2935 updateBounds(); 2936 const interval = setInterval(updateBounds, 100); 2937 window.addEventListener("resize", updateBounds); 2938 window.addEventListener("scroll", updateBounds, true); 2939 const mutationObserver = new MutationObserver(() => updateBounds()); 2940 mutationObserver.observe(document.body, { childList: true, subtree: true, attributes: true }); 2941 const resizeObserver = new ResizeObserver(() => updateBounds()); 2942 resizeObserver.observe(document.body); 2943 return () => { 2944 clearInterval(interval); 2945 window.removeEventListener("resize", updateBounds); 2946 window.removeEventListener("scroll", updateBounds, true); 2947 mutationObserver.disconnect(); 2948 resizeObserver.disconnect(); 2949 }; 2950 }, [targetSelector, triggerSelector, isActive]); 2951 const handlePointerDown = (e) => { 2952 var _a, _b; 2953 if (!bannerRef.current) return; 2954 const rect = bannerRef.current.getBoundingClientRect(); 2955 dragStartRef.current = { 2956 mouseX: e.clientX, 2957 mouseY: e.clientY, 2958 elemX: rect.left, 2959 elemY: rect.top 2960 }; 2961 setIsDragging(true); 2962 (_b = (_a = e.target).setPointerCapture) == null ? void 0 : _b.call(_a, e.pointerId); 2963 }; 2964 const handlePointerMove = (e) => { 2965 if (!isDragging || !dragStartRef.current || !bannerRef.current) return; 2966 const deltaX = e.clientX - dragStartRef.current.mouseX; 2967 const deltaY = e.clientY - dragStartRef.current.mouseY; 2968 const bannerRect = bannerRef.current.getBoundingClientRect(); 2969 const width = bannerRect.width || 380; 2970 const height = bannerRect.height || 80; 2971 const newX = Math.max(10, Math.min(window.innerWidth - width - 10, dragStartRef.current.elemX + deltaX)); 2972 const newY = Math.max(10, Math.min(window.innerHeight - height - 10, dragStartRef.current.elemY + deltaY)); 2973 setDragPosition({ x: newX, y: newY }); 2974 }; 2975 const handlePointerUp = (e) => { 2976 var _a, _b; 2977 if (isDragging) { 2978 setIsDragging(false); 2979 try { 2980 (_b = (_a = e.target).releasePointerCapture) == null ? void 0 : _b.call(_a, e.pointerId); 2981 } catch (err) { 2982 } 2983 } 2984 }; 2985 const handleSuccess = reactExports.useCallback(() => { 2986 setShowSuccess(true); 2987 try { 2988 const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); 2989 const osc1 = audioCtx.createOscillator(); 2990 const osc2 = audioCtx.createOscillator(); 2991 const gainNode = audioCtx.createGain(); 2992 osc1.connect(gainNode); 2993 osc2.connect(gainNode); 2994 gainNode.connect(audioCtx.destination); 2995 osc1.type = "triangle"; 2996 osc2.type = "sine"; 2997 const now = audioCtx.currentTime; 2998 osc1.frequency.setValueAtTime(523.25, now); 2999 osc1.frequency.setValueAtTime(659.25, now + 0.1); 3000 osc1.frequency.setValueAtTime(783.99, now + 0.2); 3001 osc1.frequency.setValueAtTime(1046.5, now + 0.3); 3002 osc2.frequency.setValueAtTime(1046.5, now); 3003 osc2.frequency.setValueAtTime(1318.51, now + 0.1); 3004 gainNode.gain.setValueAtTime(0, now); 3005 gainNode.gain.linearRampToValueAtTime(0.15, now + 0.05); 3006 gainNode.gain.exponentialRampToValueAtTime(1e-3, now + 0.8); 3007 osc1.start(now); 3008 osc2.start(now); 3009 osc1.stop(now + 0.8); 3010 osc2.stop(now + 0.8); 3011 } catch (e) { 3012 console.warn("Audio chime playback failed:", e); 3013 } 3014 setTimeout(() => { 3015 setShowSuccess(false); 3016 onComplete(); 3017 }, 1500); 3018 }, [onComplete]); 3019 const activeTrigger = triggerSelector || targetSelector; 3020 reactExports.useEffect(() => { 3021 if (!isActive || !activeTrigger || showSuccess) return; 3022 const handleGlobalClick = (e) => { 3023 const targetEl = document.querySelector(activeTrigger); 3024 if (targetEl && (targetEl === e.target || targetEl.contains(e.target))) { 3025 handleSuccess(); 3026 } 3027 }; 3028 document.addEventListener("click", handleGlobalClick, true); 3029 return () => { 3030 document.removeEventListener("click", handleGlobalClick, true); 3031 }; 3032 }, [isActive, activeTrigger, showSuccess, handleSuccess]); 3033 if (!isActive) return null; 3034 if (showSuccess) { 3035 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 bg-black/60 backdrop-blur-sm z-[250] flex flex-col items-center justify-center animate-fade-in pointer-events-auto", children: [
3036 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[var(--daw-elevated)] border border-[var(--daw-border-strong)] rounded-2xl p-8 flex flex-col items-center max-w-sm text-center shadow-2xl space-y-4 animate-scale-up", children: [ 3037 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-16 rounded-full bg-emerald-950/50 border-2 border-emerald-500 flex items-center justify-center text-emerald-450 animate-bounce", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { className: "w-9 h-9" }) }), 3038 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-emerald-500", children: "Success!" }), 3039 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[var(--daw-text)] text-sm font-semibold", children: successMessage }) 3040 ] }), 3041 /* @__PURE__ */ jsxRuntimeExports.jsx("style", { dangerouslySetInnerHTML: { __html: ` 3042 @keyframes scaleUp { 3043 from { transform: scale(0.9); opacity: 0; } 3044 to { transform: scale(1); opacity: 1; } 3045 } 3046 .animate-scale-up { 3047 animation: scaleUp 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; 3048 } 3049 ` } }) 3050 ] }); 3051 } 3052 if (!bounds) return null; 3053 const isTriggerInBottomHalf = triggerBounds ? triggerBounds.top > window.innerHeight / 2 : false; 3054 const isTargetInTopHalf = bounds.top < 150; 3055 let positionStyle = {}; 3056 let positionClass = ""; 3057 if (dragPosition) { 3058 positionStyle = { 3059 left: `${dragPosition.x}px`, 3060 top: `${dragPosition.y}px`, 3061 transform: "none" 3062 }; 3063 } else { 3064 if (isTriggerInBottomHalf || bounds.top >= 150) { 3065 positionClass = "top-6 left-1/2 transform -translate-x-1/2"; 3066 } else if (isTargetInTopHalf) { 3067 positionClass = "bottom-6 left-1/2 transform -translate-x-1/2"; 3068 } else { 3069 positionClass = "top-6 left-1/2 transform -translate-x-1/2"; 3070 } 3071 } 3072 const handleOverlayPointerDown = (e) => { 3073 var _a, _b; 3074 const x = e.clientX; 3075 const y = e.clientY; 3076 const isTarget = bounds && x >= bounds.left - 4 && x <= bounds.right + 4 && y >= bounds.top - 4 && y <= bounds.bottom + 4; 3077 const isTrigger = triggerBounds && x >= triggerBounds.left - 4 && x <= triggerBounds.right + 4 && y >= triggerBounds.top - 4 && y <= triggerBounds.bottom + 4; 3078 if (isTarget || isTrigger) { 3079 const overlayEl = e.currentTarget; 3080 overlayEl.style.pointerEvents = "none"; 3081 const targetEl = document.elementFromPoint(x, y); 3082 if (targetEl) { 3083 (_a = targetEl.focus) == null ? void 0 : _a.call(targetEl); 3084 (_b = targetEl.click) == null ? void 0 : _b.call(targetEl); 3085 } 3086 setTimeout(() => { 3087 if (overlayEl) overlayEl.style.pointerEvents = "auto"; 3088 }, 100); 3089 } 3090 }; 3091 return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 3092 /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "fixed inset-0 w-0 h-0 pointer-events-none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("mask", { id: "interaction-spotlight-mask", maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "100%", height: "100%", children: [
3093 /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "0", y: "0", width: "100%", height: "100%", fill: "white" }), 3094 bounds && /* @__PURE__ */ jsxRuntimeExports.jsx( 3095 "rect", 3096 { 3097 x: bounds.left - 4, 3098 y: bounds.top - 4, 3099 width: bounds.width + 8, 3100 height: bounds.height + 8, 3101 rx: "8", 3102 fill: "black" 3103 } 3104 ), 3105 triggerBounds && /* @__PURE__ */ jsxRuntimeExports.jsx( 3106 "rect", 3107 { 3108 x: triggerBounds.left - 4, 3109 y: triggerBounds.top - 4, 3110 width: triggerBounds.width + 8, 3111 height: triggerBounds.height + 8, 3112 rx: "8", 3113 fill: "black" 3114 } 3115 ) 3116 ] }) }) }), 3117 /* @__PURE__ */ jsxRuntimeExports.jsx( 3118 "div", 3119 { 3120 onPointerDown: handleOverlayPointerDown, 3121 className: "fixed inset-0 pointer-events-auto z-[140] bg-black/75 transition-opacity duration-300 select-none", 3122 style: { 3123 maskImage: "url(#interaction-spotlight-mask)", 3124 WebkitMaskImage: "url(#interaction-spotlight-mask)", 3125 clipPath: bounds ? `polygon( 3126 0% 0%, 3127 0% 100%, 3128 ${bounds.left}px 100%, 3129 ${bounds.left}px ${bounds.top}px, 3130 ${bounds.right}px ${bounds.top}px, 3131 ${bounds.right}px ${bounds.bottom}px, 3132 ${bounds.left}px ${bounds.bottom}px, 3133 ${bounds.left}px 100%, 3134 100% 100%, 3135 100% 0% 3136 )` : void 0 3137 } 3138 } 3139 ), 3140 /* @__PURE__ */ jsxRuntimeExports.jsx( 3141 "div", 3142 { 3143 className: "fixed z-[145] pointer-events-none border-2 border-emerald-500 rounded-lg shadow-[0_0_20px_rgba(16,185,129,0.5)] animate-pulse", 3144 style: { 3145 left: bounds.left - 4, 3146 top: bounds.top - 4, 3147 width: bounds.width + 8, 3148 height: bounds.height + 8, 3149 transition: "all 200ms ease-out" 3150 }, 3151 children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 border-2 border-emerald-400 rounded-lg animate-ping opacity-60", style: { animationDuration: "1.2s" } }) 3152 } 3153 ),
3154 triggerBounds && /* @__PURE__ */ jsxRuntimeExports.jsx( 3155 "div", 3156 { 3157 className: "fixed z-[146] pointer-events-none border-2 border-blue-500 rounded-lg shadow-[0_0_20px_rgba(59,130,246,0.6)] animate-pulse", 3158 style: { 3159 left: triggerBounds.left - 4, 3160 top: triggerBounds.top - 4, 3161 width: triggerBounds.width + 8, 3162 height: triggerBounds.height + 8, 3163 transition: "all 200ms ease-out" 3164 }, 3165 children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 border-2 border-blue-400 rounded-lg animate-ping opacity-75", style: { animationDuration: "1s" } }) 3166 } 3167 ), 3168 /* @__PURE__ */ jsxRuntimeExports.jsx( 3169 "div", 3170 { 3171 ref: bannerRef, 3172 className: `fixed z-[148] w-[90%] max-w-md animate-fade-in pointer-events-auto select-none ${positionClass}`, 3173 style: positionStyle, 3174 children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[var(--daw-elevated)] border border-[var(--daw-border-strong)] rounded-xl shadow-2xl overflow-hidden backdrop-blur-md", children: [
3175 /* @__PURE__ */ jsxRuntimeExports.jsxs( 3176 "div", 3177 { 3178 onPointerDown: handlePointerDown, 3179 onPointerMove: handlePointerMove, 3180 onPointerUp: handlePointerUp, 3181 className: `px-3 py-1.5 bg-[var(--daw-surface)] border-b border-[var(--daw-border-strong)] flex items-center justify-between text-[10px] font-bold text-emerald-400 uppercase tracking-widest ${isDragging ? "cursor-grabbing" : "cursor-grab"}`, 3182 title: "Click and drag to move this prompt box", 3183 children: [ 3184 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5", children: [ 3185 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "w-3.5 h-3.5 animate-pulse text-emerald-400" }), 3186 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Interactive Challenge" }) 3187 ] }), 3188 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-[var(--daw-text-secondary)] font-normal text-[9px] lowercase opacity-80", children: [
3189 /* @__PURE__ */ jsxRuntimeExports.jsx(GripHorizontal, { className: "w-3.5 h-3.5" }), 3190 " drag to move" 3191 ] }) 3192 ] 3193 } 3194 ), 3195 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3.5 flex items-center space-x-3", children: [ 3196 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-2 rounded-lg bg-emerald-950/50 border border-emerald-500/20 text-emerald-400 shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "w-5 h-5 animate-pulse" }) }), 3197 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[var(--daw-text)] text-xs font-bold leading-relaxed", children: promptText }) }) 3198 ] }) 3199 ] }) 3200 } 3201 ) 3202 ] }); 3203}; 3204const StaffTargetIndicator = ({ 3205 targetSelector, 3206 pitchLabel = "C4 (Middle C)", 3207 isActive 3208}) => { 3209 const [rect, setRect] = reactExports.useState(null); 3210 reactExports.useEffect(() => { 3211 if (!isActive) { 3212 setRect(null); 3213 return; 3214 } 3215 const updateBounds = () => { 3216 let el = null; 3217 if (targetSelector) { 3218 try { 3219 el = document.querySelector(targetSelector); 3220 } catch (e) { 3221 } 3222 } 3223 if (!el) { 3224 el = document.querySelector(".selected-note, .notehead, svg .note") || document.querySelector(".daw-desk"); 3225 } 3226 if (el) { 3227 const r = el.getBoundingClientRect(); 3228 setRect({ 3229 left: r.left, 3230 top: r.top, 3231 width: r.width || 40, 3232 height: r.height || 24 3233 }); 3234 } 3235 }; 3236 updateBounds(); 3237 const interval = setInterval(updateBounds, 150); 3238 window.addEventListener("resize", updateBounds); 3239 window.addEventListener("scroll", updateBounds, true); 3240 return () => { 3241 clearInterval(interval); 3242 window.removeEventListener("resize", updateBounds); 3243 window.removeEventListener("scroll", updateBounds, true); 3244 }; 3245 }, [targetSelector, isActive]); 3246 if (!isActive || !rect) return null; 3247 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 pointer-events-none z-[9995]", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 3248 "div", 3249 { 3250 className: "absolute transition-all duration-300 rounded-md border-2 border-emerald-400 bg-emerald-500/20 shadow-[0_0_20px_rgba(16,185,129,0.6)] animate-pulse flex items-center justify-end pr-2", 3251 style: { 3252 left: `${Math.max(10, rect.left - 20)}px`, 3253 top: `${rect.top - 4}px`, 3254 width: `${Math.max(120, rect.width + 40)}px`, 3255 height: `${Math.max(28, rect.height + 8)}px` 3256 }, 3257 children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-extrabold uppercase tracking-wider text-emerald-300 bg-emerald-950/80 px-2 py-0.5 rounded border border-emerald-500/50 shadow", children: [ 3258 "ð¯ ", 3259 pitchLabel 3260 ] }) 3261 } 3262 ) }); 3263}; 3264const TutorialManager = ({ 3265 setHumdrumContent, 3266 humdrumContent, 3267 editorRef, 3268 mode, 3269 playNotePreview, 3270 onExit, 3271 theme = "dark", 3272 tutorialStarted, 3273 setTutorialStarted 3274}) => { 3275 var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; 3276 console.log(`[TutorialManager] Rendering! tutorialStarted=${tutorialStarted}`); 3277 const engine = useTutorialEngine({ 3278 setHumdrumContent, 3279 humdrumContent, 3280 editorRef, 3281 mode, 3282 playNotePreview, 3283 onExit, 3284 tutorialStarted, 3285 setTutorialStarted 3286 }); 3287 return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 3288 /* @__PURE__ */ jsxRuntimeExports.jsx( 3289 TutorialPanel, 3290 { 3291 currentChapterIndex: engine.currentChapterIndex, 3292 currentStepIndex: engine.currentStepIndex, 3293 isPlaying: engine.isPlaying, 3294 setIsPlaying: engine.setIsPlaying, 3295 speed: engine.speed, 3296 setSpeed: engine.setSpeed, 3297 useTts: engine.useTts, 3298 setUseTts: engine.setUseTts, 3299 isCompleted: engine.isCompleted, 3300 handleNext: engine.handleNext, 3301 handlePrev: engine.handlePrev, 3302 jumpToChapter: engine.jumpToChapter, 3303 jumpToGlobalStep: engine.jumpToGlobalStep, 3304 getGlobalStepIndex: engine.getGlobalStepIndex, 3305 getGlobalStepCount: engine.getGlobalStepCount, 3306 onExit: engine.onExit, 3307 theme, 3308 autoPlayMode: engine.autoPlayMode, 3309 setAutoPlayMode: engine.setAutoPlayMode, 3310 mode: engine.mode, 3311 setMode: engine.setMode, 3312 isStuckHintVisible: engine.isStuckHintVisible, 3313 showMeThisStep: engine.showMeThisStep, 3314 skipStep: engine.skipStep, 3315 tutorialStarted, 3316 setTutorialStarted, 3317 lessonType: engine.lessonType, 3318 setLessonType: engine.setLessonType, 3319 stepByStepMode: engine.stepByStepMode, 3320 setStepByStepMode: engine.setStepByStepMode, 3321 completedMilestones: engine.completedMilestones, 3322 isSpeaking: engine.isSpeaking 3323 } 3324 ), 3325 (engine.mode === "watch" || engine.isShowMeActive) && !engine.isWaitingForInteraction && /* @__P
3325URE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 3326 /* @__PURE__ */ jsxRuntimeExports.jsx( 3327 VirtualCursor, 3328 { 3329 targetSelector: (_b = (_a = engine.currentStep) == null ? void 0 : _a.cursorTarget) == null ? void 0 : _b.selector, 3330 clickTriggered: engine.isPlaying, 3331 clickType: ((_d = (_c = engine.currentStep) == null ? void 0 : _c.action) == null ? void 0 : _d.type) === "click" && engine.currentStep.action.clickType ? engine.currentStep.action.clickType : "left", 3332 speedMultiplier: engine.speed 3333 } 3334 ), 3335 /* @__PURE__ */ jsxRuntimeExports.jsx( 3336 StaffTargetIndicator, 3337 { 3338 targetSelector: (_f = (_e = engine.currentStep) == null ? void 0 : _e.cursorTarget) == null ? void 0 : _f.selector, 3339 pitchLabel: ((_g = engine.currentStep) == null ? void 0 : _g.instruction) || "Pitch Target", 3340 isActive: true 3341 } 3342 ) 3343 ] }), 3344 engine.mode === "guided" && !engine.isShowMeActive && !engine.isWaitingForInteraction && /* @__P
3344URE__ */ jsxRuntimeExports.jsx( 3345 TutorialHints, 3346 { 3347 targetSelector: (_i = (_h = engine.currentStep) == null ? void 0 : _h.cursorTarget) == null ? void 0 : _i.selector, 3348 isActive: true, 3349 speedMultiplier: engine.speed 3350 } 3351 ), 3352 engine.isWaitingForInteraction && ((_j = engine.currentStep) == null ? void 0 : _j.interactionMilestone) && /* @__PURE__ */ jsxRuntimeExports.jsx( 3353 InteractionOverlay, 3354 { 3355 isActive: true, 3356 targetSelector: engine.currentStep.interactionMilestone.targetSelector, 3357 triggerSelector: engine.currentStep.interactionMilestone.triggerSelector, 3358 promptText: engine.currentStep.interactionMilestone.promptText, 3359 successMessage: engine.currentStep.interactionMilestone.successMessage, 3360 onComplete: () => { 3361 engine.setIsWaitingForInteraction(false); 3362 engine.handleNext(); 3363 } 3364 } 3365 ) 3366 ] }); 3367}; 3368export { 3369 TutorialManager, 3370 TutorialManager as default 3371};
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.