Oviya commited on
Commit
972a21e
·
1 Parent(s): a998588

update pronvideo

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. package-lock.json +1730 -4
  2. src/app/app.module.ts +5 -0
  3. src/app/home/home.component.html +10 -0
  4. src/app/home/home.component.ts +20 -0
  5. src/app/pronunciationragg/pronunciationragg.component.css +785 -0
  6. src/app/pronunciationragg/pronunciationragg.component.html +129 -0
  7. src/app/pronunciationragg/pronunciationragg.component.ts +950 -0
  8. src/app/pronunciationvideo/pronunciationvideo.component.css +785 -0
  9. src/app/pronunciationvideo/pronunciationvideo.component.html +129 -0
  10. src/app/pronunciationvideo/pronunciationvideo.component.ts +911 -0
  11. src/assets/pronvideo/audio.png +3 -0
  12. src/assets/pronvideo/audio/apple.mp3 +3 -0
  13. src/assets/pronvideo/audio/ball.mp3 +3 -0
  14. src/assets/pronvideo/audio/cat.mp3 +3 -0
  15. src/assets/pronvideo/audio/dog.mp3 +3 -0
  16. src/assets/pronvideo/audio/egg.mp3 +3 -0
  17. src/assets/pronvideo/audio/fish.mp3 +3 -0
  18. src/assets/pronvideo/audio/grapes.mp3 +3 -0
  19. src/assets/pronvideo/audio/hat.mp3 +3 -0
  20. src/assets/pronvideo/audio/icecream.mp3 +3 -0
  21. src/assets/pronvideo/audio/jar.mp3 +3 -0
  22. src/assets/pronvideo/audio/kite.mp3 +3 -0
  23. src/assets/pronvideo/audio/lion.mp3 +3 -0
  24. src/assets/pronvideo/audio/moon.mp3 +3 -0
  25. src/assets/pronvideo/audio/nest.mp3 +3 -0
  26. src/assets/pronvideo/audio/orange.mp3 +3 -0
  27. src/assets/pronvideo/audio/pig.mp3 +3 -0
  28. src/assets/pronvideo/audio/queen.mp3 +3 -0
  29. src/assets/pronvideo/audio/rabbit.mp3 +3 -0
  30. src/assets/pronvideo/audio/sun.mp3 +3 -0
  31. src/assets/pronvideo/audio/tree.mp3 +3 -0
  32. src/assets/pronvideo/audio/umbrella.mp3 +3 -0
  33. src/assets/pronvideo/audio/van.mp3 +3 -0
  34. src/assets/pronvideo/audio/watch.mp3 +3 -0
  35. src/assets/pronvideo/audio/xylophone.mp3 +3 -0
  36. src/assets/pronvideo/audio/yarn.mp3 +3 -0
  37. src/assets/pronvideo/audio/zebra.mp3 +3 -0
  38. src/assets/pronvideo/feedback/consonant.mp4 +3 -0
  39. src/assets/pronvideo/feedback/ending.mp4 +3 -0
  40. src/assets/pronvideo/feedback/multipleword.mp4 +3 -0
  41. src/assets/pronvideo/feedback/silence.mp4 +3 -0
  42. src/assets/pronvideo/feedback/stress.mp4 +3 -0
  43. src/assets/pronvideo/feedback/success.mp4 +3 -0
  44. src/assets/pronvideo/feedback/syllable.mp4 +3 -0
  45. src/assets/pronvideo/feedback/vowels.mp4 +3 -0
  46. src/assets/pronvideo/feedback/wrongword.mp4 +3 -0
  47. src/assets/pronvideo/pause.png +3 -0
  48. src/assets/pronvideo/play.png +3 -0
  49. src/assets/pronvideo/teacher-nod.mp4 +3 -0
  50. src/assets/pronvideo/teacher.png +3 -0
package-lock.json CHANGED
@@ -13,6 +13,7 @@
13
  "@angular/compiler": "^17.1.0",
14
  "@angular/core": "^17.1.0",
15
  "@angular/forms": "^17.1.0",
 
16
  "@angular/platform-browser": "^17.1.0",
17
  "@angular/platform-browser-dynamic": "^17.1.0",
18
  "@angular/router": "^17.1.0",
@@ -26,6 +27,7 @@
26
  "rxjs": "~7.8.0",
27
  "tailwindcss": "^3.4.17",
28
  "tslib": "^2.3.0",
 
29
  "zone.js": "~0.14.3"
30
  },
31
  "devDependencies": {
@@ -362,6 +364,24 @@
362
  "@angular/core": "17.3.12"
363
  }
364
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  "node_modules/@angular/cli": {
366
  "version": "17.3.11",
367
  "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.11.tgz",
@@ -535,6 +555,71 @@
535
  "rxjs": "^6.5.3 || ^7.4.0"
536
  }
537
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
  "node_modules/@angular/platform-browser": {
539
  "version": "17.3.12",
540
  "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
@@ -3196,6 +3281,808 @@
3196
  "node": ">= 0.4"
3197
  }
3198
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3199
  "node_modules/@ngtools/webpack": {
3200
  "version": "17.3.11",
3201
  "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.11.tgz",
@@ -6191,7 +7078,7 @@
6191
  "version": "4.5.0",
6192
  "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
6193
  "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
6194
- "dev": true,
6195
  "engines": {
6196
  "node": ">=0.12"
6197
  },
@@ -10274,7 +11161,7 @@
10274
  "version": "7.2.1",
10275
  "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
10276
  "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
10277
- "dev": true,
10278
  "dependencies": {
10279
  "entities": "^4.5.0"
10280
  },
@@ -11405,6 +12292,12 @@
11405
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
11406
  "dev": true
11407
  },
 
 
 
 
 
 
11408
  "node_modules/sass": {
11409
  "version": "1.71.1",
11410
  "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
@@ -13484,6 +14377,12 @@
13484
  "node": ">=10.13.0"
13485
  }
13486
  },
 
 
 
 
 
 
13487
  "node_modules/wbuf": {
13488
  "version": "1.7.3",
13489
  "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
@@ -14136,6 +15035,16 @@
14136
  "tslib": "^2.3.0"
14137
  }
14138
  },
 
 
 
 
 
 
 
 
 
 
14139
  "@angular/cli": {
14140
  "version": "17.3.11",
14141
  "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.11.tgz",
@@ -14249,6 +15158,61 @@
14249
  "tslib": "^2.3.0"
14250
  }
14251
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14252
  "@angular/platform-browser": {
14253
  "version": "17.3.12",
14254
  "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
@@ -16010,6 +16974,758 @@
16010
  "call-bind": "^1.0.7"
16011
  }
16012
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16013
  "@ngtools/webpack": {
16014
  "version": "17.3.11",
16015
  "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.11.tgz",
@@ -18267,7 +19983,7 @@
18267
  "version": "4.5.0",
18268
  "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
18269
  "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
18270
- "dev": true
18271
  },
18272
  "env-paths": {
18273
  "version": "2.2.1",
@@ -21309,7 +23025,7 @@
21309
  "version": "7.2.1",
21310
  "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
21311
  "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
21312
- "dev": true,
21313
  "requires": {
21314
  "entities": "^4.5.0"
21315
  }
@@ -22076,6 +23792,11 @@
22076
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
22077
  "dev": true
22078
  },
 
 
 
 
 
22079
  "sass": {
22080
  "version": "1.71.1",
22081
  "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
@@ -23461,6 +25182,11 @@
23461
  "graceful-fs": "^4.1.2"
23462
  }
23463
  },
 
 
 
 
 
23464
  "wbuf": {
23465
  "version": "1.7.3",
23466
  "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
 
13
  "@angular/compiler": "^17.1.0",
14
  "@angular/core": "^17.1.0",
15
  "@angular/forms": "^17.1.0",
16
+ "@angular/material": "^17.3.10",
17
  "@angular/platform-browser": "^17.1.0",
18
  "@angular/platform-browser-dynamic": "^17.1.0",
19
  "@angular/router": "^17.1.0",
 
27
  "rxjs": "~7.8.0",
28
  "tailwindcss": "^3.4.17",
29
  "tslib": "^2.3.0",
30
+ "wavesurfer.js": "^7.11.1",
31
  "zone.js": "~0.14.3"
32
  },
33
  "devDependencies": {
 
364
  "@angular/core": "17.3.12"
365
  }
366
  },
367
+ "node_modules/@angular/cdk": {
368
+ "version": "17.3.10",
369
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.10.tgz",
370
+ "integrity": "sha512-b1qktT2c1TTTe5nTji/kFAVW92fULK0YhYAvJ+BjZTPKu2FniZNe8o4qqQ0pUuvtMu+ZQxp/QqFYoidIVCjScg==",
371
+ "license": "MIT",
372
+ "peer": true,
373
+ "dependencies": {
374
+ "tslib": "^2.3.0"
375
+ },
376
+ "optionalDependencies": {
377
+ "parse5": "^7.1.2"
378
+ },
379
+ "peerDependencies": {
380
+ "@angular/common": "^17.0.0 || ^18.0.0",
381
+ "@angular/core": "^17.0.0 || ^18.0.0",
382
+ "rxjs": "^6.5.3 || ^7.4.0"
383
+ }
384
+ },
385
  "node_modules/@angular/cli": {
386
  "version": "17.3.11",
387
  "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.11.tgz",
 
555
  "rxjs": "^6.5.3 || ^7.4.0"
556
  }
557
  },
558
+ "node_modules/@angular/material": {
559
+ "version": "17.3.10",
560
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.10.tgz",
561
+ "integrity": "sha512-hHMQES0tQPH5JW33W+mpBPuM8ybsloDTqFPuRV8cboDjosAWfJhzAKF3ozICpNlUrs62La/2Wu/756GcQrxebg==",
562
+ "license": "MIT",
563
+ "dependencies": {
564
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
565
+ "@material/auto-init": "15.0.0-canary.7f224ddd4.0",
566
+ "@material/banner": "15.0.0-canary.7f224ddd4.0",
567
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
568
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
569
+ "@material/card": "15.0.0-canary.7f224ddd4.0",
570
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
571
+ "@material/chips": "15.0.0-canary.7f224ddd4.0",
572
+ "@material/circular-progress": "15.0.0-canary.7f224ddd4.0",
573
+ "@material/data-table": "15.0.0-canary.7f224ddd4.0",
574
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
575
+ "@material/dialog": "15.0.0-canary.7f224ddd4.0",
576
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
577
+ "@material/drawer": "15.0.0-canary.7f224ddd4.0",
578
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
579
+ "@material/fab": "15.0.0-canary.7f224ddd4.0",
580
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
581
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
582
+ "@material/form-field": "15.0.0-canary.7f224ddd4.0",
583
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
584
+ "@material/image-list": "15.0.0-canary.7f224ddd4.0",
585
+ "@material/layout-grid": "15.0.0-canary.7f224ddd4.0",
586
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
587
+ "@material/linear-progress": "15.0.0-canary.7f224ddd4.0",
588
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
589
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
590
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
591
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
592
+ "@material/radio": "15.0.0-canary.7f224ddd4.0",
593
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
594
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
595
+ "@material/segmented-button": "15.0.0-canary.7f224ddd4.0",
596
+ "@material/select": "15.0.0-canary.7f224ddd4.0",
597
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
598
+ "@material/slider": "15.0.0-canary.7f224ddd4.0",
599
+ "@material/snackbar": "15.0.0-canary.7f224ddd4.0",
600
+ "@material/switch": "15.0.0-canary.7f224ddd4.0",
601
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
602
+ "@material/tab-bar": "15.0.0-canary.7f224ddd4.0",
603
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
604
+ "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0",
605
+ "@material/textfield": "15.0.0-canary.7f224ddd4.0",
606
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
607
+ "@material/tooltip": "15.0.0-canary.7f224ddd4.0",
608
+ "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0",
609
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
610
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
611
+ "tslib": "^2.3.0"
612
+ },
613
+ "peerDependencies": {
614
+ "@angular/animations": "^17.0.0 || ^18.0.0",
615
+ "@angular/cdk": "17.3.10",
616
+ "@angular/common": "^17.0.0 || ^18.0.0",
617
+ "@angular/core": "^17.0.0 || ^18.0.0",
618
+ "@angular/forms": "^17.0.0 || ^18.0.0",
619
+ "@angular/platform-browser": "^17.0.0 || ^18.0.0",
620
+ "rxjs": "^6.5.3 || ^7.4.0"
621
+ }
622
+ },
623
  "node_modules/@angular/platform-browser": {
624
  "version": "17.3.12",
625
  "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
 
3281
  "node": ">= 0.4"
3282
  }
3283
  },
