{"_id":"@aminnausin/am-lyrics","_rev":"4-35ca8ebe0538d2c99228a5e1a934b506","name":"@aminnausin/am-lyrics","dist-tags":{"latest":"1.0.3"},"versions":{"1.0.0":{"name":"@aminnausin/am-lyrics","version":"1.0.0","keywords":["lit","react","web-component","lyrics"],"author":{"name":"aminnausin"},"license":"MPL-2.0","_id":"@aminnausin/am-lyrics@1.0.0","maintainers":[{"name":"aminnausin","email":"aminnausin@gmail.com"}],"dist":{"shasum":"242f80ec7e0ca8c8f91f4ba438b7be53297a996b","tarball":"https://registry.npmjs.org/@aminnausin/am-lyrics/-/am-lyrics-1.0.0.tgz","fileCount":17,"integrity":"sha512-x4wtTXEww8MR1RH7jpc3Pi5iq7XsJGnaAtMMTJ4jFziBEB21N5XV0oy+QayvsGXXmY4SmxdEesr6b+6T4sL1Vg==","signatures":[{"sig":"MEQCIEZ8qt3FLi3fapmPMyXCPbDtnbJA6oN3HrI7HkwvTmmwAiAlV8geiCWg1IHzGSHy6tnzbvGtq1IDQQLsAUuWnXo6Ug==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1779299},"main":"dist/src/index.js","type":"module","types":"dist/src/index.d.ts","module":"dist/src/index.js","exports":{".":"./dist/src/index.js","./react":"./dist/src/react.js","./am-lyrics.js":"./dist/src/am-lyrics.js"},"gitHead":"72f0978ba2f6decfb3309e647cd8df724f506372","scripts":{"lint":"eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore","build":"rimraf dist && rollup -c rollup.config.js && rm ./dist/tsconfig.tsbuildinfo","start":"concurrently -k -r \"rollup -c --watch\" \"web-dev-server\"","format":"eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore","analyze":"cem analyze --litelement","prepublishOnly":"npm run analyze -- --exclude dist && npm run build"},"_npmUser":{"name":"aminnausin","email":"aminnausin@gmail.com"},"prettier":{"arrowParens":"avoid","singleQuote":true},"_npmVersion":"11.6.2","description":"Web Component for Apple Music lyrics. Follows open-wc recommendations.","directories":{},"lint-staged":{"*.ts":["eslint --fix","prettier --write"]},"_nodeVersion":"24.12.0","dependencies":{"lit":"^3.1.4","@babel/runtime":"^7.27.6"},"eslintConfig":{"rules":{"no-unused-vars":"off","import/extensions":["error","always",{"ignorePackages":true}],"import/no-unresolved":"off","@typescript-eslint/no-unused-vars":1,"import/no-extraneous-dependencies":["error",{"packageDir":"./","devDependencies":true,"peerDependencies":false,"optionalDependencies":false}]},"parser":"@typescript-eslint/parser","extends":["@open-wc","prettier"],"plugins":["@typescript-eslint"],"overrides":[{"files":["*.js"],"rules":{"import/no-extraneous-dependencies":"off"}}]},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"customElements":"custom-elements.json","devDependencies":{"husky":"^9.0.11","react":"^19.1.0","tslib":"^2.8.1","eslint":"^8.57.0","rimraf":"^6.0.1","rollup":"^4.44.2","prettier":"^3.3.2","deepmerge":"^4.3.1","@lit/react":"^1.0.7","typescript":"^5.5.3","@babel/core":"^7.28.0","lint-staged":"^15.2.7","@types/mocha":"^10.0.7","concurrently":"^8.2.2","es-dev-server":"^2.1.0","@web/dev-server":"^0.4.6","@open-wc/testing":"^4.0.0","@babel/preset-env":"^7.28.0","@rollup/plugin-babel":"^6.0.4","@storybook/addon-a11y":"^8.6.14","@open-wc/eslint-config":"^12.0.3","@storybook/addon-links":"^8.6.14","@web/storybook-builder":"^0.1.16","eslint-config-prettier":"^9.1.0","@babel/preset-typescript":"^7.27.1","@rollup/plugin-typescript":"^12.1.4","@storybook/web-components":"^8.6.14","@typescript-eslint/parser":"^7.16.0","@storybook/addon-essentials":"^8.6.14","@babel/plugin-transform-runtime":"^7.28.0","@typescript-eslint/eslint-plugin":"^7.16.0","@custom-elements-manifest/analyzer":"^0.10.3","@babel/plugin-proposal-class-properties":"^7.18.6","@web/storybook-framework-web-components":"^0.1.2"},"peerDependencies":{"react":">=17.0.0","@lit/react":"^1.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"@lit/react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/am-lyrics_1.0.0_1787938284188_0.22276381070259","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@aminnausin/am-lyrics","version":"1.0.1","keywords":["lit","react","web-component","lyrics"],"author":{"name":"aminnausin"},"license":"MPL-2.0","_id":"@aminnausin/am-lyrics@1.0.1","maintainers":[{"name":"aminnausin","email":"aminnausin@gmail.com"}],"dist":{"shasum":"ecd911f9fc511657321d15a922c9ca0a99a2ce21","tarball":"https://registry.npmjs.org/@aminnausin/am-lyrics/-/am-lyrics-1.0.1.tgz","fileCount":17,"integrity":"sha512-271Nly4c8SjL8BqVTsE/jR+7YJyHxa7YSDASM7/je8WVQqufgp8gmjdfSLlb3voW752A9fiaasTj1LdL/3DhaQ==","signatures":[{"sig":"MEUCIHWTNjVUWutYS6I3ZjlJqeLUSmZKlkHda/KIdxbSNTRvAiEA38rHARmwUf0C0sQdVrD4OJKfCmmMz+fS0ta+WOQkDbY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIFmgo9HyLlf60gr3EE5fB0Gg9iLvjpXFdTU7vGUjd8jVAiA/VCQJtmSOP9UYydh36lQUyxeupRgIE1ABjPj4TK2S4Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1780140},"main":"dist/src/index.js","type":"module","types":"dist/src/index.d.ts","module":"dist/src/index.js","exports":{".":"./dist/src/index.js","./react":"./dist/src/react.js","./am-lyrics.js":"./dist/src/am-lyrics.js"},"gitHead":"c10a38b9169862d8be78ce92c5344d403ced6983","scripts":{"lint":"eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore","build":"rimraf dist && rollup -c rollup.config.js && rm ./dist/tsconfig.tsbuildinfo","start":"concurrently -k -r \"rollup -c --watch\" \"web-dev-server\"","format":"eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore","analyze":"cem analyze --litelement","prepublishOnly":"npm run analyze -- --exclude dist && npm run build"},"_npmUser":{"name":"aminnausin","email":"aminnausin@gmail.com"},"prettier":{"arrowParens":"avoid","singleQuote":true},"_npmVersion":"11.6.2","description":"Web Component for Apple Music lyrics. Follows open-wc recommendations.","directories":{},"lint-staged":{"*.ts":["eslint --fix","prettier --write"]},"_nodeVersion":"24.12.0","dependencies":{"lit":"^3.1.4","@babel/runtime":"^7.27.6"},"eslintConfig":{"rules":{"no-unused-vars":"off","import/extensions":["error","always",{"ignorePackages":true}],"import/no-unresolved":"off","@typescript-eslint/no-unused-vars":1,"import/no-extraneous-dependencies":["error",{"packageDir":"./","devDependencies":true,"peerDependencies":false,"optionalDependencies":false}]},"parser":"@typescript-eslint/parser","extends":["@open-wc","prettier"],"plugins":["@typescript-eslint"],"overrides":[{"files":["*.js"],"rules":{"import/no-extraneous-dependencies":"off"}}]},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"customElements":"custom-elements.json","devDependencies":{"husky":"^9.0.11","react":"^19.1.0","tslib":"^2.8.1","eslint":"^8.57.0","rimraf":"^6.0.1","rollup":"^4.44.2","prettier":"^3.3.2","deepmerge":"^4.3.1","@lit/react":"^1.0.7","typescript":"^5.5.3","@babel/core":"^7.28.0","lint-staged":"^15.2.7","@types/mocha":"^10.0.7","concurrently":"^8.2.2","@web/dev-server":"^0.4.6","@open-wc/testing":"^4.0.0","@babel/preset-env":"^7.28.0","@rollup/plugin-babel":"^6.0.4","@storybook/addon-a11y":"^8.6.14","@open-wc/eslint-config":"^12.0.3","@storybook/addon-links":"^8.6.14","@web/storybook-builder":"^0.1.16","eslint-config-prettier":"^9.1.0","@babel/preset-typescript":"^7.27.1","@rollup/plugin-typescript":"^12.1.4","@storybook/web-components":"^8.6.14","@typescript-eslint/parser":"^7.16.0","@storybook/addon-essentials":"^8.6.14","@babel/plugin-transform-runtime":"^7.28.0","@typescript-eslint/eslint-plugin":"^7.16.0","@custom-elements-manifest/analyzer":"^0.10.3","@babel/plugin-proposal-class-properties":"^7.18.6","@web/storybook-framework-web-components":"^0.1.2"},"peerDependencies":{"react":">=17.0.0","@lit/react":"^1.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"@lit/react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/am-lyrics_1.0.1_1787940877279_0.17622414630160144","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@aminnausin/am-lyrics","version":"1.0.2","keywords":["lit","react","web-component","lyrics"],"author":{"name":"aminnausin"},"license":"MPL-2.0","_id":"@aminnausin/am-lyrics@1.0.2","maintainers":[{"name":"aminnausin","email":"aminnausin@gmail.com"}],"dist":{"shasum":"b2116bb7b51c86f0972b13ecf777cdafb4ea9e5c","tarball":"https://registry.npmjs.org/@aminnausin/am-lyrics/-/am-lyrics-1.0.2.tgz","fileCount":17,"integrity":"sha512-DOZQL4rFrCv9AF5AcYyIu0NaAzrCwYn6mXtKLgONNC0Krjz+kWhz/YV/sEWZKlPqC/afE30lOr5M5UHgRd35oA==","signatures":[{"sig":"MEQCIAIag0EFKWWeT171aM+zEHtx+xDCfP0agXzA5D/tnx8DAiB6MuviZWt0N7MChAca03YzoXvtby56eRt7+gdH+8852Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQDBT/idme27TJsD5sO73XBKTGte5rMbPyf9dUGrshiifwIgFABYAupE9daUMOFsPHj2MXovm5l+vMwz+rUuXQBOTKA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1780072},"main":"dist/src/index.js","type":"module","types":"dist/src/index.d.ts","module":"dist/src/index.js","exports":{".":"./dist/src/index.js","./react":"./dist/src/react.js","./am-lyrics.js":"./dist/src/am-lyrics.js"},"gitHead":"be99c8abf23adaac0ef5ab7b741ff30e3cea6f16","scripts":{"lint":"eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore","build":"rimraf dist && rollup -c rollup.config.js && rm ./dist/tsconfig.tsbuildinfo","start":"concurrently -k -r \"rollup -c --watch\" \"web-dev-server\"","format":"eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore","analyze":"cem analyze --litelement","prepublishOnly":"npm run analyze -- --exclude dist && npm run build"},"_npmUser":{"name":"aminnausin","email":"aminnausin@gmail.com"},"prettier":{"arrowParens":"avoid","singleQuote":true},"_npmVersion":"11.6.2","description":"Web Component for Apple Music lyrics. Follows open-wc recommendations.","directories":{},"lint-staged":{"*.ts":["eslint --fix","prettier --write"]},"_nodeVersion":"24.12.0","dependencies":{"lit":"^3.1.4","@babel/runtime":"^7.27.6"},"eslintConfig":{"rules":{"no-unused-vars":"off","import/extensions":["error","always",{"ignorePackages":true}],"import/no-unresolved":"off","@typescript-eslint/no-unused-vars":1,"import/no-extraneous-dependencies":["error",{"packageDir":"./","devDependencies":true,"peerDependencies":false,"optionalDependencies":false}]},"parser":"@typescript-eslint/parser","extends":["@open-wc","prettier"],"plugins":["@typescript-eslint"],"overrides":[{"files":["*.js"],"rules":{"import/no-extraneous-dependencies":"off"}}]},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"customElements":"custom-elements.json","devDependencies":{"husky":"^9.0.11","react":"^19.1.0","tslib":"^2.8.1","eslint":"^8.57.0","rimraf":"^6.0.1","rollup":"^4.44.2","prettier":"^3.3.2","deepmerge":"^4.3.1","@lit/react":"^1.0.7","typescript":"^5.5.3","@babel/core":"^7.28.0","lint-staged":"^15.2.7","@types/mocha":"^10.0.7","concurrently":"^8.2.2","@web/dev-server":"^0.4.6","@open-wc/testing":"^4.0.0","@babel/preset-env":"^7.28.0","@rollup/plugin-babel":"^6.0.4","@storybook/addon-a11y":"^8.6.14","@open-wc/eslint-config":"^12.0.3","@storybook/addon-links":"^8.6.14","@web/storybook-builder":"^0.1.16","eslint-config-prettier":"^9.1.0","@babel/preset-typescript":"^7.27.1","@rollup/plugin-typescript":"^12.1.4","@storybook/web-components":"^8.6.14","@typescript-eslint/parser":"^7.16.0","@storybook/addon-essentials":"^8.6.14","@babel/plugin-transform-runtime":"^7.28.0","@typescript-eslint/eslint-plugin":"^7.16.0","@custom-elements-manifest/analyzer":"^0.10.3","@babel/plugin-proposal-class-properties":"^7.18.6","@web/storybook-framework-web-components":"^0.1.2"},"peerDependencies":{"react":">=17.0.0","@lit/react":"^1.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"@lit/react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/am-lyrics_1.0.2_1787941260439_0.5795411456012709","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"_id":"@aminnausin/am-lyrics@1.0.3","dist":{"shasum":"fef0707d85e451687483948dec9e55d0cceb2f87","tarball":"https://registry.npmjs.org/@aminnausin/am-lyrics/-/am-lyrics-1.0.3.tgz","fileCount":17,"integrity":"sha512-3lG5u3DP/KOK4gD1JaZeb2oLQx1fKXcKQegepOnubPbILImib/NraneQKT0Q2r9FyU6Q2Cs2ctJ4/mQDyy4eRA==","signatures":[{"sig":"MEUCIQCFr/pmi3+zztAQldceZ8vQX+0e735fXp05DBgxx4+IowIgG28JOouyvDIpYGvNP+h5PwOxgb2gAc90b/B20P/XlvM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEnjBCJDh+qlc9OPbeP7cRphjYcG5vDHqZ8RKQEJo9wfAiEA5TU4ylbX6eaHbLOBDVktxs1zbtl9qA105qmLAiT00U0="}],"unpackedSize":1701300},"main":"dist/src/index.js","name":"@aminnausin/am-lyrics","type":"module","types":"dist/src/index.d.ts","author":{"name":"aminnausin"},"module":"dist/src/index.js","exports":{".":"./dist/src/index.js","./react":"./dist/src/react.js","./am-lyrics.js":"./dist/src/am-lyrics.js"},"gitHead":"149caea9e1d307b4515f682ccb885fab82876d4f","license":"MPL-2.0","scripts":{"lint":"eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore","build":"rimraf dist && rollup -c rollup.config.js && rm ./dist/tsconfig.tsbuildinfo","start":"concurrently -k -r \"rollup -c --watch\" \"web-dev-server\"","format":"eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore","analyze":"cem analyze --litelement","prepublishOnly":"npm run analyze -- --exclude dist && npm run build"},"version":"1.0.3","_npmUser":{"name":"aminnausin","email":"aminnausin@gmail.com"},"keywords":["lit","react","web-component","lyrics"],"prettier":{"arrowParens":"avoid","singleQuote":true},"_npmVersion":"11.6.2","description":"Web Component for Apple Music lyrics. Follows open-wc recommendations.","directories":{},"lint-staged":{"*.ts":["eslint --fix","prettier --write"]},"maintainers":[{"name":"aminnausin","email":"aminnausin@gmail.com"}],"_nodeVersion":"24.12.0","dependencies":{"lit":"^3.1.4","@babel/runtime":"^7.27.6"},"eslintConfig":{"rules":{"no-unused-vars":"off","import/extensions":["error","always",{"ignorePackages":true}],"import/no-unresolved":"off","@typescript-eslint/no-unused-vars":1,"import/no-extraneous-dependencies":["error",{"packageDir":"./","devDependencies":true,"peerDependencies":false,"optionalDependencies":false}]},"parser":"@typescript-eslint/parser","extends":["@open-wc","prettier"],"plugins":["@typescript-eslint"],"overrides":[{"files":["*.js"],"rules":{"import/no-extraneous-dependencies":"off"}}]},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"customElements":"custom-elements.json","devDependencies":{"husky":"^9.0.11","react":"^19.1.0","tslib":"^2.8.1","eslint":"^8.57.0","rimraf":"^6.0.1","rollup":"^4.44.2","prettier":"^3.3.2","deepmerge":"^4.3.1","@lit/react":"^1.0.7","typescript":"^5.5.3","@babel/core":"^7.28.0","lint-staged":"^15.2.7","@types/mocha":"^10.0.7","concurrently":"^8.2.2","lightningcss":"^1.30.1","@web/dev-server":"^0.4.6","@open-wc/testing":"^4.0.0","@babel/preset-env":"^7.28.0","@rollup/plugin-babel":"^6.0.4","@storybook/addon-a11y":"^8.6.14","@open-wc/eslint-config":"^12.0.3","@storybook/addon-links":"^8.6.14","@web/storybook-builder":"^0.1.16","eslint-config-prettier":"^9.1.0","@babel/preset-typescript":"^7.27.1","@rollup/plugin-typescript":"^12.1.4","@storybook/web-components":"^8.6.14","@typescript-eslint/parser":"^7.16.0","@storybook/addon-essentials":"^8.6.14","@babel/plugin-transform-runtime":"^7.28.0","@typescript-eslint/eslint-plugin":"^7.16.0","@custom-elements-manifest/analyzer":"^0.10.3","babel-plugin-template-html-minifier":"^4.1.0","@babel/plugin-proposal-class-properties":"^7.18.6","@web/storybook-framework-web-components":"^0.1.2"},"peerDependencies":{"react":">=17.0.0","@lit/react":"^1.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"@lit/react":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/am-lyrics_1.0.3_1790029831571_0.49580832116496953"}}},"time":{"created":"2026-08-28T17:31:23.946Z","modified":"2026-09-21T22:30:31.851Z","1.0.0":"2026-08-28T17:31:24.343Z","1.0.1":"2026-08-28T18:14:37.369Z","1.0.2":"2026-08-28T18:21:00.542Z","1.0.3":"2026-09-21T22:30:31.695Z"},"author":{"name":"aminnausin"},"license":"MPL-2.0","keywords":["lit","react","web-component","lyrics"],"description":"Web Component for Apple Music lyrics. Follows open-wc recommendations.","maintainers":[{"name":"aminnausin","email":"aminnausin@gmail.com"}],"readme":"# \\<am-lyrics>\r\n\r\n[![npm](https://img.shields.io/npm/v/@aminnausin/am-lyrics.svg)](https://www.npmjs.com/package/@aminnausin/am-lyrics) [![npm](https://img.shields.io/npm/dt/@aminnausin/am-lyrics.svg)](https://www.npmjs.com/package/@aminnausin/am-lyrics) [![](https://data.jsdelivr.com/v1/package/npm/@aminnausin/am-lyrics/badge)](https://www.jsdelivr.com/package/npm/@aminnausin/am-lyrics)\r\n\r\nThis project is a modified fork of binimum/am-lyrics by uimaxbai.\r\n\r\n![Running on MediaServer in fullscreen](./demo.png)\r\n\r\n<p align=\"center\">Running on <a href=\"https://github.com/aminnausin/mediaserver\">MediaServer</a> in fullscreen.</p>\r\n\r\nThis webcomponent follows the [open-wc](https://github.com/open-wc/open-wc) recommendation.\r\n\r\nThis web component is my take on Apple Music's word by word lyrics, utilises various APIs to fetch lyrics and the animations are inspired by [YouLy+](https://github.com/ibratabian17/YouLyPlus), [Apple Music-like Lyrics](https://github.com/amll-dev/applemusic-like-lyrics) and [LyricsBlossom](https://lyricsblossom.theoscarshen.com/)'s Swift reference. It's designed to be faithful while also being lightweight and able to run on all major browsers and many mid-to-high end devices smoothly. Because it's a Web Component, it's also really easy to use!\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install @aminnausin/am-lyrics\r\n```\r\n\r\n## Usage\r\n\r\n```html\r\n<am-lyrics\r\n  song-title=\"Uptown Funk\"\r\n  song-artist=\"Mark Ronson\"\r\n  song-album=\"Uptown Special\"\r\n  song-duration=\"269000\"\r\n  query=\"Uptown Funk Mark Ronson\"\r\n  current-time=\"0\"\r\n  duration=\"\"\r\n  highlight-color=\"#f00\"\r\n  font-family=\"'Inter', Arial, sans-serif\"\r\n  autoscroll\r\n  interpolate\r\n></am-lyrics>\r\n```\r\n\r\n## Properties & Attributes\r\n\r\n| Property/Attribute       | Type      | Default     | Description                                                                                    |\r\n| ------------------------ | --------- | ----------- | ---------------------------------------------------------------------------------------------- |\r\n| `query`                  | `string`  | `undefined` | Search phrase for lrc.red, with LyricsPlus catalog fallback |\r\n| `music-id`               | `string`  | `undefined` | Specific Apple Music song ID (served through the backup Apple endpoint)                        |\r\n| `isrc`                   | `string`  | `undefined` | ISRC code for direct lrc.red TTML lookup and provider matching |\r\n| `ttml`                   | `string`  | `undefined` | A string of TTML formatted lyrics to be rendered directly (bypasses all external requests)     |\r\n| `lrc`                   | `string`  | `undefined` | A string of LRC formatted lyrics to be rendered directly (Is used as the default fallback if ttml fails)     |\r\n| `song-title`             | `string`  | `undefined` | Track title for lrc.red and fallback providers |\r\n| `song-artist`            | `string`  | `undefined` | Artist name for lrc.red and fallback providers |\r\n| `song-album`             | `string`  | `undefined` | Optional album name for provider matching |\r\n| `song-duration`          | `number`  | `undefined` | Optional song duration in milliseconds sent to LyricsPlus                                      |\r\n| `current-time`           | `number`  | `0`         | Current playback time in milliseconds                                                          |\r\n| `duration`               | `number`  | `undefined` | Playback timer duration in milliseconds. **Set to `-1` to reset/stop playback**                |\r\n| `highlight-color`        | `string`  | `\"#fff\"`    | Color for highlighted/active lyrics                                                            |\r\n| ~~`hide-source-footer`~~ | `boolean` | `false`     | Hide/show the source attribution footer                                                        |\r\n| `font-family`            | `string`  | `undefined` | Custom font family for lyrics                                                                  |\r\n| `autoscroll`             | `boolean` | `true`      | Enable automatic scrolling to active lyrics                                                    |\r\n| `interpolate`            | `boolean` | `true`      | Enable smooth word-by-word highlighting animation                                              |\r\n| `allowed-sources`        | `string`  | `undefined` | Comma-separated list of optional lyric sources to query: `lrcred`, `bini`, `unison`, `lyplus`, and `genius`. LRCLIB is always used as the final fallback and is not affected. |\r\n\r\n## CSS Custom Properties (CSS Variables)\r\n\r\nYou can customize the appearance using CSS custom properties:\r\n\r\n```css\r\nam-lyrics {\r\n  /* Highlight color for active lyrics */\r\n  --am-lyrics-highlight-color: #007aff;\r\n\r\n  /* Alternative highlight color (fallback) */\r\n  --highlight-color: #000;\r\n\r\n  /* Swift-derived layout and motion defaults */\r\n  --lyplus-font-size-base: 34px;\r\n  --am-lyrics-line-height: 1.2;\r\n  --am-lyrics-line-spacing: 25px;\r\n  --am-lyrics-background-vocal-spacing: 15px;\r\n  --am-lyrics-background-vocal-font-size: 0.65em;\r\n  --am-lyrics-background-vocal-stack-shift: 7.5px;\r\n  --am-lyrics-background-vocal-max-height: 8em;\r\n  --am-lyrics-background-vocal-exit-duration: 450ms;\r\n  --am-lyrics-instrumental-height: 40px;\r\n  --am-lyrics-instrumental-spacing: 16px;\r\n  --am-lyrics-instrumental-enter-duration: 400ms;\r\n  --am-lyrics-instrumental-collapse-duration: 500ms;\r\n  --am-lyrics-instrumental-exit-duration: 350ms;\r\n  --am-lyrics-instrumental-exit-scale: 0;\r\n  --am-lyrics-inactive-scale: 0.98;\r\n  --am-lyrics-background-vocal-scale: 0.9;\r\n  --am-lyrics-touch-scale: 0.96;\r\n  --am-lyrics-highlight-radius: 16px;\r\n  --am-lyrics-highlight-surface: rgba(255, 255, 255, 0.08);\r\n  --am-lyrics-progression-feather: 30px;\r\n  --am-lyrics-glow-radius: 5px;\r\n  --am-lyrics-character-rise-peak: -1.25px;\r\n  --am-lyrics-inline-padding: 20px;\r\n  --lyrics-scroll-padding-top: 12%;\r\n  --am-lyrics-compact-font-size: 28px;\r\n  --am-lyrics-compact-background-vocal-font-size: 0.857em;\r\n  --am-lyrics-compact-line-spacing: 20px;\r\n  --am-lyrics-compact-selected-position: 18%;\r\n  --am-lyrics-wide-font-size: 48px;\r\n  --am-lyrics-wide-background-vocal-font-size: 0.667em;\r\n  --am-lyrics-wide-line-spacing: 32px;\r\n  --am-lyrics-wide-selected-position: 20%;\r\n}\r\n```\r\n\r\nThe component automatically uses a 28px compact preset below 520px and a 48px\r\nwide preset from 900px. The compact and wide variables above override those\r\nresponsive values. CSS variables take precedence over the properties above.\r\n\r\n## Lyrics matching\r\n\r\n1. lrc.red is the highest-priority provider. An `isrc` requests `/s/{isrc}.ttml` directly. Otherwise, `song-title` and `song-artist` use `/match.json`, including optional album and duration (converted from milliseconds to seconds). A standalone `query` uses `/search.json?q=...`. Match and search results supply the ISRC for the TTML download. If a direct ISRC lookup fails, the component tries the supplied track/artist or query.\r\n2. If lrc.red cannot serve usable lyrics, the component continues through BiniLyrics and the existing Unison, LyricsPlus, LRCLIB, and Genius fallbacks as metadata and synchronization quality allow. lrc.red does not accept Apple Music IDs directly.\r\n3. The footer shows the active provider, including “lrc.red”, and lets you load alternative sources. lrc.red uses the same TTML parser as BiniLyrics, and TTML downloads preserve the original file, including its timing, background vocals, and metadata. LRC, TTML and plain-text exports remain available.\r\n\r\nProviding as much song metadata as possible generally improves matching accuracy. If you already have the lyrics, you can bypass external lyric requests entirely by supplying `ttml` or `lrc` directly\r\n\r\n## Events\r\n\r\n### `line-click`\r\n\r\nFired when a user clicks on a lyrics line.\r\n\r\n```javascript\r\namLyrics.addEventListener('line-click', event => {\r\n  console.log('Seek to:', event.detail.timestamp); // timestamp in milliseconds\r\n});\r\n```\r\n\r\n### For React Users\r\n\r\nFirst, ensure you have `react` and `@lit/react` installed in your project.\r\n\r\n```bash\r\nnpm install react @lit/react # Very important or errors will arise\r\n```\r\n\r\nThen, you can import the `AmLyrics` component from `am-lyrics/react` and use it in your components.\r\n\r\n```jsx\r\n'use client'; // VERY IMPORTANT!!!\r\n\r\nimport React, { useState, useCallback, useRef, useEffect } from 'react';\r\nimport { AmLyrics } from '@aminnausin/am-lyrics/react';\r\n\r\nexport default function App() {\r\n  const [currentTime, setCurrentTime] = useState(0);\r\n  const audioRef = useRef<HTMLAudioElement>(null);\r\n\r\n  // Sync audio player time with the component\r\n  useEffect(() => {\r\n    const audio = audioRef.current;\r\n    if (!audio) return;\r\n\r\n    let animationFrameId: number;\r\n\r\n    const updateCurrentTime = () => {\r\n      setCurrentTime(audio.currentTime * 1000);\r\n      animationFrameId = requestAnimationFrame(updateCurrentTime); // Use requestAnimationFrame to prevent choppy scrolling\r\n    };\r\n\r\n    const handlePlay = () => {\r\n      animationFrameId = requestAnimationFrame(updateCurrentTime);\r\n    };\r\n\r\n    const handlePause = () => {\r\n      cancelAnimationFrame(animationFrameId);\r\n    };\r\n\r\n    const handleTimeUpdate = () => {\r\n      setCurrentTime(audio.currentTime * 1000); // Convert to milliseconds\r\n    };\r\n\r\n    audio.addEventListener('play', handlePlay);\r\n    audio.addEventListener('pause', handlePause);\r\n    audio.addEventListener('timeupdate', handleTimeUpdate);\r\n\r\n    return () => {\r\n      cancelAnimationFrame(animationFrameId);\r\n      audio.removeEventListener('play', handlePlay);\r\n      audio.removeEventListener('pause', handlePause);\r\n      audio.removeEventListener('timeupdate', handleTimeUpdate);\r\n    };\r\n  }, []);\r\n\r\n  // Handle line clicks to seek the audio\r\n  const handleLineClick = useCallback((event: Event) => {\r\n    const customEvent = event as CustomEvent<{ timestamp: number }>;\r\n    const audio = audioRef.current;\r\n    if (audio) {\r\n      audio.currentTime = customEvent.detail.timestamp / 1000; // Convert to seconds\r\n      audio.play();\r\n    }\r\n  }, []);\r\n\r\n  return (\r\n    <div>\r\n      <audio ref={audioRef} src=\"/uptown_funk.flac\" controls />\r\n        <AmLyrics\r\n          songTitle=\"Uptown Funk\"\r\n          songArtist=\"Mark Ronson\"\r\n          query=\"Uptown Funk Mark Ronson\"\r\n          currentTime={currentTime}\r\n          onLineClick={handleLineClick}\r\n          autoScroll\r\n          highlightColor='#fff'\r\n        />\r\n    </div>\r\n  );\r\n}\r\n```\r\n\r\nUsing NextJS? See [`next.md`](./next.md).\r\n\r\n### SSR\r\n\r\nLit web components only partially support SSR, so this package is very volatile in SSR. Either:\r\n\r\n- Use the CDN solution and place it direct into your HTML or\r\n- Disable SSR on the page with lyrics.\r\n\r\nUsing NextJS? See [`next.md`](./next.md).\r\n\r\nThe timer needs to be defined by yourself. For example:\r\n\r\n### Just play the lyrics\r\n\r\n```html\r\n<script>\r\n  let animationFrameId;\r\n  let songStartTime = 0;\r\n  let systemStartTime = 0;\r\n\r\n  function stopAnimation() {\r\n    if (animationFrameId) {\r\n      cancelAnimationFrame(animationFrameId);\r\n      animationFrameId = null;\r\n    }\r\n  }\r\n\r\n  function animate() {\r\n    const amLyrics = document.querySelector('am-lyrics');\r\n    if (!amLyrics) return;\r\n\r\n    const elapsedTime = Date.now() - systemStartTime;\r\n    amLyrics.currentTime = songStartTime + elapsedTime;\r\n\r\n    animationFrameId = requestAnimationFrame(animate);\r\n  }\r\n\r\n  function startPlayback() {\r\n    stopAnimation();\r\n    songStartTime = 0;\r\n    systemStartTime = Date.now(); // Use dates instead of setInterval() for more accuracy\r\n    animate();\r\n  }\r\n\r\n  function handleLineClick(e) {\r\n    stopAnimation();\r\n    songStartTime = e.detail.timestamp;\r\n    systemStartTime = Date.now();\r\n    animate();\r\n  }\r\n\r\n  function handleSearch() {\r\n    const searchInput = document.querySelector('#search-input');\r\n    const amLyrics = document.querySelector('am-lyrics');\r\n    if (searchInput && amLyrics) {\r\n      // Expect \"Title - Artist\" in the search field for optimal LyricsPlus results\r\n      const userInput = searchInput.value.trim();\r\n      const [titlePart = '', artistPart = ''] = userInput\r\n        .split(' - ')\r\n        .map(part => part.trim());\r\n\r\n      amLyrics.songTitle = titlePart || userInput;\r\n      amLyrics.songArtist = artistPart;\r\n      amLyrics.query = userInput;\r\n      amLyrics.isrc = '';\r\n      amLyrics.musicId = '';\r\n    }\r\n  }\r\n\r\n  document.addEventListener('DOMContentLoaded', () => {\r\n    const amLyrics = document.querySelector('am-lyrics');\r\n    const searchButton = document.querySelector('#search-button');\r\n    const startButton = document.querySelector('#start-button');\r\n\r\n    if (amLyrics) {\r\n      amLyrics.addEventListener('line-click', handleLineClick);\r\n    }\r\n\r\n    if (searchButton) {\r\n      searchButton.addEventListener('click', handleSearch);\r\n    }\r\n\r\n    if (startButton) {\r\n      startButton.addEventListener('click', startPlayback);\r\n    }\r\n  });\r\n</script>\r\n```\r\n\r\n### With an `<audio>` element\r\n\r\nYou can synchronize the lyrics with an HTML `<audio>` element.\r\n\r\n```html\r\n<audio id=\"audio-player\" src=\"path/to/your/song.mp3\" controls></audio>\r\n<am-lyrics\r\n  song-title=\"Uptown Funk\"\r\n  song-artist=\"Mark Ronson\"\r\n  query=\"Uptown Funk Mark Ronson\"\r\n></am-lyrics>\r\n\r\n<script>\r\n  document.addEventListener('DOMContentLoaded', () => {\r\n    const amLyrics = document.querySelector('am-lyrics');\r\n    const audioPlayer = document.querySelector('#audio-player');\r\n\r\n    if (amLyrics && audioPlayer) {\r\n      // Update lyrics time when audio time updates\r\n      audioPlayer.addEventListener('timeupdate', () => {\r\n        // The component expects time in milliseconds\r\n        amLyrics.currentTime = audioPlayer.currentTime * 1000;\r\n      });\r\n\r\n      // Seek audio when a lyric line is clicked\r\n      amLyrics.addEventListener('line-click', e => {\r\n        // The event detail contains the timestamp in milliseconds\r\n        audioPlayer.currentTime = e.detail.timestamp / 1000;\r\n        audioPlayer.play();\r\n      });\r\n    }\r\n  });\r\n</script>\r\n```\r\n\r\nSee [`demo/index.html`](./demo/index.html) for a functional demo.\r\n\r\n## Development\r\n\r\n### Dependencies\r\n\r\n```bash\r\nnpm i # may take ages\r\nyarn install\r\nbun i\r\n```\r\n\r\n### Local developer demo with `web-dev-server`\r\n\r\n```bash\r\nnpm start\r\n```\r\n\r\n### Linting and formatting\r\n\r\nTo scan the project for linting and formatting errors, run\r\n\r\n```bash\r\nnpm run lint\r\n```\r\n\r\nTo automatically fix linting and formatting errors, run\r\n\r\n```bash\r\nnpm run format\r\n```\r\n","readmeFilename":"README.md"}