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 @@ +
    + + +
    +

    Pronunciation Practice

    +
    + + +
    + + +
    +
    +
    + +
    + +
    {{ current.word }}
    + +
    + {{ current.phonetics }} +
    + + +
    + Round Image +
    + +
    +
    + + +
    +
    +
    + + Teacher + + + +
    + + + + +
    + + + + +
    +
    +
    + + +
    + + +
    +
    +
    +
    +
    + +
    + {{score}}% +
    +
    + + + +
    + + {{ 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 @@ +
    + + +
    +

    Pronunciation Practice

    +
    + + +
    + + +
    +
    +
    + +
    + +
    {{ current.word }}
    + +
    + {{ current.phonetics }} +
    + + +
    + Round Image +
    + +
    +
    + + +
    +
    +
    + + Teacher + + + +
    + + + + +
    + + + + +
    +
    +
    + + +
    + + +
    +
    +
    +
    +
    + +
    + {{score}}% +
    +
    + + + +
    + + {{ 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