3284
+ "node_modules/@material/animation": {
3285
+ "version": "15.0.0-canary.7f224ddd4.0",
3286
+ "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.7f224ddd4.0.tgz",
3287
+ "integrity": "sha512-1GSJaPKef+7HRuV+HusVZHps64cmZuOItDbt40tjJVaikcaZvwmHlcTxRIqzcRoCdt5ZKHh3NoO7GB9Khg4Jnw==",
3288
+ "license": "MIT",
3289
+ "dependencies": {
3290
+ "tslib": "^2.1.0"
3291
+ }
3292
+ },
3293
+ "node_modules/@material/auto-init": {
3294
+ "version": "15.0.0-canary.7f224ddd4.0",
3295
+ "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.7f224ddd4.0.tgz",
3296
+ "integrity": "sha512-t7ZGpRJ3ec0QDUO0nJu/SMgLW7qcuG2KqIsEYD1Ej8qhI2xpdR2ydSDQOkVEitXmKoGol1oq4nYSBjTlB65GqA==",
3297
+ "license": "MIT",
3298
+ "dependencies": {
3299
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3300
+ "tslib": "^2.1.0"
3301
+ }
3302
+ },
3303
+ "node_modules/@material/banner": {
3304
+ "version": "15.0.0-canary.7f224ddd4.0",
3305
+ "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.7f224ddd4.0.tgz",
3306
+ "integrity": "sha512-g9wBUZzYBizyBcBQXTIafnRUUPi7efU9gPJfzeGgkynXiccP/vh5XMmH+PBxl5v+4MlP/d4cZ2NUYoAN7UTqSA==",
3307
+ "license": "MIT",
3308
+ "dependencies": {
3309
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3310
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
3311
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3312
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3313
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3314
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3315
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3316
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3317
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3318
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3319
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3320
+ "tslib": "^2.1.0"
3321
+ }
3322
+ },
3323
+ "node_modules/@material/base": {
3324
+ "version": "15.0.0-canary.7f224ddd4.0",
3325
+ "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.7f224ddd4.0.tgz",
3326
+ "integrity": "sha512-I9KQOKXpLfJkP8MqZyr8wZIzdPHrwPjFvGd9zSK91/vPyE4hzHRJc/0njsh9g8Lm9PRYLbifXX+719uTbHxx+A==",
3327
+ "license": "MIT",
3328
+ "dependencies": {
3329
+ "tslib": "^2.1.0"
3330
+ }
3331
+ },
3332
+ "node_modules/@material/button": {
3333
+ "version": "15.0.0-canary.7f224ddd4.0",
3334
+ "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.7f224ddd4.0.tgz",
3335
+ "integrity": "sha512-BHB7iyHgRVH+JF16+iscR+Qaic+p7LU1FOLgP8KucRlpF9tTwIxQA6mJwGRi5gUtcG+vyCmzVS+hIQ6DqT/7BA==",
3336
+ "license": "MIT",
3337
+ "dependencies": {
3338
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3339
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3340
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3341
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3342
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
3343
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3344
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3345
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3346
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3347
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3348
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
3349
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3350
+ "tslib": "^2.1.0"
3351
+ }
3352
+ },
3353
+ "node_modules/@material/card": {
3354
+ "version": "15.0.0-canary.7f224ddd4.0",
3355
+ "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.7f224ddd4.0.tgz",
3356
+ "integrity": "sha512-kt7y9/IWOtJTr3Z/AoWJT3ZLN7CLlzXhx2udCLP9ootZU2bfGK0lzNwmo80bv/pJfrY9ihQKCtuGTtNxUy+vIw==",
3357
+ "license": "MIT",
3358
+ "dependencies": {
3359
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3360
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3361
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3362
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3363
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3364
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3365
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3366
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3367
+ "tslib": "^2.1.0"
3368
+ }
3369
+ },
3370
+ "node_modules/@material/checkbox": {
3371
+ "version": "15.0.0-canary.7f224ddd4.0",
3372
+ "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.7f224ddd4.0.tgz",
3373
+ "integrity": "sha512-rURcrL5O1u6hzWR+dNgiQ/n89vk6tdmdP3mZgnxJx61q4I/k1yijKqNJSLrkXH7Rto3bM5NRKMOlgvMvVd7UMQ==",
3374
+ "license": "MIT",
3375
+ "dependencies": {
3376
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3377
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3378
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3379
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3380
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3381
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
3382
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3383
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3384
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3385
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
3386
+ "tslib": "^2.1.0"
3387
+ }
3388
+ },
3389
+ "node_modules/@material/chips": {
3390
+ "version": "15.0.0-canary.7f224ddd4.0",
3391
+ "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.7f224ddd4.0.tgz",
3392
+ "integrity": "sha512-AYAivV3GSk/T/nRIpH27sOHFPaSMrE3L0WYbnb5Wa93FgY8a0fbsFYtSH2QmtwnzXveg+B1zGTt7/xIIcynKdQ==",
3393
+ "license": "MIT",
3394
+ "dependencies": {
3395
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3396
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3397
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
3398
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3399
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3400
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3401
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3402
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
3403
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3404
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3405
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3406
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3407
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3408
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
3409
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3410
+ "safevalues": "^0.3.4",
3411
+ "tslib": "^2.1.0"
3412
+ }
3413
+ },
3414
+ "node_modules/@material/circular-progress": {
3415
+ "version": "15.0.0-canary.7f224ddd4.0",
3416
+ "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.7f224ddd4.0.tgz",
3417
+ "integrity": "sha512-DJrqCKb+LuGtjNvKl8XigvyK02y36GRkfhMUYTcJEi3PrOE00bwXtyj7ilhzEVshQiXg6AHGWXtf5UqwNrx3Ow==",
3418
+ "license": "MIT",
3419
+ "dependencies": {
3420
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3421
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3422
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3423
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3424
+ "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0",
3425
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3426
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3427
+ "tslib": "^2.1.0"
3428
+ }
3429
+ },
3430
+ "node_modules/@material/data-table": {
3431
+ "version": "15.0.0-canary.7f224ddd4.0",
3432
+ "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.7f224ddd4.0.tgz",
3433
+ "integrity": "sha512-/2WZsuBIq9z9RWYF5Jo6b7P6u0fwit+29/mN7rmAZ6akqUR54nXyNfoSNiyydMkzPlZZsep5KrSHododDhBZbA==",
3434
+ "license": "MIT",
3435
+ "dependencies": {
3436
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3437
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3438
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
3439
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3440
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3441
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3442
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3443
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
3444
+ "@material/linear-progress": "15.0.0-canary.7f224ddd4.0",
3445
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
3446
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
3447
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3448
+ "@material/select": "15.0.0-canary.7f224ddd4.0",
3449
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3450
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3451
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3452
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
3453
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3454
+ "tslib": "^2.1.0"
3455
+ }
3456
+ },
3457
+ "node_modules/@material/density": {
3458
+ "version": "15.0.0-canary.7f224ddd4.0",
3459
+ "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.7f224ddd4.0.tgz",
3460
+ "integrity": "sha512-o9EXmGKVpiQ6mHhyV3oDDzc78Ow3E7v8dlaOhgaDSXgmqaE8v5sIlLNa/LKSyUga83/fpGk3QViSGXotpQx0jA==",
3461
+ "license": "MIT",
3462
+ "dependencies": {
3463
+ "tslib": "^2.1.0"
3464
+ }
3465
+ },
3466
+ "node_modules/@material/dialog": {
3467
+ "version": "15.0.0-canary.7f224ddd4.0",
3468
+ "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.7f224ddd4.0.tgz",
3469
+ "integrity": "sha512-u0XpTlv1JqWC/bQ3DavJ1JguofTelLT2wloj59l3/1b60jv42JQ6Am7jU3I8/SIUB1MKaW7dYocXjDWtWJakLA==",
3470
+ "license": "MIT",
3471
+ "dependencies": {
3472
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3473
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3474
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
3475
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3476
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3477
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3478
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
3479
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3480
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3481
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3482
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3483
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3484
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
3485
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3486
+ "tslib": "^2.1.0"
3487
+ }
3488
+ },
3489
+ "node_modules/@material/dom": {
3490
+ "version": "15.0.0-canary.7f224ddd4.0",
3491
+ "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.7f224ddd4.0.tgz",
3492
+ "integrity": "sha512-mQ1HT186GPQSkRg5S18i70typ5ZytfjL09R0gJ2Qg5/G+MLCGi7TAjZZSH65tuD/QGOjel4rDdWOTmYbPYV6HA==",
3493
+ "license": "MIT",
3494
+ "dependencies": {
3495
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3496
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3497
+ "tslib": "^2.1.0"
3498
+ }
3499
+ },
3500
+ "node_modules/@material/drawer": {
3501
+ "version": "15.0.0-canary.7f224ddd4.0",
3502
+ "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.7f224ddd4.0.tgz",
3503
+ "integrity": "sha512-qyO0W0KBftfH8dlLR0gVAgv7ZHNvU8ae11Ao6zJif/YxcvK4+gph1z8AO4H410YmC2kZiwpSKyxM1iQCCzbb4g==",
3504
+ "license": "MIT",
3505
+ "dependencies": {
3506
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3507
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3508
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3509
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3510
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3511
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
3512
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3513
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3514
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3515
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3516
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3517
+ "tslib": "^2.1.0"
3518
+ }
3519
+ },
3520
+ "node_modules/@material/elevation": {
3521
+ "version": "15.0.0-canary.7f224ddd4.0",
3522
+ "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.7f224ddd4.0.tgz",
3523
+ "integrity": "sha512-tV6s4/pUBECedaI36Yj18KmRCk1vfue/JP/5yYRlFNnLMRVISePbZaKkn/BHXVf+26I3W879+XqIGlDVdmOoMA==",
3524
+ "license": "MIT",
3525
+ "dependencies": {
3526
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3527
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3528
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3529
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3530
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3531
+ "tslib": "^2.1.0"
3532
+ }
3533
+ },
3534
+ "node_modules/@material/fab": {
3535
+ "version": "15.0.0-canary.7f224ddd4.0",
3536
+ "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.7f224ddd4.0.tgz",
3537
+ "integrity": "sha512-4h76QrzfZTcPdd+awDPZ4Q0YdSqsXQnS540TPtyXUJ/5G99V6VwGpjMPIxAsW0y+pmI9UkLL/srrMaJec+7r4Q==",
3538
+ "license": "MIT",
3539
+ "dependencies": {
3540
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3541
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3542
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3543
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3544
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
3545
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3546
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3547
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3548
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3549
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3550
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
3551
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3552
+ "tslib": "^2.1.0"
3553
+ }
3554
+ },
3555
+ "node_modules/@material/feature-targeting": {
3556
+ "version": "15.0.0-canary.7f224ddd4.0",
3557
+ "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.7f224ddd4.0.tgz",
3558
+ "integrity": "sha512-SAjtxYh6YlKZriU83diDEQ7jNSP2MnxKsER0TvFeyG1vX/DWsUyYDOIJTOEa9K1N+fgJEBkNK8hY55QhQaspew==",
3559
+ "license": "MIT",
3560
+ "dependencies": {
3561
+ "tslib": "^2.1.0"
3562
+ }
3563
+ },
3564
+ "node_modules/@material/floating-label": {
3565
+ "version": "15.0.0-canary.7f224ddd4.0",
3566
+ "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.7f224ddd4.0.tgz",
3567
+ "integrity": "sha512-0KMo5ijjYaEHPiZ2pCVIcbaTS2LycvH9zEhEMKwPPGssBCX7iz5ffYQFk7e5yrQand1r3jnQQgYfHAwtykArnQ==",
3568
+ "license": "MIT",
3569
+ "dependencies": {
3570
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3571
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3572
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3573
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3574
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3575
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3576
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3577
+ "tslib": "^2.1.0"
3578
+ }
3579
+ },
3580
+ "node_modules/@material/focus-ring": {
3581
+ "version": "15.0.0-canary.7f224ddd4.0",
3582
+ "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.7f224ddd4.0.tgz",
3583
+ "integrity": "sha512-Jmg1nltq4J6S6A10EGMZnvufrvU3YTi+8R8ZD9lkSbun0Fm2TVdICQt/Auyi6An9zP66oQN6c31eqO6KfIPsDg==",
3584
+ "license": "MIT",
3585
+ "dependencies": {
3586
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3587
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3588
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0"
3589
+ }
3590
+ },
3591
+ "node_modules/@material/form-field": {
3592
+ "version": "15.0.0-canary.7f224ddd4.0",
3593
+ "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.7f224ddd4.0.tgz",
3594
+ "integrity": "sha512-fEPWgDQEPJ6WF7hNnIStxucHR9LE4DoDSMqCsGWS2Yu+NLZYLuCEecgR0UqQsl1EQdNRaFh8VH93KuxGd2hiPg==",
3595
+ "license": "MIT",
3596
+ "dependencies": {
3597
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3598
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3599
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3600
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3601
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3602
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3603
+ "tslib": "^2.1.0"
3604
+ }
3605
+ },
3606
+ "node_modules/@material/icon-button": {
3607
+ "version": "15.0.0-canary.7f224ddd4.0",
3608
+ "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.7f224ddd4.0.tgz",
3609
+ "integrity": "sha512-DcK7IL4ICY/DW+48YQZZs9g0U1kRaW0Wb0BxhvppDMYziHo/CTpFdle4gjyuTyRxPOdHQz5a97ru48Z9O4muTw==",
3610
+ "license": "MIT",
3611
+ "dependencies": {
3612
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3613
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3614
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3615
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3616
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3617
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
3618
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3619
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3620
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3621
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
3622
+ "tslib": "^2.1.0"
3623
+ }
3624
+ },
3625
+ "node_modules/@material/image-list": {
3626
+ "version": "15.0.0-canary.7f224ddd4.0",
3627
+ "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.7f224ddd4.0.tgz",
3628
+ "integrity": "sha512-voMjG2p80XbjL1B2lmF65zO5gEgJOVKClLdqh4wbYzYfwY/SR9c8eLvlYG7DLdFaFBl/7gGxD8TvvZ329HUFPw==",
3629
+ "license": "MIT",
3630
+ "dependencies": {
3631
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3632
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3633
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3634
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3635
+ "tslib": "^2.1.0"
3636
+ }
3637
+ },
3638
+ "node_modules/@material/layout-grid": {
3639
+ "version": "15.0.0-canary.7f224ddd4.0",
3640
+ "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.7f224ddd4.0.tgz",
3641
+ "integrity": "sha512-veDABLxMn2RmvfnUO2RUmC1OFfWr4cU+MrxKPoDD2hl3l3eDYv5fxws6r5T1JoSyXoaN+oEZpheS0+M9Ure8Pg==",
3642
+ "license": "MIT",
3643
+ "dependencies": {
3644
+ "tslib": "^2.1.0"
3645
+ }
3646
+ },
3647
+ "node_modules/@material/line-ripple": {
3648
+ "version": "15.0.0-canary.7f224ddd4.0",
3649
+ "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.7f224ddd4.0.tgz",
3650
+ "integrity": "sha512-f60hVJhIU6I3/17Tqqzch1emUKEcfVVgHVqADbU14JD+oEIz429ZX9ksZ3VChoU3+eejFl+jVdZMLE/LrAuwpg==",
3651
+ "license": "MIT",
3652
+ "dependencies": {
3653
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3654
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3655
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3656
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3657
+ "tslib": "^2.1.0"
3658
+ }
3659
+ },
3660
+ "node_modules/@material/linear-progress": {
3661
+ "version": "15.0.0-canary.7f224ddd4.0",
3662
+ "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.7f224ddd4.0.tgz",
3663
+ "integrity": "sha512-pRDEwPQielDiC9Sc5XhCXrGxP8wWOnAO8sQlMebfBYHYqy5hhiIzibezS8CSaW4MFQFyXmCmpmqWlbqGYRmiyg==",
3664
+ "license": "MIT",
3665
+ "dependencies": {
3666
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3667
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3668
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3669
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3670
+ "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0",
3671
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3672
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3673
+ "tslib": "^2.1.0"
3674
+ }
3675
+ },
3676
+ "node_modules/@material/list": {
3677
+ "version": "15.0.0-canary.7f224ddd4.0",
3678
+ "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.7f224ddd4.0.tgz",
3679
+ "integrity": "sha512-Is0NV91sJlXF5pOebYAtWLF4wU2MJDbYqztML/zQNENkQxDOvEXu3nWNb3YScMIYJJXvARO0Liur5K4yPagS1Q==",
3680
+ "license": "MIT",
3681
+ "dependencies": {
3682
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3683
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3684
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3685
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3686
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3687
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3688
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3689
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3690
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3691
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3692
+ "tslib": "^2.1.0"
3693
+ }
3694
+ },
3695
+ "node_modules/@material/menu": {
3696
+ "version": "15.0.0-canary.7f224ddd4.0",
3697
+ "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.7f224ddd4.0.tgz",
3698
+ "integrity": "sha512-D11QU1dXqLbh5X1zKlEhS3QWh0b5BPNXlafc5MXfkdJHhOiieb7LC9hMJhbrHtj24FadJ7evaFW/T2ugJbJNnQ==",
3699
+ "license": "MIT",
3700
+ "dependencies": {
3701
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3702
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3703
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3704
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3705
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
3706
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
3707
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3708
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3709
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3710
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3711
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3712
+ "tslib": "^2.1.0"
3713
+ }
3714
+ },
3715
+ "node_modules/@material/menu-surface": {
3716
+ "version": "15.0.0-canary.7f224ddd4.0",
3717
+ "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.7f224ddd4.0.tgz",
3718
+ "integrity": "sha512-7RZHvw0gbwppaAJ/Oh5SWmfAKJ62aw1IMB3+3MRwsb5PLoV666wInYa+zJfE4i7qBeOn904xqT2Nko5hY0ssrg==",
3719
+ "license": "MIT",
3720
+ "dependencies": {
3721
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3722
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3723
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3724
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3725
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3726
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3727
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3728
+ "tslib": "^2.1.0"
3729
+ }
3730
+ },
3731
+ "node_modules/@material/notched-outline": {
3732
+ "version": "15.0.0-canary.7f224ddd4.0",
3733
+ "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.7f224ddd4.0.tgz",
3734
+ "integrity": "sha512-Yg2usuKB2DKlKIBISbie9BFsOVuffF71xjbxPbybvqemxqUBd+bD5/t6H1fLE+F8/NCu5JMigho4ewUU+0RCiw==",
3735
+ "license": "MIT",
3736
+ "dependencies": {
3737
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3738
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3739
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
3740
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3741
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3742
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3743
+ "tslib": "^2.1.0"
3744
+ }
3745
+ },
3746
+ "node_modules/@material/progress-indicator": {
3747
+ "version": "15.0.0-canary.7f224ddd4.0",
3748
+ "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.7f224ddd4.0.tgz",
3749
+ "integrity": "sha512-UPbDjE5CqT+SqTs0mNFG6uFEw7wBlgYmh+noSkQ6ty/EURm8lF125dmi4dv4kW0+octonMXqkGtAoZwLIHKf/w==",
3750
+ "license": "MIT",
3751
+ "dependencies": {
3752
+ "tslib": "^2.1.0"
3753
+ }
3754
+ },
3755
+ "node_modules/@material/radio": {
3756
+ "version": "15.0.0-canary.7f224ddd4.0",
3757
+ "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.7f224ddd4.0.tgz",
3758
+ "integrity": "sha512-wR1X0Sr0KmQLu6+YOFKAI84G3L6psqd7Kys5kfb8WKBM36zxO5HQXC5nJm/Y0rdn22ixzsIz2GBo0MNU4V4k1A==",
3759
+ "license": "MIT",
3760
+ "dependencies": {
3761
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3762
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3763
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3764
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3765
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3766
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
3767
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3768
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3769
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
3770
+ "tslib": "^2.1.0"
3771
+ }
3772
+ },
3773
+ "node_modules/@material/ripple": {
3774
+ "version": "15.0.0-canary.7f224ddd4.0",
3775
+ "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.7f224ddd4.0.tgz",
3776
+ "integrity": "sha512-JqOsWM1f4aGdotP0rh1vZlPZTg6lZgh39FIYHFMfOwfhR+LAikUJ+37ciqZuewgzXB6iiRO6a8aUH6HR5SJYPg==",
3777
+ "license": "MIT",
3778
+ "dependencies": {
3779
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3780
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3781
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3782
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3783
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3784
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3785
+ "tslib": "^2.1.0"
3786
+ }
3787
+ },
3788
+ "node_modules/@material/rtl": {
3789
+ "version": "15.0.0-canary.7f224ddd4.0",
3790
+ "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.7f224ddd4.0.tgz",
3791
+ "integrity": "sha512-UVf14qAtmPiaaZjuJtmN36HETyoKWmsZM/qn1L5ciR2URb8O035dFWnz4ZWFMmAYBno/L7JiZaCkPurv2ZNrGA==",
3792
+ "license": "MIT",
3793
+ "dependencies": {
3794
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3795
+ "tslib": "^2.1.0"
3796
+ }
3797
+ },
3798
+ "node_modules/@material/segmented-button": {
3799
+ "version": "15.0.0-canary.7f224ddd4.0",
3800
+ "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.7f224ddd4.0.tgz",
3801
+ "integrity": "sha512-LCnVRUSAhELTKI/9hSvyvIvQIpPpqF29BV+O9yM4WoNNmNWqTulvuiv7grHZl6Z+kJuxSg4BGbsPxxb9dXozPg==",
3802
+ "license": "MIT",
3803
+ "dependencies": {
3804
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3805
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3806
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3807
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3808
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3809
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
3810
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3811
+ "tslib": "^2.1.0"
3812
+ }
3813
+ },
3814
+ "node_modules/@material/select": {
3815
+ "version": "15.0.0-canary.7f224ddd4.0",
3816
+ "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.7f224ddd4.0.tgz",
3817
+ "integrity": "sha512-WioZtQEXRpglum0cMSzSqocnhsGRr+ZIhvKb3FlaNrTaK8H3Y4QA7rVjv3emRtrLOOjaT6/RiIaUMTo9AGzWQQ==",
3818
+ "license": "MIT",
3819
+ "dependencies": {
3820
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3821
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3822
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3823
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3824
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3825
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3826
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
3827
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
3828
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
3829
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
3830
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
3831
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
3832
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3833
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3834
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3835
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3836
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3837
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3838
+ "tslib": "^2.1.0"
3839
+ }
3840
+ },
3841
+ "node_modules/@material/shape": {
3842
+ "version": "15.0.0-canary.7f224ddd4.0",
3843
+ "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.7f224ddd4.0.tgz",
3844
+ "integrity": "sha512-8z8l1W3+cymObunJoRhwFPKZ+FyECfJ4MJykNiaZq7XJFZkV6xNmqAVrrbQj93FtLsECn9g4PjjIomguVn/OEw==",
3845
+ "license": "MIT",
3846
+ "dependencies": {
3847
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3848
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3849
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3850
+ "tslib": "^2.1.0"
3851
+ }
3852
+ },
3853
+ "node_modules/@material/slider": {
3854
+ "version": "15.0.0-canary.7f224ddd4.0",
3855
+ "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.7f224ddd4.0.tgz",
3856
+ "integrity": "sha512-QU/WSaSWlLKQRqOhJrPgm29wqvvzRusMqwAcrCh1JTrCl+xwJ43q5WLDfjYhubeKtrEEgGu9tekkAiYfMG7EBw==",
3857
+ "license": "MIT",
3858
+ "dependencies": {
3859
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3860
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3861
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3862
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3863
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3864
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3865
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3866
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3867
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3868
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3869
+ "tslib": "^2.1.0"
3870
+ }
3871
+ },
3872
+ "node_modules/@material/snackbar": {
3873
+ "version": "15.0.0-canary.7f224ddd4.0",
3874
+ "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.7f224ddd4.0.tgz",
3875
+ "integrity": "sha512-sm7EbVKddaXpT/aXAYBdPoN0k8yeg9+dprgBUkrdqGzWJAeCkxb4fv2B3He88YiCtvkTz2KLY4CThPQBSEsMFQ==",
3876
+ "license": "MIT",
3877
+ "dependencies": {
3878
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3879
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3880
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
3881
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3882
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3883
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3884
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
3885
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3886
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3887
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3888
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3889
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3890
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3891
+ "tslib": "^2.1.0"
3892
+ }
3893
+ },
3894
+ "node_modules/@material/switch": {
3895
+ "version": "15.0.0-canary.7f224ddd4.0",
3896
+ "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.7f224ddd4.0.tgz",
3897
+ "integrity": "sha512-lEDJfRvkVyyeHWIBfoxYjJVl+WlEAE2kZ/+6OqB1FW0OV8ftTODZGhHRSzjVBA1/p4FPuhAtKtoK9jTpa4AZjA==",
3898
+ "license": "MIT",
3899
+ "dependencies": {
3900
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3901
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3902
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3903
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3904
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3905
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3906
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
3907
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3908
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3909
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3910
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3911
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3912
+ "safevalues": "^0.3.4",
3913
+ "tslib": "^2.1.0"
3914
+ }
3915
+ },
3916
+ "node_modules/@material/tab": {
3917
+ "version": "15.0.0-canary.7f224ddd4.0",
3918
+ "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.7f224ddd4.0.tgz",
3919
+ "integrity": "sha512-E1xGACImyCLurhnizyOTCgOiVezce4HlBFAI6YhJo/AyVwjN2Dtas4ZLQMvvWWqpyhITNkeYdOchwCC1mrz3AQ==",
3920
+ "license": "MIT",
3921
+ "dependencies": {
3922
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3923
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3924
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3925
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
3926
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3927
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3928
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
3929
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3930
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3931
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3932
+ "tslib": "^2.1.0"
3933
+ }
3934
+ },
3935
+ "node_modules/@material/tab-bar": {
3936
+ "version": "15.0.0-canary.7f224ddd4.0",
3937
+ "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.7f224ddd4.0.tgz",
3938
+ "integrity": "sha512-p1Asb2NzrcECvAQU3b2SYrpyJGyJLQWR+nXTYzDKE8WOpLIRCXap2audNqD7fvN/A20UJ1J8U01ptrvCkwJ4eA==",
3939
+ "license": "MIT",
3940
+ "dependencies": {
3941
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3942
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3943
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3944
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
3945
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3946
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
3947
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
3948
+ "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0",
3949
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3950
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
3951
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
3952
+ "tslib": "^2.1.0"
3953
+ }
3954
+ },
3955
+ "node_modules/@material/tab-indicator": {
3956
+ "version": "15.0.0-canary.7f224ddd4.0",
3957
+ "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.7f224ddd4.0.tgz",
3958
+ "integrity": "sha512-h9Td3MPqbs33spcPS7ecByRHraYgU4tNCZpZzZXw31RypjKvISDv/PS5wcA4RmWqNGih78T7xg4QIGsZg4Pk4w==",
3959
+ "license": "MIT",
3960
+ "dependencies": {
3961
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3962
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3963
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3964
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
3965
+ "tslib": "^2.1.0"
3966
+ }
3967
+ },
3968
+ "node_modules/@material/tab-scroller": {
3969
+ "version": "15.0.0-canary.7f224ddd4.0",
3970
+ "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.7f224ddd4.0.tgz",
3971
+ "integrity": "sha512-LFeYNjQpdXecwECd8UaqHYbhscDCwhGln5Yh+3ctvcEgvmDPNjhKn/DL3sWprWvG8NAhP6sHMrsGhQFVdCWtTg==",
3972
+ "license": "MIT",
3973
+ "dependencies": {
3974
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3975
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3976
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3977
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3978
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
3979
+ "tslib": "^2.1.0"
3980
+ }
3981
+ },
3982
+ "node_modules/@material/textfield": {
3983
+ "version": "15.0.0-canary.7f224ddd4.0",
3984
+ "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.7f224ddd4.0.tgz",
3985
+ "integrity": "sha512-AExmFvgE5nNF0UA4l2cSzPghtxSUQeeoyRjFLHLy+oAaE4eKZFrSy0zEpqPeWPQpEMDZk+6Y+6T3cOFYBeSvsw==",
3986
+ "license": "MIT",
3987
+ "dependencies": {
3988
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
3989
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
3990
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
3991
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
3992
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
3993
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
3994
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
3995
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
3996
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
3997
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
3998
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
3999
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
4000
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
4001
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
4002
+ "tslib": "^2.1.0"
4003
+ }
4004
+ },
4005
+ "node_modules/@material/theme": {
4006
+ "version": "15.0.0-canary.7f224ddd4.0",
4007
+ "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.7f224ddd4.0.tgz",
4008
+ "integrity": "sha512-hs45hJoE9yVnoVOcsN1jklyOa51U4lzWsEnQEuJTPOk2+0HqCQ0yv/q0InpSnm2i69fNSyZC60+8HADZGF8ugQ==",
4009
+ "license": "MIT",
4010
+ "dependencies": {
4011
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
4012
+ "tslib": "^2.1.0"
4013
+ }
4014
+ },
4015
+ "node_modules/@material/tokens": {
4016
+ "version": "15.0.0-canary.7f224ddd4.0",
4017
+ "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.7f224ddd4.0.tgz",
4018
+ "integrity": "sha512-r9TDoicmcT7FhUXC4eYMFnt9TZsz0G8T3wXvkKncLppYvZ517gPyD/1+yhuGfGOxAzxTrM66S/oEc1fFE2q4hw==",
4019
+ "license": "MIT",
4020
+ "dependencies": {
4021
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0"
4022
+ }
4023
+ },
4024
+ "node_modules/@material/tooltip": {
4025
+ "version": "15.0.0-canary.7f224ddd4.0",
4026
+ "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.7f224ddd4.0.tgz",
4027
+ "integrity": "sha512-8qNk3pmPLTnam3XYC1sZuplQXW9xLn4Z4MI3D+U17Q7pfNZfoOugGr+d2cLA9yWAEjVJYB0mj8Yu86+udo4N9w==",
4028
+ "license": "MIT",
4029
+ "dependencies": {
4030
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
4031
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
4032
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
4033
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
4034
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
4035
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
4036
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
4037
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
4038
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
4039
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
4040
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
4041
+ "safevalues": "^0.3.4",
4042
+ "tslib": "^2.1.0"
4043
+ }
4044
+ },
4045
+ "node_modules/@material/top-app-bar": {
4046
+ "version": "15.0.0-canary.7f224ddd4.0",
4047
+ "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.7f224ddd4.0.tgz",
4048
+ "integrity": "sha512-SARR5/ClYT4CLe9qAXakbr0i0cMY0V3V4pe3ElIJPfL2Z2c4wGR1mTR8m2LxU1MfGKK8aRoUdtfKaxWejp+eNA==",
4049
+ "license": "MIT",
4050
+ "dependencies": {
4051
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
4052
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
4053
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
4054
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
4055
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
4056
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
4057
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
4058
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
4059
+ "tslib": "^2.1.0"
4060
+ }
4061
+ },
4062
+ "node_modules/@material/touch-target": {
4063
+ "version": "15.0.0-canary.7f224ddd4.0",
4064
+ "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.7f224ddd4.0.tgz",
4065
+ "integrity": "sha512-BJo/wFKHPYLGsRaIpd7vsQwKr02LtO2e89Psv0on/p0OephlNIgeB9dD9W+bQmaeZsZ6liKSKRl6wJWDiK71PA==",
4066
+ "license": "MIT",
4067
+ "dependencies": {
4068
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
4069
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
4070
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
4071
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
4072
+ "tslib": "^2.1.0"
4073
+ }
4074
+ },
4075
+ "node_modules/@material/typography": {
4076
+ "version": "15.0.0-canary.7f224ddd4.0",
4077
+ "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.7f224ddd4.0.tgz",
4078
+ "integrity": "sha512-kBaZeCGD50iq1DeRRH5OM5Jl7Gdk+/NOfKArkY4ksBZvJiStJ7ACAhpvb8MEGm4s3jvDInQFLsDq3hL+SA79sQ==",
4079
+ "license": "MIT",
4080
+ "dependencies": {
4081
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
4082
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
4083
+ "tslib": "^2.1.0"
4084
+ }
4085
+ },
4086
  "node_modules/@ngtools/webpack": {
4087
  "version": "17.3.11",
4088
  "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.11.tgz",
 
7078
  "version": "4.5.0",
7079
  "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
7080
  "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
7081
+ "devOptional": true,
7082
  "engines": {
7083
  "node": ">=0.12"
7084
  },
 
11161
  "version": "7.2.1",
11162
  "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
11163
  "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
11164
+ "devOptional": true,
11165
  "dependencies": {
11166
  "entities": "^4.5.0"
11167
  },
 
12292
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
12293
  "dev": true
12294
  },
12295
+ "node_modules/safevalues": {
12296
+ "version": "0.3.4",
12297
+ "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz",
12298
+ "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==",
12299
+ "license": "Apache-2.0"
12300
+ },
12301
  "node_modules/sass": {
12302
  "version": "1.71.1",
12303
  "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
 
14377
  "node": ">=10.13.0"
14378
  }
14379
  },
14380
+ "node_modules/wavesurfer.js": {
14381
+ "version": "7.12.1",
14382
+ "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.12.1.tgz",
14383
+ "integrity": "sha512-NswPjVHxk0Q1F/VMRemCPUzSojjuHHisQrBqQiRXg7MVbe3f5vQ6r0rTTXA/a/neC/4hnOEC4YpXca4LpH0SUg==",
14384
+ "license": "BSD-3-Clause"
14385
+ },
14386
  "node_modules/wbuf": {
14387
  "version": "1.7.3",
14388
  "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
 
15035
  "tslib": "^2.3.0"
15036
  }
15037
  },
15038
+ "@angular/cdk": {
15039
+ "version": "17.3.10",
15040
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.10.tgz",
15041
+ "integrity": "sha512-b1qktT2c1TTTe5nTji/kFAVW92fULK0YhYAvJ+BjZTPKu2FniZNe8o4qqQ0pUuvtMu+ZQxp/QqFYoidIVCjScg==",
15042
+ "peer": true,
15043
+ "requires": {
15044
+ "parse5": "^7.1.2",
15045
+ "tslib": "^2.3.0"
15046
+ }
15047
+ },
15048
  "@angular/cli": {
15049
  "version": "17.3.11",
15050
  "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.11.tgz",
 
15158
  "tslib": "^2.3.0"
15159
  }
15160
  },
15161
+ "@angular/material": {
15162
+ "version": "17.3.10",
15163
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.10.tgz",
15164
+ "integrity": "sha512-hHMQES0tQPH5JW33W+mpBPuM8ybsloDTqFPuRV8cboDjosAWfJhzAKF3ozICpNlUrs62La/2Wu/756GcQrxebg==",
15165
+ "requires": {
15166
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
15167
+ "@material/auto-init": "15.0.0-canary.7f224ddd4.0",
15168
+ "@material/banner": "15.0.0-canary.7f224ddd4.0",
15169
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
15170
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
15171
+ "@material/card": "15.0.0-canary.7f224ddd4.0",
15172
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
15173
+ "@material/chips": "15.0.0-canary.7f224ddd4.0",
15174
+ "@material/circular-progress": "15.0.0-canary.7f224ddd4.0",
15175
+ "@material/data-table": "15.0.0-canary.7f224ddd4.0",
15176
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
15177
+ "@material/dialog": "15.0.0-canary.7f224ddd4.0",
15178
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
15179
+ "@material/drawer": "15.0.0-canary.7f224ddd4.0",
15180
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
15181
+ "@material/fab": "15.0.0-canary.7f224ddd4.0",
15182
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
15183
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
15184
+ "@material/form-field": "15.0.0-canary.7f224ddd4.0",
15185
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
15186
+ "@material/image-list": "15.0.0-canary.7f224ddd4.0",
15187
+ "@material/layout-grid": "15.0.0-canary.7f224ddd4.0",
15188
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
15189
+ "@material/linear-progress": "15.0.0-canary.7f224ddd4.0",
15190
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
15191
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
15192
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
15193
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
15194
+ "@material/radio": "15.0.0-canary.7f224ddd4.0",
15195
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
15196
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
15197
+ "@material/segmented-button": "15.0.0-canary.7f224ddd4.0",
15198
+ "@material/select": "15.0.0-canary.7f224ddd4.0",
15199
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
15200
+ "@material/slider": "15.0.0-canary.7f224ddd4.0",
15201
+ "@material/snackbar": "15.0.0-canary.7f224ddd4.0",
15202
+ "@material/switch": "15.0.0-canary.7f224ddd4.0",
15203
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
15204
+ "@material/tab-bar": "15.0.0-canary.7f224ddd4.0",
15205
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
15206
+ "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0",
15207
+ "@material/textfield": "15.0.0-canary.7f224ddd4.0",
15208
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
15209
+ "@material/tooltip": "15.0.0-canary.7f224ddd4.0",
15210
+ "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0",
15211
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
15212
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
15213
+ "tslib": "^2.3.0"
15214
+ }
15215
+ },
15216
  "@angular/platform-browser": {
15217
  "version": "17.3.12",
15218
  "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
 
16974
  "call-bind": "^1.0.7"
16975
  }
16976
  },
