diff --git a/package-lock.json b/package-lock.json
index df650355466664f411e5a6848ae45f192ed1ba9d..db4d5c9ba755c22a4526a38ff0a894aafd19324b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@angular/compiler": "^17.1.0",
"@angular/core": "^17.1.0",
"@angular/forms": "^17.1.0",
+ "@angular/material": "^17.3.10",
"@angular/platform-browser": "^17.1.0",
"@angular/platform-browser-dynamic": "^17.1.0",
"@angular/router": "^17.1.0",
@@ -26,6 +27,7 @@
"rxjs": "~7.8.0",
"tailwindcss": "^3.4.17",
"tslib": "^2.3.0",
+ "wavesurfer.js": "^7.11.1",
"zone.js": "~0.14.3"
},
"devDependencies": {
@@ -362,6 +364,24 @@
"@angular/core": "17.3.12"
}
},
+ "node_modules/@angular/cdk": {
+ "version": "17.3.10",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.10.tgz",
+ "integrity": "sha512-b1qktT2c1TTTe5nTji/kFAVW92fULK0YhYAvJ+BjZTPKu2FniZNe8o4qqQ0pUuvtMu+ZQxp/QqFYoidIVCjScg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "optionalDependencies": {
+ "parse5": "^7.1.2"
+ },
+ "peerDependencies": {
+ "@angular/common": "^17.0.0 || ^18.0.0",
+ "@angular/core": "^17.0.0 || ^18.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@angular/cli": {
"version": "17.3.11",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.11.tgz",
@@ -535,6 +555,71 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
+ "node_modules/@angular/material": {
+ "version": "17.3.10",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.10.tgz",
+ "integrity": "sha512-hHMQES0tQPH5JW33W+mpBPuM8ybsloDTqFPuRV8cboDjosAWfJhzAKF3ozICpNlUrs62La/2Wu/756GcQrxebg==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/auto-init": "15.0.0-canary.7f224ddd4.0",
+ "@material/banner": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
+ "@material/card": "15.0.0-canary.7f224ddd4.0",
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
+ "@material/chips": "15.0.0-canary.7f224ddd4.0",
+ "@material/circular-progress": "15.0.0-canary.7f224ddd4.0",
+ "@material/data-table": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dialog": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/drawer": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/fab": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
+ "@material/form-field": "15.0.0-canary.7f224ddd4.0",
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
+ "@material/image-list": "15.0.0-canary.7f224ddd4.0",
+ "@material/layout-grid": "15.0.0-canary.7f224ddd4.0",
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/linear-progress": "15.0.0-canary.7f224ddd4.0",
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
+ "@material/radio": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/segmented-button": "15.0.0-canary.7f224ddd4.0",
+ "@material/select": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/slider": "15.0.0-canary.7f224ddd4.0",
+ "@material/snackbar": "15.0.0-canary.7f224ddd4.0",
+ "@material/switch": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-bar": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0",
+ "@material/textfield": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tooltip": "15.0.0-canary.7f224ddd4.0",
+ "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/animations": "^17.0.0 || ^18.0.0",
+ "@angular/cdk": "17.3.10",
+ "@angular/common": "^17.0.0 || ^18.0.0",
+ "@angular/core": "^17.0.0 || ^18.0.0",
+ "@angular/forms": "^17.0.0 || ^18.0.0",
+ "@angular/platform-browser": "^17.0.0 || ^18.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@angular/platform-browser": {
"version": "17.3.12",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
@@ -3196,6 +3281,808 @@
"node": ">= 0.4"
}
},
+ "node_modules/@material/animation": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-1GSJaPKef+7HRuV+HusVZHps64cmZuOItDbt40tjJVaikcaZvwmHlcTxRIqzcRoCdt5ZKHh3NoO7GB9Khg4Jnw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/auto-init": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-t7ZGpRJ3ec0QDUO0nJu/SMgLW7qcuG2KqIsEYD1Ej8qhI2xpdR2ydSDQOkVEitXmKoGol1oq4nYSBjTlB65GqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/banner": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-g9wBUZzYBizyBcBQXTIafnRUUPi7efU9gPJfzeGgkynXiccP/vh5XMmH+PBxl5v+4MlP/d4cZ2NUYoAN7UTqSA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/base": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-I9KQOKXpLfJkP8MqZyr8wZIzdPHrwPjFvGd9zSK91/vPyE4hzHRJc/0njsh9g8Lm9PRYLbifXX+719uTbHxx+A==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/button": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-BHB7iyHgRVH+JF16+iscR+Qaic+p7LU1FOLgP8KucRlpF9tTwIxQA6mJwGRi5gUtcG+vyCmzVS+hIQ6DqT/7BA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/card": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-kt7y9/IWOtJTr3Z/AoWJT3ZLN7CLlzXhx2udCLP9ootZU2bfGK0lzNwmo80bv/pJfrY9ihQKCtuGTtNxUy+vIw==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/checkbox": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-rURcrL5O1u6hzWR+dNgiQ/n89vk6tdmdP3mZgnxJx61q4I/k1yijKqNJSLrkXH7Rto3bM5NRKMOlgvMvVd7UMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/chips": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-AYAivV3GSk/T/nRIpH27sOHFPaSMrE3L0WYbnb5Wa93FgY8a0fbsFYtSH2QmtwnzXveg+B1zGTt7/xIIcynKdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "safevalues": "^0.3.4",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/circular-progress": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-DJrqCKb+LuGtjNvKl8XigvyK02y36GRkfhMUYTcJEi3PrOE00bwXtyj7ilhzEVshQiXg6AHGWXtf5UqwNrx3Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/data-table": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-/2WZsuBIq9z9RWYF5Jo6b7P6u0fwit+29/mN7rmAZ6akqUR54nXyNfoSNiyydMkzPlZZsep5KrSHododDhBZbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
+ "@material/linear-progress": "15.0.0-canary.7f224ddd4.0",
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/select": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/density": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-o9EXmGKVpiQ6mHhyV3oDDzc78Ow3E7v8dlaOhgaDSXgmqaE8v5sIlLNa/LKSyUga83/fpGk3QViSGXotpQx0jA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/dialog": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-u0XpTlv1JqWC/bQ3DavJ1JguofTelLT2wloj59l3/1b60jv42JQ6Am7jU3I8/SIUB1MKaW7dYocXjDWtWJakLA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/dom": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-mQ1HT186GPQSkRg5S18i70typ5ZytfjL09R0gJ2Qg5/G+MLCGi7TAjZZSH65tuD/QGOjel4rDdWOTmYbPYV6HA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/drawer": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-qyO0W0KBftfH8dlLR0gVAgv7ZHNvU8ae11Ao6zJif/YxcvK4+gph1z8AO4H410YmC2kZiwpSKyxM1iQCCzbb4g==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/elevation": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-tV6s4/pUBECedaI36Yj18KmRCk1vfue/JP/5yYRlFNnLMRVISePbZaKkn/BHXVf+26I3W879+XqIGlDVdmOoMA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/fab": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-4h76QrzfZTcPdd+awDPZ4Q0YdSqsXQnS540TPtyXUJ/5G99V6VwGpjMPIxAsW0y+pmI9UkLL/srrMaJec+7r4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/feature-targeting": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-SAjtxYh6YlKZriU83diDEQ7jNSP2MnxKsER0TvFeyG1vX/DWsUyYDOIJTOEa9K1N+fgJEBkNK8hY55QhQaspew==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/floating-label": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-0KMo5ijjYaEHPiZ2pCVIcbaTS2LycvH9zEhEMKwPPGssBCX7iz5ffYQFk7e5yrQand1r3jnQQgYfHAwtykArnQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/focus-ring": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-Jmg1nltq4J6S6A10EGMZnvufrvU3YTi+8R8ZD9lkSbun0Fm2TVdICQt/Auyi6An9zP66oQN6c31eqO6KfIPsDg==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0"
+ }
+ },
+ "node_modules/@material/form-field": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-fEPWgDQEPJ6WF7hNnIStxucHR9LE4DoDSMqCsGWS2Yu+NLZYLuCEecgR0UqQsl1EQdNRaFh8VH93KuxGd2hiPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/icon-button": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-DcK7IL4ICY/DW+48YQZZs9g0U1kRaW0Wb0BxhvppDMYziHo/CTpFdle4gjyuTyRxPOdHQz5a97ru48Z9O4muTw==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/image-list": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-voMjG2p80XbjL1B2lmF65zO5gEgJOVKClLdqh4wbYzYfwY/SR9c8eLvlYG7DLdFaFBl/7gGxD8TvvZ329HUFPw==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/layout-grid": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-veDABLxMn2RmvfnUO2RUmC1OFfWr4cU+MrxKPoDD2hl3l3eDYv5fxws6r5T1JoSyXoaN+oEZpheS0+M9Ure8Pg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/line-ripple": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-f60hVJhIU6I3/17Tqqzch1emUKEcfVVgHVqADbU14JD+oEIz429ZX9ksZ3VChoU3+eejFl+jVdZMLE/LrAuwpg==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/linear-progress": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-pRDEwPQielDiC9Sc5XhCXrGxP8wWOnAO8sQlMebfBYHYqy5hhiIzibezS8CSaW4MFQFyXmCmpmqWlbqGYRmiyg==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/list": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-Is0NV91sJlXF5pOebYAtWLF4wU2MJDbYqztML/zQNENkQxDOvEXu3nWNb3YScMIYJJXvARO0Liur5K4yPagS1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/menu": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-D11QU1dXqLbh5X1zKlEhS3QWh0b5BPNXlafc5MXfkdJHhOiieb7LC9hMJhbrHtj24FadJ7evaFW/T2ugJbJNnQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/menu-surface": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-7RZHvw0gbwppaAJ/Oh5SWmfAKJ62aw1IMB3+3MRwsb5PLoV666wInYa+zJfE4i7qBeOn904xqT2Nko5hY0ssrg==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/notched-outline": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-Yg2usuKB2DKlKIBISbie9BFsOVuffF71xjbxPbybvqemxqUBd+bD5/t6H1fLE+F8/NCu5JMigho4ewUU+0RCiw==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/progress-indicator": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-UPbDjE5CqT+SqTs0mNFG6uFEw7wBlgYmh+noSkQ6ty/EURm8lF125dmi4dv4kW0+octonMXqkGtAoZwLIHKf/w==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/radio": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-wR1X0Sr0KmQLu6+YOFKAI84G3L6psqd7Kys5kfb8WKBM36zxO5HQXC5nJm/Y0rdn22ixzsIz2GBo0MNU4V4k1A==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/ripple": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-JqOsWM1f4aGdotP0rh1vZlPZTg6lZgh39FIYHFMfOwfhR+LAikUJ+37ciqZuewgzXB6iiRO6a8aUH6HR5SJYPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/rtl": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-UVf14qAtmPiaaZjuJtmN36HETyoKWmsZM/qn1L5ciR2URb8O035dFWnz4ZWFMmAYBno/L7JiZaCkPurv2ZNrGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/segmented-button": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-LCnVRUSAhELTKI/9hSvyvIvQIpPpqF29BV+O9yM4WoNNmNWqTulvuiv7grHZl6Z+kJuxSg4BGbsPxxb9dXozPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/select": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-WioZtQEXRpglum0cMSzSqocnhsGRr+ZIhvKb3FlaNrTaK8H3Y4QA7rVjv3emRtrLOOjaT6/RiIaUMTo9AGzWQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/shape": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-8z8l1W3+cymObunJoRhwFPKZ+FyECfJ4MJykNiaZq7XJFZkV6xNmqAVrrbQj93FtLsECn9g4PjjIomguVn/OEw==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/slider": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-QU/WSaSWlLKQRqOhJrPgm29wqvvzRusMqwAcrCh1JTrCl+xwJ43q5WLDfjYhubeKtrEEgGu9tekkAiYfMG7EBw==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/snackbar": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-sm7EbVKddaXpT/aXAYBdPoN0k8yeg9+dprgBUkrdqGzWJAeCkxb4fv2B3He88YiCtvkTz2KLY4CThPQBSEsMFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/switch": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-lEDJfRvkVyyeHWIBfoxYjJVl+WlEAE2kZ/+6OqB1FW0OV8ftTODZGhHRSzjVBA1/p4FPuhAtKtoK9jTpa4AZjA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "safevalues": "^0.3.4",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/tab": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-E1xGACImyCLurhnizyOTCgOiVezce4HlBFAI6YhJo/AyVwjN2Dtas4ZLQMvvWWqpyhITNkeYdOchwCC1mrz3AQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/tab-bar": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-p1Asb2NzrcECvAQU3b2SYrpyJGyJLQWR+nXTYzDKE8WOpLIRCXap2audNqD7fvN/A20UJ1J8U01ptrvCkwJ4eA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/tab-indicator": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-h9Td3MPqbs33spcPS7ecByRHraYgU4tNCZpZzZXw31RypjKvISDv/PS5wcA4RmWqNGih78T7xg4QIGsZg4Pk4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/tab-scroller": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-LFeYNjQpdXecwECd8UaqHYbhscDCwhGln5Yh+3ctvcEgvmDPNjhKn/DL3sWprWvG8NAhP6sHMrsGhQFVdCWtTg==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/textfield": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-AExmFvgE5nNF0UA4l2cSzPghtxSUQeeoyRjFLHLy+oAaE4eKZFrSy0zEpqPeWPQpEMDZk+6Y+6T3cOFYBeSvsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/theme": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-hs45hJoE9yVnoVOcsN1jklyOa51U4lzWsEnQEuJTPOk2+0HqCQ0yv/q0InpSnm2i69fNSyZC60+8HADZGF8ugQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/tokens": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-r9TDoicmcT7FhUXC4eYMFnt9TZsz0G8T3wXvkKncLppYvZ517gPyD/1+yhuGfGOxAzxTrM66S/oEc1fFE2q4hw==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0"
+ }
+ },
+ "node_modules/@material/tooltip": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-8qNk3pmPLTnam3XYC1sZuplQXW9xLn4Z4MI3D+U17Q7pfNZfoOugGr+d2cLA9yWAEjVJYB0mj8Yu86+udo4N9w==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "safevalues": "^0.3.4",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/top-app-bar": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-SARR5/ClYT4CLe9qAXakbr0i0cMY0V3V4pe3ElIJPfL2Z2c4wGR1mTR8m2LxU1MfGKK8aRoUdtfKaxWejp+eNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/touch-target": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-BJo/wFKHPYLGsRaIpd7vsQwKr02LtO2e89Psv0on/p0OephlNIgeB9dD9W+bQmaeZsZ6liKSKRl6wJWDiK71PA==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@material/typography": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-kBaZeCGD50iq1DeRRH5OM5Jl7Gdk+/NOfKArkY4ksBZvJiStJ7ACAhpvb8MEGm4s3jvDInQFLsDq3hL+SA79sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
"node_modules/@ngtools/webpack": {
"version": "17.3.11",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.11.tgz",
@@ -6191,7 +7078,7 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=0.12"
},
@@ -10274,7 +11161,7 @@
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
"integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"entities": "^4.5.0"
},
@@ -11405,6 +12292,12 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
+ "node_modules/safevalues": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz",
+ "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==",
+ "license": "Apache-2.0"
+ },
"node_modules/sass": {
"version": "1.71.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
@@ -13484,6 +14377,12 @@
"node": ">=10.13.0"
}
},
+ "node_modules/wavesurfer.js": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.12.1.tgz",
+ "integrity": "sha512-NswPjVHxk0Q1F/VMRemCPUzSojjuHHisQrBqQiRXg7MVbe3f5vQ6r0rTTXA/a/neC/4hnOEC4YpXca4LpH0SUg==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/wbuf": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
@@ -14136,6 +15035,16 @@
"tslib": "^2.3.0"
}
},
+ "@angular/cdk": {
+ "version": "17.3.10",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.10.tgz",
+ "integrity": "sha512-b1qktT2c1TTTe5nTji/kFAVW92fULK0YhYAvJ+BjZTPKu2FniZNe8o4qqQ0pUuvtMu+ZQxp/QqFYoidIVCjScg==",
+ "peer": true,
+ "requires": {
+ "parse5": "^7.1.2",
+ "tslib": "^2.3.0"
+ }
+ },
"@angular/cli": {
"version": "17.3.11",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.11.tgz",
@@ -14249,6 +15158,61 @@
"tslib": "^2.3.0"
}
},
+ "@angular/material": {
+ "version": "17.3.10",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.10.tgz",
+ "integrity": "sha512-hHMQES0tQPH5JW33W+mpBPuM8ybsloDTqFPuRV8cboDjosAWfJhzAKF3ozICpNlUrs62La/2Wu/756GcQrxebg==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/auto-init": "15.0.0-canary.7f224ddd4.0",
+ "@material/banner": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
+ "@material/card": "15.0.0-canary.7f224ddd4.0",
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
+ "@material/chips": "15.0.0-canary.7f224ddd4.0",
+ "@material/circular-progress": "15.0.0-canary.7f224ddd4.0",
+ "@material/data-table": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dialog": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/drawer": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/fab": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
+ "@material/form-field": "15.0.0-canary.7f224ddd4.0",
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
+ "@material/image-list": "15.0.0-canary.7f224ddd4.0",
+ "@material/layout-grid": "15.0.0-canary.7f224ddd4.0",
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/linear-progress": "15.0.0-canary.7f224ddd4.0",
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
+ "@material/radio": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/segmented-button": "15.0.0-canary.7f224ddd4.0",
+ "@material/select": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/slider": "15.0.0-canary.7f224ddd4.0",
+ "@material/snackbar": "15.0.0-canary.7f224ddd4.0",
+ "@material/switch": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-bar": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0",
+ "@material/textfield": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tooltip": "15.0.0-canary.7f224ddd4.0",
+ "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.3.0"
+ }
+ },
"@angular/platform-browser": {
"version": "17.3.12",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz",
@@ -16010,6 +16974,758 @@
"call-bind": "^1.0.7"
}
},
+ "@material/animation": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-1GSJaPKef+7HRuV+HusVZHps64cmZuOItDbt40tjJVaikcaZvwmHlcTxRIqzcRoCdt5ZKHh3NoO7GB9Khg4Jnw==",
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/auto-init": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-t7ZGpRJ3ec0QDUO0nJu/SMgLW7qcuG2KqIsEYD1Ej8qhI2xpdR2ydSDQOkVEitXmKoGol1oq4nYSBjTlB65GqA==",
+ "requires": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/banner": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-g9wBUZzYBizyBcBQXTIafnRUUPi7efU9gPJfzeGgkynXiccP/vh5XMmH+PBxl5v+4MlP/d4cZ2NUYoAN7UTqSA==",
+ "requires": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/base": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-I9KQOKXpLfJkP8MqZyr8wZIzdPHrwPjFvGd9zSK91/vPyE4hzHRJc/0njsh9g8Lm9PRYLbifXX+719uTbHxx+A==",
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/button": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-BHB7iyHgRVH+JF16+iscR+Qaic+p7LU1FOLgP8KucRlpF9tTwIxQA6mJwGRi5gUtcG+vyCmzVS+hIQ6DqT/7BA==",
+ "requires": {
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/card": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-kt7y9/IWOtJTr3Z/AoWJT3ZLN7CLlzXhx2udCLP9ootZU2bfGK0lzNwmo80bv/pJfrY9ihQKCtuGTtNxUy+vIw==",
+ "requires": {
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/checkbox": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-rURcrL5O1u6hzWR+dNgiQ/n89vk6tdmdP3mZgnxJx61q4I/k1yijKqNJSLrkXH7Rto3bM5NRKMOlgvMvVd7UMQ==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/chips": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-AYAivV3GSk/T/nRIpH27sOHFPaSMrE3L0WYbnb5Wa93FgY8a0fbsFYtSH2QmtwnzXveg+B1zGTt7/xIIcynKdQ==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "safevalues": "^0.3.4",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/circular-progress": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-DJrqCKb+LuGtjNvKl8XigvyK02y36GRkfhMUYTcJEi3PrOE00bwXtyj7ilhzEVshQiXg6AHGWXtf5UqwNrx3Ow==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/data-table": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-/2WZsuBIq9z9RWYF5Jo6b7P6u0fwit+29/mN7rmAZ6akqUR54nXyNfoSNiyydMkzPlZZsep5KrSHododDhBZbA==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/checkbox": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
+ "@material/linear-progress": "15.0.0-canary.7f224ddd4.0",
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/select": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/density": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-o9EXmGKVpiQ6mHhyV3oDDzc78Ow3E7v8dlaOhgaDSXgmqaE8v5sIlLNa/LKSyUga83/fpGk3QViSGXotpQx0jA==",
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/dialog": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-u0XpTlv1JqWC/bQ3DavJ1JguofTelLT2wloj59l3/1b60jv42JQ6Am7jU3I8/SIUB1MKaW7dYocXjDWtWJakLA==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/dom": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-mQ1HT186GPQSkRg5S18i70typ5ZytfjL09R0gJ2Qg5/G+MLCGi7TAjZZSH65tuD/QGOjel4rDdWOTmYbPYV6HA==",
+ "requires": {
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/drawer": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-qyO0W0KBftfH8dlLR0gVAgv7ZHNvU8ae11Ao6zJif/YxcvK4+gph1z8AO4H410YmC2kZiwpSKyxM1iQCCzbb4g==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/elevation": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-tV6s4/pUBECedaI36Yj18KmRCk1vfue/JP/5yYRlFNnLMRVISePbZaKkn/BHXVf+26I3W879+XqIGlDVdmOoMA==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/fab": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-4h76QrzfZTcPdd+awDPZ4Q0YdSqsXQnS540TPtyXUJ/5G99V6VwGpjMPIxAsW0y+pmI9UkLL/srrMaJec+7r4Q==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/feature-targeting": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-SAjtxYh6YlKZriU83diDEQ7jNSP2MnxKsER0TvFeyG1vX/DWsUyYDOIJTOEa9K1N+fgJEBkNK8hY55QhQaspew==",
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/floating-label": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-0KMo5ijjYaEHPiZ2pCVIcbaTS2LycvH9zEhEMKwPPGssBCX7iz5ffYQFk7e5yrQand1r3jnQQgYfHAwtykArnQ==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/focus-ring": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-Jmg1nltq4J6S6A10EGMZnvufrvU3YTi+8R8ZD9lkSbun0Fm2TVdICQt/Auyi6An9zP66oQN6c31eqO6KfIPsDg==",
+ "requires": {
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0"
+ }
+ },
+ "@material/form-field": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-fEPWgDQEPJ6WF7hNnIStxucHR9LE4DoDSMqCsGWS2Yu+NLZYLuCEecgR0UqQsl1EQdNRaFh8VH93KuxGd2hiPg==",
+ "requires": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/icon-button": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-DcK7IL4ICY/DW+48YQZZs9g0U1kRaW0Wb0BxhvppDMYziHo/CTpFdle4gjyuTyRxPOdHQz5a97ru48Z9O4muTw==",
+ "requires": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/image-list": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-voMjG2p80XbjL1B2lmF65zO5gEgJOVKClLdqh4wbYzYfwY/SR9c8eLvlYG7DLdFaFBl/7gGxD8TvvZ329HUFPw==",
+ "requires": {
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/layout-grid": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-veDABLxMn2RmvfnUO2RUmC1OFfWr4cU+MrxKPoDD2hl3l3eDYv5fxws6r5T1JoSyXoaN+oEZpheS0+M9Ure8Pg==",
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/line-ripple": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-f60hVJhIU6I3/17Tqqzch1emUKEcfVVgHVqADbU14JD+oEIz429ZX9ksZ3VChoU3+eejFl+jVdZMLE/LrAuwpg==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/linear-progress": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-pRDEwPQielDiC9Sc5XhCXrGxP8wWOnAO8sQlMebfBYHYqy5hhiIzibezS8CSaW4MFQFyXmCmpmqWlbqGYRmiyg==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/list": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-Is0NV91sJlXF5pOebYAtWLF4wU2MJDbYqztML/zQNENkQxDOvEXu3nWNb3YScMIYJJXvARO0Liur5K4yPagS1Q==",
+ "requires": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/menu": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-D11QU1dXqLbh5X1zKlEhS3QWh0b5BPNXlafc5MXfkdJHhOiieb7LC9hMJhbrHtj24FadJ7evaFW/T2ugJbJNnQ==",
+ "requires": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/menu-surface": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-7RZHvw0gbwppaAJ/Oh5SWmfAKJ62aw1IMB3+3MRwsb5PLoV666wInYa+zJfE4i7qBeOn904xqT2Nko5hY0ssrg==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/notched-outline": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-Yg2usuKB2DKlKIBISbie9BFsOVuffF71xjbxPbybvqemxqUBd+bD5/t6H1fLE+F8/NCu5JMigho4ewUU+0RCiw==",
+ "requires": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/progress-indicator": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-UPbDjE5CqT+SqTs0mNFG6uFEw7wBlgYmh+noSkQ6ty/EURm8lF125dmi4dv4kW0+octonMXqkGtAoZwLIHKf/w==",
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/radio": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-wR1X0Sr0KmQLu6+YOFKAI84G3L6psqd7Kys5kfb8WKBM36zxO5HQXC5nJm/Y0rdn22ixzsIz2GBo0MNU4V4k1A==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/ripple": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-JqOsWM1f4aGdotP0rh1vZlPZTg6lZgh39FIYHFMfOwfhR+LAikUJ+37ciqZuewgzXB6iiRO6a8aUH6HR5SJYPg==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/rtl": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-UVf14qAtmPiaaZjuJtmN36HETyoKWmsZM/qn1L5ciR2URb8O035dFWnz4ZWFMmAYBno/L7JiZaCkPurv2ZNrGA==",
+ "requires": {
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/segmented-button": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-LCnVRUSAhELTKI/9hSvyvIvQIpPpqF29BV+O9yM4WoNNmNWqTulvuiv7grHZl6Z+kJuxSg4BGbsPxxb9dXozPg==",
+ "requires": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/touch-target": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/select": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-WioZtQEXRpglum0cMSzSqocnhsGRr+ZIhvKb3FlaNrTaK8H3Y4QA7rVjv3emRtrLOOjaT6/RiIaUMTo9AGzWQQ==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/list": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu": "15.0.0-canary.7f224ddd4.0",
+ "@material/menu-surface": "15.0.0-canary.7f224ddd4.0",
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/shape": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-8z8l1W3+cymObunJoRhwFPKZ+FyECfJ4MJykNiaZq7XJFZkV6xNmqAVrrbQj93FtLsECn9g4PjjIomguVn/OEw==",
+ "requires": {
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/slider": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-QU/WSaSWlLKQRqOhJrPgm29wqvvzRusMqwAcrCh1JTrCl+xwJ43q5WLDfjYhubeKtrEEgGu9tekkAiYfMG7EBw==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/snackbar": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-sm7EbVKddaXpT/aXAYBdPoN0k8yeg9+dprgBUkrdqGzWJAeCkxb4fv2B3He88YiCtvkTz2KLY4CThPQBSEsMFQ==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/icon-button": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/switch": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-lEDJfRvkVyyeHWIBfoxYjJVl+WlEAE2kZ/+6OqB1FW0OV8ftTODZGhHRSzjVBA1/p4FPuhAtKtoK9jTpa4AZjA==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "safevalues": "^0.3.4",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/tab": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-E1xGACImyCLurhnizyOTCgOiVezce4HlBFAI6YhJo/AyVwjN2Dtas4ZLQMvvWWqpyhITNkeYdOchwCC1mrz3AQ==",
+ "requires": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/focus-ring": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/tab-bar": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-p1Asb2NzrcECvAQU3b2SYrpyJGyJLQWR+nXTYzDKE8WOpLIRCXap2audNqD7fvN/A20UJ1J8U01ptrvCkwJ4eA==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/tab-indicator": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-h9Td3MPqbs33spcPS7ecByRHraYgU4tNCZpZzZXw31RypjKvISDv/PS5wcA4RmWqNGih78T7xg4QIGsZg4Pk4w==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/tab-scroller": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-LFeYNjQpdXecwECd8UaqHYbhscDCwhGln5Yh+3ctvcEgvmDPNjhKn/DL3sWprWvG8NAhP6sHMrsGhQFVdCWtTg==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/tab": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/textfield": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-AExmFvgE5nNF0UA4l2cSzPghtxSUQeeoyRjFLHLy+oAaE4eKZFrSy0zEpqPeWPQpEMDZk+6Y+6T3cOFYBeSvsw==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/density": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/floating-label": "15.0.0-canary.7f224ddd4.0",
+ "@material/line-ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/notched-outline": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/theme": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-hs45hJoE9yVnoVOcsN1jklyOa51U4lzWsEnQEuJTPOk2+0HqCQ0yv/q0InpSnm2i69fNSyZC60+8HADZGF8ugQ==",
+ "requires": {
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/tokens": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-r9TDoicmcT7FhUXC4eYMFnt9TZsz0G8T3wXvkKncLppYvZ517gPyD/1+yhuGfGOxAzxTrM66S/oEc1fFE2q4hw==",
+ "requires": {
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0"
+ }
+ },
+ "@material/tooltip": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-8qNk3pmPLTnam3XYC1sZuplQXW9xLn4Z4MI3D+U17Q7pfNZfoOugGr+d2cLA9yWAEjVJYB0mj8Yu86+udo4N9w==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/button": "15.0.0-canary.7f224ddd4.0",
+ "@material/dom": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/tokens": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "safevalues": "^0.3.4",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/top-app-bar": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-SARR5/ClYT4CLe9qAXakbr0i0cMY0V3V4pe3ElIJPfL2Z2c4wGR1mTR8m2LxU1MfGKK8aRoUdtfKaxWejp+eNA==",
+ "requires": {
+ "@material/animation": "15.0.0-canary.7f224ddd4.0",
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/elevation": "15.0.0-canary.7f224ddd4.0",
+ "@material/ripple": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/shape": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "@material/typography": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/touch-target": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-BJo/wFKHPYLGsRaIpd7vsQwKr02LtO2e89Psv0on/p0OephlNIgeB9dD9W+bQmaeZsZ6liKSKRl6wJWDiK71PA==",
+ "requires": {
+ "@material/base": "15.0.0-canary.7f224ddd4.0",
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/rtl": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "@material/typography": {
+ "version": "15.0.0-canary.7f224ddd4.0",
+ "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.7f224ddd4.0.tgz",
+ "integrity": "sha512-kBaZeCGD50iq1DeRRH5OM5Jl7Gdk+/NOfKArkY4ksBZvJiStJ7ACAhpvb8MEGm4s3jvDInQFLsDq3hL+SA79sQ==",
+ "requires": {
+ "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0",
+ "@material/theme": "15.0.0-canary.7f224ddd4.0",
+ "tslib": "^2.1.0"
+ }
+ },
"@ngtools/webpack": {
"version": "17.3.11",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.11.tgz",
@@ -18267,7 +19983,7 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true
+ "devOptional": true
},
"env-paths": {
"version": "2.2.1",
@@ -21309,7 +23025,7 @@
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
"integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
- "dev": true,
+ "devOptional": true,
"requires": {
"entities": "^4.5.0"
}
@@ -22076,6 +23792,11 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
+ "safevalues": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz",
+ "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw=="
+ },
"sass": {
"version": "1.71.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
@@ -23461,6 +25182,11 @@
"graceful-fs": "^4.1.2"
}
},
+ "wavesurfer.js": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.12.1.tgz",
+ "integrity": "sha512-NswPjVHxk0Q1F/VMRemCPUzSojjuHHisQrBqQiRXg7MVbe3f5vQ6r0rTTXA/a/neC/4hnOEC4YpXca4LpH0SUg=="
+ },
"wbuf": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 9c0b285e61de9cc53c1183f6404a33ea8973301f..a2e5263c405c8586f42b4dd1a86148a88d5d44fa 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -25,6 +25,9 @@ import { RootRedirectComponent } from './root-redirect/root-redirect.component';
import { HeaderComponent } from './shared/header/header.component';
import { SignInComponent } from './sign-in/sign-in.component';
import { PronunciationComponent } from './pronunciation/pronunciation.component';
+import { PronunciationVideoComponent } from './pronunciationvideo/pronunciationvideo.component';
+import { PronunciationRaggComponent } from './pronunciationragg/pronunciationragg.component';
+
import { FooterComponent } from './footer/footer.component';
// If you have AppButtonComponent, import it here as well
// import { AppButtonComponent } from './app-button/app-button.component';
@@ -37,6 +40,8 @@ import { FooterComponent } from './footer/footer.component';
AuthComponent,
RootRedirectComponent,
PronunciationComponent,
+ PronunciationVideoComponent,
+ PronunciationRaggComponent,
FooterComponent,
//GenerateQuestionsComponent,
//VocabularyBuilderComponent,
diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html
index e3413b695fd5bce875d60f1d29a7202de5e8a284..566d91b6798edc3cf8bdf114d4fdee3d1763d039 100644
--- a/src/app/home/home.component.html
+++ b/src/app/home/home.component.html
@@ -25,6 +25,16 @@
Pronunciation Trainer
+
+
+ Pronunciation Trainer Video
+
+
+
+
+ Pronunciation Trainer Ragg
+
+
Personality Improvement
Body Language Improvement
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts
index b563c72091b2e8f8e6f8d33adab76d3ae73dd469..8ac28ac2a59bdbeddef7ad77efc5fb8c1e6d61fd 100644
--- a/src/app/home/home.component.ts
+++ b/src/app/home/home.component.ts
@@ -5,6 +5,8 @@ import { Subscription } from 'rxjs';
import { BrandService } from '../shared/brand.service';
import { MatDialog } from '@angular/material/dialog';
import { PronunciationComponent } from '../pronunciation/pronunciation.component';
+import { PronunciationVideoComponent } from '../pronunciationvideo/pronunciationvideo.component';
+import { PronunciationRaggComponent } from '../pronunciationragg/pronunciationragg.component';
@Component({
selector: 'app-home',
@@ -170,4 +172,22 @@ export class HomeComponent implements AfterViewInit, OnInit, OnDestroy {
disableClose: true
});
}
+
+ openPronunciationVideo(): void {
+ const dialogRef = this.dialog.open(PronunciationVideoComponent, {
+ width: '90vw',
+ maxWidth: '95vw',
+ height: '85vh',
+ disableClose: true
+ });
+ }
+
+ openPronunciationRagg(): void {
+ const dialogRef = this.dialog.open(PronunciationRaggComponent, {
+ width: '90vw',
+ maxWidth: '95vw',
+ height: '85vh',
+ disableClose: true
+ });
+ }
}
diff --git a/src/app/pronunciationragg/pronunciationragg.component.css b/src/app/pronunciationragg/pronunciationragg.component.css
new file mode 100644
index 0000000000000000000000000000000000000000..11e9c4a566d51133a183ce2f346ec1033e90ca11
--- /dev/null
+++ b/src/app/pronunciationragg/pronunciationragg.component.css
@@ -0,0 +1,785 @@
+:host {
+ display: block;
+ /*font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;*/
+ font-family: Raleway, Roboto, "Helvetica Neue", sans-serif;
+}
+
+/* Page background */
+.pp-page {
+ height: 85vh;
+ /* background: #e9f7f6;*/
+ padding: 28px 24px 18px;
+ box-sizing: border-box;
+ border: 7px solid #3aaea8;
+ border-radius: 1vw;
+}
+
+/* Header */
+.pp-header {
+ text-align: center;
+ margin-bottom: 18px;
+}
+
+ .pp-header h1 {
+ margin: 0;
+ font-size: 42px;
+ font-weight: 800;
+ color: #3aaea8;
+ letter-spacing: 0.3px;
+ }
+
+.pp-sub {
+ margin-top: 6px;
+ color: #6b7f7e;
+ font-size: 15px;
+ position: relative;
+}
+
+.pp-tooltip {
+ margin-left: 8px;
+ display: inline-block;
+ background: #ffffff;
+ border: 1px solid #d8eeee;
+ color: #2f6f6b;
+ padding: 6px 10px;
+ border-radius: 14px;
+ font-size: 12px;
+ box-shadow: 0 6px 14px rgba(0,0,0,0.06);
+}
+
+/* Main 3 columns */
+.pp-main {
+ display: flex;
+ gap: 1vw;
+ align-items: start;
+ justify-content: space-around;
+}
+
+/* LEFT */
+.pp-left {
+ display: flex;
+ justify-content: center;
+}
+
+.word-card {
+ width: 22vw;
+ height: 34vw;
+ background: #e9f7f6;
+ border-radius: 18px;
+ padding: 22px 18px 26px;
+ text-align: center;
+ box-shadow: 0 12px 26px rgba(0, 0, 0, 0.08);
+ border: 3px dashed #3aaea8;
+ gap: 0.5vw;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.word-img-wrap {
+ width: 20vw;
+ height: 20vw;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+ .word-img-wrap img {
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ border-radius:1vw;
+ }
+
+.word-text {
+ font-size: 3vw;
+ font-weight: 800;
+ color: #1f2b2a;
+}
+
+.phonetic-pill {
+ color: #3aaea8;
+ font-size: 1.5vw;
+ font-weight: 600;
+}
+
+.audio-img {
+ width: 4.8vw;
+ cursor: pointer;
+
+
+}
+/* CENTER */
+.pp-center {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+ position: relative;
+}
+
+.teacher-frame {
+ /* width: 240px;
+ height: 210px;
+ border-radius: 18px;
+ padding: 12px;
+ background: #e7f4f3;
+ border: 2px dashed #b9dedb;
+ display: flex;
+ align-items: center;
+ justify-content: center;*/
+ width: 22vw;
+ height: 33vw;
+ background: #e9f7f6;
+ border-radius: 18px;
+ padding: 22px 18px 26px;
+ text-align: center;
+ box-shadow: 0 12px 26px rgba(0, 0, 0, 0.08);
+ border: 3px dashed #3aaea8;
+}
+
+ .teacher-frame img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border-radius: 12px;
+ background: #ffffff;
+ }
+
+/* Listen button */
+.listen-btn {
+ border: none;
+ background: #49b6ae;
+ color: #ffffff;
+ padding: 12px 18px;
+ border-radius: 12px;
+ font-weight: 700;
+ font-size: 16px;
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ cursor: pointer;
+ box-shadow: 0 8px 18px rgba(0,0,0,0.08);
+}
+
+ .listen-btn:active {
+ transform: scale(0.99);
+ }
+
+.listen-ico {
+ font-size: 18px;
+}
+
+/* Record circle */
+.rec-circle {
+ width: 92px;
+ height: 92px;
+ border-radius: 50%;
+ border: none;
+ background: #f07b48;
+ color: #ffffff;
+ cursor: pointer;
+ /* box-shadow: 0 12px 22px rgba(0,0,0,0.12);*/
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ /* transition: transform 0.08s ease, filter 0.2s ease;*/
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
+ transition: all 0.3s ease;
+}
+
+ .rec-circle:active {
+ transform: scale(0.98);
+ }
+
+ .rec-circle.recording {
+ filter: brightness(0.95);
+ animation: recPulse 1s infinite;
+ }
+
+@keyframes recPulse {
+ 0% {
+ box-shadow: 0 0 0 0 rgba(240,123,72,0.35);
+ }
+
+ 70% {
+ box-shadow: 0 0 0 18px rgba(240,123,72,0);
+ }
+
+ 100% {
+ box-shadow: 0 0 0 0 rgba(240,123,72,0);
+ }
+}
+
+.rec-inner {
+ text-align: center;
+ line-height: 1.1;
+}
+
+.mic {
+ font-size: 22px;
+ margin-bottom: 4px;
+}
+
+.rec-text {
+ font-size: 12px;
+ font-weight: 800;
+ letter-spacing: 0.6px;
+}
+
+/* RIGHT */
+.pp-right {
+
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ flex-direction: column;
+ gap: 18vw;
+}
+
+/* little connector dots */
+.connector {
+
+ display: flex;
+ flex-direction: column;
+ gap: 2vw;
+}
+
+ .connector span {
+ width: 34px;
+ height: 10px;
+ border-radius: 999px;
+ background: #98d8d4;
+ opacity: 0.6;
+ }
+
+/* feedback card */
+.feedback-card {
+ width: 15vw;
+ background: #9edfd9;
+ border-radius: 16px;
+ padding: 22px 22px 24px;
+ box-shadow: 0 12px 22px rgba(0,0,0,0.08);
+ height: 15vw;
+}
+
+.feedback-title {
+ font-size: 18px;
+ font-weight: 800;
+ color: #205f5a;
+ letter-spacing: 0.6px;
+ margin-bottom: 8px;
+}
+
+.feedback-body {
+ background: transparent;
+}
+
+.feedback-muted {
+ color: #2c6d68;
+ opacity: 0.8;
+ font-style: italic;
+ font-size: 14px;
+ margin-top: 8px;
+}
+
+/* Result UI inside feedback */
+.feedback-result {
+ margin-top: 8px;
+}
+
+.score-row {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+}
+
+.score-label {
+ font-size: 12px;
+ color: #1d514d;
+ font-weight: 700;
+}
+
+.score-value {
+ font-size: 28px;
+ font-weight: 900;
+ color: #103c39;
+}
+
+/* Speakometer */
+.meter {
+ margin-top: 10px;
+}
+
+.meter-track {
+ height: 10px;
+ background: rgba(255,255,255,0.45);
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.meter-fill {
+ height: 100%;
+ width: 0%;
+ background: #2b8f88;
+ transition: width 0.5s ease;
+}
+
+/* Stars */
+.stars {
+ margin-top: 10px;
+ font-size: 22px;
+ display: flex;
+ gap: 4px;
+}
+
+ .stars span {
+ color: rgba(255,255,255,0.55);
+ }
+
+ .stars span.active {
+ color: #ffcc4d;
+ animation: starPop 0.3s ease;
+ }
+
+@keyframes starPop {
+ 0% {
+ transform: scale(0.85);
+ }
+
+ 60% {
+ transform: scale(1.12);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+/* 3 lines feedback */
+.feedback-lines {
+ margin: 10px 0 0 18px;
+ color: #114744;
+ font-size: 13.5px;
+ font-weight: 600;
+}
+
+/* Bottom area */
+.pp-bottom {
+ max-width: 1200px;
+ margin: 22px auto 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+}
+
+/* Prev/Next line */
+/* Prev/Next line */
+.nav-row {
+ display: flex;
+ align-items: center;
+ gap: 3vw;
+}
+
+/* Increased arrow size and perfectly center it inside the circular button */
+.nav-btn {
+ width: 7vw;
+ height: 7vw;
+ border-radius: 50%;
+ border: none;
+ background: #dcefee;
+ color: #1b5551;
+ font-size: 7vw; /* larger arrow */
+ display: flex; /* center text horizontally & vertically */
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ line-height: 1; /* avoid font baseline shifts */
+ padding: 0; /* ensure perfect centering */
+ cursor: pointer;
+ font-weight: 700;
+ box-shadow: 0 8px 18px rgba(0,0,0,0.06);
+ transition: transform 0.08s ease;
+}
+
+ .nav-btn:active {
+ transform: scale(0.98);
+ }
+
+ .nav-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+.nav-center {
+ display: inline-flex;
+ align-items: baseline;
+ gap: 8px;
+}
+
+.nav-letter {
+ font-size: 5vw;
+ font-weight: 900;
+ color: #3aaea8;
+}
+
+.nav-count {
+ font-size: 14px;
+ color: #667b79;
+ font-weight: 600;
+}
+
+/* Responsive override so buttons don't overflow on smaller screens */
+@media (max-width: 980px) {
+ .nav-btn {
+ width: 56px;
+ height: 56px;
+ font-size: 28px;
+ }
+}
+
+/* Alphabet pills */
+.alpha-row {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 8px;
+ max-width: 900px;
+}
+
+.alpha-pill {
+ width: 34px;
+ height: 34px;
+ border-radius: 50%;
+ border: none;
+ background: #dfeeee;
+ color: #3a5a58;
+ font-weight: 700;
+ cursor: pointer;
+ font-size: 13px;
+}
+
+ .alpha-pill.active {
+ background: #49b6ae;
+ color: #ffffff;
+ box-shadow: 0 6px 14px rgba(0,0,0,0.08);
+ }
+
+/* Responsive */
+@media (max-width: 1100px) {
+ .pp-main {
+ grid-template-columns: 260px 320px 1fr;
+ }
+}
+
+@media (max-width: 980px) {
+ .pp-main {
+ grid-template-columns: 1fr;
+ }
+
+ .pp-left, .pp-center, .pp-right {
+ justify-content: center;
+ }
+
+ .connector {
+ display: none;
+ }
+
+ .feedback-card {
+ width: 100%;
+ max-width: 520px;
+ }
+}
+.gauge-wrapper {
+ position: relative;
+ width: 20vw;
+ height: 10vw;
+}
+
+.gauge {
+ position: absolute;
+ left: 50%;
+ top: 0;
+ transform: translateX(-50%);
+ width: 100%;
+ height: 100%;
+ border-radius: 260px 260px 0 0;
+ overflow: hidden;
+ background: #f3f3f3;
+ box-shadow: 0 4px 10px rgba(0,0,0,0.25) inset;
+}
+
+.gauge-arc {
+ position: absolute;
+ inset: 0;
+ border-radius: 50%;
+ background: conic-gradient( from 270deg, #e53935 0deg 45deg, #fb8c00 45deg 90deg, #fbc02d 90deg 135deg, #43a047 135deg 180deg, transparent 180deg 360deg );
+ height: 20vw;
+}
+
+.needle {
+ position: absolute;
+ bottom: 0vw;
+ left: 50%;
+ width: 0.7vw;
+ height: 8vw;
+ background: #333;
+ transform: translateX(-50%) rotate(var(--angle, -90deg));
+ transform-origin: 50% 100%;
+ transition: transform 700ms cubic-bezier(.2,.9,.2,1);
+ border-radius: 10px;
+ box-shadow: 0 2px 6px rgba(0,0,0,0.5);
+}
+
+.mic-badge {
+ position: absolute;
+ bottom: -0.3vw;
+ left: 50%;
+ transform: translate(-50%, 35%);
+ width: 3vw;
+ height: 3vw;
+ border-radius: 50%;
+ background: #000;
+ box-shadow: 0 8px 18px rgba(0,0,0,0.4);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.score-span {
+ color: white;
+ font-size: 1vw;
+ font-weight: bold;
+}
+.notepad{
+ display:flex;
+ align-items:center;
+}
+
+.user-guide-close-icon {
+ position: fixed;
+ top: 3vw;
+ right: 4vw;
+ background: #009688;
+ border: none;
+ width: 44px;
+ height: 44px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2vw;
+ color: black;
+ cursor: pointer;
+ z-index: 2010;
+ box-shadow: 0 2px 8px rgba(93, 145, 195, 0.18);
+ transition: background 0.2s, color 0.2s;
+}
+/* Teacher media container — ensures image and video occupy exactly the same box */
+.teacher-media {
+ width: 20vw; /* same as you used inline before */
+ max-width: 260px; /* optional limit for very wide screens */
+ min-width: 140px; /* optional floor */
+ aspect-ratio: 3 / 4; /* change to 16 / 9 if your videos are widescreen */
+ position: relative;
+ overflow: hidden;
+ display: block;
+}
+
+/* Shared styles for image and video so sizes match exactly */
+.teacher-media__img,
+.teacher-media__video {
+ width: 100%;
+ height: 100%;
+ display: block;
+ border-radius: 1vw;
+ border: 2px solid #ccc; /* keep previously visible border */
+ object-fit: cover; /* makes video/image fill box similarly */
+}
+
+/* A neutral background for video while it loads */
+.teacher-media__video {
+ background-color: #000;
+}
+/* Make play/pause image match record button size and add shadow */
+.listen-img {
+ width: 92px; /* match .rec-circle size */
+ height: 92px;
+ border-radius: 50%;
+ display: inline-block;
+ object-fit: contain; /* keep icon aspect */
+ cursor: pointer;
+ user-select: none;
+ margin-right: 1vw;
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
+ transition: all 0.3s ease;
+ /*box-shadow: 0 12px 22px rgba(0,0,0,0.12);
+ transition: transform 0.08s ease, filter 0.15s ease, box-shadow 0.15s ease;
+
+ box-sizing: border-box;*/
+ border: none;
+}
+
+ /* pressed / active */
+ .listen-img:active {
+ transform: scale(0.98);
+ }
+
+ /* playing state — subtle visual change */
+ .listen-img.playing,
+ .listen-img[aria-pressed="true"] {
+ filter: brightness(0.95);
+ box-shadow: 0 18px 30px rgba(0,0,0,0.16);
+ }
+
+ /* keyboard focus for accessibility */
+ .listen-img:focus {
+ outline: 3px solid rgba(58,174,168,0.18);
+ outline-offset: 3px;
+ }
+
+/* Small-screen fallback (keep sizes proportional) */
+@media (max-width: 980px) {
+ .listen-img {
+ width: 72px;
+ height: 72px;
+ padding: 14px;
+ }
+}
+/* add oscillation keyframes and state */
+@keyframes needleOscillate {
+ 0% {
+ transform: translateX(-50%) rotate(-70deg);
+ }
+
+ 25% {
+ transform: translateX(-50%) rotate(-20deg);
+ }
+
+ 50% {
+ transform: translateX(-50%) rotate(60deg);
+ }
+
+ 75% {
+ transform: translateX(-50%) rotate(-10deg);
+ }
+
+ 100% {
+ transform: translateX(-50%) rotate(-70deg);
+ }
+}
+
+/* existing needle default uses CSS variable for final angle */
+.needle {
+ position: absolute;
+ bottom: 0vw;
+ left: 50%;
+ width: 0.7vw;
+ height: 8vw;
+ background: #333;
+ transform: translateX(-50%) rotate(var(--angle, -90deg));
+ transform-origin: 50% 100%;
+ transition: transform 700ms cubic-bezier(.2,.9,.2,1);
+ border-radius: 10px;
+ box-shadow: 0 2px 6px rgba(0,0,0,0.5);
+}
+
+ /* while recording / waiting for score, run oscillation animation */
+ .needle.oscillate {
+ /* override transform with animation while oscillating */
+ animation: needleOscillate 1.2s ease-in-out infinite;
+ /* disable the smooth transition while animation runs to prevent conflicts */
+ transition: none;
+ }
+
+/* when oscillation ends (isRecording/isScoring false), the animation class is removed
+ and the element will smoothly transition to the value provided by --angle */
+.container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 40px; /* Increase the space between elements */
+}
+
+.arrow {
+ font-size: 4rem; /* Increased font size for bigger buttons */
+ background-color: #e0f7fa;
+ border: none;
+ width: 92px; /* Set width */
+ height: 92px; /* Set height */
+ border-radius: 50%; /* Make the button circular */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ box-shadow: 4px 4px 15px rgba(0, 0, 0, 0.2); /* Box shadow for the button */
+ color: #00796b; /* Set color of the arrows */
+ transition: background-color 0.3s, color 0.3s; /* Smooth transition for background and text color */
+}
+
+ /* Disabled state styles */
+ .arrow:disabled {
+ background-color: #cfd8dc; /* Light gray background when disabled */
+ color: #90a4ae; /* Gray color for the arrow */
+ cursor: not-allowed; /* Change cursor to indicate the button is disabled */
+ box-shadow: none; /* Remove box shadow for disabled button */
+ }
+
+.center-text {
+ font-size: 5rem; /* Increased font size for the 'Y' text */
+ font-weight: bold;
+ color: #00796b;
+ width: 4vw;
+ text-align: center;
+}
+
+
+/* Styling the container */
+.image-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+}
+
+/* Styling the round image with shadow */
+.round-image {
+ width: 4.8vw; /* Adjust the size as needed */
+ height: 4.8vw;
+ border-radius: 50%; /* Makes the image round */
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
+ transition: all 0.3s ease; /* Smooth transition for animation */
+ cursor: pointer;
+}
+
+ /* Scaling effect on click */
+ .round-image:active {
+ transform: scale(1.1); /* Scale up by 10% when clicked */
+ }
+
+/* Subtle zoom in/out */
+.apple-anim {
+ transform-origin: 50% 50%;
+ animation: appleZoom 2.8s ease-in-out infinite alternate;
+ will-change: transform;
+}
+
+@keyframes appleZoom {
+ from {
+ transform: scale(0.8);
+ }
+
+ to {
+ transform: scale(1.06);
+ }
+ /* slight zoom */
+}
+
+/* Respect reduced motion preference */
+@media (prefers-reduced-motion: reduce) {
+ .apple-anim {
+ animation: none;
+ transform: none;
+ }
+}
diff --git a/src/app/pronunciationragg/pronunciationragg.component.html b/src/app/pronunciationragg/pronunciationragg.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..392de84a76a937c452f4b0d79602122379332816
--- /dev/null
+++ b/src/app/pronunciationragg/pronunciationragg.component.html
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
{{ current.word }}
+
+
+ {{ current.phonetics }}
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ current.letter }}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/pronunciationragg/pronunciationragg.component.ts b/src/app/pronunciationragg/pronunciationragg.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..be4f34af12cdf66b51b81761a6ad23f9980c4ee3
--- /dev/null
+++ b/src/app/pronunciationragg/pronunciationragg.component.ts
@@ -0,0 +1,950 @@
+import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { finalize } from 'rxjs/operators';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { ChangeDetectorRef } from '@angular/core';
+
+interface PracticeItem {
+ letter: string;
+ word: string;
+ phonetics: string;
+ imgSrc: string;
+ audioSrc: string;
+}
+
+@Component({
+ selector: 'app-pronunciationragg',
+ templateUrl: './pronunciationragg.component.html',
+ styleUrls: ['./pronunciationragg.component.css']
+})
+export class PronunciationRaggComponent implements OnInit, OnDestroy {
+ @ViewChild('videoEl') videoElRef?: ElementRef;
+
+ // toggle & src for teacher video
+ showVideo = false;
+ videoSrc = '';
+
+ // track play/pause state for the toggle button
+ isPlayingVideo = false;
+
+ // use asset image paths (replace with your actual asset filenames)
+ playIconDataUrl = '';
+ pauseIconDataUrl = '';
+
+ // ---------------------------
+ // CONFIG
+ // ---------------------------
+ // Change this to your server domain
+
+ private API_BASE = location.hostname.endsWith('hf.space')
+ ? 'https://pykara-py-learn-backend.hf.space'
+ : 'http://localhost:5000';
+
+ private readonly SCORE_ENDPOINT = `${this.API_BASE}/pronragg/score`;
+
+ // Prefer a mime type supported by the browser
+ private readonly preferredMimeTypes = [
+ 'audio/webm;codecs=opus',
+ 'audio/webm',
+ 'audio/ogg;codecs=opus',
+ 'audio/ogg'
+ ];
+
+ // ---------------------------
+ // DATA
+ // ---------------------------
+ items: PracticeItem[] = [
+ {
+ letter: 'A',
+ word: 'Apple',
+ phonetics: '/ˈæpəl/',
+ imgSrc: 'assets/images/pron/letter-a.png',
+ audioSrc: 'assets/pronvideo/audio/apple.mp3'
+
+ },
+ {
+ letter: 'B',
+ word: 'Ball',
+ phonetics: '/bɔːl/',
+ imgSrc: 'assets/images/pron/letter-b.png',
+ audioSrc: 'assets/pronvideo/audio/ball.mp3'
+
+ },
+ {
+ letter: 'C',
+ word: 'Cat',
+ phonetics: '/kæt/',
+ imgSrc: 'assets/images/pron/letter-c.png',
+ audioSrc: 'assets/pronvideo/audio/cat.mp3'
+
+ },
+ {
+ letter: 'D',
+ word: 'Dog',
+ phonetics: '/dɒɡ/',
+ imgSrc: 'assets/images/pron/letter-d.png',
+ audioSrc: 'assets/pronvideo/audio/dog.mp3'
+
+ },
+ {
+ letter: 'E',
+ word: 'Egg',
+ phonetics: '/eɡ/',
+ imgSrc: 'assets/images/pron/letter-e.png',
+ audioSrc: 'assets/pronvideo/audio/egg.mp3'
+
+ },
+ {
+ letter: 'F',
+ word: 'Fish',
+ phonetics: '/fɪʃ/',
+ imgSrc: 'assets/images/pron/letter-f.png',
+ audioSrc: 'assets/pronvideo/audio/fish.mp3'
+
+ },
+ {
+ letter: 'G',
+ word: 'Grapes',
+ phonetics: '/ɡreɪps/',
+ imgSrc: 'assets/images/pron/letter-g.png',
+ audioSrc: 'assets/pronvideo/audio/grapes.mp3'
+
+ },
+ {
+ letter: 'H',
+ word: 'Hat',
+ phonetics: '/hæt/',
+ imgSrc: 'assets/images/pron/letter-h.png',
+ audioSrc: 'assets/pronvideo/audio/hat.mp3'
+
+ },
+ {
+ letter: 'I',
+ word: 'Ice cream',
+ phonetics: '/ˈaɪs ˌkriːm/',
+ imgSrc: 'assets/images/pron/letter-i.png',
+ audioSrc: 'assets/pronvideo/audio/icecream.mp3'
+
+ },
+ {
+ letter: 'J',
+ word: 'Jar',
+ phonetics: '/dʒɑːr/',
+ imgSrc: 'assets/images/pron/letter-j.png',
+ audioSrc: 'assets/pronvideo/audio/jar.mp3'
+
+ },
+ {
+ letter: 'K',
+ word: 'Kite',
+ phonetics: '/kaɪt/',
+ imgSrc: 'assets/images/pron/letter-k.png',
+ audioSrc: 'assets/pronvideo/audio/kite.mp3'
+
+ },
+ {
+ letter: 'L',
+ word: 'Lion',
+ phonetics: '/ˈlaɪən/',
+ imgSrc: 'assets/images/pron/letter-l.png',
+ audioSrc: 'assets/pronvideo/audio/lion.mp3'
+
+ },
+ {
+ letter: 'M',
+ word: 'Moon',
+ phonetics: '/muːn/',
+ imgSrc: 'assets/images/pron/letter-m.png',
+ audioSrc: 'assets/pronvideo/audio/moon.mp3'
+
+ },
+ {
+ letter: 'N',
+ word: 'Nest',
+ phonetics: '/nest/',
+ imgSrc: 'assets/images/pron/letter-n.png',
+ audioSrc: 'assets/pronvideo/audio/nest.mp3'
+
+ },
+ {
+ letter: 'O',
+ word: 'Orange',
+ phonetics: '/ˈɒrɪndʒ/',
+ imgSrc: 'assets/images/pron/letter-o.png',
+ audioSrc: 'assets/pronvideo/audio/orange.mp3'
+
+ },
+ {
+ letter: 'P',
+ word: 'Pig',
+ phonetics: '/pɪɡ/',
+ imgSrc: 'assets/images/pron/letter-p.png',
+ audioSrc: 'assets/pronvideo/audio/pig.mp3'
+
+ },
+ {
+ letter: 'Q',
+ word: 'Queen',
+ phonetics: '/kwiːn/',
+ imgSrc: 'assets/images/pron/letter-q.png',
+ audioSrc: 'assets/pronvideo/audio/queen.mp3'
+
+ },
+ {
+ letter: 'R',
+ word: 'Rabbit',
+ phonetics: '/ˈræbɪt/',
+ imgSrc: 'assets/images/pron/letter-r.png',
+ audioSrc: 'assets/pronvideo/audio/rabbit.mp3'
+
+ },
+ {
+ letter: 'S',
+ word: 'Sun',
+ phonetics: '/sʌn/',
+ imgSrc: 'assets/images/pron/letter-s.png',
+ audioSrc: 'assets/pronvideo/audio/sun.mp3'
+
+ },
+ {
+ letter: 'T',
+ word: 'Tree',
+ phonetics: '/triː/',
+ imgSrc: 'assets/images/pron/letter-t.png',
+ audioSrc: 'assets/pronvideo/audio/tree.mp3'
+
+ },
+ {
+ letter: 'U',
+ word: 'Umbrella',
+ phonetics: '/ʌmˈbrelə/',
+ imgSrc: 'assets/images/pron/letter-u.png',
+ audioSrc: 'assets/pronvideo/audio/umbrella.mp3'
+
+ },
+ {
+ letter: 'V',
+ word: 'Van',
+ phonetics: '/væn/',
+ imgSrc: 'assets/images/pron/letter-v.png',
+ audioSrc: 'assets/pronvideo/audio/van.mp3'
+
+ },
+ {
+ letter: 'W',
+ word: 'Watch',
+ phonetics: '/wɒtʃ/',
+ imgSrc: 'assets/images/pron/letter-w.png',
+ audioSrc: 'assets/pronvideo/audio/watch.mp3'
+
+ },
+ {
+ letter: 'X',
+ word: 'Xylophone',
+ phonetics: '/ˈzaɪləfəʊn/',
+ imgSrc: 'assets/images/pron/letter-x.png',
+ audioSrc: 'assets/pronvideo/audio/xylophone.mp3'
+
+ },
+ {
+ letter: 'Y',
+ word: 'Yarn',
+ phonetics: '/jɑːn/',
+ imgSrc: 'assets/images/pron/letter-y.png',
+ audioSrc: 'assets/pronvideo/audio/yarn.mp3'
+
+ },
+ {
+ letter: 'Z',
+ word: 'Zebra',
+ phonetics: '/ˈzebrə/',
+ imgSrc: 'assets/images/pron/letter-z.png',
+ audioSrc: 'assets/pronvideo/audio/zebra.mp3'
+
+ }
+ ];
+
+
+ index = 0;
+
+ get current(): PracticeItem {
+ return this.items[this.index];
+ }
+
+ // ---------------------------
+ // AUDIO (word)
+ // ---------------------------
+ private wordAudio = new Audio();
+
+ // ---------------------------
+ // RECORDING STATE
+ // ---------------------------
+ isRecording = false;
+ isScoring = false;
+
+ // NEW: flag to trigger needle oscillation after release and while waiting for score
+ isOscillating = false;
+
+ private mediaStream?: MediaStream;
+ private mediaRecorder?: MediaRecorder;
+ private chunks: BlobPart[] = [];
+ private currentMimeType = 'audio/webm';
+
+ recordedAudioUrl: string | null = null;
+ lastRecordedBlob: Blob | null = null;
+
+ // pending gesture play support (to satisfy autoplay policies)
+ private pendingVideoUrl: string | null = null;
+ private pendingGestureListener?: (e: Event) => void;
+
+ // Last created video blob URL (revoked when replaced) — DECLARED HERE
+ private lastVideoBlobUrl: string | null = null;
+
+ // ---------------------------
+ // RESULT UI
+ // ---------------------------
+ showResult = false;
+ score = 0;
+ stars = 0;
+ feedbackLines: string[] = [];
+ feedbackHint: string = '';
+ videoUrl: string = '';
+
+ // ---------------------------
+ // LIFECYCLE
+ // ---------------------------
+ constructor(private http: HttpClient, public dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: any, private cdr: ChangeDetectorRef) { }
+
+ ngOnInit(): void {
+ this.resetResult();
+ this.setupBestMimeType();
+
+ // Use images from assets instead of inline SVG data URIs.
+ // Ensure the files exist at these paths or update the paths to match your project.
+ this.playIconDataUrl = 'assets/pronvideo/play.png';
+ this.pauseIconDataUrl = 'assets/pronvideo/pause.png';
+ }
+
+ ngOnDestroy(): void {
+ this.cleanupRecordingUrl();
+ this.stopTracks();
+ this.safeStopRecorder();
+ this.wordAudio.pause();
+ this.wordAudio.src = '';
+ // ensure video cleaned up
+ this.resetVideo();
+ // revoke any created blob URL
+ if (this.lastVideoBlobUrl) {
+ try { URL.revokeObjectURL(this.lastVideoBlobUrl); } catch { }
+ this.lastVideoBlobUrl = null;
+ }
+ // remove any pending gesture listener to avoid leaks
+ if (this.pendingGestureListener) {
+ try { document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
+ this.pendingGestureListener = undefined;
+ this.pendingVideoUrl = null;
+ }
+ }
+
+ // ---------------------------
+ // RECORD & HOLD - EVENTS
+ // ---------------------------
+ async startRecording(evt?: Event): Promise {
+ evt?.preventDefault();
+
+ if (this.isRecording || this.isScoring) return;
+
+ this.resetResult();
+ this.cleanupRecordingUrl();
+ this.lastRecordedBlob = null;
+
+ try {
+ this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
+
+ const options: MediaRecorderOptions = {};
+ if (this.currentMimeType) {
+ options.mimeType = this.currentMimeType;
+ }
+
+ this.mediaRecorder = new MediaRecorder(this.mediaStream, options);
+
+ this.chunks = [];
+
+ this.mediaRecorder.ondataavailable = (e: BlobEvent) => {
+ if (e.data && e.data.size > 0) this.chunks.push(e.data);
+ };
+
+ this.mediaRecorder.onstop = () => {
+ this.onRecordingStopped();
+ };
+
+ this.mediaRecorder.start();
+ this.isRecording = true;
+
+ } catch {
+ this.isRecording = false;
+ this.stopTracks();
+ }
+ }
+
+ stopRecording(evt?: Event): void {
+ evt?.preventDefault();
+
+ if (!this.isRecording) return;
+
+ // End recording state
+ this.isRecording = false;
+
+ // Start oscillation immediately when user releases the record button:
+ // visual feedback that we are waiting for the backend scoring result.
+ this.isOscillating = true;
+
+ // Stop recorder safely
+ this.safeStopRecorder();
+
+ // Stop mic tracks
+ this.stopTracks();
+ }
+
+ private safeStopRecorder(): void {
+ try {
+ if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
+ this.mediaRecorder.stop();
+ }
+ } catch {
+ // ignore
+ }
+ }
+
+ private onRecordingStopped(): void {
+ try {
+ const blob = new Blob(this.chunks, { type: this.currentMimeType || 'audio/webm' });
+ this.lastRecordedBlob = blob;
+ this.recordedAudioUrl = URL.createObjectURL(blob);
+
+ // Send to backend for scoring
+ this.sendForScoring(blob, this.current.word);
+ } catch {
+ // If blob creation fails, stop oscillation and keep score hidden
+ this.isOscillating = false;
+ this.showResult = false;
+ } finally {
+ this.chunks = [];
+ }
+ }
+
+ // Optional: play student recording from UI button
+ playUserRecording(): void {
+ if (!this.recordedAudioUrl) return;
+ try {
+ const a = new Audio(this.recordedAudioUrl);
+ a.play().catch(() => { });
+ } catch {
+ // ignore
+ }
+ }
+
+ // ---------------------------
+ // BACKEND SCORING
+ // ---------------------------
+ private sendForScoring(blob: Blob, expectedWord: string): void {
+ if (!blob || !expectedWord) {
+ // nothing to do -> stop oscillation
+ this.isOscillating = false;
+ return;
+ }
+
+ const fd = new FormData();
+
+ // Give a reasonable filename extension
+ const ext = this.currentMimeType.includes('ogg') ? 'ogg' : 'webm';
+ fd.append('audio', blob, `student.${ext}`);
+ fd.append('word', expectedWord);
+
+ // Request inline base64 MP4 segment from the backend
+ fd.append('return_segment_base64', '1');
+
+ // show scoring state
+ this.isScoring = true;
+
+ this.http.post(this.SCORE_ENDPOINT, fd)
+ .pipe(finalize(() => {
+
+ // stop scoring state and oscillation once HTTP completes (success or error)
+ this.isScoring = false;
+ this.isOscillating = false;
+ this.cdr.detectChanges();
+ }))
+ .subscribe({
+ next: (res) => {
+ const s = this.normalizeScore(res?.score);
+ this.score = s;
+ this.feedbackHint = res.hint || '';
+ // prefer inline blob when returned
+ if (res?.videoBlobBase64) {
+ // revoke previous blob URL if present
+ if (this.lastVideoBlobUrl) {
+ try { URL.revokeObjectURL(this.lastVideoBlobUrl); } catch { }
+ this.lastVideoBlobUrl = null;
+ }
+ try {
+ const binary = atob(res.videoBlobBase64);
+ const len = binary.length;
+ const bytes = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binary.charCodeAt(i);
+ }
+ const blob = new Blob([bytes], { type: 'video/mp4' });
+ const url = URL.createObjectURL(blob);
+ this.lastVideoBlobUrl = url;
+ this.videoUrl = url;
+ } catch {
+ // fallback: use server-provided videoUrl if base64 decoding fails
+ this.videoUrl = res.videoUrl || '';
+ }
+ } else {
+ this.videoUrl = res.videoUrl || '';
+ }
+
+ this.stars = this.mapStars(s);
+
+ this.feedbackLines = this.buildFeedbackFromScore(s);
+
+ this.showResult = true;
+
+ // Try to play feedback video; if autoplay blocked we register a gesture fallback.
+ if (this.videoUrl) {
+ this.tryPlayFeedbackVideo(s, this.videoUrl);
+ }
+ },
+ error: () => {
+ this.score = 0;
+ this.stars = 1;
+ this.feedbackLines = this.buildFeedbackFromScore(0);
+ this.showResult = true;
+ this.cdr.detectChanges();
+ }
+ });
+ }
+
+ private normalizeScore(val: any): number {
+ const n = Number(val);
+ if (Number.isNaN(n)) return 0;
+ if (n < 0) return 0;
+ if (n > 100) return 100;
+ return Math.round(n);
+ }
+
+ // ---------------------------
+ // RESULT HELPERS (frontend-only)
+ // ---------------------------
+ private resetResult(): void {
+ this.showResult = false;
+ this.score = 0;
+ this.stars = 0;
+ this.feedbackLines = [];
+ this.isPlayingVideo = false;
+ this.isOscillating = false;
+ }
+
+ /**
+ * Reset only the speakometer/score UI without touching recording state or video playback flags.
+ */
+ private resetSpeakometer(): void {
+ this.score = 0;
+ this.stars = 0;
+ this.feedbackLines = [];
+ this.feedbackHint = '';
+ this.showResult = false;
+ this.isOscillating = false;
+ }
+
+ private mapStars(s: number): number {
+ if (s >= 90) return 5;
+ if (s >= 80) return 4;
+ if (s >= 70) return 3;
+ if (s >= 60) return 2;
+ return 1;
+ }
+
+ private buildFeedbackFromScore(s: number): string[] {
+ if (s >= 90) {
+ return [
+ 'Excellent pronunciation.',
+ 'Very clear vowel and ending sound.',
+ 'Keep the same speed.'
+ ];
+ }
+ if (s >= 80) {
+ return [
+ 'Very good attempt.',
+ 'Slightly improve the main vowel.',
+ 'Ending sound is almost perfect.'
+ ];
+ }
+ if (s >= 70) {
+ return [
+ 'Good try.',
+ 'Listen once more and repeat slowly.',
+ 'Focus on the first sound.'
+ ];
+ }
+ if (s >= 50) {
+ return [
+ 'Nice effort.',
+ 'Try a slower pronunciation.',
+ 'Record again after listening.'
+ ];
+ }
+ return [
+ 'Try again.',
+ 'Listen to the model carefully.',
+ 'Speak clearly and record once more.'
+ ];
+ }
+
+ // ---------------------------
+ // MIME TYPE SELECTION
+ // ---------------------------
+ private setupBestMimeType(): void {
+ if (!(window as any).MediaRecorder) {
+ this.currentMimeType = 'audio/webm';
+ return;
+ }
+
+ for (const t of this.preferredMimeTypes) {
+ try {
+ if ((window as any).MediaRecorder.isTypeSupported(t)) {
+ this.currentMimeType = t;
+ return;
+ }
+ } catch {
+ // ignore
+ }
+ }
+
+ this.currentMimeType = 'audio/webm';
+ }
+
+ // ---------------------------
+ // CLEANUP
+ // ---------------------------
+ private stopTracks(): void {
+ try {
+ this.mediaStream?.getTracks().forEach(t => t.stop());
+ } catch {
+ // ignore
+ } finally {
+ this.mediaStream = undefined;
+ }
+ }
+
+ private cleanupRecordingUrl(): void {
+ if (this.recordedAudioUrl) {
+ try {
+ URL.revokeObjectURL(this.recordedAudioUrl);
+ } catch {
+ // ignore
+ }
+ this.recordedAudioUrl = null;
+ }
+ }
+
+ // ---------------------------
+ // NAVIGATION
+ // ---------------------------
+ prev(): void {
+ if (this.index === 0) return;
+ this.index--;
+ this.resetExerciseState();
+ }
+
+ next(): void {
+ if (this.index >= this.items.length - 1) return;
+ this.index++;
+ this.resetExerciseState();
+ }
+
+ goTo(i: number): void {
+ if (i < 0 || i >= this.items.length) return;
+ this.index = i;
+ this.resetExerciseState();
+ }
+
+ private resetExerciseState(): void {
+ // reset UI result data
+ this.resetResult();
+ this.cleanupRecordingUrl();
+ this.lastRecordedBlob = null;
+
+ // stop any recording/scoring
+ this.isRecording = false;
+ this.isScoring = false;
+
+ // stop media tracks and recorder
+ this.stopTracks();
+ this.safeStopRecorder();
+
+ // STOP and CLEAR any playing video so next exercise starts clean
+ this.resetVideo();
+ }
+
+ // new helper: stop & clear video element + reset related flags
+ private resetVideo(): void {
+ try {
+ const v = this.videoElRef?.nativeElement;
+ if (v) {
+ try { v.pause(); } catch { /* ignore */ }
+ try { v.currentTime = 0; } catch { /* ignore */ }
+ try { v.removeAttribute('src'); } catch { /* ignore */ }
+ try { v.load(); } catch { /* ignore */ }
+ }
+ } catch {
+ // ignore any DOM errors
+ } finally {
+ // revoke any blob url we created
+ if (this.lastVideoBlobUrl) {
+ try { URL.revokeObjectURL(this.lastVideoBlobUrl); } catch { }
+ this.lastVideoBlobUrl = null;
+ }
+ this.showVideo = false;
+ this.videoSrc = '';
+ this.isPlayingVideo = false;
+ }
+ }
+
+ // ---------------------------
+ // WORD AUDIO
+ // ---------------------------
+ playWordAudio(): void {
+ // reset speakometer when starting model audio playback
+ this.resetSpeakometer();
+
+ const src = this.current.audioSrc || this.getAudioSrcFromWord(this.current.word);
+ if (!src) return;
+
+ try {
+ this.wordAudio.pause();
+ this.wordAudio.currentTime = 0;
+ this.wordAudio.src = src;
+ this.wordAudio.play().catch(() => { });
+ } catch {
+ // ignore
+ }
+ }
+
+ private getAudioSrcFromWord(word: string): string {
+ if (!word) return '';
+ const fileName = word.trim().toLowerCase().replace(/\s+/g, '-');
+ return `assets/pronvideo/audio/${fileName}.mp3`;
+ }
+
+ get needleAngle(): number {
+ const val = Math.max(0, Math.min(100, Number(this.score ?? 0)));
+ return -90 + (val * 1.8);
+ }
+
+ closePopup(): void {
+ this.dialogRef.close();
+ }
+
+ // ---------------------------
+ // TEACHER VIDEO
+ // ---------------------------
+ playWordVideo(): void {
+ // Reset speakometer when user requests the teacher video
+ this.resetSpeakometer();
+
+ // Build video path based on current.word (assets/videos/.mp4)
+ const src = this.getVideoSrcFromWord(this.current.word);
+ if (!src) return;
+
+ this.showVideo = true;
+ this.videoSrc = src;
+
+ // Wait for template to render, then set and play video safely
+ setTimeout(() => {
+ try {
+ const v = this.videoElRef?.nativeElement;
+ if (v) {
+ // sync UI state to element events and reset speakometer when native play occurs
+ v.addEventListener('play', () => {
+ this.isPlayingVideo = true;
+ // keep resetting speakometer on native play to ensure gauge clears
+ this.resetSpeakometer();
+ });
+ v.addEventListener('pause', () => this.isPlayingVideo = false);
+
+ v.pause();
+ v.src = this.videoSrc;
+ v.load();
+ v.play().then(() => {
+ this.isPlayingVideo = true;
+ }).catch(() => {
+ // ignore autoplay errors; UI still shows the control
+ this.isPlayingVideo = !v.paused;
+ });
+ }
+ } catch {
+ // ignore
+ }
+ }, 0);
+ }
+
+ /**
+ * Toggles play/pause for the currently-displayed video.
+ */
+ toggleVideoPlay(): void {
+ try {
+ if (!this.showVideo) {
+ this.playWordVideo();
+ return;
+ }
+
+ const v = this.videoElRef?.nativeElement;
+ if (!v) {
+ this.videoSrc = this.getVideoSrcFromWord(this.current.word);
+ this.showVideo = true;
+ return;
+ }
+
+ if (v.paused) {
+ // user is starting playback -> reset speakometer
+ this.resetSpeakometer();
+
+ v.play().then(() => {
+ this.isPlayingVideo = true;
+ }).catch(() => {
+ this.isPlayingVideo = !v.paused;
+ });
+ } else {
+ v.pause();
+ this.isPlayingVideo = false;
+ }
+ } catch {
+ // ignore
+ }
+ }
+
+ /**
+ * Attempts to play a feedback video returned by the backend in the same video element.
+ * If autoplay is blocked we register a one-time body click handler so the first user gesture starts playback.
+ */
+ private tryPlayFeedbackVideo(score: number, videoUrl: string): void {
+ if (!videoUrl) return;
+
+ // ensure UI shows the video element
+ this.showVideo = true;
+ this.videoSrc = videoUrl;
+
+ setTimeout(() => {
+ try {
+ const v = this.videoElRef?.nativeElement;
+ if (!v) {
+ // element not yet available -> register gesture fallback
+ this.registerGestureForPendingVideo(videoUrl);
+ return;
+ }
+
+ try { v.pause(); } catch { /* ignore */ }
+
+ v.src = this.videoSrc;
+ v.load();
+ v.play().then(() => {
+ this.isPlayingVideo = true;
+ }).catch(() => {
+ // autoplay likely blocked -> register gesture fallback
+ this.isPlayingVideo = !v.paused;
+ this.registerGestureForPendingVideo(videoUrl);
+ });
+ } catch {
+ // ignore
+ }
+ }, 0);
+ }
+
+ /**
+ * Register a one-time body click handler to play a pending feedback video when user interacts.
+ * This satisfies browser autoplay policies (requires a user gesture).
+ */
+ private registerGestureForPendingVideo(videoUrl: string): void {
+ // if already registered, update pending URL and exit
+ this.pendingVideoUrl = videoUrl;
+
+ if (this.pendingGestureListener) return;
+
+ this.pendingGestureListener = (e: Event) => {
+ try {
+ // show video and set src so element renders if it wasn't already
+ this.showVideo = true;
+ this.videoSrc = this.pendingVideoUrl || videoUrl;
+
+ // attempt play on the element after change detection
+ setTimeout(() => {
+ try {
+ const v = this.videoElRef?.nativeElement;
+ if (v) {
+ v.src = this.videoSrc;
+ v.load();
+ v.play().then(() => {
+ this.isPlayingVideo = true;
+ }).catch(() => {
+ this.isPlayingVideo = !v.paused;
+ });
+ }
+ } catch {
+ // ignore
+ }
+ }, 0);
+ } finally {
+ // cleanup listener and pending state
+ try { if (this.pendingGestureListener) document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
+ this.pendingGestureListener = undefined;
+ this.pendingVideoUrl = null;
+ // ensure gauge is not oscillating after user gesture
+ this.isOscillating = false;
+ }
+ };
+
+ // use capture so early gestures are caught even if other handlers stop propagation
+ try { document.body.addEventListener('click', this.pendingGestureListener, true); } catch { /* ignore */ }
+ }
+
+ onVideoEnded(): void {
+ // Ensure the native element is stopped and cleared so the thumbnail can re-appear.
+ try {
+ const v = this.videoElRef?.nativeElement;
+ if (v) {
+ try { v.pause(); } catch { }
+ try { v.currentTime = 0; } catch { }
+ try { v.src = ''; } catch { }
+ try { v.load(); } catch { }
+ }
+ } catch {
+ // ignore DOM errors
+ } finally {
+ // show the thumbnail again and reset video state
+ this.showVideo = false;
+ this.isPlayingVideo = false;
+ this.videoSrc = '';
+
+ // stop any lingering oscillation and pending gesture state
+ this.isOscillating = false;
+ if (this.pendingGestureListener) {
+ try { document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
+ this.pendingGestureListener = undefined;
+ this.pendingVideoUrl = null;
+ }
+
+ // ensure template updates immediately
+ try { this.cdr.detectChanges(); } catch { }
+ }
+ }
+
+ private getVideoSrcFromWord(word: string): string {
+ if (!word) return '';
+ const fileName = word.trim().toLowerCase().replace(/\s+/g, '-');
+ return `assets/pronvideo/videos/${fileName}.mp4`;
+ }
+}
diff --git a/src/app/pronunciationvideo/pronunciationvideo.component.css b/src/app/pronunciationvideo/pronunciationvideo.component.css
new file mode 100644
index 0000000000000000000000000000000000000000..11e9c4a566d51133a183ce2f346ec1033e90ca11
--- /dev/null
+++ b/src/app/pronunciationvideo/pronunciationvideo.component.css
@@ -0,0 +1,785 @@
+:host {
+ display: block;
+ /*font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;*/
+ font-family: Raleway, Roboto, "Helvetica Neue", sans-serif;
+}
+
+/* Page background */
+.pp-page {
+ height: 85vh;
+ /* background: #e9f7f6;*/
+ padding: 28px 24px 18px;
+ box-sizing: border-box;
+ border: 7px solid #3aaea8;
+ border-radius: 1vw;
+}
+
+/* Header */
+.pp-header {
+ text-align: center;
+ margin-bottom: 18px;
+}
+
+ .pp-header h1 {
+ margin: 0;
+ font-size: 42px;
+ font-weight: 800;
+ color: #3aaea8;
+ letter-spacing: 0.3px;
+ }
+
+.pp-sub {
+ margin-top: 6px;
+ color: #6b7f7e;
+ font-size: 15px;
+ position: relative;
+}
+
+.pp-tooltip {
+ margin-left: 8px;
+ display: inline-block;
+ background: #ffffff;
+ border: 1px solid #d8eeee;
+ color: #2f6f6b;
+ padding: 6px 10px;
+ border-radius: 14px;
+ font-size: 12px;
+ box-shadow: 0 6px 14px rgba(0,0,0,0.06);
+}
+
+/* Main 3 columns */
+.pp-main {
+ display: flex;
+ gap: 1vw;
+ align-items: start;
+ justify-content: space-around;
+}
+
+/* LEFT */
+.pp-left {
+ display: flex;
+ justify-content: center;
+}
+
+.word-card {
+ width: 22vw;
+ height: 34vw;
+ background: #e9f7f6;
+ border-radius: 18px;
+ padding: 22px 18px 26px;
+ text-align: center;
+ box-shadow: 0 12px 26px rgba(0, 0, 0, 0.08);
+ border: 3px dashed #3aaea8;
+ gap: 0.5vw;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.word-img-wrap {
+ width: 20vw;
+ height: 20vw;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+ .word-img-wrap img {
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ border-radius:1vw;
+ }
+
+.word-text {
+ font-size: 3vw;
+ font-weight: 800;
+ color: #1f2b2a;
+}
+
+.phonetic-pill {
+ color: #3aaea8;
+ font-size: 1.5vw;
+ font-weight: 600;
+}
+
+.audio-img {
+ width: 4.8vw;
+ cursor: pointer;
+
+
+}
+/* CENTER */
+.pp-center {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+ position: relative;
+}
+
+.teacher-frame {
+ /* width: 240px;
+ height: 210px;
+ border-radius: 18px;
+ padding: 12px;
+ background: #e7f4f3;
+ border: 2px dashed #b9dedb;
+ display: flex;
+ align-items: center;
+ justify-content: center;*/
+ width: 22vw;
+ height: 33vw;
+ background: #e9f7f6;
+ border-radius: 18px;
+ padding: 22px 18px 26px;
+ text-align: center;
+ box-shadow: 0 12px 26px rgba(0, 0, 0, 0.08);
+ border: 3px dashed #3aaea8;
+}
+
+ .teacher-frame img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border-radius: 12px;
+ background: #ffffff;
+ }
+
+/* Listen button */
+.listen-btn {
+ border: none;
+ background: #49b6ae;
+ color: #ffffff;
+ padding: 12px 18px;
+ border-radius: 12px;
+ font-weight: 700;
+ font-size: 16px;
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ cursor: pointer;
+ box-shadow: 0 8px 18px rgba(0,0,0,0.08);
+}
+
+ .listen-btn:active {
+ transform: scale(0.99);
+ }
+
+.listen-ico {
+ font-size: 18px;
+}
+
+/* Record circle */
+.rec-circle {
+ width: 92px;
+ height: 92px;
+ border-radius: 50%;
+ border: none;
+ background: #f07b48;
+ color: #ffffff;
+ cursor: pointer;
+ /* box-shadow: 0 12px 22px rgba(0,0,0,0.12);*/
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ /* transition: transform 0.08s ease, filter 0.2s ease;*/
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
+ transition: all 0.3s ease;
+}
+
+ .rec-circle:active {
+ transform: scale(0.98);
+ }
+
+ .rec-circle.recording {
+ filter: brightness(0.95);
+ animation: recPulse 1s infinite;
+ }
+
+@keyframes recPulse {
+ 0% {
+ box-shadow: 0 0 0 0 rgba(240,123,72,0.35);
+ }
+
+ 70% {
+ box-shadow: 0 0 0 18px rgba(240,123,72,0);
+ }
+
+ 100% {
+ box-shadow: 0 0 0 0 rgba(240,123,72,0);
+ }
+}
+
+.rec-inner {
+ text-align: center;
+ line-height: 1.1;
+}
+
+.mic {
+ font-size: 22px;
+ margin-bottom: 4px;
+}
+
+.rec-text {
+ font-size: 12px;
+ font-weight: 800;
+ letter-spacing: 0.6px;
+}
+
+/* RIGHT */
+.pp-right {
+
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ flex-direction: column;
+ gap: 18vw;
+}
+
+/* little connector dots */
+.connector {
+
+ display: flex;
+ flex-direction: column;
+ gap: 2vw;
+}
+
+ .connector span {
+ width: 34px;
+ height: 10px;
+ border-radius: 999px;
+ background: #98d8d4;
+ opacity: 0.6;
+ }
+
+/* feedback card */
+.feedback-card {
+ width: 15vw;
+ background: #9edfd9;
+ border-radius: 16px;
+ padding: 22px 22px 24px;
+ box-shadow: 0 12px 22px rgba(0,0,0,0.08);
+ height: 15vw;
+}
+
+.feedback-title {
+ font-size: 18px;
+ font-weight: 800;
+ color: #205f5a;
+ letter-spacing: 0.6px;
+ margin-bottom: 8px;
+}
+
+.feedback-body {
+ background: transparent;
+}
+
+.feedback-muted {
+ color: #2c6d68;
+ opacity: 0.8;
+ font-style: italic;
+ font-size: 14px;
+ margin-top: 8px;
+}
+
+/* Result UI inside feedback */
+.feedback-result {
+ margin-top: 8px;
+}
+
+.score-row {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+}
+
+.score-label {
+ font-size: 12px;
+ color: #1d514d;
+ font-weight: 700;
+}
+
+.score-value {
+ font-size: 28px;
+ font-weight: 900;
+ color: #103c39;
+}
+
+/* Speakometer */
+.meter {
+ margin-top: 10px;
+}
+
+.meter-track {
+ height: 10px;
+ background: rgba(255,255,255,0.45);
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.meter-fill {
+ height: 100%;
+ width: 0%;
+ background: #2b8f88;
+ transition: width 0.5s ease;
+}
+
+/* Stars */
+.stars {
+ margin-top: 10px;
+ font-size: 22px;
+ display: flex;
+ gap: 4px;
+}
+
+ .stars span {
+ color: rgba(255,255,255,0.55);
+ }
+
+ .stars span.active {
+ color: #ffcc4d;
+ animation: starPop 0.3s ease;
+ }
+
+@keyframes starPop {
+ 0% {
+ transform: scale(0.85);
+ }
+
+ 60% {
+ transform: scale(1.12);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+/* 3 lines feedback */
+.feedback-lines {
+ margin: 10px 0 0 18px;
+ color: #114744;
+ font-size: 13.5px;
+ font-weight: 600;
+}
+
+/* Bottom area */
+.pp-bottom {
+ max-width: 1200px;
+ margin: 22px auto 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+}
+
+/* Prev/Next line */
+/* Prev/Next line */
+.nav-row {
+ display: flex;
+ align-items: center;
+ gap: 3vw;
+}
+
+/* Increased arrow size and perfectly center it inside the circular button */
+.nav-btn {
+ width: 7vw;
+ height: 7vw;
+ border-radius: 50%;
+ border: none;
+ background: #dcefee;
+ color: #1b5551;
+ font-size: 7vw; /* larger arrow */
+ display: flex; /* center text horizontally & vertically */
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ line-height: 1; /* avoid font baseline shifts */
+ padding: 0; /* ensure perfect centering */
+ cursor: pointer;
+ font-weight: 700;
+ box-shadow: 0 8px 18px rgba(0,0,0,0.06);
+ transition: transform 0.08s ease;
+}
+
+ .nav-btn:active {
+ transform: scale(0.98);
+ }
+
+ .nav-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+.nav-center {
+ display: inline-flex;
+ align-items: baseline;
+ gap: 8px;
+}
+
+.nav-letter {
+ font-size: 5vw;
+ font-weight: 900;
+ color: #3aaea8;
+}
+
+.nav-count {
+ font-size: 14px;
+ color: #667b79;
+ font-weight: 600;
+}
+
+/* Responsive override so buttons don't overflow on smaller screens */
+@media (max-width: 980px) {
+ .nav-btn {
+ width: 56px;
+ height: 56px;
+ font-size: 28px;
+ }
+}
+
+/* Alphabet pills */
+.alpha-row {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 8px;
+ max-width: 900px;
+}
+
+.alpha-pill {
+ width: 34px;
+ height: 34px;
+ border-radius: 50%;
+ border: none;
+ background: #dfeeee;
+ color: #3a5a58;
+ font-weight: 700;
+ cursor: pointer;
+ font-size: 13px;
+}
+
+ .alpha-pill.active {
+ background: #49b6ae;
+ color: #ffffff;
+ box-shadow: 0 6px 14px rgba(0,0,0,0.08);
+ }
+
+/* Responsive */
+@media (max-width: 1100px) {
+ .pp-main {
+ grid-template-columns: 260px 320px 1fr;
+ }
+}
+
+@media (max-width: 980px) {
+ .pp-main {
+ grid-template-columns: 1fr;
+ }
+
+ .pp-left, .pp-center, .pp-right {
+ justify-content: center;
+ }
+
+ .connector {
+ display: none;
+ }
+
+ .feedback-card {
+ width: 100%;
+ max-width: 520px;
+ }
+}
+.gauge-wrapper {
+ position: relative;
+ width: 20vw;
+ height: 10vw;
+}
+
+.gauge {
+ position: absolute;
+ left: 50%;
+ top: 0;
+ transform: translateX(-50%);
+ width: 100%;
+ height: 100%;
+ border-radius: 260px 260px 0 0;
+ overflow: hidden;
+ background: #f3f3f3;
+ box-shadow: 0 4px 10px rgba(0,0,0,0.25) inset;
+}
+
+.gauge-arc {
+ position: absolute;
+ inset: 0;
+ border-radius: 50%;
+ background: conic-gradient( from 270deg, #e53935 0deg 45deg, #fb8c00 45deg 90deg, #fbc02d 90deg 135deg, #43a047 135deg 180deg, transparent 180deg 360deg );
+ height: 20vw;
+}
+
+.needle {
+ position: absolute;
+ bottom: 0vw;
+ left: 50%;
+ width: 0.7vw;
+ height: 8vw;
+ background: #333;
+ transform: translateX(-50%) rotate(var(--angle, -90deg));
+ transform-origin: 50% 100%;
+ transition: transform 700ms cubic-bezier(.2,.9,.2,1);
+ border-radius: 10px;
+ box-shadow: 0 2px 6px rgba(0,0,0,0.5);
+}
+
+.mic-badge {
+ position: absolute;
+ bottom: -0.3vw;
+ left: 50%;
+ transform: translate(-50%, 35%);
+ width: 3vw;
+ height: 3vw;
+ border-radius: 50%;
+ background: #000;
+ box-shadow: 0 8px 18px rgba(0,0,0,0.4);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.score-span {
+ color: white;
+ font-size: 1vw;
+ font-weight: bold;
+}
+.notepad{
+ display:flex;
+ align-items:center;
+}
+
+.user-guide-close-icon {
+ position: fixed;
+ top: 3vw;
+ right: 4vw;
+ background: #009688;
+ border: none;
+ width: 44px;
+ height: 44px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2vw;
+ color: black;
+ cursor: pointer;
+ z-index: 2010;
+ box-shadow: 0 2px 8px rgba(93, 145, 195, 0.18);
+ transition: background 0.2s, color 0.2s;
+}
+/* Teacher media container — ensures image and video occupy exactly the same box */
+.teacher-media {
+ width: 20vw; /* same as you used inline before */
+ max-width: 260px; /* optional limit for very wide screens */
+ min-width: 140px; /* optional floor */
+ aspect-ratio: 3 / 4; /* change to 16 / 9 if your videos are widescreen */
+ position: relative;
+ overflow: hidden;
+ display: block;
+}
+
+/* Shared styles for image and video so sizes match exactly */
+.teacher-media__img,
+.teacher-media__video {
+ width: 100%;
+ height: 100%;
+ display: block;
+ border-radius: 1vw;
+ border: 2px solid #ccc; /* keep previously visible border */
+ object-fit: cover; /* makes video/image fill box similarly */
+}
+
+/* A neutral background for video while it loads */
+.teacher-media__video {
+ background-color: #000;
+}
+/* Make play/pause image match record button size and add shadow */
+.listen-img {
+ width: 92px; /* match .rec-circle size */
+ height: 92px;
+ border-radius: 50%;
+ display: inline-block;
+ object-fit: contain; /* keep icon aspect */
+ cursor: pointer;
+ user-select: none;
+ margin-right: 1vw;
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
+ transition: all 0.3s ease;
+ /*box-shadow: 0 12px 22px rgba(0,0,0,0.12);
+ transition: transform 0.08s ease, filter 0.15s ease, box-shadow 0.15s ease;
+
+ box-sizing: border-box;*/
+ border: none;
+}
+
+ /* pressed / active */
+ .listen-img:active {
+ transform: scale(0.98);
+ }
+
+ /* playing state — subtle visual change */
+ .listen-img.playing,
+ .listen-img[aria-pressed="true"] {
+ filter: brightness(0.95);
+ box-shadow: 0 18px 30px rgba(0,0,0,0.16);
+ }
+
+ /* keyboard focus for accessibility */
+ .listen-img:focus {
+ outline: 3px solid rgba(58,174,168,0.18);
+ outline-offset: 3px;
+ }
+
+/* Small-screen fallback (keep sizes proportional) */
+@media (max-width: 980px) {
+ .listen-img {
+ width: 72px;
+ height: 72px;
+ padding: 14px;
+ }
+}
+/* add oscillation keyframes and state */
+@keyframes needleOscillate {
+ 0% {
+ transform: translateX(-50%) rotate(-70deg);
+ }
+
+ 25% {
+ transform: translateX(-50%) rotate(-20deg);
+ }
+
+ 50% {
+ transform: translateX(-50%) rotate(60deg);
+ }
+
+ 75% {
+ transform: translateX(-50%) rotate(-10deg);
+ }
+
+ 100% {
+ transform: translateX(-50%) rotate(-70deg);
+ }
+}
+
+/* existing needle default uses CSS variable for final angle */
+.needle {
+ position: absolute;
+ bottom: 0vw;
+ left: 50%;
+ width: 0.7vw;
+ height: 8vw;
+ background: #333;
+ transform: translateX(-50%) rotate(var(--angle, -90deg));
+ transform-origin: 50% 100%;
+ transition: transform 700ms cubic-bezier(.2,.9,.2,1);
+ border-radius: 10px;
+ box-shadow: 0 2px 6px rgba(0,0,0,0.5);
+}
+
+ /* while recording / waiting for score, run oscillation animation */
+ .needle.oscillate {
+ /* override transform with animation while oscillating */
+ animation: needleOscillate 1.2s ease-in-out infinite;
+ /* disable the smooth transition while animation runs to prevent conflicts */
+ transition: none;
+ }
+
+/* when oscillation ends (isRecording/isScoring false), the animation class is removed
+ and the element will smoothly transition to the value provided by --angle */
+.container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 40px; /* Increase the space between elements */
+}
+
+.arrow {
+ font-size: 4rem; /* Increased font size for bigger buttons */
+ background-color: #e0f7fa;
+ border: none;
+ width: 92px; /* Set width */
+ height: 92px; /* Set height */
+ border-radius: 50%; /* Make the button circular */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ box-shadow: 4px 4px 15px rgba(0, 0, 0, 0.2); /* Box shadow for the button */
+ color: #00796b; /* Set color of the arrows */
+ transition: background-color 0.3s, color 0.3s; /* Smooth transition for background and text color */
+}
+
+ /* Disabled state styles */
+ .arrow:disabled {
+ background-color: #cfd8dc; /* Light gray background when disabled */
+ color: #90a4ae; /* Gray color for the arrow */
+ cursor: not-allowed; /* Change cursor to indicate the button is disabled */
+ box-shadow: none; /* Remove box shadow for disabled button */
+ }
+
+.center-text {
+ font-size: 5rem; /* Increased font size for the 'Y' text */
+ font-weight: bold;
+ color: #00796b;
+ width: 4vw;
+ text-align: center;
+}
+
+
+/* Styling the container */
+.image-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+}
+
+/* Styling the round image with shadow */
+.round-image {
+ width: 4.8vw; /* Adjust the size as needed */
+ height: 4.8vw;
+ border-radius: 50%; /* Makes the image round */
+ box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.4);
+ transition: all 0.3s ease; /* Smooth transition for animation */
+ cursor: pointer;
+}
+
+ /* Scaling effect on click */
+ .round-image:active {
+ transform: scale(1.1); /* Scale up by 10% when clicked */
+ }
+
+/* Subtle zoom in/out */
+.apple-anim {
+ transform-origin: 50% 50%;
+ animation: appleZoom 2.8s ease-in-out infinite alternate;
+ will-change: transform;
+}
+
+@keyframes appleZoom {
+ from {
+ transform: scale(0.8);
+ }
+
+ to {
+ transform: scale(1.06);
+ }
+ /* slight zoom */
+}
+
+/* Respect reduced motion preference */
+@media (prefers-reduced-motion: reduce) {
+ .apple-anim {
+ animation: none;
+ transform: none;
+ }
+}
diff --git a/src/app/pronunciationvideo/pronunciationvideo.component.html b/src/app/pronunciationvideo/pronunciationvideo.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..392de84a76a937c452f4b0d79602122379332816
--- /dev/null
+++ b/src/app/pronunciationvideo/pronunciationvideo.component.html
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
{{ current.word }}
+
+
+ {{ current.phonetics }}
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ current.letter }}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/pronunciationvideo/pronunciationvideo.component.ts b/src/app/pronunciationvideo/pronunciationvideo.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e0714fbea7a4be34e21d333d796bf47f3883d2f5
--- /dev/null
+++ b/src/app/pronunciationvideo/pronunciationvideo.component.ts
@@ -0,0 +1,911 @@
+import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { finalize } from 'rxjs/operators';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { ChangeDetectorRef } from '@angular/core';
+
+interface PracticeItem {
+ letter: string;
+ word: string;
+ phonetics: string;
+ imgSrc: string;
+ audioSrc: string;
+}
+
+@Component({
+ selector: 'app-pronunciationvideo',
+ templateUrl: './pronunciationvideo.component.html',
+ styleUrls: ['./pronunciationvideo.component.css']
+})
+export class PronunciationVideoComponent implements OnInit, OnDestroy {
+ @ViewChild('videoEl') videoElRef?: ElementRef;
+
+ // toggle & src for teacher video
+ showVideo = false;
+ videoSrc = '';
+
+ // track play/pause state for the toggle button
+ isPlayingVideo = false;
+
+ // use asset image paths (replace with your actual asset filenames)
+ playIconDataUrl = '';
+ pauseIconDataUrl = '';
+
+ // ---------------------------
+ // CONFIG
+ // ---------------------------
+ // Change this to your server domain
+
+ private API_BASE = location.hostname.endsWith('hf.space')
+ ? 'https://pykara-py-learn-backend.hf.space'
+ : 'http://localhost:5000';
+
+ private readonly SCORE_ENDPOINT = `${this.API_BASE}/pronvideo/score`;
+
+ // Prefer a mime type supported by the browser
+ private readonly preferredMimeTypes = [
+ 'audio/webm;codecs=opus',
+ 'audio/webm',
+ 'audio/ogg;codecs=opus',
+ 'audio/ogg'
+ ];
+
+ // ---------------------------
+ // DATA
+ // ---------------------------
+ items: PracticeItem[] = [
+ {
+ letter: 'A',
+ word: 'Apple',
+ phonetics: '/ˈæpəl/',
+ imgSrc: 'assets/images/pron/letter-a.png',
+ audioSrc: 'assets/pronvideo/audio/apple.mp3'
+
+ },
+ {
+ letter: 'B',
+ word: 'Ball',
+ phonetics: '/bɔːl/',
+ imgSrc: 'assets/images/pron/letter-b.png',
+ audioSrc: 'assets/pronvideo/audio/ball.mp3'
+
+ },
+ {
+ letter: 'C',
+ word: 'Cat',
+ phonetics: '/kæt/',
+ imgSrc: 'assets/images/pron/letter-c.png',
+ audioSrc: 'assets/pronvideo/audio/cat.mp3'
+
+ },
+ {
+ letter: 'D',
+ word: 'Dog',
+ phonetics: '/dɒɡ/',
+ imgSrc: 'assets/images/pron/letter-d.png',
+ audioSrc: 'assets/pronvideo/audio/dog.mp3'
+
+ },
+ {
+ letter: 'E',
+ word: 'Egg',
+ phonetics: '/eɡ/',
+ imgSrc: 'assets/images/pron/letter-e.png',
+ audioSrc: 'assets/pronvideo/audio/egg.mp3'
+
+ },
+ {
+ letter: 'F',
+ word: 'Fish',
+ phonetics: '/fɪʃ/',
+ imgSrc: 'assets/images/pron/letter-f.png',
+ audioSrc: 'assets/pronvideo/audio/fish.mp3'
+
+ },
+ {
+ letter: 'G',
+ word: 'Grapes',
+ phonetics: '/ɡreɪps/',
+ imgSrc: 'assets/images/pron/letter-g.png',
+ audioSrc: 'assets/pronvideo/audio/grapes.mp3'
+
+ },
+ {
+ letter: 'H',
+ word: 'Hat',
+ phonetics: '/hæt/',
+ imgSrc: 'assets/images/pron/letter-h.png',
+ audioSrc: 'assets/pronvideo/audio/hat.mp3'
+
+ },
+ {
+ letter: 'I',
+ word: 'Ice cream',
+ phonetics: '/ˈaɪs ˌkriːm/',
+ imgSrc: 'assets/images/pron/letter-i.png',
+ audioSrc: 'assets/pronvideo/audio/icecream.mp3'
+
+ },
+ {
+ letter: 'J',
+ word: 'Jar',
+ phonetics: '/dʒɑːr/',
+ imgSrc: 'assets/images/pron/letter-j.png',
+ audioSrc: 'assets/pronvideo/audio/jar.mp3'
+
+ },
+ {
+ letter: 'K',
+ word: 'Kite',
+ phonetics: '/kaɪt/',
+ imgSrc: 'assets/images/pron/letter-k.png',
+ audioSrc: 'assets/pronvideo/audio/kite.mp3'
+
+ },
+ {
+ letter: 'L',
+ word: 'Lion',
+ phonetics: '/ˈlaɪən/',
+ imgSrc: 'assets/images/pron/letter-l.png',
+ audioSrc: 'assets/pronvideo/audio/lion.mp3'
+
+ },
+ {
+ letter: 'M',
+ word: 'Moon',
+ phonetics: '/muːn/',
+ imgSrc: 'assets/images/pron/letter-m.png',
+ audioSrc: 'assets/pronvideo/audio/moon.mp3'
+
+ },
+ {
+ letter: 'N',
+ word: 'Nest',
+ phonetics: '/nest/',
+ imgSrc: 'assets/images/pron/letter-n.png',
+ audioSrc: 'assets/pronvideo/audio/nest.mp3'
+
+ },
+ {
+ letter: 'O',
+ word: 'Orange',
+ phonetics: '/ˈɒrɪndʒ/',
+ imgSrc: 'assets/images/pron/letter-o.png',
+ audioSrc: 'assets/pronvideo/audio/orange.mp3'
+
+ },
+ {
+ letter: 'P',
+ word: 'Pig',
+ phonetics: '/pɪɡ/',
+ imgSrc: 'assets/images/pron/letter-p.png',
+ audioSrc: 'assets/pronvideo/audio/pig.mp3'
+
+ },
+ {
+ letter: 'Q',
+ word: 'Queen',
+ phonetics: '/kwiːn/',
+ imgSrc: 'assets/images/pron/letter-q.png',
+ audioSrc: 'assets/pronvideo/audio/queen.mp3'
+
+ },
+ {
+ letter: 'R',
+ word: 'Rabbit',
+ phonetics: '/ˈræbɪt/',
+ imgSrc: 'assets/images/pron/letter-r.png',
+ audioSrc: 'assets/pronvideo/audio/rabbit.mp3'
+
+ },
+ {
+ letter: 'S',
+ word: 'Sun',
+ phonetics: '/sʌn/',
+ imgSrc: 'assets/images/pron/letter-s.png',
+ audioSrc: 'assets/pronvideo/audio/sun.mp3'
+
+ },
+ {
+ letter: 'T',
+ word: 'Tree',
+ phonetics: '/triː/',
+ imgSrc: 'assets/images/pron/letter-t.png',
+ audioSrc: 'assets/pronvideo/audio/tree.mp3'
+
+ },
+ {
+ letter: 'U',
+ word: 'Umbrella',
+ phonetics: '/ʌmˈbrelə/',
+ imgSrc: 'assets/images/pron/letter-u.png',
+ audioSrc: 'assets/pronvideo/audio/umbrella.mp3'
+
+ },
+ {
+ letter: 'V',
+ word: 'Van',
+ phonetics: '/væn/',
+ imgSrc: 'assets/images/pron/letter-v.png',
+ audioSrc: 'assets/pronvideo/audio/van.mp3'
+
+ },
+ {
+ letter: 'W',
+ word: 'Watch',
+ phonetics: '/wɒtʃ/',
+ imgSrc: 'assets/images/pron/letter-w.png',
+ audioSrc: 'assets/pronvideo/audio/watch.mp3'
+
+ },
+ {
+ letter: 'X',
+ word: 'Xylophone',
+ phonetics: '/ˈzaɪləfəʊn/',
+ imgSrc: 'assets/images/pron/letter-x.png',
+ audioSrc: 'assets/pronvideo/audio/xylophone.mp3'
+
+ },
+ {
+ letter: 'Y',
+ word: 'Yarn',
+ phonetics: '/jɑːn/',
+ imgSrc: 'assets/images/pron/letter-y.png',
+ audioSrc: 'assets/pronvideo/audio/yarn.mp3'
+
+ },
+ {
+ letter: 'Z',
+ word: 'Zebra',
+ phonetics: '/ˈzebrə/',
+ imgSrc: 'assets/images/pron/letter-z.png',
+ audioSrc: 'assets/pronvideo/audio/zebra.mp3'
+
+ }
+ ];
+
+
+ index = 0;
+
+ get current(): PracticeItem {
+ return this.items[this.index];
+ }
+
+ // ---------------------------
+ // AUDIO (word)
+ // ---------------------------
+ private wordAudio = new Audio();
+
+ // ---------------------------
+ // RECORDING STATE
+ // ---------------------------
+ isRecording = false;
+ isScoring = false;
+
+ // NEW: flag to trigger needle oscillation after release and while waiting for score
+ isOscillating = false;
+
+ private mediaStream?: MediaStream;
+ private mediaRecorder?: MediaRecorder;
+ private chunks: BlobPart[] = [];
+ private currentMimeType = 'audio/webm';
+
+ recordedAudioUrl: string | null = null;
+ lastRecordedBlob: Blob | null = null;
+
+ // pending gesture play support (to satisfy autoplay policies)
+ private pendingVideoUrl: string | null = null;
+ private pendingGestureListener?: (e: Event) => void;
+
+ // ---------------------------
+ // RESULT UI
+ // ---------------------------
+ showResult = false;
+ score = 0;
+ stars = 0;
+ feedbackLines: string[] = [];
+ feedbackHint: string = '';
+ videoUrl: string = '';
+
+ // ---------------------------
+ // LIFECYCLE
+ // ---------------------------
+ constructor(private http: HttpClient, public dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: any, private cdr: ChangeDetectorRef) { }
+
+ ngOnInit(): void {
+ this.resetResult();
+ this.setupBestMimeType();
+
+ // Use images from assets instead of inline SVG data URIs.
+ // Ensure the files exist at these paths or update the paths to match your project.
+ this.playIconDataUrl = 'assets/pronvideo/play.png';
+ this.pauseIconDataUrl = 'assets/pronvideo/pause.png';
+ }
+
+ ngOnDestroy(): void {
+ this.cleanupRecordingUrl();
+ this.stopTracks();
+ this.safeStopRecorder();
+ this.wordAudio.pause();
+ this.wordAudio.src = '';
+ // ensure video cleaned up
+ this.resetVideo();
+ // remove any pending gesture listener to avoid leaks
+ if (this.pendingGestureListener) {
+ try { document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
+ this.pendingGestureListener = undefined;
+ this.pendingVideoUrl = null;
+ }
+ }
+
+ // ---------------------------
+ // RECORD & HOLD - EVENTS
+ // ---------------------------
+ async startRecording(evt?: Event): Promise {
+ evt?.preventDefault();
+
+ if (this.isRecording || this.isScoring) return;
+
+ this.resetResult();
+ this.cleanupRecordingUrl();
+ this.lastRecordedBlob = null;
+
+ try {
+ this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
+
+ const options: MediaRecorderOptions = {};
+ if (this.currentMimeType) {
+ options.mimeType = this.currentMimeType;
+ }
+
+ this.mediaRecorder = new MediaRecorder(this.mediaStream, options);
+
+ this.chunks = [];
+
+ this.mediaRecorder.ondataavailable = (e: BlobEvent) => {
+ if (e.data && e.data.size > 0) this.chunks.push(e.data);
+ };
+
+ this.mediaRecorder.onstop = () => {
+ this.onRecordingStopped();
+ };
+
+ this.mediaRecorder.start();
+ this.isRecording = true;
+
+ } catch {
+ this.isRecording = false;
+ this.stopTracks();
+ }
+ }
+
+ stopRecording(evt?: Event): void {
+ evt?.preventDefault();
+
+ if (!this.isRecording) return;
+
+ // End recording state
+ this.isRecording = false;
+
+ // Start oscillation immediately when user releases the record button:
+ // visual feedback that we are waiting for the backend scoring result.
+ this.isOscillating = true;
+
+ // Stop recorder safely
+ this.safeStopRecorder();
+
+ // Stop mic tracks
+ this.stopTracks();
+ }
+
+ private safeStopRecorder(): void {
+ try {
+ if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
+ this.mediaRecorder.stop();
+ }
+ } catch {
+ // ignore
+ }
+ }
+
+ private onRecordingStopped(): void {
+ try {
+ const blob = new Blob(this.chunks, { type: this.currentMimeType || 'audio/webm' });
+ this.lastRecordedBlob = blob;
+ this.recordedAudioUrl = URL.createObjectURL(blob);
+
+ // Send to backend for scoring
+ this.sendForScoring(blob, this.current.word);
+ } catch {
+ // If blob creation fails, stop oscillation and keep score hidden
+ this.isOscillating = false;
+ this.showResult = false;
+ } finally {
+ this.chunks = [];
+ }
+ }
+
+ // Optional: play student recording from UI button
+ playUserRecording(): void {
+ if (!this.recordedAudioUrl) return;
+ try {
+ const a = new Audio(this.recordedAudioUrl);
+ a.play().catch(() => { });
+ } catch {
+ // ignore
+ }
+ }
+
+ // ---------------------------
+ // BACKEND SCORING
+ // ---------------------------
+ private sendForScoring(blob: Blob, expectedWord: string): void {
+ if (!blob || !expectedWord) {
+ // nothing to do -> stop oscillation
+ this.isOscillating = false;
+ return;
+ }
+
+ const fd = new FormData();
+
+ // Give a reasonable filename extension
+ const ext = this.currentMimeType.includes('ogg') ? 'ogg' : 'webm';
+ fd.append('audio', blob, `student.${ext}`);
+ fd.append('word', expectedWord);
+
+ // show scoring state
+ this.isScoring = true;
+
+ this.http.post<{ score: number, hint: string, videoUrl: string }>(this.SCORE_ENDPOINT, fd)
+ .pipe(finalize(() => {
+
+ // stop scoring state and oscillation once HTTP completes (success or error)
+ this.isScoring = false;
+ this.isOscillating = false;
+ this.cdr.detectChanges();
+ }))
+ .subscribe({
+ next: (res) => {
+ const s = this.normalizeScore(res?.score);
+ this.score = s;
+ this.feedbackHint = res.hint || '';
+ this.videoUrl = res.videoUrl || '';
+ this.stars = this.mapStars(s);
+
+ this.feedbackLines = this.buildFeedbackFromScore(s);
+
+ this.showResult = true;
+ //this.cdr.detectChanges();
+
+ // Try to play feedback video; if autoplay blocked we register a gesture fallback.
+ if (this.videoUrl) {
+ this.tryPlayFeedbackVideo(s, this.videoUrl);
+ }
+ },
+ error: () => {
+ this.score = 0;
+ this.stars = 1;
+ this.feedbackLines = this.buildFeedbackFromScore(0);
+ this.showResult = true;
+ this.cdr.detectChanges();
+ }
+ });
+ }
+
+ private normalizeScore(val: any): number {
+ const n = Number(val);
+ if (Number.isNaN(n)) return 0;
+ if (n < 0) return 0;
+ if (n > 100) return 100;
+ return Math.round(n);
+ }
+
+ // ---------------------------
+ // RESULT HELPERS (frontend-only)
+ // ---------------------------
+ private resetResult(): void {
+ this.showResult = false;
+ this.score = 0;
+ this.stars = 0;
+ this.feedbackLines = [];
+ this.isPlayingVideo = false;
+ this.isOscillating = false;
+ }
+
+ /**
+ * Reset only the speakometer/score UI without touching recording state or video playback flags.
+ */
+ private resetSpeakometer(): void {
+ this.score = 0;
+ this.stars = 0;
+ this.feedbackLines = [];
+ this.feedbackHint = '';
+ this.showResult = false;
+ this.isOscillating = false;
+ }
+
+ private mapStars(s: number): number {
+ if (s >= 90) return 5;
+ if (s >= 80) return 4;
+ if (s >= 70) return 3;
+ if (s >= 60) return 2;
+ return 1;
+ }
+
+ private buildFeedbackFromScore(s: number): string[] {
+ if (s >= 90) {
+ return [
+ 'Excellent pronunciation.',
+ 'Very clear vowel and ending sound.',
+ 'Keep the same speed.'
+ ];
+ }
+ if (s >= 80) {
+ return [
+ 'Very good attempt.',
+ 'Slightly improve the main vowel.',
+ 'Ending sound is almost perfect.'
+ ];
+ }
+ if (s >= 70) {
+ return [
+ 'Good try.',
+ 'Listen once more and repeat slowly.',
+ 'Focus on the first sound.'
+ ];
+ }
+ if (s >= 50) {
+ return [
+ 'Nice effort.',
+ 'Try a slower pronunciation.',
+ 'Record again after listening.'
+ ];
+ }
+ return [
+ 'Try again.',
+ 'Listen to the model carefully.',
+ 'Speak clearly and record once more.'
+ ];
+ }
+
+ // ---------------------------
+ // MIME TYPE SELECTION
+ // ---------------------------
+ private setupBestMimeType(): void {
+ if (!(window as any).MediaRecorder) {
+ this.currentMimeType = 'audio/webm';
+ return;
+ }
+
+ for (const t of this.preferredMimeTypes) {
+ try {
+ if ((window as any).MediaRecorder.isTypeSupported(t)) {
+ this.currentMimeType = t;
+ return;
+ }
+ } catch {
+ // ignore
+ }
+ }
+
+ this.currentMimeType = 'audio/webm';
+ }
+
+ // ---------------------------
+ // CLEANUP
+ // ---------------------------
+ private stopTracks(): void {
+ try {
+ this.mediaStream?.getTracks().forEach(t => t.stop());
+ } catch {
+ // ignore
+ } finally {
+ this.mediaStream = undefined;
+ }
+ }
+
+ private cleanupRecordingUrl(): void {
+ if (this.recordedAudioUrl) {
+ try {
+ URL.revokeObjectURL(this.recordedAudioUrl);
+ } catch {
+ // ignore
+ }
+ this.recordedAudioUrl = null;
+ }
+ }
+
+ // ---------------------------
+ // NAVIGATION
+ // ---------------------------
+ prev(): void {
+ if (this.index === 0) return;
+ this.index--;
+ this.resetExerciseState();
+ }
+
+ next(): void {
+ if (this.index >= this.items.length - 1) return;
+ this.index++;
+ this.resetExerciseState();
+ }
+
+ goTo(i: number): void {
+ if (i < 0 || i >= this.items.length) return;
+ this.index = i;
+ this.resetExerciseState();
+ }
+
+ private resetExerciseState(): void {
+ // reset UI result data
+ this.resetResult();
+ this.cleanupRecordingUrl();
+ this.lastRecordedBlob = null;
+
+ // stop any recording/scoring
+ this.isRecording = false;
+ this.isScoring = false;
+
+ // stop media tracks and recorder
+ this.stopTracks();
+ this.safeStopRecorder();
+
+ // STOP and CLEAR any playing video so next exercise starts clean
+ this.resetVideo();
+ }
+
+ // new helper: stop & clear video element + reset related flags
+ private resetVideo(): void {
+ try {
+ const v = this.videoElRef?.nativeElement;
+ if (v) {
+ try { v.pause(); } catch { /* ignore */ }
+ try { v.currentTime = 0; } catch { /* ignore */ }
+ // remove src so browser releases resource
+ try { v.removeAttribute('src'); } catch { /* ignore */ }
+ try { v.load(); } catch { /* ignore */ }
+ }
+ } catch {
+ // ignore any DOM errors
+ } finally {
+ this.showVideo = false;
+ this.videoSrc = '';
+ this.isPlayingVideo = false;
+ }
+ }
+
+ // ---------------------------
+ // WORD AUDIO
+ // ---------------------------
+ playWordAudio(): void {
+ // reset speakometer when starting model audio playback
+ this.resetSpeakometer();
+
+ const src = this.current.audioSrc || this.getAudioSrcFromWord(this.current.word);
+ if (!src) return;
+
+ try {
+ this.wordAudio.pause();
+ this.wordAudio.currentTime = 0;
+ this.wordAudio.src = src;
+ this.wordAudio.play().catch(() => { });
+ } catch {
+ // ignore
+ }
+ }
+
+ private getAudioSrcFromWord(word: string): string {
+ if (!word) return '';
+ const fileName = word.trim().toLowerCase().replace(/\s+/g, '-');
+ return `assets/pronvideo/audio/${fileName}.mp3`;
+ }
+
+ get needleAngle(): number {
+ const val = Math.max(0, Math.min(100, Number(this.score ?? 0)));
+ return -90 + (val * 1.8);
+ }
+
+ closePopup(): void {
+ this.dialogRef.close();
+ }
+
+ // ---------------------------
+ // TEACHER VIDEO
+ // ---------------------------
+ playWordVideo(): void {
+ // Reset speakometer when user requests the teacher video
+ this.resetSpeakometer();
+
+ // Build video path based on current.word (assets/videos/.mp4)
+ const src = this.getVideoSrcFromWord(this.current.word);
+ if (!src) return;
+
+ this.showVideo = true;
+ this.videoSrc = src;
+
+ // Wait for template to render, then set and play video safely
+ setTimeout(() => {
+ try {
+ const v = this.videoElRef?.nativeElement;
+ if (v) {
+ // sync UI state to element events and reset speakometer when native play occurs
+ v.addEventListener('play', () => {
+ this.isPlayingVideo = true;
+ // keep resetting speakometer on native play to ensure gauge clears
+ this.resetSpeakometer();
+ });
+ v.addEventListener('pause', () => this.isPlayingVideo = false);
+
+ v.pause();
+ v.src = this.videoSrc;
+ v.load();
+ v.play().then(() => {
+ this.isPlayingVideo = true;
+ }).catch(() => {
+ // ignore autoplay errors; UI still shows the control
+ this.isPlayingVideo = !v.paused;
+ });
+ }
+ } catch {
+ // ignore
+ }
+ }, 0);
+ }
+
+ /**
+ * Toggles play/pause for the currently-displayed video.
+ */
+ toggleVideoPlay(): void {
+ try {
+ if (!this.showVideo) {
+ this.playWordVideo();
+ return;
+ }
+
+ const v = this.videoElRef?.nativeElement;
+ if (!v) {
+ this.videoSrc = this.getVideoSrcFromWord(this.current.word);
+ this.showVideo = true;
+ return;
+ }
+
+ if (v.paused) {
+ // user is starting playback -> reset speakometer
+ this.resetSpeakometer();
+
+ v.play().then(() => {
+ this.isPlayingVideo = true;
+ }).catch(() => {
+ this.isPlayingVideo = !v.paused;
+ });
+ } else {
+ v.pause();
+ this.isPlayingVideo = false;
+ }
+ } catch {
+ // ignore
+ }
+ }
+
+ /**
+ * Attempts to play a feedback video returned by the backend in the same video element.
+ * If autoplay is blocked we register a one-time body click handler so the first user gesture starts playback.
+ */
+ private tryPlayFeedbackVideo(score: number, videoUrl: string): void {
+ if (!videoUrl) return;
+
+ // ensure UI shows the video element
+ this.showVideo = true;
+ this.videoSrc = videoUrl;
+
+ setTimeout(() => {
+ try {
+ const v = this.videoElRef?.nativeElement;
+ if (!v) {
+ // element not yet available -> register gesture fallback
+ this.registerGestureForPendingVideo(videoUrl);
+ return;
+ }
+
+ try { v.pause(); } catch { /* ignore */ }
+
+ v.src = this.videoSrc;
+ v.load();
+ v.play().then(() => {
+ this.isPlayingVideo = true;
+ }).catch(() => {
+ // autoplay likely blocked -> register gesture fallback
+ this.isPlayingVideo = !v.paused;
+ this.registerGestureForPendingVideo(videoUrl);
+ });
+ } catch {
+ // ignore
+ }
+ }, 0);
+ }
+
+ /**
+ * Register a one-time body click handler to play a pending feedback video when user interacts.
+ * This satisfies browser autoplay policies (requires a user gesture).
+ */
+ private registerGestureForPendingVideo(videoUrl: string): void {
+ // if already registered, update pending URL and exit
+ this.pendingVideoUrl = videoUrl;
+
+ if (this.pendingGestureListener) return;
+
+ this.pendingGestureListener = (e: Event) => {
+ try {
+ // show video and set src so element renders if it wasn't already
+ this.showVideo = true;
+ this.videoSrc = this.pendingVideoUrl || videoUrl;
+
+ // attempt play on the element after change detection
+ setTimeout(() => {
+ try {
+ const v = this.videoElRef?.nativeElement;
+ if (v) {
+ v.src = this.videoSrc;
+ v.load();
+ v.play().then(() => {
+ this.isPlayingVideo = true;
+ }).catch(() => {
+ this.isPlayingVideo = !v.paused;
+ });
+ }
+ } catch {
+ // ignore
+ }
+ }, 0);
+ } finally {
+ // cleanup listener and pending state
+ try { if (this.pendingGestureListener) document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
+ this.pendingGestureListener = undefined;
+ this.pendingVideoUrl = null;
+ // ensure gauge is not oscillating after user gesture
+ this.isOscillating = false;
+ }
+ };
+
+ // use capture so early gestures are caught even if other handlers stop propagation
+ try { document.body.addEventListener('click', this.pendingGestureListener, true); } catch { /* ignore */ }
+ }
+
+ onVideoEnded(): void {
+ // Ensure the native element is stopped and cleared so the thumbnail can re-appear.
+ try {
+ const v = this.videoElRef?.nativeElement;
+ if (v) {
+ try { v.pause(); } catch { }
+ try { v.currentTime = 0; } catch { }
+ try { v.src = ''; } catch { }
+ try { v.load(); } catch { }
+ }
+ } catch {
+ // ignore DOM errors
+ } finally {
+ // show the thumbnail again and reset video state
+ this.showVideo = false;
+ this.isPlayingVideo = false;
+ this.videoSrc = '';
+
+ // stop any lingering oscillation and pending gesture state
+ this.isOscillating = false;
+ if (this.pendingGestureListener) {
+ try { document.body.removeEventListener('click', this.pendingGestureListener, true); } catch { }
+ this.pendingGestureListener = undefined;
+ this.pendingVideoUrl = null;
+ }
+
+ // ensure template updates immediately
+ try { this.cdr.detectChanges(); } catch { }
+ }
+ }
+
+ private getVideoSrcFromWord(word: string): string {
+ if (!word) return '';
+ const fileName = word.trim().toLowerCase().replace(/\s+/g, '-');
+ return `assets/pronvideo/videos/${fileName}.mp4`;
+ }
+}
diff --git a/src/assets/pronvideo/audio.png b/src/assets/pronvideo/audio.png
new file mode 100644
index 0000000000000000000000000000000000000000..d61fc08ba9444f02a57463640de4517418edbc37
--- /dev/null
+++ b/src/assets/pronvideo/audio.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:21066ffba9cccf98ff08e440f64e16328124c88e428aefc46e4131f4021d2aa5
+size 6159
diff --git a/src/assets/pronvideo/audio/apple.mp3 b/src/assets/pronvideo/audio/apple.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..7303312c0abbc8aab78c048fdf02d8a7e645c957
--- /dev/null
+++ b/src/assets/pronvideo/audio/apple.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cd3749451facc8a9736f5ec3c16ab71c1c0a44e23c88bc2faaaba86676cfd930
+size 13560
diff --git a/src/assets/pronvideo/audio/ball.mp3 b/src/assets/pronvideo/audio/ball.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..18f83ace12a0c02785725f9b526d176e22b36e40
--- /dev/null
+++ b/src/assets/pronvideo/audio/ball.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:eae7be3c52cf56a513aa701d71b1fbc93db1db23d5f15cb7a34b6b3f3cfa320f
+size 12792
diff --git a/src/assets/pronvideo/audio/cat.mp3 b/src/assets/pronvideo/audio/cat.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..30f0dac1a040d4d9eb7d59d133c202e565a80cc6
--- /dev/null
+++ b/src/assets/pronvideo/audio/cat.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d5f8746b9ea52ac6f34f449603c8d24dac05bdf1463e7b2217ed3dfb36e877a9
+size 12912
diff --git a/src/assets/pronvideo/audio/dog.mp3 b/src/assets/pronvideo/audio/dog.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..9c2f49f9b14ff1400b9bd13df213996a0344c6af
--- /dev/null
+++ b/src/assets/pronvideo/audio/dog.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8198fcb9e7ef7ff2eb4a2a57f5bad61bb5de4896750e4004917c8a51c82ce0cb
+size 11592
diff --git a/src/assets/pronvideo/audio/egg.mp3 b/src/assets/pronvideo/audio/egg.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..56323026e43cd7fe6f1a3fe67bb505e625e032c8
--- /dev/null
+++ b/src/assets/pronvideo/audio/egg.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b72a200ec8e163f9615b89599b4e6cc034c93d49e9d05580ea96c5fa5a94ec45
+size 9312
diff --git a/src/assets/pronvideo/audio/fish.mp3 b/src/assets/pronvideo/audio/fish.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..d89dbbba3fba3c74fc1082fe1a83f8dacc194c45
--- /dev/null
+++ b/src/assets/pronvideo/audio/fish.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2f1c7898a6fe74acf4a509755dbcd5a14961622ab212bd9e13ce2dd423d1077a
+size 8976
diff --git a/src/assets/pronvideo/audio/grapes.mp3 b/src/assets/pronvideo/audio/grapes.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..56f5db67120e6710688686867bb329a527da0387
--- /dev/null
+++ b/src/assets/pronvideo/audio/grapes.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cda848a9e69ea6f80a617d1f6f718820f86667e468087bdd89a388629e147057
+size 12264
diff --git a/src/assets/pronvideo/audio/hat.mp3 b/src/assets/pronvideo/audio/hat.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..a0044c13cceb576e93175d1c8ba2e479b99fe2a8
--- /dev/null
+++ b/src/assets/pronvideo/audio/hat.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:09df5945baf67d5d6f3b9337dc65b768a458b70e7046e2441218e1037fe7fdb1
+size 9672
diff --git a/src/assets/pronvideo/audio/icecream.mp3 b/src/assets/pronvideo/audio/icecream.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..6d0e6d1ccedfbb2f77718f8e50514fdff0e773e8
--- /dev/null
+++ b/src/assets/pronvideo/audio/icecream.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:60a2a41f57a2fdadce831e6b18582cfcdfe4a87b80256effb5d832a429d784d2
+size 13200
diff --git a/src/assets/pronvideo/audio/jar.mp3 b/src/assets/pronvideo/audio/jar.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..439f3e637e73de2d79434b3ad542d242bdcb791f
--- /dev/null
+++ b/src/assets/pronvideo/audio/jar.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c96580ccebde2b23c3b23399aa1d1956c7f290a8235ec3dd4881f250c6f4c0c1
+size 11616
diff --git a/src/assets/pronvideo/audio/kite.mp3 b/src/assets/pronvideo/audio/kite.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..a0079a4f573ce8d68ae8df186f4452d2e6a3265d
--- /dev/null
+++ b/src/assets/pronvideo/audio/kite.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:27a9dee3aaa483d21ac7cf45ab11208f1c2541481d38900a2eeb0912a7d1eafe
+size 10896
diff --git a/src/assets/pronvideo/audio/lion.mp3 b/src/assets/pronvideo/audio/lion.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..37352bc55d1c0c35e0a946d9fcc7ec67597b51c2
--- /dev/null
+++ b/src/assets/pronvideo/audio/lion.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9c4c156b7f55749be6d0a81045fadc7fed907d7ad4ffe338ba2c60140f861ff1
+size 10680
diff --git a/src/assets/pronvideo/audio/moon.mp3 b/src/assets/pronvideo/audio/moon.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..296ee06a41efa23ff38df19726d1ad1c29ddd245
--- /dev/null
+++ b/src/assets/pronvideo/audio/moon.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2595f31a5d53a3f48535975f196807d4e5b01d50a895b462e7de2b0fc84ab2aa
+size 9960
diff --git a/src/assets/pronvideo/audio/nest.mp3 b/src/assets/pronvideo/audio/nest.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..8011de04820ba1db9d06bb66b358ca44492633b0
--- /dev/null
+++ b/src/assets/pronvideo/audio/nest.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:530beb8d1ebd6b89cce0187d8a3cb401b0c237eb5f656e430caf1fe96b67c9a1
+size 12024
diff --git a/src/assets/pronvideo/audio/orange.mp3 b/src/assets/pronvideo/audio/orange.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..82296a46d630193932087b19cb0486f38dea23ca
--- /dev/null
+++ b/src/assets/pronvideo/audio/orange.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c96726c87265cadcdf3f392190fd8b66964451da60add484d39dcb5db4226879
+size 9792
diff --git a/src/assets/pronvideo/audio/pig.mp3 b/src/assets/pronvideo/audio/pig.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..3ce1ae9cd458fe5b7cf7a13ac7b638a71bfda1be
--- /dev/null
+++ b/src/assets/pronvideo/audio/pig.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:48c66e98d9b044cfde83397ae699c6a90c3c396e747dc2d71f1290820082bf43
+size 8544
diff --git a/src/assets/pronvideo/audio/queen.mp3 b/src/assets/pronvideo/audio/queen.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..632a234e72fc3f1ef3308e0f3287a8abef0c47fc
--- /dev/null
+++ b/src/assets/pronvideo/audio/queen.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1b7fc5588e5bada0d30e696253c0c47829d5a374ec8dc17de8c7b3df5200c5aa
+size 9216
diff --git a/src/assets/pronvideo/audio/rabbit.mp3 b/src/assets/pronvideo/audio/rabbit.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..429db03bfbabf174b8c68cf89d8dbda15eb93527
--- /dev/null
+++ b/src/assets/pronvideo/audio/rabbit.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b22661ed72eaff38af8cccc055a2ac91200e213cad78371ee50db8023a0078f0
+size 10848
diff --git a/src/assets/pronvideo/audio/sun.mp3 b/src/assets/pronvideo/audio/sun.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..bb0ff47ef56a60efc79a92d792f60fc28026841a
--- /dev/null
+++ b/src/assets/pronvideo/audio/sun.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:170d1bf45ca7c8f1dc268b4a1a5f19a986faae55780a834ecb5470509ac5ceff
+size 11184
diff --git a/src/assets/pronvideo/audio/tree.mp3 b/src/assets/pronvideo/audio/tree.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..89e75cf2ee260c3888905f8e3864ac574768659d
--- /dev/null
+++ b/src/assets/pronvideo/audio/tree.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ab5cf4ad1e05e45125bab5f1ed611402e2c16c1175827e1d4fffd80cb7c45c11
+size 9288
diff --git a/src/assets/pronvideo/audio/umbrella.mp3 b/src/assets/pronvideo/audio/umbrella.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..6aaee8c737cbc199e94d8255f0e51f6ff31539e9
--- /dev/null
+++ b/src/assets/pronvideo/audio/umbrella.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fa1bf35b7f8f9ef437639cc80c9a2b231fae5096327f7069c176bde84efc216d
+size 11640
diff --git a/src/assets/pronvideo/audio/van.mp3 b/src/assets/pronvideo/audio/van.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..a056aad4db828052dfbeeeae5e13768049297e67
--- /dev/null
+++ b/src/assets/pronvideo/audio/van.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:249632f20e354aa4c41ecd60ef9e8cc8ee55601f72eb66b4a3a07bc7a2bb92af
+size 9624
diff --git a/src/assets/pronvideo/audio/watch.mp3 b/src/assets/pronvideo/audio/watch.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..90a1e901c8768b61020fbfbfde4a50f2ea913c06
--- /dev/null
+++ b/src/assets/pronvideo/audio/watch.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:629b00cae653b2aedb022391b6c9b4e0e869145f3c348fda674f206ffb635101
+size 11568
diff --git a/src/assets/pronvideo/audio/xylophone.mp3 b/src/assets/pronvideo/audio/xylophone.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..a79ff38ed555edbc53b12461faa6d12878b2f2df
--- /dev/null
+++ b/src/assets/pronvideo/audio/xylophone.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0b8f2764c762e59440f4bfcc40dfcdc516e86a437fbf7fc5623aa130f6dbec25
+size 15744
diff --git a/src/assets/pronvideo/audio/yarn.mp3 b/src/assets/pronvideo/audio/yarn.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..f10745dfeeb4ebb73240cbd8942748f18c4b9825
--- /dev/null
+++ b/src/assets/pronvideo/audio/yarn.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b8e6f8aa4e3b7e44aa3faafe13f113fb2704fc7fbcd0378e74006f9b0ebc1684
+size 9504
diff --git a/src/assets/pronvideo/audio/zebra.mp3 b/src/assets/pronvideo/audio/zebra.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..6f69bdd64a720043dd24ca93cf77138d2a8fc656
--- /dev/null
+++ b/src/assets/pronvideo/audio/zebra.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2873545095c089d763fd6d9185ffc868629041e7c0965d0fbbb1223d7c1a1730
+size 12984
diff --git a/src/assets/pronvideo/feedback/consonant.mp4 b/src/assets/pronvideo/feedback/consonant.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..2deed8d0182bc71a6da686c083ae194350841c61
--- /dev/null
+++ b/src/assets/pronvideo/feedback/consonant.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:eae0c82c7556e573bd3d4c0e9b4b885430641885fe4a445e0425f6d90a04589a
+size 5481531
diff --git a/src/assets/pronvideo/feedback/ending.mp4 b/src/assets/pronvideo/feedback/ending.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..654025470b5aaebf889ad554d2dd19f55831e047
--- /dev/null
+++ b/src/assets/pronvideo/feedback/ending.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e1bbfff2a733efd22b952ae4e32f686b06ada31b247208b81c8cb187e818695f
+size 4184262
diff --git a/src/assets/pronvideo/feedback/multipleword.mp4 b/src/assets/pronvideo/feedback/multipleword.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..ed31f0a02daa43121ff0644cce5bfc0126238a6f
--- /dev/null
+++ b/src/assets/pronvideo/feedback/multipleword.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7da6fc60d82f728164d12cb57acae93af3d7092be447f3b884ba262cf5533a1d
+size 2252316
diff --git a/src/assets/pronvideo/feedback/silence.mp4 b/src/assets/pronvideo/feedback/silence.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..3ab7995c40eb2b1e5d2866aa9d760486c59941d4
--- /dev/null
+++ b/src/assets/pronvideo/feedback/silence.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b31c9c9bf22c6ebcf0ab84e23398ae019f613d5cfc494ea3d01c4541e16a56b0
+size 5405291
diff --git a/src/assets/pronvideo/feedback/stress.mp4 b/src/assets/pronvideo/feedback/stress.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..e74a63ad35124ce3219bc74ced3585d81042fd1c
--- /dev/null
+++ b/src/assets/pronvideo/feedback/stress.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:67cbdef5635080cf0d38afbd1e4c5a0c79051363dd6fc138c7f3a1632ce47d7b
+size 5214043
diff --git a/src/assets/pronvideo/feedback/success.mp4 b/src/assets/pronvideo/feedback/success.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..36b2ecca9751dc34e356ec0ff5a003fb0c071112
--- /dev/null
+++ b/src/assets/pronvideo/feedback/success.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7065eb4444fff7033ffae8183901123ce45bca47114d8307652523006ca4b7b9
+size 7149268
diff --git a/src/assets/pronvideo/feedback/syllable.mp4 b/src/assets/pronvideo/feedback/syllable.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..f6c7cdc2e2390152e9d8bf3313fa1db1baf8cce7
--- /dev/null
+++ b/src/assets/pronvideo/feedback/syllable.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:01c7ecb44cc4eee9fb9cb32ec7f363e367c17479c0ae91883d902f428e3db9c9
+size 3955318
diff --git a/src/assets/pronvideo/feedback/vowels.mp4 b/src/assets/pronvideo/feedback/vowels.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..3bb0cf3ec75077a7c824d073209f4fd1f5300c80
--- /dev/null
+++ b/src/assets/pronvideo/feedback/vowels.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cd9a3f4ddfdadb90b0aaadbfa0a00c5aa80342f4b133543836d2b21592eb3443
+size 5837969
diff --git a/src/assets/pronvideo/feedback/wrongword.mp4 b/src/assets/pronvideo/feedback/wrongword.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..198eb7ab19d9034c2aaa8325a8da3905aa23d25a
--- /dev/null
+++ b/src/assets/pronvideo/feedback/wrongword.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b0a32e7b4ed1f4355aa0eca1bd48c5ed4030e3deb7f2e5a412cb6a3fc3d06df1
+size 5194285
diff --git a/src/assets/pronvideo/pause.png b/src/assets/pronvideo/pause.png
new file mode 100644
index 0000000000000000000000000000000000000000..74f6fae2b84605c0e01643fc3645ea1892fba70e
--- /dev/null
+++ b/src/assets/pronvideo/pause.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9efce36e388aaccad1b743e590ff1e6ff53e27260c6b48b28f46db0187d6b1db
+size 10788
diff --git a/src/assets/pronvideo/play.png b/src/assets/pronvideo/play.png
new file mode 100644
index 0000000000000000000000000000000000000000..d8e515f3b886cc860c20f5a62715198cdb9cd54e
--- /dev/null
+++ b/src/assets/pronvideo/play.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a5b587892782a79de9117ba4259158866d80f56b97aa764121cff189b135fb4e
+size 3112
diff --git a/src/assets/pronvideo/teacher-nod.mp4 b/src/assets/pronvideo/teacher-nod.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..a2064498c93a886c334609324a8c8b4c7f5a9200
--- /dev/null
+++ b/src/assets/pronvideo/teacher-nod.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8f85a86632a64280579167285cfe71bd32d556797ec8e58e2fc14cf8285e1fb3
+size 117067
diff --git a/src/assets/pronvideo/teacher.png b/src/assets/pronvideo/teacher.png
new file mode 100644
index 0000000000000000000000000000000000000000..9de44aedc768dbe31b98022cb6fb7d219b781c10
--- /dev/null
+++ b/src/assets/pronvideo/teacher.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e5e6f6e2ef93ec59d4b47f41af45bc73a229c4225d2337c506ba7113ec72357c
+size 293505
diff --git a/src/assets/pronvideo/videos/apple.mp4 b/src/assets/pronvideo/videos/apple.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..b76a10925148eadc912f124b9c18d6bd80c49979
--- /dev/null
+++ b/src/assets/pronvideo/videos/apple.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2dc1cd633647903a950c33999a221db9444b4ecffa94461344055b6727af6e7f
+size 7134565
diff --git a/src/assets/pronvideo/videos/ball.mp4 b/src/assets/pronvideo/videos/ball.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..4c25d6602333b68079e9c12ac9489cd5421f3215
--- /dev/null
+++ b/src/assets/pronvideo/videos/ball.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a508e9d7a8d0efe88d8f4d3633832266090793136f06b2e59c87e8ee694fbeda
+size 7606207
diff --git a/src/assets/pronvideo/videos/cat.mp4 b/src/assets/pronvideo/videos/cat.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..a9c85eb3bc54973c77436a94c444a6922ff08578
--- /dev/null
+++ b/src/assets/pronvideo/videos/cat.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f171b0200d6662ee0cb94fc36c98a65ca1daf8053e98bcd5d4a91eb883cbc294
+size 7805298
diff --git a/src/assets/pronvideo/videos/dog.mp4 b/src/assets/pronvideo/videos/dog.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..e85061401593cc32964cf0006991fba3d4b436cd
--- /dev/null
+++ b/src/assets/pronvideo/videos/dog.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:58140b73777fc39ebed3d282f215573b3ffb8f9c05d74b68c938ab2aefddb31e
+size 7150587
diff --git a/src/assets/pronvideo/videos/egg.mp4 b/src/assets/pronvideo/videos/egg.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..043af61e02c4d6e341b4ecb6f48668dc62d85902
--- /dev/null
+++ b/src/assets/pronvideo/videos/egg.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:522a425054b0d16f1bb87ace5ed9bddb6f366d0bfc4fdd91907c38db18602035
+size 6383313
diff --git a/src/assets/pronvideo/videos/fish.mp4 b/src/assets/pronvideo/videos/fish.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..772c7e933d9b59da0232e2cb78a6f31e7556d2eb
--- /dev/null
+++ b/src/assets/pronvideo/videos/fish.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4d81deb2735fdb7f41c5514f505fb75d050ca769fe308df665a533b42821374e
+size 7276274
diff --git a/src/assets/pronvideo/videos/grapes.mp4 b/src/assets/pronvideo/videos/grapes.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..8daba3b4f407b8c17d26f29cdc52451fe5142602
--- /dev/null
+++ b/src/assets/pronvideo/videos/grapes.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6f4542c920fe022d427d69b8fa9bc7668c78c149eb79c42bd96776472f5d94d5
+size 6988222
diff --git a/src/assets/pronvideo/videos/hat.mp4 b/src/assets/pronvideo/videos/hat.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..c3366c1fa65c8d1fe52945aaf6b53c0b324c0a78
--- /dev/null
+++ b/src/assets/pronvideo/videos/hat.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a7ad6c5f8ffa23b57e85ae36b4b2c60ac58fcf4a7c5bc086cfd74a22cb2ef92b
+size 7053688
diff --git a/src/assets/pronvideo/videos/ice-cream.mp4 b/src/assets/pronvideo/videos/ice-cream.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..95de063d1a737a7f562cfffcffb000f44504db90
--- /dev/null
+++ b/src/assets/pronvideo/videos/ice-cream.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cb339a511694db8051ab8e331f91a6a112b3ce8de939ce6e398204d86c298650
+size 8151667
diff --git a/src/assets/pronvideo/videos/jar.mp4 b/src/assets/pronvideo/videos/jar.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..c292f7c88902e2b11b92cee05e94fb769b6e8f3b
--- /dev/null
+++ b/src/assets/pronvideo/videos/jar.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:45b6d886b970282b375031b20c3007443417fa4d7ab4147f624bcde08fef5905
+size 7558106
diff --git a/src/assets/pronvideo/videos/kite.mp4 b/src/assets/pronvideo/videos/kite.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..60bb639caabbd02f3d628df9025a2a21929a1497
--- /dev/null
+++ b/src/assets/pronvideo/videos/kite.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:396d670691bc4c714789c3ba4d8a080ffd210e6ca398d3869c04e9eeeda9516c
+size 7384715
diff --git a/src/assets/pronvideo/videos/lion.mp4 b/src/assets/pronvideo/videos/lion.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..937d9a72f1fbc6094dfc7507dfb199cf3b9acb18
--- /dev/null
+++ b/src/assets/pronvideo/videos/lion.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ca2d4e211b05380e1152ede6052e0751d29e46da96c78431d7bd22a83f301e94
+size 5431178
diff --git a/src/assets/pronvideo/videos/moon.mp4 b/src/assets/pronvideo/videos/moon.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..e809ab291db8ed3dcd92c3745e17811e9472f578
--- /dev/null
+++ b/src/assets/pronvideo/videos/moon.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:790afab95dd4a4a94d96b7edc525fa37dc8ebb3ae0752df4db4a4e655760f7bb
+size 4255165
diff --git a/src/assets/pronvideo/videos/nest.mp4 b/src/assets/pronvideo/videos/nest.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..cd184c4c01d7ca4f0f3540da9c67dcfc38276194
--- /dev/null
+++ b/src/assets/pronvideo/videos/nest.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b0335f6a7ad4bb94dd6ce4bb9efcaade16bac270b7d5d2f43bad8fa8a331a484
+size 4301304
diff --git a/src/assets/pronvideo/videos/orange.mp4 b/src/assets/pronvideo/videos/orange.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..edab062c65348212e3fb4307fbaf4b41c03e7214
--- /dev/null
+++ b/src/assets/pronvideo/videos/orange.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e9778f3a81f2d925d552b8b03431468aac03b89f5713ee5b9776d388e58d8fd8
+size 5016720
diff --git a/src/assets/pronvideo/videos/pig.mp4 b/src/assets/pronvideo/videos/pig.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..0cbf865784dc48c0778ef940dfabbb154c8bfaf5
--- /dev/null
+++ b/src/assets/pronvideo/videos/pig.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:72d4ae305e0a4db38bf0b794baefb6f81834416de57187549eab3f7ad3c8a561
+size 4072009
diff --git a/src/assets/pronvideo/videos/queen.mp4 b/src/assets/pronvideo/videos/queen.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..ad2328797a2e87b0860bf51d44df796d1af33941
--- /dev/null
+++ b/src/assets/pronvideo/videos/queen.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:07256cdf96e4e603a8604688f6d1c42667319128c5874281b8f5e29a83197884
+size 4707339
diff --git a/src/assets/pronvideo/videos/rabbit.mp4 b/src/assets/pronvideo/videos/rabbit.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..3017062045b1bda7d37a4ac7ae67ee322953471f
--- /dev/null
+++ b/src/assets/pronvideo/videos/rabbit.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9c3b59991ee348ba0a9117ad5648fbf0ea03fc69298b4d3f8863d45dd94194ee
+size 4932831
diff --git a/src/assets/pronvideo/videos/sun.mp4 b/src/assets/pronvideo/videos/sun.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..026824bbe5b14945c96e041afd4ede3844ed27ee
--- /dev/null
+++ b/src/assets/pronvideo/videos/sun.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ec104e7db0534360ef9eebeae09472b0e19267f3555d0acc5bb66c996cb049a6
+size 4805357
diff --git a/src/assets/pronvideo/videos/tree.mp4 b/src/assets/pronvideo/videos/tree.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..afc883ebef165c27e0b0e1b1f11202ecb522a182
--- /dev/null
+++ b/src/assets/pronvideo/videos/tree.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:54073d3b56747c9b34ab2edb7777875f2c63549a212dcd367623921400f15e07
+size 5155482
diff --git a/src/assets/pronvideo/videos/umbrella.mp4 b/src/assets/pronvideo/videos/umbrella.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..8bd529488484ed51144e83df6c46dea06858059b
--- /dev/null
+++ b/src/assets/pronvideo/videos/umbrella.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2c4a22ead24864c9a1966147fd0cf6f1e248313841fd98ceb702682825d3073d
+size 5174794
diff --git a/src/assets/pronvideo/videos/van.mp4 b/src/assets/pronvideo/videos/van.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..dd3da1ea204bff27922c15cb2e04c26842f9d16d
--- /dev/null
+++ b/src/assets/pronvideo/videos/van.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4724e2cab20be89b9148c699a33c2a433e239611a7787c45487942ba7ef9d981
+size 4462232
diff --git a/src/assets/pronvideo/videos/watch.mp4 b/src/assets/pronvideo/videos/watch.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..2917d8c0cb3c2fe2038cf8d4cd7393deceabab92
--- /dev/null
+++ b/src/assets/pronvideo/videos/watch.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1e4f27454697987ebb17c25fb86e88a2196873b040c222d5970da05b568f91db
+size 4960176
diff --git a/src/assets/pronvideo/videos/xylophone.mp4 b/src/assets/pronvideo/videos/xylophone.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..1076eb925a1913061ed7d9368bff3b9477fc594a
--- /dev/null
+++ b/src/assets/pronvideo/videos/xylophone.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:93fc9aef9f9419f344a0ba97ff3b5d1c0743ad121956f462e504e31388893164
+size 5199424
diff --git a/src/assets/pronvideo/videos/yarn.mp4 b/src/assets/pronvideo/videos/yarn.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..bb1fc165d47b459bd6edd28f53c3fea9ebbcbd4c
--- /dev/null
+++ b/src/assets/pronvideo/videos/yarn.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3fc19acd88c47a3f7c33d750ef0f77c2dce6272d51e9e532576c2d00bda52413
+size 5095081
diff --git a/src/assets/pronvideo/videos/zebra.mp4 b/src/assets/pronvideo/videos/zebra.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..fad180098b2b4f46357210f27c9e3e8c40cde5e0
--- /dev/null
+++ b/src/assets/pronvideo/videos/zebra.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:60b31a107a1ea872150219c4f5c5b903ddaab3cf07671e5960494f90f9b20074
+size 4569058