16977
+ "@material/animation": {
16978
+ "version": "15.0.0-canary.7f224ddd4.0",
16979
+ "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.7f224ddd4.0.tgz",
16980
+ "integrity": "sha512-1GSJaPKef+7HRuV+HusVZHps64cmZuOItDbt40tjJVaikcaZvwmHlcTxRIqzcRoCdt5ZKHh3NoO7GB9Khg4Jnw==",
16981
+ "requires": {
16982
+ "tslib": "^2.1.0"
16983
+ }
16984
+ },
16985
+ "@material/auto-init": {
16986
+ "version": "15.0.0-canary.7f224ddd4.0",
16987
+ "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.7f224ddd4.0.tgz",
16988
+ "integrity": "sha512-t7ZGpRJ3ec0QDUO0nJu/SMgLW7qcuG2KqIsEYD1Ej8qhI2xpdR2ydSDQOkVEitXmKoGol1oq4nYSBjTlB65GqA==",
16989
+ "requires": {
16990
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
16991
+ "tslib": "^2.1.0"
16992
+ }
16993
+ },
16994
+ "@material/banner": {
16995
+ "version": "15.0.0-canary.7f224ddd4.0",
16996
+ "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.7f224ddd4.0.tgz",
16997
+ "integrity": "sha512-g9wBUZzYBizyBcBQXTIafnRUUPi7efU9gPJfzeGgkynXiccP/vh5XMmH+PBxl5v+4MlP/d4cZ2NUYoAN7UTqSA==",
16998
+ "requires": {
16999
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17000
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
17001
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17002
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17003
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17004
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17005
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17006
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17007
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17008
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17009
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17010
+ "tslib": "^2.1.0"
17011
+ }
17012
+ },
17013
+ "@material/base": {
17014
+ "version": "15.0.0-canary.7f224ddd4.0",
17015
+ "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.7f224ddd4.0.tgz",
17016
+ "integrity": "sha512-I9KQOKXpLfJkP8MqZyr8wZIzdPHrwPjFvGd9zSK91/vPyE4hzHRJc/0njsh9g8Lm9PRYLbifXX+719uTbHxx+A==",
17017
+ "requires": {
17018
+ "tslib": "^2.1.0"
17019
+ }
17020
+ },
17021
+ "@material/button": {
17022
+ "version": "15.0.0-canary.7f224ddd4.0",
17023
+ "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.7f224ddd4.0.tgz",
17024
+ "integrity": "sha512-BHB7iyHgRVH+JF16+iscR+Qaic+p7LU1FOLgP8KucRlpF9tTwIxQA6mJwGRi5gUtcG+vyCmzVS+hIQ6DqT/7BA==",
17025
+ "requires": {
17026
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17027
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17028
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17029
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17030
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
17031
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17032
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17033
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17034
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17035
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17036
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
17037
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17038
+ "tslib": "^2.1.0"
17039
+ }
17040
+ },
17041
+ "@material/card": {
17042
+ "version": "15.0.0-canary.7f224ddd4.0",
17043
+ "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.7f224ddd4.0.tgz",
17044
+ "integrity": "sha512-kt7y9/IWOtJTr3Z/AoWJT3ZLN7CLlzXhx2udCLP9ootZU2bfGK0lzNwmo80bv/pJfrY9ihQKCtuGTtNxUy+vIw==",
17045
+ "requires": {
17046
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17047
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17048
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17049
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17050
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17051
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17052
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17053
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17054
+ "tslib": "^2.1.0"
17055
+ }
17056
+ },
17057
+ "@material/checkbox": {
17058
+ "version": "15.0.0-canary.7f224ddd4.0",
17059
+ "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.7f224ddd4.0.tgz",
17060
+ "integrity": "sha512-rURcrL5O1u6hzWR+dNgiQ/n89vk6tdmdP3mZgnxJx61q4I/k1yijKqNJSLrkXH7Rto3bM5NRKMOlgvMvVd7UMQ==",
17061
+ "requires": {
17062
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17063
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17064
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17065
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17066
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17067
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
17068
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17069
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17070
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17071
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
17072
+ "tslib": "^2.1.0"
17073
+ }
17074
+ },
17075
+ "@material/chips": {
17076
+ "version": "15.0.0-canary.7f224ddd4.0",
17077
+ "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.7f224ddd4.0.tgz",
17078
+ "integrity": "sha512-AYAivV3GSk/T/nRIpH27sOHFPaSMrE3L0WYbnb5Wa93FgY8a0fbsFYtSH2QmtwnzXveg+B1zGTt7/xIIcynKdQ==",
17079
+ "requires": {
17080
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17081
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17082
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
17083
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17084
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17085
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17086
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17087
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
17088
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17089
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17090
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17091
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17092
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17093
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
17094
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17095
+ "safevalues": "^0.3.4",
17096
+ "tslib": "^2.1.0"
17097
+ }
17098
+ },
17099
+ "@material/circular-progress": {
17100
+ "version": "15.0.0-canary.7f224ddd4.0",
17101
+ "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.7f224ddd4.0.tgz",
17102
+ "integrity": "sha512-DJrqCKb+LuGtjNvKl8XigvyK02y36GRkfhMUYTcJEi3PrOE00bwXtyj7ilhzEVshQiXg6AHGWXtf5UqwNrx3Ow==",
17103
+ "requires": {
17104
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17105
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17106
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17107
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17108
+ "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0",
17109
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17110
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17111
+ "tslib": "^2.1.0"
17112
+ }
17113
+ },
17114
+ "@material/data-table": {
17115
+ "version": "15.0.0-canary.7f224ddd4.0",
17116
+ "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.7f224ddd4.0.tgz",
17117
+ "integrity": "sha512-/2WZsuBIq9z9RWYF5Jo6b7P6u0fwit+29/mN7rmAZ6akqUR54nXyNfoSNiyydMkzPlZZsep5KrSHododDhBZbA==",
17118
+ "requires": {
17119
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17120
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17121
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
17122
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17123
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17124
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17125
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17126
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
17127
+ "@material/linear-progress": "15.0.0-canary.7f224ddd4.0",
17128
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
17129
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
17130
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17131
+ "@material/select": "15.0.0-canary.7f224ddd4.0",
17132
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17133
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17134
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17135
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
17136
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17137
+ "tslib": "^2.1.0"
17138
+ }
17139
+ },
17140
+ "@material/density": {
17141
+ "version": "15.0.0-canary.7f224ddd4.0",
17142
+ "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.7f224ddd4.0.tgz",
17143
+ "integrity": "sha512-o9EXmGKVpiQ6mHhyV3oDDzc78Ow3E7v8dlaOhgaDSXgmqaE8v5sIlLNa/LKSyUga83/fpGk3QViSGXotpQx0jA==",
17144
+ "requires": {
17145
+ "tslib": "^2.1.0"
17146
+ }
17147
+ },
17148
+ "@material/dialog": {
17149
+ "version": "15.0.0-canary.7f224ddd4.0",
17150
+ "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.7f224ddd4.0.tgz",
17151
+ "integrity": "sha512-u0XpTlv1JqWC/bQ3DavJ1JguofTelLT2wloj59l3/1b60jv42JQ6Am7jU3I8/SIUB1MKaW7dYocXjDWtWJakLA==",
17152
+ "requires": {
17153
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17154
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17155
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
17156
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17157
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17158
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17159
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
17160
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17161
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17162
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17163
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17164
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17165
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
17166
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17167
+ "tslib": "^2.1.0"
17168
+ }
17169
+ },
17170
+ "@material/dom": {
17171
+ "version": "15.0.0-canary.7f224ddd4.0",
17172
+ "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.7f224ddd4.0.tgz",
17173
+ "integrity": "sha512-mQ1HT186GPQSkRg5S18i70typ5ZytfjL09R0gJ2Qg5/G+MLCGi7TAjZZSH65tuD/QGOjel4rDdWOTmYbPYV6HA==",
17174
+ "requires": {
17175
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17176
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17177
+ "tslib": "^2.1.0"
17178
+ }
17179
+ },
17180
+ "@material/drawer": {
17181
+ "version": "15.0.0-canary.7f224ddd4.0",
17182
+ "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.7f224ddd4.0.tgz",
17183
+ "integrity": "sha512-qyO0W0KBftfH8dlLR0gVAgv7ZHNvU8ae11Ao6zJif/YxcvK4+gph1z8AO4H410YmC2kZiwpSKyxM1iQCCzbb4g==",
17184
+ "requires": {
17185
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17186
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17187
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17188
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17189
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17190
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
17191
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17192
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17193
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17194
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17195
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17196
+ "tslib": "^2.1.0"
17197
+ }
17198
+ },
17199
+ "@material/elevation": {
17200
+ "version": "15.0.0-canary.7f224ddd4.0",
17201
+ "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.7f224ddd4.0.tgz",
17202
+ "integrity": "sha512-tV6s4/pUBECedaI36Yj18KmRCk1vfue/JP/5yYRlFNnLMRVISePbZaKkn/BHXVf+26I3W879+XqIGlDVdmOoMA==",
17203
+ "requires": {
17204
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17205
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17206
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17207
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17208
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17209
+ "tslib": "^2.1.0"
17210
+ }
17211
+ },
17212
+ "@material/fab": {
17213
+ "version": "15.0.0-canary.7f224ddd4.0",
17214
+ "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.7f224ddd4.0.tgz",
17215
+ "integrity": "sha512-4h76QrzfZTcPdd+awDPZ4Q0YdSqsXQnS540TPtyXUJ/5G99V6VwGpjMPIxAsW0y+pmI9UkLL/srrMaJec+7r4Q==",
17216
+ "requires": {
17217
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17218
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17219
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17220
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17221
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
17222
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17223
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17224
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17225
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17226
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17227
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
17228
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17229
+ "tslib": "^2.1.0"
17230
+ }
17231
+ },
17232
+ "@material/feature-targeting": {
17233
+ "version": "15.0.0-canary.7f224ddd4.0",
17234
+ "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.7f224ddd4.0.tgz",
17235
+ "integrity": "sha512-SAjtxYh6YlKZriU83diDEQ7jNSP2MnxKsER0TvFeyG1vX/DWsUyYDOIJTOEa9K1N+fgJEBkNK8hY55QhQaspew==",
17236
+ "requires": {
17237
+ "tslib": "^2.1.0"
17238
+ }
17239
+ },
17240
+ "@material/floating-label": {
17241
+ "version": "15.0.0-canary.7f224ddd4.0",
17242
+ "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.7f224ddd4.0.tgz",
17243
+ "integrity": "sha512-0KMo5ijjYaEHPiZ2pCVIcbaTS2LycvH9zEhEMKwPPGssBCX7iz5ffYQFk7e5yrQand1r3jnQQgYfHAwtykArnQ==",
17244
+ "requires": {
17245
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17246
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17247
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17248
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17249
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17250
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17251
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17252
+ "tslib": "^2.1.0"
17253
+ }
17254
+ },
17255
+ "@material/focus-ring": {
17256
+ "version": "15.0.0-canary.7f224ddd4.0",
17257
+ "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.7f224ddd4.0.tgz",
17258
+ "integrity": "sha512-Jmg1nltq4J6S6A10EGMZnvufrvU3YTi+8R8ZD9lkSbun0Fm2TVdICQt/Auyi6An9zP66oQN6c31eqO6KfIPsDg==",
17259
+ "requires": {
17260
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17261
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17262
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0"
17263
+ }
17264
+ },
17265
+ "@material/form-field": {
17266
+ "version": "15.0.0-canary.7f224ddd4.0",
17267
+ "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.7f224ddd4.0.tgz",
17268
+ "integrity": "sha512-fEPWgDQEPJ6WF7hNnIStxucHR9LE4DoDSMqCsGWS2Yu+NLZYLuCEecgR0UqQsl1EQdNRaFh8VH93KuxGd2hiPg==",
17269
+ "requires": {
17270
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17271
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17272
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17273
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17274
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17275
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17276
+ "tslib": "^2.1.0"
17277
+ }
17278
+ },
17279
+ "@material/icon-button": {
17280
+ "version": "15.0.0-canary.7f224ddd4.0",
17281
+ "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.7f224ddd4.0.tgz",
17282
+ "integrity": "sha512-DcK7IL4ICY/DW+48YQZZs9g0U1kRaW0Wb0BxhvppDMYziHo/CTpFdle4gjyuTyRxPOdHQz5a97ru48Z9O4muTw==",
17283
+ "requires": {
17284
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17285
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17286
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17287
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17288
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17289
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
17290
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17291
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17292
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17293
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
17294
+ "tslib": "^2.1.0"
17295
+ }
17296
+ },
17297
+ "@material/image-list": {
17298
+ "version": "15.0.0-canary.7f224ddd4.0",
17299
+ "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.7f224ddd4.0.tgz",
17300
+ "integrity": "sha512-voMjG2p80XbjL1B2lmF65zO5gEgJOVKClLdqh4wbYzYfwY/SR9c8eLvlYG7DLdFaFBl/7gGxD8TvvZ329HUFPw==",
17301
+ "requires": {
17302
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17303
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17304
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17305
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17306
+ "tslib": "^2.1.0"
17307
+ }
17308
+ },
17309
+ "@material/layout-grid": {
17310
+ "version": "15.0.0-canary.7f224ddd4.0",
17311
+ "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.7f224ddd4.0.tgz",
17312
+ "integrity": "sha512-veDABLxMn2RmvfnUO2RUmC1OFfWr4cU+MrxKPoDD2hl3l3eDYv5fxws6r5T1JoSyXoaN+oEZpheS0+M9Ure8Pg==",
17313
+ "requires": {
17314
+ "tslib": "^2.1.0"
17315
+ }
17316
+ },
17317
+ "@material/line-ripple": {
17318
+ "version": "15.0.0-canary.7f224ddd4.0",
17319
+ "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.7f224ddd4.0.tgz",
17320
+ "integrity": "sha512-f60hVJhIU6I3/17Tqqzch1emUKEcfVVgHVqADbU14JD+oEIz429ZX9ksZ3VChoU3+eejFl+jVdZMLE/LrAuwpg==",
17321
+ "requires": {
17322
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17323
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17324
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17325
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17326
+ "tslib": "^2.1.0"
17327
+ }
17328
+ },
17329
+ "@material/linear-progress": {
17330
+ "version": "15.0.0-canary.7f224ddd4.0",
17331
+ "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.7f224ddd4.0.tgz",
17332
+ "integrity": "sha512-pRDEwPQielDiC9Sc5XhCXrGxP8wWOnAO8sQlMebfBYHYqy5hhiIzibezS8CSaW4MFQFyXmCmpmqWlbqGYRmiyg==",
17333
+ "requires": {
17334
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17335
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17336
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17337
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17338
+ "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0",
17339
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17340
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17341
+ "tslib": "^2.1.0"
17342
+ }
17343
+ },
17344
+ "@material/list": {
17345
+ "version": "15.0.0-canary.7f224ddd4.0",
17346
+ "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.7f224ddd4.0.tgz",
17347
+ "integrity": "sha512-Is0NV91sJlXF5pOebYAtWLF4wU2MJDbYqztML/zQNENkQxDOvEXu3nWNb3YScMIYJJXvARO0Liur5K4yPagS1Q==",
17348
+ "requires": {
17349
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17350
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17351
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17352
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17353
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17354
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17355
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17356
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17357
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17358
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17359
+ "tslib": "^2.1.0"
17360
+ }
17361
+ },
17362
+ "@material/menu": {
17363
+ "version": "15.0.0-canary.7f224ddd4.0",
17364
+ "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.7f224ddd4.0.tgz",
17365
+ "integrity": "sha512-D11QU1dXqLbh5X1zKlEhS3QWh0b5BPNXlafc5MXfkdJHhOiieb7LC9hMJhbrHtj24FadJ7evaFW/T2ugJbJNnQ==",
17366
+ "requires": {
17367
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17368
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17369
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17370
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17371
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
17372
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
17373
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17374
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17375
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17376
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17377
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17378
+ "tslib": "^2.1.0"
17379
+ }
17380
+ },
17381
+ "@material/menu-surface": {
17382
+ "version": "15.0.0-canary.7f224ddd4.0",
17383
+ "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.7f224ddd4.0.tgz",
17384
+ "integrity": "sha512-7RZHvw0gbwppaAJ/Oh5SWmfAKJ62aw1IMB3+3MRwsb5PLoV666wInYa+zJfE4i7qBeOn904xqT2Nko5hY0ssrg==",
17385
+ "requires": {
17386
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17387
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17388
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17389
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17390
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17391
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17392
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17393
+ "tslib": "^2.1.0"
17394
+ }
17395
+ },
17396
+ "@material/notched-outline": {
17397
+ "version": "15.0.0-canary.7f224ddd4.0",
17398
+ "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.7f224ddd4.0.tgz",
17399
+ "integrity": "sha512-Yg2usuKB2DKlKIBISbie9BFsOVuffF71xjbxPbybvqemxqUBd+bD5/t6H1fLE+F8/NCu5JMigho4ewUU+0RCiw==",
17400
+ "requires": {
17401
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17402
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17403
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
17404
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17405
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17406
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17407
+ "tslib": "^2.1.0"
17408
+ }
17409
+ },
17410
+ "@material/progress-indicator": {
17411
+ "version": "15.0.0-canary.7f224ddd4.0",
17412
+ "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.7f224ddd4.0.tgz",
17413
+ "integrity": "sha512-UPbDjE5CqT+SqTs0mNFG6uFEw7wBlgYmh+noSkQ6ty/EURm8lF125dmi4dv4kW0+octonMXqkGtAoZwLIHKf/w==",
17414
+ "requires": {
17415
+ "tslib": "^2.1.0"
17416
+ }
17417
+ },
17418
+ "@material/radio": {
17419
+ "version": "15.0.0-canary.7f224ddd4.0",
17420
+ "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.7f224ddd4.0.tgz",
17421
+ "integrity": "sha512-wR1X0Sr0KmQLu6+YOFKAI84G3L6psqd7Kys5kfb8WKBM36zxO5HQXC5nJm/Y0rdn22ixzsIz2GBo0MNU4V4k1A==",
17422
+ "requires": {
17423
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17424
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17425
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17426
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17427
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17428
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
17429
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17430
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17431
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
17432
+ "tslib": "^2.1.0"
17433
+ }
17434
+ },
17435
+ "@material/ripple": {
17436
+ "version": "15.0.0-canary.7f224ddd4.0",
17437
+ "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.7f224ddd4.0.tgz",
17438
+ "integrity": "sha512-JqOsWM1f4aGdotP0rh1vZlPZTg6lZgh39FIYHFMfOwfhR+LAikUJ+37ciqZuewgzXB6iiRO6a8aUH6HR5SJYPg==",
17439
+ "requires": {
17440
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17441
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17442
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17443
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17444
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17445
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17446
+ "tslib": "^2.1.0"
17447
+ }
17448
+ },
17449
+ "@material/rtl": {
17450
+ "version": "15.0.0-canary.7f224ddd4.0",
17451
+ "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.7f224ddd4.0.tgz",
17452
+ "integrity": "sha512-UVf14qAtmPiaaZjuJtmN36HETyoKWmsZM/qn1L5ciR2URb8O035dFWnz4ZWFMmAYBno/L7JiZaCkPurv2ZNrGA==",
17453
+ "requires": {
17454
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17455
+ "tslib": "^2.1.0"
17456
+ }
17457
+ },
17458
+ "@material/segmented-button": {
17459
+ "version": "15.0.0-canary.7f224ddd4.0",
17460
+ "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.7f224ddd4.0.tgz",
17461
+ "integrity": "sha512-LCnVRUSAhELTKI/9hSvyvIvQIpPpqF29BV+O9yM4WoNNmNWqTulvuiv7grHZl6Z+kJuxSg4BGbsPxxb9dXozPg==",
17462
+ "requires": {
17463
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17464
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17465
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17466
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17467
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17468
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
17469
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17470
+ "tslib": "^2.1.0"
17471
+ }
17472
+ },
17473
+ "@material/select": {
17474
+ "version": "15.0.0-canary.7f224ddd4.0",
17475
+ "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.7f224ddd4.0.tgz",
17476
+ "integrity": "sha512-WioZtQEXRpglum0cMSzSqocnhsGRr+ZIhvKb3FlaNrTaK8H3Y4QA7rVjv3emRtrLOOjaT6/RiIaUMTo9AGzWQQ==",
17477
+ "requires": {
17478
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17479
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17480
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17481
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17482
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17483
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17484
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
17485
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
17486
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
17487
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
17488
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
17489
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
17490
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17491
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17492
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17493
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17494
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17495
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17496
+ "tslib": "^2.1.0"
17497
+ }
17498
+ },
17499
+ "@material/shape": {
17500
+ "version": "15.0.0-canary.7f224ddd4.0",
17501
+ "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.7f224ddd4.0.tgz",
17502
+ "integrity": "sha512-8z8l1W3+cymObunJoRhwFPKZ+FyECfJ4MJykNiaZq7XJFZkV6xNmqAVrrbQj93FtLsECn9g4PjjIomguVn/OEw==",
17503
+ "requires": {
17504
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17505
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17506
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17507
+ "tslib": "^2.1.0"
17508
+ }
17509
+ },
17510
+ "@material/slider": {
17511
+ "version": "15.0.0-canary.7f224ddd4.0",
17512
+ "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.7f224ddd4.0.tgz",
17513
+ "integrity": "sha512-QU/WSaSWlLKQRqOhJrPgm29wqvvzRusMqwAcrCh1JTrCl+xwJ43q5WLDfjYhubeKtrEEgGu9tekkAiYfMG7EBw==",
17514
+ "requires": {
17515
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17516
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17517
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17518
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17519
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17520
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17521
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17522
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17523
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17524
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17525
+ "tslib": "^2.1.0"
17526
+ }
17527
+ },
17528
+ "@material/snackbar": {
17529
+ "version": "15.0.0-canary.7f224ddd4.0",
17530
+ "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.7f224ddd4.0.tgz",
17531
+ "integrity": "sha512-sm7EbVKddaXpT/aXAYBdPoN0k8yeg9+dprgBUkrdqGzWJAeCkxb4fv2B3He88YiCtvkTz2KLY4CThPQBSEsMFQ==",
17532
+ "requires": {
17533
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17534
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17535
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
17536
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17537
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17538
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17539
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
17540
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17541
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17542
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17543
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17544
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17545
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17546
+ "tslib": "^2.1.0"
17547
+ }
17548
+ },
17549
+ "@material/switch": {
17550
+ "version": "15.0.0-canary.7f224ddd4.0",
17551
+ "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.7f224ddd4.0.tgz",
17552
+ "integrity": "sha512-lEDJfRvkVyyeHWIBfoxYjJVl+WlEAE2kZ/+6OqB1FW0OV8ftTODZGhHRSzjVBA1/p4FPuhAtKtoK9jTpa4AZjA==",
17553
+ "requires": {
17554
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17555
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17556
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17557
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17558
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17559
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17560
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
17561
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17562
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17563
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17564
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17565
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17566
+ "safevalues": "^0.3.4",
17567
+ "tslib": "^2.1.0"
17568
+ }
17569
+ },
17570
+ "@material/tab": {
17571
+ "version": "15.0.0-canary.7f224ddd4.0",
17572
+ "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.7f224ddd4.0.tgz",
17573
+ "integrity": "sha512-E1xGACImyCLurhnizyOTCgOiVezce4HlBFAI6YhJo/AyVwjN2Dtas4ZLQMvvWWqpyhITNkeYdOchwCC1mrz3AQ==",
17574
+ "requires": {
17575
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17576
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17577
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17578
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
17579
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17580
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17581
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
17582
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17583
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17584
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17585
+ "tslib": "^2.1.0"
17586
+ }
17587
+ },
17588
+ "@material/tab-bar": {
17589
+ "version": "15.0.0-canary.7f224ddd4.0",
17590
+ "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.7f224ddd4.0.tgz",
17591
+ "integrity": "sha512-p1Asb2NzrcECvAQU3b2SYrpyJGyJLQWR+nXTYzDKE8WOpLIRCXap2audNqD7fvN/A20UJ1J8U01ptrvCkwJ4eA==",
17592
+ "requires": {
17593
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17594
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17595
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17596
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17597
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17598
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
17599
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
17600
+ "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0",
17601
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17602
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17603
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17604
+ "tslib": "^2.1.0"
17605
+ }
17606
+ },
17607
+ "@material/tab-indicator": {
17608
+ "version": "15.0.0-canary.7f224ddd4.0",
17609
+ "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.7f224ddd4.0.tgz",
17610
+ "integrity": "sha512-h9Td3MPqbs33spcPS7ecByRHraYgU4tNCZpZzZXw31RypjKvISDv/PS5wcA4RmWqNGih78T7xg4QIGsZg4Pk4w==",
17611
+ "requires": {
17612
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17613
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17614
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17615
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17616
+ "tslib": "^2.1.0"
17617
+ }
17618
+ },
17619
+ "@material/tab-scroller": {
17620
+ "version": "15.0.0-canary.7f224ddd4.0",
17621
+ "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.7f224ddd4.0.tgz",
17622
+ "integrity": "sha512-LFeYNjQpdXecwECd8UaqHYbhscDCwhGln5Yh+3ctvcEgvmDPNjhKn/DL3sWprWvG8NAhP6sHMrsGhQFVdCWtTg==",
17623
+ "requires": {
17624
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17625
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17626
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17627
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17628
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
17629
+ "tslib": "^2.1.0"
17630
+ }
17631
+ },
17632
+ "@material/textfield": {
17633
+ "version": "15.0.0-canary.7f224ddd4.0",
17634
+ "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.7f224ddd4.0.tgz",
17635
+ "integrity": "sha512-AExmFvgE5nNF0UA4l2cSzPghtxSUQeeoyRjFLHLy+oAaE4eKZFrSy0zEpqPeWPQpEMDZk+6Y+6T3cOFYBeSvsw==",
17636
+ "requires": {
17637
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17638
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17639
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
17640
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17641
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17642
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
17643
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
17644
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
17645
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17646
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17647
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17648
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17649
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17650
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17651
+ "tslib": "^2.1.0"
17652
+ }
17653
+ },
17654
+ "@material/theme": {
17655
+ "version": "15.0.0-canary.7f224ddd4.0",
17656
+ "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.7f224ddd4.0.tgz",
17657
+ "integrity": "sha512-hs45hJoE9yVnoVOcsN1jklyOa51U4lzWsEnQEuJTPOk2+0HqCQ0yv/q0InpSnm2i69fNSyZC60+8HADZGF8ugQ==",
17658
+ "requires": {
17659
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17660
+ "tslib": "^2.1.0"
17661
+ }
17662
+ },
17663
+ "@material/tokens": {
17664
+ "version": "15.0.0-canary.7f224ddd4.0",
17665
+ "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.7f224ddd4.0.tgz",
17666
+ "integrity": "sha512-r9TDoicmcT7FhUXC4eYMFnt9TZsz0G8T3wXvkKncLppYvZ517gPyD/1+yhuGfGOxAzxTrM66S/oEc1fFE2q4hw==",
17667
+ "requires": {
17668
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0"
17669
+ }
17670
+ },
17671
+ "@material/tooltip": {
17672
+ "version": "15.0.0-canary.7f224ddd4.0",
17673
+ "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.7f224ddd4.0.tgz",
17674
+ "integrity": "sha512-8qNk3pmPLTnam3XYC1sZuplQXW9xLn4Z4MI3D+U17Q7pfNZfoOugGr+d2cLA9yWAEjVJYB0mj8Yu86+udo4N9w==",
17675
+ "requires": {
17676
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17677
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17678
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
17679
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
17680
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17681
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17682
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17683
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17684
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17685
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
17686
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17687
+ "safevalues": "^0.3.4",
17688
+ "tslib": "^2.1.0"
17689
+ }
17690
+ },
17691
+ "@material/top-app-bar": {
17692
+ "version": "15.0.0-canary.7f224ddd4.0",
17693
+ "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.7f224ddd4.0.tgz",
17694
+ "integrity": "sha512-SARR5/ClYT4CLe9qAXakbr0i0cMY0V3V4pe3ElIJPfL2Z2c4wGR1mTR8m2LxU1MfGKK8aRoUdtfKaxWejp+eNA==",
17695
+ "requires": {
17696
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
17697
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17698
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
17699
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
17700
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17701
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
17702
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17703
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
17704
+ "tslib": "^2.1.0"
17705
+ }
17706
+ },
17707
+ "@material/touch-target": {
17708
+ "version": "15.0.0-canary.7f224ddd4.0",
17709
+ "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.7f224ddd4.0.tgz",
17710
+ "integrity": "sha512-BJo/wFKHPYLGsRaIpd7vsQwKr02LtO2e89Psv0on/p0OephlNIgeB9dD9W+bQmaeZsZ6liKSKRl6wJWDiK71PA==",
17711
+ "requires": {
17712
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
17713
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17714
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
17715
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17716
+ "tslib": "^2.1.0"
17717
+ }
17718
+ },
17719
+ "@material/typography": {
17720
+ "version": "15.0.0-canary.7f224ddd4.0",
17721
+ "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.7f224ddd4.0.tgz",
17722
+ "integrity": "sha512-kBaZeCGD50iq1DeRRH5OM5Jl7Gdk+/NOfKArkY4ksBZvJiStJ7ACAhpvb8MEGm4s3jvDInQFLsDq3hL+SA79sQ==",
17723
+ "requires": {
17724
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
17725
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
17726
+ "tslib": "^2.1.0"
17727
+ }
17728
+ },
17729
  "@ngtools/webpack": {
17730
  "version": "17.3.11",
17731
  "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.11.tgz",
 
19983
  "version": "4.5.0",
19984
  "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
19985
  "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
19986
+ "devOptional": true
19987
  },
19988
  "env-paths": {
19989
  "version": "2.2.1",
 
23025
  "version": "7.2.1",
23026
  "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
23027
  "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
23028
+ "devOptional": true,
23029
  "requires": {
23030
  "entities": "^4.5.0"
23031
  }
 
23792
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
23793
  "dev": true
23794
  },
23795
+ "safevalues": {
23796
+ "version": "0.3.4",
23797
+ "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz",
23798
+ "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw=="
23799
+ },
23800
  "sass": {
23801
  "version": "1.71.1",
23802
  "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
 
25182
  "graceful-fs": "^4.1.2"
25183
  }
25184
  },
25185
+ "wavesurfer.js": {
25186
+ "version": "7.12.1",
25187
+ "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.12.1.tgz",
25188
+ "integrity": "sha512-NswPjVHxk0Q1F/VMRemCPUzSojjuHHisQrBqQiRXg7MVbe3f5vQ6r0rTTXA/a/neC/4hnOEC4YpXca4LpH0SUg=="
25189
+ },
25190
  "wbuf": {
25191
  "version": "1.7.3",
25192
  "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
src/app/app.module.ts CHANGED
@@ -25,6 +25,9 @@ import { RootRedirectComponent } from './root-redirect/root-redirect.component';
25
  import { HeaderComponent } from './shared/header/header.component';
26
  import { SignInComponent } from './sign-in/sign-in.component';
27
  import { PronunciationComponent } from './pronunciation/pronunciation.component';
 
 
 
28
  import { FooterComponent } from './footer/footer.component';
29
  // If you have AppButtonComponent, import it here as well
30
  // import { AppButtonComponent } from './app-button/app-button.component';
@@ -37,6 +40,8 @@ import { FooterComponent } from './footer/footer.component';
37
  AuthComponent,
38
  RootRedirectComponent,
39
  PronunciationComponent,
 
 
40
  FooterComponent,
41
  //GenerateQuestionsComponent,
42
  //VocabularyBuilderComponent,
 
25
  import { HeaderComponent } from './shared/header/header.component';
26
  import { SignInComponent } from './sign-in/sign-in.component';
27
  import { PronunciationComponent } from './pronunciation/pronunciation.component';
28
+ import { PronunciationVideoComponent } from './pronunciationvideo/pronunciationvideo.component';
29
+ import { PronunciationRaggComponent } from './pronunciationragg/pronunciationragg.component';
30
+
31
  import { FooterComponent } from './footer/footer.component';
32
  // If you have AppButtonComponent, import it here as well
33
  // import { AppButtonComponent } from './app-button/app-button.component';
 
40
  AuthComponent,
41
  RootRedirectComponent,
42
  PronunciationComponent,
43
+ PronunciationVideoComponent,
44
+ PronunciationRaggComponent,
45
  FooterComponent,
46
  //GenerateQuestionsComponent,
47
  //VocabularyBuilderComponent,
src/app/home/home.component.html CHANGED
@@ -25,6 +25,16 @@
25
  Pronunciation Trainer
26
  </a>
27
  </li>
 
 
 
 
 
 
 
 
 
 
28
  <li><a class="nav-link--disabled" routerLink="/personality-improvement" routerLinkActive="active-link">Personality Improvement</a></li>
29
  <li><a class="nav-link--disabled" routerLink="/body-language-improvement" routerLinkActive="active-link">Body Language Improvement</a></li>
30
  </ul>
 
25
  Pronunciation Trainer
26
  </a>
27
  </li>
28
+ <li>
29
+ <a href="#" (click)="openPronunciationVideo(); $event.preventDefault()" role="button" aria-pressed="false">
30
+ Pronunciation Trainer Video
31
+ </a>
32
+ </li>
33
+ <li>
34
+ <a href="#" (click)="openPronunciationRagg(); $event.preventDefault()" role="button" aria-pressed="false">
35
+ Pronunciation Trainer Ragg
36
+ </a>
37
+ </li>
38
  <li><a class="nav-link--disabled" routerLink="/personality-improvement" routerLinkActive="active-link">Personality Improvement</a></li>
39
  <li><a class="nav-link--disabled" routerLink="/body-language-improvement" routerLinkActive="active-link">Body Language Improvement</a></li>
40
  </ul>
src/app/home/home.component.ts CHANGED
@@ -5,6 +5,8 @@ import { Subscription } from 'rxjs';
5
  import { BrandService } from '../shared/brand.service';
6
  import { MatDialog } from '@angular/material/dialog';
7
  import { PronunciationComponent } from '../pronunciation/pronunciation.component';
 
 
8
 
9
  @Component({
10
  selector: 'app-home',
@@ -170,4 +172,22 @@ export class HomeComponent implements AfterViewInit, OnInit, OnDestroy {
170
  disableClose: true
171
  });
172
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  }
 
5
  import { BrandService } from '../shared/brand.service';
6
  import { MatDialog } from '@angular/material/dialog';
7
  import { PronunciationComponent } from '../pronunciation/pronunciation.component';
8
+ import { PronunciationVideoComponent } from '../pronunciationvideo/pronunciationvideo.component';
9
+ import { PronunciationRaggComponent } from '../pronunciationragg/pronunciationragg.component';
10
 
11
  @Component({
12
  selector: 'app-home',
 
172
  disableClose: true
173
  });
174
  }
175
+
176
+ openPronunciationVideo(): void {
177
+ const dialogRef = this.dialog.open(PronunciationVideoComponent, {
178
+ width: '90vw',
179
+ maxWidth: '95vw',
180
+ height: '85vh',
181
+ disableClose: true
182
+ });
183
+ }
184
+
185
+ openPronunciationRagg(): void {
186
+ const dialogRef = this.dialog.open(PronunciationRaggComponent, {
187
+ width: '90vw',
188
+ maxWidth: '95vw',
189
+ height: '85vh',
190
+ disableClose: true
191
+ });
192
+ }
193
  }
src/app/pronunciationragg/pronunciationragg.component.css ADDED
@@ -0,0 +1,785 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :host {
2
+ display: block;
3
+ /*font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;*/
4
+ font-family: Raleway, Roboto, "Helvetica Neue", sans-serif;
5
+ }
6
+
7
+ /* Page background */
8
+ .pp-page {
9
+ height: 85vh;
10
+ /* background: #e9f7f6;*/
11
+ padding: 28px 24px 18px;
12
+ box-sizing: border-box;
13
+ border: 7px solid #3aaea8;
14
+ border-radius: 1vw;
15
+ }
16
+
17
+ /* Header */
18
+ .pp-header {
19
+ text-align: center;
20
+ margin-bottom: 18px;
21
+ }
22
+
23
+ .pp-header h1 {
24
+ margin: 0;
25
+ font-size: 42px;
26
+ font-weight: 800;
27
+ color: #3aaea8;
28
+ letter-spacing: 0.3px;
29
+ }
30
+
31
+ .pp-sub {
32
+ margin-top: 6px;
33
+ color: #6b7f7e;
34
+ font-size: 15px;
35
+ position: relative;
36
+ }
37
+
38
+ .pp-tooltip {
39
+ margin-left: 8px;
40
+ display: inline-block;
41
+ background: #ffffff;
42
+ border: 1px solid #d8eeee;
43
+ color: #2f6f6b;
44
+ padding: 6px 10px;
45
+ border-radius: 14px;
46
+ font-size: 12px;
47
+ box-shadow: 0 6px 14px rgba(0,0,0,0.06);
48
+ }
49
+
50
+ /* Main 3 columns */
51
+ .pp-main {
52
+ display: flex;
53
+ gap: 1vw;
54
+ align-items: start;
55
+ justify-content: space-around;
56
+ }
57
+
58
+ /* LEFT */
59
+ .pp-left {
60
+ display: flex;
61
+ justify-content: center;
62
+ }
63
+
64
+ .word-card {
65
+ width: 22vw;
66
+ height: 34vw;
67
+ background: #e9f7f6;
68
+ border-radius: 18px;
69
+ padding: 22px 18px 26px;
70
+ text-align: center;
71
+ box-shadow: 0 12px 26px rgba(0, 0, 0, 0.08);
72
+ border: 3px dashed #3aaea8;
73
+ gap: 0.5vw;
74
+ display: flex;
75
+ flex-direction: column;
76
+ align-items: center;
77
+ justify-content: space-between;
78
+ }
79
+
80
+ .word-img-wrap {
81
+ width: 20vw;
82
+ height: 20vw;
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ }
87
+
88
+ .word-img-wrap img {
89
+ max-width: 100%;
90
+ max-height: 100%;
91
+ object-fit: contain;
92
+ border-radius:1vw;
93
+ }
94
+
95
+ .word-text {
96
+ font-size: 3vw;
97
+ font-weight: 800;
98
+ color: #1f2b2a;
99
+ }
100
+
101
+ .phonetic-pill {
102
+ color: #3aaea8;
103
+ font-size: 1.5vw;
104
+ font-weight: 600;
105
+ }
106
+
107
+ .audio-img {
108
+ width: 4.8vw;
109
+ cursor: pointer;
110
+
111
+
112
+ }
113
+ /* CENTER */
114
+ .pp-center {
115
+ display: flex;
116
+ flex-direction: column;
117
+ align-items: center;
118
+ gap: 14px;
119
+ position: relative;
120
+ }
121
+
122
+ .teacher-frame {
123
+ /* width: 240px;
124
+ height: 210px;
125
+ border-radius: 18px;
126
+ padding: 12px;
127
+ background: #e7f4f3;
128
+ border: 2px dashed #b9dedb;
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;*/
132
+ width: 22vw;
133
+ height: 33vw;
134
+ background: #e9f7f6;
135
+ border-radius: 18px;
136
+ padding: 22px 18px 26px;
137
+ text-align: center;
138
+ box-shadow: 0 12px 26px rgba(0, 0, 0, 0.08);
139
+ border: 3px dashed #3aaea8;
140
+ }
141
+
142
+ .teacher-frame img {
143
+ width: 100%;
144
+ height: 100%;
145
+ object-fit: cover;
146
+ border-radius: 12px;
147
+ background: #ffffff;
148
+ }
149
+
150
+ /* Listen button */
151
+ .listen-btn {
152
+ border: none;
153
+ background: #49b6ae;
154
+ color: #ffffff;
155
+ padding: 12px 18px;
156
+ border-radius: 12px;
157
+ font-weight: 700;
158
+ font-size: 16px;
159
+ display: inline-flex;
160
+ align-items: center;
161
+ gap: 10px;
162
+ cursor: pointer;
163
+ box-shadow: 0 8px 18px rgba(0,0,0,0.08);
164
+ }
165
+
166
+ .listen-btn:active {
167
+ transform: scale(0.99);
168
+ }
169
+
170
+ .listen-ico {
171
+ font-size: 18px;
172
+ }
173
+
174
+ /* Record circle */
175
+ .rec-circle {
176
+ width: 92px;
177
+ height: 92px;
178
+ border-radius: 50%;
179
+ border: none;
180
+ background: #f07b48;
181
+ color: #ffffff;
182
+ cursor: pointer;
183
+ /* box-shadow: 0 12px 22px rgba(0,0,0,0.12);*/
184
+ display: flex;
185
+ align-items: center;
186
+ justify-content: center;
187
+ /* transition: transform 0.08s ease, filter 0.2s ease;*/
188
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
189
+ transition: all 0.3s ease;
190
+ }
191
+
192
+ .rec-circle:active {
193
+ transform: scale(0.98);
194
+ }
195
+
196
+ .rec-circle.recording {
197
+ filter: brightness(0.95);
198
+ animation: recPulse 1s infinite;
199
+ }
200
+
201
+ @keyframes recPulse {
202
+ 0% {
203
+ box-shadow: 0 0 0 0 rgba(240,123,72,0.35);
204
+ }
205
+
206
+ 70% {
207
+ box-shadow: 0 0 0 18px rgba(240,123,72,0);
208
+ }
209
+
210
+ 100% {
211
+ box-shadow: 0 0 0 0 rgba(240,123,72,0);
212
+ }
213
+ }
214
+
215
+ .rec-inner {
216
+ text-align: center;
217
+ line-height: 1.1;
218
+ }
219
+
220
+ .mic {
221
+ font-size: 22px;
222
+ margin-bottom: 4px;
223
+ }
224
+
225
+ .rec-text {
226
+ font-size: 12px;
227
+ font-weight: 800;
228
+ letter-spacing: 0.6px;
229
+ }
230
+
231
+ /* RIGHT */
232
+ .pp-right {
233
+
234
+ display: flex;
235
+ justify-content: flex-start;
236
+ align-items: center;
237
+ flex-direction: column;
238
+ gap: 18vw;
239
+ }
240
+
241
+ /* little connector dots */
242
+ .connector {
243
+
244
+ display: flex;
245
+ flex-direction: column;
246
+ gap: 2vw;
247
+ }
248
+
249
+ .connector span {
250
+ width: 34px;
251
+ height: 10px;
252
+ border-radius: 999px;
253
+ background: #98d8d4;
254
+ opacity: 0.6;
255
+ }
256
+
257
+ /* feedback card */
258
+ .feedback-card {
259
+ width: 15vw;
260
+ background: #9edfd9;
261
+ border-radius: 16px;
262
+ padding: 22px 22px 24px;
263
+ box-shadow: 0 12px 22px rgba(0,0,0,0.08);
264
+ height: 15vw;
265
+ }
266
+
267
+ .feedback-title {
268
+ font-size: 18px;
269
+ font-weight: 800;
270
+ color: #205f5a;
271
+ letter-spacing: 0.6px;
272
+ margin-bottom: 8px;
273
+ }
274
+
275
+ .feedback-body {
276
+ background: transparent;
277
+ }
278
+
279
+ .feedback-muted {
280
+ color: #2c6d68;
281
+ opacity: 0.8;
282
+ font-style: italic;
283
+ font-size: 14px;
284
+ margin-top: 8px;
285
+ }
286
+
287
+ /* Result UI inside feedback */
288
+ .feedback-result {
289
+ margin-top: 8px;
290
+ }
291
+
292
+ .score-row {
293
+ display: flex;
294
+ align-items: baseline;
295
+ gap: 10px;
296
+ }
297
+
298
+ .score-label {
299
+ font-size: 12px;
300
+ color: #1d514d;
301
+ font-weight: 700;
302
+ }
303
+
304
+ .score-value {
305
+ font-size: 28px;
306
+ font-weight: 900;
307
+ color: #103c39;
308
+ }
309
+
310
+ /* Speakometer */
311
+ .meter {
312
+ margin-top: 10px;
313
+ }
314
+
315
+ .meter-track {
316
+ height: 10px;
317
+ background: rgba(255,255,255,0.45);
318
+ border-radius: 999px;
319
+ overflow: hidden;
320
+ }
321
+
322
+ .meter-fill {
323
+ height: 100%;
324
+ width: 0%;
325
+ background: #2b8f88;
326
+ transition: width 0.5s ease;
327
+ }
328
+
329
+ /* Stars */
330
+ .stars {
331
+ margin-top: 10px;
332
+ font-size: 22px;
333
+ display: flex;
334
+ gap: 4px;
335
+ }
336
+
337
+ .stars span {
338
+ color: rgba(255,255,255,0.55);
339
+ }
340
+
341
+ .stars span.active {
342
+ color: #ffcc4d;
343
+ animation: starPop 0.3s ease;
344
+ }
345
+
346
+ @keyframes starPop {
347
+ 0% {
348
+ transform: scale(0.85);
349
+ }
350
+
351
+ 60% {
352
+ transform: scale(1.12);
353
+ }
354
+
355
+ 100% {
356
+ transform: scale(1);
357
+ }
358
+ }
359
+
360
+ /* 3 lines feedback */
361
+ .feedback-lines {
362
+ margin: 10px 0 0 18px;
363
+ color: #114744;
364
+ font-size: 13.5px;
365
+ font-weight: 600;
366
+ }
367
+
368
+ /* Bottom area */
369
+ .pp-bottom {
370
+ max-width: 1200px;
371
+ margin: 22px auto 0;
372
+ display: flex;
373
+ flex-direction: column;
374
+ align-items: center;
375
+ gap: 14px;
376
+ }
377
+
378
+ /* Prev/Next line */
379
+ /* Prev/Next line */
380
+ .nav-row {
381
+ display: flex;
382
+ align-items: center;
383
+ gap: 3vw;
384
+ }
385
+
386
+ /* Increased arrow size and perfectly center it inside the circular button */
387
+ .nav-btn {
388
+ width: 7vw;
389
+ height: 7vw;
390
+ border-radius: 50%;
391
+ border: none;
392
+ background: #dcefee;
393
+ color: #1b5551;
394
+ font-size: 7vw; /* larger arrow */
395
+ display: flex; /* center text horizontally & vertically */
396
+ align-items: center;
397
+ justify-content: center;
398
+ text-align: center;
399
+ line-height: 1; /* avoid font baseline shifts */
400
+ padding: 0; /* ensure perfect centering */
401
+ cursor: pointer;
402
+ font-weight: 700;
403
+ box-shadow: 0 8px 18px rgba(0,0,0,0.06);
404
+ transition: transform 0.08s ease;
405
+ }
406
+
407
+ .nav-btn:active {
408
+ transform: scale(0.98);
409
+ }
410
+
411
+ .nav-btn:disabled {
412
+ opacity: 0.5;
413
+ cursor: not-allowed;
414
+ }
415
+ .nav-center {
416
+ display: inline-flex;
417
+ align-items: baseline;
418
+ gap: 8px;
419
+ }
420
+
421
+ .nav-letter {
422
+ font-size: 5vw;
423
+ font-weight: 900;
424
+ color: #3aaea8;
425
+ }
426
+
427
+ .nav-count {
428
+ font-size: 14px;
429
+ color: #667b79;
430
+ font-weight: 600;
431
+ }
432
+
433
+ /* Responsive override so buttons don't overflow on smaller screens */
434
+ @media (max-width: 980px) {
435
+ .nav-btn {
436
+ width: 56px;
437
+ height: 56px;
438
+ font-size: 28px;
439
+ }
440
+ }
441
+
442
+ /* Alphabet pills */
443
+ .alpha-row {
444
+ display: flex;
445
+ flex-wrap: wrap;
446
+ justify-content: center;
447
+ gap: 8px;
448
+ max-width: 900px;
449
+ }
450
+
451
+ .alpha-pill {
452
+ width: 34px;
453
+ height: 34px;
454
+ border-radius: 50%;
455
+ border: none;
456
+ background: #dfeeee;
457
+ color: #3a5a58;
458
+ font-weight: 700;
459
+ cursor: pointer;
460
+ font-size: 13px;
461
+ }
462
+
463
+ .alpha-pill.active {
464
+ background: #49b6ae;
465
+ color: #ffffff;
466
+ box-shadow: 0 6px 14px rgba(0,0,0,0.08);
467
+ }
468
+
469
+ /* Responsive */
470
+ @media (max-width: 1100px) {
471
+ .pp-main {
472
+ grid-template-columns: 260px 320px 1fr;
473
+ }
474
+ }
475
+
476
+ @media (max-width: 980px) {
477
+ .pp-main {
478
+ grid-template-columns: 1fr;
479
+ }
480
+
481
+ .pp-left, .pp-center, .pp-right {
482
+ justify-content: center;
483
+ }
484
+
485
+ .connector {
486
+ display: none;
487
+ }
488
+
489
+ .feedback-card {
490
+ width: 100%;
491
+ max-width: 520px;
492
+ }
493
+ }
494
+ .gauge-wrapper {
495
+ position: relative;
496
+ width: 20vw;
497
+ height: 10vw;
498
+ }
499
+
500
+ .gauge {
501
+ position: absolute;
502
+ left: 50%;
503
+ top: 0;
504
+ transform: translateX(-50%);
505
+ width: 100%;
506
+ height: 100%;
507
+ border-radius: 260px 260px 0 0;
508
+ overflow: hidden;
509
+ background: #f3f3f3;
510
+ box-shadow: 0 4px 10px rgba(0,0,0,0.25) inset;
511
+ }
512
+
513
+ .gauge-arc {
514
+ position: absolute;
515
+ inset: 0;
516
+ border-radius: 50%;
517
+ background: conic-gradient( from 270deg, #e53935 0deg 45deg, #fb8c00 45deg 90deg, #fbc02d 90deg 135deg, #43a047 135deg 180deg, transparent 180deg 360deg );
518
+ height: 20vw;
519
+ }
520
+
521
+ .needle {
522
+ position: absolute;
523
+ bottom: 0vw;
524
+ left: 50%;
525
+ width: 0.7vw;
526
+ height: 8vw;
527
+ background: #333;
528
+ transform: translateX(-50%) rotate(var(--angle, -90deg));
529
+ transform-origin: 50% 100%;
530
+ transition: transform 700ms cubic-bezier(.2,.9,.2,1);
531
+ border-radius: 10px;
532
+ box-shadow: 0 2px 6px rgba(0,0,0,0.5);
533
+ }
534
+
535
+ .mic-badge {
536
+ position: absolute;
537
+ bottom: -0.3vw;
538
+ left: 50%;
539
+ transform: translate(-50%, 35%);
540
+ width: 3vw;
541
+ height: 3vw;
542
+ border-radius: 50%;
543
+ background: #000;
544
+ box-shadow: 0 8px 18px rgba(0,0,0,0.4);
545
+ display: flex;
546
+ align-items: center;
547
+ justify-content: center;
548
+ }
549
+
550
+ .score-span {
551
+ color: white;
552
+ font-size: 1vw;
553
+ font-weight: bold;
554
+ }
555
+ .notepad{
556
+ display:flex;
557
+ align-items:center;
558
+ }
559
+
560
+ .user-guide-close-icon {
561
+ position: fixed;
562
+ top: 3vw;
563
+ right: 4vw;
564
+ background: #009688;
565
+ border: none;
566
+ width: 44px;
567
+ height: 44px;
568
+ border-radius: 50%;
569
+ display: flex;
570
+ align-items: center;
571
+ justify-content: center;
572
+ font-size: 2vw;
573
+ color: black;
574
+ cursor: pointer;
575
+ z-index: 2010;
576
+ box-shadow: 0 2px 8px rgba(93, 145, 195, 0.18);
577
+ transition: background 0.2s, color 0.2s;
578
+ }
579
+ /* Teacher media container — ensures image and video occupy exactly the same box */
580
+ .teacher-media {
581
+ width: 20vw; /* same as you used inline before */
582
+ max-width: 260px; /* optional limit for very wide screens */
583
+ min-width: 140px; /* optional floor */
584
+ aspect-ratio: 3 / 4; /* change to 16 / 9 if your videos are widescreen */
585
+ position: relative;
586
+ overflow: hidden;
587
+ display: block;
588
+ }
589
+
590
+ /* Shared styles for image and video so sizes match exactly */
591
+ .teacher-media__img,
592
+ .teacher-media__video {
593
+ width: 100%;
594
+ height: 100%;
595
+ display: block;
596
+ border-radius: 1vw;
597
+ border: 2px solid #ccc; /* keep previously visible border */
598
+ object-fit: cover; /* makes video/image fill box similarly */
599
+ }
600
+
601
+ /* A neutral background for video while it loads */
602
+ .teacher-media__video {
603
+ background-color: #000;
604
+ }
605
+ /* Make play/pause image match record button size and add shadow */
606
+ .listen-img {
607
+ width: 92px; /* match .rec-circle size */
608
+ height: 92px;
609
+ border-radius: 50%;
610
+ display: inline-block;
611
+ object-fit: contain; /* keep icon aspect */
612
+ cursor: pointer;
613
+ user-select: none;
614
+ margin-right: 1vw;
615
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
616
+ transition: all 0.3s ease;
617
+ /*box-shadow: 0 12px 22px rgba(0,0,0,0.12);
618
+ transition: transform 0.08s ease, filter 0.15s ease, box-shadow 0.15s ease;
619
+
620
+ box-sizing: border-box;*/
621
+ border: none;
622
+ }
623
+
624
+ /* pressed / active */
625
+ .listen-img:active {
626
+ transform: scale(0.98);
627
+ }
628
+
629
+ /* playing state — subtle visual change */
630
+ .listen-img.playing,
631
+ .listen-img[aria-pressed="true"] {
632
+ filter: brightness(0.95);
633
+ box-shadow: 0 18px 30px rgba(0,0,0,0.16);
634
+ }
635
+
636
+ /* keyboard focus for accessibility */
637
+ .listen-img:focus {
638
+ outline: 3px solid rgba(58,174,168,0.18);
639
+ outline-offset: 3px;
640
+ }
641
+
642
+ /* Small-screen fallback (keep sizes proportional) */
643
+ @media (max-width: 980px) {
644
+ .listen-img {
645
+ width: 72px;
646
+ height: 72px;
647
+ padding: 14px;
648
+ }
649
+ }
650
+ /* add oscillation keyframes and state */
651
+ @keyframes needleOscillate {
652
+ 0% {
653
+ transform: translateX(-50%) rotate(-70deg);
654
+ }
655
+
656
+ 25% {
657
+ transform: translateX(-50%) rotate(-20deg);
658
+ }
659
+
660
+ 50% {
661
+ transform: translateX(-50%) rotate(60deg);
662
+ }
663
+
664
+ 75% {
665
+ transform: translateX(-50%) rotate(-10deg);
666
+ }
667
+
668
+ 100% {
669
+ transform: translateX(-50%) rotate(-70deg);
670
+ }
671
+ }
672
+
673
+ /* existing needle default uses CSS variable for final angle */
674
+ .needle {
675
+ position: absolute;
676
+ bottom: 0vw;
677
+ left: 50%;
678
+ width: 0.7vw;
679
+ height: 8vw;
680
+ background: #333;
681
+ transform: translateX(-50%) rotate(var(--angle, -90deg));
682
+ transform-origin: 50% 100%;
683
+ transition: transform 700ms cubic-bezier(.2,.9,.2,1);
684
+ border-radius: 10px;
685
+ box-shadow: 0 2px 6px rgba(0,0,0,0.5);
686
+ }
687
+
688
+ /* while recording / waiting for score, run oscillation animation */
689
+ .needle.oscillate {
690
+ /* override transform with animation while oscillating */
691
+ animation: needleOscillate 1.2s ease-in-out infinite;
692
+ /* disable the smooth transition while animation runs to prevent conflicts */
693
+ transition: none;
694
+ }
695
+
696
+ /* when oscillation ends (isRecording/isScoring false), the animation class is removed
697
+ and the element will smoothly transition to the value provided by --angle */
698
+ .container {
699
+ display: flex;
700
+ justify-content: center;
701
+ align-items: center;
702
+ gap: 40px; /* Increase the space between elements */
703
+ }
704
+
705
+ .arrow {
706
+ font-size: 4rem; /* Increased font size for bigger buttons */
707
+ background-color: #e0f7fa;
708
+ border: none;
709
+ width: 92px; /* Set width */
710
+ height: 92px; /* Set height */
711
+ border-radius: 50%; /* Make the button circular */
712
+ display: flex;
713
+ justify-content: center;
714
+ align-items: center;
715
+ cursor: pointer;
716
+ box-shadow: 4px 4px 15px rgba(0, 0, 0, 0.2); /* Box shadow for the button */
717
+ color: #00796b; /* Set color of the arrows */
718
+ transition: background-color 0.3s, color 0.3s; /* Smooth transition for background and text color */
719
+ }
720
+
721
+ /* Disabled state styles */
722
+ .arrow:disabled {
723
+ background-color: #cfd8dc; /* Light gray background when disabled */
724
+ color: #90a4ae; /* Gray color for the arrow */
725
+ cursor: not-allowed; /* Change cursor to indicate the button is disabled */
726
+ box-shadow: none; /* Remove box shadow for disabled button */
727
+ }
728
+
729
+ .center-text {
730
+ font-size: 5rem; /* Increased font size for the 'Y' text */
731
+ font-weight: bold;
732
+ color: #00796b;
733
+ width: 4vw;
734
+ text-align: center;
735
+ }
736
+
737
+
738
+ /* Styling the container */
739
+ .image-container {
740
+ display: flex;
741
+ justify-content: center;
742
+ align-items: center;
743
+ height: 100vh;
744
+ }
745
+
746
+ /* Styling the round image with shadow */
747
+ .round-image {
748
+ width: 4.8vw; /* Adjust the size as needed */
749
+ height: 4.8vw;
750
+ border-radius: 50%; /* Makes the image round */
751
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
752
+ transition: all 0.3s ease; /* Smooth transition for animation */
753
+ cursor: pointer;
754
+ }
755
+
756
+ /* Scaling effect on click */
757
+ .round-image:active {
758
+ transform: scale(1.1); /* Scale up by 10% when clicked */
759
+ }
760
+
761
+ /* Subtle zoom in/out */
762
+ .apple-anim {
763
+ transform-origin: 50% 50%;
764
+ animation: appleZoom 2.8s ease-in-out infinite alternate;
765
+ will-change: transform;
766
+ }
767
+
768
+ @keyframes appleZoom {
769
+ from {
770
+ transform: scale(0.8);
771
+ }
772
+
773
+ to {
774
+ transform: scale(1.06);
775
+ }
776
+ /* slight zoom */
777
+ }
778
+
779
+ /* Respect reduced motion preference */
780
+ @media (prefers-reduced-motion: reduce) {
781
+ .apple-anim {
782
+ animation: none;
783
+ transform: none;
784
+ }
785
+ }
src/app/pronunciationragg/pronunciationragg.component.html ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="pp-page">
2
+
3
+ <!-- Header -->
4
+ <div class="pp-header">
5
+ <h1>Pronunciation Practice</h1>
6
+ </div>
7
+
8
+ <!-- Main area -->
9
+ <div class="pp-main">
10
+
11
+ <!-- LEFT: Word card -->
12
+ <div class="pp-left">
13
+ <div class="word-card">
14
+ <div class="word-img-wrap">
15
+ <img class="apple-anim" [src]="current.imgSrc" [alt]="current.word" />
16
+ </div>
17
+
18
+ <div class="word-text">{{ current.word }}</div>
19
+
20
+ <div class="phonetic-pill">
21
+ {{ current.phonetics }}
22
+ </div>
23
+
24
+
25
+ <div class="image-container">
26
+ <img src="assets/pronvideo/audio.png" alt="Round Image" class="round-image" (click)="playWordAudio()">
27
+ </div>
28
+
29
+ </div>
30
+ </div>
31
+
32
+ <!-- CENTER: Teacher area -->
33
+ <div class="pp-center">
34
+ <div class="word-card" style="width:30vw!important">
35
+ <div style="width:28vw;height:25vw">
36
+ <!-- show teacher image when no video is active -->
37
+ <img *ngIf="!showVideo"
38
+ style="height:100%; border-radius:1vw;"
39
+ src="assets/pronvideo/teacher.png"
40
+ alt="Teacher" />
41
+
42
+ <!-- single video element used for both teacher and feedback videos -->
43
+ <video *ngIf="showVideo"
44
+ #videoEl
45
+ [src]="videoSrc"
46
+ style="border-radius:1vw; object-fit:cover;"
47
+ controls
48
+ autoplay
49
+ (ended)="onVideoEnded()"
50
+ height="469"
51
+ width="521">
52
+ Your browser does not support the video tag.
53
+ </video>
54
+ </div>
55
+
56
+ <!-- Toggle listen button: image acts as the button and switches between play/pause -->
57
+ <!-- Replace the button with a single image that acts as a toggle control -->
58
+
59
+ <div style="display:flex;margin-top:1.8vw;gap:2vw;">
60
+ <img class="listen-img"
61
+ [src]="isPlayingVideo ? pauseIconDataUrl : playIconDataUrl"
62
+ [attr.alt]="isPlayingVideo ? 'Pause pronunciation' : 'Play pronunciation'"
63
+ [attr.aria-label]="isPlayingVideo ? 'Pause pronunciation' : 'Play pronunciation'"
64
+ [attr.aria-pressed]="isPlayingVideo"
65
+ role="button"
66
+ tabindex="0"
67
+ (click)="toggleVideoPlay()"
68
+ (keydown.enter)="toggleVideoPlay()"
69
+ (keydown.space)="toggleVideoPlay(); $event.preventDefault()" />
70
+
71
+ <!-- Record circle -->
72
+ <button class="rec-circle"
73
+ [class.recording]="isRecording"
74
+ (mousedown)="startRecording()"
75
+ (mouseup)="stopRecording()"
76
+ (mouseleave)="stopRecording()"
77
+ (touchstart)="startRecording($event)"
78
+ (touchend)="stopRecording($event)"
79
+ (touchcancel)="stopRecording($event)">
80
+ <div class="rec-inner">
81
+ <div class="mic">🎤</div>
82
+ <div class="rec-text">REC</div>
83
+ </div>
84
+ </button>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- RIGHT: Feedback panel -->
90
+ <div class="pp-right">
91
+
92
+ <!-- needle binding changed to use isOscillating -->
93
+ <div class="gauge-wrapper">
94
+ <div class="gauge">
95
+ <div class="gauge-arc"></div>
96
+ <div class="needle" [class.oscillate]="isOscillating" [style.--angle]="needleAngle + 'deg'"></div>
97
+ </div>
98
+
99
+ <div class="mic-badge">
100
+ <span class="score-span">{{score}}%</span>
101
+ </div>
102
+ </div>
103
+
104
+ <!--<div class="nav-row">
105
+ <button class="nav-btn" (click)="prev()" [disabled]="index === 0" aria-label="Previous">
106
+ <span class="nav-icon">‹</span>
107
+ </button>
108
+
109
+ <div class="nav-center">
110
+ <div class="nav-letter">{{ current.letter }}</div>
111
+ </div>
112
+
113
+ <button class="nav-btn" (click)="next()" [disabled]="index === items.length - 1" aria-label="Next">
114
+ <span class="nav-icon">›</span>
115
+ </button>
116
+ </div>-->
117
+
118
+ <div class="container">
119
+ <button class="arrow left" (click)="prev()" [disabled]="index === 0">&#8249;</button>
120
+ <span class="center-text">{{ current.letter }}</span>
121
+ <button class="arrow right" (click)="next()" [disabled]="index === items.length - 1">&#8250;</button>
122
+ </div>
123
+
124
+ </div>
125
+
126
+ </div>
127
+
128
+ </div>
129
+ <button aria-label="Close" class="user-guide-close-icon" (click)="closePopup()">×</button>
src/app/pronunciationragg/pronunciationragg.component.ts ADDED
@@ -0,0 +1,950 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
2
+ import { HttpClient } from '@angular/common/http';
3
+ import { finalize } from 'rxjs/operators';
4
+ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
5
+ import { ChangeDetectorRef } from '@angular/core';
6
+
7
+ interface PracticeItem {
8
+ letter: string;
9
+ word: string;
10
+ phonetics: string;
11
+ imgSrc: string;
12
+ audioSrc: string;
13
+ }
14
+
15
+ @Component({
16
+ selector: 'app-pronunciationragg',
17
+ templateUrl: './pronunciationragg.component.html',
18
+ styleUrls: ['./pronunciationragg.component.css']
19
+ })
20
+ export class PronunciationRaggComponent implements OnInit, OnDestroy {
21
+ @ViewChild('videoEl') videoElRef?: ElementRef<HTMLVideoElement>;
22
+
23
+ // toggle & src for teacher video
24
+ showVideo = false;
25
+ videoSrc = '';
26
+
27
+ // track play/pause state for the toggle button
28
+ isPlayingVideo = false;
29
+
30
+ // use asset image paths (replace with your actual asset filenames)
31
+ playIconDataUrl = '';
32
+ pauseIconDataUrl = '';
33
+
34
+ // ---------------------------
35
+ // CONFIG
36
+ // ---------------------------
37
+ // Change this to your server domain
38
+
39
+ private API_BASE = location.hostname.endsWith('hf.space')
40
+ ? 'https://pykara-py-learn-backend.hf.space'
41
+ : 'http://localhost:5000';
42
+
43
+ private readonly SCORE_ENDPOINT = `${this.API_BASE}/pronragg/score`;
44
+
45
+ // Prefer a mime type supported by the browser
46
+ private readonly preferredMimeTypes = [
47
+ 'audio/webm;codecs=opus',
48
+ 'audio/webm',
49
+ 'audio/ogg;codecs=opus',
50
+ 'audio/ogg'
51
+ ];
52
+
53
+ // ---------------------------
54
+ // DATA
55
+ // ---------------------------
56
+ items: PracticeItem[] = [
57
+ {
58
+ letter: 'A',
59
+ word: 'Apple',
60
+ phonetics: '/ˈæpəl/',
61
+ imgSrc: 'assets/images/pron/letter-a.png',
62
+ audioSrc: 'assets/pronvideo/audio/apple.mp3'
63
+
64
+ },
65
+ {
66
+ letter: 'B',
67
+ word: 'Ball',
68
+ phonetics: '/bɔːl/',
69
+ imgSrc: 'assets/images/pron/letter-b.png',
70
+ audioSrc: 'assets/pronvideo/audio/ball.mp3'
71
+
72
+ },
73
+ {
74
+ letter: 'C',
75
+ word: 'Cat',
76
+ phonetics: '/kæt/',
77
+ imgSrc: 'assets/images/pron/letter-c.png',
78
+ audioSrc: 'assets/pronvideo/audio/cat.mp3'
79
+
80
+ },
81
+ {
82
+ letter: 'D',
83
+ word: 'Dog',
84
+ phonetics: '/dɒɡ/',
85
+ imgSrc: 'assets/images/pron/letter-d.png',
86
+ audioSrc: 'assets/pronvideo/audio/dog.mp3'
87
+
88
+ },
89
+ {
90
+ letter: 'E',
91
+ word: 'Egg',
92
+ phonetics: '/eɡ/',
93
+ imgSrc: 'assets/images/pron/letter-e.png',
94
+ audioSrc: 'assets/pronvideo/audio/egg.mp3'
95
+
96
+ },
97
+ {
98
+ letter: 'F',
99
+ word: 'Fish',
100
+ phonetics: '/fɪʃ/',
101
+ imgSrc: 'assets/images/pron/letter-f.png',
102
+ audioSrc: 'assets/pronvideo/audio/fish.mp3'
103
+
104
+ },
105
+ {
106
+ letter: 'G',
107
+ word: 'Grapes',
108
+ phonetics: '/ɡreɪps/',
109
+ imgSrc: 'assets/images/pron/letter-g.png',
110
+ audioSrc: 'assets/pronvideo/audio/grapes.mp3'
111
+
112
+ },
113
+ {
114
+ letter: 'H',
115
+ word: 'Hat',
116
+ phonetics: '/hæt/',
117
+ imgSrc: 'assets/images/pron/letter-h.png',
118
+ audioSrc: 'assets/pronvideo/audio/hat.mp3'
119
+
120
+ },
121
+ {
122
+ letter: 'I',
123
+ word: 'Ice cream',
124
+ phonetics: '/ˈaɪs ˌkriːm/',
125
+ imgSrc: 'assets/images/pron/letter-i.png',
126
+ audioSrc: 'assets/pronvideo/audio/icecream.mp3'
127
+
128
+ },
129
+ {
130
+ letter: 'J',
131
+ word: 'Jar',
132
+ phonetics: '/dʒɑːr/',
133
+ imgSrc: 'assets/images/pron/letter-j.png',
134
+ audioSrc: 'assets/pronvideo/audio/jar.mp3'
135
+
136
+ },
137
+ {
138
+ letter: 'K',
139
+ word: 'Kite',
140
+ phonetics: '/kaɪt/',
141
+ imgSrc: 'assets/images/pron/letter-k.png',
142
+ audioSrc: 'assets/pronvideo/audio/kite.mp3'
143
+
144
+ },
145
+ {
146
+ letter: 'L',
147
+ word: 'Lion',
148
+ phonetics: '/ˈlaɪən/',
149
+ imgSrc: 'assets/images/pron/letter-l.png',
150
+ audioSrc: 'assets/pronvideo/audio/lion.mp3'
151
+
152
+ },
153
+ {
154
+ letter: 'M',
155
+ word: 'Moon',
156
+ phonetics: '/muːn/',
157
+ imgSrc: 'assets/images/pron/letter-m.png',
158
+ audioSrc: 'assets/pronvideo/audio/moon.mp3'
159
+
160
+ },
161
+ {
162
+ letter: 'N',
163
+ word: 'Nest',
164
+ phonetics: '/nest/',
165
+ imgSrc: 'assets/images/pron/letter-n.png',
166
+ audioSrc: 'assets/pronvideo/audio/nest.mp3'
167
+
168
+ },
169
+ {
170
+ letter: 'O',
171
+ word: 'Orange',
172
+ phonetics: '/ˈɒrɪndʒ/',
173
+ imgSrc: 'assets/images/pron/letter-o.png',
174
+ audioSrc: 'assets/pronvideo/audio/orange.mp3'
175
+
176
+ },
177
+ {
178
+ letter: 'P',
179
+ word: 'Pig',
180
+ phonetics: '/pɪɡ/',
181
+ imgSrc: 'assets/images/pron/letter-p.png',
182
+ audioSrc: 'assets/pronvideo/audio/pig.mp3'
183
+
184
+ },
185
+ {
186
+ letter: 'Q',
187
+ word: 'Queen',
188
+ phonetics: '/kwiːn/',
189
+ imgSrc: 'assets/images/pron/letter-q.png',
190
+ audioSrc: 'assets/pronvideo/audio/queen.mp3'
191
+
192
+ },
193
+ {
194
+ letter: 'R',
195
+ word: 'Rabbit',
196
+ phonetics: '/ˈræbɪt/',
197
+ imgSrc: 'assets/images/pron/letter-r.png',
198
+ audioSrc: 'assets/pronvideo/audio/rabbit.mp3'
199
+
200
+ },
201
+ {
202
+ letter: 'S',
203
+ word: 'Sun',
204
+ phonetics: '/sʌn/',
205
+ imgSrc: 'assets/images/pron/letter-s.png',
206
+ audioSrc: 'assets/pronvideo/audio/sun.mp3'
207
+
208
+ },
209
+ {
210
+ letter: 'T',
211
+ word: 'Tree',
212
+ phonetics: '/triː/',
213
+ imgSrc: 'assets/images/pron/letter-t.png',
214
+ audioSrc: 'assets/pronvideo/audio/tree.mp3'
215
+
216
+ },
217
+ {
218
+ letter: 'U',
219
+ word: 'Umbrella',
220
+ phonetics: '/ʌmˈbrelə/',
221
+ imgSrc: 'assets/images/pron/letter-u.png',
222
+ audioSrc: 'assets/pronvideo/audio/umbrella.mp3'
223
+
224
+ },
225
+ {
226
+ letter: 'V',
227
+ word: 'Van',
228
+ phonetics: '/væn/',
229
+ imgSrc: 'assets/images/pron/letter-v.png',
230
+ audioSrc: 'assets/pronvideo/audio/van.mp3'
231
+
232
+ },
233
+ {
234
+ letter: 'W',
235
+ word: 'Watch',
236
+ phonetics: '/wɒtʃ/',
237
+ imgSrc: 'assets/images/pron/letter-w.png',
238
+ audioSrc: 'assets/pronvideo/audio/watch.mp3'
239
+
240
+ },
241
+ {
242
+ letter: 'X',
243
+ word: 'Xylophone',
244
+ phonetics: '/ˈzaɪləfəʊn/',
245
+ imgSrc: 'assets/images/pron/letter-x.png',
246
+ audioSrc: 'assets/pronvideo/audio/xylophone.mp3'
247
+
248
+ },
249
+ {
250
+ letter: 'Y',
251
+ word: 'Yarn',
252
+ phonetics: '/jɑːn/',
253
+ imgSrc: 'assets/images/pron/letter-y.png',
254
+ audioSrc: 'assets/pronvideo/audio/yarn.mp3'
255
+
256
+ },
257
+ {
258
+ letter: 'Z',
259
+ word: 'Zebra',
260
+ phonetics: '/ˈzebrə/',
261
+ imgSrc: 'assets/images/pron/letter-z.png',
262
+ audioSrc: 'assets/pronvideo/audio/zebra.mp3'
263
+
264
+ }
265
+ ];
266
+
267
+
268
+ index = 0;
269
+
270
+ get current(): PracticeItem {
271
+ return this.items[this.index];
272
+ }
273
+
274
+ // ---------------------------
275
+ // AUDIO (word)
276
+ // ---------------------------
277
+ private wordAudio = new Audio();
278
+
279
+ // ---------------------------
280
+ // RECORDING STATE
281
+ // ---------------------------
282
+ isRecording = false;
283
+ isScoring = false;
284
+
285
+ // NEW: flag to trigger needle oscillation after release and while waiting for score
286
+ isOscillating = false;
287
+
288
+ private mediaStream?: MediaStream;
289
+ private mediaRecorder?: MediaRecorder;
290
+ private chunks: BlobPart[] = [];
291
+ private currentMimeType = 'audio/webm';
292
+
293
+ recordedAudioUrl: string | null = null;
294
+ lastRecordedBlob: Blob | null = null;
295
+
296
+ // pending gesture play support (to satisfy autoplay policies)
297
+ private pendingVideoUrl: string | null = null;
298
+ private pendingGestureListener?: (e: Event) => void;
299
+
300
+ // Last created video blob URL (revoked when replaced) — DECLARED HERE
301
+ private lastVideoBlobUrl: string | null = null;
302
+
303
+ // ---------------------------
304
+ // RESULT UI
305
+ // ---------------------------
306
+ showResult = false;
307
+ score = 0;
308
+ stars = 0;
309
+ feedbackLines: string[] = [];
310
+ feedbackHint: string = '';
311
+ videoUrl: string = '';
312
+
313
+ // ---------------------------
314
+ // LIFECYCLE
315
+ // ---------------------------
316
+ constructor(private http: HttpClient, public dialogRef: MatDialogRef<PronunciationRaggComponent>,
317
+ @Inject(MAT_DIALOG_DATA) public data: any, private cdr: ChangeDetectorRef) { }
318
+
319
+ ngOnInit(): void {
320
+ this.resetResult();
321
+ this.setupBestMimeType();
322
+
323
+ // Use images from assets instead of inline SVG data URIs.
324
+ // Ensure the files exist at these paths or update the paths to match your project.
325
+ this.playIconDataUrl = 'assets/pronvideo/play.png';
326
+ this.pauseIconDataUrl = 'assets/pronvideo/pause.png';
327
+ }
328
+
329
+ ngOnDestroy(): void {
330
+ this.cleanupRecordingUrl();
331
+ this.stopTracks();
332
+ this.safeStopRecorder();
333
+ this.wordAudio.pause();
334
+ this.wordAudio.src = '';
335
+ // ensure video cleaned up
336
+ this.resetVideo();
337
+ // revoke any created blob URL
338
+ if (this.lastVideoBlobUrl) {
339
+ try { URL.revokeObjectURL(this.lastVideoBlobUrl); } catch { }
340
+ this.lastVideoBlobUrl = null;
341
+ }
342
+ // remove any pending gesture listener to avoid leaks
343
+ if (this.pendingGestureListener) {
344
+ try { document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
345
+ this.pendingGestureListener = undefined;
346
+ this.pendingVideoUrl = null;
347
+ }
348
+ }
349
+
350
+ // ---------------------------
351
+ // RECORD & HOLD - EVENTS
352
+ // ---------------------------
353
+ async startRecording(evt?: Event): Promise<void> {
354
+ evt?.preventDefault();
355
+
356
+ if (this.isRecording || this.isScoring) return;
357
+
358
+ this.resetResult();
359
+ this.cleanupRecordingUrl();
360
+ this.lastRecordedBlob = null;
361
+
362
+ try {
363
+ this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
364
+
365
+ const options: MediaRecorderOptions = {};
366
+ if (this.currentMimeType) {
367
+ options.mimeType = this.currentMimeType;
368
+ }
369
+
370
+ this.mediaRecorder = new MediaRecorder(this.mediaStream, options);
371
+
372
+ this.chunks = [];
373
+
374
+ this.mediaRecorder.ondataavailable = (e: BlobEvent) => {
375
+ if (e.data && e.data.size > 0) this.chunks.push(e.data);
376
+ };
377
+
378
+ this.mediaRecorder.onstop = () => {
379
+ this.onRecordingStopped();
380
+ };
381
+
382
+ this.mediaRecorder.start();
383
+ this.isRecording = true;
384
+
385
+ } catch {
386
+ this.isRecording = false;
387
+ this.stopTracks();
388
+ }
389
+ }
390
+
391
+ stopRecording(evt?: Event): void {
392
+ evt?.preventDefault();
393
+
394
+ if (!this.isRecording) return;
395
+
396
+ // End recording state
397
+ this.isRecording = false;
398
+
399
+ // Start oscillation immediately when user releases the record button:
400
+ // visual feedback that we are waiting for the backend scoring result.
401
+ this.isOscillating = true;
402
+
403
+ // Stop recorder safely
404
+ this.safeStopRecorder();
405
+
406
+ // Stop mic tracks
407
+ this.stopTracks();
408
+ }
409
+
410
+ private safeStopRecorder(): void {
411
+ try {
412
+ if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
413
+ this.mediaRecorder.stop();
414
+ }
415
+ } catch {
416
+ // ignore
417
+ }
418
+ }
419
+
420
+ private onRecordingStopped(): void {
421
+ try {
422
+ const blob = new Blob(this.chunks, { type: this.currentMimeType || 'audio/webm' });
423
+ this.lastRecordedBlob = blob;
424
+ this.recordedAudioUrl = URL.createObjectURL(blob);
425
+
426
+ // Send to backend for scoring
427
+ this.sendForScoring(blob, this.current.word);
428
+ } catch {
429
+ // If blob creation fails, stop oscillation and keep score hidden
430
+ this.isOscillating = false;
431
+ this.showResult = false;
432
+ } finally {
433
+ this.chunks = [];
434
+ }
435
+ }
436
+
437
+ // Optional: play student recording from UI button
438
+ playUserRecording(): void {
439
+ if (!this.recordedAudioUrl) return;
440
+ try {
441
+ const a = new Audio(this.recordedAudioUrl);
442
+ a.play().catch(() => { });
443
+ } catch {
444
+ // ignore
445
+ }
446
+ }
447
+
448
+ // ---------------------------
449
+ // BACKEND SCORING
450
+ // ---------------------------
451
+ private sendForScoring(blob: Blob, expectedWord: string): void {
452
+ if (!blob || !expectedWord) {
453
+ // nothing to do -> stop oscillation
454
+ this.isOscillating = false;
455
+ return;
456
+ }
457
+
458
+ const fd = new FormData();
459
+
460
+ // Give a reasonable filename extension
461
+ const ext = this.currentMimeType.includes('ogg') ? 'ogg' : 'webm';
462
+ fd.append('audio', blob, `student.${ext}`);
463
+ fd.append('word', expectedWord);
464
+
465
+ // Request inline base64 MP4 segment from the backend
466
+ fd.append('return_segment_base64', '1');
467
+
468
+ // show scoring state
469
+ this.isScoring = true;
470
+
471
+ this.http.post<any>(this.SCORE_ENDPOINT, fd)
472
+ .pipe(finalize(() => {
473
+
474
+ // stop scoring state and oscillation once HTTP completes (success or error)
475
+ this.isScoring = false;
476
+ this.isOscillating = false;
477
+ this.cdr.detectChanges();
478
+ }))
479
+ .subscribe({
480
+ next: (res) => {
481
+ const s = this.normalizeScore(res?.score);
482
+ this.score = s;
483
+ this.feedbackHint = res.hint || '';
484
+ // prefer inline blob when returned
485
+ if (res?.videoBlobBase64) {
486
+ // revoke previous blob URL if present
487
+ if (this.lastVideoBlobUrl) {
488
+ try { URL.revokeObjectURL(this.lastVideoBlobUrl); } catch { }
489
+ this.lastVideoBlobUrl = null;
490
+ }
491
+ try {
492
+ const binary = atob(res.videoBlobBase64);
493
+ const len = binary.length;
494
+ const bytes = new Uint8Array(len);
495
+ for (let i = 0; i < len; i++) {
496
+ bytes[i] = binary.charCodeAt(i);
497
+ }
498
+ const blob = new Blob([bytes], { type: 'video/mp4' });
499
+ const url = URL.createObjectURL(blob);
500
+ this.lastVideoBlobUrl = url;
501
+ this.videoUrl = url;
502
+ } catch {
503
+ // fallback: use server-provided videoUrl if base64 decoding fails
504
+ this.videoUrl = res.videoUrl || '';
505
+ }
506
+ } else {
507
+ this.videoUrl = res.videoUrl || '';
508
+ }
509
+
510
+ this.stars = this.mapStars(s);
511
+
512
+ this.feedbackLines = this.buildFeedbackFromScore(s);
513
+
514
+ this.showResult = true;
515
+
516
+ // Try to play feedback video; if autoplay blocked we register a gesture fallback.
517
+ if (this.videoUrl) {
518
+ this.tryPlayFeedbackVideo(s, this.videoUrl);
519
+ }
520
+ },
521
+ error: () => {
522
+ this.score = 0;
523
+ this.stars = 1;
524
+ this.feedbackLines = this.buildFeedbackFromScore(0);
525
+ this.showResult = true;
526
+ this.cdr.detectChanges();
527
+ }
528
+ });
529
+ }
530
+
531
+ private normalizeScore(val: any): number {
532
+ const n = Number(val);
533
+ if (Number.isNaN(n)) return 0;
534
+ if (n < 0) return 0;
535
+ if (n > 100) return 100;
536
+ return Math.round(n);
537
+ }
538
+
539
+ // ---------------------------
540
+ // RESULT HELPERS (frontend-only)
541
+ // ---------------------------
542
+ private resetResult(): void {
543
+ this.showResult = false;
544
+ this.score = 0;
545
+ this.stars = 0;
546
+ this.feedbackLines = [];
547
+ this.isPlayingVideo = false;
548
+ this.isOscillating = false;
549
+ }
550
+
551
+ /**
552
+ * Reset only the speakometer/score UI without touching recording state or video playback flags.
553
+ */
554
+ private resetSpeakometer(): void {
555
+ this.score = 0;
556
+ this.stars = 0;
557
+ this.feedbackLines = [];
558
+ this.feedbackHint = '';
559
+ this.showResult = false;
560
+ this.isOscillating = false;
561
+ }
562
+
563
+ private mapStars(s: number): number {
564
+ if (s >= 90) return 5;
565
+ if (s >= 80) return 4;
566
+ if (s >= 70) return 3;
567
+ if (s >= 60) return 2;
568
+ return 1;
569
+ }
570
+
571
+ private buildFeedbackFromScore(s: number): string[] {
572
+ if (s >= 90) {
573
+ return [
574
+ 'Excellent pronunciation.',
575
+ 'Very clear vowel and ending sound.',
576
+ 'Keep the same speed.'
577
+ ];
578
+ }
579
+ if (s >= 80) {
580
+ return [
581
+ 'Very good attempt.',
582
+ 'Slightly improve the main vowel.',
583
+ 'Ending sound is almost perfect.'
584
+ ];
585
+ }
586
+ if (s >= 70) {
587
+ return [
588
+ 'Good try.',
589
+ 'Listen once more and repeat slowly.',
590
+ 'Focus on the first sound.'
591
+ ];
592
+ }
593
+ if (s >= 50) {
594
+ return [
595
+ 'Nice effort.',
596
+ 'Try a slower pronunciation.',
597
+ 'Record again after listening.'
598
+ ];
599
+ }
600
+ return [
601
+ 'Try again.',
602
+ 'Listen to the model carefully.',
603
+ 'Speak clearly and record once more.'
604
+ ];
605
+ }
606
+
607
+ // ---------------------------
608
+ // MIME TYPE SELECTION
609
+ // ---------------------------
610
+ private setupBestMimeType(): void {
611
+ if (!(window as any).MediaRecorder) {
612
+ this.currentMimeType = 'audio/webm';
613
+ return;
614
+ }
615
+
616
+ for (const t of this.preferredMimeTypes) {
617
+ try {
618
+ if ((window as any).MediaRecorder.isTypeSupported(t)) {
619
+ this.currentMimeType = t;
620
+ return;
621
+ }
622
+ } catch {
623
+ // ignore
624
+ }
625
+ }
626
+
627
+ this.currentMimeType = 'audio/webm';
628
+ }
629
+
630
+ // ---------------------------
631
+ // CLEANUP
632
+ // ---------------------------
633
+ private stopTracks(): void {
634
+ try {
635
+ this.mediaStream?.getTracks().forEach(t => t.stop());
636
+ } catch {
637
+ // ignore
638
+ } finally {
639
+ this.mediaStream = undefined;
640
+ }
641
+ }
642
+
643
+ private cleanupRecordingUrl(): void {
644
+ if (this.recordedAudioUrl) {
645
+ try {
646
+ URL.revokeObjectURL(this.recordedAudioUrl);
647
+ } catch {
648
+ // ignore
649
+ }
650
+ this.recordedAudioUrl = null;
651
+ }
652
+ }
653
+
654
+ // ---------------------------
655
+ // NAVIGATION
656
+ // ---------------------------
657
+ prev(): void {
658
+ if (this.index === 0) return;
659
+ this.index--;
660
+ this.resetExerciseState();
661
+ }
662
+
663
+ next(): void {
664
+ if (this.index >= this.items.length - 1) return;
665
+ this.index++;
666
+ this.resetExerciseState();
667
+ }
668
+
669
+ goTo(i: number): void {
670
+ if (i < 0 || i >= this.items.length) return;
671
+ this.index = i;
672
+ this.resetExerciseState();
673
+ }
674
+
675
+ private resetExerciseState(): void {
676
+ // reset UI result data
677
+ this.resetResult();
678
+ this.cleanupRecordingUrl();
679
+ this.lastRecordedBlob = null;
680
+
681
+ // stop any recording/scoring
682
+ this.isRecording = false;
683
+ this.isScoring = false;
684
+
685
+ // stop media tracks and recorder
686
+ this.stopTracks();
687
+ this.safeStopRecorder();
688
+
689
+ // STOP and CLEAR any playing video so next exercise starts clean
690
+ this.resetVideo();
691
+ }
692
+
693
+ // new helper: stop & clear video element + reset related flags
694
+ private resetVideo(): void {
695
+ try {
696
+ const v = this.videoElRef?.nativeElement;
697
+ if (v) {
698
+ try { v.pause(); } catch { /* ignore */ }
699
+ try { v.currentTime = 0; } catch { /* ignore */ }
700
+ try { v.removeAttribute('src'); } catch { /* ignore */ }
701
+ try { v.load(); } catch { /* ignore */ }
702
+ }
703
+ } catch {
704
+ // ignore any DOM errors
705
+ } finally {
706
+ // revoke any blob url we created
707
+ if (this.lastVideoBlobUrl) {
708
+ try { URL.revokeObjectURL(this.lastVideoBlobUrl); } catch { }
709
+ this.lastVideoBlobUrl = null;
710
+ }
711
+ this.showVideo = false;
712
+ this.videoSrc = '';
713
+ this.isPlayingVideo = false;
714
+ }
715
+ }
716
+
717
+ // ---------------------------
718
+ // WORD AUDIO
719
+ // ---------------------------
720
+ playWordAudio(): void {
721
+ // reset speakometer when starting model audio playback
722
+ this.resetSpeakometer();
723
+
724
+ const src = this.current.audioSrc || this.getAudioSrcFromWord(this.current.word);
725
+ if (!src) return;
726
+
727
+ try {
728
+ this.wordAudio.pause();
729
+ this.wordAudio.currentTime = 0;
730
+ this.wordAudio.src = src;
731
+ this.wordAudio.play().catch(() => { });
732
+ } catch {
733
+ // ignore
734
+ }
735
+ }
736
+
737
+ private getAudioSrcFromWord(word: string): string {
738
+ if (!word) return '';
739
+ const fileName = word.trim().toLowerCase().replace(/\s+/g, '-');
740
+ return `assets/pronvideo/audio/${fileName}.mp3`;
741
+ }
742
+
743
+ get needleAngle(): number {
744
+ const val = Math.max(0, Math.min(100, Number(this.score ?? 0)));
745
+ return -90 + (val * 1.8);
746
+ }
747
+
748
+ closePopup(): void {
749
+ this.dialogRef.close();
750
+ }
751
+
752
+ // ---------------------------
753
+ // TEACHER VIDEO
754
+ // ---------------------------
755
+ playWordVideo(): void {
756
+ // Reset speakometer when user requests the teacher video
757
+ this.resetSpeakometer();
758
+
759
+ // Build video path based on current.word (assets/videos/<word>.mp4)
760
+ const src = this.getVideoSrcFromWord(this.current.word);
761
+ if (!src) return;
762
+
763
+ this.showVideo = true;
764
+ this.videoSrc = src;
765
+
766
+ // Wait for template to render, then set and play video safely
767
+ setTimeout(() => {
768
+ try {
769
+ const v = this.videoElRef?.nativeElement;
770
+ if (v) {
771
+ // sync UI state to element events and reset speakometer when native play occurs
772
+ v.addEventListener('play', () => {
773
+ this.isPlayingVideo = true;
774
+ // keep resetting speakometer on native play to ensure gauge clears
775
+ this.resetSpeakometer();
776
+ });
777
+ v.addEventListener('pause', () => this.isPlayingVideo = false);
778
+
779
+ v.pause();
780
+ v.src = this.videoSrc;
781
+ v.load();
782
+ v.play().then(() => {
783
+ this.isPlayingVideo = true;
784
+ }).catch(() => {
785
+ // ignore autoplay errors; UI still shows the control
786
+ this.isPlayingVideo = !v.paused;
787
+ });
788
+ }
789
+ } catch {
790
+ // ignore
791
+ }
792
+ }, 0);
793
+ }
794
+
795
+ /**
796
+ * Toggles play/pause for the currently-displayed video.
797
+ */
798
+ toggleVideoPlay(): void {
799
+ try {
800
+ if (!this.showVideo) {
801
+ this.playWordVideo();
802
+ return;
803
+ }
804
+
805
+ const v = this.videoElRef?.nativeElement;
806
+ if (!v) {
807
+ this.videoSrc = this.getVideoSrcFromWord(this.current.word);
808
+ this.showVideo = true;
809
+ return;
810
+ }
811
+
812
+ if (v.paused) {
813
+ // user is starting playback -> reset speakometer
814
+ this.resetSpeakometer();
815
+
816
+ v.play().then(() => {
817
+ this.isPlayingVideo = true;
818
+ }).catch(() => {
819
+ this.isPlayingVideo = !v.paused;
820
+ });
821
+ } else {
822
+ v.pause();
823
+ this.isPlayingVideo = false;
824
+ }
825
+ } catch {
826
+ // ignore
827
+ }
828
+ }
829
+
830
+ /**
831
+ * Attempts to play a feedback video returned by the backend in the same video element.
832
+ * If autoplay is blocked we register a one-time body click handler so the first user gesture starts playback.
833
+ */
834
+ private tryPlayFeedbackVideo(score: number, videoUrl: string): void {
835
+ if (!videoUrl) return;
836
+
837
+ // ensure UI shows the video element
838
+ this.showVideo = true;
839
+ this.videoSrc = videoUrl;
840
+
841
+ setTimeout(() => {
842
+ try {
843
+ const v = this.videoElRef?.nativeElement;
844
+ if (!v) {
845
+ // element not yet available -> register gesture fallback
846
+ this.registerGestureForPendingVideo(videoUrl);
847
+ return;
848
+ }
849
+
850
+ try { v.pause(); } catch { /* ignore */ }
851
+
852
+ v.src = this.videoSrc;
853
+ v.load();
854
+ v.play().then(() => {
855
+ this.isPlayingVideo = true;
856
+ }).catch(() => {
857
+ // autoplay likely blocked -> register gesture fallback
858
+ this.isPlayingVideo = !v.paused;
859
+ this.registerGestureForPendingVideo(videoUrl);
860
+ });
861
+ } catch {
862
+ // ignore
863
+ }
864
+ }, 0);
865
+ }
866
+
867
+ /**
868
+ * Register a one-time body click handler to play a pending feedback video when user interacts.
869
+ * This satisfies browser autoplay policies (requires a user gesture).
870
+ */
871
+ private registerGestureForPendingVideo(videoUrl: string): void {
872
+ // if already registered, update pending URL and exit
873
+ this.pendingVideoUrl = videoUrl;
874
+
875
+ if (this.pendingGestureListener) return;
876
+
877
+ this.pendingGestureListener = (e: Event) => {
878
+ try {
879
+ // show video and set src so element renders if it wasn't already
880
+ this.showVideo = true;
881
+ this.videoSrc = this.pendingVideoUrl || videoUrl;
882
+
883
+ // attempt play on the element after change detection
884
+ setTimeout(() => {
885
+ try {
886
+ const v = this.videoElRef?.nativeElement;
887
+ if (v) {
888
+ v.src = this.videoSrc;
889
+ v.load();
890
+ v.play().then(() => {
891
+ this.isPlayingVideo = true;
892
+ }).catch(() => {
893
+ this.isPlayingVideo = !v.paused;
894
+ });
895
+ }
896
+ } catch {
897
+ // ignore
898
+ }
899
+ }, 0);
900
+ } finally {
901
+ // cleanup listener and pending state
902
+ try { if (this.pendingGestureListener) document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
903
+ this.pendingGestureListener = undefined;
904
+ this.pendingVideoUrl = null;
905
+ // ensure gauge is not oscillating after user gesture
906
+ this.isOscillating = false;
907
+ }
908
+ };
909
+
910
+ // use capture so early gestures are caught even if other handlers stop propagation
911
+ try { document.body.addEventListener('click', this.pendingGestureListener, true); } catch { /* ignore */ }
912
+ }
913
+
914
+ onVideoEnded(): void {
915
+ // Ensure the native element is stopped and cleared so the thumbnail can re-appear.
916
+ try {
917
+ const v = this.videoElRef?.nativeElement;
918
+ if (v) {
919
+ try { v.pause(); } catch { }
920
+ try { v.currentTime = 0; } catch { }
921
+ try { v.src = ''; } catch { }
922
+ try { v.load(); } catch { }
923
+ }
924
+ } catch {
925
+ // ignore DOM errors
926
+ } finally {
927
+ // show the thumbnail again and reset video state
928
+ this.showVideo = false;
929
+ this.isPlayingVideo = false;
930
+ this.videoSrc = '';
931
+
932
+ // stop any lingering oscillation and pending gesture state
933
+ this.isOscillating = false;
934
+ if (this.pendingGestureListener) {
935
+ try { document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
936
+ this.pendingGestureListener = undefined;
937
+ this.pendingVideoUrl = null;
938
+ }
939
+
940
+ // ensure template updates immediately
941
+ try { this.cdr.detectChanges(); } catch { }
942
+ }
943
+ }
944
+
945
+ private getVideoSrcFromWord(word: string): string {
946
+ if (!word) return '';
947
+ const fileName = word.trim().toLowerCase().replace(/\s+/g, '-');
948
+ return `assets/pronvideo/videos/${fileName}.mp4`;
949
+ }
950
+ }
src/app/pronunciationvideo/pronunciationvideo.component.css ADDED
@@ -0,0 +1,785 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :host {
2
+ display: block;
3
+ /*font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;*/
4
+ font-family: Raleway, Roboto, "Helvetica Neue", sans-serif;
5
+ }
6
+
7
+ /* Page background */
8
+ .pp-page {
9
+ height: 85vh;
10
+ /* background: #e9f7f6;*/
11
+ padding: 28px 24px 18px;
12
+ box-sizing: border-box;
13
+ border: 7px solid #3aaea8;
14
+ border-radius: 1vw;
15
+ }
16
+
17
+ /* Header */
18
+ .pp-header {
19
+ text-align: center;
20
+ margin-bottom: 18px;
21
+ }
22
+
23
+ .pp-header h1 {
24
+ margin: 0;
25
+ font-size: 42px;
26
+ font-weight: 800;
27
+ color: #3aaea8;
28
+ letter-spacing: 0.3px;
29
+ }
30
+
31
+ .pp-sub {
32
+ margin-top: 6px;
33
+ color: #6b7f7e;
34
+ font-size: 15px;
35
+ position: relative;
36
+ }
37
+
38
+ .pp-tooltip {
39
+ margin-left: 8px;
40
+ display: inline-block;
41
+ background: #ffffff;
42
+ border: 1px solid #d8eeee;
43
+ color: #2f6f6b;
44
+ padding: 6px 10px;
45
+ border-radius: 14px;
46
+ font-size: 12px;
47
+ box-shadow: 0 6px 14px rgba(0,0,0,0.06);
48
+ }
49
+
50
+ /* Main 3 columns */
51
+ .pp-main {
52
+ display: flex;
53
+ gap: 1vw;
54
+ align-items: start;
55
+ justify-content: space-around;
56
+ }
57
+
58
+ /* LEFT */
59
+ .pp-left {
60
+ display: flex;
61
+ justify-content: center;
62
+ }
63
+
64
+ .word-card {
65
+ width: 22vw;
66
+ height: 34vw;
67
+ background: #e9f7f6;
68
+ border-radius: 18px;
69
+ padding: 22px 18px 26px;
70
+ text-align: center;
71
+ box-shadow: 0 12px 26px rgba(0, 0, 0, 0.08);
72
+ border: 3px dashed #3aaea8;
73
+ gap: 0.5vw;
74
+ display: flex;
75
+ flex-direction: column;
76
+ align-items: center;
77
+ justify-content: space-between;
78
+ }
79
+
80
+ .word-img-wrap {
81
+ width: 20vw;
82
+ height: 20vw;
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ }
87
+
88
+ .word-img-wrap img {
89
+ max-width: 100%;
90
+ max-height: 100%;
91
+ object-fit: contain;
92
+ border-radius:1vw;
93
+ }
94
+
95
+ .word-text {
96
+ font-size: 3vw;
97
+ font-weight: 800;
98
+ color: #1f2b2a;
99
+ }
100
+
101
+ .phonetic-pill {
102
+ color: #3aaea8;
103
+ font-size: 1.5vw;
104
+ font-weight: 600;
105
+ }
106
+
107
+ .audio-img {
108
+ width: 4.8vw;
109
+ cursor: pointer;
110
+
111
+
112
+ }
113
+ /* CENTER */
114
+ .pp-center {
115
+ display: flex;
116
+ flex-direction: column;
117
+ align-items: center;
118
+ gap: 14px;
119
+ position: relative;
120
+ }
121
+
122
+ .teacher-frame {
123
+ /* width: 240px;
124
+ height: 210px;
125
+ border-radius: 18px;
126
+ padding: 12px;
127
+ background: #e7f4f3;
128
+ border: 2px dashed #b9dedb;
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;*/
132
+ width: 22vw;
133
+ height: 33vw;
134
+ background: #e9f7f6;
135
+ border-radius: 18px;
136
+ padding: 22px 18px 26px;
137
+ text-align: center;
138
+ box-shadow: 0 12px 26px rgba(0, 0, 0, 0.08);
139
+ border: 3px dashed #3aaea8;
140
+ }
141
+
142
+ .teacher-frame img {
143
+ width: 100%;
144
+ height: 100%;
145
+ object-fit: cover;
146
+ border-radius: 12px;
147
+ background: #ffffff;
148
+ }
149
+
150
+ /* Listen button */
151
+ .listen-btn {
152
+ border: none;
153
+ background: #49b6ae;
154
+ color: #ffffff;
155
+ padding: 12px 18px;
156
+ border-radius: 12px;
157
+ font-weight: 700;
158
+ font-size: 16px;
159
+ display: inline-flex;
160
+ align-items: center;
161
+ gap: 10px;
162
+ cursor: pointer;
163
+ box-shadow: 0 8px 18px rgba(0,0,0,0.08);
164
+ }
165
+
166
+ .listen-btn:active {
167
+ transform: scale(0.99);
168
+ }
169
+
170
+ .listen-ico {
171
+ font-size: 18px;
172
+ }
173
+
174
+ /* Record circle */
175
+ .rec-circle {
176
+ width: 92px;
177
+ height: 92px;
178
+ border-radius: 50%;
179
+ border: none;
180
+ background: #f07b48;
181
+ color: #ffffff;
182
+ cursor: pointer;
183
+ /* box-shadow: 0 12px 22px rgba(0,0,0,0.12);*/
184
+ display: flex;
185
+ align-items: center;
186
+ justify-content: center;
187
+ /* transition: transform 0.08s ease, filter 0.2s ease;*/
188
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
189
+ transition: all 0.3s ease;
190
+ }
191
+
192
+ .rec-circle:active {
193
+ transform: scale(0.98);
194
+ }
195
+
196
+ .rec-circle.recording {
197
+ filter: brightness(0.95);
198
+ animation: recPulse 1s infinite;
199
+ }
200
+
201
+ @keyframes recPulse {
202
+ 0% {
203
+ box-shadow: 0 0 0 0 rgba(240,123,72,0.35);
204
+ }
205
+
206
+ 70% {
207
+ box-shadow: 0 0 0 18px rgba(240,123,72,0);
208
+ }
209
+
210
+ 100% {
211
+ box-shadow: 0 0 0 0 rgba(240,123,72,0);
212
+ }
213
+ }
214
+
215
+ .rec-inner {
216
+ text-align: center;
217
+ line-height: 1.1;
218
+ }
219
+
220
+ .mic {
221
+ font-size: 22px;
222
+ margin-bottom: 4px;
223
+ }
224
+
225
+ .rec-text {
226
+ font-size: 12px;
227
+ font-weight: 800;
228
+ letter-spacing: 0.6px;
229
+ }
230
+
231
+ /* RIGHT */
232
+ .pp-right {
233
+
234
+ display: flex;
235
+ justify-content: flex-start;
236
+ align-items: center;
237
+ flex-direction: column;
238
+ gap: 18vw;
239
+ }
240
+
241
+ /* little connector dots */
242
+ .connector {
243
+
244
+ display: flex;
245
+ flex-direction: column;
246
+ gap: 2vw;
247
+ }
248
+
249
+ .connector span {
250
+ width: 34px;
251
+ height: 10px;
252
+ border-radius: 999px;
253
+ background: #98d8d4;
254
+ opacity: 0.6;
255
+ }
256
+
257
+ /* feedback card */
258
+ .feedback-card {
259
+ width: 15vw;
260
+ background: #9edfd9;
261
+ border-radius: 16px;
262
+ padding: 22px 22px 24px;
263
+ box-shadow: 0 12px 22px rgba(0,0,0,0.08);
264
+ height: 15vw;
265
+ }
266
+
267
+ .feedback-title {
268
+ font-size: 18px;
269
+ font-weight: 800;
270
+ color: #205f5a;
271
+ letter-spacing: 0.6px;
272
+ margin-bottom: 8px;
273
+ }
274
+
275
+ .feedback-body {
276
+ background: transparent;
277
+ }
278
+
279
+ .feedback-muted {
280
+ color: #2c6d68;
281
+ opacity: 0.8;
282
+ font-style: italic;
283
+ font-size: 14px;
284
+ margin-top: 8px;
285
+ }
286
+
287
+ /* Result UI inside feedback */
288
+ .feedback-result {
289
+ margin-top: 8px;
290
+ }
291
+
292
+ .score-row {
293
+ display: flex;
294
+ align-items: baseline;
295
+ gap: 10px;
296
+ }
297
+
298
+ .score-label {
299
+ font-size: 12px;
300
+ color: #1d514d;
301
+ font-weight: 700;
302
+ }
303
+
304
+ .score-value {
305
+ font-size: 28px;
306
+ font-weight: 900;
307
+ color: #103c39;
308
+ }
309
+
310
+ /* Speakometer */
311
+ .meter {
312
+ margin-top: 10px;
313
+ }
314
+
315
+ .meter-track {
316
+ height: 10px;
317
+ background: rgba(255,255,255,0.45);
318
+ border-radius: 999px;
319
+ overflow: hidden;
320
+ }
321
+
322
+ .meter-fill {
323
+ height: 100%;
324
+ width: 0%;
325
+ background: #2b8f88;
326
+ transition: width 0.5s ease;
327
+ }
328
+
329
+ /* Stars */
330
+ .stars {
331
+ margin-top: 10px;
332
+ font-size: 22px;
333
+ display: flex;
334
+ gap: 4px;
335
+ }
336
+
337
+ .stars span {
338
+ color: rgba(255,255,255,0.55);
339
+ }
340
+
341
+ .stars span.active {
342
+ color: #ffcc4d;
343
+ animation: starPop 0.3s ease;
344
+ }
345
+
346
+ @keyframes starPop {
347
+ 0% {
348
+ transform: scale(0.85);
349
+ }
350
+
351
+ 60% {
352
+ transform: scale(1.12);
353
+ }
354
+
355
+ 100% {
356
+ transform: scale(1);
357
+ }
358
+ }
359
+
360
+ /* 3 lines feedback */
361
+ .feedback-lines {
362
+ margin: 10px 0 0 18px;
363
+ color: #114744;
364
+ font-size: 13.5px;
365
+ font-weight: 600;
366
+ }
367
+
368
+ /* Bottom area */
369
+ .pp-bottom {
370
+ max-width: 1200px;
371
+ margin: 22px auto 0;
372
+ display: flex;
373
+ flex-direction: column;
374
+ align-items: center;
375
+ gap: 14px;
376
+ }
377
+
378
+ /* Prev/Next line */
379
+ /* Prev/Next line */
380
+ .nav-row {
381
+ display: flex;
382
+ align-items: center;
383
+ gap: 3vw;
384
+ }
385
+
386
+ /* Increased arrow size and perfectly center it inside the circular button */
387
+ .nav-btn {
388
+ width: 7vw;
389
+ height: 7vw;
390
+ border-radius: 50%;
391
+ border: none;
392
+ background: #dcefee;
393
+ color: #1b5551;
394
+ font-size: 7vw; /* larger arrow */
395
+ display: flex; /* center text horizontally & vertically */
396
+ align-items: center;
397
+ justify-content: center;
398
+ text-align: center;
399
+ line-height: 1; /* avoid font baseline shifts */
400
+ padding: 0; /* ensure perfect centering */
401
+ cursor: pointer;
402
+ font-weight: 700;
403
+ box-shadow: 0 8px 18px rgba(0,0,0,0.06);
404
+ transition: transform 0.08s ease;
405
+ }
406
+
407
+ .nav-btn:active {
408
+ transform: scale(0.98);
409
+ }
410
+
411
+ .nav-btn:disabled {
412
+ opacity: 0.5;
413
+ cursor: not-allowed;
414
+ }
415
+ .nav-center {
416
+ display: inline-flex;
417
+ align-items: baseline;
418
+ gap: 8px;
419
+ }
420
+
421
+ .nav-letter {
422
+ font-size: 5vw;
423
+ font-weight: 900;
424
+ color: #3aaea8;
425
+ }
426
+
427
+ .nav-count {
428
+ font-size: 14px;
429
+ color: #667b79;
430
+ font-weight: 600;
431
+ }
432
+
433
+ /* Responsive override so buttons don't overflow on smaller screens */
434
+ @media (max-width: 980px) {
435
+ .nav-btn {
436
+ width: 56px;
437
+ height: 56px;
438
+ font-size: 28px;
439
+ }
440
+ }
441
+
442
+ /* Alphabet pills */
443
+ .alpha-row {
444
+ display: flex;
445
+ flex-wrap: wrap;
446
+ justify-content: center;
447
+ gap: 8px;
448
+ max-width: 900px;
449
+ }
450
+
451
+ .alpha-pill {
452
+ width: 34px;
453
+ height: 34px;
454
+ border-radius: 50%;
455
+ border: none;
456
+ background: #dfeeee;
457
+ color: #3a5a58;
458
+ font-weight: 700;
459
+ cursor: pointer;
460
+ font-size: 13px;
461
+ }
462
+
463
+ .alpha-pill.active {
464
+ background: #49b6ae;
465
+ color: #ffffff;
466
+ box-shadow: 0 6px 14px rgba(0,0,0,0.08);
467
+ }
468
+
469
+ /* Responsive */
470
+ @media (max-width: 1100px) {
471
+ .pp-main {
472
+ grid-template-columns: 260px 320px 1fr;
473
+ }
474
+ }
475
+
476
+ @media (max-width: 980px) {
477
+ .pp-main {
478
+ grid-template-columns: 1fr;
479
+ }
480
+
481
+ .pp-left, .pp-center, .pp-right {
482
+ justify-content: center;
483
+ }
484
+
485
+ .connector {
486
+ display: none;
487
+ }
488
+
489
+ .feedback-card {
490
+ width: 100%;
491
+ max-width: 520px;
492
+ }
493
+ }
494
+ .gauge-wrapper {
495
+ position: relative;
496
+ width: 20vw;
497
+ height: 10vw;
498
+ }
499
+
500
+ .gauge {
501
+ position: absolute;
502
+ left: 50%;
503
+ top: 0;
504
+ transform: translateX(-50%);
505
+ width: 100%;
506
+ height: 100%;
507
+ border-radius: 260px 260px 0 0;
508
+ overflow: hidden;
509
+ background: #f3f3f3;
510
+ box-shadow: 0 4px 10px rgba(0,0,0,0.25) inset;
511
+ }
512
+
513
+ .gauge-arc {
514
+ position: absolute;
515
+ inset: 0;
516
+ border-radius: 50%;
517
+ background: conic-gradient( from 270deg, #e53935 0deg 45deg, #fb8c00 45deg 90deg, #fbc02d 90deg 135deg, #43a047 135deg 180deg, transparent 180deg 360deg );
518
+ height: 20vw;
519
+ }
520
+
521
+ .needle {
522
+ position: absolute;
523
+ bottom: 0vw;
524
+ left: 50%;
525
+ width: 0.7vw;
526
+ height: 8vw;
527
+ background: #333;
528
+ transform: translateX(-50%) rotate(var(--angle, -90deg));
529
+ transform-origin: 50% 100%;
530
+ transition: transform 700ms cubic-bezier(.2,.9,.2,1);
531
+ border-radius: 10px;
532
+ box-shadow: 0 2px 6px rgba(0,0,0,0.5);
533
+ }
534
+
535
+ .mic-badge {
536
+ position: absolute;
537
+ bottom: -0.3vw;
538
+ left: 50%;
539
+ transform: translate(-50%, 35%);
540
+ width: 3vw;
541
+ height: 3vw;
542
+ border-radius: 50%;
543
+ background: #000;
544
+ box-shadow: 0 8px 18px rgba(0,0,0,0.4);
545
+ display: flex;
546
+ align-items: center;
547
+ justify-content: center;
548
+ }
549
+
550
+ .score-span {
551
+ color: white;
552
+ font-size: 1vw;
553
+ font-weight: bold;
554
+ }
555
+ .notepad{
556
+ display:flex;
557
+ align-items:center;
558
+ }
559
+
560
+ .user-guide-close-icon {
561
+ position: fixed;
562
+ top: 3vw;
563
+ right: 4vw;
564
+ background: #009688;
565
+ border: none;
566
+ width: 44px;
567
+ height: 44px;
568
+ border-radius: 50%;
569
+ display: flex;
570
+ align-items: center;
571
+ justify-content: center;
572
+ font-size: 2vw;
573
+ color: black;
574
+ cursor: pointer;
575
+ z-index: 2010;
576
+ box-shadow: 0 2px 8px rgba(93, 145, 195, 0.18);
577
+ transition: background 0.2s, color 0.2s;
578
+ }
579
+ /* Teacher media container — ensures image and video occupy exactly the same box */
580
+ .teacher-media {
581
+ width: 20vw; /* same as you used inline before */
582
+ max-width: 260px; /* optional limit for very wide screens */
583
+ min-width: 140px; /* optional floor */
584
+ aspect-ratio: 3 / 4; /* change to 16 / 9 if your videos are widescreen */
585
+ position: relative;
586
+ overflow: hidden;
587
+ display: block;
588
+ }
589
+
590
+ /* Shared styles for image and video so sizes match exactly */
591
+ .teacher-media__img,
592
+ .teacher-media__video {
593
+ width: 100%;
594
+ height: 100%;
595
+ display: block;
596
+ border-radius: 1vw;
597
+ border: 2px solid #ccc; /* keep previously visible border */
598
+ object-fit: cover; /* makes video/image fill box similarly */
599
+ }
600
+
601
+ /* A neutral background for video while it loads */
602
+ .teacher-media__video {
603
+ background-color: #000;
604
+ }
605
+ /* Make play/pause image match record button size and add shadow */
606
+ .listen-img {
607
+ width: 92px; /* match .rec-circle size */
608
+ height: 92px;
609
+ border-radius: 50%;
610
+ display: inline-block;
611
+ object-fit: contain; /* keep icon aspect */
612
+ cursor: pointer;
613
+ user-select: none;
614
+ margin-right: 1vw;
615
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
616
+ transition: all 0.3s ease;
617
+ /*box-shadow: 0 12px 22px rgba(0,0,0,0.12);
618
+ transition: transform 0.08s ease, filter 0.15s ease, box-shadow 0.15s ease;
619
+
620
+ box-sizing: border-box;*/
621
+ border: none;
622
+ }
623
+
624
+ /* pressed / active */
625
+ .listen-img:active {
626
+ transform: scale(0.98);
627
+ }
628
+
629
+ /* playing state — subtle visual change */
630
+ .listen-img.playing,
631
+ .listen-img[aria-pressed="true"] {
632
+ filter: brightness(0.95);
633
+ box-shadow: 0 18px 30px rgba(0,0,0,0.16);
634
+ }
635
+
636
+ /* keyboard focus for accessibility */
637
+ .listen-img:focus {
638
+ outline: 3px solid rgba(58,174,168,0.18);
639
+ outline-offset: 3px;
640
+ }
641
+
642
+ /* Small-screen fallback (keep sizes proportional) */
643
+ @media (max-width: 980px) {
644
+ .listen-img {
645
+ width: 72px;
646
+ height: 72px;
647
+ padding: 14px;
648
+ }
649
+ }
650
+ /* add oscillation keyframes and state */
651
+ @keyframes needleOscillate {
652
+ 0% {
653
+ transform: translateX(-50%) rotate(-70deg);
654
+ }
655
+
656
+ 25% {
657
+ transform: translateX(-50%) rotate(-20deg);
658
+ }
659
+
660
+ 50% {
661
+ transform: translateX(-50%) rotate(60deg);
662
+ }
663
+
664
+ 75% {
665
+ transform: translateX(-50%) rotate(-10deg);
666
+ }
667
+
668
+ 100% {
669
+ transform: translateX(-50%) rotate(-70deg);
670
+ }
671
+ }
672
+
673
+ /* existing needle default uses CSS variable for final angle */
674
+ .needle {
675
+ position: absolute;
676
+ bottom: 0vw;
677
+ left: 50%;
678
+ width: 0.7vw;
679
+ height: 8vw;
680
+ background: #333;
681
+ transform: translateX(-50%) rotate(var(--angle, -90deg));
682
+ transform-origin: 50% 100%;
683
+ transition: transform 700ms cubic-bezier(.2,.9,.2,1);
684
+ border-radius: 10px;
685
+ box-shadow: 0 2px 6px rgba(0,0,0,0.5);
686
+ }
687
+
688
+ /* while recording / waiting for score, run oscillation animation */
689
+ .needle.oscillate {
690
+ /* override transform with animation while oscillating */
691
+ animation: needleOscillate 1.2s ease-in-out infinite;
692
+ /* disable the smooth transition while animation runs to prevent conflicts */
693
+ transition: none;
694
+ }
695
+
696
+ /* when oscillation ends (isRecording/isScoring false), the animation class is removed
697
+ and the element will smoothly transition to the value provided by --angle */
698
+ .container {
699
+ display: flex;
700
+ justify-content: center;
701
+ align-items: center;
702
+ gap: 40px; /* Increase the space between elements */
703
+ }
704
+
705
+ .arrow {
706
+ font-size: 4rem; /* Increased font size for bigger buttons */
707
+ background-color: #e0f7fa;
708
+ border: none;
709
+ width: 92px; /* Set width */
710
+ height: 92px; /* Set height */
711
+ border-radius: 50%; /* Make the button circular */
712
+ display: flex;
713
+ justify-content: center;
714
+ align-items: center;
715
+ cursor: pointer;
716
+ box-shadow: 4px 4px 15px rgba(0, 0, 0, 0.2); /* Box shadow for the button */
717
+ color: #00796b; /* Set color of the arrows */
718
+ transition: background-color 0.3s, color 0.3s; /* Smooth transition for background and text color */
719
+ }
720
+
721
+ /* Disabled state styles */
722
+ .arrow:disabled {
723
+ background-color: #cfd8dc; /* Light gray background when disabled */
724
+ color: #90a4ae; /* Gray color for the arrow */
725
+ cursor: not-allowed; /* Change cursor to indicate the button is disabled */
726
+ box-shadow: none; /* Remove box shadow for disabled button */
727
+ }
728
+
729
+ .center-text {
730
+ font-size: 5rem; /* Increased font size for the 'Y' text */
731
+ font-weight: bold;
732
+ color: #00796b;
733
+ width: 4vw;
734
+ text-align: center;
735
+ }
736
+
737
+
738
+ /* Styling the container */
739
+ .image-container {
740
+ display: flex;
741
+ justify-content: center;
742
+ align-items: center;
743
+ height: 100vh;
744
+ }
745
+
746
+ /* Styling the round image with shadow */
747
+ .round-image {
748
+ width: 4.8vw; /* Adjust the size as needed */
749
+ height: 4.8vw;
750
+ border-radius: 50%; /* Makes the image round */
751
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
752
+ transition: all 0.3s ease; /* Smooth transition for animation */
753
+ cursor: pointer;
754
+ }
755
+
756
+ /* Scaling effect on click */
757
+ .round-image:active {
758
+ transform: scale(1.1); /* Scale up by 10% when clicked */
759
+ }
760
+
761
+ /* Subtle zoom in/out */
762
+ .apple-anim {
763
+ transform-origin: 50% 50%;
764
+ animation: appleZoom 2.8s ease-in-out infinite alternate;
765
+ will-change: transform;
766
+ }
767
+
768
+ @keyframes appleZoom {
769
+ from {
770
+ transform: scale(0.8);
771
+ }
772
+
773
+ to {
774
+ transform: scale(1.06);
775
+ }
776
+ /* slight zoom */
777
+ }
778
+
779
+ /* Respect reduced motion preference */
780
+ @media (prefers-reduced-motion: reduce) {
781
+ .apple-anim {
782
+ animation: none;
783
+ transform: none;
784
+ }
785
+ }
src/app/pronunciationvideo/pronunciationvideo.component.html ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="pp-page">
2
+
3
+ <!-- Header -->
4
+ <div class="pp-header">
5
+ <h1>Pronunciation Practice</h1>
6
+ </div>
7
+
8
+ <!-- Main area -->
9
+ <div class="pp-main">
10
+
11
+ <!-- LEFT: Word card -->
12
+ <div class="pp-left">
13
+ <div class="word-card">
14
+ <div class="word-img-wrap">
15
+ <img class="apple-anim" [src]="current.imgSrc" [alt]="current.word" />
16
+ </div>
17
+
18
+ <div class="word-text">{{ current.word }}</div>
19
+
20
+ <div class="phonetic-pill">
21
+ {{ current.phonetics }}
22
+ </div>
23
+
24
+
25
+ <div class="image-container">
26
+ <img src="assets/pronvideo/audio.png" alt="Round Image" class="round-image" (click)="playWordAudio()">
27
+ </div>
28
+
29
+ </div>
30
+ </div>
31
+
32
+ <!-- CENTER: Teacher area -->
33
+ <div class="pp-center">
34
+ <div class="word-card" style="width:30vw!important">
35
+ <div style="width:28vw;height:25vw">
36
+ <!-- show teacher image when no video is active -->
37
+ <img *ngIf="!showVideo"
38
+ style="height:100%; border-radius:1vw;"
39
+ src="assets/pronvideo/teacher.png"
40
+ alt="Teacher" />
41
+
42
+ <!-- single video element used for both teacher and feedback videos -->
43
+ <video *ngIf="showVideo"
44
+ #videoEl
45
+ [src]="videoSrc"
46
+ style="border-radius:1vw; object-fit:cover;"
47
+ controls
48
+ autoplay
49
+ (ended)="onVideoEnded()"
50
+ height="469"
51
+ width="521">
52
+ Your browser does not support the video tag.
53
+ </video>
54
+ </div>
55
+
56
+ <!-- Toggle listen button: image acts as the button and switches between play/pause -->
57
+ <!-- Replace the button with a single image that acts as a toggle control -->
58
+
59
+ <div style="display:flex;margin-top:1.8vw;gap:2vw;">
60
+ <img class="listen-img"
61
+ [src]="isPlayingVideo ? pauseIconDataUrl : playIconDataUrl"
62
+ [attr.alt]="isPlayingVideo ? 'Pause pronunciation' : 'Play pronunciation'"
63
+ [attr.aria-label]="isPlayingVideo ? 'Pause pronunciation' : 'Play pronunciation'"
64
+ [attr.aria-pressed]="isPlayingVideo"
65
+ role="button"
66
+ tabindex="0"
67
+ (click)="toggleVideoPlay()"
68
+ (keydown.enter)="toggleVideoPlay()"
69
+ (keydown.space)="toggleVideoPlay(); $event.preventDefault()" />
70
+
71
+ <!-- Record circle -->
72
+ <button class="rec-circle"
73
+ [class.recording]="isRecording"
74
+ (mousedown)="startRecording()"
75
+ (mouseup)="stopRecording()"
76
+ (mouseleave)="stopRecording()"
77
+ (touchstart)="startRecording($event)"
78
+ (touchend)="stopRecording($event)"
79
+ (touchcancel)="stopRecording($event)">
80
+ <div class="rec-inner">
81
+ <div class="mic">🎤</div>
82
+ <div class="rec-text">REC</div>
83
+ </div>
84
+ </button>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- RIGHT: Feedback panel -->
90
+ <div class="pp-right">
91
+
92
+ <!-- needle binding changed to use isOscillating -->
93
+ <div class="gauge-wrapper">
94
+ <div class="gauge">
95
+ <div class="gauge-arc"></div>
96
+ <div class="needle" [class.oscillate]="isOscillating" [style.--angle]="needleAngle + 'deg'"></div>
97
+ </div>
98
+
99
+ <div class="mic-badge">
100
+ <span class="score-span">{{score}}%</span>
101
+ </div>
102
+ </div>
103
+
104
+ <!--<div class="nav-row">
105
+ <button class="nav-btn" (click)="prev()" [disabled]="index === 0" aria-label="Previous">
106
+ <span class="nav-icon">‹</span>
107
+ </button>
108
+
109
+ <div class="nav-center">
110
+ <div class="nav-letter">{{ current.letter }}</div>
111
+ </div>
112
+
113
+ <button class="nav-btn" (click)="next()" [disabled]="index === items.length - 1" aria-label="Next">
114
+ <span class="nav-icon">›</span>
115
+ </button>
116
+ </div>-->
117
+
118
+ <div class="container">
119
+ <button class="arrow left" (click)="prev()" [disabled]="index === 0">&#8249;</button>
120
+ <span class="center-text">{{ current.letter }}</span>
121
+ <button class="arrow right" (click)="next()" [disabled]="index === items.length - 1">&#8250;</button>
122
+ </div>
123
+
124
+ </div>
125
+
126
+ </div>
127
+
128
+ </div>
129
+ <button aria-label="Close" class="user-guide-close-icon" (click)="closePopup()">×</button>
src/app/pronunciationvideo/pronunciationvideo.component.ts ADDED
@@ -0,0 +1,911 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
2
+ import { HttpClient } from '@angular/common/http';
3
+ import { finalize } from 'rxjs/operators';
4
+ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
5
+ import { ChangeDetectorRef } from '@angular/core';
6
+
7
+ interface PracticeItem {
8
+ letter: string;
9
+ word: string;
10
+ phonetics: string;
11
+ imgSrc: string;
12
+ audioSrc: string;
13
+ }
14
+
15
+ @Component({
16
+ selector: 'app-pronunciationvideo',
17
+ templateUrl: './pronunciationvideo.component.html',
18
+ styleUrls: ['./pronunciationvideo.component.css']
19
+ })
20
+ export class PronunciationVideoComponent implements OnInit, OnDestroy {
21
+ @ViewChild('videoEl') videoElRef?: ElementRef<HTMLVideoElement>;
22
+
23
+ // toggle & src for teacher video
24
+ showVideo = false;
25
+ videoSrc = '';
26
+
27
+ // track play/pause state for the toggle button
28
+ isPlayingVideo = false;
29
+
30
+ // use asset image paths (replace with your actual asset filenames)
31
+ playIconDataUrl = '';
32
+ pauseIconDataUrl = '';
33
+
34
+ // ---------------------------
35
+ // CONFIG
36
+ // ---------------------------
37
+ // Change this to your server domain
38
+
39
+ private API_BASE = location.hostname.endsWith('hf.space')
40
+ ? 'https://pykara-py-learn-backend.hf.space'
41
+ : 'http://localhost:5000';
42
+
43
+ private readonly SCORE_ENDPOINT = `${this.API_BASE}/pronvideo/score`;
44
+
45
+ // Prefer a mime type supported by the browser
46
+ private readonly preferredMimeTypes = [
47
+ 'audio/webm;codecs=opus',
48
+ 'audio/webm',
49
+ 'audio/ogg;codecs=opus',
50
+ 'audio/ogg'
51
+ ];
52
+
53
+ // ---------------------------
54
+ // DATA
55
+ // ---------------------------
56
+ items: PracticeItem[] = [
57
+ {
58
+ letter: 'A',
59
+ word: 'Apple',
60
+ phonetics: '/ˈæpəl/',
61
+ imgSrc: 'assets/images/pron/letter-a.png',
62
+ audioSrc: 'assets/pronvideo/audio/apple.mp3'
63
+
64
+ },
65
+ {
66
+ letter: 'B',
67
+ word: 'Ball',
68
+ phonetics: '/bɔːl/',
69
+ imgSrc: 'assets/images/pron/letter-b.png',
70
+ audioSrc: 'assets/pronvideo/audio/ball.mp3'
71
+
72
+ },
73
+ {
74
+ letter: 'C',
75
+ word: 'Cat',
76
+ phonetics: '/kæt/',
77
+ imgSrc: 'assets/images/pron/letter-c.png',
78
+ audioSrc: 'assets/pronvideo/audio/cat.mp3'
79
+
80
+ },
81
+ {
82
+ letter: 'D',
83
+ word: 'Dog',
84
+ phonetics: '/dɒɡ/',
85
+ imgSrc: 'assets/images/pron/letter-d.png',
86
+ audioSrc: 'assets/pronvideo/audio/dog.mp3'
87
+
88
+ },
89
+ {
90
+ letter: 'E',
91
+ word: 'Egg',
92
+ phonetics: '/eɡ/',
93
+ imgSrc: 'assets/images/pron/letter-e.png',
94
+ audioSrc: 'assets/pronvideo/audio/egg.mp3'
95
+
96
+ },
97
+ {
98
+ letter: 'F',
99
+ word: 'Fish',
100
+ phonetics: '/fɪʃ/',
101
+ imgSrc: 'assets/images/pron/letter-f.png',
102
+ audioSrc: 'assets/pronvideo/audio/fish.mp3'
103
+
104
+ },
105
+ {
106
+ letter: 'G',
107
+ word: 'Grapes',
108
+ phonetics: '/ɡreɪps/',
109
+ imgSrc: 'assets/images/pron/letter-g.png',
110
+ audioSrc: 'assets/pronvideo/audio/grapes.mp3'
111
+
112
+ },
113
+ {
114
+ letter: 'H',
115
+ word: 'Hat',
116
+ phonetics: '/hæt/',
117
+ imgSrc: 'assets/images/pron/letter-h.png',
118
+ audioSrc: 'assets/pronvideo/audio/hat.mp3'
119
+
120
+ },
121
+ {
122
+ letter: 'I',
123
+ word: 'Ice cream',
124
+ phonetics: '/ˈaɪs ˌkriːm/',
125
+ imgSrc: 'assets/images/pron/letter-i.png',
126
+ audioSrc: 'assets/pronvideo/audio/icecream.mp3'
127
+
128
+ },
129
+ {
130
+ letter: 'J',
131
+ word: 'Jar',
132
+ phonetics: '/dʒɑːr/',
133
+ imgSrc: 'assets/images/pron/letter-j.png',
134
+ audioSrc: 'assets/pronvideo/audio/jar.mp3'
135
+
136
+ },
137
+ {
138
+ letter: 'K',
139
+ word: 'Kite',
140
+ phonetics: '/kaɪt/',
141
+ imgSrc: 'assets/images/pron/letter-k.png',
142
+ audioSrc: 'assets/pronvideo/audio/kite.mp3'
143
+
144
+ },
145
+ {
146
+ letter: 'L',
147
+ word: 'Lion',
148
+ phonetics: '/ˈlaɪən/',
149
+ imgSrc: 'assets/images/pron/letter-l.png',
150
+ audioSrc: 'assets/pronvideo/audio/lion.mp3'
151
+
152
+ },
153
+ {
154
+ letter: 'M',
155
+ word: 'Moon',
156
+ phonetics: '/muːn/',
157
+ imgSrc: 'assets/images/pron/letter-m.png',
158
+ audioSrc: 'assets/pronvideo/audio/moon.mp3'
159
+
160
+ },
161
+ {
162
+ letter: 'N',
163
+ word: 'Nest',
164
+ phonetics: '/nest/',
165
+ imgSrc: 'assets/images/pron/letter-n.png',
166
+ audioSrc: 'assets/pronvideo/audio/nest.mp3'
167
+
168
+ },
169
+ {
170
+ letter: 'O',
171
+ word: 'Orange',
172
+ phonetics: '/ˈɒrɪndʒ/',
173
+ imgSrc: 'assets/images/pron/letter-o.png',
174
+ audioSrc: 'assets/pronvideo/audio/orange.mp3'
175
+
176
+ },
177
+ {
178
+ letter: 'P',
179
+ word: 'Pig',
180
+ phonetics: '/pɪɡ/',
181
+ imgSrc: 'assets/images/pron/letter-p.png',
182
+ audioSrc: 'assets/pronvideo/audio/pig.mp3'
183
+
184
+ },
185
+ {
186
+ letter: 'Q',
187
+ word: 'Queen',
188
+ phonetics: '/kwiːn/',
189
+ imgSrc: 'assets/images/pron/letter-q.png',
190
+ audioSrc: 'assets/pronvideo/audio/queen.mp3'
191
+
192
+ },
193
+ {
194
+ letter: 'R',
195
+ word: 'Rabbit',
196
+ phonetics: '/ˈræbɪt/',
197
+ imgSrc: 'assets/images/pron/letter-r.png',
198
+ audioSrc: 'assets/pronvideo/audio/rabbit.mp3'
199
+
200
+ },
201
+ {
202
+ letter: 'S',
203
+ word: 'Sun',
204
+ phonetics: '/sʌn/',
205
+ imgSrc: 'assets/images/pron/letter-s.png',
206
+ audioSrc: 'assets/pronvideo/audio/sun.mp3'
207
+
208
+ },
209
+ {
210
+ letter: 'T',
211
+ word: 'Tree',
212
+ phonetics: '/triː/',
213
+ imgSrc: 'assets/images/pron/letter-t.png',
214
+ audioSrc: 'assets/pronvideo/audio/tree.mp3'
215
+
216
+ },
217
+ {
218
+ letter: 'U',
219
+ word: 'Umbrella',
220
+ phonetics: '/ʌmˈbrelə/',
221
+ imgSrc: 'assets/images/pron/letter-u.png',
222
+ audioSrc: 'assets/pronvideo/audio/umbrella.mp3'
223
+
224
+ },
225
+ {
226
+ letter: 'V',
227
+ word: 'Van',
228
+ phonetics: '/væn/',
229
+ imgSrc: 'assets/images/pron/letter-v.png',
230
+ audioSrc: 'assets/pronvideo/audio/van.mp3'
231
+
232
+ },
233
+ {
234
+ letter: 'W',
235
+ word: 'Watch',
236
+ phonetics: '/wɒtʃ/',
237
+ imgSrc: 'assets/images/pron/letter-w.png',
238
+ audioSrc: 'assets/pronvideo/audio/watch.mp3'
239
+
240
+ },
241
+ {
242
+ letter: 'X',
243
+ word: 'Xylophone',
244
+ phonetics: '/ˈzaɪləfəʊn/',
245
+ imgSrc: 'assets/images/pron/letter-x.png',
246
+ audioSrc: 'assets/pronvideo/audio/xylophone.mp3'
247
+
248
+ },
249
+ {
250
+ letter: 'Y',
251
+ word: 'Yarn',
252
+ phonetics: '/jɑːn/',
253
+ imgSrc: 'assets/images/pron/letter-y.png',
254
+ audioSrc: 'assets/pronvideo/audio/yarn.mp3'
255
+
256
+ },
257
+ {
258
+ letter: 'Z',
259
+ word: 'Zebra',
260
+ phonetics: '/ˈzebrə/',
261
+ imgSrc: 'assets/images/pron/letter-z.png',
262
+ audioSrc: 'assets/pronvideo/audio/zebra.mp3'
263
+
264
+ }
265
+ ];
266
+
267
+
268
+ index = 0;
269
+
270
+ get current(): PracticeItem {
271
+ return this.items[this.index];
272
+ }
273
+
274
+ // ---------------------------
275
+ // AUDIO (word)
276
+ // ---------------------------
277
+ private wordAudio = new Audio();
278
+
279
+ // ---------------------------
280
+ // RECORDING STATE
281
+ // ---------------------------
282
+ isRecording = false;
283
+ isScoring = false;
284
+
285
+ // NEW: flag to trigger needle oscillation after release and while waiting for score
286
+ isOscillating = false;
287
+
288
+ private mediaStream?: MediaStream;
289
+ private mediaRecorder?: MediaRecorder;
290
+ private chunks: BlobPart[] = [];
291
+ private currentMimeType = 'audio/webm';
292
+
293
+ recordedAudioUrl: string | null = null;
294
+ lastRecordedBlob: Blob | null = null;
295
+
296
+ // pending gesture play support (to satisfy autoplay policies)
297
+ private pendingVideoUrl: string | null = null;
298
+ private pendingGestureListener?: (e: Event) => void;
299
+
300
+ // ---------------------------
301
+ // RESULT UI
302
+ // ---------------------------
303
+ showResult = false;
304
+ score = 0;
305
+ stars = 0;
306
+ feedbackLines: string[] = [];
307
+ feedbackHint: string = '';
308
+ videoUrl: string = '';
309
+
310
+ // ---------------------------
311
+ // LIFECYCLE
312
+ // ---------------------------
313
+ constructor(private http: HttpClient, public dialogRef: MatDialogRef<PronunciationVideoComponent>,
314
+ @Inject(MAT_DIALOG_DATA) public data: any, private cdr: ChangeDetectorRef) { }
315
+
316
+ ngOnInit(): void {
317
+ this.resetResult();
318
+ this.setupBestMimeType();
319
+
320
+ // Use images from assets instead of inline SVG data URIs.
321
+ // Ensure the files exist at these paths or update the paths to match your project.
322
+ this.playIconDataUrl = 'assets/pronvideo/play.png';
323
+ this.pauseIconDataUrl = 'assets/pronvideo/pause.png';
324
+ }
325
+
326
+ ngOnDestroy(): void {
327
+ this.cleanupRecordingUrl();
328
+ this.stopTracks();
329
+ this.safeStopRecorder();
330
+ this.wordAudio.pause();
331
+ this.wordAudio.src = '';
332
+ // ensure video cleaned up
333
+ this.resetVideo();
334
+ // remove any pending gesture listener to avoid leaks
335
+ if (this.pendingGestureListener) {
336
+ try { document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
337
+ this.pendingGestureListener = undefined;
338
+ this.pendingVideoUrl = null;
339
+ }
340
+ }
341
+
342
+ // ---------------------------
343
+ // RECORD & HOLD - EVENTS
344
+ // ---------------------------
345
+ async startRecording(evt?: Event): Promise<void> {
346
+ evt?.preventDefault();
347
+
348
+ if (this.isRecording || this.isScoring) return;
349
+
350
+ this.resetResult();
351
+ this.cleanupRecordingUrl();
352
+ this.lastRecordedBlob = null;
353
+
354
+ try {
355
+ this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
356
+
357
+ const options: MediaRecorderOptions = {};
358
+ if (this.currentMimeType) {
359
+ options.mimeType = this.currentMimeType;
360
+ }
361
+
362
+ this.mediaRecorder = new MediaRecorder(this.mediaStream, options);
363
+
364
+ this.chunks = [];
365
+
366
+ this.mediaRecorder.ondataavailable = (e: BlobEvent) => {
367
+ if (e.data && e.data.size > 0) this.chunks.push(e.data);
368
+ };
369
+
370
+ this.mediaRecorder.onstop = () => {
371
+ this.onRecordingStopped();
372
+ };
373
+
374
+ this.mediaRecorder.start();
375
+ this.isRecording = true;
376
+
377
+ } catch {
378
+ this.isRecording = false;
379
+ this.stopTracks();
380
+ }
381
+ }
382
+
383
+ stopRecording(evt?: Event): void {
384
+ evt?.preventDefault();
385
+
386
+ if (!this.isRecording) return;
387
+
388
+ // End recording state
389
+ this.isRecording = false;
390
+
391
+ // Start oscillation immediately when user releases the record button:
392
+ // visual feedback that we are waiting for the backend scoring result.
393
+ this.isOscillating = true;
394
+
395
+ // Stop recorder safely
396
+ this.safeStopRecorder();
397
+
398
+ // Stop mic tracks
399
+ this.stopTracks();
400
+ }
401
+
402
+ private safeStopRecorder(): void {
403
+ try {
404
+ if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
405
+ this.mediaRecorder.stop();
406
+ }
407
+ } catch {
408
+ // ignore
409
+ }
410
+ }
411
+
412
+ private onRecordingStopped(): void {
413
+ try {
414
+ const blob = new Blob(this.chunks, { type: this.currentMimeType || 'audio/webm' });
415
+ this.lastRecordedBlob = blob;
416
+ this.recordedAudioUrl = URL.createObjectURL(blob);
417
+
418
+ // Send to backend for scoring
419
+ this.sendForScoring(blob, this.current.word);
420
+ } catch {
421
+ // If blob creation fails, stop oscillation and keep score hidden
422
+ this.isOscillating = false;
423
+ this.showResult = false;
424
+ } finally {
425
+ this.chunks = [];
426
+ }
427
+ }
428
+
429
+ // Optional: play student recording from UI button
430
+ playUserRecording(): void {
431
+ if (!this.recordedAudioUrl) return;
432
+ try {
433
+ const a = new Audio(this.recordedAudioUrl);
434
+ a.play().catch(() => { });
435
+ } catch {
436
+ // ignore
437
+ }
438
+ }
439
+
440
+ // ---------------------------
441
+ // BACKEND SCORING
442
+ // ---------------------------
443
+ private sendForScoring(blob: Blob, expectedWord: string): void {
444
+ if (!blob || !expectedWord) {
445
+ // nothing to do -> stop oscillation
446
+ this.isOscillating = false;
447
+ return;
448
+ }
449
+
450
+ const fd = new FormData();
451
+
452
+ // Give a reasonable filename extension
453
+ const ext = this.currentMimeType.includes('ogg') ? 'ogg' : 'webm';
454
+ fd.append('audio', blob, `student.${ext}`);
455
+ fd.append('word', expectedWord);
456
+
457
+ // show scoring state
458
+ this.isScoring = true;
459
+
460
+ this.http.post<{ score: number, hint: string, videoUrl: string }>(this.SCORE_ENDPOINT, fd)
461
+ .pipe(finalize(() => {
462
+
463
+ // stop scoring state and oscillation once HTTP completes (success or error)
464
+ this.isScoring = false;
465
+ this.isOscillating = false;
466
+ this.cdr.detectChanges();
467
+ }))
468
+ .subscribe({
469
+ next: (res) => {
470
+ const s = this.normalizeScore(res?.score);
471
+ this.score = s;
472
+ this.feedbackHint = res.hint || '';
473
+ this.videoUrl = res.videoUrl || '';
474
+ this.stars = this.mapStars(s);
475
+
476
+ this.feedbackLines = this.buildFeedbackFromScore(s);
477
+
478
+ this.showResult = true;
479
+ //this.cdr.detectChanges();
480
+
481
+ // Try to play feedback video; if autoplay blocked we register a gesture fallback.
482
+ if (this.videoUrl) {
483
+ this.tryPlayFeedbackVideo(s, this.videoUrl);
484
+ }
485
+ },
486
+ error: () => {
487
+ this.score = 0;
488
+ this.stars = 1;
489
+ this.feedbackLines = this.buildFeedbackFromScore(0);
490
+ this.showResult = true;
491
+ this.cdr.detectChanges();
492
+ }
493
+ });
494
+ }
495
+
496
+ private normalizeScore(val: any): number {
497
+ const n = Number(val);
498
+ if (Number.isNaN(n)) return 0;
499
+ if (n < 0) return 0;
500
+ if (n > 100) return 100;
501
+ return Math.round(n);
502
+ }
503
+
504
+ // ---------------------------
505
+ // RESULT HELPERS (frontend-only)
506
+ // ---------------------------
507
+ private resetResult(): void {
508
+ this.showResult = false;
509
+ this.score = 0;
510
+ this.stars = 0;
511
+ this.feedbackLines = [];
512
+ this.isPlayingVideo = false;
513
+ this.isOscillating = false;
514
+ }
515
+
516
+ /**
517
+ * Reset only the speakometer/score UI without touching recording state or video playback flags.
518
+ */
519
+ private resetSpeakometer(): void {
520
+ this.score = 0;
521
+ this.stars = 0;
522
+ this.feedbackLines = [];
523
+ this.feedbackHint = '';
524
+ this.showResult = false;
525
+ this.isOscillating = false;
526
+ }
527
+
528
+ private mapStars(s: number): number {
529
+ if (s >= 90) return 5;
530
+ if (s >= 80) return 4;
531
+ if (s >= 70) return 3;
532
+ if (s >= 60) return 2;
533
+ return 1;
534
+ }
535
+
536
+ private buildFeedbackFromScore(s: number): string[] {
537
+ if (s >= 90) {
538
+ return [
539
+ 'Excellent pronunciation.',
540
+ 'Very clear vowel and ending sound.',
541
+ 'Keep the same speed.'
542
+ ];
543
+ }
544
+ if (s >= 80) {
545
+ return [
546
+ 'Very good attempt.',
547
+ 'Slightly improve the main vowel.',
548
+ 'Ending sound is almost perfect.'
549
+ ];
550
+ }
551
+ if (s >= 70) {
552
+ return [
553
+ 'Good try.',
554
+ 'Listen once more and repeat slowly.',
555
+ 'Focus on the first sound.'
556
+ ];
557
+ }
558
+ if (s >= 50) {
559
+ return [
560
+ 'Nice effort.',
561
+ 'Try a slower pronunciation.',
562
+ 'Record again after listening.'
563
+ ];
564
+ }
565
+ return [
566
+ 'Try again.',
567
+ 'Listen to the model carefully.',
568
+ 'Speak clearly and record once more.'
569
+ ];
570
+ }
571
+
572
+ // ---------------------------
573
+ // MIME TYPE SELECTION
574
+ // ---------------------------
575
+ private setupBestMimeType(): void {
576
+ if (!(window as any).MediaRecorder) {
577
+ this.currentMimeType = 'audio/webm';
578
+ return;
579
+ }
580
+
581
+ for (const t of this.preferredMimeTypes) {
582
+ try {
583
+ if ((window as any).MediaRecorder.isTypeSupported(t)) {
584
+ this.currentMimeType = t;
585
+ return;
586
+ }
587
+ } catch {
588
+ // ignore
589
+ }
590
+ }
591
+
592
+ this.currentMimeType = 'audio/webm';
593
+ }
594
+
595
+ // ---------------------------
596
+ // CLEANUP
597
+ // ---------------------------
598
+ private stopTracks(): void {
599
+ try {
600
+ this.mediaStream?.getTracks().forEach(t => t.stop());
601
+ } catch {
602
+ // ignore
603
+ } finally {
604
+ this.mediaStream = undefined;
605
+ }
606
+ }
607
+
608
+ private cleanupRecordingUrl(): void {
609
+ if (this.recordedAudioUrl) {
610
+ try {
611
+ URL.revokeObjectURL(this.recordedAudioUrl);
612
+ } catch {
613
+ // ignore
614
+ }
615
+ this.recordedAudioUrl = null;
616
+ }
617
+ }
618
+
619
+ // ---------------------------
620
+ // NAVIGATION
621
+ // ---------------------------
622
+ prev(): void {
623
+ if (this.index === 0) return;
624
+ this.index--;
625
+ this.resetExerciseState();
626
+ }
627
+
628
+ next(): void {
629
+ if (this.index >= this.items.length - 1) return;
630
+ this.index++;
631
+ this.resetExerciseState();
632
+ }
633
+
634
+ goTo(i: number): void {
635
+ if (i < 0 || i >= this.items.length) return;
636
+ this.index = i;
637
+ this.resetExerciseState();
638
+ }
639
+
640
+ private resetExerciseState(): void {
641
+ // reset UI result data
642
+ this.resetResult();
643
+ this.cleanupRecordingUrl();
644
+ this.lastRecordedBlob = null;
645
+
646
+ // stop any recording/scoring
647
+ this.isRecording = false;
648
+ this.isScoring = false;
649
+
650
+ // stop media tracks and recorder
651
+ this.stopTracks();
652
+ this.safeStopRecorder();
653
+
654
+ // STOP and CLEAR any playing video so next exercise starts clean
655
+ this.resetVideo();
656
+ }
657
+
658
+ // new helper: stop & clear video element + reset related flags
659
+ private resetVideo(): void {
660
+ try {
661
+ const v = this.videoElRef?.nativeElement;
662
+ if (v) {
663
+ try { v.pause(); } catch { /* ignore */ }
664
+ try { v.currentTime = 0; } catch { /* ignore */ }
665
+ // remove src so browser releases resource
666
+ try { v.removeAttribute('src'); } catch { /* ignore */ }
667
+ try { v.load(); } catch { /* ignore */ }
668
+ }
669
+ } catch {
670
+ // ignore any DOM errors
671
+ } finally {
672
+ this.showVideo = false;
673
+ this.videoSrc = '';
674
+ this.isPlayingVideo = false;
675
+ }
676
+ }
677
+
678
+ // ---------------------------
679
+ // WORD AUDIO
680
+ // ---------------------------
681
+ playWordAudio(): void {
682
+ // reset speakometer when starting model audio playback
683
+ this.resetSpeakometer();
684
+
685
+ const src = this.current.audioSrc || this.getAudioSrcFromWord(this.current.word);
686
+ if (!src) return;
687
+
688
+ try {
689
+ this.wordAudio.pause();
690
+ this.wordAudio.currentTime = 0;
691
+ this.wordAudio.src = src;
692
+ this.wordAudio.play().catch(() => { });
693
+ } catch {
694
+ // ignore
695
+ }
696
+ }
697
+
698
+ private getAudioSrcFromWord(word: string): string {
699
+ if (!word) return '';
700
+ const fileName = word.trim().toLowerCase().replace(/\s+/g, '-');
701
+ return `assets/pronvideo/audio/${fileName}.mp3`;
702
+ }
703
+
704
+ get needleAngle(): number {
705
+ const val = Math.max(0, Math.min(100, Number(this.score ?? 0)));
706
+ return -90 + (val * 1.8);
707
+ }
708
+
709
+ closePopup(): void {
710
+ this.dialogRef.close();
711
+ }
712
+
713
+ // ---------------------------
714
+ // TEACHER VIDEO
715
+ // ---------------------------
716
+ playWordVideo(): void {
717
+ // Reset speakometer when user requests the teacher video
718
+ this.resetSpeakometer();
719
+
720
+ // Build video path based on current.word (assets/videos/<word>.mp4)
721
+ const src = this.getVideoSrcFromWord(this.current.word);
722
+ if (!src) return;
723
+
724
+ this.showVideo = true;
725
+ this.videoSrc = src;
726
+
727
+ // Wait for template to render, then set and play video safely
728
+ setTimeout(() => {
729
+ try {
730
+ const v = this.videoElRef?.nativeElement;
731
+ if (v) {
732
+ // sync UI state to element events and reset speakometer when native play occurs
733
+ v.addEventListener('play', () => {
734
+ this.isPlayingVideo = true;
735
+ // keep resetting speakometer on native play to ensure gauge clears
736
+ this.resetSpeakometer();
737
+ });
738
+ v.addEventListener('pause', () => this.isPlayingVideo = false);
739
+
740
+ v.pause();
741
+ v.src = this.videoSrc;
742
+ v.load();
743
+ v.play().then(() => {
744
+ this.isPlayingVideo = true;
745
+ }).catch(() => {
746
+ // ignore autoplay errors; UI still shows the control
747
+ this.isPlayingVideo = !v.paused;
748
+ });
749
+ }
750
+ } catch {
751
+ // ignore
752
+ }
753
+ }, 0);
754
+ }
755
+
756
+ /**
757
+ * Toggles play/pause for the currently-displayed video.
758
+ */
759
+ toggleVideoPlay(): void {
760
+ try {
761
+ if (!this.showVideo) {
762
+ this.playWordVideo();
763
+ return;
764
+ }
765
+
766
+ const v = this.videoElRef?.nativeElement;
767
+ if (!v) {
768
+ this.videoSrc = this.getVideoSrcFromWord(this.current.word);
769
+ this.showVideo = true;
770
+ return;
771
+ }
772
+
773
+ if (v.paused) {
774
+ // user is starting playback -> reset speakometer
775
+ this.resetSpeakometer();
776
+
777
+ v.play().then(() => {
778
+ this.isPlayingVideo = true;
779
+ }).catch(() => {
780
+ this.isPlayingVideo = !v.paused;
781
+ });
782
+ } else {
783
+ v.pause();
784
+ this.isPlayingVideo = false;
785
+ }
786
+ } catch {
787
+ // ignore
788
+ }
789
+ }
790
+
791
+ /**
792
+ * Attempts to play a feedback video returned by the backend in the same video element.
793
+ * If autoplay is blocked we register a one-time body click handler so the first user gesture starts playback.
794
+ */
795
+ private tryPlayFeedbackVideo(score: number, videoUrl: string): void {
796
+ if (!videoUrl) return;
797
+
798
+ // ensure UI shows the video element
799
+ this.showVideo = true;
800
+ this.videoSrc = videoUrl;
801
+
802
+ setTimeout(() => {
803
+ try {
804
+ const v = this.videoElRef?.nativeElement;
805
+ if (!v) {
806
+ // element not yet available -> register gesture fallback
807
+ this.registerGestureForPendingVideo(videoUrl);
808
+ return;
809
+ }
810
+
811
+ try { v.pause(); } catch { /* ignore */ }
812
+
813
+ v.src = this.videoSrc;
814
+ v.load();
815
+ v.play().then(() => {
816
+ this.isPlayingVideo = true;
817
+ }).catch(() => {
818
+ // autoplay likely blocked -> register gesture fallback
819
+ this.isPlayingVideo = !v.paused;
820
+ this.registerGestureForPendingVideo(videoUrl);
821
+ });
822
+ } catch {
823
+ // ignore
824
+ }
825
+ }, 0);
826
+ }
827
+
828
+ /**
829
+ * Register a one-time body click handler to play a pending feedback video when user interacts.
830
+ * This satisfies browser autoplay policies (requires a user gesture).
831
+ */
832
+ private registerGestureForPendingVideo(videoUrl: string): void {
833
+ // if already registered, update pending URL and exit
834
+ this.pendingVideoUrl = videoUrl;
835
+
836
+ if (this.pendingGestureListener) return;
837
+
838
+ this.pendingGestureListener = (e: Event) => {
839
+ try {
840
+ // show video and set src so element renders if it wasn't already
841
+ this.showVideo = true;
842
+ this.videoSrc = this.pendingVideoUrl || videoUrl;
843
+
844
+ // attempt play on the element after change detection
845
+ setTimeout(() => {
846
+ try {
847
+ const v = this.videoElRef?.nativeElement;
848
+ if (v) {
849
+ v.src = this.videoSrc;
850
+ v.load();
851
+ v.play().then(() => {
852
+ this.isPlayingVideo = true;
853
+ }).catch(() => {
854
+ this.isPlayingVideo = !v.paused;
855
+ });
856
+ }
857
+ } catch {
858
+ // ignore
859
+ }
860
+ }, 0);
861
+ } finally {
862
+ // cleanup listener and pending state
863
+ try { if (this.pendingGestureListener) document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
864
+ this.pendingGestureListener = undefined;
865
+ this.pendingVideoUrl = null;
866
+ // ensure gauge is not oscillating after user gesture
867
+ this.isOscillating = false;
868
+ }
869
+ };
870
+
871
+ // use capture so early gestures are caught even if other handlers stop propagation
872
+ try { document.body.addEventListener('click', this.pendingGestureListener, true); } catch { /* ignore */ }
873
+ }
874
+
875
+ onVideoEnded(): void {
876
+ // Ensure the native element is stopped and cleared so the thumbnail can re-appear.
877
+ try {
878
+ const v = this.videoElRef?.nativeElement;
879
+ if (v) {
880
+ try { v.pause(); } catch { }
881
+ try { v.currentTime = 0; } catch { }
882
+ try { v.src = ''; } catch { }
883
+ try { v.load(); } catch { }
884
+ }
885
+ } catch {
886
+ // ignore DOM errors
887
+ } finally {
888
+ // show the thumbnail again and reset video state
889
+ this.showVideo = false;
890
+ this.isPlayingVideo = false;
891
+ this.videoSrc = '';
892
+
893
+ // stop any lingering oscillation and pending gesture state
894
+ this.isOscillating = false;
895
+ if (this.pendingGestureListener) {
896
+ try { document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
897
+ this.pendingGestureListener = undefined;
898
+ this.pendingVideoUrl = null;
899
+ }
900
+
901
+ // ensure template updates immediately
902
+ try { this.cdr.detectChanges(); } catch { }
903
+ }
904
+ }
905
+
906
+ private getVideoSrcFromWord(word: string): string {
907
+ if (!word) return '';
908
+ const fileName = word.trim().toLowerCase().replace(/\s+/g, '-');
909
+ return `assets/pronvideo/videos/${fileName}.mp4`;
910
+ }
911
+ }
src/assets/pronvideo/audio.png ADDED

Git LFS Details

  • SHA256: 21066ffba9cccf98ff08e440f64e16328124c88e428aefc46e4131f4021d2aa5
  • Pointer size: 129 Bytes
  • Size of remote file: 6.16 kB
src/assets/pronvideo/audio/apple.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cd3749451facc8a9736f5ec3c16ab71c1c0a44e23c88bc2faaaba86676cfd930
3
+ size 13560
src/assets/pronvideo/audio/ball.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:eae7be3c52cf56a513aa701d71b1fbc93db1db23d5f15cb7a34b6b3f3cfa320f
3
+ size 12792
src/assets/pronvideo/audio/cat.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d5f8746b9ea52ac6f34f449603c8d24dac05bdf1463e7b2217ed3dfb36e877a9
3
+ size 12912
src/assets/pronvideo/audio/dog.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8198fcb9e7ef7ff2eb4a2a57f5bad61bb5de4896750e4004917c8a51c82ce0cb
3
+ size 11592
src/assets/pronvideo/audio/egg.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b72a200ec8e163f9615b89599b4e6cc034c93d49e9d05580ea96c5fa5a94ec45
3
+ size 9312
src/assets/pronvideo/audio/fish.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2f1c7898a6fe74acf4a509755dbcd5a14961622ab212bd9e13ce2dd423d1077a
3
+ size 8976
src/assets/pronvideo/audio/grapes.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cda848a9e69ea6f80a617d1f6f718820f86667e468087bdd89a388629e147057
3
+ size 12264
src/assets/pronvideo/audio/hat.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:09df5945baf67d5d6f3b9337dc65b768a458b70e7046e2441218e1037fe7fdb1
3
+ size 9672
src/assets/pronvideo/audio/icecream.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:60a2a41f57a2fdadce831e6b18582cfcdfe4a87b80256effb5d832a429d784d2
3
+ size 13200
src/assets/pronvideo/audio/jar.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c96580ccebde2b23c3b23399aa1d1956c7f290a8235ec3dd4881f250c6f4c0c1
3
+ size 11616
src/assets/pronvideo/audio/kite.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:27a9dee3aaa483d21ac7cf45ab11208f1c2541481d38900a2eeb0912a7d1eafe
3
+ size 10896
src/assets/pronvideo/audio/lion.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9c4c156b7f55749be6d0a81045fadc7fed907d7ad4ffe338ba2c60140f861ff1
3
+ size 10680
src/assets/pronvideo/audio/moon.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2595f31a5d53a3f48535975f196807d4e5b01d50a895b462e7de2b0fc84ab2aa
3
+ size 9960
src/assets/pronvideo/audio/nest.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:530beb8d1ebd6b89cce0187d8a3cb401b0c237eb5f656e430caf1fe96b67c9a1
3
+ size 12024
src/assets/pronvideo/audio/orange.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c96726c87265cadcdf3f392190fd8b66964451da60add484d39dcb5db4226879
3
+ size 9792
src/assets/pronvideo/audio/pig.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:48c66e98d9b044cfde83397ae699c6a90c3c396e747dc2d71f1290820082bf43
3
+ size 8544
src/assets/pronvideo/audio/queen.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1b7fc5588e5bada0d30e696253c0c47829d5a374ec8dc17de8c7b3df5200c5aa
3
+ size 9216
src/assets/pronvideo/audio/rabbit.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b22661ed72eaff38af8cccc055a2ac91200e213cad78371ee50db8023a0078f0
3
+ size 10848
src/assets/pronvideo/audio/sun.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:170d1bf45ca7c8f1dc268b4a1a5f19a986faae55780a834ecb5470509ac5ceff
3
+ size 11184
src/assets/pronvideo/audio/tree.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ab5cf4ad1e05e45125bab5f1ed611402e2c16c1175827e1d4fffd80cb7c45c11
3
+ size 9288
src/assets/pronvideo/audio/umbrella.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa1bf35b7f8f9ef437639cc80c9a2b231fae5096327f7069c176bde84efc216d
3
+ size 11640
src/assets/pronvideo/audio/van.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:249632f20e354aa4c41ecd60ef9e8cc8ee55601f72eb66b4a3a07bc7a2bb92af
3
+ size 9624
src/assets/pronvideo/audio/watch.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:629b00cae653b2aedb022391b6c9b4e0e869145f3c348fda674f206ffb635101
3
+ size 11568
src/assets/pronvideo/audio/xylophone.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b8f2764c762e59440f4bfcc40dfcdc516e86a437fbf7fc5623aa130f6dbec25
3
+ size 15744
src/assets/pronvideo/audio/yarn.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b8e6f8aa4e3b7e44aa3faafe13f113fb2704fc7fbcd0378e74006f9b0ebc1684
3
+ size 9504
src/assets/pronvideo/audio/zebra.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2873545095c089d763fd6d9185ffc868629041e7c0965d0fbbb1223d7c1a1730
3
+ size 12984
src/assets/pronvideo/feedback/consonant.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:eae0c82c7556e573bd3d4c0e9b4b885430641885fe4a445e0425f6d90a04589a
3
+ size 5481531
src/assets/pronvideo/feedback/ending.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e1bbfff2a733efd22b952ae4e32f686b06ada31b247208b81c8cb187e818695f
3
+ size 4184262
src/assets/pronvideo/feedback/multipleword.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7da6fc60d82f728164d12cb57acae93af3d7092be447f3b884ba262cf5533a1d
3
+ size 2252316
src/assets/pronvideo/feedback/silence.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b31c9c9bf22c6ebcf0ab84e23398ae019f613d5cfc494ea3d01c4541e16a56b0
3
+ size 5405291
src/assets/pronvideo/feedback/stress.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:67cbdef5635080cf0d38afbd1e4c5a0c79051363dd6fc138c7f3a1632ce47d7b
3
+ size 5214043
src/assets/pronvideo/feedback/success.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7065eb4444fff7033ffae8183901123ce45bca47114d8307652523006ca4b7b9
3
+ size 7149268
src/assets/pronvideo/feedback/syllable.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:01c7ecb44cc4eee9fb9cb32ec7f363e367c17479c0ae91883d902f428e3db9c9
3
+ size 3955318
src/assets/pronvideo/feedback/vowels.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cd9a3f4ddfdadb90b0aaadbfa0a00c5aa80342f4b133543836d2b21592eb3443
3
+ size 5837969
src/assets/pronvideo/feedback/wrongword.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b0a32e7b4ed1f4355aa0eca1bd48c5ed4030e3deb7f2e5a412cb6a3fc3d06df1
3
+ size 5194285
src/assets/pronvideo/pause.png ADDED

Git LFS Details

  • SHA256: 9efce36e388aaccad1b743e590ff1e6ff53e27260c6b48b28f46db0187d6b1db
  • Pointer size: 130 Bytes
  • Size of remote file: 10.8 kB
src/assets/pronvideo/play.png ADDED

Git LFS Details

  • SHA256: a5b587892782a79de9117ba4259158866d80f56b97aa764121cff189b135fb4e
  • Pointer size: 129 Bytes
  • Size of remote file: 3.11 kB
src/assets/pronvideo/teacher-nod.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8f85a86632a64280579167285cfe71bd32d556797ec8e58e2fc14cf8285e1fb3
3
+ size 117067
src/assets/pronvideo/teacher.png ADDED

Git LFS Details

  • SHA256: e5e6f6e2ef93ec59d4b47f41af45bc73a229c4225d2337c506ba7113ec72357c
  • Pointer size: 131 Bytes
  • Size of remote file: 294 kB