[
  {
    "path": ".github/workflows/submit.yml",
    "content": "name: \"Submit to Web Store\"\non:\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Cache pnpm modules\n        uses: actions/cache@v3\n        with:\n          path: ~/.pnpm-store\n          key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}\n          restore-keys: |\n            ${{ runner.os }}-\n      - uses: pnpm/action-setup@v2.2.4\n        with:\n          version: latest\n          run_install: true\n      - name: Use Node.js 16.x\n        uses: actions/setup-node@v3.4.1\n        with:\n          node-version: 16.x\n          cache: \"pnpm\"\n      - name: Build the extension\n        run: pnpm build\n      - name: Package the extension into a zip artifact\n        run: pnpm package\n      - name: Browser Platform Publish\n        uses: PlasmoHQ/bpp@v3\n        with:\n          keys: ${{ secrets.SUBMIT_KEYS }}\n          artifact: build/chrome-mv3-prod.zip\n"
  },
  {
    "path": ".gitignore",
    "content": "\n# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# local env files\n.env*.local\n\nout/\nbuild/\ndist/\n\n# plasmo\n.plasmo\n\n# typescript\n.tsbuildinfo\n.next"
  },
  {
    "path": ".prettierrc.mjs",
    "content": "/**\n * @type {import('prettier').Options}\n */\nexport default {\n  printWidth: 80,\n  tabWidth: 2,\n  useTabs: false,\n  semi: false,\n  singleQuote: false,\n  trailingComma: \"none\",\n  bracketSpacing: true,\n  bracketSameLine: true,\n  plugins: [\"@ianvs/prettier-plugin-sort-imports\"],\n  importOrder: [\n    \"<BUILTIN_MODULES>\", // Node.js built-in modules\n    \"<THIRD_PARTY_MODULES>\", // Imports not matched by other special words or groups.\n    \"\", // Empty line\n    \"^@plasmo/(.*)$\",\n    \"\",\n    \"^@plasmohq/(.*)$\",\n    \"\",\n    \"^~(.*)$\",\n    \"\",\n    \"^[./]\"\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0."
  },
  {
    "path": "README.md",
    "content": "<a name=\"readme-top\"></a>\n\n<div align=\"center\">\n<img src=\"assets/icon.png\" width=\"32\" >\n<h1>X Cards</h1>\n\n[English](README.md) | [中文](README_ZH.md)\n\n[![x-cards intro](https://img.youtube.com/vi/v8iQV8ZoVBk/0.jpg)](https://youtu.be/okCIZrFrTCE)\n\neasy to use x-cards in x.com\n\n[![][vercel-shield]][vercel-link]\n\n[![][share-x-shield]][share-x-link]\n[![][share-whatsapp-shield]][share-whatsapp-link]\n[![][share-reddit-shield]][share-reddit-link]\n[![][share-weibo-shield]][share-weibo-link]\n\n[![][share-linkedin-shield]][share-linkedin-link]\n\n[github-issues-link]: https://github.com/hzeyuan/x-cards/issues\n[github-contributors-shield]: https://img.shields.io/github/contributors/hzeyuan/OpenGPTS?color=c4f042&labelColor=black&style=flat-square\n[github-contributors-link]: https://github.com/hzeyuan/OpenGPTS/graphs/contributors\n[vercel-link]: https://x-cards.net\n[vercel-shield]: https://img.shields.io/website?down_message=offline&label=vercel&labelColor=black&logo=vercel&style=flat-square&up_message=online&url=https://x-cards.net\n[share-linkedin-link]: https://linkedin.com/feed\n[share-linkedin-shield]: https://img.shields.io/badge/-share%20on%20linkedin-black?labelColor=black&logo=linkedin&logoColor=white&style=flat-square\n[share-reddit-link]: https://www.reddit.com/submit?title=x-cards&url=https://github.com/hzeyuan/x-cards\n[share-reddit-shield]: https://img.shields.io/badge/-share%20on%20reddit-black?labelColor=black&logo=reddit&logoColor=white&style=flat-square\n[share-telegram-link]: https://t.me/share/url?text=x-cards&url=https://github.com/hzeyuan/x-cards\n[share-telegram-shield]: https://img.shields.io/badge/-share%20on%20telegram-black?labelColor=black&logo=telegram&logoColor=white&style=flat-square\n[share-weibo-link]: http://service.weibo.com/share/share.php?sharesource=weibo&title=x-cards\n[share-weibo-shield]: https://img.shields.io/badge/-share%20on%20weibo-black?labelColor=black&logo=sinaweibo&logoColor=white&style=flat-square\n[share-whatsapp-link]: https://api.whatsapp.com/send?text=x-cards\n[share-whatsapp-shield]: https://img.shields.io/badge/-share%20on%20whatsapp-black?labelColor=black&logo=whatsapp&logoColor=white&style=flat-square\n[share-x-link]: https://x.com/intent/tweet?hashtags=chatbot%2CchatGPT%2CopenAI&url=https://github.com/hzeyuan/x-cards\n[share-x-shield]: https://img.shields.io/badge/-share%20on%20x-black?labelColor=black&logo=x&logoColor=white&style=flat-square\n\n</div>\n\n⚡ **X Cards** Share Tweet anywhere ,any format,\n\n## Project Background\n\nX is a source of information for many platforms，So this project came into being\n\n## Changelog\n\n<details>\n<summary><strong>v0.0.3</strong></summary>\n\n* Performance Optimization: Utilized web workers for image generation, addressing issues with blank spaces on X.com after scrolling down.\n* Enhanced Padding Settings: Added the ability to adjust padding within the card for better layout control.\n* Image Quality Settings: Introduced options to set the quality of generated images for export.\n* Markdown Export: Now supports exporting content in Markdown format for easy integration into documentation or blogs.\n* Font Size Adjustment: Added support for modifying font sizes to improve readability and customization.\n* Interaction Optimization: After installing the plugin or clicking the icon, redirect to the welcome page.\n\n![Download Extension](./assets/v0.02-demo.jpg)\n\n</details>\n\n<details>\n<summary><strong>v0.0.2</strong></summary>\n\n- Added real-time preview feature, now a toast in the upper right corner allows you to observe the generated card.\n\n- Introduced customization for card background color.\n\n- Customizable card width.\n\n- Improved: Now clicking defaults to copying the image, rather than downloading the image.\n\n- Fixed the issue of unable to fetch cover image for videos.\n\n- Added support for fetching continuous posts.\n\n- Enabled dynamic addition, deletion, dragging, and management of posts.\n\n![Download Extension](./assets/v0.02-demo.jpg)\n\n</details>\n\n<details>\n<summary><strong>v0.0.1</strong></summary>\n\n- Easy to access, just a simple click away.\n- Obtain videos, images, text, likes。\n- Export in multiple formats, including JSON, Markdown, PNG, JPEG, and SVG.\n\n</details>\n\n## features\n\n- Added real-time preview feature, now a toast in the upper right corner allows you to observe the generated card.\n\n- Introduced customization for card background color.\n\n- Customizable card width.\n\n- Improved: Now clicking defaults to copying the image, rather than downloading the image.\n\n- Fixed the issue of unable to fetch cover image for videos.\n\n- Added support for fetching continuous posts.\n\n- Enabled dynamic addition, deletion, dragging, and management of posts.\n\n<br/>\n\n## How to Use\n\n### [chrome web Store](https://chromewebstore.google.com/detail/x-card/mbinooofmcjhjklihfejnkkebffceeop)\n\n## or\n\n1. Download Extension\n\n![Download Extension](./assets/install_guide/1.download.png)\n\n2. url input:chrome://extensions/ in your chrome browser, and open the developer mode\n\n3. unzip and Drag the extension file to the page\n\n![Drag the extension file to the page](./assets/install_guide/2.install.png)\n\n4. open x.com and browse the post, you will find your card button in the bottom right corner\n\n## Development Guide\n\n1. The project uses the Plasmo framework for rapid Chrome extension development.\n2. Uses Next.js for frontend development.\n3. Tailwind CSS and Shadcn as CSS frameworks.\n4. Langchain for developing agents.\n5. Deployed on Vercel.\n\nLocal development:\n\n```bash\npnpm install\n\n# Run frontend\nnpm run dev:next\n# Run plugin\nnpm run dev:plasmo\n```\n\n## Starchart\n\n[![Star History Chart](https://api.star-history.com/svg?repos=hzeyuan/x-cards&type=Date)](https://star-history.com/#hzeyuan/x-cards&Date)\n"
  },
  {
    "path": "README_ZH.md",
    "content": "<a name=\"readme-top\"></a>\n\n<div align=\"center\">\n<img src=\"assets/icon.png\" width=\"32\" >\n<h1>X Cards</h1>\n\n[English](README.md) | [中文](README_ZH.md)\n\n[![x-cards intro](https://img.youtube.com/vi/v8iQV8ZoVBk/0.jpg)](https://www.youtube.com/watch?v=v8iQV8ZoVBk)\n\n轻松的X平台上使用x-cards\n\n[![][vercel-shield]][vercel-link]\n\n[![][share-x-shield]][share-x-link]\n[![][share-whatsapp-shield]][share-whatsapp-link]\n[![][share-reddit-shield]][share-reddit-link]\n[![][share-weibo-shield]][share-weibo-link]\n\n[![][share-linkedin-shield]][share-linkedin-link]\n\n[github-issues-link]: https://github.com/hzeyuan/x-cards/issues\n[github-contributors-shield]: https://img.shields.io/github/contributors/hzeyuan/OpenGPTS?color=c4f042&labelColor=black&style=flat-square\n[github-contributors-link]: https://github.com/hzeyuan/OpenGPTS/graphs/contributors\n[vercel-link]: https://x-cards.net\n[vercel-shield]: https://img.shields.io/website?down_message=offline&label=vercel&labelColor=black&logo=vercel&style=flat-square&up_message=online&url=https://x-cards.net\n[share-linkedin-link]: https://linkedin.com/feed\n[share-linkedin-shield]: https://img.shields.io/badge/-share%20on%20linkedin-black?labelColor=black&logo=linkedin&logoColor=white&style=flat-square\n[share-reddit-link]: https://www.reddit.com/submit?title=x-cards&url=https://github.com/hzeyuan/x-cards\n[share-reddit-shield]: https://img.shields.io/badge/-share%20on%20reddit-black?labelColor=black&logo=reddit&logoColor=white&style=flat-square\n[share-telegram-link]: https://t.me/share/url?text=x-cards&url=https://github.com/hzeyuan/x-cards\n[share-telegram-shield]: https://img.shields.io/badge/-share%20on%20telegram-black?labelColor=black&logo=telegram&logoColor=white&style=flat-square\n[share-weibo-link]: http://service.weibo.com/share/share.php?sharesource=weibo&title=x-cards\n[share-weibo-shield]: https://img.shields.io/badge/-share%20on%20weibo-black?labelColor=black&logo=sinaweibo&logoColor=white&style=flat-square\n[share-whatsapp-link]: https://api.whatsapp.com/send?text=x-cards\n[share-whatsapp-shield]: https://img.shields.io/badge/-share%20on%20whatsapp-black?labelColor=black&logo=whatsapp&logoColor=white&style=flat-square\n[share-x-link]: https://x.com/intent/tweet?hashtags=chatbot%2CchatGPT%2CopenAI&url=https://github.com/hzeyuan/x-cards\n[share-x-shield]: https://img.shields.io/badge/-share%20on%20x-black?labelColor=black&logo=x&logoColor=white&style=flat-square\n\n</div>\n\n⚡ **X Cards** Share X anywhere ,any format,\n\n## 项目背景\n\nX是许多平台的信息来源，因此产生了这个项目。\n\n## Changelog\n\n<details>\n<summary><strong>v0.0.3</strong></summary>\n\n- 优化性能，使用web worker生成图片， 处理下拉后x.com空白问题。\n- 增加padding设置，调整卡片内边距。\n- 增加生成图片质量设置。\n- 增加md格式导出。\n- 支持调整字体大小。\n- 交互优化，安装插件后或者，点击icon，跳转到welcome页面。\n\n<details>\n<summary><strong>v0.0.2</strong></summary>\n\n- 新增实时预览功能，现在右上角有个toast可以观察到生成的卡片。\n- 引入了自定义卡片背景颜色。\n- 可自定义卡片宽度。\n- 改进：现在点击默认复制图片，而不是下载图片。\n- 修复了视频获取封面图的问题。\n- 增加了连续帖子获取的支持。\n- 实现了帖子的动态添加、删除、拖拽和管理。\n\n</details>\n\n<details>\n<summary><strong>v0.0.1</strong></summary>\n\n- 点击即可轻松访问。\n- 获取视频、图片、文本、点赞等。\n- 导出多种格式，包括JSON、Markdown、PNG、JPEG和SVG。\n\n</details>\n\n## features\n\n- 简单易用，只需点击一下即生成卡片。\n- 轻松获取视频、图片、文字、点赞和浏览历史。\n- 支持多种格式导出，包括JSON、Markdown、PNG、JPEG和SVG。\n- 模板功能，保存您经常使用的卡片样式。\n\n<br/>\n\n## How to Use\n\n1. 点击下载插件\n   ![Download Extension](./assets/install_guide/1.download.png)\n\n2. 浏览器输入 chrome://extensions/ 并打开开发者模式\n\n3. 解压并，拖动整个文件夹到页面，如图：\n   ![Drag the extension file to the page](./assets/install_guide/2.install.png)\n\n4. 打开x.com并浏览帖子，您将在右下角找到您的卡片按钮，参考上方视频。\n\n## 开发指南\n\n本项目使用 Plasmo 框架进行快速 Chrome 扩展开发。\n使用 Next.js 进行前端开发。\n采用 Tailwind CSS 和 Shadcn 作为 CSS 框架。\n使用 Langchain 开发智能代理。\n部署在 Vercel 平台上。\n\nLocal development:\n\n```bash\npnpm install\n\n# Run frontend\nnpm run dev:next\n# Run plugin\nnpm run dev:plasmo\n```\n\n## Starchart\n\n[![Star History Chart](https://api.star-history.com/svg?repos=hzeyuan/x-cards&type=Date)](https://star-history.com/#hzeyuan/x-cards&Date)\n"
  },
  {
    "path": "components/ui/EditableButton.tsx",
    "content": "import React, { useState, useRef } from 'react';\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\n\nconst EditableButton = ({ text }) => {\n  const [isEditing, setIsEditing] = useState(false);\n  const [value, setValue] = useState(text);\n  const inputRef = useRef(null);\n\n  const handleClick = () => {\n    if (!isEditing) {\n      setIsEditing(true);\n      setTimeout(() => inputRef.current?.focus(), 0);\n    } else {\n      setIsEditing(false);\n      // 在这里可以添加保存或提交的逻辑\n      console.log('Submitted:', value);\n    }\n  };\n\n  const handleChange = (e) => {\n    setValue(e.target.value);\n  };\n\n  const handleKeyDown = (e) => {\n    if (e.key === 'Enter') {\n      handleClick();\n    }\n  };\n\n  return (\n    <div className=\"relative inline-block\">\n      {isEditing ? (\n        <Input\n          ref={inputRef}\n          type=\"text\"\n          value={value}\n          onChange={handleChange}\n          onKeyDown={handleKeyDown}\n          className=\"pr-20\"\n        />\n      ) : (\n        <Button size=\"sm\" onClick={handleClick} variant=\"outline\">\n          {value}\n        </Button>\n      )}\n      {isEditing && (\n        <Button\n          onClick={handleClick}\n          className=\"absolute right-1 top-1/2 transform -translate-y-1/2\"\n          size=\"sm\"\n        >\n          保存\n        </Button>\n      )}\n    </div>\n  );\n};\n\nexport default EditableButton;"
  },
  {
    "path": "components/ui/accordion.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\"\nimport { ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Accordion = AccordionPrimitive.Root\n\nconst AccordionItem = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(({ className, ...props }, ref) => (\n  <AccordionPrimitive.Item\n    ref={ref}\n    // className={cn(\"border-b\", className)}\n    className={cn(\n      \"w-full bg-secondary items-center  cursor-pointer text-[13px] mb-3 px-4 py-0 rounded-md\",\n      className,\n    )}\n    {...props}\n  />\n))\nAccordionItem.displayName = \"AccordionItem\"\n\nconst AccordionTrigger = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <AccordionPrimitive.Header className=\"flex\">\n    <AccordionPrimitive.Trigger\n      ref={ref}\n      className={cn(\n        \"flex flex-1 items-center justify-between py-2 font-medium transition-all  [&[data-state=open]>svg]:rotate-180\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronDown className=\"h-4 w-4 shrink-0 transition-transform duration-200\" />\n    </AccordionPrimitive.Trigger>\n  </AccordionPrimitive.Header>\n))\nAccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName\n\nconst AccordionContent = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <AccordionPrimitive.Content\n    ref={ref}\n    className=\"overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down\"\n    {...props}\n  >\n    <div className={cn(\"pb-4 pt-0\", className)}>{children}</div>\n  </AccordionPrimitive.Content>\n))\n\nAccordionContent.displayName = AccordionPrimitive.Content.displayName\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent }\n"
  },
  {
    "path": "components/ui/alert-dialog.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\n\nconst AlertDialog = AlertDialogPrimitive.Root\n\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger\n\nconst AlertDialogPortal = AlertDialogPrimitive.Portal\n\nconst AlertDialogOverlay = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName\n\nconst AlertDialogContent = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPortal>\n    <AlertDialogOverlay />\n    <AlertDialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg\",\n        className\n      )}\n      {...props}\n    />\n  </AlertDialogPortal>\n))\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName\n\nconst AlertDialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nAlertDialogHeader.displayName = \"AlertDialogHeader\"\n\nconst AlertDialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nAlertDialogFooter.displayName = \"AlertDialogFooter\"\n\nconst AlertDialogTitle = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold\", className)}\n    {...props}\n  />\n))\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName\n\nconst AlertDialogDescription = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n))\nAlertDialogDescription.displayName =\n  AlertDialogPrimitive.Description.displayName\n\nconst AlertDialogAction = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Action>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Action\n    ref={ref}\n    className={cn(buttonVariants(), className)}\n    {...props}\n  />\n))\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName\n\nconst AlertDialogCancel = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Cancel\n    ref={ref}\n    className={cn(\n      buttonVariants({ variant: \"outline\" }),\n      \"mt-2 sm:mt-0\",\n      className\n    )}\n    {...props}\n  />\n))\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName\n\nexport {\n  AlertDialog,\n  AlertDialogPortal,\n  AlertDialogOverlay,\n  AlertDialogTrigger,\n  AlertDialogContent,\n  AlertDialogHeader,\n  AlertDialogFooter,\n  AlertDialogTitle,\n  AlertDialogDescription,\n  AlertDialogAction,\n  AlertDialogCancel,\n}\n"
  },
  {
    "path": "components/ui/badge.tsx",
    "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n  \"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n        secondary:\n          \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        destructive:\n          \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n        outline: \"text-foreground\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nexport interface BadgeProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof badgeVariants> {}\n\nfunction Badge({ className, variant, ...props }: BadgeProps) {\n  return (\n    <div className={cn(badgeVariants({ variant }), className)} {...props} />\n  )\n}\n\nexport { Badge, badgeVariants }\n"
  },
  {
    "path": "components/ui/button.tsx",
    "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n  \"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n        outline:\n          \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost: \"hover:bg-accent hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-10 px-4 py-2\",\n        sm: \"h-9 rounded-md px-3\",\n        lg: \"h-11 rounded-md px-8\",\n        icon: \"h-10 w-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : \"button\"\n    return (\n      <Comp\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "components/ui/color-picker.tsx",
    "content": "'use client';\n\nimport { forwardRef, useEffect, useMemo, useRef, useState } from 'react';\nimport { HexColorPicker } from 'react-colorful';\nimport { cn } from '@/lib/utils';\nimport type { ButtonProps } from '@/components/ui/button';\nimport { Button } from '@/components/ui/button';\n\nimport { Input } from '@/components/ui/input';\nimport { PopoverContent, PopoverTrigger, Popover } from './popover';\n\n\n\nexport function useForwardedRef<T>(ref: React.ForwardedRef<T>) {\n    const innerRef = useRef<T>(null);\n\n    useEffect(() => {\n        if (!ref) return;\n        if (typeof ref === 'function') {\n            ref(innerRef.current);\n        } else {\n            ref.current = innerRef.current;\n        }\n    });\n\n    return innerRef;\n}\n\n\ninterface ColorPickerProps {\n    value: string;\n    onChange: (value: string) => void;\n    onBlur?: () => void;\n}\n\nconst ColorPicker = forwardRef<\n    HTMLInputElement,\n    Omit<ButtonProps, 'value' | 'onChange' | 'onBlur'> & ColorPickerProps\n>(\n    (\n        { disabled, value, onChange, onBlur, name, className, ...props },\n        forwardedRef\n    ) => {\n        const ref = useForwardedRef(forwardedRef);\n        const [open, setOpen] = useState(false);\n\n        const parsedValue = useMemo(() => {\n            return value || '#FFFFFF';\n        }, [value]);\n\n        return (\n            <Popover onOpenChange={setOpen} open={open}>\n                <PopoverTrigger asChild disabled={disabled} onBlur={onBlur}>\n                    <Button\n                        {...props}\n                        className={cn('block', className)}\n                        name={name}\n                        onClick={() => {\n                            setOpen(true);\n                        }}\n                        size='icon'\n                        style={{\n                            backgroundColor: parsedValue,\n                        }}\n                        variant='outline'\n                    >\n                        <div />\n                    </Button>\n                </PopoverTrigger>\n                <PopoverContent className='w-full flex flex-col gap-y-2'>\n                    <HexColorPicker color={parsedValue} onChange={onChange} />\n                    <Input\n                        maxLength={7}\n                        onChange={(e) => {\n                            onChange(e?.currentTarget?.value);\n                        }}\n                        ref={ref}\n                        value={parsedValue}\n                    />\n                </PopoverContent>\n            </Popover>\n        );\n    }\n);\nColorPicker.displayName = 'ColorPicker';\n\nexport { ColorPicker };"
  },
  {
    "path": "components/ui/dropdown-menu.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { Check, ChevronRight, Circle } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst DropdownMenu = DropdownMenuPrimitive.Root\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal\n\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n    inset?: boolean\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      \"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto h-4 w-4\" />\n  </DropdownMenuPrimitive.SubTrigger>\n))\nDropdownMenuSubTrigger.displayName =\n  DropdownMenuPrimitive.SubTrigger.displayName\n\nconst DropdownMenuSubContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n      className\n    )}\n    {...props}\n  />\n))\nDropdownMenuSubContent.displayName =\n  DropdownMenuPrimitive.SubContent.displayName\n\nconst DropdownMenuContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <DropdownMenuPrimitive.Portal>\n    <DropdownMenuPrimitive.Content\n      ref={ref}\n      sideOffset={sideOffset}\n      className={cn(\n        \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        className\n      )}\n      {...props}\n    />\n  </DropdownMenuPrimitive.Portal>\n))\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName\n\nconst DropdownMenuItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n))\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <DropdownMenuPrimitive.CheckboxItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    checked={checked}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.CheckboxItem>\n))\nDropdownMenuCheckboxItem.displayName =\n  DropdownMenuPrimitive.CheckboxItem.displayName\n\nconst DropdownMenuRadioItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.RadioItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <Circle className=\"h-2 w-2 fill-current\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.RadioItem>\n))\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName\n\nconst DropdownMenuLabel = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Label\n    ref={ref}\n    className={cn(\n      \"px-2 py-1.5 text-sm font-semibold\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n))\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName\n\nconst DropdownMenuSeparator = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n    {...props}\n  />\n))\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName\n\nconst DropdownMenuShortcut = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\"ml-auto text-xs tracking-widest opacity-60\", className)}\n      {...props}\n    />\n  )\n}\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\"\n\nexport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuGroup,\n  DropdownMenuPortal,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuRadioGroup,\n}\n"
  },
  {
    "path": "components/ui/input.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n  extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n  ({ className, type, ...props }, ref) => {\n    return (\n      <input\n        type={type}\n        className={cn(\n          \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n"
  },
  {
    "path": "components/ui/popover.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Popover = PopoverPrimitive.Root\n\nconst PopoverTrigger = PopoverPrimitive.Trigger\n\nconst PopoverContent = React.forwardRef<\n  React.ElementRef<typeof PopoverPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = 4, ...props }, ref) => (\n  <PopoverPrimitive.Portal>\n    <PopoverPrimitive.Content\n      ref={ref}\n      align={align}\n      sideOffset={sideOffset}\n      className={cn(\n        \"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        className\n      )}\n      {...props}\n    />\n  </PopoverPrimitive.Portal>\n))\nPopoverContent.displayName = PopoverPrimitive.Content.displayName\n\nexport { Popover, PopoverTrigger, PopoverContent }\n"
  },
  {
    "path": "components/ui/radio-group.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\"\nimport { Circle } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst RadioGroup = React.forwardRef<\n  React.ElementRef<typeof RadioGroupPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>\n>(({ className, ...props }, ref) => {\n  return (\n    <RadioGroupPrimitive.Root\n      className={cn(\"grid gap-2\", className)}\n      {...props}\n      ref={ref}\n    />\n  )\n})\nRadioGroup.displayName = RadioGroupPrimitive.Root.displayName\n\nconst RadioGroupItem = React.forwardRef<\n  React.ElementRef<typeof RadioGroupPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, ...props }, ref) => {\n  return (\n    <RadioGroupPrimitive.Item\n      ref={ref}\n      className={cn(\n        \"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      <RadioGroupPrimitive.Indicator className=\"flex items-center justify-center\">\n        <Circle className=\"h-2.5 w-2.5 fill-current text-current\" />\n      </RadioGroupPrimitive.Indicator>\n    </RadioGroupPrimitive.Item>\n  )\n})\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName\n\nexport { RadioGroup, RadioGroupItem }\n"
  },
  {
    "path": "components/ui/select-position.tsx",
    "content": "\"use client\"\nimport { cn } from \"@lib/utils\"\nimport { useState } from \"react\"\n\nexport const SelectBackgroundPosition = ({ onChange }) => {\n    const [position, setPosition] = useState('center center')\n\n    const positions = [\n        { value: \"left top\", title: \"Top left\" },\n        { value: \"center top\", title: \"Top center\" },\n        { value: \"right top\", title: \"Top right\" },\n        { value: \"left center\", title: \"Left center\" },\n        { value: \"center center\", title: \"Center\" },\n        { value: \"right center\", title: \"Right center\" },\n        { value: \"left bottom\", title: \"Bottom left\" },\n        { value: \"center bottom\", title: \"Bottom center\" },\n        { value: \"right bottom\", title: \"Bottom right\" }\n    ]\n\n    const handleClick = (newPosition) => {\n        setPosition(newPosition)\n        onChange(newPosition)\n    }\n\n    return (\n        <div className=\"relative grid w-12 h-12 grid-cols-3 p-1 bg-white border border-gray-200 rounded-lg dark:border-gray-700 place-content-around place-items-center aspect-square dark:bg-gray-900 shadow hover:scale-[1.4] duration-300 ease-[cubic-bezier(.75,-0.5,0,1.75)]\">\n            {positions.map(({ value, title }) => (\n                <div\n                    key={value}\n                    onClick={() => handleClick(value)}\n                    title={title}\n                    className={cn(\n                        \"w-[8px] h-[8px] rounded-full cursor-pointer\",\n                        position === value\n                            ? \"bg-gray-800 dark:bg-gray-200\"\n                            : \"bg-gray-300 hover:bg-gray-500 dark:hover:bg-gray-400 dark:bg-gray-600/50\"\n                    )}\n                />\n            ))}\n        </div>\n    )\n}"
  },
  {
    "path": "components/ui/select.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Select = SelectPrimitive.Root\n\nconst SelectGroup = SelectPrimitive.Group\n\nconst SelectValue = SelectPrimitive.Value\n\nconst SelectTrigger = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <SelectPrimitive.Icon asChild>\n      <ChevronDown className=\"h-4 w-4 opacity-50\" />\n    </SelectPrimitive.Icon>\n  </SelectPrimitive.Trigger>\n))\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName\n\nconst SelectScrollUpButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollUpButton\n    ref={ref}\n    className={cn(\n      \"flex cursor-default items-center justify-center py-1\",\n      className\n    )}\n    {...props}\n  >\n    <ChevronUp className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollUpButton>\n))\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName\n\nconst SelectScrollDownButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollDownButton\n    ref={ref}\n    className={cn(\n      \"flex cursor-default items-center justify-center py-1\",\n      className\n    )}\n    {...props}\n  >\n    <ChevronDown className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollDownButton>\n))\nSelectScrollDownButton.displayName =\n  SelectPrimitive.ScrollDownButton.displayName\n\nconst SelectContent = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n  <SelectPrimitive.Portal>\n    <SelectPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        position === \"popper\" &&\n          \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n        className\n      )}\n      position={position}\n      {...props}\n    >\n      <SelectScrollUpButton />\n      <SelectPrimitive.Viewport\n        className={cn(\n          \"p-1\",\n          position === \"popper\" &&\n            \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\"\n        )}\n      >\n        {children}\n      </SelectPrimitive.Viewport>\n      <SelectScrollDownButton />\n    </SelectPrimitive.Content>\n  </SelectPrimitive.Portal>\n))\nSelectContent.displayName = SelectPrimitive.Content.displayName\n\nconst SelectLabel = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Label\n    ref={ref}\n    className={cn(\"py-1.5 pl-8 pr-2 text-sm font-semibold\", className)}\n    {...props}\n  />\n))\nSelectLabel.displayName = SelectPrimitive.Label.displayName\n\nconst SelectItem = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <SelectPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </SelectPrimitive.ItemIndicator>\n    </span>\n\n    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n  </SelectPrimitive.Item>\n))\nSelectItem.displayName = SelectPrimitive.Item.displayName\n\nconst SelectSeparator = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n    {...props}\n  />\n))\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName\n\nexport {\n  Select,\n  SelectGroup,\n  SelectValue,\n  SelectTrigger,\n  SelectContent,\n  SelectLabel,\n  SelectItem,\n  SelectSeparator,\n  SelectScrollUpButton,\n  SelectScrollDownButton,\n}\n"
  },
  {
    "path": "components/ui/slider.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SliderPrimitive from \"@radix-ui/react-slider\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Slider = React.forwardRef<\n  React.ElementRef<typeof SliderPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <SliderPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"relative flex w-full touch-none select-none items-center\",\n      className\n    )}\n    {...props}\n  >\n    {/*  bg-secondary */}\n    <SliderPrimitive.Track className=\"relative h-2 w-full grow overflow-hidden rounded-full bg-white\">\n      <SliderPrimitive.Range className=\"absolute h-full bg-primary\" />\n    </SliderPrimitive.Track>\n    <SliderPrimitive.Thumb className=\"block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\" />\n  </SliderPrimitive.Root>\n))\nSlider.displayName = SliderPrimitive.Root.displayName\n\nexport { Slider }\n"
  },
  {
    "path": "components/ui/sonner.tsx",
    "content": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner } from \"sonner\"\n\ntype ToasterProps = React.ComponentProps<typeof Sonner>\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = \"system\" } = useTheme()\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps[\"theme\"]}\n      className=\"toaster group\"\n      toastOptions={{\n        classNames: {\n          toast:\n            \"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg\",\n          description: \"group-[.toast]:text-muted-foreground\",\n          actionButton:\n            \"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground\",\n          cancelButton:\n            \"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground\",\n        },\n      }}\n      {...props}\n    />\n  )\n}\n\nexport { Toaster }\n"
  },
  {
    "path": "components/ui/switch.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Switch = React.forwardRef<\n  React.ElementRef<typeof SwitchPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n  <SwitchPrimitives.Root\n    className={cn(\n      \"peer inline-flex h-4 w-7 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  >\n    <SwitchPrimitives.Thumb\n      className={cn(\n        \"pointer-events-none block h-2.5 w-2.5 rounded-full bg-white ring-0 transition-transform data-[state=checked]:translate-x-3 data-[state=unchecked]:translate-x-0 duration-250\"\n      )}\n    />\n  </SwitchPrimitives.Root>\n))\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"
  },
  {
    "path": "components/ui/tabs.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Tabs = TabsPrimitive.Root\n\nconst TabsList = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.List\n    ref={ref}\n    className={cn(\n      \"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground\",\n      className\n    )}\n    {...props}\n  />\n))\nTabsList.displayName = TabsPrimitive.List.displayName\n\nconst TabsTrigger = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm\",\n      className\n    )}\n    {...props}\n  />\n))\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName\n\nconst TabsContent = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Content\n    ref={ref}\n    className={cn(\n      \"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n      className\n    )}\n    {...props}\n  />\n))\nTabsContent.displayName = TabsPrimitive.Content.displayName\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent }\n"
  },
  {
    "path": "components/ui/textarea.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface TextareaProps\n  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n  ({ className, ...props }, ref) => {\n    return (\n      <textarea\n        className={cn(\n          \"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nTextarea.displayName = \"Textarea\"\n\nexport { Textarea }\n"
  },
  {
    "path": "components/ui/toast.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ToastPrimitives from \"@radix-ui/react-toast\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ToastProvider = ToastPrimitives.Provider\n\nconst ToastViewport = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Viewport>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Viewport\n    ref={ref}\n    className={cn(\n      \"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]\",\n      className\n    )}\n    {...props}\n  />\n))\nToastViewport.displayName = ToastPrimitives.Viewport.displayName\n\nconst toastVariants = cva(\n  \"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full\",\n  {\n    variants: {\n      variant: {\n        default: \"border bg-background text-foreground\",\n        destructive:\n          \"destructive group border-destructive bg-destructive text-destructive-foreground\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nconst Toast = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &\n    VariantProps<typeof toastVariants>\n>(({ className, variant, ...props }, ref) => {\n  return (\n    <ToastPrimitives.Root\n      ref={ref}\n      className={cn(toastVariants({ variant }), className)}\n      {...props}\n    />\n  )\n})\nToast.displayName = ToastPrimitives.Root.displayName\n\nconst ToastAction = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Action>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Action\n    ref={ref}\n    className={cn(\n      \"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive\",\n      className\n    )}\n    {...props}\n  />\n))\nToastAction.displayName = ToastPrimitives.Action.displayName\n\nconst ToastClose = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Close>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Close\n    ref={ref}\n    className={cn(\n      \"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600\",\n      className\n    )}\n    toast-close=\"\"\n    {...props}\n  >\n    <X className=\"h-4 w-4\" />\n  </ToastPrimitives.Close>\n))\nToastClose.displayName = ToastPrimitives.Close.displayName\n\nconst ToastTitle = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Title>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Title\n    ref={ref}\n    className={cn(\"text-sm font-semibold\", className)}\n    {...props}\n  />\n))\nToastTitle.displayName = ToastPrimitives.Title.displayName\n\nconst ToastDescription = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Description>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Description\n    ref={ref}\n    className={cn(\"text-sm opacity-90\", className)}\n    {...props}\n  />\n))\nToastDescription.displayName = ToastPrimitives.Description.displayName\n\ntype ToastProps = React.ComponentPropsWithoutRef<typeof Toast>\n\ntype ToastActionElement = React.ReactElement<typeof ToastAction>\n\nexport {\n  type ToastProps,\n  type ToastActionElement,\n  ToastProvider,\n  ToastViewport,\n  Toast,\n  ToastTitle,\n  ToastDescription,\n  ToastClose,\n  ToastAction,\n}\n"
  },
  {
    "path": "components/ui/toaster.tsx",
    "content": "\"use client\"\n\nimport {\n  Toast,\n  ToastClose,\n  ToastDescription,\n  ToastProvider,\n  ToastTitle,\n  ToastViewport,\n} from \"@/components/ui/toast\"\nimport { useToast } from \"@/components/ui/use-toast\"\n\nexport function Toaster() {\n  const { toasts } = useToast()\n\n  return (\n    <ToastProvider>\n      {toasts.map(function ({ id, title, description, action, ...props }) {\n        return (\n          <Toast key={id} {...props}>\n            <div className=\"grid gap-1\">\n              {title && <ToastTitle>{title}</ToastTitle>}\n              {description && (\n                <ToastDescription>{description}</ToastDescription>\n              )}\n            </div>\n            {action}\n            <ToastClose />\n          </Toast>\n        )\n      })}\n      <ToastViewport />\n    </ToastProvider>\n  )\n}\n"
  },
  {
    "path": "components/ui/use-toast.ts",
    "content": "\"use client\"\n\n// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport type {\n  ToastActionElement,\n  ToastProps,\n} from \"@/components/ui/toast\"\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000000\n\ntype ToasterToast = ToastProps & {\n  id: string\n  title?: React.ReactNode\n  description?: React.ReactNode\n  action?: ToastActionElement\n}\n\nconst actionTypes = {\n  ADD_TOAST: \"ADD_TOAST\",\n  UPDATE_TOAST: \"UPDATE_TOAST\",\n  DISMISS_TOAST: \"DISMISS_TOAST\",\n  REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const\n\nlet count = 0\n\nfunction genId() {\n  count = (count + 1) % Number.MAX_SAFE_INTEGER\n  return count.toString()\n}\n\ntype ActionType = typeof actionTypes\n\ntype Action =\n  | {\n      type: ActionType[\"ADD_TOAST\"]\n      toast: ToasterToast\n    }\n  | {\n      type: ActionType[\"UPDATE_TOAST\"]\n      toast: Partial<ToasterToast>\n    }\n  | {\n      type: ActionType[\"DISMISS_TOAST\"]\n      toastId?: ToasterToast[\"id\"]\n    }\n  | {\n      type: ActionType[\"REMOVE_TOAST\"]\n      toastId?: ToasterToast[\"id\"]\n    }\n\ninterface State {\n  toasts: ToasterToast[]\n}\n\nconst toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()\n\nconst addToRemoveQueue = (toastId: string) => {\n  if (toastTimeouts.has(toastId)) {\n    return\n  }\n\n  const timeout = setTimeout(() => {\n    toastTimeouts.delete(toastId)\n    dispatch({\n      type: \"REMOVE_TOAST\",\n      toastId: toastId,\n    })\n  }, TOAST_REMOVE_DELAY)\n\n  toastTimeouts.set(toastId, timeout)\n}\n\nexport const reducer = (state: State, action: Action): State => {\n  switch (action.type) {\n    case \"ADD_TOAST\":\n      return {\n        ...state,\n        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n      }\n\n    case \"UPDATE_TOAST\":\n      return {\n        ...state,\n        toasts: state.toasts.map((t) =>\n          t.id === action.toast.id ? { ...t, ...action.toast } : t\n        ),\n      }\n\n    case \"DISMISS_TOAST\": {\n      const { toastId } = action\n\n      // ! Side effects ! - This could be extracted into a dismissToast() action,\n      // but I'll keep it here for simplicity\n      if (toastId) {\n        addToRemoveQueue(toastId)\n      } else {\n        state.toasts.forEach((toast) => {\n          addToRemoveQueue(toast.id)\n        })\n      }\n\n      return {\n        ...state,\n        toasts: state.toasts.map((t) =>\n          t.id === toastId || toastId === undefined\n            ? {\n                ...t,\n                open: false,\n              }\n            : t\n        ),\n      }\n    }\n    case \"REMOVE_TOAST\":\n      if (action.toastId === undefined) {\n        return {\n          ...state,\n          toasts: [],\n        }\n      }\n      return {\n        ...state,\n        toasts: state.toasts.filter((t) => t.id !== action.toastId),\n      }\n  }\n}\n\nconst listeners: Array<(state: State) => void> = []\n\nlet memoryState: State = { toasts: [] }\n\nfunction dispatch(action: Action) {\n  memoryState = reducer(memoryState, action)\n  listeners.forEach((listener) => {\n    listener(memoryState)\n  })\n}\n\ntype Toast = Omit<ToasterToast, \"id\">\n\nfunction toast({ ...props }: Toast) {\n  const id = genId()\n\n  const update = (props: ToasterToast) =>\n    dispatch({\n      type: \"UPDATE_TOAST\",\n      toast: { ...props, id },\n    })\n  const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id })\n\n  dispatch({\n    type: \"ADD_TOAST\",\n    toast: {\n      ...props,\n      id,\n      open: true,\n      onOpenChange: (open) => {\n        if (!open) dismiss()\n      },\n    },\n  })\n\n  return {\n    id: id,\n    dismiss,\n    update,\n  }\n}\n\nfunction useToast() {\n  const [state, setState] = React.useState<State>(memoryState)\n\n  React.useEffect(() => {\n    listeners.push(setState)\n    return () => {\n      const index = listeners.indexOf(setState)\n      if (index > -1) {\n        listeners.splice(index, 1)\n      }\n    }\n  }, [state])\n\n  return {\n    ...state,\n    toast,\n    dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n  }\n}\n\nexport { useToast, toast }\n"
  },
  {
    "path": "components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"tailwind.config.ts\",\n    \"css\": \"app/globals.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\"\n  }\n}"
  },
  {
    "path": "lib/utils.ts",
    "content": "import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
  },
  {
    "path": "next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/basic-features/typescript for more information.\n"
  },
  {
    "path": "next.config.js",
    "content": "// const isProd = process.env.NODE_ENV === 'production'\nconst isProd = false;\nconst bundleAnalyzer = require('@next/bundle-analyzer')\nconst withBundleAnalyzer = bundleAnalyzer({\n    enabled: false,\n    openAnalyzer: true,\n})\n\n\nmodule.exports = withBundleAnalyzer({\n    swcMinify: true,\n    crossOrigin: 'anonymous',\n    reactStrictMode: false,\n    env: {\n        STATIC_URL: isProd ? STATIC_URL : \"http://localhost:3000\",\n    },\n    // typescript: {\n    //     ignoreBuildErrors: true,\n    // },\n    // webpack: (config) => {\n    //     config.externals = [...config.externals, { canvas: 'canvas' }];\n    //     return config;\n    // },\n    // experimental: {\n    //     serverActions: {\n    //         allowedOrigins: []\n    //     },\n    // },\n\n    // async redirects() {\n    //     return [\n    //         {\n    //             source: \"/home\",\n    //             destination: \"/\",\n    //             permanent: false,\n    //         }]\n    // }\n\n\n    typescript: {\n        ignoreBuildErrors: true,\n    },\n    output: 'export',\n    // 禁用图像优化，因为它需要 Next.js 服务器\n    images: {\n        unoptimized: true,\n    },\n    webpack: (config, { isServer }) => {\n        if (isServer) {\n            // 在服务器端构建时忽略 API 路由\n            config.externals = config.externals || [];\n            config.externals.push((context, request, callback) => {\n                if (request.startsWith('pages/api/') || request.startsWith('app/api/')) {\n                    return callback(null, `commonjs ${request}`);\n                }\n                callback();\n            });\n        }\n        return config;\n    },\n\n})\n\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"X Cards Native Tweet Card service for X\",\n  \"displayName\": \"Seamlessly integrate card services directly on your X\",\n  \"version\": \"0.0.3\",\n  \"description\": \"Create stunning tweet cards effortlessly with X Cards on Twitter. Share your thoughts, ideas, and creations with style and flair\",\n  \"keywords\": [\n    \"x\",\n    \"card\",\n    \"share\",\n    \"anywhere\"\n  ],\n  \"author\": \"yixtotieq@gmail.com\",\n  \"scripts\": {\n    \"dev\": \"run-p dev:*\",\n    \"dev:plasmo\": \"plasmo dev\",\n    \"dev:next\": \"next dev --port 1947\",\n    \"build\": \"plasmo build\",\n    \"start:next\": \"next start\",\n    \"build:next\": \"next build\",\n    \"package\": \"plasmo package\"\n  },\n  \"dependencies\": {\n    \"@csstools/convert-colors\": \"^2.0.0\",\n    \"@langchain/core\": \"^0.2.16\",\n    \"@langchain/openai\": \"^0.2.2\",\n    \"@next/env\": \"^14.2.5\",\n    \"@plasmohq/messaging\": \"0.6.0\",\n    \"@radix-ui/react-accordion\": \"^1.2.0\",\n    \"@radix-ui/react-alert-dialog\": \"^1.1.1\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.1.1\",\n    \"@radix-ui/react-icons\": \"^1.3.0\",\n    \"@radix-ui/react-popover\": \"^1.1.1\",\n    \"@radix-ui/react-radio-group\": \"^1.2.0\",\n    \"@radix-ui/react-select\": \"^2.1.1\",\n    \"@radix-ui/react-slider\": \"^1.2.0\",\n    \"@radix-ui/react-slot\": \"^1.1.0\",\n    \"@radix-ui/react-switch\": \"^1.1.0\",\n    \"@radix-ui/react-tabs\": \"^1.1.0\",\n    \"@radix-ui/react-toast\": \"^1.2.1\",\n    \"@supabase/supabase-js\": \"^2.45.1\",\n    \"@tippyjs/react\": \"^4.2.6\",\n    \"@vercel/analytics\": \"^1.3.1\",\n    \"@visactor/react-vchart\": \"^1.11.9\",\n    \"add\": \"^2.0.6\",\n    \"class-variance-authority\": \"^0.7.0\",\n    \"classnames\": \"^2.5.1\",\n    \"clsx\": \"^2.1.1\",\n    \"date-fns\": \"^3.6.0\",\n    \"file-saver\": \"^2.0.5\",\n    \"framer-motion\": \"^11.3.7\",\n    \"html-to-image\": \"^1.11.11\",\n    \"idb-keyval\": \"3.0.0\",\n    \"jsonrepair\": \"^3.8.0\",\n    \"jszip\": \"^3.10.1\",\n    \"langchain\": \"^0.2.10\",\n    \"lodash-es\": \"^4.17.21\",\n    \"lucide-react\": \"^0.408.0\",\n    \"markmap-common\": \"^0.17.0\",\n    \"markmap-lib\": \"^0.17.0\",\n    \"markmap-view\": \"^0.17.0\",\n    \"mini-svg-data-uri\": \"^1.4.4\",\n    \"modern-screenshot\": \"^4.4.39\",\n    \"next\": \"14.1.0\",\n    \"next-themes\": \"^0.3.0\",\n    \"p-limit\": \"^6.1.0\",\n    \"p-retry\": \"^6.2.0\",\n    \"plasmo\": \"0.88.0\",\n    \"popover\": \"^2.4.1\",\n    \"randomcolor\": \"^0.6.2\",\n    \"react\": \"18.2.0\",\n    \"react-accessible-treeview\": \"^2.9.1\",\n    \"react-color\": \"^2.19.3\",\n    \"react-colorful\": \"^5.6.1\",\n    \"react-day-picker\": \"^9.0.0\",\n    \"react-dom\": \"18.2.0\",\n    \"react-hot-toast\": \"^2.4.1\",\n    \"react-icon-cloud\": \"^4.1.4\",\n    \"react-intersection-observer\": \"^9.13.0\",\n    \"react-player\": \"^2.16.0\",\n    \"react-scroll-parallax\": \"^3.4.5\",\n    \"react-use-measure\": \"^2.1.1\",\n    \"react-virtual\": \"^2.10.4\",\n    \"recharts\": \"^2.12.7\",\n    \"sonner\": \"^1.5.0\",\n    \"styled-components\": \"^6.1.12\",\n    \"tailwind-merge\": \"^2.4.0\",\n    \"tailwindcss-animate\": \"^1.0.7\",\n    \"uuid\": \"^10.0.0\",\n    \"webextension-polyfill\": \"^0.12.0\",\n    \"zod\": \"^3.23.8\",\n    \"zustand\": \"^4.5.4\"\n  },\n  \"devDependencies\": {\n    \"@ianvs/prettier-plugin-sort-imports\": \"4.1.1\",\n    \"@next/bundle-analyzer\": \"^14.2.5\",\n    \"@types/chrome\": \"0.0.258\",\n    \"@types/lodash-es\": \"^4.17.12\",\n    \"@types/node\": \"20.11.5\",\n    \"@types/react\": \"18.2.48\",\n    \"@types/react-dom\": \"18.2.18\",\n    \"@types/webextension-polyfill\": \"^0.10.7\",\n    \"autoprefixer\": \"^10.4.19\",\n    \"postcss\": \"^8.4.39\",\n    \"postcss-import\": \"^15.1.0\",\n    \"postcss-nested\": \"^6.2.0\",\n    \"prettier\": \"3.2.4\",\n    \"tailwindcss\": \"^3.3.6\",\n    \"typescript\": \"5.3.3\"\n  },\n  \"manifest\": {\n    \"permissions\": [\n      \"storage\",\n       \"tabs\"\n    ],\n    \"key\": \"$CRX_PUBLIC_KEY\"\n  }\n}"
  },
  {
    "path": "popup.tsx",
    "content": "// export const Popup = () => {\n//     return (\n//         <div className=\"w-4 h-4\">\n//         </div>\n//     )\n// }"
  },
  {
    "path": "postcss.config.js",
    "content": "/**\n * @type {import('postcss').ProcessOptions}\n */\nmodule.exports = {\n    plugins: {\n        \"postcss-import\": {},\n        \"postcss-nested\": {},\n        tailwindcss: {},\n        autoprefixer: {}\n    }\n}"
  },
  {
    "path": "src/app/(app)/components/FrequentlyAskedQuestions.tsx",
    "content": "\"use client\"\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from \"@components/ui/accordion\"\nimport BoldCopy from \"@src/components/ui/bold-copy\"\n\nexport const FrequentlyAskedQuestions = () => {\n    return (\n        <div>\n            <BoldCopy\n                className=\"border border-gray-200 dark:border-zinc-800\"\n                text=\"FAQ\">\n\n            </BoldCopy>\n            <div className=\"not-prose mt-4 flex flex-col gap-4 md:mt-8 text-2xl\">\n                <Accordion type=\"single\" collapsible className=\"w-full\">\n                    <AccordionItem value=\"item-1\">\n                        <AccordionTrigger>1.Why was this product created?</AccordionTrigger>\n                        <AccordionContent className=\"py-8 text-xl\">\n                            Our product was born out of a common workplace challenge. Many of us find ourselves juggling numerous websites throughout our workday. With an ever-growing list of URLs to remember, it's easy to get overwhelmed. This tool was developed to streamline your digital workflow and keep all your important web resources organized in one place.\n                        </AccordionContent>\n                    </AccordionItem>\n                    <AccordionItem value=\"item-2\">\n                        <AccordionTrigger>2.Do you collect or share my data?</AccordionTrigger>\n                        <AccordionContent className=\"py-8 text-xl\">\n                            Your privacy is our top priority. All data is stored locally on your device. We do not have access to, collect, or share any of your personal information or browsing history. Your data remains entirely under your control.\n                        </AccordionContent>\n                    </AccordionItem>\n                    <AccordionItem value=\"item-3\">\n                        <AccordionTrigger>3. What are your future plans for the product?</AccordionTrigger>\n                        <AccordionContent className=\"py-8 text-xl\">\n                            We're constantly working on improvements and new features! To stay up-to-date with our latest developments:\n                            <li>Follow us on Twitter/X for real-time updates</li>\n                            <li>Check our website regularly for announcements</li>\n                            <li>Join our mailing list for exclusive news and features</li>\n                        </AccordionContent>\n                    </AccordionItem>\n                </Accordion>\n            </div>\n\n        </div>\n\n    )\n}\n\n\n"
  },
  {
    "path": "src/app/(app)/components/GoogleFontSelector.tsx",
    "content": "import React, { useState, useEffect } from 'react';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';\nimport { useCardStore } from '@src/hooks/useCardStore';\nimport { RadioGroup, RadioGroupItem } from '@components/ui/radio-group';\n// import { Label } from '@radix-ui/react-select';\n\nconst googleFonts = [\n    { name: 'Default', value: 'sans-serif' },\n    { name: 'Roboto', value: 'Roboto' },\n    { name: 'Open Sans', value: 'Open Sans' },\n    { name: 'Lato', value: 'Lato' },\n    { name: 'Montserrat', value: 'Montserrat' },\n    { name: 'Noto Sans SC', value: 'Noto Sans SC' },\n    { name: 'Playfair Display', value: 'Playfair Display' },\n    { name: 'Merriweather', value: 'Merriweather' },\n    { name: 'Source Sans Pro', value: 'Source Sans Pro' },\n    { name: 'PT Sans', value: 'PT Sans' },\n    { name: 'Raleway', value: 'Raleway' },\n    { name: 'Oswald', value: 'Oswald' },\n    { name: 'Nunito', value: 'Nunito' },\n    { name: 'Ubuntu', value: 'Ubuntu' },\n    { name: 'Poppins', value: 'Poppins' },\n    { name: 'Quicksand', value: 'Quicksand' },\n    { name: 'Rubik', value: 'Rubik' },\n    { name: 'Work Sans', value: 'Work Sans' },\n    { name: 'Fira Sans', value: 'Fira Sans' },\n    { name: 'Noto Serif', value: 'Noto Serif' },\n    // 中文字体\n    { name: 'Noto Sans SC', value: 'Noto Sans SC' },\n    { name: 'Noto Serif SC', value: 'Noto Serif SC' },\n    { name: 'ZCOOL XiaoWei', value: 'ZCOOL XiaoWei' },\n    { name: 'ZCOOL QingKe HuangYou', value: 'ZCOOL QingKe HuangYou' },\n    { name: 'Ma Shan Zheng', value: 'Ma Shan Zheng' },\n];\n\nexport default function GoogleFontSelector({ onFontChange }) {\n    // const [selectedFont, setSelectedFont] = useState('sans-serif');\n    const setFontStyles = useCardStore((state) => state.updateCardStyles);\n    const fontFamily = useCardStore((state) => state.cardStyles.fontFamily);\n\n\n    const loadFont = (fontName) => {\n        if (fontName === 'sans-serif') return;\n        const link = document.createElement('link');\n        link.href = `https://fonts.googleapis.com/css2?family=${fontName.replace(' ', '+')}:wght@400;700&display=swap`;\n        link.rel = 'stylesheet';\n        document.head.appendChild(link);\n    };\n\n    useEffect(() => {\n        // 动态加载谷歌字体\n        const link = document.createElement('link');\n        link.href = 'https://fonts.googleapis.com/css2?family=Roboto&family=Open+Sans&family=Lato&family=Montserrat&family=Noto+Sans+SC&display=swap';\n        link.rel = 'stylesheet';\n        document.head.appendChild(link);\n\n        return () => {\n            document.head.removeChild(link);\n        };\n    }, []);\n\n\n    const handleFontChange = (value) => {\n        setFontStyles({\n            fontFamily: value\n        })\n        loadFont(value);\n        onFontChange?.(value);\n    };\n\n\n    return (\n        <div className=\"\">\n            <RadioGroup value={fontFamily} onValueChange={handleFontChange}>\n                {googleFonts.map((font) => (\n                    <div key={font.value} className=\"flex items-center space-x-2\">\n                        <RadioGroupItem value={font.value} id={font.value} />\n                        <span>{font.name}</span>\n                    </div>\n                ))}\n            </RadioGroup>\n        </div>\n    );\n}"
  },
  {
    "path": "src/app/(app)/components/ImageLayout.tsx",
    "content": "import { useCardStore } from '@src/hooks/useCardStore';\nimport React, { useEffect, useRef, useState } from 'react';\n\nconst imageCache: { [key: string]: string } = {};\n\nconst ImageLayout: React.FC<{\n    images: string[],\n    layout: 'vertical' | 'grid2' | 'grid4',\n    onAllImagesLoaded?: () => void\n\n}> = ({ images, layout }) => {\n    if (images.length === 0) return null;\n\n\n    const [loadedImages, setLoadedImages] = useState<string[]>([]);\n    const isMounted = useRef(true);\n\n    useEffect(() => {\n        return () => {\n            isMounted.current = false;\n        };\n    }, []);\n\n    useEffect(() => {\n        const loadImages = async () => {\n            const loadPromises = images.map(src =>\n                new Promise<string>(async (resolve, reject) => {\n                    if (imageCache[src]) {\n                        console.log('Image loaded from cache:', src);\n                        resolve(src);\n                        return;\n                    }\n                    try {\n                        // 使用 fetch 来利用浏览器的缓存机制\n                        const response = await fetch(src, { cache: 'force-cache' });\n                        const blob = await response.blob();\n                        const objectURL = URL.createObjectURL(blob);\n\n                        // 将图片添加到内存缓存\n                        imageCache[src] = objectURL;\n\n                        const img = new Image();\n                        img.src = objectURL;\n                        img.onload = () => {\n                            useCardStore.getState().addLoadedImage(src, 'success');\n                            resolve(src);\n                        }\n                        img.onerror = () => {\n                            useCardStore.getState().addLoadedImage(src, 'error');\n                            reject();\n                        };\n                    } catch (error) {\n                        reject(error);\n                        useCardStore.getState().addLoadedImage(src, 'error');\n                    }\n                })\n            );\n\n            try {\n                const loaded = await Promise.all(loadPromises);\n                if (isMounted.current) {\n                    setLoadedImages(loaded);\n                }\n            } catch (error) {\n                console.error('Failed to load one or more images:', error);\n            }\n        };\n\n        loadImages();\n    }, [images]);\n\n    if (loadedImages.length === 0) return <div>Loading...</div>;\n\n\n    const renderImage = (src: string, index: number, className: string = \"w-full h-full object-contain\") => (\n        <img\n            key={index}\n            src={imageCache[src] || src}\n            alt={`Image ${index + 1}`}\n            className={className}\n        />\n    );\n\n\n    const layoutStyles = {\n        vertical: \"flex flex-col space-y-2\",\n        grid2: \"grid grid-cols-2 gap-2\",\n        grid4: \"grid grid-cols-2 grid-rows-2 gap-2\"\n    };\n\n    if (images.length === 2) {\n        return (\n            <div className=\"w-full flex flex-row gap-2 \">\n                <div className=\"w-1/2\">\n                    {renderImage(images[0], 0)}\n                </div>\n                <div className=\"w-1/2\">\n                    {renderImage(images[1], 1)}\n                </div>\n            </div>\n        );\n    }\n\n    if (images.length === 3) {\n        return (\n            <div className=\"w-full flex flex-row gap-2 \">\n                <div className=\"w-1/2\">\n                    {renderImage(images[0], 0)}\n                </div>\n                <div className=\"w-1/4 flex flex-col gap-2\">\n                    <div className=\"h-1/2\">\n                        {renderImage(images[1], 1)}\n                    </div>\n                    <div className=\"h-1/2\">\n                        {renderImage(images[2], 2)}\n                    </div>\n                </div>\n            </div>\n        );\n    }\n\n    return (\n        <div className={`w-full ${layoutStyles[layout]}`}>\n            {images.slice(0, layout === 'vertical' ? undefined : (layout === 'grid2' ? 2 : 4))\n                .map((src, index) => renderImage(src, index))}\n        </div>\n    );\n};\n\nexport default ImageLayout;"
  },
  {
    "path": "src/app/(app)/components/LazyLoadAnimatedSection.tsx",
    "content": "import { useInView } from 'react-intersection-observer';\nimport { motion} from \"framer-motion\"\nconst LazyLoadAnimatedSection = ({ children, animation = 'fadeIn' }) => {\n    const [ref, inView] = useInView({\n        triggerOnce: true,\n        threshold: 0.1, // Adjust this value to control when the animation triggers\n    });\n\n    const animations = {\n        fadeIn: {\n            opacity: inView ? 1 : 0,\n            y: inView ? 0 : 50,\n            transition: { duration: 0.5 }\n        },\n        slideIn: {\n            x: inView ? 0 : -100,\n            opacity: inView ? 1 : 0,\n            transition: { duration: 0.5 }\n        },\n        scaleIn: {\n            scale: inView ? 1 : 0.8,\n            opacity: inView ? 1 : 0,\n            transition: { duration: 0.5 }\n        }\n    };\n\n    return (\n        <motion.div\n            ref={ref}\n            initial={false}\n            animate={animations[animation]}\n        >\n            {children}\n        </motion.div>\n    );\n};\n\n\nexport default LazyLoadAnimatedSection;"
  },
  {
    "path": "src/app/(app)/components/ResultIcon.tsx",
    "content": "import React, { useId } from \"react\";\n\n// import noisePicture from \"../assets/noise.inline.png\";\n\n// import { SettingsType } from \"../lib/types\";\n\ntype PropTypes = {\n    //   settings: SettingsType;\n    size?: number;\n    isPreview?: boolean;\n    // TODO: fix icon type?\n    IconComponent?: React.FC<React.SVGProps<SVGSVGElement>>;\n};\n\nconst ResultIcon = React.forwardRef<SVGSVGElement, PropTypes>(\n    ({ settings, size = 512, isPreview, IconComponent }, svgRef) => {\n        const strokeSize = isPreview ? 0 : settings.backgroundStrokeSize;\n        const strokeWidth = isNaN(parseInt(strokeSize.toString())) ? 0 : parseInt(strokeSize.toString());\n\n        const rectId = useId().replace(/:/g, \"\");\n        const gradientId = useId().replace(/:/g, \"\");\n        const radialGlareGradientId = useId().replace(/:/g, \"\");\n        const gradientX = settings.backgroundPosition?.split(\",\")[0];\n        const gradientY = settings.backgroundPosition?.split(\",\")[1];\n\n        return (\n            <svg\n                ref={svgRef}\n                width={size}\n                height={size}\n                viewBox={`0 0 ${size} ${size}`}\n                fill=\"none\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n                xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n            >\n                <rect\n                    id={rectId}\n                    width={size - strokeSize}\n                    height={size - strokeSize}\n                    x={strokeSize / 2}\n                    y={strokeSize / 2}\n                    rx={settings.backgroundRadius}\n                    fill={settings.backgroundFillType === \"Solid\" ? settings.backgroundStartColor : `url(#${gradientId})`}\n                    stroke={settings.backgroundStrokeColor}\n                    strokeWidth={strokeWidth}\n                    strokeOpacity={`${settings.backgroundStrokeOpacity}%`}\n                    paintOrder=\"stroke\"\n                />\n\n                {settings.backgroundRadialGlare ? (\n                    <rect\n                        width={size - strokeSize}\n                        height={size - strokeSize}\n                        x={strokeSize / 2}\n                        y={strokeSize / 2}\n                        fill={`url(#${radialGlareGradientId})`}\n                        rx={settings.backgroundRadius}\n                        style={{ mixBlendMode: \"overlay\" }}\n                    />\n                ) : null}\n\n                {settings.backgroundNoiseTexture && !isPreview ? (\n                    <image\n                        // href={noisePicture as unknown as string}\n                        width={size - strokeSize}\n                        height={size - strokeSize}\n                        x={strokeSize / 2}\n                        y={strokeSize / 2}\n                        clipPath=\"url(#clip)\"\n                        opacity={`${settings.backgroundNoiseTextureOpacity}%`}\n                    />\n                ) : null}\n                <clipPath id=\"clip\">\n                    <use xlinkHref={`#${rectId}`} />\n                </clipPath>\n\n                <defs>\n                    {settings.backgroundFillType === \"Radial\" ? (\n                        <radialGradient\n                            id={gradientId}\n                            cx=\"50%\"\n                            cy=\"50%\"\n                            r=\"100%\"\n                            fx={gradientX}\n                            fy={gradientY}\n                            gradientUnits=\"objectBoundingBox\"\n                        >\n                            <stop stopColor={settings.backgroundStartColor} />\n                            <stop offset={settings.backgroundSpread / 100} stopColor={settings.backgroundEndColor} />\n                        </radialGradient>\n                    ) : (\n                        <linearGradient\n                            id={gradientId}\n                            gradientUnits=\"userSpaceOnUse\"\n                            gradientTransform={`rotate(${settings.backgroundAngle})`}\n                            style={{ transformOrigin: \"center\" }}\n                        >\n                            <stop stopColor={settings.backgroundStartColor} />\n                            <stop offset=\"1\" stopColor={settings.backgroundEndColor} />\n                        </linearGradient>\n                    )}\n                    <radialGradient\n                        id={radialGlareGradientId}\n                        cx=\"0\"\n                        cy=\"0\"\n                        r=\"1\"\n                        gradientUnits=\"userSpaceOnUse\"\n                        gradientTransform={`translate(${size / 2}) rotate(90) scale(${size})`}\n                    >\n                        <stop stopColor=\"white\" />\n                        <stop offset=\"1\" stopColor=\"white\" stopOpacity=\"0\" />\n                    </radialGradient>\n                </defs>\n\n                {IconComponent ? (\n                    <IconComponent\n                        width={settings.iconSize}\n                        height={settings.iconSize}\n                        x={(size - settings.iconSize) / 2 + +settings.iconOffsetX}\n                        y={(size - settings.iconSize) / 2 + +settings.iconOffsetY}\n                        style={{ color: settings.iconColor }}\n                        alignmentBaseline=\"middle\"\n                    />\n                ) : null}\n            </svg>\n        );\n    }\n);\n\nResultIcon.displayName = \"ResultIcon\";\n\nexport default ResultIcon;"
  },
  {
    "path": "src/app/(app)/components/card-generator/color.tsx",
    "content": "import { cn } from \"@lib/utils\";\nimport { ColorChangeHandler, SketchPicker } from \"react-color\";\nimport ResultIcon from \"../ResultIcon\";\n\nimport { AccordionContent, AccordionTrigger, AccordionItem } from \"@components/ui/accordion\";\nimport { useCardStore } from \"@src/hooks/useCardStore\";\nimport { ColorPicker } from \"@components/ui/color-picker\";\n\nexport const presets: PresetType[] = [\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#FF7DB4\",\n        backgroundEndColor: \"#654EA3\",\n        backgroundAngle: 45,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#8E2DE2\",\n        backgroundEndColor: \"#4A00E0\",\n        backgroundAngle: 45,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#99F2C8\",\n        backgroundEndColor: \"#1F4037\",\n        backgroundAngle: 45,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#F953C6\",\n        backgroundEndColor: \"#B91D73\",\n        backgroundAngle: 45,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#91EAE4\",\n        backgroundEndColor: \"#7F7FD5\",\n        backgroundAngle: 45,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#F5AF19\",\n        backgroundEndColor: \"#F12711\",\n        backgroundAngle: 45,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#EAAFC8\",\n        backgroundEndColor: \"#EC2F4B\",\n        backgroundAngle: 45,\n    },\n\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#00B4DB\",\n        backgroundEndColor: \"#003357\",\n        backgroundAngle: 45,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#A8C0FF\",\n        backgroundEndColor: \"#3F2B96\",\n        backgroundAngle: 90,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#DD1818\",\n        backgroundEndColor: \"#380202\",\n        backgroundAngle: 135,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#DECBA4\",\n        backgroundEndColor: \"#3E5151\",\n        backgroundAngle: 45,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#FC466B\",\n        backgroundEndColor: \"#3F5EFB\",\n        backgroundAngle: 180,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#CCCFE2\",\n        backgroundEndColor: \"#25242B\",\n        backgroundAngle: 180,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#68AEFF\",\n        backgroundEndColor: \"#003EB7\",\n        backgroundAngle: 180,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#C9D6FF\",\n        backgroundEndColor: \"#596AA1\",\n        backgroundAngle: 180,\n    },\n    {\n        backgroundFillType: \"Linear\",\n        backgroundStartColor: \"#5C5C5C\",\n        backgroundEndColor: \"#0F1015\",\n        backgroundAngle: 180,\n    },\n    {\n        backgroundFillType: \"Radial\",\n        backgroundStartColor: \"#695BF8\",\n        backgroundEndColor: \"#131308\",\n        backgroundPosition: \"50%,0%\",\n    },\n    {\n        backgroundFillType: \"Radial\",\n        backgroundStartColor: \"#4d4d4d\",\n        backgroundEndColor: \"#000000\",\n        backgroundPosition: \"50%,0%\",\n    },\n    {\n        backgroundFillType: \"Radial\",\n        backgroundStartColor: \"#f5af19\",\n        backgroundEndColor: \"#f12711\",\n        backgroundPosition: \"50%,50%\",\n    },\n    {\n        backgroundFillType: \"Radial\",\n        backgroundStartColor: \"#1D6E47\",\n        backgroundEndColor: \"#041B11\",\n        backgroundPosition: \"50%,0%\",\n    },\n    {\n        backgroundFillType: \"Radial\",\n        backgroundStartColor: \"#ffffff\",\n        backgroundEndColor: \"#666666\",\n        backgroundPosition: \"50%,100%\",\n    },\n    {\n        backgroundFillType: \"Radial\",\n        backgroundStartColor: \"#d9f1f8\",\n        backgroundEndColor: \"#002069\",\n        backgroundPosition: \"50%,100%\",\n    },\n    {\n        backgroundFillType: \"Radial\",\n        backgroundStartColor: \"#f95356\",\n        backgroundEndColor: \"#7e0000\",\n        backgroundPosition: \"50%,50%\",\n    },\n    {\n        backgroundFillType: \"Radial\",\n        backgroundStartColor: \"#ffbb00\",\n        backgroundEndColor: \"#ffe74b\",\n        backgroundPosition: \"50%,0%\",\n    },\n];\n\ntype ColorInputPropTypes = {\n    value: string;\n    name: string;\n    recentColors: string[];\n    onChange: ColorChangeHandler;\n    disabled?: boolean;\n};\n\n\nexport const ColorSelect = () => {\n\n    const colorIndex = useCardStore(state => state.colorIndex);\n    const setColorIndex = useCardStore(state => state.setColorIndex);\n    const updateCardStyles = useCardStore((state) => state.updateCardStyles);\n    // const filStyles = useCardStore(state => state.filStyles);\n\n\n    return (\n        <div className={cn('flex flex-wrap gap-4 px-2 py-2')}>\n\n            {presets.map((preset, index) => {\n                return (\n                    <label key={index}\n                        className={cn(\n                            'relative overflow-hidden w-5 h-5 shrink-0 rounded-[5px] ',\n                            colorIndex === index ? \"dark:ring-primary-500 dark:ring-opacity-100 dark:ring-2 dark:ring-offset-2 dark:ring-offset-gray-700 ring-primary-500 ring-2 ring-offset-2 ring-offset-white  inline-flex  active:scale-95 transition  \" : \"\"\n\n                        )}\n                    >\n                        <input\n                            className={cn(\n                                \"absolute w-full h-full appearance-none opacity-0 inset-0 cursor-pointer\",\n                            )}\n                            type=\"radio\"\n                            name=\"preset\"\n                            value={index}\n                            checked={colorIndex === index}\n                            onChange={() => {\n                                setColorIndex(index);\n                            }}\n                        />\n\n                        <ResultIcon size={20} isPreview settings={{ ...preset, backgroundRadius: 0 }} />\n                    </label>\n                );\n            })}\n            {/* <ColorPicker className=\" w-[20px] h-[20px]\" value={cardStyles.borderColor}\n                onChange={(v) => {\n                    updateCardStyles({\n                        borderColor: v,\n                    });\n                }}\n            ></ColorPicker> */}\n        </div>\n\n    )\n}"
  },
  {
    "path": "src/app/(app)/components/card-generator/controller/background-controller.tsx",
    "content": "import { AccordionContent, AccordionItem, AccordionTrigger } from \"@components/ui/accordion\";\nimport { ColorPicker } from \"@components/ui/color-picker\";\nimport { ColorSelect } from \"../color\";\nimport { Switch } from \"@components/ui/switch\";\nimport { useCardStore } from \"@src/hooks/useCardStore\";\nimport { Slider } from \"@components/ui/slider\";\nimport { Input } from \"@components/ui/input\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useState } from \"react\";\n\nexport const BackgroundController = (props) => {\n\n    const tabConfig = useCardStore((state) => state.tabConfig);\n    const setTabConfig = useCardStore((state) => state.setTabConfig);\n    const backgroundStyles = useCardStore((state) => state.backgroundStyles);\n    const updateBackgroundStyles = useCardStore((state) => state.updateBackgroundStyles);\n\n\n    const handleImageUpload = (event) => {\n        const file = event.target.files[0];\n        if (file) {\n            const reader = new FileReader();\n            reader.onload = (e) => updateBackgroundStyles({\n                backgroundImage: e.target.result as string,\n            })\n            reader.readAsDataURL(file);\n        }\n    };\n\n    return (\n        <AccordionItem value={'background'}>\n            <AccordionTrigger>Background</AccordionTrigger>\n            <AccordionContent>\n                {/* preset color */}\n                <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\" text-[13px]\">Preset Color</span>\n                    <div className=\" w-full\">\n                        <ColorSelect></ColorSelect>\n                    </div>\n                </label>\n                {/* custom Color */}\n                <div>\n                    <label className=\"flex min-h-[40px] flex-row items-center justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                        <span className=\"grow-[2] text-[13px]\">Custom Color</span>\n                        <div className=\"inline-flex\">\n                            <Switch checked={tabConfig.openCustomColor}\n                                onCheckedChange={v => setTabConfig({ openCustomColor: v })}\n                            />\n                        </div>\n                    </label>\n                    <AnimatePresence>\n                        {tabConfig?.openCustomColor && <motion.div\n                            initial={{ opacity: 0 }}\n                            animate={{ opacity: 1 }}\n                            exit={{ opacity: 0 }}\n                        >\n                            <div className=\"py-2\">\n                                <ColorPicker className=\" w-[20px] h-[20px]\" value={backgroundStyles.backgroundColor}\n                                    onChange={(v) => {\n                                        updateBackgroundStyles({\n                                            backgroundColor: v,\n                                        });\n                                    }}\n                                ></ColorPicker>\n                            </div>\n                            {/* Background Opacity */}\n                            <label className=\"flex flex-col gap-y-2\">\n                                <span className=\"text-[13px]\">Background Opacity</span>\n                                <div className=\"py-2\">\n                                    <Slider\n                                        min={0}\n                                        max={1}\n                                        step={0.1}\n                                        value={[backgroundStyles.backgroundOpacity]}\n                                        onValueChange={(value) => {\n                                            updateBackgroundStyles({\n                                                backgroundOpacity: value,\n                                            })\n                                        }}\n                                    />\n                                </div>\n                            </label>\n                            {/* Background Blur  */}\n                            <label className=\"flex flex-col gap-y-2\">\n                                <span className=\"text-[13px]\">Background Blur</span>\n                                <div className=\"py-2\">\n                                    <Slider\n                                        min={0}\n                                        max={20}\n                                        step={1}\n                                        value={[backgroundStyles.backgroundBlur]}\n                                        onValueChange={(value) => updateBackgroundStyles({\n                                            backgroundBlur: value,\n                                        })}\n                                    />\n                                </div>\n                            </label>\n                        </motion.div>}\n                    </AnimatePresence>\n\n\n                    {/* Background Gradient */}\n                    <label className=\"flex min-h-[40px] flex-row items-center justify-start transition-opacity duration-[0.15s] ease-[ease-in-out] py-1\">\n                        <span className=\"grow-[2] text-[13px]\">Use Gradient</span>\n                        <div className=\"inline-flex\">\n                            <Switch\n                                checked={backgroundStyles.useGradient}\n                                onCheckedChange={(v) => updateBackgroundStyles({ useGradient: v })}\n                            />\n                        </div>\n                    </label>\n                    <AnimatePresence>\n                        {backgroundStyles.useGradient && (\n                            <motion.div\n                                initial={{ opacity: 0 }}\n                                animate={{ opacity: 1 }}\n                                exit={{ opacity: 0 }}\n                            >\n                                <label className=\"flex flex-col gap-y-2\">\n                                    <span className=\"text-[13px]\">Gradient Angle</span>\n                                    <div className=\"py-2\">\n                                        <Slider\n                                            min={0}\n                                            max={360}\n                                            step={1}\n                                            value={[backgroundStyles.backgroundGradientAngle]}\n                                            onValueChange={(value) => updateBackgroundStyles({ backgroundGradientAngle: value[0] })}\n                                        />\n                                    </div>\n                                </label>\n                                <label className=\"flex flex-col gap-y-2\">\n                                    <span className=\"text-[13px]\">Gradient Start Color</span>\n                                    <ColorPicker\n                                        value={backgroundStyles.backgroundStartColor}\n                                        onChange={(v) => updateBackgroundStyles({ backgroundStartColor: v })}\n                                    />\n                                </label>\n                                <label className=\"flex flex-col gap-y-2\">\n                                    <span className=\"text-[13px]\">Gradient End Color</span>\n                                    <ColorPicker\n                                        value={backgroundStyles.backgroundEndColor}\n                                        onChange={(v) => updateBackgroundStyles({ backgroundEndColor: v })}\n                                    />\n                                </label>\n                            </motion.div>\n                        )}\n                    </AnimatePresence>\n                </div>\n                {/* preset  Image */}\n                <label className=\"flex min-h-[40px] flex-col gap-y-2 justify-start transition-opacity duration-[0.15s] ease-[ease-in-out] py-1\">\n                    <span className=\"text-[13px]\">Image</span>\n                    <div className=\"w-full\">\n                        <Input\n                            onChange={handleImageUpload}\n                            accept=\"image/*\"\n                            type=\"file\"\n                            multiple\n                        />\n                    </div>\n                </label>\n\n\n\n                {/* <label className=\"flex flex-col gap-y-2\">\n                    <span className=\"text-[13px]\">Background Position</span>\n                    <Select\n                        value={backgroundStyles.backgroundPosition}\n                        onValueChange={(value) => updateBackgroundStyles({ backgroundPosition: value })}\n                    >\n                        <Select.Option value=\"center center\">Center</Select.Option>\n                        <Select.Option value=\"top left\">Top Left</Select.Option>\n                        <Select.Option value=\"top right\">Top Right</Select.Option>\n                        <Select.Option value=\"bottom left\">Bottom Left</Select.Option>\n                        <Select.Option value=\"bottom right\">Bottom Right</Select.Option>\n                    </Select>\n                </label> */}\n\n                {/* Background Repeat */}\n                {/* <label className=\"flex min-h-[40px] flex-row items-center justify-start transition-opacity duration-[0.15s] ease-[ease-in-out] py-1\">\n                    <span className=\"grow-[2] text-[13px]\">Background Repeat</span>\n                    <div className=\"inline-flex\">\n                        <Switch\n                            checked={backgroundStyles.backgroundRepeat === 'repeat'}\n                            onCheckedChange={(v) => updateBackgroundStyles({ backgroundRepeat: v ? 'repeat' : 'no-repeat' })}\n                        />\n                    </div>\n                </label> */}\n\n\n\n\n                {/* Background Width */}\n                <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\" text-[13px]\">Width</span>\n                    <div className=\" w-full\">\n                        <Slider step={1}\n                            value={[backgroundStyles.backgroundWidth]}\n                            max={100}\n                            min={50}\n                            onValueChange={(v) => {\n                                updateBackgroundStyles({\n                                    backgroundWidth: v[0],\n                                });\n                            }}\n\n                        ></Slider>\n                    </div>\n                </label>\n                {/* background padding */}\n                <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\" text-[13px]\">Padding</span>\n                    <div className=\" w-full\">\n                        <Slider step={1}\n                            value={[backgroundStyles.padding]}\n                            max={100}\n                            min={0}\n                            onValueChange={(v) => {\n                                updateBackgroundStyles({\n                                    padding: v[0],\n                                });\n                            }}\n\n                        ></Slider>\n                    </div>\n                </label>\n                {/* <label className=\"flex min-h-[40px] flex-row items-center justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\"grow-[2] text-[13px]\">Noise</span>\n                    <div className=\"inline-flex\">\n                        <Switch checked={cardStyles.hasNoiseTexture}\n                            onCheckedChange={v => updateCardStyles({ hasNoiseTexture: v })}\n                        />\n                    </div>\n                </label>\n                <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\" text-[13px]\">Opacity</span>\n                    <div className=\" w-full\">\n                        <Slider step={0.01}\n                            value={[cardStyles.noiseTextureOpacity]}\n                            max={1}\n                            min={0}\n                            onValueChange={(v) => {\n                                console.log('v', v);\n                                updateCardStyles({\n                                    noiseTextureOpacity: v[0],\n                                });\n                            }}\n\n                        ></Slider>\n                    </div>\n                </label>\n                <label className=\"flex min-h-[40px] flex-row items-center justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\"grow-[2] text-[13px]\">Noise</span>\n                    <SelectBackgroundPosition onChange={v => {\n                        updateCardStyles({\n                            texturePosition: v,\n                        });\n                    }}></SelectBackgroundPosition>\n                </label> */}\n            </AccordionContent>\n        </AccordionItem>\n    )\n}"
  },
  {
    "path": "src/app/(app)/components/card-generator/controller/card-controller.tsx",
    "content": "import { AccordionContent, AccordionItem, AccordionTrigger } from \"@components/ui/accordion\";\nimport { ColorPicker } from \"@components/ui/color-picker\";\nimport { Slider } from \"@components/ui/slider\";\nimport { useCardStore } from \"@src/hooks/useCardStore\";\nimport { useState } from \"react\";\nimport { RadioGroup, RadioGroupItem } from \"@components/ui/radio-group\";\nimport LayoutOptions from \"@src/components/extension/layout-options\";\nexport const CardController = () => {\n    const cardStyles = useCardStore((state) => state.cardStyles);\n    const updateCardStyles = useCardStore((state) => state.updateCardStyles);\n\n    const [selectedFiles, setSelectedFiles] = useState<File[]>([]);\n\n\n    const handleImageUpload = (event) => {\n        // const file = event.target.files[0];\n        // if (file) {\n        //     const reader = new FileReader();\n        //     reader.onload = (e) => updateBackgroundStyles({\n        //         backgroundImage: e.target.result as string,\n        //     })\n        //     reader.readAsDataURL(file);\n        // }\n    };\n\n    const handleLayoutChange = () => {\n\n    }\n\n    return (\n        <AccordionItem value={'card'}>\n            <AccordionTrigger>Card</AccordionTrigger>\n            <AccordionContent className=\"\">\n\n                <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\" text-[13px]\">Width</span>\n                    <div className=\" w-full\">\n                        <Slider step={1}\n                            value={[cardStyles.width]}\n                            max={1080}\n                            min={379}\n                            onValueChange={(v) => {\n                                updateCardStyles({\n                                    width: v[0],\n                                });\n                            }}\n\n                        ></Slider>\n                    </div>\n                </label>\n\n                <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\" text-[13px]\"> Scale</span>\n                    <div className=\" w-full\">\n                        <Slider step={1}\n                            value={[cardStyles.scale]}\n                            max={150}\n                            min={1}\n                            onValueChange={(v) => {\n                                console.log('v', v);\n                                updateCardStyles({\n                                    scale: v[0],\n                                });\n                            }}\n\n                        ></Slider>\n                    </div>\n                </label>\n\n\n                <label className=\"flex min-h-[40px] flex-row items-center justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\"grow-[2] text-[13px]\">Border Width</span>\n                    <div className=\" w-full\">\n                        <Slider step={1}\n                            value={[cardStyles.borderWidth]}\n                            max={10}\n                            min={0}\n                            onValueChange={(v) => {\n                                updateCardStyles({\n                                    borderWidth: v[0],\n                                });\n                            }}\n\n                        ></Slider>\n                    </div>\n                </label>\n                <label className=\"flex min-h-[40px] flex-row items-center justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\"grow-[2] text-[13px]\">Border Color</span>\n                    <ColorPicker className=\" w-[20px] h-[20px]\" value={cardStyles.borderColor}\n                        onChange={(v) => {\n                            updateCardStyles({\n                                borderColor: v,\n                            });\n                        }}\n                    ></ColorPicker>\n                </label>\n\n                <label className=\"flex min-h-[40px] flex-row items-center justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\"grow-[2] text-[13px]\">Style</span>\n                    <RadioGroup value={cardStyles.style} onValueChange={(v) => {\n                        updateCardStyles({\n                            style: v,\n                        });\n                    }}>\n                        <div className=\"flex items-center space-x-2\">\n                            <RadioGroupItem value=\"article\" id=\"article\" />\n                            <span>article</span>\n                        </div>\n                        <div className=\"flex items-center space-x-2\">\n                            <RadioGroupItem value=\"posts\" id=\"posts\" />\n                            <span>posts</span>\n                        </div>\n                    </RadioGroup>\n                </label>\n\n                <label className=\"flex min-h-[40px] flex-row items-center justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\"grow-[2] text-[13px]\">Border Radius</span>\n                    <Slider step={1}\n                        value={[cardStyles.borderRadius]}\n                        min={0}\n                        onValueChange={(v) => {\n                            updateCardStyles({\n                                borderRadius: v[0],\n                            });\n                        }}\n\n                    ></Slider>\n                </label>\n\n                {/* <label className=\"flex flex-col min-h-[40px] justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\"grow-[2] text-[13px]\">Resize</span>\n                    <LayoutOptions onSelect={(option) => {\n                        updateCardStyles({\n                            width: option.dimensions.width,\n                            height: option.dimensions.height,\n                        })\n                    }}></LayoutOptions>\n                </label> */}\n\n\n\n                {selectedFiles.length > 0 && (\n                    <div className=\"mt-2\">\n                        <p className=\"text-sm mb-1\">Selected files:</p>\n                        <ul className=\"list-disc list-inside\">\n                            {selectedFiles.map((file, index) => (\n                                <li key={index} className=\"text-sm\">{file.name}</li>\n                            ))}\n                        </ul>\n                    </div>\n                )}\n\n\n                <label className=\"flex min-h-[40px] flex-col gap-y-2 justify-start transition-opacity duration-[0.15s] ease-[ease-in-out] py-1\">\n                    <span className=\"text-[13px]\">Image Layout</span>\n                    <div className=\"w-full\">\n                        <RadioGroup value={cardStyles.imageLayout} onValueChange={(v) => {\n                            updateCardStyles({\n                                imageLayout: v,\n                            })\n                        }}>\n                            {[\n                                'vertical',\n                                'grid2',\n                                'grid4',\n                            ].map((layout) => (\n                                <div key={layout} className=\"flex items-center space-x-2\">\n                                    <RadioGroupItem value={layout} id={layout} />\n                                    <span>{layout}</span>\n                                </div>\n                            ))}\n                        </RadioGroup>\n                    </div>\n                </label>\n\n            </AccordionContent>\n        </AccordionItem>\n    )\n\n}"
  },
  {
    "path": "src/app/(app)/components/card-generator/controller/font-controller.tsx",
    "content": "import { AccordionContent, AccordionItem, AccordionTrigger } from \"@components/ui/accordion\";\nimport { Slider } from \"@components/ui/slider\";\nimport { useCardStore } from \"@src/hooks/useCardStore\";\nimport GoogleFontSelector from \"../../GoogleFontSelector\";\n\ninterface FontControllerProps {\n\n}\n\nconst googleFonts = [\n    { name: 'Default', value: 'sans-serif' },\n    { name: 'Roboto', value: \"'Roboto', sans-serif\" },\n    { name: 'Open Sans', value: \"'Open Sans', sans-serif\" },\n    { name: 'Lato', value: \"'Lato', sans-serif\" },\n    { name: 'Montserrat', value: \"'Montserrat', sans-serif\" },\n    { name: 'Noto Sans SC', value: \"'Noto Sans SC', sans-serif\" }, // 中文字体\n];\n\nexport const FontController = (props: FontControllerProps) => {\n    const updateCardStyles = useCardStore((state) => state.updateCardStyles);\n    const cardStyles = useCardStore((state) => state.cardStyles);\n    return (\n        <AccordionItem value={'Font'}>\n            <AccordionTrigger>Font</AccordionTrigger>\n            <AccordionContent className=\"\">\n                <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\" text-[13px]\">font-size</span>\n                    <div className=\" w-full\">\n                        <Slider step={0.5}\n                            value={[cardStyles.fontSize]}\n                            max={24}\n                            min={12}\n                            onValueChange={(v) => {\n                                updateCardStyles({\n                                    fontSize: v[0],\n                                });\n                            }}\n                        ></Slider>\n                    </div>\n                </label>\n                <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\" text-[13px]\">Select Font</span>\n                    <div className=\" w-full\">\n                        <GoogleFontSelector></GoogleFontSelector>\n                    </div>\n                </label>\n\n            </AccordionContent>\n        </AccordionItem>\n    )\n}"
  },
  {
    "path": "src/app/(app)/components/card-generator/controller/iframe-controller.tsx",
    "content": " {/* <AccordionItem value={'frames'}>\n                        <AccordionTrigger>Frames</AccordionTrigger>\n                        <AccordionContent>\n                            <div className=\"py-2\">\n                                <div className=\" max-w-xl  rounded-xl  flex flex-col\">\n                                    <div className=\"max-h-96 overflow-y-auto\">\n                                        <div className=\"  grid  grid-cols-3 gap-4  items-center p-4\">\n                                            <button\n                                                name=\"None\"\n                                                className=\"flex flex-col gap-1 justify-center items-center w-10 outline-none text-gray  transition-all ease-in-out active:scale-95 \"\n                                            >\n                                                <svg\n                                                    stroke=\"currentColor\"\n                                                    fill=\"currentColor\"\n                                                    strokeWidth={0}\n                                                    viewBox=\"0 0 16 16\"\n                                                    className=\"w-10 h-10\"\n                                                    height=\"1em\"\n                                                    width=\"1em\"\n                                                    xmlns=\"http://www.w3.org/2000/svg\"\n                                                >\n                                                    <path d=\"M2.5 5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1ZM4 5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1Zm2-.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z\" />\n                                                    <path d=\"M0 4a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v4a.5.5 0 0 1-1 0V7H1v5a1 1 0 0 0 1 1h5.5a.5.5 0 0 1 0 1H2a2 2 0 0 1-2-2V4Zm1 2h13V4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2Z\" />\n                                                    <path d=\"M16 12.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Zm-4.854-1.354a.5.5 0 0 0 0 .708l.647.646-.647.646a.5.5 0 0 0 .708.708l.646-.647.646.647a.5.5 0 0 0 .708-.708l-.647-.646.647-.646a.5.5 0 0 0-.708-.708l-.646.647-.646-.647a.5.5 0 0 0-.708 0Z\" />\n                                                </svg>\n                                                <span className=\"text-sm\">None</span>\n                                            </button>\n                                            <button\n                                                name=\"MacOS\"\n                                                className=\"flex flex-col gap-1 justify-center items-center w-10 outline-none text-gray  transition-all ease-in-out active:scale-95 \"\n                                            >\n                                                <svg\n                                                    stroke=\"currentColor\"\n                                                    fill=\"currentColor\"\n                                                    strokeWidth={0}\n                                                    viewBox=\"0 0 16 16\"\n                                                    className=\"w-10 h-10\"\n                                                    height=\"1em\"\n                                                    width=\"1em\"\n                                                    xmlns=\"http://www.w3.org/2000/svg\"\n                                                >\n                                                    <path d=\"M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43Zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56.244.729.625 1.924 1.273 2.796.576.984 1.34 1.667 1.659 1.899.319.232 1.219.386 1.843.067.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758.347-.79.505-1.217.473-1.282Z\" />\n                                                    <path d=\"M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43Zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56.244.729.625 1.924 1.273 2.796.576.984 1.34 1.667 1.659 1.899.319.232 1.219.386 1.843.067.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758.347-.79.505-1.217.473-1.282Z\" />\n                                                </svg>\n                                                <span className=\"text-sm\">MacOS</span>\n                                            </button>\n                                            <button\n                                                name=\"Windows\"\n                                                className=\"flex flex-col gap-1 justify-center items-center w-10 outline-none text-gray  transition-all ease-in-out active:scale-95 \"\n                                            >\n                                                <svg\n                                                    stroke=\"currentColor\"\n                                                    fill=\"currentColor\"\n                                                    strokeWidth={0}\n                                                    viewBox=\"0 0 16 16\"\n                                                    className=\"w-10 h-10\"\n                                                    height=\"1em\"\n                                                    width=\"1em\"\n                                                    xmlns=\"http://www.w3.org/2000/svg\"\n                                                >\n                                                    <path d=\"M6.555 1.375 0 2.237v5.45h6.555V1.375zM0 13.795l6.555.933V8.313H0v5.482zm7.278-5.4.026 6.378L16 16V8.395H7.278zM16 0 7.33 1.244v6.414H16V0z\" />\n                                                </svg>\n                                                <span className=\"text-sm\">Windows</span>\n                                            </button>\n                                            <button\n                                                name=\"Fimojis\"\n                                                className=\"flex flex-col gap-1 justify-center items-center w-10 outline-none text-gray  transition-all ease-in-out active:scale-95 \"\n                                            >\n                                                <svg\n                                                    stroke=\"currentColor\"\n                                                    fill=\"currentColor\"\n                                                    strokeWidth={0}\n                                                    viewBox=\"0 0 16 16\"\n                                                    className=\"w-10 h-10\"\n                                                    height=\"1em\"\n                                                    width=\"1em\"\n                                                    xmlns=\"http://www.w3.org/2000/svg\"\n                                                >\n                                                    <path d=\"M4.968 9.75a.5.5 0 1 0-.866.5A4.498 4.498 0 0 0 8 12.5a4.5 4.5 0 0 0 3.898-2.25.5.5 0 1 0-.866-.5A3.498 3.498 0 0 1 8 11.5a3.498 3.498 0 0 1-3.032-1.75zM7 5.116V5a1 1 0 0 0-1-1H3.28a1 1 0 0 0-.97 1.243l.311 1.242A2 2 0 0 0 4.561 8H5a2 2 0 0 0 1.994-1.839A2.99 2.99 0 0 1 8 6c.393 0 .74.064 1.006.161A2 2 0 0 0 11 8h.438a2 2 0 0 0 1.94-1.515l.311-1.242A1 1 0 0 0 12.72 4H10a1 1 0 0 0-1 1v.116A4.22 4.22 0 0 0 8 5c-.35 0-.69.04-1 .116z\" />\n                                                    <path d=\"M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-1 0A7 7 0 1 0 1 8a7 7 0 0 0 14 0z\" />\n                                                </svg>\n                                                <span className=\"text-sm\">Fimojis</span>\n                                            </button>\n                                            <button\n                                                name=\"Emojis\"\n                                                className=\"flex flex-col gap-1 justify-center items-center w-10 outline-none text-gray  transition-all ease-in-out active:scale-95 \"\n                                            >\n                                                <svg\n                                                    stroke=\"currentColor\"\n                                                    fill=\"currentColor\"\n                                                    strokeWidth={0}\n                                                    viewBox=\"0 0 16 16\"\n                                                    className=\"w-10 h-10\"\n                                                    height=\"1em\"\n                                                    width=\"1em\"\n                                                    xmlns=\"http://www.w3.org/2000/svg\"\n                                                >\n                                                    <path d=\"M8 16c3.314 0 6-2 6-5.5 0-1.5-.5-4-2.5-6 .25 1.5-1.25 2-1.25 2C11 4 9 .5 6 0c.357 2 .5 4-2 6-1.25 1-2 2.729-2 4.5C2 14 4.686 16 8 16Zm0-1c-1.657 0-3-1-3-2.75 0-.75.25-2 1.25-3C6.125 10 7 10.5 7 10.5c-.375-1.25.5-3.25 2-3.5-.179 1-.25 2 1 3 .625.5 1 1.364 1 2.25C11 14 9.657 15 8 15Z\" />\n                                                </svg>\n                                                <span className=\"text-sm\">Emojis</span>\n                                            </button>\n                                        </div>\n                                    </div>\n                                </div>\n\n                            </div>\n                        </AccordionContent>\n                    </AccordionItem> */}"
  },
  {
    "path": "src/app/(app)/components/card-generator/controller/input-controller.tsx",
    "content": "import { AccordionContent, AccordionItem, AccordionTrigger } from \"@components/ui/accordion\";\nimport { Input } from \"@components/ui/input\";\nimport { Textarea } from \"@components/ui/textarea\";\nimport { useCardStore } from \"@src/hooks/useCardStore\";\n\nexport const InputController = () => {\n    const xConfig = useCardStore(state => state.xConfig);\n    return (\n        <div>\n            <AccordionItem value={'Input'}>\n                <AccordionTrigger>Input</AccordionTrigger>\n                <AccordionContent className=\"\">\n                    <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                        <span className=\" text-[13px]\">username</span>\n                        <div className=\" px-1.5\">\n                            <Input maxLength={200} minLength={2} value={xConfig.username} onChange={(e) => {\n                                useCardStore.setState({\n                                    xConfig: {\n                                        ...xConfig,\n                                        username: e.target.value\n                                    }\n                                })\n                            }}></Input>\n                        </div>\n                    </label>\n                    <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                        <span className=\" text-[13px]\">text</span>\n                        <div className=\" px-1.5\">\n                            <Textarea rows={10} value={xConfig.text} onChange={(e) => {\n                                useCardStore.setState({\n                                    xConfig: {\n                                        ...xConfig,\n                                        text: e.target.value\n                                    }\n                                })\n                            }}></Textarea>\n                        </div>\n                    </label>\n                  \n                    <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                        <span className=\" text-[13px]\">replies</span>\n                        <div className=\" px-1.5\">\n                            <Input type=\"number\" value={xConfig.replies} onChange={(e) => {\n                                useCardStore.setState({\n                                    xConfig: {\n                                        ...xConfig,\n                                        replies: Number(e.target.value)\n                                    }\n                                })\n                            }}></Input>\n                        </div>\n                    </label>\n                    <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                        <span className=\" text-[13px]\">shares</span>\n                        <div className=\" px-1.5\">\n                            <Input type=\"number\" value={xConfig.shares} onChange={(e) => {\n                                useCardStore.setState({\n                                    xConfig: {\n                                        ...xConfig,\n                                        shares: Number(e.target.value)\n                                    }\n                                })\n                            }}></Input>\n                        </div>\n                    </label>\n                    <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                        <span className=\" text-[13px]\">shares</span>\n                        <div className=\" px-1.5\">\n                            <Input type=\"number\" value={xConfig.likes} onChange={(e) => {\n                                useCardStore.setState({\n                                    xConfig: {\n                                        ...xConfig,\n                                        likes: Number(e.target.value)\n                                    }\n                                })\n                            }}></Input>\n                        </div>\n                    </label>\n                </AccordionContent>\n            </AccordionItem>\n        </div>\n    )\n}"
  },
  {
    "path": "src/app/(app)/components/card-generator/display.tsx",
    "content": "import { CommonLayouts, useCardStore } from \"@src/hooks/useCardStore\"\nimport { useMemo } from \"react\"\n\n\nimport { presets } from \"./color\"\nimport { TwitterCard } from \"./twitter-card\"\nimport { WeChatCard } from \"./wechat-card\"\n\nexport const Display = () => {\n    const colorIndex = useCardStore((state) => state.colorIndex)\n    const cardStyles = useCardStore((state) => state.cardStyles)\n    const tabConfig = useCardStore((state) => state.tabConfig);\n    const backgroundStyles = useCardStore((state) => state.backgroundStyles)\n    const xConfig = useCardStore((state) => state.xConfig)\n\n    const finalBackgroundStyles = useMemo(() => {\n        const color = presets[colorIndex]\n\n        let borderRadius = cardStyles.borderRadius;\n        if (backgroundStyles.padding === 0) {\n            borderRadius = 0;\n        }\n\n        if (backgroundStyles?.useGradient) {\n            return {\n                ...backgroundStyles,\n                borderRadius,\n                backgroundImage: `linear-gradient(${backgroundStyles.backgroundGradientAngle}deg, ${backgroundStyles.backgroundStartColor}, ${backgroundStyles.backgroundEndColor})`,\n            }\n        }\n\n        if (tabConfig.openCustomColor) {\n            return {\n                ...backgroundStyles,\n                borderRadius,\n                backgroundColor: backgroundStyles.backgroundColor,\n            }\n\n        }\n        if (color.backgroundFillType === \"Linear\") {\n            return {\n                ...backgroundStyles,\n                borderRadius,\n                backgroundImage: `linear-gradient(${color.backgroundAngle}deg, ${color.backgroundStartColor}, ${color.backgroundEndColor})`,\n                backgroundRepeat: \"no-repeat\",\n            }\n        } else if (color.backgroundFillType === \"Radial\") {\n            return {\n                ...backgroundStyles,\n                borderRadius,\n                backgroundImage: `radial-gradient(${color.backgroundStartColor}, ${color.backgroundEndColor})`,\n                backgroundRepeat: \"no-repeat\",\n                backgroundPosition: color.backgroundPosition,\n\n            }\n        } else {\n            return {\n                ...backgroundStyles,\n                borderRadius,\n            }\n        }\n    }, [colorIndex, backgroundStyles, tabConfig.openCustomColor])\n\n    const calculateScale = (width, height) => {\n        const maxWidth = 1920 / 2; // Maximum width of the card in the display\n        const maxHeight = 1080 / 2; // Maximum height of the card in the display\n        const widthScale = maxWidth / width;\n        const heightScale = maxHeight / height;\n        return Math.min(widthScale, heightScale);\n    };\n\n    return (\n        <div\n            className=\"w-full  justify-center  flex  gap-x-4 relative mt-0\">\n\n            <TwitterCard\n                xConfig={xConfig}\n                cardStyles={{\n                    ...cardStyles,\n                    // aspectRatio: \"16/9\",\n                    // width: '1920px',\n                    // height: '1080px',\n                }}\n                backgroundStyles={finalBackgroundStyles}\n            ></TwitterCard>\n\n            {/* <WeChatCard xConfig={xConfig}></WeChatCard> */}\n        </div>\n    )\n}\n\n\n\n"
  },
  {
    "path": "src/app/(app)/components/card-generator/export-tab.tsx",
    "content": "import { AccordionContent, AccordionItem, AccordionTrigger } from \"@components/ui/accordion\";\n\nimport { Button } from \"@components/ui/button\";\nimport { exportImage } from \"@src/app/utils/export\";\nexport const ExportTab = () => {\n\n\n    return (\n        <AccordionItem value={'export'} className=\" border  border-destructive \">\n            <AccordionTrigger className=\"\">Export </AccordionTrigger>\n            <AccordionContent className=\" \">\n                <label className=\"flex min-h-[40px] flex-col gap-y-2  justify-start transition-opacity duration-[0.15s] ease-[ease-in-out]  py-1\">\n                    <span className=\" text-[13px]\">image type</span>\n                    <div className=\"mt-4 flex space-x-2\">\n                        <Button className=\"\" size=\"sm\" onClick={() => exportImage('png')}>As PNG</Button>\n                        <Button size=\"sm\" onClick={() => exportImage('jpeg')}>As JPEG</Button>\n                        <Button size=\"sm\" onClick={() => exportImage('svg')}>As SVG</Button>\n                    </div>\n                </label>\n\n            </AccordionContent>\n        </AccordionItem>\n    )\n}"
  },
  {
    "path": "src/app/(app)/components/card-generator/index.tsx",
    "content": "import { Accordion, } from \"@components/ui/accordion\";\nimport { Display } from \"./display\";\nimport { useCardStore } from \"@src/hooks/useCardStore\";\nimport { BackgroundController } from \"./controller/background-controller\";\nimport { FontController } from \"./controller/font-controller\";\nimport { ExportTab } from \"./export-tab\";\nimport { Button } from \"@components/ui/button\";\nimport { InputController } from \"./controller/input-controller\";\nimport { SaveAsTemplateButton } from \"../save-as-template-button\";\nimport { CardController } from \"./controller/card-controller\";\n\nexport const CardGenerator = () => {\n    const resetAll = useCardStore((state) => state.resetAll);\n\n    return (\n\n        <div className=\"flex   mx-auto  w-full   md:py-5 px-0 md:px-6\">\n            <Display></Display>\n            <div className=\"flex-1 min-w-[320px]  h-[578px] px-4 pb-4 overflow-auto  sm:block hidden \">\n                <div className=\"py-2 flex gap-x-2\">\n                    <Button onClick={() => resetAll()} size=\"sm\" variant=\"secondary\">Reset</Button>\n                    <SaveAsTemplateButton></SaveAsTemplateButton>\n\n                </div>\n                <Accordion type=\"multiple\" className=\"w-full\">\n                    <ExportTab></ExportTab>\n                    <InputController></InputController>\n                    <BackgroundController></BackgroundController>\n\n                    <CardController></CardController>\n                    <FontController></FontController>\n                </Accordion>\n            </div>\n        </div >\n    )\n}"
  },
  {
    "path": "src/app/(app)/components/card-generator/twitter-card.tsx",
    "content": "import React, { useMemo, useState } from 'react';\nimport { cn } from '@/lib/utils'; // Assuming you have a utility for class names\nimport ImageLayout from '../ImageLayout';\nimport XLogo from '@assets/x-logo.svg';\nimport * as _ from \"lodash-es\"\nimport { formatTimestamp } from '@src/app/utils/format';\nimport type { CardStore, XConfig } from '@src/hooks/useCardStore';\n\ninterface TwitterCardProps {\n    xConfig: XConfig[],\n    backgroundStyles: CardStore['backgroundStyles'],\n    cardStyles: CardStore['cardStyles'],\n}\n\nexport const TwitterCard: React.FC<TwitterCardProps> = ({ xConfig, backgroundStyles, cardStyles }) => {\n    const card = useMemo(() => {\n        const controls = cardStyles.controls;\n        return (\n            <div className='flex flex-col gap-y-4 relative'>\n                {\n                    xConfig.map((config, index) => (\n                        <div className=\"flex flex-col h-full\" key={`xConfig-${index}`}>\n                            <CardHeader xConfig={config} controls={controls} />\n                            <CardBody xConfig={config} cardStyles={cardStyles} />\n                            {controls.showFooter && (<CardFooter xConfig={config} />)}\n                        </div>\n                    ))\n                }\n                <div className=' absolute right-0 bottom-0 opacity-40 text-[##6d6d6d]'>\n                    ∙ Made with x-cards.net\n                </div>\n            </div>\n        )\n    }, [backgroundStyles, xConfig, cardStyles]);\n\n    return (\n        <div\n            id=\"card\"\n            //  aspect-video\n            className=\"flex h-fit\"\n            style={{\n                boxShadow: 'rgba(245, 208, 254, 0.3) 0px 0px 200px',\n                width: cardStyles.width,\n            }}\n        >\n            <div className=\"relative w-full grid place-items-center mobile-scaling pointer-events-none\">\n\n                <div className=\" overflow-hidden w-full h-full relative content-shadow\">\n                    <div\n                        id=\"content\"\n                        className=\"grid place-items-center content-container transition-colors h-full w-full\"\n                        style={{\n                            padding: backgroundStyles.padding || 0,\n                            fontSize: 14,\n                            perspective: 1000\n                        }}\n                    >\n                        {/* Background layers */}\n                        <BackgroundLayers backgroundStyles={backgroundStyles} cardStyles={cardStyles} />\n\n                        {/* Card container */}\n                        <div\n                            className=\"relative z-20 transition-all w-full card-holder\"\n                            style={{\n                                transform: `scale(${cardStyles.scale}%)`,\n                                // pointer-events: none;\n                                // fontFamily: cardStyles?.fontFamily && `'${cardStyles?.fontFamily}', sans-serif`,\n                                // fontFamily: 'ui-sans-serif',\n                            }}\n                        >\n                            {/* Card body */}\n                            <div\n                                className={cn(\n                                    \"select-none relative transition-all\",\n                                    \"h-full w-full backdrop-blur-[18px] backdrop-saturate-[177%] pt-[2em] pb-[1.5em] px-[2em]\",\n                                    'card-background-light transition-colors  inset-0 rounded-[inherit]',\n                                    // absolute\n                                )}\n\n                                style={{\n                                    overflow: 'visible',\n                                    zIndex: -1,\n                                    background: `linear-gradient(150deg, rgba(255,255,255,0.5), rgba(255,255,255,0.95) 80%)`,\n                                    // boxShadow: 'inset 0 0 0 2px rgba(255,255,255,0.15)',\n                                    borderRadius: `${backgroundStyles.borderRadius}px`,\n                                }}\n                            >\n                                {card}\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div >\n    );\n};\n\n\nconst BackgroundLayers = ({ backgroundStyles, cardStyles }) => (\n    <>\n        <div\n            className=\"absolute inset-0\"\n            style={{\n                ..._.omit(backgroundStyles, 'borderRadius'),\n                backgroundRepeat: \"no-repeat\",\n            }}\n        />\n        {backgroundStyles?.backgroundImage && (\n            <div\n                className=\"absolute inset-0\"\n                style={{\n                    backgroundRepeat: backgroundStyles.backgroundRepeat,\n                    backgroundImage: `url(${backgroundStyles.backgroundImage})`,\n                    filter: `blur(${backgroundStyles.backgroundBlur}px)`,\n                    opacity: backgroundStyles.backgroundOpacity,\n                }}\n            />\n        )}\n        {cardStyles.hasNoiseTexture && (\n            <div\n                className=\"absolute inset-0 mix-blend-overlay\"\n                style={{\n                    backgroundImage: `url(/noise1.png)`, // Assuming noise1 is now a public asset\n                    backgroundRepeat: \"repeat\",\n                    opacity: cardStyles.noiseTextureOpacity,\n                    backgroundPosition: cardStyles.texturePosition,\n                    zIndex: 10,\n                    borderRadius: `${cardStyles.borderRadius}px`,\n                }}\n            />\n        )}\n    </>\n);\n\nconst CardHeader: React.FC<{\n    xConfig: XConfig,\n    controls: CardStore['cardStyles']['controls'],\n}> = ({ xConfig, controls }) => {\n\n    return (\n        <div className='flex w-full'>\n            {controls.showUser ? (<div className=\"flex  flex-grow items-center pb-3\">\n                <img\n                    src={xConfig?.avatar || ''}\n                    className=\"inline object-cover rounded-full transition-all duration-150\"\n                    alt=\"Profile image\"\n                    style={{\n                        width: \"3em\",\n                        height: \"3em\",\n                        marginRight: \"0.75em\"\n                    }}\n                />\n                <div>\n                    <div className=\"flex text-secondary-foreground\" style={{ fontWeight: 600, lineHeight: \"1.2\" }}>\n                        <div className=\"whitespace-nowrap\" style={{ paddingRight: \"0.375em\", fontSize: 18 }}>\n                            {xConfig.username}\n                        </div>\n                    </div>\n                    <div className=\"whitespace-nowrap text-secondary\" style={{ fontSize: \"1em\", fontWeight: 400, lineHeight: \"1.2\" }} />\n                </div>\n            </div>) : <div className='flex w-full pb-3'></div>}\n            {\n                controls.showLogo ? <CardLogo xConfig={xConfig} /> : <></>\n            }\n        </div>\n    );\n}\n\nconst CardLogo = ({ xConfig }) => {\n    return (<div className='flex justify-center'>\n        <img\n            id=\"x-logo\"\n            src={XLogo.src}\n            alt=\"\"\n            className=\" h-[20px] w-[20px] right-4 top-4 opacity-30 dark:invert saturate-0 cursor-alias active:scale-90 transition-all ease-in-out\"\n        />\n    </div>\n    )\n}\n\nconst Watermark = () => {\n\n}\n\nconst CardBody: React.FC<{\n    xConfig: XConfig,\n    cardStyles: CardStore['cardStyles'],\n}> = ({ xConfig, cardStyles }) => {\n    //  cardStyles.imageLayout ||\n    const layout = xConfig.images.length >= 2 ? 'grid4' : 'vertical';\n\n    const images = _.compact(\n        [\n            ...xConfig.images,\n            xConfig?.video?.poster,\n            ...(xConfig?.links?.map((link) => link.src) || []),\n        ]\n    )\n\n    return (\n        <div className=\"flex-grow flex flex-col justify-center\">\n            <div className={cn(\"tweet-content text-lg leading-normal pointer-events-none mb-[1em]    \")} style={{ fontSize: cardStyles.fontSize, overflowWrap: 'anywhere' }}>\n                <div className=\"content whitespace-pre-wrap\" dir=\"auto\">\n                    {xConfig.text}\n                </div>\n            </div>\n            {images && images.length > 0 && (\n                <ImageLayout\n                    images={images}\n                    layout={layout}\n                />\n            )}\n        </div>\n    )\n}\n\nconst CardFooter = ({ xConfig }) => {\n    const time = _.isArray(xConfig) ? _.last(xConfig).time : xConfig.time;\n    return (\n        <div>\n            <div className=\"text-secondary-foreground flex items-center \" style={{ paddingBottom: \"0.5em\", paddingTop: '0.5rem' }}>\n                <span>{formatTimestamp(time)}</span>\n\n            </div>\n            <div className='flex  items-center justify-between'>\n                <div className=\"flex\">\n                    {['replies', 'shares', 'likes'].map((stat) => (\n                        <div key={stat} className=\"whitespace-nowrap text-secondary-foreground\" style={{ marginRight: \"1em\" }}>\n                            <span className=\"font-medium\" style={{ color: \"var(--textPrimary)\" }}>\n                                {xConfig[stat]}\n                            </span>{\" \"}\n                            {stat}\n                        </div>\n                    ))}\n                </div>\n            </div>\n        </div>\n    );\n}"
  },
  {
    "path": "src/app/(app)/components/card-generator/wechat-card.tsx",
    "content": "import type { CardStore, XConfig } from \"@src/hooks/useCardStore\"\n\ninterface WechatCardProps {\n    xConfig: XConfig,\n    backgroundStyles: CardStore['backgroundStyles'],\n    cardStyles: CardStore['cardStyles'],\n    fontStyles: CardStore['fontStyles'],\n}\n\nexport const WeChatCard: React.FC<WechatCardProps> = () => {\n    return (\n        <div\n            name=\"tempB\"\n            className=\"sm:w-full w-[100vw] bg-[#1B1C1E] text-[#E3D2B3] flex flex-col justify-between tempB\"\n            style={{\n                transition: \"padding 500ms\",\n                padding: 30,\n                fontFamily: \"SourceHanSerifCN_SemiBold\",\n                minHeight: \"auto\"\n            }}\n        >\n            <div>\n                <div\n                    className=\"n-collapse-transition\"\n                    style={{ nBezier: \"cubic-bezier(.4, 0, .2, 1)\" }}\n                >\n                    <div className=\"mb-2\">\n                        <div className=\"cursor-pointer flex w-max\">\n                            <input type=\"file\" accept=\".jpg, .jpeg, .png\" className=\"hidden\" />\n                            <img\n                                src=\"/_nuxt/default-avatar.C85jNu88.png\"\n                                id=\"icon\"\n                                className=\"rounded-[50%]\"\n                                style={{ width: 40, height: 40 }}\n                            />\n                        </div>\n                    </div>\n                </div>\n                <div\n                    className=\"leading-[1.4] opacity-90 font-bold custom-transition-2 mb-1.5\"\n                    style={{ fontSize: \"calc(1.25rem)\" }}\n                >\n                    <div className=\"bg-[transparent]\">\n                        <div contentEditable=\"true\" translate=\"no\" className=\"editable-element\">\n                            <p>Space</p>\n                        </div>\n                    </div>\n                </div>\n                <div\n                    className=\"leading-[1.5] opacity-90 custom-transition-2 mb-6 mt-2\"\n                    style={{ fontSize: \"calc(0.875rem)\" }}\n                >\n                    <div>\n                        <div className=\"bg-[transparent]\">\n                            <div\n                                contentEditable=\"true\"\n                                translate=\"no\"\n                                className=\"editable-element\"\n                            >\n                                <p>摘录于2024/8/18</p>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n                <div\n                    name=\"showContent\"\n                    className=\"content-body custom-transition-2 opacity-90\"\n                    style={{ fontSize: \"calc(1.25rem)\" }}\n                >\n                    <div className=\"bg-[transparent]\">\n                        <div\n                            contentEditable=\"true\"\n                            translate=\"no\"\n                            className=\"editable-element md-class\"\n                        >\n                            <p>\n                                人性和商业的能力，这样才能更好地发现市场痛点，了解用户需求，并用商业来完善产品想法，而不是仅仅作为一名员工去实现公司指定的产品路线。很多产品经理的工作多年间都无法得到进一步提升，其实相当一部分人是卡在了商业洞察这一块。于是很多人在问，是不是多读几本书就可以系统训练商业知识和商业嗅觉，从而增强自己的商业洞察能力?\n                            </p>\n                        </div>\n                    </div>\n                </div>\n            </div>\n\n            <div>\n                <div className=\"mt-6\" />\n                <div\n                    className=\"color-[#BBBD9E] font-light opacity-70 leading-[1.4] custom-transition-2 mb-1.5\"\n                    style={{ fontSize: \"calc(1rem)\" }}\n                >\n                    <div className=\"bg-[transparent]\">\n                        <div contentEditable=\"true\" translate=\"no\" className=\"editable-element\">\n                            <p>/产品之旅:产品经理的方法论与实战进阶</p>\n                        </div>\n                    </div>\n                </div>\n                <div\n                    className=\"custom-transition-2 mb-1.5 opacity-70\"\n                    style={{ fontSize: \"calc(0.9rem)\" }}\n                >\n                    <div className=\"bg-[transparent]\">\n                        <div contentEditable=\"true\" translate=\"no\" className=\"editable-element\">\n                            <p>赖京露</p>\n                        </div>\n                    </div>\n                </div>\n                <div>\n                    <div className=\"divider bg-[#F6ECD4] opacity-10 h-px my-5\" />\n                    <div\n                        className=\"qr-code-section flex items-center justify-between pt-2\"\n                        style={{ fontFamily: '\"Noto Serif SC\"' }}\n                    >\n                        <div>\n                            <div\n                                className=\"leading-[1.4] font-bold custom-transition-2\"\n                                style={{ fontSize: \"calc(0.8rem)\" }}\n                            >\n                                <div className=\"bg-[transparent]\">\n                                    <div\n                                        contentEditable=\"true\"\n                                        translate=\"no\"\n                                        className=\"editable-element\"\n                                    >\n                                        <p>微信读书</p>\n                                    </div>\n                                </div>\n                            </div>\n                        </div>\n                        <div className=\"bg-[#F6E2B5] p-1\">\n                            <div className=\"flex\" id=\"qr-code\">\n                                <div\n                                    className=\"n-qr-code cursor-pointer\"\n                                    style={{\n                                        padding: 0,\n                                        backgroundColor: \"transparent\",\n                                        width: 58,\n                                        height: 58,\n                                        borderRadius: 3\n                                    }}\n                                >\n                                    <canvas\n                                        width={116}\n                                        height={116}\n                                        style={{ width: 58, height: 58 }}\n                                    />\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    )\n}"
  },
  {
    "path": "src/app/(app)/components/dynamic-style-tippy.tsx",
    "content": "import Tippy from \"@tippyjs/react\";\nimport { useEffect, useRef } from \"react\";\n\nexport const DynamicStyleTippyComponent = ({ children, content, tippyOptions = {} }) => {\n  const styleId = 'x-cards-dropdown-styles';\n  const tippyRef = useRef(null);\n\n  useEffect(() => {\n    // Check if the style element already exists\n    let styleElement = document.getElementById(styleId);\n\n    if (!styleElement) {\n      // If it doesn't exist, create it\n      styleElement = document.createElement('style');\n      styleElement.id = styleId;\n      document.head.appendChild(styleElement);\n    }\n\n    // Define your styles\n    const styles = `\n     .tippy-box[data-theme~='custom'] {\n --background: 240 10% 3.9%;\n    --foreground: 0 0% 98%;\n    --muted: 240 3.7% 15.9%;\n    --muted-foreground: 240 5% 64.9%;\n    --popover: 240 10% 3.9%;\n    --popover-foreground: 0 0% 98%;\n    --card: 240 10% 3.9%;\n    --card-foreground: 0 0% 98%;\n    --border: 240 3.7% 15.9%;\n    --input: 240 3.7% 15.9%;\n    --primary: 0 0% 98%;\n    --primary-foreground: 240 5.9% 10%;\n    --secondary: 240 3.7% 15.9%;\n    --secondary-foreground: 0 0% 98%;\n    --accent: 240 3.7% 15.9%;\n    --accent-foreground: 0 0% 98%;\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 0 0% 98%;\n    --ring: 240 4.9% 83.9%;\n\n  background-color: black;\n  color: hsl(var(--foreground));\n  border: 1px solid hsl(var(--border));\n  border-radius: 6px;\n  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);\n  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n  overflow: hidden;\n  animation: scaleIn 0.2s ease-out;\n}\n\n.tippy-box[data-theme~='custom'] .tippy-content {\n  padding: 4px;\n  min-width: 152px;\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-list {\n  list-style-type: none;\n  padding: 0;\n  margin: 0;\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-item {\n  display: flex;\n  align-items: center;\n  padding: 8px 12px;\n  font-size: 14px;\n  color: hsl(var(--foreground));\n  cursor: pointer;\n  transition: background-color 0.2s, color 0.2s;\n  border-radius: 4px;\n  user-select: none;\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-item:hover {\n  background-color: hsl(var(--accent));\n  color: hsl(var(--accent-foreground));\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-item.no-hover:hover {\n  background-color: transparent;\n  color: hsl(var(--foreground));\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-item:active {\n  background-color: hsl(var(--muted));\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-icon  {\n  width: 16px;\n  height: 16px;\n  margin-right: 8px;\n  color: var(--muted-foreground);\n  transition: color 0.2s;\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-icon:hover  {\n  color: hsl(var(--accent-foreground));\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-separator {\n  display: flex;\n  align-items: center;\n  text-align: center;\n  // margin: 10px 0;\n}\n\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-separator::before,\n.tippy-box[data-theme~='custom'] .dropdown-menu-separator::after {\n  content: '';\n  flex: 1;\n  border-bottom: 1px solid hsl(var(--border));\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-separator::before {\n  margin-right: 0.5em;\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-separator::after {\n  margin-left: 0.5em;\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-separator-text {\n  padding: 0 10px;\n  font-size: 0.85em;\n  color: hsl(var(--muted-foreground));\n  white-space: nowrap;\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-item.danger {\n  color: hsl(var(--destructive-foreground));\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-item.danger:hover {\n  background-color: hsl(var(--destructive));\n}\n\n.tippy-box[data-theme~='custom'] .dropdown-menu-item.danger svg {\n  color: var(--destructive-foreground);\n}\n\n@keyframes scaleIn {\n  from { opacity: 0; transform: scale(0.95); }\n  to { opacity: 1; transform: scale(1); }\n}\n        `;\n\n    // Set the styles\n    styleElement.textContent = styles;\n\n    // Cleanup function\n    return () => {\n    };\n  }, []);\n\n\n\n\n  return (\n    <Tippy\n      ref={tippyRef}\n      content={content}\n      interactive={true}\n      appendTo={document.body}\n      placement=\"top-start\"\n      theme=\"custom\" // Use our custom theme\n      {...tippyOptions}\n    >\n      {children}\n    </Tippy>\n  );\n};"
  },
  {
    "path": "src/app/(app)/components/hero.tsx",
    "content": "\nimport chromeSvg from '@assets/chrome.svg';\nimport Image from \"next/image\";\n\nconst Hero = () => {\n    return (\n        <>\n            <div className='flex flex-col gap-y-8 justify-center'>\n                <div className=\"relative mx-auto flex max-w-2xl flex-col items-center\">\n                    <h2 className=\"text-center text-3xl font-medium text-gray-900 dark:text-gray-50 sm:text-6xl\">\n                        X Cards  {' '}\n                        <span className=\"animate-text-gradient inline-flex bg-gradient-to-r from-neutral-900 via-slate-500 to-neutral-500 bg-[200%_auto] bg-clip-text leading-tight text-transparent dark:from-neutral-100 dark:via-slate-400 dark:to-neutral-400\">\n                            Native Tweet Card service for X\n                        </span>\n                    </h2>\n                    <p className=\"mt-6 text-center text-lg leading-6 text-gray-600 dark:text-gray-200\">\n                        Capture and share posts as beautiful images, cards, Markdown, or JSON, making sharing Twitter posts on other platforms more visual and attention-grabbing.\n                        {' '}\n                    </p>\n                    <div className=\"mt-10 flex gap-4\">\n                        <a target=\"_blank\" href=\"https://chromewebstore.google.com/detail/x-card/mbinooofmcjhjklihfejnkkebffceeop\"\n                            className=\"p-[3px] relative group\">\n                            <div className=\"absolute inset-0 bg-gradient-to-r from-[#8F01FF] to-[#FDB83B] rounded-lg\" />\n                            <div className=\"px-8 py-2  group-hover:scale-105  flex  gap-x-3   rounded-[6px]  relative group transition duration-200 text-white \">\n                                <Image src={chromeSvg} alt=\"Chrome Extension\" className=\"w-6 h-6\" />\n                                Download Extension\n                            </div>\n\n                        </a>\n\n                    </div>\n                    <a\n                        className='mt-10'\n                        href=\"https://www.producthunt.com/posts/x-cards?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-x-cards\"\n                        target=\"_blank\"\n                    >\n                        <img\n                            src=\"https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=481821&theme=light\"\n                            alt=\"X Cards - Simple, Beautiful Tweets with Card Integration on X.com | Product Hunt\"\n                            style={{ width: 250, height: 54 }}\n                            width={250}\n                            height={54}\n                        />\n                    </a>\n                </div>\n                {/* <VideoSection /> */}\n\n            </div>\n\n        </>\n\n    )\n}\n\nexport default Hero\n\n\n"
  },
  {
    "path": "src/app/(app)/components/save-as-template-button.tsx",
    "content": "import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from \"@components/ui/alert-dialog\";\nimport { Button } from \"@components/ui/button\";\nimport { Input } from \"@components/ui/input\";\nimport { useCardStore } from \"@src/hooks/useCardStore\";\nimport { useTemplatesStore } from \"@src/hooks/useTemplatesStore\";\nimport React from \"react\";\n\nexport const SaveAsTemplateButton = () => {\n    const cardStyles = useCardStore((state) => state.cardStyles);\n    const addTemplate = useTemplatesStore((state) => state.addTemplate);\n    const backgroundStyles = useCardStore((state) => state.backgroundStyles);\n    const [name, setName] = React.useState('');\n\n    return (<AlertDialog>\n        <AlertDialogTrigger asChild>\n            <Button size=\"sm\" >Save as Template</Button>\n        </AlertDialogTrigger>\n        <AlertDialogContent>\n            <AlertDialogHeader>\n                <AlertDialogTitle>Input Template Name</AlertDialogTitle>\n                {/* <AlertDialogDescription>\n    This action cannot be undone. This will permanently delete your\n    account and remove your data from our servers.\n</AlertDialogDescription> */}\n                <Input value={name} onChange={(e) => {\n                    setName(e.target.value)\n                }}></Input>\n            </AlertDialogHeader>\n            <AlertDialogFooter>\n                <AlertDialogCancel>Cancel</AlertDialogCancel>\n                <AlertDialogAction onClick={() => addTemplate({\n                    name: name,\n                    colorIndex: useCardStore.getState().colorIndex,\n                    backgroundStyles: backgroundStyles,\n                    tabConfig: useCardStore.getState().tabConfig,\n                    cardStyles: cardStyles\n                })}>Confirm</AlertDialogAction>\n            </AlertDialogFooter>\n        </AlertDialogContent>\n    </AlertDialog>)\n}"
  },
  {
    "path": "src/app/(app)/components/sections/FeaturesGridSection.tsx",
    "content": "// import yellowWaveDown from \"@/public/images/banner-wave.png\";\n// import Image from \"next/image\";\nimport { BookCopy, LayoutGrid, ListCollapse, Palette } from \"lucide-react\";\nimport { useId } from \"react\";\n\n function FeaturesGridSection() {\n\treturn (\n\t\t<>\n\t\t\t<div className=\"relative py-32\">\n\t\t\t\t<div className=\"flex flex-col items-center justify-center \">\n\t\t\t\t\t<h1 className=\"m-[33.2px_0px_8px] h-[46px] max-w-screen-xl text-center font-semibold text-[40px]  leading-[46px]\">\n\t\t\t\t\t\tFeatures\n\t\t\t\t\t</h1>\n\t\t\t\t\t<div className=\"max-w-screen-xl p-[0px_25px] text-center  text-xl tracking-[-0.2px]\">\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\tWe provide a variety of features to help you create beautiful\n\t\t\t\t\t\t\timages, cards, Markdown, or JSON from tweets, making sharing\n\t\t\t\t\t\t\tTwitter posts on other platforms more visual and\n\t\t\t\t\t\t\tattention-grabbing.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t<div className=\"m-auto mx-auto mt-[50px] flex max-w-7xl flex-row flex-wrap justify-center overflow-x-hidden \">\n\t\t\t\t\t{grid.map((feature) => (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tkey={feature.title}\n\t\t\t\t\t\t\tclassName=\"relative m-4 w-[260px] min-w-[260px] overflow-hidden rounded-3xl bg-gradient-to-b from-neutral-100 to-white p-6 dark:from-neutral-900 dark:to-neutral-950\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Grid size={20} />\n\t\t\t\t\t\t\t<div className=\"mb-2 p-0\">{feature?.icon}</div>\n\t\t\t\t\t\t\t<p className=\"relative z-20 font-bold text-base text-neutral-800 dark:text-white\">\n\t\t\t\t\t\t\t\t{feature.title}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<p className=\"relative z-20 mt-4 font-normal text-base text-neutral-600 dark:text-neutral-400\">\n\t\t\t\t\t\t\t\t{feature.description}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t{/* <Image src={yellowWaveDown} alt=\"\" className=\" -mt-32 absolute\" /> */}\n\t\t</>\n\t);\n}\n\nconst grid = [\n\t{\n\t\ticon: (\n\t\t\t<BookCopy className=\"w-8 h-8\" />\n\t\t),\n\t\ttitle: \"Fast capture\",\n\t\t// description:\n\t\t// \t\"快速将任何帖子转换为卡片,只需要亲亲点击\"\n\n\t\tdescription:\n\t\t\t\"Capture and share posts as beautiful images, cards, Markdown, or JSON, making sharing Twitter posts on other platforms more visual and attention-grabbing.\"\n\t},\n\t{\n\t\ticon: (\n\t\t\t<Palette className=\"w-8 h-8\" />\n\t\t),\n\t\ttitle: \"Custom Styles\",\n\t\t// description:\n\t\t// \t\"多种卡片风格,自定义颜色，边距等等\"\n\n\t\tdescription:\n\t\t\t\"Multiple card styles, custom colors, margins, and more.\"\n\t},\n\t{\n\t\ticon: (\n\t\t\t<ListCollapse className=\"w-8 h-8\" />\n\t\t),\n\t\ttitle: \"Combining multiple posts\",\n\t\t// description:\n\t\t// \t\"拼接多个tweet内容，生成长卡片\",\n\n\t\tdescription:\n\t\t\t\"Combine multiple tweet contents to generate long cards.\"\n\t},\n\t{\n\t\ticon: (\n\t\t\t<LayoutGrid className=\"w-8 h-8\" />\n\t\t),\n\t\ttitle: \"Dynamic management\",\n\t\t// description:\n\t\t// \t\"动态管理你的帖子，随时删除，添加\",\n\n\t\tdescription:\n\t\t\t\"Manage your posts dynamically, delete or add them at any time.\"\n\n\t},\n\n];\n\nconst Grid = ({\n\tpattern,\n\tsize,\n}: {\n\tpattern?: number[][];\n\tsize?: number;\n}) => {\n\tconst p = pattern ?? [\n\t\t[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],\n\t\t[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],\n\t\t[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],\n\t\t[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],\n\t\t[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],\n\t];\n\treturn (\n\t\t<div className=\"-ml-20 -mt-2 pointer-events-none absolute top-0 left-1/2 h-full [mask-image:linear-gradient(white,transparent)]\">\n\t\t\t<div className=\"absolute inset-0 bg-gradient-to-r from-zinc-100/30 to-zinc-300/30 opacity-100 [mask-image:radial-gradient(farthest-side_at_top,white,transparent)] dark:from-zinc-900/30 dark:to-zinc-900/30\">\n\t\t\t\t<GridPattern\n\t\t\t\t\twidth={size ?? 20}\n\t\t\t\t\theight={size ?? 20}\n\t\t\t\t\tx=\"-12\"\n\t\t\t\t\ty=\"4\"\n\t\t\t\t\tsquares={p}\n\t\t\t\t\tclassName=\"absolute inset-0 h-full w-full fill-black/10 stroke-black/10 mix-blend-overlay dark:fill-white/10 dark:stroke-white/10\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n};\n\nfunction GridPattern({ width, height, x, y, squares, ...props }: any) {\n\tconst patternId = useId();\n\n\treturn (\n\t\t<svg aria-hidden=\"true\" {...props}>\n\t\t\t<defs>\n\t\t\t\t<pattern\n\t\t\t\t\tid={patternId}\n\t\t\t\t\twidth={width}\n\t\t\t\t\theight={height}\n\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\tx={x}\n\t\t\t\t\ty={y}\n\t\t\t\t>\n\t\t\t\t\t<path d={`M.5 ${height}V.5H${width}`} fill=\"none\" />\n\t\t\t\t</pattern>\n\t\t\t</defs>\n\t\t\t<rect\n\t\t\t\twidth=\"100%\"\n\t\t\t\theight=\"100%\"\n\t\t\t\tstrokeWidth={0}\n\t\t\t\tfill={`url(#${patternId})`}\n\t\t\t/>\n\t\t\t{squares && (\n\t\t\t\t<svg x={x} y={y} className=\"overflow-visible\">\n\t\t\t\t\t<title>noise</title>\n\t\t\t\t\t{squares.map(([x, y]: any) => (\n\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\tstrokeWidth=\"0\"\n\t\t\t\t\t\t\tkey={`${x}-${y}`}\n\t\t\t\t\t\t\twidth={width + 1}\n\t\t\t\t\t\t\theight={height + 1}\n\t\t\t\t\t\t\tx={x * width}\n\t\t\t\t\t\t\ty={y * height}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</svg>\n\t\t\t)}\n\t\t</svg>\n\t);\n}\n\n\nexport default FeaturesGridSection;"
  },
  {
    "path": "src/app/(app)/components/sections/features2.tsx",
    "content": "// import { Icon } from \"@/components/ui/icon\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@src/components/ui/card\";\nimport { Icon } from \"@src/components/ui/icon\";\nimport { icons } from \"lucide-react\";\ninterface FeaturesProps {\n    icon: string;\n    title: string;\n    description: string;\n}\n\nconst featureList: FeaturesProps[] = [\n    {\n        icon: \"Globe\",\n        title: \"Seamless Integration\",\n        description: \"Integrate directly on x.com without any hassle. Our product works smoothly within the platform you're already using.\",\n    },\n    {\n        icon: \"MousePointerClick\",\n        title: \"One-Click Generation\",\n        description: \"Simple and easy to use. Generate and copy cards with just a single click, streamlining your workflow.\",\n    },\n    {\n        icon: \"Palette\",\n        title: \"Customizable Design\",\n        description: \"Personalize your cards by modifying styles, background colors, and more to match your brand or preferences.\",\n    },\n    {\n        icon: \"Eye\",\n        title: \"Real-Time Preview\",\n        description: \"See your changes instantly and manage your tweets dynamically, ensuring your content looks perfect before posting.\",\n    },\n    {\n        icon: \"FileText\",\n        title: \"Long-Form Support\",\n        description: \"Create and manage longer posts effortlessly, perfect for in-depth content or thread creation.\",\n    },\n    {\n        icon: \"Lock\",\n        title: \"Open Source & Secure\",\n        description: \"Our product is open source, ensuring transparency and allowing for community contributions while maintaining high security standards.\",\n    },\n];\n\nexport const FeaturesSection = () => {\n    return (\n        <section id=\"features\" className=\"container py-24 sm:py-32\">\n            <h2 className=\"text-lg text-primary text-center mb-2 tracking-wider\">\n                Features\n            </h2>\n\n            <h2 className=\"text-3xl md:text-4xl text-center font-bold mb-4\">\n                Why Choose Our Twitter Card Generator\n            </h2>\n\n            <h3 className=\"md:w-1/2 mx-auto text-xl text-center text-muted-foreground mb-8\">\n                Elevate your Twitter presence with our seamless, customizable, and secure card generator.\n                Create eye-catching content directly on x.com with just a few clicks.\n            </h3>\n\n            <div className=\"grid sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n                {featureList.map(({ icon, title, description }) => (\n                    <div key={title}>\n                        <Card className=\"h-full bg-background border-0 shadow-none\">\n                            <CardHeader className=\"flex justify-center items-center\">\n                                <div className=\"bg-primary/20 p-2 rounded-full ring-8 ring-primary/10 mb-4\">\n                                    <Icon\n                                        name={icon as keyof typeof icons}\n                                        size={24}\n                                        color=\"hsl(var(--primary))\"\n                                        className=\"text-primary\"\n                                    />\n                                </div>\n\n                                <CardTitle>{title}</CardTitle>\n                            </CardHeader>\n\n                            <CardContent className=\"text-muted-foreground text-center\">\n                                {description}\n                            </CardContent>\n                        </Card>\n                    </div>\n                ))}\n            </div>\n        </section>\n    );\n};"
  },
  {
    "path": "src/app/(app)/components/sections/footer.tsx",
    "content": "import Link from \"next/link\";\nimport Logo from \"@assets/icon.png\";\n\nexport const FooterSection = () => {\n    return (\n        <footer id=\"footer\" className=\"container py-24 sm:py-32\">\n            <div className=\"p-10 bg-card border border-secondary rounded-2xl\">\n                <div className=\"grid grid-cols-2 md:grid-cols-4 xl:grid-cols-6 gap-x-12 gap-y-8\">\n                    <div className=\"col-span-full xl:col-span-2\">\n                        <Link href=\"#\" className=\"flex font-bold items-center\">\n                            {/* <ChevronsDownIcon className=\"w-9 h-9 mr-2 bg-gradient-to-tr from-primary via-primary/70 to-primary rounded-lg border border-secondary\" /> */}\n                            <img className=\"w-9 h-9 mr-2 \" src={Logo.src} alt=\"xcards\" ></img>\n                            <h3 className=\"text-2xl\">X Cards</h3>\n                        </Link>\n                    </div>\n\n                    <div className=\"flex flex-col gap-2\">\n                        <h3 className=\"font-bold text-lg\">Contact</h3>\n                        <div>\n                            <Link href=\"https://github.com/hzeyuan/x-cards\" className=\"opacity-60 hover:opacity-100\">\n                                Github\n                            </Link>\n                        </div>\n\n                        <div>\n                            <Link href=\"https://x.com/FeigelC35583\" className=\"opacity-60 hover:opacity-100\">\n                                Twitter\n                            </Link>\n                        </div>\n                    </div>\n\n\n\n                    <div className=\"flex flex-col gap-2\">\n                        <h3 className=\"font-bold text-lg\">Socials</h3>\n\n                        <div>\n                            <Link href=\"https://discord.gg/Prjas7Qh\" className=\"opacity-60 hover:opacity-100\">\n                                Discord\n                            </Link>\n                        </div>\n                    </div>\n                </div>\n\n                {/* <Separator className=\"my-6\" /> */}\n                <section className=\"\">\n                    <h3 className=\"\">\n                        &copy; 2024 Designed and developed by\n                        <Link\n                            target=\"_blank\"\n                            href=\"https://x.com/FeigelC35583\"\n                            className=\"text-primary transition-all border-primary hover:border-b-2 ml-1\"\n                        >\n                            Zane Ryan\n                        </Link>\n                    </h3>\n                </section>\n            </div>\n        </footer>\n    );\n};"
  },
  {
    "path": "src/app/(app)/components/sections/video.tsx",
    "content": "\"use client\"\nimport ReactPlayer from 'react-player'\n\n\nconst VideoSection = () => {\n    return (<div className='mx-auto'>\n        <ReactPlayer url=\"https://youtu.be/okCIZrFrTCE\" />\n    </div>)\n}\n\nexport default VideoSection"
  },
  {
    "path": "src/app/(app)/components/template-list.tsx",
    "content": "import { Button } from \"@components/ui/button\";\nimport { useCardStore } from \"@src/hooks/useCardStore\";\nimport { useTemplatesStore } from \"@src/hooks/useTemplatesStore\";\nimport { X } from \"lucide-react\";\n\nexport const TemplateList = () => {\n    const templates = useTemplatesStore(state => state.templates);\n    const delTemplate = useTemplatesStore(state => state.delTemplate);\n    const setFontStyles = useCardStore(state => state.setFontStyles);\n    const setColorIndex = useCardStore(state => state.setColorIndex);\n    const updateBackgroundStyles = useCardStore(state => state.updateBackgroundStyles);\n    const updateCardStyles = useCardStore(state => state.updateCardStyles);\n    return (\n        <div className=\"flex gap-4 py-4\">\n            {templates.map((t, index) => {\n                return (\n                    <Button size=\"sm\" key={index} variant=\"secondary\"\n                        onClick={() => {\n                            setColorIndex(t.colorIndex)\n                            updateBackgroundStyles(t.backgroundStyles)\n                            updateCardStyles(t.cardStyles)\n                        }\n                        }\n                        className=\"hover:shadow-md\">\n                        <span>\n                            <div>\n                                <div className=\"action-btn flex-shrink-0 flex items-center justify-center cursor-pointer rounded-[4px] text-accent-foreground   \">\n                                    {t.name}\n                                </div>\n                            </div>\n                        </span>\n                        <X className=\" cursor-pointer hover:shadw-md ml-1 w-3 h-3\" onClick={(e) => {\n                            e.preventDefault()\n                            e.stopPropagation()\n                            delTemplate(index)\n\n                        }} />\n                    </Button>\n\n                )\n            })}\n\n        </div>\n    )\n}"
  },
  {
    "path": "src/app/(app)/components/x-form.tsx",
    "content": "import { Button } from \"@components/ui/button\";\nimport { Input } from \"@components/ui/input\";\nimport { useCardStore, type XConfig } from \"@src/hooks/useCardStore\";\nimport { useState } from \"react\";\n\ninterface XFormProps {\n}\n\nexport const XForm = (props: XFormProps) => {\n\n    const [url, setUrl] = useState<string>('');\n    const setXconfig = useCardStore(state => state.setXConfig);\n    const handleGetX = async (url: string) => {\n        const res = await fetch(`/api/x?url=${url}`, {\n            method: 'GET',\n        })\n        const data = await res.json();\n        console.log('res', res);\n        setXconfig({\n            ...data.data,\n            images: [data.data.imageUrl]\n        } as XConfig)\n    }\n\n\n\n    return (\n        <div className=\"flex w-full  pt-8 items-center space-x-2\">\n            <Input onChange={(e) => {\n                setUrl(e.target.value);\n            }} value={url} type=\"url\" placeholder=\"input X url\" />\n            <Button type=\"submit\" onClick={() => {\n                handleGetX(url);\n            }}>Get Tweet →</Button>\n        </div>\n    )\n}"
  },
  {
    "path": "src/app/(app)/layout.tsx",
    "content": "// \"use client\"\nimport Image from 'next/image';\nimport Logo from '@assets/icon.png';\n\nconst AppLayout = ({ children }) => {\n\n    return (\n        <div>\n            <header\n                style={{\n                    zIndex: 9999\n                }}\n                className=\"sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60\">\n                <div className=\"bg-gradient-to-br from-green-300 via-blue-500 to-purple-600 fixed w-full px-4 py-2 text-white !bg-gradient-to-r\">\n                    <p className=\"flex items-center justify-center text-sm font-medium !text-white\">\n                        {\" \"}\n                        X cards also support the chrome extension version.{\" \"}\n                        <a\n                            className=\"underline flex items-center\"\n                            href=\"https://chromewebstore.google.com/detail/x-card/mbinooofmcjhjklihfejnkkebffceeop\"\n                            target=\"_blank\"\n                        >\n                            <span className=\"ml-2 text-xs\">Get Chrome Extension →</span>\n                        </a>\n                    </p>\n                </div>\n                <div className=\" relative  top-[57px] container flex h-14 max-w-screen-2xl items-center\">\n                    <div className=\"mr-4 hidden md:flex\">\n                        <a className=\"mr-6 flex items-center space-x-2\" href=\"/\">\n                            <Image className='w-6 h-6' alt=\"logo\" src={Logo} width={24} ></Image>\n                            <span className=\"hidden  sm:inline-block  whitespace-nowrap\">X Cards</span>\n                        </a>\n                    </div>\n                    <div className=\"w-full flex justify-center\">\n                        <nav className=\"flex items-center gap-4 text-sm lg:gap-6\">\n                        </nav>\n                    </div>\n                    <button\n                        className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:text-accent-foreground h-9 py-2 mr-2 px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden\"\n                        type=\"button\"\n                        aria-haspopup=\"dialog\"\n                        aria-expanded=\"false\"\n                        aria-controls=\"radix-:R4mja:\"\n                        data-state=\"closed\"\n                    >\n                        <svg\n                            strokeWidth=\"1.5\"\n                            viewBox=\"0 0 24 24\"\n                            fill=\"none\"\n                            xmlns=\"http://www.w3.org/2000/svg\"\n                            className=\"h-5 w-5\"\n                        >\n                            <path\n                                d=\"M3 5H11\"\n                                stroke=\"currentColor\"\n                                strokeWidth=\"1.5\"\n                                strokeLinecap=\"round\"\n                                strokeLinejoin=\"round\"\n                            />\n                            <path\n                                d=\"M3 12H16\"\n                                stroke=\"currentColor\"\n                                strokeWidth=\"1.5\"\n                                strokeLinecap=\"round\"\n                                strokeLinejoin=\"round\"\n                            />\n                            <path\n                                d=\"M3 19H21\"\n                                stroke=\"currentColor\"\n                                strokeWidth=\"1.5\"\n                                strokeLinecap=\"round\"\n                                strokeLinejoin=\"round\"\n                            />\n                        </svg>\n                        <span className=\"sr-only\">Toggle Menu</span>\n                    </button>\n                    <div className=\"flex flex-1 items-center justify-between space-x-2 md:justify-end\">\n                        <nav className=\"flex items-center\">\n                            <a\n                                target=\"_blank\"\n                                rel=\"noreferrer\"\n                                href=\"https://github.com/hzeyuan/tweet-cards\"\n                            >\n                                <div className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 py-2 w-9 px-0\">\n                                    <svg viewBox=\"0 0 438.549 438.549\" className=\"h-4 w-4\">\n                                        <path\n                                            fill=\"currentColor\"\n                                            d=\"M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z\"\n                                        />\n                                    </svg>\n                                    <span className=\"sr-only\">GitHub</span>\n                                </div>\n                            </a>\n                            <a\n                                target=\"_blank\"\n                                rel=\"noreferrer\"\n                                href=\"https://x.com/FeigelC35583\"\n                            >\n                                <div className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 py-2 w-9 px-0\">\n                                    <svg\n                                        className=\"h-3 w-3 fill-current\"\n                                        height={23}\n                                        viewBox=\"0 0 1200 1227\"\n                                        width={23}\n                                        xmlns=\"http://www.w3.org/2000/svg\"\n                                    >\n                                        <path d=\"M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z\" />\n                                    </svg>\n                                    <span className=\"sr-only\">Twitter</span>\n                                </div>\n                            </a>\n                            <button\n                                className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 py-2 w-9 px-0\"\n                                type=\"button\"\n                                id=\"radix-:R3mmja:\"\n                                aria-haspopup=\"menu\"\n                                aria-expanded=\"false\"\n                                data-state=\"closed\"\n                            >\n                                <svg\n                                    width={15}\n                                    height={15}\n                                    viewBox=\"0 0 15 15\"\n                                    fill=\"none\"\n                                    xmlns=\"http://www.w3.org/2000/svg\"\n                                    className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\"\n                                >\n                                    <path\n                                        d=\"M7.5 0C7.77614 0 8 0.223858 8 0.5V2.5C8 2.77614 7.77614 3 7.5 3C7.22386 3 7 2.77614 7 2.5V0.5C7 0.223858 7.22386 0 7.5 0ZM2.1967 2.1967C2.39196 2.00144 2.70854 2.00144 2.90381 2.1967L4.31802 3.61091C4.51328 3.80617 4.51328 4.12276 4.31802 4.31802C4.12276 4.51328 3.80617 4.51328 3.61091 4.31802L2.1967 2.90381C2.00144 2.70854 2.00144 2.39196 2.1967 2.1967ZM0.5 7C0.223858 7 0 7.22386 0 7.5C0 7.77614 0.223858 8 0.5 8H2.5C2.77614 8 3 7.77614 3 7.5C3 7.22386 2.77614 7 2.5 7H0.5ZM2.1967 12.8033C2.00144 12.608 2.00144 12.2915 2.1967 12.0962L3.61091 10.682C3.80617 10.4867 4.12276 10.4867 4.31802 10.682C4.51328 10.8772 4.51328 11.1938 4.31802 11.3891L2.90381 12.8033C2.70854 12.9986 2.39196 12.9986 2.1967 12.8033ZM12.5 7C12.2239 7 12 7.22386 12 7.5C12 7.77614 12.2239 8 12.5 8H14.5C14.7761 8 15 7.77614 15 7.5C15 7.22386 14.7761 7 14.5 7H12.5ZM10.682 4.31802C10.4867 4.12276 10.4867 3.80617 10.682 3.61091L12.0962 2.1967C12.2915 2.00144 12.608 2.00144 12.8033 2.1967C12.9986 2.39196 12.9986 2.70854 12.8033 2.90381L11.3891 4.31802C11.1938 4.51328 10.8772 4.51328 10.682 4.31802ZM8 12.5C8 12.2239 7.77614 12 7.5 12C7.22386 12 7 12.2239 7 12.5V14.5C7 14.7761 7.22386 15 7.5 15C7.77614 15 8 14.7761 8 14.5V12.5ZM10.682 10.682C10.8772 10.4867 11.1938 10.4867 11.3891 10.682L12.8033 12.0962C12.9986 12.2915 12.9986 12.608 12.8033 12.8033C12.608 12.9986 12.2915 12.9986 12.0962 12.8033L10.682 11.3891C10.4867 11.1938 10.4867 10.8772 10.682 10.682ZM5.5 7.5C5.5 6.39543 6.39543 5.5 7.5 5.5C8.60457 5.5 9.5 6.39543 9.5 7.5C9.5 8.60457 8.60457 9.5 7.5 9.5C6.39543 9.5 5.5 8.60457 5.5 7.5ZM7.5 4.5C5.84315 4.5 4.5 5.84315 4.5 7.5C4.5 9.15685 5.84315 10.5 7.5 10.5C9.15685 10.5 10.5 9.15685 10.5 7.5C10.5 5.84315 9.15685 4.5 7.5 4.5Z\"\n                                        fill=\"currentColor\"\n                                        fillRule=\"evenodd\"\n                                        clipRule=\"evenodd\"\n                                    />\n                                </svg>\n                                <svg\n                                    width={15}\n                                    height={15}\n                                    viewBox=\"0 0 15 15\"\n                                    fill=\"none\"\n                                    xmlns=\"http://www.w3.org/2000/svg\"\n                                    className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\"\n                                >\n                                    <path\n                                        d=\"M2.89998 0.499976C2.89998 0.279062 2.72089 0.0999756 2.49998 0.0999756C2.27906 0.0999756 2.09998 0.279062 2.09998 0.499976V1.09998H1.49998C1.27906 1.09998 1.09998 1.27906 1.09998 1.49998C1.09998 1.72089 1.27906 1.89998 1.49998 1.89998H2.09998V2.49998C2.09998 2.72089 2.27906 2.89998 2.49998 2.89998C2.72089 2.89998 2.89998 2.72089 2.89998 2.49998V1.89998H3.49998C3.72089 1.89998 3.89998 1.72089 3.89998 1.49998C3.89998 1.27906 3.72089 1.09998 3.49998 1.09998H2.89998V0.499976ZM5.89998 3.49998C5.89998 3.27906 5.72089 3.09998 5.49998 3.09998C5.27906 3.09998 5.09998 3.27906 5.09998 3.49998V4.09998H4.49998C4.27906 4.09998 4.09998 4.27906 4.09998 4.49998C4.09998 4.72089 4.27906 4.89998 4.49998 4.89998H5.09998V5.49998C5.09998 5.72089 5.27906 5.89998 5.49998 5.89998C5.72089 5.89998 5.89998 5.72089 5.89998 5.49998V4.89998H6.49998C6.72089 4.89998 6.89998 4.72089 6.89998 4.49998C6.89998 4.27906 6.72089 4.09998 6.49998 4.09998H5.89998V3.49998ZM1.89998 6.49998C1.89998 6.27906 1.72089 6.09998 1.49998 6.09998C1.27906 6.09998 1.09998 6.27906 1.09998 6.49998V7.09998H0.499976C0.279062 7.09998 0.0999756 7.27906 0.0999756 7.49998C0.0999756 7.72089 0.279062 7.89998 0.499976 7.89998H1.09998V8.49998C1.09998 8.72089 1.27906 8.89997 1.49998 8.89997C1.72089 8.89997 1.89998 8.72089 1.89998 8.49998V7.89998H2.49998C2.72089 7.89998 2.89998 7.72089 2.89998 7.49998C2.89998 7.27906 2.72089 7.09998 2.49998 7.09998H1.89998V6.49998ZM8.54406 0.98184L8.24618 0.941586C8.03275 0.917676 7.90692 1.1655 8.02936 1.34194C8.17013 1.54479 8.29981 1.75592 8.41754 1.97445C8.91878 2.90485 9.20322 3.96932 9.20322 5.10022C9.20322 8.37201 6.82247 11.0878 3.69887 11.6097C3.45736 11.65 3.20988 11.6772 2.96008 11.6906C2.74563 11.702 2.62729 11.9535 2.77721 12.1072C2.84551 12.1773 2.91535 12.2458 2.98667 12.3128L3.05883 12.3795L3.31883 12.6045L3.50684 12.7532L3.62796 12.8433L3.81491 12.9742L3.99079 13.089C4.11175 13.1651 4.23536 13.2375 4.36157 13.3059L4.62496 13.4412L4.88553 13.5607L5.18837 13.6828L5.43169 13.7686C5.56564 13.8128 5.70149 13.8529 5.83857 13.8885C5.94262 13.9155 6.04767 13.9401 6.15405 13.9622C6.27993 13.9883 6.40713 14.0109 6.53544 14.0298L6.85241 14.0685L7.11934 14.0892C7.24637 14.0965 7.37436 14.1002 7.50322 14.1002C11.1483 14.1002 14.1032 11.1453 14.1032 7.50023C14.1032 7.25044 14.0893 7.00389 14.0623 6.76131L14.0255 6.48407C13.991 6.26083 13.9453 6.04129 13.8891 5.82642C13.8213 5.56709 13.7382 5.31398 13.6409 5.06881L13.5279 4.80132L13.4507 4.63542L13.3766 4.48666C13.2178 4.17773 13.0353 3.88295 12.8312 3.60423L12.6782 3.40352L12.4793 3.16432L12.3157 2.98361L12.1961 2.85951L12.0355 2.70246L11.8134 2.50184L11.4925 2.24191L11.2483 2.06498L10.9562 1.87446L10.6346 1.68894L10.3073 1.52378L10.1938 1.47176L9.95488 1.3706L9.67791 1.2669L9.42566 1.1846L9.10075 1.09489L8.83599 1.03486L8.54406 0.98184ZM10.4032 5.30023C10.4032 4.27588 10.2002 3.29829 9.83244 2.40604C11.7623 3.28995 13.1032 5.23862 13.1032 7.50023C13.1032 10.593 10.596 13.1002 7.50322 13.1002C6.63646 13.1002 5.81597 12.9036 5.08355 12.5522C6.5419 12.0941 7.81081 11.2082 8.74322 10.0416C8.87963 10.2284 9.10028 10.3497 9.34928 10.3497C9.76349 10.3497 10.0993 10.0139 10.0993 9.59971C10.0993 9.24256 9.84965 8.94373 9.51535 8.86816C9.57741 8.75165 9.63653 8.63334 9.6926 8.51332C9.88358 8.63163 10.1088 8.69993 10.35 8.69993C11.0403 8.69993 11.6 8.14028 11.6 7.44993C11.6 6.75976 11.0406 6.20024 10.3505 6.19993C10.3853 5.90487 10.4032 5.60464 10.4032 5.30023Z\"\n                                        fill=\"currentColor\"\n                                        fillRule=\"evenodd\"\n                                        clipRule=\"evenodd\"\n                                    />\n                                </svg>\n                                <span className=\"sr-only\">Toggle theme</span>\n                            </button>\n                        </nav>\n                    </div>\n                </div>\n            </header>\n            <main>\n                {children}\n            </main>\n        </div>\n    )\n}\n\nexport default AppLayout;\n"
  },
  {
    "path": "src/app/(app)/page.tsx",
    "content": "import * as _ from 'lodash-es';\nimport Hero from \"./components/hero\";\nimport { cn } from \"@lib/utils\";\nimport gradientBottomSvg from '@assets/gradient-bottom.svg';\nimport { FooterSection } from './components/sections/footer';\nimport FeaturesGridSection from './components/sections/FeaturesGridSection';\n\nconst HomePage = () => {\n\n    return (\n        <>\n            <div className=\"    relative pb-14  h-screen w-full dark:bg-black bg-white  dark:bg-grid-white/[0.2] bg-grid-black/[0.2]  flex items-center justify-center\">\n\n                <div className=\"z-0 absolute w-80 h-60 bg-blue-400 blur-[80px] opacity-30 top-40 left-40\"></div>\n                <div className=\"z-0 absolute w-80 h-60 bg-purple-400 blur-[80px] opacity-30 top-40 right-40\"></div>\n                <div\n                    className=\"absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80\"\n                    aria-hidden=\"true\"\n                >\n                    <div\n                        className=\"relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-[#d797e7] to-[#75a6f5] opacity-30 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]\"\n                        style={{\n                            clipPath:\n                                \"polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)\"\n                        }}\n                    />\n                </div>\n\n                <div\n                    style={{\n                        background: `url('${gradientBottomSvg.src}')`,\n                        backgroundSize: 'cover',\n                        backgroundPosition: '50% 100%',\n                    }}\n                    className={cn(\n                        \"absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-black  [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)]\",\n                        ` bg-no-repeat bg-cover bg-[50%_100%]`\n                    )}>\n\n                </div>\n                <div className=\" mx-auto w-full max-w-7xl px-6 md:px-8 lg:px-12\">\n                    <div className=\"pt-8\">\n                        <Hero />\n                    </div>\n                </div>\n            </div>\n\n            {/* <Reason /> */}\n            {/* <Features /> */}\n            <FeaturesGridSection></FeaturesGridSection>\n            <FooterSection />\n            {/* <WhyChooseUs /> */}\n            {/* <FaqSection /> */}\n            {/* <Newsletter /> */}\n        </>\n    );\n};\n\nexport default HomePage;"
  },
  {
    "path": "src/app/(app)/request.tsx",
    "content": ""
  },
  {
    "path": "src/app/(extension)/independent/components/index.tsx",
    "content": "\"use client\"\nimport { useEffect, useRef } from \"react\";\nimport { useCardStore, type XConfig } from \"@src/hooks/useCardStore\";\nimport { CardGenerator } from \"../../../(app)/components/card-generator\";\nimport * as _ from \"lodash-es\"\nimport { generateImage } from \"@src/app/utils/image\";\nimport type { CardConfig } from \"@src/components/extension/use-tweet-collection\";\n\n\n\nexport async function generateStaticParams() {\n    return [{ slug: 'independent' }]\n}\n\nconst Index = () => {\n\n    useEffect(() => {\n        const fn = async (event) => {\n            // if (event.origin !== 'https://x-cards.net') return;\n            const actionName = 'generate-card-local'\n            if (event.data.action === actionName) {\n                console.log('收到消息来自', event.data, event.origin);\n\n                const body = event.data.body as {\n                    tweetInfo: XConfig[],\n                    cardConfig: CardConfig,\n                }\n                const { tweetInfo, cardConfig } = body;\n                useCardStore.getState().setXConfig(tweetInfo)\n\n                if (cardConfig?.colorIndex) {\n                    useCardStore.getState().setColorIndex(cardConfig.colorIndex);\n                }\n\n                if (cardConfig.padding >= 0) {\n                    useCardStore.getState().updateBackgroundStyles({\n                        padding: cardConfig.padding\n                    });\n                }\n\n                if (cardConfig.fontSize\n                ) {\n                    useCardStore.getState().updateCardStyles({\n                        fontSize: cardConfig.fontSize,\n                        fontFamily: cardConfig.fontFamily || 'Inter'\n                    });\n                }\n\n                // if (cardConfig.fontFamily) {\n                //     loadFont(cardConfig.fontFamily);\n                // }\n\n                if (cardConfig?.controls) {\n                    useCardStore.getState().updateCardStyles({\n                        controls: cardConfig.controls\n                    });\n                }\n\n                if (cardConfig?.width) {\n                    useCardStore.getState().updateCardStyles({\n                        width: cardConfig.width,\n                        // height: cardConfig.height\n                    });\n                }\n\n                // const loadedImages = useCardStore.getState().loadedImages;\n                // const images = _.flatten(tweetInfo.map((tweet) => tweet.images));\n                // let allImagesLoaded = _.every(images, (src) => loadedImages[src] === 'success' || loadedImages[src] === 'error');\n                // // console.log('本次等待加载的图片', images);\n                // while (!allImagesLoaded) {\n                //     // console.log('等待图片加载完成', loadedImages);\n                //     allImagesLoaded = _.every(images, (src) => loadedImages[src] === 'success' || loadedImages[src] === 'error');\n                //     await new Promise((resolve) => setTimeout(resolve, 1000));\n                // }\n\n                requestAnimationFrame(async () => {\n                    const dataUrl = await generateImage({\n                        data: tweetInfo,\n                        format: cardConfig?.format || 'png',\n                        scale: cardConfig?.scale || 2,\n                        fontFamily: cardConfig.fontFamily || false,\n                    });\n                    if (event.source && event.source.postMessage) {\n                        event.source.postMessage({\n                            action: 'generate-card-local', value: {\n                                dataUrl: dataUrl\n                            }\n                        }, event.origin);\n                    }\n                });\n            }\n        }\n        if (typeof window !== 'undefined') {\n            window.addEventListener('message', fn);\n\n            return () => {\n                window.removeEventListener('message', fn);\n            }\n        }\n    }, [])\n\n\n\n\n\n    return (\n        <CardGenerator></CardGenerator>\n    );\n}\n\nexport default Index;"
  },
  {
    "path": "src/app/(extension)/independent/page.tsx",
    "content": "// import Index from \"./components\";\n\nexport async function generateStaticParams() {\n    return [{ slug: 'independent' }]\n}\n\nimport dynamic from 'next/dynamic'\n\nconst Index = dynamic(() => import('./components'), {\n    ssr: false,\n});\n\n\nconst Page = () => {\n    return (\n        <div className=\"relative h-full w-full bg-white\">\n            <div className=\"pt-12 \">\n                <Index></Index>\n            </div>\n        </div>\n    );\n}\n\nexport default Page;"
  },
  {
    "path": "src/app/(extension)/welcome/page.tsx",
    "content": "import { cn } from \"@lib/utils\"\nimport gradientBottomSvg from '@assets/gradient-bottom.svg';\nimport introPng from '@assets/install_guide/intro.png';\nimport Image from 'next/image';\nimport XCardLogo from '@assets/icon.png';\nimport VideoSection from \"@src/app/(app)/components/sections/video\";\n\nconst WelcomePage = () => {\n    return (<div className=\" pb-14 min-h-[100vh]   w-full dark:bg-black bg-white  dark:bg-grid-white/[0.2] bg-grid-black/[0.2] relative flex items-center justify-center\">\n        <div className=\"z-0 absolute w-80 h-60 bg-blue-400 blur-[80px] opacity-30 top-40 left-40\"></div>\n        <div className=\"z-0 absolute w-80 h-60 bg-purple-400 blur-[80px] opacity-30 top-40 right-40\"></div>\n        <div\n            className=\"absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80\"\n            aria-hidden=\"true\"\n        >\n            <div\n                className=\"relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-[#d797e7] to-[#75a6f5] opacity-30 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]\"\n                style={{\n                    clipPath:\n                        \"polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)\"\n                }}\n            />\n        </div>\n\n        <div\n            style={{\n                background: `url('${gradientBottomSvg.src}')`,\n                backgroundSize: 'cover',\n                backgroundPosition: '50% 100%',\n            }}\n            className={cn(\n                \"absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-black  [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)]\",\n                ` bg-no-repeat bg-cover bg-[50%_100%]`\n            )}></div>\n        <div className=\"  mx-auto w-full max-w-7xl px-6 md:px-8\">\n            <div className=\"pt-8\">\n\n                <div className='flex flex-col opacity-100 gap-y-8 justify-center'>\n\n                    <div className=\"w-full flex  justify-center\">\n                        <img\n                            src={XCardLogo.src}\n                            alt=\"\"\n                            className=\" h-[64px] w-[64px]\"\n                        />\n                    </div>\n\n\n                    <div className=\"relative mx-auto flex  flex-col items-center\">\n                        <h2 className=\"text-center text-3xl font-medium text-gray-900 dark:text-gray-50 sm:text-6xl mb-10\">\n                            Welcome to X cards  {' '}\n                        </h2>\n                        <div className=\"w-full   pb-10 flex gap-x-10\">\n                            <div>\n                                <div className=\"relative z-20 pt-10   mx-auto\">\n                                    <h4 className=\"text-2xl lg:text-3xl lg:leading-tight max-w-5xl mx-auto text-center tracking-tight font-medium text-black dark:text-white\">\n                                        Now Open x.com,and Generate Cards immediately\n                                    </h4>\n\n                                </div>\n\n\n\n                                <div className=\" py-10\">\n                                    <Image src={introPng.src} width={768} height={440} alt=\"xcards intro\"></Image>\n                                </div>\n                                <div className=\"flex flex-col justify-start space-y-12 text-center \">\n\n                                    <div>\n                                        <div style={{ opacity: 1, willChange: \"auto\", transform: \"none\" }}>\n                                            <span className=\" bg-[aquamarine] hover:shadow-xl hover:opacity-80  border  cursor-pointer rounded-lg px-2 py-1 text-5xl font-bold text-black dark:text-white\">\n                                                Enjoy!!\n                                            </span>\n                                        </div>\n                                    </div>\n                                </div>\n                            </div>\n                            <VideoSection />\n\n                        </div>\n\n\n                        <div className=\"flex items-center\">\n                            <a\n                                target=\"_blank\"\n                                rel=\"noreferrer\"\n                                href=\"https://github.com/hzeyuan/x-cards\"\n                            >\n                                <div className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 py-2 w-9 px-0\">\n                                    <svg viewBox=\"0 0 438.549 438.549\" className=\"h-4 w-4\">\n                                        <path\n                                            fill=\"currentColor\"\n                                            d=\"M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z\"\n                                        />\n                                    </svg>\n                                    <span className=\"sr-only\">GitHub</span>\n                                </div>\n                            </a>\n                            <a target=\"_blank\" rel=\"noreferrer\" href=\"https://x.com/FeigelC35583\">\n                                <div className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 py-2 w-9 px-0\">\n                                    <svg\n                                        className=\"h-3 w-3 fill-current\"\n                                        height={23}\n                                        viewBox=\"0 0 1200 1227\"\n                                        width={23}\n                                        xmlns=\"http://www.w3.org/2000/svg\"\n                                    >\n                                        <path d=\"M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z\" />\n                                    </svg>\n                                    <span className=\"sr-only\">Twitter</span>\n                                </div>\n                            </a>\n                            <a target=\"_blank\" rel=\"noreferrer\" href=\"https://discord.gg/ZwcKez9E\">\n                                <div className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 py-2 w-9 px-0\">\n\n                                    <svg\n                                        className=\"icon\"\n                                        viewBox=\"0 0 1024 1024\"\n                                        xmlns=\"http://www.w3.org/2000/svg\"\n                                        height={23}\n                                        width={23}\n                                    >\n                                        <path d=\"M0 512a512 512 0 101024 0A512 512 0 100 512z\" fill=\"#738BD8\" />\n                                        <path d=\"M190.915 234.305h642.169v477.288H190.915z\" fill=\"#FFF\" />\n                                        <path\n                                            d=\"M698.157 932.274L157.288 862.85c-58.43-7.5-55.4-191.167-50.26-249.853l26.034-297.22c5.14-58.686 74.356-120.22 132.7-128.362l466.441-65.085c58.346-8.14 177.24 212.65 176.09 271.548l-8.677 445.108M512 300.373c-114.347 0-194.56 49.067-194.56 49.067 43.947-39.253 120.747-61.867 120.747-61.867l-7.254-7.253c-72.106 1.28-137.386 51.2-137.386 51.2-73.387 153.173-68.694 285.44-68.694 285.44 59.734 77.227 148.48 71.68 148.48 71.68l30.294-38.4c-53.334-11.52-87.04-58.88-87.04-58.88S396.8 645.973 512 645.973c115.2 0 195.413-54.613 195.413-54.613s-33.706 47.36-87.04 58.88l30.294 38.4s88.746 5.547 148.48-71.68c0 0 4.693-132.267-68.694-285.44 0 0-65.28-49.92-137.386-51.2l-7.254 7.253s76.8 22.614 120.747 61.867c0 0-80.213-49.067-194.56-49.067M423.68 462.08c27.733 0 50.347 24.32 49.92 54.187 0 29.44-22.187 54.186-49.92 54.186-27.307 0-49.493-24.746-49.493-54.186 0-29.867 21.76-54.187 49.493-54.187m177.92 0c27.733 0 49.92 24.32 49.92 54.187 0 29.44-22.187 54.186-49.92 54.186-27.307 0-49.493-24.746-49.493-54.186 0-29.867 21.76-54.187 49.493-54.187z\"\n                                            fill=\"#738BD8\"\n                                        />\n                                    </svg>\n                                    <span className=\"sr-only\">Discord</span>\n                                </div>\n                            </a>\n                        </div>\n\n                    </div>\n                    {/* <VideoSection /> */}\n                    {/* <FeaturesSection></FeaturesSection> */}\n                </div>\n            </div>\n        </div>\n    </div>)\n}\nexport default WelcomePage;"
  },
  {
    "path": "src/app/api/license/route.ts",
    "content": "import { NextResponse, type NextRequest } from \"next/server\";\nimport { createClient } from '@supabase/supabase-js'\n\nexport const POST = async (req: NextRequest) => {\n\n\n\n    const { purchaseCode } = await req.json();\n\n    // 确保 purchaseCode 不为空\n    if (!purchaseCode) {\n        return NextResponse.json({ message: 'Purchase code is required' }, { status: 400 });\n    }\n\n    const supabase = createClient(\n        process.env.NEXT_PUBLIC_SUPABASE_URL!,\n        process.env.SUPABASE_SERVICE_KEY!\n    );\n\n    try {\n        // 查询注册码\n        const { data, error } = await supabase\n            .from('licenses')\n            .select('*')\n            .eq('code', purchaseCode)\n            .single();\n\n\n\n        if (error) {\n            console.error('Error querying database:', error);\n            return NextResponse.json({ message: 'Internal server error' }, { status: 500 });\n        }\n\n        if (!data) {\n            return NextResponse.json({ message: 'Invalid purchase code' }, { status: 400 });\n        }\n\n        // 检查是否已被使用\n        // if (data.is_used === 1) {\n        //     return NextResponse.json({ message: 'Purchase code has already been used' }, { status: 400 });\n        // }\n\n        // 标记为已使用\n        const { error: updateError } = await supabase\n            .from('licenses')\n            .update({ is_used: data.is_used + 1 })\n            .eq('id', data.id);\n\n        if (updateError) {\n            console.error('Error updating license:', updateError);\n            return NextResponse.json({ message: 'Error activating license' }, { status: 500 });\n        }\n\n        return NextResponse.json({ message: 'License activated', });\n\n    } catch (error) {\n        console.error('Error checking purchase code:', error);\n        return NextResponse.json({ message: 'Internal server error' }, { status: 500 });\n    }\n};\n"
  },
  {
    "path": "src/app/api/x/route.ts",
    "content": "// import type { NextApiRequest } from \"next\";\n// import { NextResponse, type NextRequest } from \"next/server\";\n\n// // export const dynamic = 'dynamic';\n\n\n// export const GET = async (req: NextApiRequest) => {\n//     // const url = req.searchParams.get('url');\n//     const url = req.url;\n//     const regex = /[?&]url=([^&#]*)/;\n//     const match = url.match(regex);\n//     const extractedUrl = match ? decodeURIComponent(match[1]) : null;\n//     const res = await fetch(`https://cmt.itsvg.in/api?url=${extractedUrl}`, {\n//         method: 'GET',\n\n//     })\n//     const data = await res.json();\n//     console.log('res', data);\n//     return NextResponse.json({\n//         status: 200,\n//         data,\n//     });\n// }"
  },
  {
    "path": "src/app/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --foreground: 20 14.3% 4.1%;\n    --card: 0 0% 100%;\n    --card-foreground: 20 14.3% 4.1%;\n    --popover: 0 0% 100%;\n    --popover-foreground: 20 14.3% 4.1%;\n    --primary: 47.9 95.8% 53.1%;\n    --primary-foreground: 26 83.3% 14.1%;\n    --secondary: 60 4.8% 95.9%;\n    --secondary-foreground: 24 9.8% 10%;\n    --muted: 60 4.8% 95.9%;\n    --muted-foreground: 25 5.3% 44.7%;\n    --accent: 60 4.8% 95.9%;\n    --accent-foreground: 24 9.8% 10%;\n    --destructive: 0 84.2% 60.2%;\n    --destructive-foreground: 60 9.1% 97.8%;\n    --border: 20 5.9% 90%;\n    --input: 20 5.9% 90%;\n    --ring: 20 14.3% 4.1%;\n    --radius: 0.5rem;\n    --chart-1: 12 76% 61%;\n    --chart-2: 173 58% 39%;\n    --chart-3: 197 37% 24%;\n    --chart-4: 43 74% 66%;\n    --chart-5: 27 87% 67%;\n  }\n\n  .dark {\n    --background: 20 14.3% 4.1%;\n    --foreground: 60 9.1% 97.8%;\n    --card: 20 14.3% 4.1%;\n    --card-foreground: 60 9.1% 97.8%;\n    --popover: 20 14.3% 4.1%;\n    --popover-foreground: 60 9.1% 97.8%;\n    --primary: 47.9 95.8% 53.1%;\n    --primary-foreground: 26 83.3% 14.1%;\n    --secondary: 12 6.5% 15.1%;\n    --secondary-foreground: 60 9.1% 97.8%;\n    --muted: 12 6.5% 15.1%;\n    --muted-foreground: 24 5.4% 63.9%;\n    --accent: 12 6.5% 15.1%;\n    --accent-foreground: 60 9.1% 97.8%;\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 60 9.1% 97.8%;\n    --border: 12 6.5% 15.1%;\n    --input: 12 6.5% 15.1%;\n    --ring: 35.5 91.7% 32.9%;\n    --chart-1: 220 70% 50%;\n    --chart-2: 160 60% 45%;\n    --chart-3: 30 80% 55%;\n    --chart-4: 280 65% 60%;\n    --chart-5: 340 75% 55%;\n  }\n}\n\n/* #card {\n  font-family:\n    ui-sans-serif,\n    system-ui,\n    -apple-system,\n    \"system-ui\",\n    \"Segoe UI\",\n    Roboto,\n    \"Helvetica Neue\",\n    Arial,\n    \"Noto Sans\",\n    sans-serif,\n    \"Apple Color Emoji\",\n    \"Segoe UI Emoji\",\n    \"Segoe UI Symbol\",\n    \"Noto Color Emoji\";\n} */\n\n.sidebar-active {\n  --tw-bg-opacity: 0.2;\n  /* background-color: rgba(243, 244, 246, var(--tw-bg-opacity)); bg-gray-100 */\n  background-color: rgba(156, 163, 175, var(--tw-bg-opacity)); /* bg-gray-400 */\n  --tw-text-opacity: 1;\n  color: rgba(11, 166, 101, var(--tw-text-opacity));\n  /* text-indigo-500 */\n}\n\n.no-scrollbar::-webkit-scrollbar {\n  display: none;\n}\n\n.no-scrollbar {\n  -ms-overflow-style: none; /* IE and Edge */\n  scrollbar-width: none; /* Firefox */\n}\n"
  },
  {
    "path": "src/app/layout.tsx",
    "content": "import { Toaster } from \"@/components/ui/sonner\"\nimport Logo from '@assets/icon.png'\nimport './globals.css'\nimport { siteConfig } from \"@src/config/site\";\n\nexport const metadata = {\n  title: siteConfig.name,\n  description: siteConfig.description,\n  keywords: siteConfig.keywords,\n  authors: siteConfig.authors,\n  creator: siteConfig.creator,\n  themeColor: siteConfig.themeColor,\n  icons: siteConfig.icons,\n  // metadataBase: siteConfig.metadataBase,\n  openGraph: siteConfig.openGraph,\n  twitter: siteConfig.twitter,\n};\n\n\n\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode\n}) {\n\n\n  const getClarityAnalyticsTag = () => {\n    return {\n      __html: `\n       (function(c,l,a,r,i,t,y){\n        c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};\n        t=l.createElement(r);t.async=1;t.src=\"https://www.clarity.ms/tag/\"+i;\n        y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);\n    })(window, document, \"clarity\", \"script\", \"nn68ny4psp\");`,\n    }\n  }\n\n  const getGoogleAnalyticsTag = () => {\n    return {\n      __html: `\n      window.dataLayer = window.dataLayer || [];\n  function gtag(){dataLayer.push(arguments);}\n  gtag('js', new Date());\n\n  gtag('config', 'G-18KTPX8NV1');\n      `,\n    }\n  }\n\n\n  return (\n    <html lang=\"en\">\n      <head>\n        <script async src=\"https://www.googletagmanager.com/gtag/js?id=G-18KTPX8NV1\"></script>\n        <script dangerouslySetInnerHTML={getGoogleAnalyticsTag()} />\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <title>{metadata.title}</title>\n        <meta name=\"description\" content={metadata.description} />\n        <link rel=\"icon\" href={Logo.src} />\n      </head>\n      <body>\n        {children}\n        <Toaster\n          richColors\n          position=\"top-center\"\n          duration={2000}\n        />\n      </body>\n      {/* <Analytics /> */}\n      <script dangerouslySetInnerHTML={getClarityAnalyticsTag()} />\n    </html>\n  )\n}\n"
  },
  {
    "path": "src/app/utils/IFrameMessageSystem.ts",
    "content": "type MessageCallback = (value: any) => void;\n\ninterface MessageEvent {\n  data: {\n    action: string;\n    value: any;\n  };\n}\n\nclass IFrameMessageSystem {\n  private subscribers: Map<string, Set<MessageCallback>>;\n\n  constructor() {\n    this.subscribers = new Map();\n    this.handleMessage = this.handleMessage.bind(this);\n    window.addEventListener('message', this.handleMessage as unknown as EventListener);\n  }\n\n  subscribe(action: string, callback: MessageCallback): () => void {\n    if (!this.subscribers.has(action)) {\n      this.subscribers.set(action, new Set());\n    }\n    this.subscribers.get(action)!.add(callback);\n    return () => this.unsubscribe(action, callback);\n  }\n\n  unsubscribe(action: string, callback: MessageCallback): void {\n    if (this.subscribers.has(action)) {\n      this.subscribers.get(action)!.delete(callback);\n    }\n  }\n\n  publish(iframe: HTMLIFrameElement, action: string, data: any, timeout: number = 10000): Promise<any> {\n    return new Promise((resolve, reject) => {\n      const timeoutId = setTimeout(() => {\n        unsubscribe();\n        reject(new Error('Iframe message timeout'));\n      }, timeout);\n\n      const responseAction = action; // 使用相同的 action 名称\n      const responseHandler = (response: any) => {\n        clearTimeout(timeoutId);\n        unsubscribe();\n        resolve(response);\n      };\n\n      const unsubscribe = this.subscribe(responseAction, responseHandler);\n\n      iframe.contentWindow?.postMessage({\n        action,\n        body: data\n      }, '*');\n    });\n  }\n\n  private handleMessage(event: MessageEvent): void {\n    const { action, value } = event.data;\n    if (this.subscribers.has(action)) {\n      this.subscribers.get(action)!.forEach(callback => callback(value));\n    }\n  }\n}\n\nexport const iframeMessageSystem = new IFrameMessageSystem();"
  },
  {
    "path": "src/app/utils/element.ts",
    "content": "export function traverseAndCheck(element, condition) {\n    if (condition(element)) return true;\n    return Array.from(element.children).some(child => traverseAndCheck(child, condition));\n}\n\n\n// export function getAdjacentCellDiv(element, direction) {\n//     let currentElement = element.closest('div[data-testid=\"cellInnerDiv\"]');\n//     while (currentElement) {\n//         currentElement = direction === 'next' ? currentElement.nextElementSibling : currentElement.previousElementSibling;\n//         if (!currentElement) break;\n//         if (currentElement && currentElement.getAttribute('data-testid') === 'cellInnerDiv') {\n//             return currentElement;\n//         }\n//     }\n//     return null;\n// }\n\nexport function getAdjacentCellDiv(element, direction) {\n    const sibling = direction === 'next' ? 'nextElementSibling' : 'previousElementSibling';\n    let current = element.closest('div[data-testid=\"cellInnerDiv\"]');\n    while (current = current[sibling]) {\n        if (current.getAttribute('data-testid') === 'cellInnerDiv') return current;\n    }\n    return null;\n}\n\n\n\nexport function checkAppliedStyle(element, properties) {\n    let foundProperties = new Set();\n\n    for (let sheet of document.styleSheets) {\n        for (let rule of sheet.cssRules) {\n            if (element.matches(rule.selectorText)) {\n                for (let [prop, value] of properties) {\n                    if (rule.style[prop] === value) {\n                        foundProperties.add(prop);\n                    }\n                }\n            }\n        }\n    }\n\n    return foundProperties.size === properties.length;\n}"
  },
  {
    "path": "src/app/utils/export.ts",
    "content": "import type {XConfig} from \"@src/hooks/useCardStore\";\n\nimport JSZip from 'jszip';\nimport {saveAs} from 'file-saver';\n\n\nexport const tweet2Markdown = (tweetData: XConfig[]) => {\n    const mdArray = tweetData.map((tweet) => {\n        const {\n            authorUrl,\n            username,\n            time,\n            text,\n            url,\n            likes,\n            shares,\n            replies,\n            video,\n            images\n        } = tweet;\n\n        const formattedDate = new Date(time * 1000).toLocaleString();\n\n        let markdownContent = `# Tweet by ${username}\\n\\n`;\n        markdownContent += `*Posted on ${formattedDate}*\\n\\n`;\n        markdownContent += `${text}\\n\\n`;\n\n        if (video && video.poster) {\n            markdownContent += `![Video Thumbnail](${video.poster})\\n\\n`;\n            markdownContent += `*This tweet contains a video*\\n\\n`;\n        }\n\n        if (images && images.length > 0) {\n            images.forEach((img, index) => {\n                markdownContent += `![Image ${index + 1}](${img})\\n\\n`;\n            });\n        }\n\n        //账号URL\n        markdownContent += `[${username}](${authorUrl})\\n\\n`;\n\n        //帖子URL\n        const displayText = text.replace(/\\n/g, '').length > 100\n            ? `${text.replace(/\\n/g, '').substring(0, 100)}...`\n            : text.replace(/\\n/g, '');\n        markdownContent += `[${displayText}](${url})\\n\\n`;\n\n        markdownContent += `--- \\n\\n`;\n        markdownContent += `**Stats**\\n\\n`;\n        markdownContent += `Likes: ${likes}\\n`;\n        markdownContent += `Shares: ${shares}\\n`;\n        markdownContent += `Replies: ${replies}\\n\\n`;\n\n        // 创建并下载 Markdown 文件\n\n        return markdownContent;\n    });\n    return mdArray.join('\\n\\n');\n};\n\n\nexport const exportAsAsset = async (tweetData: XConfig) => {\n\n    const {username, images, video, text} = tweetData;\n\n    // 创建一个新的 JSZip 实例\n    const zip = new JSZip();\n\n    // 辅助函数：获取高清图片URL\n    const getHighQualityImageUrl = (url) => {\n        // 移除已有的尺寸参数\n        let baseUrl = url.split('?')[0];\n        // 添加 'large' 参数来获取高质量版本\n        return `${baseUrl}?format=jpg&name=large`;\n    };\n\n    try {\n        // 添加文本内容到 zip\n        zip.file(\"tweet_text.txt\", text);\n\n        // 下载并添加图片到 zip\n        if (images && images.length > 0) {\n            for (let i = 0; i < images.length; i++) {\n                const highQualityUrl = getHighQualityImageUrl(images[i]);\n                const response = await fetch(highQualityUrl);\n                if (!response.ok) {\n                    console.warn(`Failed to fetch high quality image, falling back to original URL for image ${i + 1}`);\n                    // 如果高清版获取失败，尝试原始URL\n                    const fallbackResponse = await fetch(images[i]);\n                    if (!fallbackResponse.ok) throw new Error(`HTTP error! status: ${fallbackResponse.status}`);\n                    const blob = await fallbackResponse.blob();\n                    zip.file(`image_${i + 1}.jpg`, blob);\n                } else {\n                    const blob = await response.blob();\n                    zip.file(`image_${i + 1}.jpg`, blob);\n                }\n            }\n        }\n\n        // 下载并添加视频到 zip\n        if (video?.src) {\n            const response = await fetch(video.src);\n            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n            const blob = await response.blob();\n            zip.file(\"video.mp4\", blob);\n        }\n\n        // 生成 zip 文件\n        const content = await zip.generateAsync({type: \"blob\"});\n\n        const zipName = '' || `twwet_assets_${username || 'none'}_${text.slice(0, 15)}`;\n        // 使用 file-saver 保存文件\n        saveAs(content, `${zipName}.zip`);\n\n        console.log('Export completed successfully');\n    } catch (error) {\n        console.error('Export failed', error);\n        throw error; // 重新抛出错误，允许调用者处理它\n    }\n};\n\n\nexport const exportAsJSON = async (data: Object) => {\n    const jsonContent = JSON.stringify(data, null, 2);\n    const blob = new Blob([jsonContent], {type: 'application/json'});\n    const url = URL.createObjectURL(blob);\n    const a = document.createElement('a');\n    a.href = url;\n    a.download = 'tweetInfo.json'; // 设置下载的文件名\n    document.body.appendChild(a); // 必须将a元素添加到文档中才能触发点击\n    a.click();\n    document.body.removeChild(a); // 下载后从文档中移除a元素\n    URL.revokeObjectURL(url); // 清理创建的URL对象\n}"
  },
  {
    "path": "src/app/utils/format.ts",
    "content": "export function formatTimestamp(timestamp: number): string {\n    const date = new Date(timestamp * 1000); // Convert seconds to milliseconds\n\n    const hours = date.getHours();\n    const minutes = date.getMinutes();\n    const ampm = hours >= 12 ? 'PM' : 'AM';\n\n    // Convert to 12-hour format\n    const formattedHours = hours % 12 || 12;\n\n    // Ensure minutes are two digits\n    const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes;\n\n    const day = date.getDate();\n    const month = date.toLocaleString('en', { month: 'short' });\n    const year = date.getFullYear();\n\n    return `${formattedHours}:${formattedMinutes} ${ampm} · ${day} ${month}, ${year}`;\n}"
  },
  {
    "path": "src/app/utils/image.ts",
    "content": "import { domToPng, domToJpeg, domToSvg, type Options, createContext, destroyContext } from 'modern-screenshot'\n\n\n\n// const waitForImages = () => {\n//   const images = cardEle.querySelectorAll('img');\n//   const imagePromises = Array.from(images).map(img => {\n//     if (img.complete) {\n//       return Promise.resolve();\n//     } else {\n//       return new Promise((resolve, reject) => {\n//         img.onload = resolve;\n//         img.onerror = reject;\n//       });\n//     }\n//   });\n//   return Promise.all(imagePromises);\n// };\n// await waitForImages();\n\nexport const generateImage = async (options: {\n    // data?: XConfig[],\n    format: string\n    width?: number,\n    height?: number,\n    scale?: number\n    fontFamily: false | string\n}) => {\n\n    const cardEle = document.querySelector('#card') as HTMLElement;\n    const { format, width, height, scale = 2, fontFamily = false } = options;\n    // console.log('format', format, width, height, quality);\n    // 存储原始尺寸\n    const originalWidth = cardEle.style.width;\n    const originalHeight = cardEle.style.height;\n    console.log('fontFamily', fontFamily);\n\n    const cssText = `\n @font-face {\n  font-family: 'Font Awesome 6 Free';\n  font-style: normal;\n  font-weight: 400;\n  src: url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/webfonts/fa-regular-400.woff2') format('woff2');\n}\n\n@font-face {\n  font-family: 'Font Awesome 6 Free';\n  font-style: normal;\n  font-weight: 900;\n  src: url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/webfonts/fa-solid-900.woff2') format('woff2');\n}\n  @font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: 300;\n  src: url('https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5vAw.ttf') format('truetype');\n}\n\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: 400;\n  src: url('https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Me5Q.ttf') format('truetype');\n}\n\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: 700;\n  src: url('https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlvAw.ttf') format('truetype');\n}\n\n@font-face {\n  font-family: 'Roboto Flex';\n  font-style: normal;\n  font-weight: 400;\n  src: url('https://cdn.jsdelivr.net/fontsource/fonts/roboto-flex/vf@latest/latin-wght-normal.woff2') format('woff2-variations');\n  font-variation-settings: 'wght' 400;\n}\n`;\n    try {\n        // 如果提供了新的尺寸，则应用它们\n        let dataUrl: string;\n        const exportOptions: Options = {\n            quality: 0.95,\n            scale: scale,\n            features: {\n                // Without this, render fails on certain images\n                removeControlCharacter: false,\n            },\n            font: !fontFamily ? false : {\n                preferredFormat: 'woff2',\n                cssText,\n            },\n            fetchFn: async (url) => {\n                try {\n                    // Use Capacitor's HTTP plugin for native fetch\n                    const response = await fetch(url);\n\n                    if (!response.ok) {\n                        throw new Error(`HTTP error, status = ${response.status}`);\n                    }\n\n                    const arrayBuffer = await response.arrayBuffer();\n\n                    // Convert ArrayBuffer to base64\n                    const base64Data = btoa(\n                        new Uint8Array(arrayBuffer).reduce((data, byte) => data + String.fromCharCode(byte), '')\n                    );\n\n\n                    // Construct the data URL\n                    const mimeType = response.headers.get('content-type') || 'image/png'; // Default to PNG if no type is specified\n                    const dataUrl = `data:${mimeType};base64,${base64Data}`;\n\n                    return dataUrl;\n                } catch (error) {\n                    console.error('Error fetching image:', error);\n                    return false; // Fall back to normal fetch if there's an error\n                }\n            },\n            workerUrl: 'https://unpkg.com/modern-screenshot/dist/worker.js',\n            workerNumber: 2,\n        };\n\n        switch (format) {\n            case 'png':\n                dataUrl = await domToPng(cardEle, {\n                    ...exportOptions,\n                });\n                break;\n            case 'jpeg':\n                dataUrl = await domToJpeg(cardEle, exportOptions);\n                break;\n            case 'svg':\n                dataUrl = await domToSvg(cardEle, exportOptions);\n                break;\n            default:\n                throw new Error('Unsupported format');\n        }\n        return dataUrl;\n    } catch (err) {\n        console.error('Error exporting image:', err);\n        return 'error: ' + (err as Error).message;\n    } finally {\n    }\n}\n\nexport const exportImage = async (format: string) => {\n    try {\n        const dataUrl = await generateImage({\n            format: format,\n        });\n        const link = document.createElement('a');\n        link.download = `export.${format}`;\n        link.href = dataUrl;\n        link.click();\n    } catch (err) {\n        console.error('Error exporting image:', err);\n    }\n};\n"
  },
  {
    "path": "src/app/utils/index.ts",
    "content": "import * as _ from 'lodash-es';\n\nimport { domToPng, domToJpeg, domToSvg, type Options, createContext, destroyContext } from 'modern-screenshot'\nimport type { XConfig } from '@src/hooks/useCardStore';\nimport { iframeMessageSystem } from './IFrameMessageSystem';\nimport { checkAppliedStyle, getAdjacentCellDiv, traverseAndCheck } from './element';\n\n\n\nexport function checkTheVerticalLine(element) {\n  if (!element) return false;\n  const styleProperties = [['width', '2px'], ['flex-grow', '1']];\n  if (checkAppliedStyle(element, styleProperties)) return true;\n  return false;\n}\n\n\nexport const checkPostIsThread = (postElement) => {\n\n  function isElementVisible(element) {\n    const style = window.getComputedStyle(element);\n    return style.height !== '0px';\n  }\n\n  const isThread = traverseAndCheck(postElement, (element) => { return checkTheVerticalLine(element) });\n  let prePostIsThread = false;\n  let prePost = getAdjacentCellDiv(postElement, 'previous');\n  while (true) {\n    if (!prePost) break;\n    if (isElementVisible(prePost)) {\n      // console.log('prePost', prePost);\n      prePostIsThread = traverseAndCheck(prePost, (element) => { return checkTheVerticalLine(element) });\n      break;\n    }\n    prePost = getAdjacentCellDiv(prePost, 'previous');\n  }\n  return isThread || prePostIsThread;\n}\n\nexport const getPostThread = (postElement) => {\n  // function isElementVisible(element) {\n  //   const style = window.getComputedStyle(element);\n  //   return style.height !== '0px';\n  // }\n  const isElementVisible = element => element.offsetHeight !== 0;\n\n\n  // if (!isThread) return [postElement];\n\n  const postThread = [postElement];\n  let prePost = getAdjacentCellDiv(postElement, 'previous');\n\n  while (true) {\n    if (!prePost) break;\n    if (isElementVisible(prePost)) {\n      const prePostIsThread = traverseAndCheck(prePost, (element) => { return checkTheVerticalLine(element) });\n      if (prePostIsThread) {\n        postThread.unshift(prePost);\n      } else {\n        break\n      }\n    }\n    prePost = getAdjacentCellDiv(prePost, 'previous');\n  }\n\n  // let nextPost = getAdjacentCellDiv(postElement, 'next');\n  let nextPost = getAdjacentCellDiv(postElement, 'next');\n  const isEndThread = !traverseAndCheck(postElement, (element) => { return checkTheVerticalLine(element) });\n  console.log('isEndThread', isEndThread, postElement);\n  if (isEndThread) {\n    return postThread;\n  }\n  while (true) {\n    if (!nextPost) break;\n    if (isElementVisible(nextPost)) {\n      const nextPostIsThread = traverseAndCheck(nextPost, (element) => { return checkTheVerticalLine(element) });\n      console.log('nextPostIsThread', nextPostIsThread, nextPost);\n      postThread.push(nextPost);\n      if (!nextPostIsThread) {\n        break;\n      }\n    }\n    nextPost = getAdjacentCellDiv(nextPost, 'next');\n  }\n\n  return postThread;\n}\n\n\n\nexport type CopyImage = (tweetInfo: XConfig | XConfig[], cardConfig: any) => Promise<string>;\nexport const copyImage: CopyImage = async (tweetInfo, cardConfig = {}) => {\n  const format2Array = _.isArray(tweetInfo) ? tweetInfo : [tweetInfo];\n  const value = await sendMessageToIframe('generate-card-local', {\n    tweetInfo: format2Array,\n    cardConfig\n  }, 3000);\n\n  const imageDataUrl = value.dataUrl;\n\n  try {\n    // Extract MIME type and base64 data from the data URL\n    const [, mimeType, base64Data] = imageDataUrl.match(/^data:(.+);base64,(.+)$/);\n\n    // Convert base64 to Blob more efficiently\n    const blob = await fetch(imageDataUrl).then(res => res.blob());\n\n    // Copy to clipboard\n    await navigator.clipboard.write([\n      new ClipboardItem({\n        [mimeType]: blob\n      })\n    ]);\n\n    console.log('Image copied to clipboard successfully');\n    return imageDataUrl;\n  } catch (err) {\n    console.error('Failed to copy image:', err);\n  }\n}\n\n\nexport const downloadImage = async (tweetInfo: XConfig | XConfig[], cardConfig = {}) => {\n\n  const format2Array = _.isArray(tweetInfo) ? tweetInfo : [tweetInfo];\n\n  const value = await sendMessageToIframe('generate-card-local', {\n    tweetInfo: format2Array,\n    cardConfig,\n  }, 3000);\n  const { url, dataUrl } = value;\n  const xName = 'x-card'\n  //  tweetInfo.username || 'x-card';\n  const link = document.createElement('a');\n  link.download = `${xName}.${cardConfig?.format || 'png'}`\n  link.href = dataUrl;\n  link.click();\n}\n\n\n\nexport const sendMessageToIframe = async (name: string, data: any, timeout: number = 10000): Promise<any> => {\n  const iframe = findXCardsIframe();\n  if (!iframe || !iframe.contentWindow) {\n    throw new Error('Invalid iframe');\n  }\n  try {\n    const value = await iframeMessageSystem.publish(iframe, name, data, timeout);\n    return value;\n  } catch (error) {\n    console.error('Error sending message to iframe:', error);\n    throw error;\n  }\n};\n\n\n\n\nexport function extractTweetInfo(postElement) {\n  const tweet: XConfig = {\n    authorUrl: '',\n    url: '',\n    avatar: \"\",\n    username: \"\",\n    time: 0,\n    text: \"\",\n    tags: [],\n    images: [],\n    links: [],\n    shares: 0,\n    replies: 0,\n    likes: 0,\n  };\n\n  // 提取头像\n  tweet.avatar = postElement.querySelector('img[draggable=\"true\"]')?.src;\n\n  // 提取用户信息和时间\n  const userInfoDiv = postElement.querySelector('[data-testid=\"User-Name\"]');\n  if (!userInfoDiv) return;\n\n  const authorUrls = Array.from(userInfoDiv.querySelectorAll('[data-testid=\"User-Name\"] a'))?.map(e => e.href);\n  //console.log('authorUrls', authorUrls);\n\n  //账号URL\n  tweet.authorUrl = authorUrls.length > 0 ? authorUrls[0] : null;\n\n  //帖子URL\n  const xPostStatusUrls = Array.from(postElement.querySelectorAll('a[href*=\"/status/\"]')).map(a => a.href);\n  //console.log('xPostStatusUrls', xPostStatusUrls);\n  tweet.url = xPostStatusUrls.length > 0 ? xPostStatusUrls[0] : null;\n\n  tweet.username = userInfoDiv.querySelector('div[dir=\"ltr\"]').textContent;\n  let timeStr = userInfoDiv.querySelector('time')?.getAttribute('datetime');\n  // console.log('timeStr', timeStr, postElement.querySelector('time'));\n  tweet.time = new Date(timeStr).getTime() / 1000;\n  if (/\\/status\\/\\d+$/.test(window.location.href)) {\n    timeStr = postElement.querySelector('time')?.getAttribute('datetime');\n    console.log('timeStr', timeStr);\n    if (timeStr) {\n      tweet.time = new Date(timeStr).getTime() / 1000;\n    }\n  }\n\n\n  // 提取文本内容\n  // tweet.text = postElement.querySelector('[data-testid=\"tweetText\"]').textContent;\n  const tweetTextElement = postElement.querySelector('[data-testid=\"tweetText\"]');\n  tweet.text = tweetTextElement?.textContent || '';\n  tweet.tags = [];\n  tweetTextElement?.querySelectorAll('a[href^=\"/hashtag/\"]').forEach(tag => {\n    const tagText = tag.textContent;\n    tweet.tags.push(tagText);\n    tweet.text = tweet.text.replace(tagText, ''); // 从文本中移除标签\n  });\n  tweet.text = tweet.text.trim(); // 移除可能的多余空格\n\n\n  // 提取图片（如果有）\n  const images = _.compact([\n    ...postElement.querySelectorAll('[data-testid=\"tweetPhoto\"] img'),\n    // ...postElement.querySelectorAll('[data-testid=\"tweetPhoto\"] video')\n  ]);\n  tweet.images = Array.from(images).map(img => img.src);\n\n\n\n  // 图片换成高清图，\n  // https://pbs.twimg.com/media/GUjKHbca8AEeT7e?format=jpg&name=small\n  // https://pbs.twimg.com/media/GUjKHbca8AEeT7e?format=jpg&name=large\n  tweet.images = tweet.images.map(img => img.replace(/name=\\w+/, 'name=large'));\n\n  // console.log('tweet.images', tweet.images);\n\n  // 提取视频（如果有）\n  const video = postElement.querySelector('video');\n  if (video) {\n    tweet.video = {\n      poster: video.poster,\n      src: video.querySelector('source').src\n    };\n  }\n\n  // 提取链接（如果有）\n  const links = postElement.querySelectorAll('a[href^=\"https://\"]') as NodeListOf<HTMLAnchorElement>;\n  tweet.links = Array.from(links).map(link => ({\n    href: link.href,\n    text: link.textContent,\n    src: link?.querySelector('img')?.src\n  }));\n\n  // 提取统计信息\n  const stats = postElement.querySelector('[role=\"group\"]');\n  tweet.replies = stats.querySelector('[data-testid=\"reply\"]').textContent || 0;\n  tweet.shares = stats.querySelector('[data-testid=\"retweet\"]').textContent || 0;\n  tweet.likes = stats.querySelector('[data-testid=\"like\"]').textContent || 0;\n  // tweet.views = stats.querySelector('a[href$=\"/analytics\"]').textContent;\n\n  return tweet;\n}\n\nexport const findXCardsIframe = () => {\n  const shadowRoot = document.querySelector(\"xcards-ui\")?.shadowRoot;\n  if (!shadowRoot) throw new Error('Shadow root not found');\n\n  const iframe = shadowRoot.getElementById('x-card-ai') as HTMLIFrameElement;\n  if (!iframe) throw new Error('Iframe not found');\n  return iframe;\n\n}\n\n\n\n"
  },
  {
    "path": "src/background/index.ts",
    "content": "import { sendToContentScript } from \"@plasmohq/messaging\"\n\n\nchrome.runtime.onMessage.addListener((request, sender, sendResponse) => {\n    if (request.action === \"checkInstallation\") {\n        // 直接回复 content script\n        sendResponse({ isInstalled: true });\n    }\n});\n\n\nchrome.runtime.onInstalled.addListener(function (details) {\n    console.log('onInstalled', details);\n    if (details.reason == \"install\") {\n        chrome.tabs.create({ url: 'https://x-cards.net/welcome' });\n    }\n});\n\nchrome.action.onClicked.addListener(function (tab) {\n    chrome.tabs.create({ url: 'https://x-cards.net/welcome' });\n});\n"
  },
  {
    "path": "src/background/messages/code.ts",
    "content": "import type { PlasmoMessaging } from \"@plasmohq/messaging\"\nimport * as _ from 'lodash-es'\nimport Browser from \"webextension-polyfill\";\n\nconst key = 'xCardsLicense';\n\nconst handler: PlasmoMessaging.MessageHandler = async (req, res) => {\n    const licenseKey = req.body.licenseKey;\n    const action = req.body.action;\n\n    if (action === 'activate') {\n        try {\n            const response = await fetch('https://api.gumroad.com/v2/licenses/verify', {\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'application/x-www-form-urlencoded',\n                },\n                body: new URLSearchParams({\n                    product_id: 'hxPOgEq9TDcvDW5Rw9i2Aw==', // 替换为你的实际产品 ID\n                    license_key: licenseKey,\n                }),\n            });\n\n            const data = await response.json();\n            console.log('License verification response:', data);\n\n            if (!data.success) {\n                return res.send({ message: 'Invalid license key' });\n            }\n\n            await Browser.storage.local.set({ [key]: data.purchase, licenseKey, });\n\n            res.send({ message: 'License activated' });\n        } catch (error) {\n            res.send({ message: error.message });\n        }\n    } else if (action === 'check') {\n        const { licenseKey } = await Browser.storage.local.get('licenseKey');\n        // console.log('licenseKey', licenseKey);\n        const oldData = await Browser.storage.local.get(key);\n        // console.log('oldData', oldData);\n        if (!oldData?.[key] || !licenseKey) return res.send(null);\n        // console.log('check isActivated', oldData);\n        const response = await fetch('https://api.gumroad.com/v2/licenses/verify', {\n            method: 'POST',\n            headers: {\n                'Content-Type': 'application/x-www-form-urlencoded',\n            },\n            body: new URLSearchParams({\n                product_id: 'hxPOgEq9TDcvDW5Rw9i2Aw==', // 替换为你的实际产品 ID\n                license_key: licenseKey,\n            }),\n        });\n        const data = await response.json();\n        await Browser.storage.local.set({ [key]: data.purchase });\n        console.log('data', data);\n        res.send(data?.purchase);\n    }\n}\n\nexport default handler;"
  },
  {
    "path": "src/background/messages/tweet.ts",
    "content": "import type { PlasmoMessaging } from \"@plasmohq/messaging\"\nimport * as _ from 'lodash-es'\n\n\nconst handler: PlasmoMessaging.MessageHandler = async (req, res) => {\n    const action = req.body.action;\n    if (action === 'get-tweet') {\n        const url = req.body.url;\n        const response = await fetch(`https://x-cards.net/api/x?url=${url}`, {\n            method: 'GET',\n\n        });\n        if (response.status !== 200 || !response.ok) {\n            res.send({ error: 'error' });\n        }\n        const data = await response.json();\n\n        return res.send(data.data);\n    } \n    // else if (action === 'get-card-templates') {\n    //     const templates = await templatesStorage.getAll();\n    //     console.log('templates find in bg', templates)\n    //     return res.send(templates);\n    // }\n\n}\nexport default handler;"
  },
  {
    "path": "src/components/extension/card-button.tsx",
    "content": "import { DynamicStyleTippyComponent } from \"@src/app/(app)/components/dynamic-style-tippy\";\nimport { useMemo, useRef, useState } from \"react\";\nimport pRetry from 'p-retry';\nimport { copyImage, extractTweetInfo, getPostThread } from \"@src/app/utils\";\nimport toast from 'react-hot-toast';\nimport * as _ from 'lodash-es';\nimport { PreviewToast } from \"./x-cards-toast\";\nimport { useTweetsStore } from \"./use-tweet-collection\";\nimport type { PlasmoCSUIAnchor } from \"plasmo\";\nimport { Plus } from \"lucide-react\";\n\nexport const CardButton: React.FC<{\n    anchor: PlasmoCSUIAnchor\n}> = ({ anchor }) => {\n    const [isLoading, setIsLoading] = useState(false);\n\n    const cardConfig = useRef({});\n\n    const handleCopyAsCellCard = async (e) => {\n        e.stopPropagation();\n        e.preventDefault();\n        if (isLoading) return;\n        setIsLoading(true);\n        try {\n            const postElement = anchor.element.closest('article[data-testid=\"tweet\"]');\n            const tweetInfo = extractTweetInfo(postElement)\n            const imageSrc = await pRetry(async () => {\n                return copyImage(tweetInfo, cardConfig.current);\n            }, {\n                retries: 10,\n            });\n\n            const thread = getPostThread(postElement);\n            const tweetInfos = _.compact(thread?.map(e => extractTweetInfo(e)));\n\n            // toast.success(\"👏  Copy the card to clipboard\", {\n            //     duration: 500,\n            // })\n\n            useTweetsStore.setState((state) => {\n                state.tweets = tweetInfos;\n                state.activeTab = 'linear';\n                state.imageSrc = imageSrc;\n                return state;\n            });\n            toast.remove('preview-toast');\n            toast.custom((t) => {\n                return (<PreviewToast\n                    anchor={anchor}\n                    tweetInfos={tweetInfos}\n                    tweetInfo={tweetInfo}\n                ></PreviewToast>)\n            }, {\n                id: 'preview-toast',\n                duration: Infinity,\n                position: 'top-right',\n            })\n        } catch (error) {\n            toast.error(`Error copying card: ${error.message}`);\n            console.error(\"Error generating card:\", error);\n        } finally {\n            setIsLoading(false);\n        }\n    }\n\n    const handleDefaultClick = async (e) => {\n        e.stopPropagation();\n        e.preventDefault();\n        await handleCopyAsCellCard(e);\n    }\n\n    const handleClick = async (e) => {\n        e.stopPropagation();\n        e.preventDefault();\n        if (isLoading) return;\n        setIsLoading(true);\n        try {\n            const postElement = anchor.element.closest('article[data-testid=\"tweet\"]');\n            const tweetInfo = extractTweetInfo(postElement)\n            const thread = getPostThread(postElement);\n            const tweetInfos = _.compact(thread?.map(e => extractTweetInfo(e)));\n            useTweetsStore.setState((state) => {\n                state.tweets = [\n                    ...state.tweets,\n                    tweetInfo,\n                ];\n                state.activeTab = 'linear';\n                return state;\n            });\n            toast.remove('preview-toast');\n            toast.custom((t) => {\n                return (<PreviewToast\n                    anchor={anchor}\n                    tweetInfos={tweetInfos}\n                    tweetInfo={tweetInfo}\n                ></PreviewToast>)\n            }, {\n                id: 'preview-toast',\n                duration: Infinity,\n                position: 'top-right',\n            })\n        } catch (error) {\n            toast.error(`Error copying card: ${error.message}`);\n            console.error(\"Error generating card:\", error);\n        } finally {\n            setIsLoading(false);\n        }\n    }\n\n    const dropdownMenu = useMemo(() => {\n        return (<div className=\"dropdown-menu\">\n            <ul className=\"dropdown-menu-list\">\n\n                <div className=\"dropdown-menu-separator\" />\n                <li className=\"dropdown-menu-item\"\n                    onClick={handleClick}\n                >\n                    <Plus className=\"dropdown-menu-icon\"></Plus>\n                    Add to Collection\n                </li>\n            </ul>\n        </div>)\n    }, [])\n\n    return (\n        <DynamicStyleTippyComponent\n            tippyOptions={{\n                maxWidth: 200,\n            }}\n            content={dropdownMenu}>\n            <div\n                className=\"x\"\n                onClick={handleDefaultClick}\n                style={{\n                    padding: \"4px\",\n                }}\n            >\n                <button className=\"x card-button\"\n                >\n                    <div\n                        dir=\"ltr\"\n                        style={{ textOverflow: \"unset\" }}\n                    >\n                        {isLoading ? (\n                            <svg\n                                className=\"animate-spin\"\n                                viewBox=\"0 0 1024 1024\"\n                                xmlns=\"http://www.w3.org/2000/svg\"\n                                width={22.5}\n                                height={22.5}\n                            >\n                                <path\n                                    d=\"M512 128a384 384 0 01384 384 384 384 0 01-384 384 384 384 0 01-384-384 384 384 0 01384-384zm0 115.2a268.8 268.8 0 100 537.6 268.8 268.8 0 000-537.6z\"\n                                    fillOpacity={0.05}\n                                />\n                                <path\n                                    d=\"M781.696 794.24A390.272 390.272 0 00229.76 242.304a58.539 58.539 0 0082.773 82.773 273.195 273.195 0 11386.39 386.39 58.539 58.539 0 0082.773 82.773z\"\n                                    fill=\"#768294\"\n                                />\n                            </svg>\n\n                        ) : (\n                            <>\n                                <svg\n                                    style={{\n                                        width: '22.5px',\n                                        height: '22.5px',\n                                        verticalAlign: 'text-bottom',\n\n                                    }}\n                                    viewBox=\"0 0 1024 1024\"\n                                    xmlns=\"http://www.w3.org/2000/svg\"\n                                    width={22.5}\n                                    height={22.5}\n                                >\n                                    <path\n                                        d=\"M843.7 263.4l-157-99.4c-2.9-1.9-6-3.5-9-5-11.5-20.7-31.2-36.6-56-42.6L459.2 77.3C445.8 68.9 429.9 64 412.8 64H227c-48.3 0-87.4 39.2-87.4 87.4V785c0 48.3 39.1 87.4 87.4 87.4h67.4l116.8 74c40.8 25.8 94.8 13.7 120.6-27.1l339-535.4c25.9-40.7 13.7-94.7-27.1-120.5zM205.1 680.3v-507c0-24.1 19.6-43.7 43.7-43.7H338c-.9 2.6-1.7 5.2-2.3 8L205.1 680.3zm189.2-506.2c5.6-23.5 29.2-37.9 52.7-32.3l133.4 32.1c-5.3 5-10.1 10.7-14.2 17.2L282.5 639.2l111.8-465.1zm409.5 193.3L488.2 865.9c-12.9 20.4-39.9 26.4-60.3 13.5l-120.1-76c-20.4-12.9-26.5-39.9-13.5-60.3l315.6-498.4c12.9-20.4 39.9-26.5 60.3-13.6l120.1 76c20.3 12.9 26.4 39.9 13.5 60.3zM428.7 716.6h-8.1c-21.5 0-39 17.4-39 39v8.1c0 21.5 17.5 39 39 39h8.1c21.5 0 39-17.4 39-39v-8.1c0-21.6-17.5-39-39-39z\"\n                                        fill=\"rgb(113, 118, 123)\"\n                                    />\n                                </svg>\n                            </>\n                        )}\n\n                    </div>\n                </button>\n            </div>\n        </DynamicStyleTippyComponent>\n    )\n}"
  },
  {
    "path": "src/components/extension/input-code.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport { X } from 'lucide-react';\nimport { motion } from 'framer-motion';\nimport toast from 'react-hot-toast';\nimport { sendToBackground } from '@plasmohq/messaging';\nimport { useTweetsStore } from './use-tweet-collection';\n\nexport const InputCode = ({ onClose }) => {\n    const [licenseKey, setLicenseKey] = useState('');\n    const [message, setMessage] = useState('');\n    const [isLoading, setIsLoading] = useState(false);\n    const isActivated = useTweetsStore((state) => state.isActivated);\n    const setIsActivated = useTweetsStore((state) => state.setIsActivated);\n\n    useEffect(() => {\n        if (isActivated) {\n            setMessage('License activated');\n        }\n    }, [isActivated])\n\n    const handleSubmit = async (e) => {\n        e.preventDefault();\n        setIsLoading(true);\n        setMessage('');\n\n        if (!licenseKey) {\n            setMessage('License key is required');\n            setIsLoading(false);\n            return;\n        }\n\n        try {\n            const data = await sendToBackground({\n                name: 'code',\n                body: {\n                    licenseKey,\n                    action: 'activate'\n                }\n            })\n\n            if (data.message === 'License activated') {\n                setMessage('License activated');\n                toast.success('Your license key is valid. Thank you!');\n                setIsActivated(true);\n                onClose?.();\n            } else {\n                setMessage('Invalid license key. Please try again.');\n                toast.error('Your license key is invalid. Please try again.');\n            }\n        } catch (error) {\n            console.error('Error checking license key:', error);\n            toast.error('An error occurred. Please try again.');\n            setMessage('An error occurred. Please try again.');\n        }\n\n        setIsLoading(false);\n    };\n\n    return (\n        <motion.div\n            animate={{ scale: [0.1, 1] }}\n            exit={{ scale: 0 }}\n            className=\"fixed inset-0  z-50 bg-gray-900 bg-opacity-80 backdrop-blur-sm flex items-center justify-center\">\n            <div className=\"bg-gray-800 p-8 shadow-lg w-full max-w-md rounded-xl border border-gray-700 relative\">\n                <button\n                    className='absolute right-3 top-3 z-10 hover:bg-gray-700 rounded-full p-1 transition-colors duration-200'\n                    onClick={onClose}\n                >\n                    <X className='h-5 w-5 text-neutral-500 hover:text-white' />\n                </button>\n\n                <h2 className=\"text-2xl font-bold mb-6 text-center text-white\">Enter Your License Key</h2>\n                <p className=\"text-white mb-4 text-sm\">This key represents a permanent license for this product.</p>\n\n                <form onSubmit={handleSubmit}>\n                    <input\n                        type=\"text\"\n                        value={licenseKey}\n                        onChange={(e) => setLicenseKey(e.target.value)}\n                        placeholder=\"Please enter your license key\"\n                        className=\"w-full px-4 py-2 bg-gray-700 text-white border border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mb-6 placeholder-gray-400\"\n                        disabled={isLoading}\n                    />\n                    <button\n                        type=\"submit\"\n                        className={`w-full bg-gradient-to-r from-blue-500 to-purple-600 text-white py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-opacity-50 transition duration-300 ease-in-out transform hover:scale-105 ${isLoading ? 'opacity-50 cursor-not-allowed' : 'hover:from-blue-600 hover:to-purple-700'}`}\n                        disabled={isLoading}\n                    >\n                        {isLoading ? 'Verifying...' : 'Activate License'}\n                    </button>\n                </form>\n\n                {message && (\n                    <p className={`mt-4 text-sm text-center ${message.includes('activated') ? 'text-green-400' : 'text-red-400'}`}>\n                        {message}\n                    </p>\n                )}\n                <p className=\"mt-4 text-sm text-gray-400\">\n                    Need a license key? <a href=\"https://xcards.gumroad.com/l/x-cards\" target=\"_blank\">Visit our Gumroad page , Try our 7-day free trial!</a>\n                </p>\n                <div className=\"mt-4 p-4 bg-gray-700 rounded-lg border border-gray-600\">\n                    <p className=\"text-sm font-medium text-blue-300 mb-2\">\n                        Unlock 1 Year Free Access:\n                    </p>\n                    <div className=\"flex items-center space-x-2 mb-2\">\n                        <svg className=\"w-5 h-5 text-blue-400\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n                            <path d=\"M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z\" />\n                        </svg>\n                        <span className=\"text-sm text-gray-300\">Share on X (Twitter)</span>\n                    </div>\n                    <div className=\"bg-gray-800 p-3 rounded-md\" onClick={() => {\n                        window.open('https://x.com/intent/tweet?url=https://x-cards.net&text=@FeigelC35583 Just discovered an amazing tool! @IndieDevr Check it out!&hashtags=IndieDevTool', '_blank');\n                    }}>\n                        <p className=\"text-sm text-gray-300 mb-2\" >Click Post this message:</p>\n                        <p className=\"text-sm text-white font-medium\">\n                            \"X Cards: Power Up Your Tweet Marketing on X.com<a href=\"https://x.com/IndieDevr\" target=\"_blank\">@IndieDevr</a> #IndieDevTool\"\n                        </p>\n                    </div>\n                    <p className=\"mt-3 text-sm \">\n                        DM <a className='text-blue-300' href=\"https://x.com/messages/compose?recipient_id=IndieDevr\" target=\"_blank\">@IndieDevr</a> with your post link for your 1-year activation code!\n                    </p>\n                </div>\n\n            </div>\n        </motion.div>\n    );\n};"
  },
  {
    "path": "src/components/extension/label-with-icon.tsx",
    "content": "import React from 'react';\nimport { LockOpen, Lock } from 'lucide-react';\n\ninterface LabelWithIconProps {\n    label: string;\n    isActivated?: boolean;\n    className?: string;\n    labelClassName?: string;\n}\n\nconst LabelWithIcon: React.FC<LabelWithIconProps> = ({ label, isActivated = false, className = '', labelClassName = '' }) => {\n    const Icon = isActivated ? LockOpen : Lock;\n\n    return (\n        <div className={`flex gap-x-2 items-center ${className}`}>\n            <Icon className='w-4 h-4 text-gray-400' />\n            <h3 className={`text-sm font-semibold ${labelClassName}`}>{label}</h3>\n        </div>\n    );\n};\n\nexport default LabelWithIcon;"
  },
  {
    "path": "src/components/extension/layout-options.tsx",
    "content": "import { CommonLayouts } from '@src/hooks/useCardStore';\nimport React from 'react';\nimport styled from 'styled-components';\n\nconst Container = styled.div`\n    display: flex;\n    flex-wrap: wrap;\n    max-width: 300px;\n    margin: 0 auto;\n    justify-content: center;\n`;\n\nconst Option = styled.div`\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    padding: 5px;\n    border-radius: 4px;\n    cursor: pointer;\n\n    &:hover {\n        background-color: #1c1c1c;\n    }\n`;\n\nconst RatioBox = styled.div`\n    width: ${props => props.boxWidth}px;\n    height: ${props => props.boxHeight}px;\n    border: 1px solid #38444D;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    margin-bottom: 5px;\n    color: #FFFFFF;\n    font-size: 10px;\n    font-weight: bold;\n`;\n\nconst Dimensions = styled.div`\n    font-size: 9px;\n    color: #8899A6;\n`;\n\nconst LayoutOptions = ({ onSelect }) => {\n\n    const [selectedLayout, setSelectedLayout] = React.useState(CommonLayouts.layoutOptions[0]);\n\n    const getRatioBoxDimensions = (ratio) => {\n        const [width, height] = ratio.split(':').map(Number);\n        const baseSize = 40;\n        let boxWidth, boxHeight;\n\n        if (width > height) {\n            boxWidth = baseSize;\n            boxHeight = (height / width) * baseSize;\n        } else {\n            boxHeight = baseSize;\n            boxWidth = (width / height) * baseSize;\n        }\n\n        return { boxWidth, boxHeight };\n    };\n\n    return (\n        <Container>\n            {CommonLayouts.layoutOptions.map((option, index) => {\n                const { boxWidth, boxHeight } = getRatioBoxDimensions(option.ratio);\n                return (\n                    <Option key={index}\n                        onClick={() => {\n                            setSelectedLayout(option);\n                            onSelect(option);\n                        }}\n                        style={{\n                            backgroundColor: selectedLayout.ratio === option.ratio ? '#1c1c1c' : ''\n                        }}\n                    >\n                        <RatioBox boxWidth={boxWidth} boxHeight={boxHeight}>\n                            {option.ratio}\n                        </RatioBox>\n                        <Dimensions>\n                            {`${option.dimensions.width}x${option.dimensions.height}`}\n                        </Dimensions>\n                    </Option>\n                );\n            })}\n        </Container>\n    );\n};\n\nexport default LayoutOptions;"
  },
  {
    "path": "src/components/extension/preset-color-list.tsx",
    "content": "import ResultIcon from \"@src/app/(app)/components/ResultIcon\"\nimport { presets } from \"@src/app/(app)/components/card-generator/color\"\nimport React from \"react\";\n\nexport const PresetColorList = ({ onSelect }) => {\n\n    const [colorIndex, setColorIndex] = React.useState(0);\n\n    return (\n        <div className=\"preset-selector\">\n            {presets.map((preset, index) => (\n                <label key={index} className={`preset-label ${colorIndex === index ? 'selected' : ''}`}>\n                    <input\n                        className=\"preset-input\"\n                        type=\"radio\"\n                        name=\"preset\"\n                        value={index}\n                        checked={colorIndex === index}\n                        onChange={() => {\n                            setColorIndex(index);\n                            onSelect(index);\n                        }}\n                    />\n                    <ResultIcon size={20} isPreview settings={{ ...preset, backgroundRadius: 0 }} />\n                </label>\n            ))}\n            <style jsx>{`\n.preset-selector {\ndisplay: flex;\nflex-wrap: wrap;\ngap:8px;\npadding: 4px;\n}\n.preset-label {\nposition: relative;\noverflow: hidden;\nwidth: 20px;\nheight: 20px;\nflex-shrink: 0;\nborder-radius: 5px;\ntransition: all 0.2s ease;\n}\n.preset-label:hover {\ntransform: scale(1.1);\nbox-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);\n}\n.preset-label.selected {\nbox-shadow: 0 0 0 2px #3b82f6;\n}\n.preset-input {\nposition: absolute;\nwidth: 100%;\nheight: 100%;\nappearance: none;\nopacity: 0;\ninset: 0;\ncursor: pointer;\n}\n.preset-label:active {\ntransform: scale(0.95);\n}\n`}</style>\n        </div>\n    )\n}"
  },
  {
    "path": "src/components/extension/tabs.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport { Lock } from 'lucide-react';\nimport { useTweetsStore } from './use-tweet-collection';\nconst Tabs: React.FC<{\n  tabs: { label: string; value: string, isLocked?: boolean }[];\n  defaultTab?: string;\n}> = ({ tabs }) => {\n\n  const activeTab = useTweetsStore(state => state.activeTab);\n  const setActiveTab = useTweetsStore(state => state.setActiveTab);\n\n\n  const handleTabClick = (tabValue) => {\n    setActiveTab(tabValue);\n  };\n\n  return (\n    <div style={{\n      display: \"grid\",\n      gridTemplateColumns: \"repeat(2, 1fr)\",\n      width: \"100%\",\n      background: '#313131',\n      padding: \"4px\",\n      height: \"36px\",\n      boxSizing: \"border-box\",\n      borderRadius: \"0.375rem\",\n    }}>\n      {tabs.map((tab) => (\n        <button\n          key={tab.value}\n          style={{\n            display: \"inline-flex\",\n            alignItems: \"center\",\n            justifyContent: \"center\",\n            whiteSpace: \"nowrap\",\n            borderRadius: \"0.375rem\",\n            fontSize: \"0.875rem\",\n            fontWeight: 500,\n            transition:\n              \"background-color 0.2s, border-color 0.2s, color 0.2s, box-shadow 0.2s\",\n            outline: \"none\",\n            color: \"white\",\n            cursor: \"pointer\",\n            border: \"none\",\n            padding: '4px',\n            background: 'unset',\n            ...(activeTab === tab.value ? { background: 'black' } : {}),\n\n          }}\n          onClick={() => handleTabClick(tab.value)}\n        >\n          {tab?.isLocked && <Lock className='  w-4 h-4 mr-2 text-gray-400  ' />}\n          <span>{tab.label}</span>\n        </button>\n      ))}\n    </div>\n  );\n};\n\nexport default React.memo(Tabs);"
  },
  {
    "path": "src/components/extension/tweet-manager.tsx",
    "content": "import type { XConfig } from '@src/hooks/useCardStore';\nimport { Trash } from 'lucide-react';\nimport React, { useEffect, useState } from 'react';\nimport { SortableList, SortableListItem, type Item, type SortableListProps } from '../sortableList';\nimport { useTweetsStore } from './use-tweet-collection';\nimport toast from 'react-hot-toast';\n\n\nconst TweetManager: React.FC<{\n    tweetInfos: XConfig[];\n    onRemoveItem?: (tweetInfos: XConfig[]) => void;\n    onReset?: (tweetInfos: XConfig[]) => void;\n    onDragEnd?: (tweetInfos) => void;\n}> = ({ tweetInfos, onRemoveItem, onReset, onDragEnd }) => {\n    const isActivated = useTweetsStore((state) => state.isActivated);\n    const tweets = useTweetsStore(state => state.tweets);\n    const setTweets = useTweetsStore(state => state.setTweets);\n\n    const [items, setItems] = useState<(\n        XConfig & {\n            id: number;\n        }\n    )[]>(tweetInfos.map((tweet, index) => ({\n        ...tweet,\n        id: index,\n    })\n    ));\n\n    // sync the tweets with the items\n    useEffect(() => {\n        setItems(tweets.map(\n            (tweet, index) => ({\n                ...tweet,\n                id: index,\n            })\n        ));\n    }, [tweets]);\n\n\n    const handleRemoveItem = (order: number) => {\n        // at least one item should be there\n        if (tweets.length === 1) {\n            toast(\"At least one tweet should be there\", {\n                duration: 1000,\n            });\n            onRemoveItem?.(items);\n            return;\n        }\n        const newItems = [...tweets];\n        newItems.splice(order, 1);\n        setTweets(newItems);\n        onRemoveItem?.(newItems);\n    }\n\n    const renderItem: SortableListProps<XConfig>['renderItem'] = (\n        item,\n        order,\n        onRemoveItem,\n    ) => (\n        <SortableListItem\n            key={item.id}\n            item={item}\n            onRemoveItem={onRemoveItem}\n            handleDrag={() => { }}\n            renderExtra={(item) => {\n                return (\n                    <div className='flex items-center space-x-3 h-10 bg-[#313131]  p-1.5 mb-2 rounded-xl  shadow-[0px_1px_0px_0px_hsla(0,0%,100%,.03)_inset,0px_0px_0px_1px_hsla(0,0%,100%,.03)_inset,0px_0px_0px_1px_rgba(0,0,0,.1),0px_2px_2px_0px_rgba(0,0,0,.1),0px_4px_4px_0px_rgba(0,0,0,.1),0px_8px_8px_0px_rgba(0,0,0,.1)] cursor-grab w-full'>\n                        {/* <span className=\"font-mono text-xs pl-1 text-white/50\">{index}</span> */}\n                        <div className='flex-grow w-full'>\n                            <span className=\"tracking-tighter text-base  text-white/70 line-clamp-1 break-words\">{item.text.slice(0, 20)}</span>\n                        </div>\n                        <button\n                            className=\"inline-flex h-8 items-center justify-center whitespace-nowrap rounded-md px-3 text-sm font-medium  transition-colors duration-150   focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\"\n                            onClick={() => handleRemoveItem(order)}\n                        >\n                            <Trash className=\"h-4 w-4 text-red-400 transition-colors duration-150 fill-red-400/60 \" />\n                        </button>\n                    </div>\n                )\n            }}\n            onDragEnd={() => {\n                setTweets(items);\n                // onDragEnd?.(items);\n            }}\n            order={order} />\n    )\n\n    return (\n        <div className=\" \"\n        >\n\n            <div className=' relative'>\n                <SortableList<XConfig>\n                    items={items}\n                    setItems={setItems}\n                    renderItem={renderItem}\n                />\n            </div>\n\n        </div>\n    )\n};\n\nexport default TweetManager;"
  },
  {
    "path": "src/components/extension/use-tweet-collection.ts",
    "content": "import type { CardStore, XConfig } from \"@src/hooks/useCardStore\";\nimport { create } from \"zustand\";\nimport { fontSizeMap } from \"./x-cards-toast/font-control\";\n\nexport interface TweetControlState {\n    showUser: boolean;\n    showActions: boolean;\n    showTime: boolean;\n    showFooter: boolean;\n    showLogo: boolean;\n}\n\nexport interface CardConfig {\n    padding: number;\n    format: string,\n    controls: TweetControlState\n    width: number;\n    colorIndex: number;\n    scale: number;\n    fontSize: string;\n    fontFamily: string;\n}\n\ninterface TweetCollection {\n\n    cardConfig: Partial<CardConfig>;\n    setCardConfig: (cardConfig: Partial<CardConfig>) => void;\n    isActivated: boolean;\n    setIsActivated: (isActivated: boolean) => void;\n    imageSrc?: string;\n    setImageSrc: (imageSrc: string) => void;\n    activeTab: string,\n    setActiveTab: (tabValue: string) => void;\n    tweets: XConfig[];\n    setTweets: (tweets: XConfig[]) => void;\n    getTweets: () => XConfig[];\n    addTweet: (tweet: XConfig | XConfig[]) => void;\n    removeTweet: (tweet: XConfig) => void;\n    updateTweet: (tweet: XConfig) => void;\n    moveTweet: (from: number, to: number) => void;\n    resetTweets: () => void;\n    // const [showCodeDialog, setShowCodeDialog] = useState(false);\n    showCodeDialog: boolean;\n    setShowCodeDialog: (showCodeDialog: boolean) => void;\n\n\n}\n\nexport const useTweetsStore = create<TweetCollection>(\n    (set, get) => ({\n        cardConfig: {\n            padding: 0,\n            scale: 2,\n            fontSize: fontSizeMap['xl'],\n        },\n        setCardConfig: (cardConfig) => {\n            const oldCardConfig = get().cardConfig;\n            set({\n                cardConfig: {\n                    ...oldCardConfig,\n                    ...cardConfig\n                }\n            })\n        },\n        showCodeDialog: false,\n        setShowCodeDialog: (showCodeDialog: boolean) => {\n            set({\n                showCodeDialog\n            })\n        },\n        isActivated: false,\n        setIsActivated: (isActivated: boolean) => {\n            set({\n                isActivated\n            })\n        },\n        imageSrc: '',\n        setImageSrc: (imageSrc: string) => {\n            set({\n                imageSrc\n            })\n        },\n        activeTab: 'single',\n        setActiveTab: (tabValue: string) => {\n            set({\n                activeTab: tabValue\n            })\n        },\n        tweets: [],\n        setTweets: (tweets: XConfig[]) => {\n            set({\n                tweets\n            })\n        },\n        getTweets: () => {\n            return get().tweets;\n        },\n        addTweet: (tweet) => {\n            set((state) => ({\n                tweets: state.tweets.concat(tweet)\n            }))\n        },\n        removeTweet: (tweet: XConfig) => {\n            set((state) => ({\n                tweets: state.tweets.filter((t) => t.id !== tweet.id)\n            }))\n        },\n        updateTweet: (tweet: XConfig) => {\n            set((state) => ({\n                tweets: state.tweets.map((t) => t.id === tweet.id ? tweet : t)\n            }))\n        },\n        moveTweet: (from: number, to: number) => {\n            set((state) => {\n                const tweets = [...state.tweets];\n                const [removed] = tweets.splice(from, 1);\n                tweets.splice(to, 0, removed);\n                return {\n                    tweets\n                }\n            })\n        },\n        resetTweets: () => {\n            set((state) => ({\n                tweets: []\n            }))\n        },\n    })\n);\n\n"
  },
  {
    "path": "src/components/extension/x-cards-toast/font-control.tsx",
    "content": "import { Button } from \"@components/ui/button\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport { useTweetsStore } from \"../use-tweet-collection\";\nimport { Input } from \"@components/ui/input\";\n\nexport const fontSizeMap = {\n    'xs': '0.75rem', // Example sizes, adjust as needed\n    'sm': '0.875rem',\n    'md': '1rem',\n    'lg': '1.125rem',\n    'xl': '1.25rem',\n    '2xl': '1.5rem',\n    '3xl': '1.875rem',\n};\n\nconst googleFonts = [\n    { name: 'Default', value: 'sans-serif' },\n    { name: 'Roboto', value: 'Roboto' },\n    { name: 'Open Sans', value: 'Open Sans' },\n    { name: 'Lato', value: 'Lato' },\n    { name: 'Montserrat', value: 'Montserrat' },\n    { name: 'Noto Sans SC', value: 'Noto Sans SC' },\n    { name: 'Playfair Display', value: 'Playfair Display' },\n    { name: 'Merriweather', value: 'Merriweather' },\n    { name: 'Source Sans Pro', value: 'Source Sans Pro' },\n    { name: 'PT Sans', value: 'PT Sans' },\n    { name: 'Raleway', value: 'Raleway' },\n    { name: 'Oswald', value: 'Oswald' },\n    { name: 'Nunito', value: 'Nunito' },\n    { name: 'Ubuntu', value: 'Ubuntu' },\n    { name: 'Poppins', value: 'Poppins' },\n    { name: 'Quicksand', value: 'Quicksand' },\n    { name: 'Rubik', value: 'Rubik' },\n    { name: 'Work Sans', value: 'Work Sans' },\n    { name: 'Fira Sans', value: 'Fira Sans' },\n    { name: 'Noto Serif', value: 'Noto Serif' },\n    // 中文字体\n    { name: 'Noto Sans SC', value: 'Noto Sans SC' },\n    { name: 'Noto Serif SC', value: 'Noto Serif SC' },\n    { name: 'ZCOOL XiaoWei', value: 'ZCOOL XiaoWei' },\n    { name: 'ZCOOL QingKe HuangYou', value: 'ZCOOL QingKe HuangYou' },\n    { name: 'Ma Shan Zheng', value: 'Ma Shan Zheng' },\n];\n\nconst FontControl = () => {\n    const [selectedFontSize, setSelectedFontSize] = useState('xl');\n    const [selectedFontFamily, setSelectedFontFamily] = useState('sans-serif');\n    const setCardConfig = useTweetsStore(state => state.setCardConfig);\n    const [customFontSize, setCustomFontSize] = useState('');\n    const [customFontFamily, setCustomFontFamily] = useState('');\n\n    const handleSelectFontSize = useCallback((sizeKey) => {\n        setSelectedFontSize(sizeKey);\n        setCardConfig({\n            fontSize: fontSizeMap[sizeKey]\n        });\n        setCustomFontSize('');\n    }, []);\n\n    const handleSelectFontFamily = useCallback((familyKey) => {\n        setSelectedFontFamily(familyKey);\n        setCardConfig({\n            fontFamily: familyKey\n        });\n        setCustomFontFamily('');\n    }, []);\n\n    const renderFontSizeControl = useMemo(() => {\n        return (\n            <div className=\"grid grid-cols-4 gap-1\">\n                {Object.keys(fontSizeMap).map(sizeKey => (\n                    <Button\n                        key={sizeKey}\n                        size=\"sm\"\n                        className={selectedFontSize === sizeKey ? 'bg-primary' : 'bg-[#262626]'}\n                        onClick={() => handleSelectFontSize(sizeKey)}\n                    >\n                        {sizeKey}\n                    </Button>\n                ))}\n            </div>\n        );\n    }, [selectedFontSize, customFontSize]);\n\n    // const renderFontFamilyControl = useMemo(() => {\n    //     return (\n    //         <div className=\"grid grid-cols-3 gap-1\">\n    //             {googleFonts.map(item => (\n    //                 <Button\n    //                     key={item.value}\n    //                     size=\"sm\"\n    //                     className={selectedFontFamily === item.value ? 'bg-primary' : 'bg-[#262626]'}\n    //                     onClick={() => handleSelectFontFamily(item.value)}\n    //                 >\n    //                     {item.name}\n    //                 </Button>\n    //             ))}\n    //         </div>\n    //     );\n    // }, [selectedFontFamily, customFontFamily]);\n\n    return (\n        <div>\n            <h3 className=\"text-sm font-semibold mb-2\">FONT SIZE</h3>\n            {renderFontSizeControl}\n            {/* <div className=\"mt-4\">\n                <h3 className=\"text-sm font-semibold mb-2\">FONT FAMILY</h3>\n                {renderFontFamilyControl}\n            </div> */}\n        </div>\n    );\n};\n\nexport default FontControl;"
  },
  {
    "path": "src/components/extension/x-cards-toast/image-preview.tsx",
    "content": "import React, { useState, useCallback, useRef, useEffect } from 'react';\nimport { ZoomIn, ZoomOut, Move, RotateCcw } from 'lucide-react';\nimport { useTweetsStore } from '../use-tweet-collection';\n\nconst ImagePreview = ({ alt = \"Preview\" }) => {\n  const [error, setError] = useState(null);\n  const [scale, setScale] = useState(1);\n  const [position, setPosition] = useState({ x: 0, y: 0 });\n  const [isDragging, setIsDragging] = useState(false);\n  const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 });\n  const containerRef = useRef(null);\n  const imageRef = useRef(null);\n  const format = useTweetsStore(state => state.cardConfig.format);\n  const imageSrc = useTweetsStore(state => state.imageSrc);\n\n  const handleImageLoad = useCallback(() => {\n    if (imageRef.current) {\n      setImageDimensions({\n        width: imageRef.current.naturalWidth,\n        height: imageRef.current.naturalHeight\n      });\n    }\n  }, []);\n\n  const handleImageError = useCallback(() => {\n    setError('Failed to load image');\n  }, []);\n\n  const handleZoomIn = useCallback(() => {\n    setScale(prevScale => Math.min(prevScale + 0.1, 3));\n  }, []);\n\n  const handleZoomOut = useCallback(() => {\n    setScale(prevScale => Math.max(prevScale - 0.1, 0.5));\n  }, []);\n\n  const handleMouseDown = useCallback((e) => {\n    setIsDragging(true);\n  }, []);\n\n  const handleReset = useCallback(() => {\n    setScale(1);\n    setPosition({ x: 0, y: 0 });\n  }, []);\n\n  const handleMouseMove = useCallback((e) => {\n    if (isDragging && containerRef.current) {\n      const containerRect = containerRef.current.getBoundingClientRect();\n      const scaledImageWidth = imageDimensions.width * scale;\n      const scaledImageHeight = imageDimensions.height * scale;\n\n      const maxX = Math.max(0, (scaledImageWidth - containerRect.width) / 2);\n      const maxY = Math.max(0, (scaledImageHeight - containerRect.height) / 2);\n\n      setPosition(prevPosition => {\n        const newX = prevPosition.x + e.movementX;\n        const newY = prevPosition.y + e.movementY;\n\n        return {\n          x: Math.max(-maxX, Math.min(maxX, newX)),\n          y: Math.max(-maxY, Math.min(maxY, newY))\n        };\n      });\n    }\n  }, [isDragging, scale, imageDimensions]);\n\n  const handleMouseUp = useCallback(() => {\n    setIsDragging(false);\n  }, []);\n\n  return (\n    <div className=\"relative w-full  h-full rounded-lg overflow-hidden  object-cover\" ref={containerRef}>\n      {error && (\n        <div className=\"absolute inset-0 flex items-center justify-center text-red-500\">\n          {error}\n        </div>\n      )}\n      <div\n        className=\"w-full h-full overflow-hidden cursor-move pb-3\"\n        onMouseDown={handleMouseDown}\n        onMouseMove={handleMouseMove}\n        onMouseUp={handleMouseUp}\n        onMouseLeave={handleMouseUp}\n      >\n        {\n          format === 'md' && (\n            <div className=\"w-full flex-col gap-y-2  h-full p-4 justify-center flex  items-center rounded-lg shadow\">\n              <svg\n                className=\"icon\"\n                viewBox=\"0 0 1024 1024\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width={64}\n                height={64}\n              >\n                <path\n                  d=\"M601.216 85.333a42.667 42.667 0 0130.485 12.822l209.451 213.973a42.667 42.667 0 0112.181 29.867v511.338A85.333 85.333 0 01768 938.667H256a85.333 85.333 0 01-85.333-85.334V170.667A85.333 85.333 0 01256 85.333h345.216zm49.579 421.035c-4.992-29.675-45.995-36.693-60.075-8.79l-78.805 156.225-77.291-156.011c-14.379-29.013-57.813-20.779-60.565 11.477l-21.952 256a32 32 0 0029.162 34.624l3.072.107a32 32 0 0031.552-29.27l12.182-142.015 54.89 110.826 1.515 2.731c12.864 20.864 44.33 20.075 55.744-2.517l56.341-111.723 11.52 142.55a32 32 0 0063.808-5.163l-20.714-256zM629.61 187.541v143.872h140.8l-140.8-143.872z\"\n                  fill=\"#323847\"\n                />\n              </svg>\n              Copy the markdown and paste to see the preview\n            </div>\n          )\n        }\n        {\n          format !== 'md' && (\n            <img\n              ref={imageRef}\n              src={imageSrc}\n              alt={alt}\n              draggable={false}\n              onLoad={handleImageLoad}\n              onError={handleImageError}\n              style={{\n                transform: `translate(${position.x}px, ${position.y}px) scale(${scale})`,\n                transition: 'transform 0.1s ease-out'\n              }}\n              className=\"w-full h-full object-contain\"\n            />\n          )\n        }\n\n      </div>\n      <div className=\"  absolute  top-0 pt-4 pl-4  w-full  flex space-x-2\">\n        <button onClick={handleZoomIn} className=\"p-2 bg-white rounded-full shadow-md\">\n          <ZoomIn className=\"w-5 h-5 text-gray-600\" />\n        </button>\n        <button onClick={handleZoomOut} className=\"p-2 bg-white rounded-full shadow-md\">\n          <ZoomOut className=\"w-5 h-5 text-gray-600\" />\n        </button>\n        <button onClick={handleReset} className=\"p-2 bg-white rounded-full shadow-md\">\n          <RotateCcw className=\"w-5 h-5 text-gray-600\" />\n        </button>\n      </div>\n    </div>\n  );\n};\n\nexport default React.memo(ImagePreview);"
  },
  {
    "path": "src/components/extension/x-cards-toast/index.module.css",
    "content": ""
  },
  {
    "path": "src/components/extension/x-cards-toast/index.tsx",
    "content": "import * as _ from 'lodash-es';\nimport React, { useEffect, useMemo, useRef, useState } from 'react';\nimport { PresetColorList } from '../preset-color-list';\nimport { Copy, Download, Loader2, X } from 'lucide-react';\nimport { sendMessageToIframe } from '@src/app/utils';\nimport { CardWidths, type XConfig } from '@src/hooks/useCardStore';\n\nimport type { PlasmoCSUIProps } from 'plasmo';\nimport TweetManager from '../tweet-manager';\nimport toast from 'react-hot-toast';\nimport { cn } from '@lib/utils';\nimport { InputCode } from '../input-code';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { useTweetsStore } from '../use-tweet-collection';\nimport ImagePreview from './image-preview';\nimport { Button } from '@components/ui/button';\nimport PaddingControl from './padding-control';\nimport TweetControl from './tweet-control';\nimport { tweet2Markdown } from '@src/app/utils/export';\nimport ScaleControl from './scale-control';\nimport FontControl from './font-control';\n\n\ninterface PreviewToastProps {\n    tweetInfo: XConfig;\n    tweetInfos: XConfig[];\n    anchor: PlasmoCSUIProps['anchor'],\n}\n\nexport const PreviewToast: React.FC<PreviewToastProps> = ({ tweetInfo, tweetInfos, anchor, }) => {\n    const [isLoading, setIsLoading] = useState(false);\n    const srcRef = useRef<string>();\n    const showCodeDialog = useTweetsStore(state => state.showCodeDialog);\n    const setShowCodeDialog = useTweetsStore(state => state.setShowCodeDialog);\n\n    const setImageSrc = useTweetsStore(state => state.setImageSrc);\n    const [tweetStyle] = useState('posts');\n    // const cardConfig = useRef({});\n    const [selectedFormat, setSelectedFormat] = useState('png');\n    const [selectedWidth, setSelectWidth] = useState('md');\n    const formats = ['png', 'jpeg', 'svg', 'md'];\n    // const cardStyles = ['posts', 'article',];\n    const cardConfig = useTweetsStore(state => state.cardConfig);\n    const setCardConfig = useTweetsStore(state => state.setCardConfig);\n    const tweets = useTweetsStore(state => state.tweets);\n    const activeTab = useTweetsStore(state => state.activeTab);\n    const isActivated = useTweetsStore((state) => state.isActivated);\n    const [isPreview, showIsPreview] = useState(false);\n\n\n    const tabs = useMemo(() => {\n        return [\n            { value: 'single', label: 'Single' },\n            { value: 'linear', label: 'Linear', isLocked: !isActivated },\n        ]\n    }, [isActivated])\n\n    const styles = {\n        container: {\n            display: 'flex',\n            // flexDirection: 'column' as const,\n            gap: '0.5rem',\n            borderRadius: '1rem',\n            padding: '12px',\n            boxSizing: 'border-box' as const,\n            color: 'white',\n            pointerEvents: 'visible' as const,\n            // maxWidth: '220px',\n            maxWidth: '576px',\n            background: 'linear-gradient(145deg, rgba(40,39,42,0.9) 0%, rgba(20,19,22,0.9) 100%)',\n            boxShadow: '0 4px 20px rgba(255, 255, 255, 0.1), 0 0 0 2px rgba(255, 255, 255, 0.1)',\n            backdropFilter: 'blur(5px)',\n            maxHeight: 'calc(100vh - 24px)',\n        },\n        imageContainer: {\n            position: 'relative' as const,\n            width: '100%',\n            // height: '200px',\n            // maxHeight: '489px',\n            borderRadius: '0.5rem',\n            overflow: 'hidden',\n            transition: 'opacity 0.3s ease-in-out',\n\n        },\n        loader: {\n            position: 'absolute' as const,\n            top: '50%',\n            left: '50%',\n            transform: 'translate(-50%, -50%)',\n\n        },\n        button: {\n            display: 'inline-flex',\n            alignItems: 'center',\n            justifyContent: 'center',\n            whiteSpace: 'nowrap' as const,\n            borderRadius: '0.375rem',\n            fontSize: '0.875rem',\n            fontWeight: '500',\n            transition: 'background-color 0.2s, border-color 0.2s, color 0.2s, box-shadow 0.2s',\n            outline: 'none',\n            boxShadow: '0 0 0 2px rgba(0, 0, 0, 0.2)',\n            backgroundColor: '#262626',\n            color: '#ffffff',\n            height: '2.5rem',\n            padding: '0.5rem 1rem',\n            cursor: 'pointer',\n            border: 'none',\n            '&:hover': {\n                backgroundColor: '#363636',\n            },\n            '&:focus': {\n                boxShadow: '0 0 0 2px rgba(255, 255, 255, 0.2)',\n            },\n        },\n        loading: {\n            animation: 'spin 1s linear infinite',\n        },\n        activeButton: {\n            backgroundColor: '#0066cc',\n        },\n    };\n\n    const keyframes = `\n    @keyframes spin {\n       to {\n        transform: rotate(360deg);\n    }\n    }\n`\n    const canPass = () => {\n        if (!isActivated && activeTab === 'linear') {\n            setShowCodeDialog(true);\n            return false;\n        }\n        return true;\n    }\n\n    const handleCopyImage = async () => {\n        if (selectedFormat === 'md') {\n            const md = tweet2Markdown(tweets);\n            // 复制文本\n            await navigator.clipboard.writeText(md);\n            toast.success('Markdown copied to clipboard', {\n                duration: 100,\n                position: 'top-center',\n            });\n            return;\n        }\n        const data = await sendMessageToIframe('generate-card-local', {\n            tweetInfo: tweets,\n            cardConfig: {\n                ...cardConfig,\n            },\n        }, 3000);\n        const imageSrc = data.dataUrl;\n        const [, mimeType] = imageSrc.match(/^data:(.+);base64,(.+)$/);\n        const blob = await fetch(imageSrc).then(res => res.blob());\n        await navigator.clipboard.write([\n            new ClipboardItem({\n                [mimeType]: blob\n            })\n        ]);\n        toast.success('Image copied to clipboard', {\n            duration: 1000,\n            position: 'top-center',\n        });\n    }\n\n    const handleDownloadImage = async () => {\n        if (selectedFormat === 'md') {\n            const md = tweet2Markdown(tweets);\n            const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' });\n            const url = URL.createObjectURL(blob);\n            const a = document.createElement('a');\n            a.href = url;\n            a.download = `x-cards.md`;\n            document.body.appendChild(a);\n            a.click();\n            document.body.removeChild(a);\n            URL.revokeObjectURL(url);\n            return;\n        }\n        const xName = 'x-card'\n        const data = await sendMessageToIframe('generate-card-local', {\n            tweetInfo: tweets,\n            cardConfig: {\n                ...cardConfig,\n            },\n        }, 3000);\n        const imageSrc = data.dataUrl;\n        const link = document.createElement('a');\n        link.download = `${xName}.${selectedFormat || 'png'}`\n        link.href = imageSrc;\n        link.click();\n    }\n\n    const handlePreview = async (tweetInfo: XConfig, tweetInfos: XConfig[] = []) => {\n        let finalTweetInfo = [tweetInfo];\n        const cardConfig = useTweetsStore.getState().cardConfig;\n        if (activeTab === 'linear') {\n            finalTweetInfo = tweetInfos\n        }\n        try {\n            setIsLoading(true);\n            const value = await sendMessageToIframe('generate-card-local', {\n                tweetInfo: finalTweetInfo,\n                cardConfig: {\n                    ...cardConfig,\n                    scale: 2,\n                },\n            }, 3000);\n            srcRef.current = value.dataUrl;\n            setImageSrc(value.dataUrl);\n            return value.dataUrl;\n        } catch (error) {\n\n        } finally {\n            setIsLoading(false);\n        }\n    }\n\n    const handleColorChange = async (colorIndex: number) => {\n        setCardConfig({\n            colorIndex,\n        });\n        await handlePreview(tweetInfo, tweets);\n    }\n\n    const handleSelectFormat = (format) => {\n        setCardConfig({\n            format: format.toLowerCase(),\n        });\n        setSelectedFormat(format.toLowerCase());\n    }\n\n    const handleSelectWidth = async (width: string) => {\n        setSelectWidth(width);\n        setCardConfig({\n            width: CardWidths[width],\n        });\n\n        await handlePreview(tweetInfo, tweets);\n    }\n\n\n\n    const renderTweetManager = useMemo(() => {\n        return (\n            activeTab === 'linear' && (<TweetManager\n                tweetInfos={tweetInfos}\n                onRemoveItem={(tweetInfos) => {\n                }}\n                onDragEnd={(tweetInfos) => {\n                    // handlePreview(tweetInfo, tweets);\n                }}\n                onReset={(tweetInfos) => {\n                    // handlePreview(tweetInfo, tweets);\n                }}\n            ></TweetManager>)\n        )\n    }, [activeTab])\n\n\n    const renderPreview = useMemo(() => {\n        return (\n            <motion.div\n\n                layout\n                style={{\n                    ...styles.imageContainer,\n                }}\n                className={cn(isLoading ? 'opacity-50' : '', 'min-w-[290px] min-h-[290px]')}\n            >\n                <motion.div\n                    animate={{\n                        height: isPreview ? '290px' : '100%',\n                    }}\n                    transition={{ duration: 0.3 }}\n                    className=\"w-full h-full\"\n                >\n                    <ImagePreview />\n                </motion.div>\n                {isLoading && (\n                    <div style={styles.loader}>\n                        <Loader2 size={24} style={styles.loading} />\n                    </div>\n                )}\n\n            </motion.div>\n\n        )\n    }, [isLoading, tweetInfo, tweetInfos, tweets, isPreview,\n        cardConfig.padding,\n        cardConfig.width,\n        cardConfig.colorIndex,\n        cardConfig.controls,\n        cardConfig.fontSize,\n        cardConfig.fontFamily,\n    ])\n\n    const renderControls = <div style={{\n        display: 'flex',\n        gap: '0.5rem',\n\n        flexDirection: 'column',\n    }}>\n        <div className=\"flex justify-between items-center mb-2\">\n            <div className='flex gap-x-2  justify-center items-center '>\n                <h2 className=\"text-sm font-semibold\">x-cards options</h2>\n            </div>\n        </div>\n\n        {renderTweetManager}\n        <PresetColorList onSelect={handleColorChange} />\n        <div>\n            <h3 className=\"text-sm font-semibold mb-2\">WIDTH</h3>\n            <div\n                className='grid grid-cols-4 gap-2 w-full'\n            >\n                {['sm', 'md', 'lg', 'xl'].map(width => (\n                    <Button\n                        size={'sm'}\n                        className={\n                            selectedWidth === width ? 'bg-primary' : 'bg-[#262626]'\n                        }\n                        key={width} onClick={() => handleSelectWidth(width)}>\n                        {width}\n                    </Button>\n                ))}\n            </div>\n\n        </div>\n        <PaddingControl></PaddingControl>\n        <TweetControl\n            onChange={(controls) => {\n                setCardConfig({ controls, });\n            }}\n        ></TweetControl>\n        <div>\n            <h3 className=\"text-sm font-semibold mb-2\">FORMAT</h3>\n            <div className='grid grid-cols-4 gap-2 w-full'>\n                {formats.map((format) => (\n                    <Button\n                        size={'sm'}\n                        key={format}\n                        className={\n                            selectedFormat === format ? 'bg-primary' : 'bg-[#262626]'\n                        }\n                        style={{\n                            ...(selectedFormat === format ? styles.activeButton : {}),\n                        }}\n                        onClick={() => handleSelectFormat(format)}\n                    >\n                        <span >{format}</span>\n                    </Button>\n                ))}\n            </div>\n        </div>\n\n        <FontControl></FontControl>\n\n        <ScaleControl value={cardConfig.scale} onChange={v => {\n            if (!isActivated) {\n                useTweetsStore.getState().setShowCodeDialog(true);\n                return;\n            }\n            setCardConfig({\n                scale: v,\n            });\n        }} />\n\n        <button style={styles.button} onClick={handleDownloadImage}>\n            <Download className=' h-4 w-4 mr-2' />\n            Download\n        </button>\n        <button\n            className='mb-[32px]'\n            style={styles.button} onClick={handleCopyImage}>\n            <Copy className=' h-4 w-4 mr-2' />\n            Copy\n        </button>\n\n        <span className='text-xs text-neutral-400 absolute  pt-2 bottom-1 right-4 '>\n            <div className='flex justify-between'>\n                <button onClick={() => {\n                    setShowCodeDialog(true);\n                }} className=' text-blue-500 mr-4'>Get License</button>\n                <span> DM <a target='_blank' href=\"https://x.com/IndieDevr\" className=' text-blue-500'>@IndieDevr</a>  unlock all features</span>\n            </div>\n            <div className=' flex  justify-end items-center'>\n                <a target=\"_blank\" href=\"https://discord.gg/Prjas7Qh\">\n                    <svg\n                        className=\"w-4 h-4 mr-2\"\n                        viewBox=\"0 0 1024 1024\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                        width={256}\n                        height={256}\n                    >\n                        <path d=\"M0 512a512 512 0 101024 0A512 512 0 100 512z\" fill=\"#738BD8\" />\n                        <path d=\"M190.915 234.305h642.169v477.288H190.915z\" fill=\"#FFF\" />\n                        <path\n                            d=\"M698.157 932.274L157.288 862.85c-58.43-7.5-55.4-191.167-50.26-249.853l26.034-297.22c5.14-58.686 74.356-120.22 132.7-128.362l466.441-65.085c58.346-8.14 177.24 212.65 176.09 271.548l-8.677 445.108M512 300.373c-114.347 0-194.56 49.067-194.56 49.067 43.947-39.253 120.747-61.867 120.747-61.867l-7.254-7.253c-72.106 1.28-137.386 51.2-137.386 51.2-73.387 153.173-68.694 285.44-68.694 285.44 59.734 77.227 148.48 71.68 148.48 71.68l30.294-38.4c-53.334-11.52-87.04-58.88-87.04-58.88S396.8 645.973 512 645.973c115.2 0 195.413-54.613 195.413-54.613s-33.706 47.36-87.04 58.88l30.294 38.4s88.746 5.547 148.48-71.68c0 0 4.693-132.267-68.694-285.44 0 0-65.28-49.92-137.386-51.2l-7.254 7.253s76.8 22.614 120.747 61.867c0 0-80.213-49.067-194.56-49.067M423.68 462.08c27.733 0 50.347 24.32 49.92 54.187 0 29.44-22.187 54.186-49.92 54.186-27.307 0-49.493-24.746-49.493-54.186 0-29.867 21.76-54.187 49.493-54.187m177.92 0c27.733 0 49.92 24.32 49.92 54.187 0 29.44-22.187 54.186-49.92 54.186-27.307 0-49.493-24.746-49.493-54.186 0-29.867 21.76-54.187 49.493-54.187z\"\n                            fill=\"#738BD8\"\n                        />\n                    </svg>\n                </a>\n            </div>\n        </span>\n    </div >\n\n\n    useEffect(() => {\n        handlePreview(tweetInfo, tweets);\n    }, [activeTab, tweets, tweetStyle,\n        cardConfig.padding,\n        cardConfig.width,\n        cardConfig.colorIndex,\n        cardConfig.controls,\n        cardConfig.fontSize,\n        cardConfig.fontFamily,\n\n    ])\n\n\n    return (\n        <motion.div className=''\n            layout\n            initial={{ width: isPreview ? '320px' : '696px' }}\n            animate={{ width: isPreview ? '320px' : '696px' }}\n            transition={{ duration: 0.3 }}\n            style={{\n                ...styles.container,\n                // width: isPreview ? '256px' : '696px',\n            }}\n            onMouseEnter={() => showIsPreview(false)}\n            onMouseLeave={() => showIsPreview(true)}\n        >\n            <style>{keyframes}</style>\n\n            <button className='absolute right-3 top-3 z-10'>\n                <X className=' h-5 w-5 text-neutral-500'\n                    onClick={() => {\n                        toast.remove('preview-toast');\n                    }}\n                ></X>\n            </button>\n            {renderPreview}\n            <AnimatePresence>\n                {!isPreview && (\n                    <motion.div\n                        initial={{ opacity: 0, height: 0 }}\n                        animate={{ opacity: 1, height: 'auto' }}\n                        exit={{ opacity: 0, height: 0 }}\n                        transition={{ duration: 0.3 }}\n                        className='max-w-[224px] overflow-auto'\n                    >\n                        {renderControls}\n                    </motion.div>\n                )}\n            </AnimatePresence>\n\n            <AnimatePresence>\n                {showCodeDialog && <InputCode onClose={() => setShowCodeDialog(false)} />}\n            </AnimatePresence>\n\n\n\n        </motion.div >\n\n    );\n};\n\n"
  },
  {
    "path": "src/components/extension/x-cards-toast/padding-control.tsx",
    "content": "import { Button } from \"@components/ui/button\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport { useTweetsStore } from \"../use-tweet-collection\";\nimport { Input } from \"@components/ui/input\";\n\nconst paddingMap = {\n    sm: 0,\n    md: 10,\n    lg: 25,\n    xl: 50\n};\n\nconst PaddingControl = () => {\n    const [selectedPadding, setSelectedPadding] = useState('sm');\n    const setCardConfig = useTweetsStore(state => state.setCardConfig);\n    const [customPadding, setCustomPadding] = useState('');\n\n\n    const handleSelectPadding = useCallback((paddingKey) => {\n        setSelectedPadding(paddingKey);\n        setCardConfig({\n            padding: paddingMap[paddingKey]\n        });\n        setCustomPadding('');\n    }, []);\n\n    const renderPaddingControl = useMemo(() => {\n        return (\n            <div className=\"grid grid-cols-4 gap-1\">\n                {Object.keys(paddingMap).map(paddingKey => (\n                    <Button\n                        key={paddingKey}\n                        size=\"sm\"\n                        className={selectedPadding === paddingKey ? 'bg-primary' : 'bg-[#262626]'}\n                        onClick={() => handleSelectPadding(paddingKey)}\n                    >\n                        {paddingKey}\n                    </Button>\n                ))}\n            </div>\n        );\n    }, [selectedPadding, customPadding]);\n\n    return (\n        <div>\n            <h3 className=\"text-sm font-semibold mb-2\">PADDING</h3>\n            {renderPaddingControl}\n        </div>\n    );\n};\n\nexport default PaddingControl;\n"
  },
  {
    "path": "src/components/extension/x-cards-toast/scale-control.tsx",
    "content": "import React from 'react';\nimport { Tabs, TabsList, TabsTrigger } from '@components/ui/tabs'; // Assuming you're using Radix UI\nimport { useTweetsStore } from '../use-tweet-collection';\nimport LabelWithIcon from '../label-with-icon';\n\ninterface ScaleControlProps {\n    value: number;\n    onChange: (value: number) => void;\n}\n\nconst ScaleControl: React.FC<ScaleControlProps> = ({ value, onChange }) => {\n    const isActivated = useTweetsStore((state) => state.isActivated);\n\n    return (\n        <div>\n            <LabelWithIcon\n                label=\"IMAGE QUALITY\"\n                isActivated={isActivated}\n                className='mb-2'\n            />\n            <Tabs className='w-full' value={String(value)} onValueChange={(value) => onChange(Number(value))}>\n                <TabsList className='grid grid-cols-4 justify-center items-center bg-[#262626]'>\n                    <TabsTrigger className='text-gray-400' value='1'>1x</TabsTrigger>\n                    <TabsTrigger className='text-gray-400' value='2'>2x</TabsTrigger>\n                    <TabsTrigger className='text-gray-400' value='3'>3x</TabsTrigger>\n                    <TabsTrigger className='text-gray-400' value='4'>4x</TabsTrigger>\n                </TabsList>\n            </Tabs>\n        </div>\n\n    );\n};\n\nexport default ScaleControl;"
  },
  {
    "path": "src/components/extension/x-cards-toast/tweet-control.tsx",
    "content": "import React, { useState } from 'react';\nimport { Activity, Clock, Heart, Twitter, User } from 'lucide-react';\nimport { useTweetsStore, type TweetControlState } from '../use-tweet-collection';\nimport LabelWithIcon from '../label-with-icon';\n\n\n\ninterface TweetControlProps {\n  onChange: (state: TweetControlState) => void;\n}\n\nconst TweetControl: React.FC<TweetControlProps> = ({ onChange }) => {\n  const isActivated = useTweetsStore((state) => state.isActivated);\n  const [state, setState] = useState<TweetControlState>({\n    showUser: true,\n    showActions: true,\n    showTime: true,\n    showFooter: true,\n    showLogo: true,\n  });\n\n  const toggleOption = (option: keyof TweetControlState) => {\n    const newState = { ...state, [option]: !state[option] };\n    if (!isActivated) {\n      useTweetsStore.getState().setShowCodeDialog(true);\n      return;\n    }\n    setState(newState);\n    onChange(newState);\n  };\n\n  return (\n    <div className=\" \">\n      <LabelWithIcon\n        label=\"CONTROLS\"\n        isActivated={isActivated}\n        className='mb-2'\n      />\n\n      <div className=\"grid grid-cols-4 gap-1\">\n        <ControlOption\n          label='user'\n          active={state.showUser}\n          onClick={() => toggleOption('showUser')}\n        />\n        <ControlOption\n          label='response'\n          active={state.showActions}\n          onClick={() => toggleOption('showActions')}\n        />\n        <ControlOption\n          label='footer'\n          active={state.showFooter}\n          onClick={() => toggleOption('showFooter')}\n        />\n        <ControlOption\n          label='logo'\n          active={state.showLogo}\n          onClick={() => toggleOption('showLogo')}\n        />\n      </div>\n    </div>\n  );\n};\n\ninterface ControlOptionProps {\n  label: string;\n  active: boolean;\n  onClick: () => void;\n}\n\nconst ControlOption: React.FC<ControlOptionProps> = ({ label, active, onClick, }) => {\n  const getIcon = () => {\n    switch (label) {\n      case 'user':\n        return <User className=\"w-4 h-4\" />;\n      case 'response':\n        return <Heart\n          className=\"w-4 h-4\" />;\n      case 'time':\n        return <Clock className=\"w-4 h-4\" />;\n      case 'footer':\n        return <Activity className=\"w-4 h-4\" />\n      case 'logo':\n        return <Twitter className=\"w-4 h-4\" />\n    }\n  };\n\n  return (\n    <div\n      className='flex flex-col items-center gap-y-2'\n      onClick={onClick}\n    >\n      <div\n        className={`flex items-center p-2.5 rounded-lg cursor-pointer transition-colors duration-300 ${active ? 'bg-blue-600 text-white' : 'bg-gray-800 text-gray-300 hover:bg-gray-700'\n          }`}\n      >{getIcon()}</div>\n      <div className=\"text-sm \">{label}</div>\n    </div>\n  );\n};\nexport default TweetControl;"
  },
  {
    "path": "src/components/sortableList.tsx",
    "content": "\"use client\"\n\n// npx shadcn-ui@latest add checkbox\n// npm  i react-use-measure\nimport { type Dispatch, type ReactNode, type SetStateAction, useState } from \"react\"\nimport {\n    AnimatePresence,\n    LayoutGroup,\n    Reorder,\n    motion,\n    useDragControls,\n} from \"framer-motion\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport type Item<T> = T & {\n    id: number\n}\n\ninterface SortableListItemProps<T> {\n    item: Item<T>\n    order: number\n    onCompleteItem?: (id: number) => void\n    onRemoveItem?: (id: number) => void\n    renderExtra?: (item: Item<T>) => React.ReactNode\n    isExpanded?: boolean\n    className?: string\n    handleDrag: () => void\n    onDragEnd?: () => void\n}\n\nfunction SortableListItem<T>({\n    item,\n    order,\n    onRemoveItem,\n    renderExtra,\n    handleDrag,\n    isExpanded,\n    className,\n    onDragEnd\n}: SortableListItemProps<T>) {\n    let [ref, bounds] = useMeasure()\n    const [isDragging, setIsDragging] = useState(false)\n    const [isDraggable, setIsDraggable] = useState(true)\n    const dragControls = useDragControls()\n\n    const handleDragStart = (event: any) => {\n        setIsDragging(true)\n        dragControls.start(event, { snapToCursor: true })\n        handleDrag()\n    }\n\n    const handleDragEnd = () => {\n        setIsDragging(false)\n        onDragEnd?.()\n    }\n\n    return (\n        <motion.div className={cn(\"\", className)} key={item.id}>\n            <div className=\"flex w-full items-center\">\n                <Reorder.Item\n                    value={item}\n                    className={cn(\n                        \"relative z-auto grow\",\n                        \"h-full rounded-xl bg-[#161716]/80\",\n                        \"shadow-[0px_1px_0px_0px_hsla(0,0%,100%,.03)_inset,0px_0px_0px_1px_hsla(0,0%,100%,.03)_inset,0px_0px_0px_1px_rgba(0,0,0,.1),0px_2px_2px_0px_rgba(0,0,0,.1),0px_4px_4px_0px_rgba(0,0,0,.1),0px_8px_8px_0px_rgba(0,0,0,.1)]\",\n                        !isDragging ? \"w-7/10\" : \"w-full\"\n                    )}\n                    key={item.id}\n                    initial={{ opacity: 0 }}\n                    animate={{\n                        opacity: 1,\n                        height: bounds.height > 0 ? bounds.height : undefined,\n                        transition: {\n                            type: \"spring\",\n                            bounce: 0,\n                            duration: 0.4,\n                        },\n                    }}\n                    exit={{\n                        opacity: 0,\n                        transition: {\n                            duration: 0.05,\n                            type: \"spring\",\n                            bounce: 0.1,\n                        },\n                    }}\n                    layout\n                    layoutId={`item-${item.id}`}\n                    // dragListener={!item.checked}\n                    dragControls={dragControls}\n                    onDragEnd={handleDragEnd}\n                    style={\n                        isExpanded\n                            ? {\n                                zIndex: 9999,\n                                marginTop: 10,\n                                marginBottom: 10,\n                                position: \"relative\",\n                                overflow: \"hidden\",\n                            }\n                            : {\n                                position: \"relative\",\n                                overflow: \"hidden\",\n                            }\n                    }\n                    whileDrag={{ zIndex: 9999 }}\n                >\n                    <div ref={ref} className={cn(isExpanded ? \"\" : \"\", \"z-20 \")}>\n                        <motion.div\n                            layout=\"position\"\n                            className=\"flex items-center justify-center \"\n                        >\n                            {renderExtra && renderExtra(item)}\n                        </motion.div>\n                    </div>\n                    <div\n                        onPointerDown={isDraggable ? handleDragStart : undefined}\n                        style={{ touchAction: \"none\" }}\n                    />\n                </Reorder.Item>\n            </div>\n        </motion.div>\n    )\n}\n\nSortableListItem.displayName = \"SortableListItem\"\n\nexport interface SortableListProps<T> {\n    items: Item<T>[]\n    setItems: Dispatch<SetStateAction<Item<T>[]>>\n    renderItem: (\n        item: Item<T>,\n        order: number,\n        onRemoveItem: (id: number) => void\n    ) => ReactNode,\n    onDragEnd?: () => void\n    onRemoveItem?: (id: number) => void\n}\n\nfunction SortableList<T>({\n    items,\n    setItems,\n    renderItem,\n    onDragEnd,\n    onRemoveItem,\n}: SortableListProps<T>) {\n    if (items) {\n        return (\n            <LayoutGroup>\n                <Reorder.Group\n                    axis=\"y\"\n                    values={items}\n                    onReorder={setItems}\n                    onDragEnd={onDragEnd}\n                    className=\"flex flex-col\"\n                >\n                    <AnimatePresence>\n                        {items?.map((item, index) =>\n                            renderItem(item, index, (id: number) => {\n                                setItems((items) => items.filter((item) => item.id !== id));\n                                onRemoveItem?.(id);\n                            }\n                            )\n                        )}\n                    </AnimatePresence>\n                </Reorder.Group>\n            </LayoutGroup>\n        )\n    }\n    return null\n}\n\nSortableList.displayName = \"SortableList\"\n\nexport { SortableList, SortableListItem }\n"
  },
  {
    "path": "src/components/ui/BentoGrid.tsx",
    "content": "import type { ReactNode } from \"react\";\nimport { ArrowRightIcon } from \"@radix-ui/react-icons\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"./button\";\n\nconst BentoGrid = ({\n  children,\n  className,\n}: {\n  children: ReactNode;\n  className?: string;\n}) => {\n  return (\n    <div\n      className={cn(\n        \"grid w-full auto-rows-[22rem] grid-cols-3 gap-4\",\n        className,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nconst BentoCard = ({\n  name,\n  className,\n  background,\n  Icon,\n  description,\n  href,\n  cta,\n}: {\n  name: string;\n  className: string;\n  background: ReactNode;\n  Icon: any;\n  description: string;\n  href: string;\n  cta: string;\n}) => (\n  <div\n    key={name}\n    className={cn(\n      \"group relative col-span-3 flex flex-col justify-between overflow-hidden rounded-xl\",\n      // light styles\n      \"bg-white [box-shadow:0_0_0_1px_rgba(0,0,0,.03),0_2px_4px_rgba(0,0,0,.05),0_12px_24px_rgba(0,0,0,.05)]\",\n      // dark styles\n      \"transform-gpu dark:bg-black dark:[border:1px_solid_rgba(255,255,255,.1)] dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset]\",\n      className,\n    )}\n  >\n    <div>{background}</div>\n    <div className=\"pointer-events-none z-10 flex transform-gpu flex-col gap-1 p-6 transition-all duration-300 group-hover:-translate-y-10\">\n      <Icon className=\"h-12 w-12 origin-left transform-gpu text-neutral-700 transition-all duration-300 ease-in-out group-hover:scale-75\" />\n      <h3 className=\"text-xl font-semibold text-neutral-700 dark:text-neutral-300\">\n        {name}\n      </h3>\n      <p className=\"max-w-lg text-neutral-400\">{description}</p>\n    </div>\n\n    <div\n      className={cn(\n        \"pointer-events-none absolute bottom-0 flex w-full translate-y-10 transform-gpu flex-row items-center p-4 opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100\",\n      )}\n    >\n      <Button variant=\"ghost\" asChild size=\"sm\" className=\"pointer-events-auto\">\n        <a href={href}>\n          {cta}\n          <ArrowRightIcon className=\"ml-2 h-4 w-4\" />\n        </a>\n      </Button>\n    </div>\n    <div className=\"pointer-events-none absolute inset-0 transform-gpu transition-all duration-300 group-hover:bg-black/[.03] group-hover:dark:bg-neutral-800/10\" />\n  </div>\n);\n\nexport { BentoCard, BentoGrid };\n"
  },
  {
    "path": "src/components/ui/DotPattern.tsx",
    "content": "import { useId } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface DotPatternProps {\n  width?: any;\n  height?: any;\n  x?: any;\n  y?: any;\n  cx?: any;\n  cy?: any;\n  cr?: any;\n  className?: string;\n  [key: string]: any;\n}\nexport function DotPattern({\n  width = 16,\n  height = 16,\n  x = 0,\n  y = 0,\n  cx = 1,\n  cy = 1,\n  cr = 1,\n  className,\n  ...props\n}: DotPatternProps) {\n  const id = useId();\n\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={cn(\n        \"pointer-events-none absolute inset-0 h-full w-full fill-neutral-400/80\",\n        className,\n      )}\n      {...props}\n    >\n      <defs>\n        <pattern\n          id={id}\n          width={width}\n          height={height}\n          patternUnits=\"userSpaceOnUse\"\n          patternContentUnits=\"userSpaceOnUse\"\n          x={x}\n          y={y}\n        >\n          <circle id=\"pattern-circle\" cx={cx} cy={cy} r={cr} />\n        </pattern>\n      </defs>\n      <rect width=\"100%\" height=\"100%\" strokeWidth={0} fill={`url(#${id})`} />\n    </svg>\n  );\n}\n\nexport default DotPattern;\n"
  },
  {
    "path": "src/components/ui/acetenity-tabs.tsx",
    "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { cn } from \"@lib/utils\";\n\ntype Tab = {\n    title: string;\n    value: string;\n    content?: string | React.ReactNode | any;\n};\n\nexport const Tabs = ({\n    tabs: propTabs,\n    containerClassName,\n    activeTabClassName,\n    tabClassName,\n    contentClassName,\n}: {\n    tabs: Tab[];\n    containerClassName?: string;\n    activeTabClassName?: string;\n    tabClassName?: string;\n    contentClassName?: string;\n}) => {\n    const [active, setActive] = useState<Tab>(propTabs[0]);\n    const [tabs, setTabs] = useState<Tab[]>(propTabs);\n\n    const moveSelectedTabToTop = (idx: number) => {\n        const newTabs = [...propTabs];\n        const selectedTab = newTabs.splice(idx, 1);\n        newTabs.unshift(selectedTab[0]);\n        setTabs(newTabs);\n        setActive(newTabs[0]);\n    };\n\n    const [hovering, setHovering] = useState(false);\n\n    return (\n        <>\n            <div\n                className={cn(\n                    \"flex flex-row items-center justify-start [perspective:1000px] relative overflow-auto sm:overflow-visible no-visible-scrollbar max-w-full w-full\",\n                    containerClassName\n                )}\n            >\n                {propTabs.map((tab, idx) => (\n                    <button\n                        key={tab.title}\n                        onClick={() => {\n                            moveSelectedTabToTop(idx);\n                        }}\n                        onMouseEnter={() => setHovering(true)}\n                        onMouseLeave={() => setHovering(false)}\n                        className={cn(\"relative px-4 py-2 rounded-full\", tabClassName)}\n                        style={{\n                            transformStyle: \"preserve-3d\",\n                        }}\n                    >\n                        {active.value === tab.value && (\n                            <motion.div\n                                layoutId=\"clickedbutton\"\n                                transition={{ type: \"spring\", bounce: 0.3, duration: 0.6 }}\n                                className={cn(\n                                    \"absolute inset-0 bg-gray-200 dark:bg-zinc-800 rounded-full \",\n                                    activeTabClassName\n                                )}\n                            />\n                        )}\n\n                        <span className=\"relative block text-black dark:text-white\">\n                            {tab.title}\n                        </span>\n                    </button>\n                ))}\n            </div>\n            <FadeInDiv\n                tabs={tabs}\n                active={active}\n                key={active.value}\n                hovering={hovering}\n                className={cn(\"mt-8\", contentClassName)}\n            />\n        </>\n    );\n};\n\nexport const FadeInDiv = ({\n    className,\n    tabs,\n    hovering,\n}: {\n    className?: string;\n    key?: string;\n    tabs: Tab[];\n    active: Tab;\n    hovering?: boolean;\n}) => {\n    const isActive = (tab: Tab) => {\n        return tab.value === tabs[0].value;\n    };\n    return (\n        <div className=\"relative w-full h-full\">\n            {tabs.map((tab, idx) => (\n                <motion.div\n                    key={tab.value}\n                    layoutId={tab.value}\n                    style={{\n                        scale: 1 - idx * 0.1,\n                        top: hovering ? idx * -50 : 0,\n                        zIndex: -idx,\n                        opacity: idx < 3 ? 1 - idx * 0.1 : 0,\n                    }}\n                    animate={{\n                        y: isActive(tab) ? [0, 40, 0] : 0,\n                    }}\n                    className={cn(\"w-full h-full absolute top-0 left-0\", className)}\n                >\n                    {tab.content}\n                </motion.div>\n            ))}\n        </div>\n    );\n};\n"
  },
  {
    "path": "src/components/ui/animated-list.tsx",
    "content": "\"use client\";\n\nimport React, { type ReactElement, useEffect, useMemo, useState } from \"react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\n\nexport interface AnimatedListProps {\n    className?: string;\n    children: React.ReactNode;\n    delay?: number;\n}\n\nexport const AnimatedList = React.memo(\n    ({ className, children, delay = 1000 }: AnimatedListProps) => {\n        const [index, setIndex] = useState(0);\n        const childrenArray = React.Children.toArray(children);\n\n        useEffect(() => {\n            const interval = setInterval(() => {\n                setIndex((prevIndex) => (prevIndex + 1) % childrenArray.length);\n            }, delay);\n\n            return () => clearInterval(interval);\n        }, [childrenArray.length, delay]);\n\n        const itemsToShow = useMemo(\n            () => childrenArray.slice(0, index + 1).reverse(),\n            [index, childrenArray],\n        );\n\n        return (\n            <div className={`flex flex-col items-center gap-4 ${className}`}>\n                <AnimatePresence>\n                    {itemsToShow.map((item) => (\n                        <AnimatedListItem key={(item as ReactElement).key}>\n                            {item}\n                        </AnimatedListItem>\n                    ))}\n                </AnimatePresence>\n            </div>\n        );\n    },\n);\n\nAnimatedList.displayName = \"AnimatedList\";\n\nexport function AnimatedListItem({ children }: { children: React.ReactNode }) {\n    const animations = {\n        initial: { scale: 0, opacity: 0 },\n        animate: { scale: 1, opacity: 1, originY: 0 },\n        exit: { scale: 0, opacity: 0 },\n        transition: { type: \"spring\", stiffness: 350, damping: 40 },\n    };\n\n    return (\n        <motion.div {...animations} layout className=\"mx-auto w-full\">\n            {children}\n        </motion.div>\n    );\n}\n"
  },
  {
    "path": "src/components/ui/animatedBeam.tsx",
    "content": "\"use client\";\n\nimport { type RefObject, useEffect, useId, useState } from \"react\";\nimport { motion } from \"framer-motion\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface AnimatedBeamProps {\n    className?: string;\n    containerRef: RefObject<HTMLElement>; // Container ref\n    fromRef: RefObject<HTMLElement>;\n    toRef: RefObject<HTMLElement>;\n    curvature?: number;\n    reverse?: boolean;\n    pathColor?: string;\n    pathWidth?: number;\n    pathOpacity?: number;\n    gradientStartColor?: string;\n    gradientStopColor?: string;\n    delay?: number;\n    duration?: number;\n    startXOffset?: number;\n    startYOffset?: number;\n    endXOffset?: number;\n    endYOffset?: number;\n}\n\nexport const AnimatedBeam: React.FC<AnimatedBeamProps> = ({\n    className,\n    containerRef,\n    fromRef,\n    toRef,\n    curvature = 0,\n    reverse = false, // Include the reverse prop\n    duration = Math.random() * 3 + 4,\n    delay = 0,\n    pathColor = \"gray\",\n    pathWidth = 2,\n    pathOpacity = 0.2,\n    gradientStartColor = \"#ffaa40\",\n    gradientStopColor = \"#9c40ff\",\n    startXOffset = 0,\n    startYOffset = 0,\n    endXOffset = 0,\n    endYOffset = 0,\n}) => {\n    const id = useId();\n    const [pathD, setPathD] = useState(\"\");\n    const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 });\n\n    // Calculate the gradient coordinates based on the reverse prop\n    const gradientCoordinates = reverse\n        ? {\n            x1: [\"90%\", \"-10%\"],\n            x2: [\"100%\", \"0%\"],\n            y1: [\"0%\", \"0%\"],\n            y2: [\"0%\", \"0%\"],\n        }\n        : {\n            x1: [\"10%\", \"110%\"],\n            x2: [\"0%\", \"100%\"],\n            y1: [\"0%\", \"0%\"],\n            y2: [\"0%\", \"0%\"],\n        };\n\n    useEffect(() => {\n        const updatePath = () => {\n            if (containerRef.current && fromRef.current && toRef.current) {\n                const containerRect = containerRef.current.getBoundingClientRect();\n                const rectA = fromRef.current.getBoundingClientRect();\n                const rectB = toRef.current.getBoundingClientRect();\n\n                const svgWidth = containerRect.width;\n                const svgHeight = containerRect.height;\n                setSvgDimensions({ width: svgWidth, height: svgHeight });\n\n                const startX =\n                    rectA.left - containerRect.left + rectA.width / 2 + startXOffset;\n                const startY =\n                    rectA.top - containerRect.top + rectA.height / 2 + startYOffset;\n                const endX =\n                    rectB.left - containerRect.left + rectB.width / 2 + endXOffset;\n                const endY =\n                    rectB.top - containerRect.top + rectB.height / 2 + endYOffset;\n\n                const controlY = startY - curvature;\n                const d = `M ${startX},${startY} Q ${(startX + endX) / 2\n                    },${controlY} ${endX},${endY}`;\n                setPathD(d);\n            }\n        };\n\n        // Initialize ResizeObserver\n        const resizeObserver = new ResizeObserver((entries) => {\n            // For all entries, recalculate the path\n            for (let entry of entries) {\n                updatePath();\n            }\n        });\n\n        // Observe the container element\n        if (containerRef.current) {\n            resizeObserver.observe(containerRef.current);\n        }\n\n        // Call the updatePath initially to set the initial path\n        updatePath();\n\n        // Clean up the observer on component unmount\n        return () => {\n            resizeObserver.disconnect();\n        };\n    }, [\n        containerRef,\n        fromRef,\n        toRef,\n        curvature,\n        startXOffset,\n        startYOffset,\n        endXOffset,\n        endYOffset,\n    ]);\n\n    return (\n        <svg\n            fill=\"none\"\n            width={svgDimensions.width}\n            height={svgDimensions.height}\n            xmlns=\"http://www.w3.org/2000/svg\"\n            className={cn(\n                \"pointer-events-none absolute left-0 top-0 transform-gpu stroke-2\",\n                className,\n            )}\n            viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}\n        >\n            <path\n                d={pathD}\n                stroke={pathColor}\n                strokeWidth={pathWidth}\n                strokeOpacity={pathOpacity}\n                strokeLinecap=\"round\"\n            />\n            <path\n                d={pathD}\n                strokeWidth={pathWidth}\n                stroke={`url(#${id})`}\n                strokeOpacity=\"1\"\n                strokeLinecap=\"round\"\n            />\n            <defs>\n                <motion.linearGradient\n                    className=\"transform-gpu\"\n                    id={id}\n                    gradientUnits={\"userSpaceOnUse\"}\n                    initial={{\n                        x1: \"0%\",\n                        x2: \"0%\",\n                        y1: \"0%\",\n                        y2: \"0%\",\n                    }}\n                    animate={{\n                        x1: gradientCoordinates.x1,\n                        x2: gradientCoordinates.x2,\n                        y1: gradientCoordinates.y1,\n                        y2: gradientCoordinates.y2,\n                    }}\n                    transition={{\n                        delay,\n                        duration,\n                        ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo\n                        repeat: Infinity,\n                        repeatDelay: 0,\n                    }}\n                >\n                    <stop stopColor={gradientStartColor} stopOpacity=\"0\"></stop>\n                    <stop stopColor={gradientStartColor}></stop>\n                    <stop offset=\"32.5%\" stopColor={gradientStopColor}></stop>\n                    <stop\n                        offset=\"100%\"\n                        stopColor={gradientStopColor}\n                        stopOpacity=\"0\"\n                    ></stop>\n                </motion.linearGradient>\n            </defs>\n        </svg>\n    );\n};\n"
  },
  {
    "path": "src/components/ui/api-key-panel.tsx",
    "content": "// import { Tabs } from \"@src/components/ui/acetenity-tabs\";\nimport { requestTestConnection } from \"@src/app/(app)/request\";\nimport { useEffect, useState } from \"react\";\nexport const ApiKeyPanel = () => {\n\n    const [apiKey, setApiKey] = useState('');\n    const [apiBaseUrl, setApiBaseUrl] = useState('https://api.openai.com/v1');\n\n    useEffect(() => {\n        const storedApiKey = localStorage.getItem('apikey');\n        if (storedApiKey) {\n            setApiKey(storedApiKey.trim());\n        }\n\n        const storedApiBaseUrl = localStorage.getItem('apiBaseUrl');\n        if (storedApiBaseUrl) {\n            setApiBaseUrl(storedApiBaseUrl);\n        } else {\n            localStorage.setItem('apiBaseUrl', apiBaseUrl.trim());\n        }\n    }, []);\n\n    const handleTestConnection = async () => {\n        const result = await requestTestConnection({\n            apiKey,\n            apiBaseUrl\n        })\n        if (result) {\n            alert('Connection Successful')\n        } else {\n            alert('Connection Failed')\n        }\n    }\n\n\n    return (\n        <div className=\"py-8 group\">\n            <div className=\"rounded-xl border bg-card text-card-foreground shadow  lg:py-4 relative\">\n                <div className=\"p-6 pt-0 flex flex-col w-full gap-8 relative md:items-center\">\n                    <div className=\"md:text-center space-y-3 pt-4\">\n                        <h3 className=\" flex items-center justify-center  gap-x-2 text-4xl md:text-6xl tracking-tighter font-extrabold text-neutral-900\">\n                            <svg\n                                width={41}\n                                height={41}\n                                viewBox=\"0 0 41 41\"\n                                fill=\"none\"\n                                xmlns=\"http://www.w3.org/2000/svg\"\n                                className=\"h-12 w-12 max-sm:mb-48 max-xs:mb-60\"\n                            >\n                                <text x={-9999} y={-9999}>\n                                    {\"ChatGPT\"}\n                                </text>\n                                <path\n                                    d=\"M37.532 16.87a9.963 9.963 0 00-.856-8.184 10.078 10.078 0 00-10.855-4.835A9.964 9.964 0 0018.306.5a10.079 10.079 0 00-9.614 6.977 9.967 9.967 0 00-6.664 4.834 10.08 10.08 0 001.24 11.817 9.965 9.965 0 00.856 8.185 10.079 10.079 0 0010.855 4.835 9.965 9.965 0 007.516 3.35 10.078 10.078 0 009.617-6.981 9.967 9.967 0 006.663-4.834 10.079 10.079 0 00-1.243-11.813zM22.498 37.886a7.474 7.474 0 01-4.799-1.735c.061-.033.168-.091.237-.134l7.964-4.6a1.294 1.294 0 00.655-1.134V19.054l3.366 1.944a.12.12 0 01.066.092v9.299a7.505 7.505 0 01-7.49 7.496zM6.392 31.006a7.471 7.471 0 01-.894-5.023c.06.036.162.099.237.141l7.964 4.6a1.297 1.297 0 001.308 0l9.724-5.614v3.888a.12.12 0 01-.048.103l-8.051 4.649a7.504 7.504 0 01-10.24-2.744zM4.297 13.62A7.469 7.469 0 018.2 10.333c0 .068-.004.19-.004.274v9.201a1.294 1.294 0 00.654 1.132l9.723 5.614-3.366 1.944a.12.12 0 01-.114.01L7.04 23.856a7.504 7.504 0 01-2.743-10.237zm27.658 6.437l-9.724-5.615 3.367-1.943a.121.121 0 01.113-.01l8.052 4.648a7.498 7.498 0 01-1.158 13.528v-9.476a1.293 1.293 0 00-.65-1.132zm3.35-5.043c-.059-.037-.162-.099-.236-.141l-7.965-4.6a1.298 1.298 0 00-1.308 0l-9.723 5.614v-3.888a.12.12 0 01.048-.103l8.05-4.645a7.497 7.497 0 0111.135 7.763zm-21.063 6.929l-3.367-1.944a.12.12 0 01-.065-.092v-9.299a7.497 7.497 0 0112.293-5.756 6.94 6.94 0 00-.236.134l-7.965 4.6a1.294 1.294 0 00-.654 1.132l-.006 11.225zm1.829-3.943l4.33-2.501 4.332 2.5v5l-4.331 2.5-4.331-2.5V18z\"\n                                    fill=\"currentColor\"\n                                />\n                            </svg>\n                            Setting Your OpenAI\n                            <span className='w-2'></span>\n                            KEY\n                        </h3>\n                        <ul>\n                            <li className='text-lg pb-3 tracking-tight text-neutral-800 text-pretty max-w-xl leading-5'>You also use your own apikey, we won't save any of your data</li>\n                        </ul>\n                        <div className='flex flex-col gap-y-1 items-start'>\n                            <label>API BASE URL</label>\n                            <input\n                                placeholder='https://api.openai.com'\n                                defaultValue={apiBaseUrl}\n                                onChange={(e) => {\n                                    localStorage.setItem('apiBaseUrl', e.target.value);\n                                    setApiBaseUrl(e.target.value);\n                                }}\n                                className=' border  border-border flex h-10 w-full border-none bg-gray-50 dark:bg-zinc-800 text-black dark:text-white shadow-input rounded-md px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-neutral-400 dark:placeholder-text-neutral-600 focus-visible:outline-none focus-visible:ring-[2px] focus-visible:ring-neutral-400 dark:focus-visible:ring-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:shadow-[0px_0px_1px_1px_var(--neutral-700)] group-hover/input:shadow-none transition duration-400'\n                            />\n                        </div>\n                        <div className='flex flex-col gap-y-1 items-start'>\n                            <label>\n                                <span>API KEY</span>\n                            </label>\n                            <input\n                                type=\"password\"\n                                placeholder=\"sk-your api key\"\n                                onChange={(e) => {\n                                    localStorage.setItem('apikey', e.target.value);\n                                    setApiKey(e.target.value)\n                                }}\n                                defaultValue={apiKey}\n                                // value={apikey}\n                                className='flex border  border-border h-10 w-full  bg-gray-50 dark:bg-zinc-800 text-black dark:text-white shadow-input rounded-md px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-neutral-400 dark:placeholder-text-neutral-600 focus-visible:outline-none focus-visible:ring-[2px] focus-visible:ring-neutral-400 dark:focus-visible:ring-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:shadow-[0px_0px_1px_1px_var(--neutral-700)] group-hover/input:shadow-none transition duration-400'\n                            />\n                        </div>\n\n                        <div className=\"flex items-center gap-[2px]  justify-center bg-black/80 rounded-sm cursor-pointer\">\n                            <p onClick={handleTestConnection} className=\"text-white pl-2 py-2\">Connect Test</p>\n                            <svg\n                                xmlns=\"http://www.w3.org/2000/svg\"\n                                width={24}\n                                height={24}\n                                viewBox=\"0 0 24 24\"\n                                fill=\"none\"\n                                stroke=\"currentColor\"\n                                strokeWidth={2}\n                                strokeLinecap=\"round\"\n                                strokeLinejoin=\"round\"\n                                className=\"lucide lucide-arrow-up-right text-white/80 group-hover:w-6 group-hover:h-6 group-hover:rotate-45 transition-all duration-100 ease-in-out\"\n                            >\n                                <path d=\"M7 7h10v10\" />\n                                <path d=\"M7 17 17 7\" />\n                            </svg>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    )\n}"
  },
  {
    "path": "src/components/ui/bold-copy.tsx",
    "content": "import { Tourney } from \"next/font/google\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst tourney = Tourney({\n    subsets: [\"latin\"],\n});\n\nexport default function BoldCopy({\n    text = \"re-tag\",\n    className,\n    textClassName,\n    backgroundTextClassName,\n}: {\n    text: string;\n    className?: string;\n    textClassName?: string;\n    backgroundTextClassName?: string;\n}) {\n    if (!text?.length) {\n        return null;\n    }\n\n    return (\n        <div\n            className={cn(\n                \"group relative flex items-center justify-center bg-background px-2 py-2 md:px-6 md:py-4\",\n                tourney.className,\n                className,\n            )}\n        >\n            <div\n                className={cn(\n                    \"text-4xl font-bold uppercase text-foreground/15 text-secondary transition-all group-hover:opacity-50 md:text-8xl\",\n                    backgroundTextClassName,\n                )}\n            >\n                {text}\n            </div>\n            <div\n                className={cn(\n                    \"text-md absolute font-bold uppercase text-foreground transition-all group-hover:text-4xl md:text-3xl group-hover:md:text-8xl\",\n                    textClassName,\n                )}\n            >\n                {text}\n            </div>\n        </div>\n    );\n}\n"
  },
  {
    "path": "src/components/ui/burnIn.tsx",
    "content": "\"use client\";\n\nimport { motion } from \"framer-motion\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface BlurIntProps {\n  word: string;\n  className?: string;\n  variant?: {\n    hidden: { filter: string; opacity: number };\n    visible: { filter: string; opacity: number };\n  };\n  duration?: number;\n}\nconst BlurIn = ({ word, className, variant, duration = 1 }: BlurIntProps) => {\n  const defaultVariants = {\n    hidden: { filter: \"blur(10px)\", opacity: 0 },\n    visible: { filter: \"blur(0px)\", opacity: 1 },\n  };\n  const combinedVariants = variant || defaultVariants;\n\n  return (\n    <motion.h1\n      initial=\"hidden\"\n      animate=\"visible\"\n      transition={{ duration }}\n      variants={combinedVariants}\n      className={cn(\n        className,\n        \"font-display text-center  font-bold tracking-[-0.02em] drop-shadow-sm  md:leading-[1.5rem]\",\n      )}\n    >\n      {word}\n    </motion.h1>\n  );\n};\n\nexport default BlurIn;\n"
  },
  {
    "path": "src/components/ui/card.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\n      \"rounded-lg border bg-card text-card-foreground shadow-sm\",\n      className\n    )}\n    {...props}\n  />\n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n    {...props}\n  />\n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n  <h3\n    ref={ref}\n    className={cn(\n      \"text-2xl font-semibold leading-none tracking-tight\",\n      className\n    )}\n    {...props}\n  />\n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n  <p\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex items-center p-6 pt-0\", className)}\n    {...props}\n  />\n))\nCardFooter.displayName = \"CardFooter\"\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }\n"
  },
  {
    "path": "src/components/ui/chart.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RechartsPrimitive from \"recharts\"\nimport type {\n  NameType,\n  Payload,\n  ValueType,\n} from \"recharts/types/component/DefaultTooltipContent\"\n\nimport { cn } from \"@/lib/utils\"\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: \"\", dark: \".dark\" } as const\n\nexport type ChartConfig = {\n  [k in string]: {\n    label?: React.ReactNode\n    icon?: React.ComponentType\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  )\n}\n\ntype ChartContextProps = {\n  config: ChartConfig\n}\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null)\n\nfunction useChart() {\n  const context = React.useContext(ChartContext)\n\n  if (!context) {\n    throw new Error(\"useChart must be used within a <ChartContainer />\")\n  }\n\n  return context\n}\n\nconst ChartContainer = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<\"div\"> & {\n    config: ChartConfig\n    children: React.ComponentProps<\n      typeof RechartsPrimitive.ResponsiveContainer\n    >[\"children\"]\n  }\n>(({ id, className, children, config, ...props }, ref) => {\n  const uniqueId = React.useId()\n  const chartId = `chart-${id || uniqueId.replace(/:/g, \"\")}`\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        data-chart={chartId}\n        ref={ref}\n        className={cn(\n          \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none\",\n          className\n        )}\n        {...props}\n      >\n        <ChartStyle id={chartId} config={config} />\n        <RechartsPrimitive.ResponsiveContainer>\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  )\n})\nChartContainer.displayName = \"Chart\"\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(\n    ([_, config]) => config.theme || config.color\n  )\n\n  if (!colorConfig.length) {\n    return null\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color =\n      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||\n      itemConfig.color\n    return color ? `  --color-${key}: ${color};` : null\n  })\n  .join(\"\\n\")}\n}\n`\n          )\n          .join(\"\\n\"),\n      }}\n    />\n  )\n}\n\nconst ChartTooltip = RechartsPrimitive.Tooltip\n\nconst ChartTooltipContent = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n    React.ComponentProps<\"div\"> & {\n      hideLabel?: boolean\n      hideIndicator?: boolean\n      indicator?: \"line\" | \"dot\" | \"dashed\"\n      nameKey?: string\n      labelKey?: string\n    }\n>(\n  (\n    {\n      active,\n      payload,\n      className,\n      indicator = \"dot\",\n      hideLabel = false,\n      hideIndicator = false,\n      label,\n      labelFormatter,\n      labelClassName,\n      formatter,\n      color,\n      nameKey,\n      labelKey,\n    },\n    ref\n  ) => {\n    const { config } = useChart()\n\n    const tooltipLabel = React.useMemo(() => {\n      if (hideLabel || !payload?.length) {\n        return null\n      }\n\n      const [item] = payload\n      const key = `${labelKey || item.dataKey || item.name || \"value\"}`\n      const itemConfig = getPayloadConfigFromPayload(config, item, key)\n      const value =\n        !labelKey && typeof label === \"string\"\n          ? config[label as keyof typeof config]?.label || label\n          : itemConfig?.label\n\n      if (labelFormatter) {\n        return (\n          <div className={cn(\"font-medium\", labelClassName)}>\n            {labelFormatter(value, payload)}\n          </div>\n        )\n      }\n\n      if (!value) {\n        return null\n      }\n\n      return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>\n    }, [\n      label,\n      labelFormatter,\n      payload,\n      hideLabel,\n      labelClassName,\n      config,\n      labelKey,\n    ])\n\n    if (!active || !payload?.length) {\n      return null\n    }\n\n    const nestLabel = payload.length === 1 && indicator !== \"dot\"\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          \"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl\",\n          className\n        )}\n      >\n        {!nestLabel ? tooltipLabel : null}\n        <div className=\"grid gap-1.5\">\n          {payload.map((item, index) => {\n            const key = `${nameKey || item.name || item.dataKey || \"value\"}`\n            const itemConfig = getPayloadConfigFromPayload(config, item, key)\n            const indicatorColor = color || item.payload.fill || item.color\n\n            return (\n              <div\n                key={item.dataKey}\n                className={cn(\n                  \"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground\",\n                  indicator === \"dot\" && \"items-center\"\n                )}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\n                            \"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]\",\n                            {\n                              \"h-2.5 w-2.5\": indicator === \"dot\",\n                              \"w-1\": indicator === \"line\",\n                              \"w-0 border-[1.5px] border-dashed bg-transparent\":\n                                indicator === \"dashed\",\n                              \"my-0.5\": nestLabel && indicator === \"dashed\",\n                            }\n                          )}\n                          style={\n                            {\n                              \"--color-bg\": indicatorColor,\n                              \"--color-border\": indicatorColor,\n                            } as React.CSSProperties\n                          }\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        \"flex flex-1 justify-between leading-none\",\n                        nestLabel ? \"items-end\" : \"items-center\"\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label || item.name}\n                        </span>\n                      </div>\n                      {item.value && (\n                        <span className=\"font-mono font-medium tabular-nums text-foreground\">\n                          {item.value.toLocaleString()}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            )\n          })}\n        </div>\n      </div>\n    )\n  }\n)\nChartTooltipContent.displayName = \"ChartTooltip\"\n\nconst ChartLegend = RechartsPrimitive.Legend\n\nconst ChartLegendContent = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<\"div\"> &\n    Pick<RechartsPrimitive.LegendProps, \"payload\" | \"verticalAlign\"> & {\n      hideIcon?: boolean\n      nameKey?: string\n    }\n>(\n  (\n    { className, hideIcon = false, payload, verticalAlign = \"bottom\", nameKey },\n    ref\n  ) => {\n    const { config } = useChart()\n\n    if (!payload?.length) {\n      return null\n    }\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          \"flex items-center justify-center gap-4\",\n          verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\",\n          className\n        )}\n      >\n        {payload.map((item) => {\n          const key = `${nameKey || item.dataKey || \"value\"}`\n          const itemConfig = getPayloadConfigFromPayload(config, item, key)\n\n          return (\n            <div\n              key={item.value}\n              className={cn(\n                \"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground\"\n              )}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <div\n                  className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n                  style={{\n                    backgroundColor: item.color,\n                  }}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          )\n        })}\n      </div>\n    )\n  }\n)\nChartLegendContent.displayName = \"ChartLegend\"\n\n// Helper to extract item config from a payload.\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== \"object\" || payload === null) {\n    return undefined\n  }\n\n  const payloadPayload =\n    \"payload\" in payload &&\n    typeof payload.payload === \"object\" &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined\n\n  let configLabelKey: string = key\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === \"string\"\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string\n  }\n\n  return configLabelKey in config\n    ? config[configLabelKey]\n    : config[key as keyof typeof config]\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n}\n"
  },
  {
    "path": "src/components/ui/fade-text.tsx",
    "content": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport { motion, type Variants } from \"framer-motion\";\n\ntype FadeTextProps = {\n    className?: string;\n    direction?: \"up\" | \"down\" | \"left\" | \"right\";\n    framerProps?: Variants;\n    text: string;\n};\n\nexport function FadeText({\n    direction = \"up\",\n    className,\n    framerProps = {\n        hidden: { opacity: 0 },\n        show: { opacity: 1, transition: { type: \"spring\" } },\n    },\n    text,\n}: FadeTextProps) {\n    const directionOffset = useMemo(() => {\n        const map = { up: 10, down: -10, left: -10, right: 10 };\n        return map[direction];\n    }, [direction]);\n\n    const axis = direction === \"up\" || direction === \"down\" ? \"y\" : \"x\";\n\n    const FADE_ANIMATION_VARIANTS = useMemo(() => {\n        const { hidden, show, ...rest } = framerProps as {\n            [name: string]: { [name: string]: number; opacity: number };\n        };\n\n        return {\n            ...rest,\n            hidden: {\n                ...(hidden ?? {}),\n                opacity: hidden?.opacity ?? 0,\n                [axis]: hidden?.[axis] ?? directionOffset,\n            },\n            show: {\n                ...(show ?? {}),\n                opacity: show?.opacity ?? 1,\n                [axis]: show?.[axis] ?? 0,\n            },\n        };\n    }, [directionOffset, axis, framerProps]);\n\n    return (\n        <motion.div\n            initial=\"hidden\"\n            animate=\"show\"\n            viewport={{ once: true }}\n            variants={FADE_ANIMATION_VARIANTS}\n        >\n            <motion.span className={className}>{text}</motion.span>\n        </motion.div>\n    );\n}\n"
  },
  {
    "path": "src/components/ui/grid-pattern.tsx",
    "content": "import { useId } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface GridPatternProps {\n    width?: any;\n    height?: any;\n    x?: any;\n    y?: any;\n    squares?: Array<[x: number, y: number]>;\n    strokeDasharray?: any;\n    className?: string;\n    [key: string]: any;\n}\n\nexport function GridPattern({\n    width = 80,\n    height = 40,\n    x = -1,\n    y = 0,\n    strokeDasharray = 0,\n    squares,\n    className,\n    ...props\n}: GridPatternProps) {\n    const id = useId();\n\n    return (\n        <svg\n            aria-hidden=\"true\"\n            className={cn(\n                \"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30\",\n                className,\n            )}\n            {...props}\n        >\n            <defs>\n                <pattern\n                    id={id}\n                    width={width}\n                    height={height}\n                    patternUnits=\"userSpaceOnUse\"\n                    x={x}\n                    y={y}\n                >\n                    <path\n                        d={`M.5 ${height}V.5H${width}`}\n                        fill=\"none\"\n                        strokeDasharray={strokeDasharray}\n                    />\n                </pattern>\n            </defs>\n            <rect width=\"100%\" height=\"100%\" strokeWidth={0} fill={`url(#${id})`} />\n            {squares && (\n                <svg x={x} y={y} className=\"overflow-visible\">\n                    {squares.map(([x, y]) => (\n                        <rect\n                            strokeWidth=\"0\"\n                            key={`${x}-${y}`}\n                            width={width - 1}\n                            height={height - 1}\n                            x={x * width + 1}\n                            y={y * height + 1}\n                        />\n                    ))}\n                </svg>\n            )}\n        </svg>\n    );\n}\n\nexport default GridPattern;\n"
  },
  {
    "path": "src/components/ui/grid.tsx",
    "content": "interface GridProps {\n    /**\n     * Color of the grid\n     */\n    color?: string;\n\n    /**\n     * Size of the grid in pixels\n     */\n    size?: number;\n\n    /**\n     * Content of the component\n     */\n    children?: React.ReactNode;\n}\n\nfunction Placeholder({ size = 20 }: Pick<GridProps, \"size\">) {\n    const widthSpread = 20;\n    const heightSpread = 10;\n    return (\n        <div\n            style={{\n                // +1 to account for the border\n                width: `${widthSpread * size + 1}px`,\n                height: `${heightSpread * size + 1}px`,\n            }}\n            className=\"flex max-h-full max-w-full items-center justify-center\"\n        >\n            <div className=\"rounded bg-white px-4 py-2\">This has grid background</div>\n        </div>\n    );\n}\n\nexport default function Grid({ color = \"#cacaca\", size = 20, children }: GridProps) {\n    return (\n        <div\n            className=\"w-full h-full\"\n            style={{\n                backgroundColor: \"white\",\n                backgroundImage: `linear-gradient(${color} 1px, transparent 1px), linear-gradient(to right, ${color} 1px, transparent 1px)`,\n                backgroundSize: `${size}px ${size}px`,\n            }}\n        >\n            {children ?? <Placeholder size={size} />}\n        </div>\n    );\n}\n"
  },
  {
    "path": "src/components/ui/icon.tsx",
    "content": "import { icons } from \"lucide-react\";\n\nexport const Icon = ({\n    name,\n    color,\n    size,\n    className,\n}: {\n    name: keyof typeof icons;\n    color: string;\n    size: number;\n    className?: string;\n}) => {\n    const LucideIcon = icons[name as keyof typeof icons];\n\n    return <LucideIcon color={color} size={size} className={className} />;\n};"
  },
  {
    "path": "src/components/ui/loading-spinner.tsx",
    "content": "import { cn } from \"@lib/utils\"\n\nexport const LoadingSpinner = ({ className, isLoading, text }) => {\n    {\n        return (\n            <>\n                {isLoading && (\n                    <div className={cn(\"fixed top-0 left-0 w-full h-full flex flex-col items-center justify-center bg-black bg-opacity-50 z-50\")}>\n                        <div className={cn(\"animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900 mb-4\")}></div>\n                        <p className={cn(\"text-white\")}>{text}</p>\n                    </div>\n                )}\n            </>\n        )\n    }\n}"
  },
  {
    "path": "src/components/ui/marquee.tsx",
    "content": "import { cn } from \"@/lib/utils\";\n\ninterface MarqueeProps {\n    className?: string;\n    reverse?: boolean;\n    pauseOnHover?: boolean;\n    children?: React.ReactNode;\n    vertical?: boolean;\n    repeat?: number;\n    [key: string]: any;\n}\n\nexport default function Marquee({\n    className,\n    reverse,\n    pauseOnHover = false,\n    children,\n    vertical = false,\n    repeat = 4,\n    ...props\n}: MarqueeProps) {\n    return (\n        <div\n            {...props}\n            className={cn(\n                \"group flex overflow-hidden p-2 [--duration:40s] [--gap:1rem] [gap:var(--gap)]\",\n                {\n                    \"flex-row\": !vertical,\n                    \"flex-col\": vertical,\n                },\n                className,\n            )}\n        >\n            {Array(repeat)\n                .fill(0)\n                .map((_, i) => (\n                    <div\n                        key={i}\n                        className={cn(\"flex shrink-0 justify-around [gap:var(--gap)]\", {\n                            \"animate-marquee flex-row\": !vertical,\n                            \"animate-marquee-vertical flex-col\": vertical,\n                            \"group-hover:[animation-play-state:paused]\": pauseOnHover,\n                            \"[animation-direction:reverse]\": reverse,\n                        })}\n                    >\n                        {children}\n                    </div>\n                ))}\n        </div>\n    );\n}\n"
  },
  {
    "path": "src/components/ui/scroll-based-velocity.tsx",
    "content": "\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport {\n  motion,\n  useAnimationFrame,\n  useMotionValue,\n  useScroll,\n  useSpring,\n  useTransform,\n  useVelocity,\n} from \"framer-motion\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface VelocityScrollProps {\n  text: string;\n  default_velocity?: number;\n  className?: string;\n}\n\ninterface ParallaxProps {\n  children: string;\n  baseVelocity: number;\n  className?: string;\n}\n\nexport const wrap = (min: number, max: number, v: number) => {\n  const rangeSize = max - min;\n  return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;\n};\n\nexport function VelocityScroll({\n  text,\n  default_velocity = 5,\n  className,\n}: VelocityScrollProps) {\n  function ParallaxText({\n    children,\n    baseVelocity = 100,\n    className,\n  }: ParallaxProps) {\n    const baseX = useMotionValue(0);\n    const { scrollY } = useScroll();\n    const scrollVelocity = useVelocity(scrollY);\n    const smoothVelocity = useSpring(scrollVelocity, {\n      damping: 50,\n      stiffness: 400,\n    });\n\n    const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 5], {\n      clamp: false,\n    });\n\n    const [repetitions, setRepetitions] = useState(1);\n    const containerRef = useRef<HTMLDivElement>(null);\n    const textRef = useRef<HTMLSpanElement>(null);\n\n    useEffect(() => {\n      const calculateRepetitions = () => {\n        if (containerRef.current && textRef.current) {\n          const containerWidth = containerRef.current.offsetWidth;\n          const textWidth = textRef.current.offsetWidth;\n          const newRepetitions = Math.ceil(containerWidth / textWidth) + 2;\n          setRepetitions(newRepetitions);\n        }\n      };\n\n      calculateRepetitions();\n\n      window.addEventListener(\"resize\", calculateRepetitions);\n      return () => window.removeEventListener(\"resize\", calculateRepetitions);\n    }, [children]);\n\n    const x = useTransform(baseX, (v) => `${wrap(-100 / repetitions, 0, v)}%`);\n\n    const directionFactor = React.useRef<number>(1);\n    useAnimationFrame((t, delta) => {\n      let moveBy = directionFactor.current * baseVelocity * (delta / 1000);\n\n      if (velocityFactor.get() < 0) {\n        directionFactor.current = -1;\n      } else if (velocityFactor.get() > 0) {\n        directionFactor.current = 1;\n      }\n\n      moveBy += directionFactor.current * moveBy * velocityFactor.get();\n\n      baseX.set(baseX.get() + moveBy);\n    });\n\n    return (\n      <div\n        className=\"w-full overflow-hidden whitespace-nowrap\"\n        ref={containerRef}\n      >\n        <motion.div className={cn(\"inline-block\", className)} style={{ x }}>\n          {Array.from({ length: repetitions }).map((_, i) => (\n            <span key={i} ref={i === 0 ? textRef : null}>\n              {children}{\" \"}\n            </span>\n          ))}\n        </motion.div>\n      </div>\n    );\n  }\n\n  return (\n    <section className=\"relative w-full\">\n      <ParallaxText baseVelocity={default_velocity} className={className}>\n        {text}\n      </ParallaxText>\n      {/* <ParallaxText baseVelocity={-default_velocity} className={className}>\n        {text}\n      </ParallaxText> */}\n    </section>\n  );\n}\n"
  },
  {
    "path": "src/components/ui/shimmer-button.tsx",
    "content": "import React, { type CSSProperties } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface ShimmerButtonProps\n    extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n    shimmerColor?: string;\n    shimmerSize?: string;\n    borderRadius?: string;\n    shimmerDuration?: string;\n    background?: string;\n    className?: string;\n    children?: React.ReactNode;\n}\n\nconst ShimmerButton = React.forwardRef<HTMLButtonElement, ShimmerButtonProps>(\n    (\n        {\n            shimmerColor = \"#ffffff\",\n            shimmerSize = \"0.05em\",\n            shimmerDuration = \"3s\",\n            borderRadius = \"100px\",\n            background = \"rgba(0, 0, 0, 1)\",\n            className,\n            children,\n            ...props\n        },\n        ref,\n    ) => {\n        return (\n            <button\n                style={\n                    {\n                        \"--spread\": \"90deg\",\n                        \"--shimmer-color\": shimmerColor,\n                        \"--radius\": borderRadius,\n                        \"--speed\": shimmerDuration,\n                        \"--cut\": shimmerSize,\n                        \"--bg\": background,\n                    } as CSSProperties\n                }\n                className={cn(\n                    \"group relative z-0 flex cursor-pointer items-center justify-center overflow-hidden whitespace-nowrap border border-white/10 px-2 py-0.5 text-white [background:var(--bg)] [border-radius:var(--radius)] dark:text-black\",\n                    \"transform-gpu transition-transform duration-300 ease-in-out active:translate-y-[1px]\",\n                    className,\n                )}\n                ref={ref}\n                {...props}\n            >\n                {/* spark container */}\n                <div\n                    className={cn(\n                        \"-z-30 blur-[2px]\",\n                        \"absolute inset-0 overflow-visible [container-type:size]\",\n                    )}\n                >\n                    {/* spark */}\n                    <div className=\"absolute inset-0 h-[100cqh] animate-slide [aspect-ratio:1] [border-radius:0] [mask:none]\">\n                        {/* spark before */}\n                        <div className=\"animate-spin-around absolute inset-[-100%] w-auto rotate-0 [background:conic-gradient(from_calc(270deg-(var(--spread)*0.5)),transparent_0,var(--shimmer-color)_var(--spread),transparent_var(--spread))] [translate:0_0]\" />\n                    </div>\n                </div>\n                {children}\n\n                {/* Highlight */}\n                <div\n                    className={cn(\n                        \"insert-0 absolute h-full w-full\",\n\n                        \"rounded-2xl px-2 py-0.5 text-sm font-medium shadow-[inset_0_-8px_10px_#ffffff1f]\",\n\n                        // transition\n                        \"transform-gpu transition-all duration-300 ease-in-out\",\n\n                        // on hover\n                        \"group-hover:shadow-[inset_0_-6px_10px_#ffffff3f]\",\n\n                        // on click\n                        \"group-active:shadow-[inset_0_-10px_10px_#ffffff3f]\",\n                    )}\n                />\n\n                {/* backdrop */}\n                <div\n                    className={cn(\n                        \"absolute -z-20 [background:var(--bg)] [border-radius:var(--radius)] [inset:var(--cut)]\",\n                    )}\n                />\n            </button>\n        );\n    },\n);\n\nShimmerButton.displayName = \"ShimmerButton\";\n\nexport default ShimmerButton;\n"
  },
  {
    "path": "src/components/ui/text-generate-effect.tsx",
    "content": "\"use client\";\nimport { useEffect } from \"react\";\nimport { motion, stagger, useAnimate } from \"framer-motion\";\nimport { cn } from \"@/lib/utils\";\n\nexport const TextGenerateEffect = ({\n    words,\n    className,\n}: {\n    words: string;\n    className?: string;\n}) => {\n    const [scope, animate] = useAnimate();\n    let wordsArray = words.split(\" \");\n    useEffect(() => {\n        animate(\n            \"span\",\n            {\n                opacity: 1,\n            },\n            {\n                duration: 2,\n                delay: stagger(0.2),\n            }\n        );\n    }, [scope.current]);\n\n    const renderWords = () => {\n        return (\n            <motion.div ref={scope}>\n                {wordsArray.map((word, idx) => {\n                    return (\n                        <motion.span\n                            key={word + idx}\n                            className=\"dark:text-white text-black opacity-0\"\n                        >\n                            {word}{\" \"}\n                        </motion.span>\n                    );\n                })}\n            </motion.div>\n        );\n    };\n\n    return (\n        <div className={cn(\"font-bold\", className)}>\n            <div className=\"mt-4\">\n                <div className=\" dark:text-white text-black text-2xl leading-snug tracking-wide\">\n                    {renderWords()}\n                </div>\n            </div>\n        </div>\n    );\n};\n"
  },
  {
    "path": "src/components/ui/underline-hover-text.tsx",
    "content": "\"use client\";\nimport React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface UnderlineHoverTextProps {\n    text: string;\n    textColor?: string;\n    hoverTextColor?: string;\n    hoverColor?: string;\n    fontSize?: string;\n    fontWeight?: string;\n    className?: string;\n}\n\nconst UnderlineHoverText: React.FC<UnderlineHoverTextProps> = ({\n    text,\n    textColor = \"text-yellow-600\",\n    hoverTextColor = \"hover:text-white\",\n    hoverColor = \"hover:after:bg-indigo-500\",\n    fontSize = \"text-2xl\",\n    fontWeight = \"font-medium\",\n    className,\n}) => {\n    return (\n        <span\n            className={cn(\n                \"relative inline-block cursor-pointer p-2\",\n                fontSize,\n                textColor,\n                fontWeight,\n                \"after:absolute after:bottom-0 after:left-0 after:h-1 after:w-full after:bg-gray-400\",\n                \"after:transition-all after:duration-150\",\n                hoverTextColor,\n                \"hover:after:h-full\",\n                hoverColor,\n                className,\n            )}\n        >\n            <span className=\"relative z-10\">{text}</span>\n        </span>\n    );\n};\n\nexport default UnderlineHoverText;\n"
  },
  {
    "path": "src/config/site.ts",
    "content": "const baseSiteConfig = {\n    name: \"X Cards\",\n    description:\n        \"Share X anywhere, any format. A Chrome extension for easy access to X posts in multiple formats.\",\n    url: \"https://x-cards.net\",\n    keywords: [\n        \"X.com\",\n        \"Twitter\",\n        \"Chrome Extension\",\n        \"Social Media\",\n        \"Content Sharing\",\n        \"Card Generator\",\n        \"Export Tools\",\n        \"JSON\",\n        \"Markdown\",\n        \"PNG\",\n        \"JPEG\",\n        \"SVG\",\n    ],\n    authors: [\n        {\n            name: \"hzeyuan\",\n            url: \"https://github.com/hzeyuan\",\n        }\n    ],\n    creator: '@IndieDevr',\n    themeColor: '#fff',\n    icons: {\n        icon: \"https://static.usesless.com/x-cards/favicon/favicon.ico\",\n        android: \"https://static.usesless.com/x-cards/favicon/android-chrome-192x192.png\",\n        shortcut: \"https://static.usesless.com/x-cards/favicon/favicon.ico\",\n        apple: \"https://static.usesless.com/x-cards/favicon/apple-touch-icon.png\",\n    },\n    ogImage: \"https://static.usesless.com/x-cards/favicon/x-cards-og.png\",\n    links: {\n        github: \"https://github.com/hzeyuan/x-cards\",\n    },\n}\n\nexport const siteConfig = {\n    ...baseSiteConfig,\n    openGraph: {\n        type: \"website\",\n        locale: \"en_US\",\n        url: baseSiteConfig.url,\n        image: baseSiteConfig.ogImage,\n        title: baseSiteConfig.name,\n        description: baseSiteConfig.description,\n        siteName: baseSiteConfig.name,\n    },\n    twitter: {\n        card: \"summary_large_image\",\n        title: baseSiteConfig.name,\n        image: baseSiteConfig.ogImage,\n        description: baseSiteConfig.description,\n        images: [`${baseSiteConfig.url}/xcards/favicon/x-cards-og.png`],\n        creator: baseSiteConfig.creator,\n    },\n}"
  },
  {
    "path": "src/contents/plasmo-overlay.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n"
  },
  {
    "path": "src/contents/plasmo-overlay.tsx",
    "content": "import React, { useEffect, useRef } from 'react'\nimport type { PlasmoCSConfig, PlasmoCSUIProps, PlasmoRender } from \"plasmo\"\n// import 'tippy.js/dist/tippy.css';\nimport cssText from \"data-text:@src/contents/plasmo-overlay.css\"\nimport { Toaster } from 'react-hot-toast';\nimport { iframeMessageSystem } from '@src/app/utils/IFrameMessageSystem';\nimport { sendToBackground } from '@plasmohq/messaging';\nimport { useTweetsStore } from '@src/components/extension/use-tweet-collection';\nimport { createRoot } from 'react-dom/client';\nexport const config: PlasmoCSConfig = {\n    matches: [\"https://x.com/*\"],\n}\n\n\nexport const getStyle = () => {\n    const style = document.createElement(\"style\")\n    style.textContent = cssText\n    return style\n}\n\nexport const getShadowHostId = () => \"x-cards-overlay\"\n\n\n\nconst AnchorOverlay: React.FC<PlasmoCSUIProps> = ({ anchor }) => {\n    const iframeRef = useRef<HTMLIFrameElement>(null)\n    const setIsActivated = useTweetsStore((state) => state.setIsActivated);\n\n    const websiteURL = \"https://x-cards.net/independent\";\n    // const websiteURL = \"http://127.0.0.1:1947/independent\";\n\n\n    useEffect(() => {\n        const unsubscribe = iframeMessageSystem.subscribe('generate-card-local', (value: any) => {\n        });\n\n        return () => unsubscribe();\n    }, []);\n\n\n    useEffect(() => {\n        sendToBackground({\n            name: 'code',\n            body: {\n                action: 'check',\n            }\n        }).then((data) => {\n            if (!data) {\n                setIsActivated(false);\n                return;\n            }\n            const isNotActivated = data?.subscription_ended_at || data?.subscription_cancelled_at || data?.subscription_failed_at\n            setIsActivated(!isNotActivated);\n        }\n        );\n    }, [])\n\n\n    return (\n        <div>\n            {/* <PreviewToast tweetInfo={{}} tweetInfos={[]} /> */}\n            <iframe\n                ref={iframeRef}\n                id=\"x-card-ai\"\n                src={websiteURL}\n                style={{\n                    width: '100vw',\n                    height: '0px',\n                    border: 'none',\n                    opacity: 0,\n                }}\n            />\n        </div >\n    )\n}\n\n\nexport const render: PlasmoRender<Element> = async ({\n    anchor,\n    createRootContainer\n}) => {\n    if (!anchor || !createRootContainer) return\n\n    const rootContainer = await createRootContainer(anchor)\n\n    const variantStyle = document.createElement(\"style\")\n    variantStyle.textContent = `\n    :host {\n    --background: 0 0% 100%;\n    --foreground: 222.2 84% 4.9%;\n    --card: 0 0% 100%;\n    --card-foreground: 222.2 84% 4.9%;\n    --popover: 0 0% 100%;\n    --popover-foreground: 222.2 84% 4.9%;\n    --primary: 221.2 83.2% 53.3%;\n    --primary-foreground: 210 40% 98%;\n    --secondary: 210 40% 96.1%;\n    --secondary-foreground: 222.2 47.4% 11.2%;\n    --muted: 210 40% 96.1%;\n    --muted-foreground: 215.4 16.3% 46.9%;\n    --accent: 210 40% 96.1%;\n    --accent-foreground: 222.2 47.4% 11.2%;\n    --destructive: 0 84.2% 60.2%;\n    --destructive-foreground: 210 40% 98%;\n    --border: 214.3 31.8% 91.4%;\n    --input: 214.3 31.8% 91.4%;\n    --ring: 221.2 83.2% 53.3%;\n    --radius: 0.5rem;\n    --chart-1: 12 76% 61%;\n    --chart-2: 173 58% 39%;\n    --chart-3: 197 37% 24%;\n    --chart-4: 43 74% 66%;\n    --chart-5: 27 87% 67%;\n  }\n \n  .dark {\n    --background: 222.2 84% 4.9%;\n    --foreground: 210 40% 98%;\n    --card: 222.2 84% 4.9%;\n    --card-foreground: 210 40% 98%;\n    --popover: 222.2 84% 4.9%;\n    --popover-foreground: 210 40% 98%;\n    --primary: 217.2 91.2% 59.8%;\n    --primary-foreground: 222.2 47.4% 11.2%;\n    --secondary: 217.2 32.6% 17.5%;\n    --secondary-foreground: 210 40% 98%;\n    --muted: 217.2 32.6% 17.5%;\n    --muted-foreground: 215 20.2% 65.1%;\n    --accent: 217.2 32.6% 17.5%;\n    --accent-foreground: 210 40% 98%;\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 210 40% 98%;\n    --border: 217.2 32.6% 17.5%;\n    --input: 217.2 32.6% 17.5%;\n    --ring: 224.3 76.3% 48%;\n    --chart-1: 220 70% 50%;\n    --chart-2: 160 60% 45%;\n    --chart-3: 30 80% 55%;\n    --chart-4: 280 65% 60%;\n    --chart-5: 340 75% 55%;\n}\n    `\n\n    const style = document.createElement(\"style\")\n\n    const wrapper = document.createElement('xcards-ui')\n\n\n    // Attach the wrapper to the document body\n    document.body.appendChild(wrapper)\n\n    // Create a shadow root\n    const shadowRoot = wrapper.attachShadow({ mode: 'open' })\n\n    style.textContent = cssText;\n    shadowRoot.appendChild(style)\n    shadowRoot.appendChild(variantStyle)\n\n    // Append the rootContainer to the shadow root\n    shadowRoot.appendChild(rootContainer)\n\n    const root = createRoot(rootContainer)\n    root.render(\n        <div>\n            <Toaster\n                toastOptions={{\n                    className: '',\n                    style: {\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        background: \"#333\",\n                        color: \"#fff\",\n                        lineHeight: 1.3,\n                        willChange: \"transform\",\n                        boxShadow: \"0 3px 10px rgba(0, 0, 0, 0.1), 0 3px 3px rgba(0, 0, 0, 0.05)\",\n                        maxWidth: \"350px\",\n                        pointerEvents: \"auto\",\n                        padding: \"8px 10px\",\n                        borderRadius: \"10px\"\n                    },\n                }}\n            />\n            <AnchorOverlay></AnchorOverlay>\n        </div>\n    )\n}"
  },
  {
    "path": "src/contents/x-home.tsx",
    "content": "import cssText from \"data-text:@src/contents/x.css\"\n\nimport type { PlasmoCSConfig, PlasmoCSUIProps, PlasmoGetInlineAnchorList } from \"plasmo\"\n\nimport { CardButton } from \"../components/extension/card-button\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nexport const config: PlasmoCSConfig = {\n    matches: [\"https://x.com/*\"],\n    run_at: 'document_end'\n}\n\n\nexport const getStyle = () => {\n    const style = document.createElement(\"style\")\n    style.textContent = cssText\n    return style\n}\n\nexport const getInlineAnchorList: PlasmoGetInlineAnchorList = async () => { // document.querySelector(`h1`)\n    const targetSVGList = document.querySelectorAll('path[d^=\"M12 2.59l5.7 5.7-1.41 1\"]');\n    // console.log('getInlineAnchorList', targetSVGList)\n    const l = [];\n    targetSVGList.forEach((svg) => {\n        const buttonElement = svg.closest('button')\n        const li = buttonElement?.parentNode?.parentNode\n        l.push({\n            element: li,\n            position: 'afterend',\n        })\n    });\n    return l;\n}\n\n\nconst PlasmoInline: React.FC<PlasmoCSUIProps> = ({ anchor, }) => {\n    const [isVisible, setIsVisible] = useState(false);\n    const cardButtonRef = useRef(null);\n    useEffect(() => {\n        const observer = new IntersectionObserver(\n            ([entry]) => {\n                if (entry.isIntersecting) {\n                    setIsVisible(true);\n                    observer.disconnect(); // 一旦可见就停止观察\n                }\n            },\n            { threshold: 0.1 } // 当10%的元素可见时触发\n        );\n\n        if (cardButtonRef.current) {\n            observer.observe(cardButtonRef.current);\n        }\n\n        return () => observer.disconnect();\n    }, []);\n\n\n    // useEffect(() => {\n    //     if (isVisible && cardButtonRef.current) {\n    //         const root = createRoot(cardButtonRef.current);\n    //         root.render(<CardButton anchor={anchor} />);\n    //     }\n    // }, [isVisible]);\n\n    // // return (\n    // //     <div className=\"x-cards-button-group\">\n    // //         <CardButton anchor={anchor} />\n    // //     </ div>\n    // // )\n    // return <div ref={cardButtonRef} style={{ minHeight: '20px' }} />\n    return <CardButton anchor={anchor} />\n}\n\nexport default PlasmoInline\n\n\n\n"
  },
  {
    "path": "src/contents/x.css",
    "content": "#plasmo-shadow-container {\n  z-index: 10 !important;\n  /* position: fixed !important; */\n  height: 100%;\n}\n\n#plasmo-inline {\n  height: 100%;\n  display: flex;\n  align-items: center;\n}\n\n.x {\n  align-items: stretch;\n  background-color: rgba(0, 0, 0, 0);\n  border: 0 solid black;\n  box-sizing: border-box;\n  display: flex;\n  flex-basis: auto;\n  flex-direction: column;\n  flex-shrink: 0;\n  list-style: none;\n  margin: 0px;\n  min-height: 0px;\n  min-width: 0px;\n  padding: 0px;\n  position: relative;\n  text-decoration: none;\n  z-index: 0;\n}\n\n.card-button {\n  cursor: pointer;\n}\n\n#plasmo-inline .card-button:hover path {\n  fill: rgb(29, 155, 240);\n}\n\n@keyframes spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n.animate-spin {\n  animation: spin 1s linear infinite;\n}\n\n.card-copy-button {\n  cursor: pointer;\n}\n\n.x-cards-button-group {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 0 4px;\n}\n\n#plasmo-inline .card-copy-button svg {\n  width: 18px;\n  height: 18px;\n  color: #768294;\n  vertical-align: bottom;\n}\n#plasmo-inline .card-copy-button:hover path {\n  fill: rgb(29, 155, 240);\n}\n\n.preset-selector {\n  display: flex;\n}\n.preset-label {\n  position: relative;\n  overflow: hidden;\n  width: 20px;\n  height: 20px;\n  flex-shrink: 0;\n  border-radius: 5px;\n  margin-right: 5px;\n}\n.preset-label.selected {\n  ring: 2px solid #3b82f6;\n  ring-offset: 2px;\n}\n\n.preset-input {\n  position: absolute;\n  width: 100%;\n  height: 100%;\n  appearance: none;\n  opacity: 0;\n  inset: 0;\n  cursor: pointer;\n}\n@media (prefers-color-scheme: dark) {\n  .preset-label.selected {\n    ring: 2px solid #3b82f6;\n    ring-offset: 2px;\n    ring-offset-color: #374151;\n  }\n}\n.preset-label:active {\n  transform: scale(0.95);\n}\n.preset-label {\n  transition: transform 0.1s ease;\n}\n\n"
  },
  {
    "path": "src/hooks/useCardStore.tsx",
    "content": "import type { TweetControlState } from '@src/components/extension/use-tweet-collection';\nimport { create } from 'zustand'\n\n\ntype Frame = 'none' | 'macos' | 'windows'\n\ntype SocialPlatform = 'instagram' | 'facebook' | 'linkedin' | 'whatsapp' | 'youtube' | 'common'\n\ntype PostType = 'landscape' | 'square' | 'portrait' | 'post' | 'story' | 'thumbnail';\n\ntype AspectRatio = '16:9' | '3:2' | '4:3' | '5:4' | '1:1' | '4:5' | '3:4' | '2:3' | '9:16' | '3:1'\n\n\ninterface ImageDimensions {\n    width: number;\n    height: number;\n\n}\n\n// 各大社交平台的卡片比例\ntype ImageLayoutProps = {\n    social: SocialPlatform;\n    layoutOptions: LayoutOption[];\n\n}\n\n\nexport interface LayoutOption {\n    ratio: AspectRatio;\n    dimensions: ImageDimensions;\n    name: string;\n    scale?: number;\n}\n\n\nconst instagramLayouts: ImageLayoutProps = {\n    social: 'instagram',\n    layoutOptions: [\n        { ratio: '1:1', name: '', dimensions: { width: 1080, height: 1080 } },\n        { ratio: '4:5', name: '', dimensions: { width: 1080, height: 1350 } },\n    ]\n};\n\nconst twitterLayouts: ImageLayoutProps = {\n    social: 'facebook',\n    layoutOptions: [\n        { ratio: '16:9', name: '', dimensions: { width: 1080, height: 1920 } },\n        { ratio: '3:1', name: '', dimensions: { width: 1500, height: 500 } },\n    ]\n};\n\nconst YoutubeLayouts: ImageLayoutProps = {\n    social: 'youtube',\n    layoutOptions: [\n        { ratio: '16:9', name: 'Banner', dimensions: { width: 2560, height: 1440 } },\n        { ratio: '16:9', name: 'Thumbnail', dimensions: { width: 1280, height: 720 } },\n        { ratio: '16:9', name: 'video', dimensions: { width: 1280, height: 720 } },\n    ]\n};\n\nexport const CommonLayouts: ImageLayoutProps = {\n    social: 'common',\n    layoutOptions: [\n        { ratio: '16:9', name: '', dimensions: { width: 1920, height: 1080, } },\n        { ratio: '3:2', name: '', dimensions: { width: 1920, height: 1280 } },\n        { ratio: '4:3', name: '', dimensions: { width: 1920, height: 1440 } },\n        { ratio: '5:4', name: '', dimensions: { width: 1920, height: 1536 } },\n        { ratio: '1:1', name: '', dimensions: { width: 1920, height: 1920 } },\n        { ratio: '4:5', name: '', dimensions: { width: 1080, height: 1350 } },\n        { ratio: '3:4', name: '', dimensions: { width: 1080, height: 1440 } },\n        { ratio: '2:3', name: '', dimensions: { width: 1080, height: 1620 } },\n        { ratio: '9:16', name: '', dimensions: { width: 1080, height: 1920 } },\n    ]\n};\n\n\nexport const CardWidths = {\n    sm: 440,\n    md: 582.547,\n    lg: 768,\n    xl: 1024\n};\n\nexport interface XConfig {\n    //发帖账号URL\n    authorUrl: string,\n    url: string;\n    avatar: string,\n    username: string,\n    text: string,\n    links?: {\n        href: string,\n        text: string,\n        src?: string,\n    }[],\n    video?: {\n        src: string,\n        poster: string,\n    },\n    tags?: string[],\n    images: string[],\n    replies: number,\n    likes: number,\n    shares: number,\n    time: number,\n}\n\nexport interface CardStore {\n    loadedImages: {\n        [key: string]: 'success' | 'error'\n    },\n    setLoadedImages: (loadedImages: CardStore['loadedImages']) => void;\n    addLoadedImage: (src: string, status: 'success' | 'error') => void;\n    xConfig: XConfig[],\n    setXConfig: (config: XConfig[]) => void;\n    colorIndex: number;\n    setColorIndex: (colorIndex: number) => void;\n    frame: Frame;\n    setFrame: (frame: Frame) => void;\n    noise: number;\n    setNoise: (noise: number) => void;\n    backgroundStyles: {\n        borderRadius: number,\n        padding: number,\n        backgroundWidth: number,\n        backgroundImage?: string,\n        backgroundOpacity?: number,\n        backgroundBlur?: number,\n        backgroundColor?: string,\n        backgroundRepeat?: string,\n        backgroundGradientAngle?: number,\n        backgroundStartColor?: string,\n        backgroundEndColor?: string,\n        useGradient?: boolean,\n\n        backgroundFilter: {\n            brightness: number,\n            contrast: number,\n            saturate: number,\n            hueRotate: number,\n            invert: number,\n        }\n    },\n    cardStyles: {\n        style: 'posts' | 'article'\n        width: number,\n        height: number,\n        aspectRatio?: AspectRatio,\n        // imageLayout?: ImageLayoutProps['layout'],\n        scale: number,\n        fontSize: number,\n        borderRadius: number,\n        hasNoiseTexture: boolean,\n        noiseTextureOpacity: number,\n        texturePosition: string,\n        texture: string,\n        borderWidth: number,\n        controls: TweetControlState\n        fontFamily: string,\n\n    },\n    tabConfig: {\n        openCustomColor: boolean,\n    }\n    setTabConfig: (tab: {\n        openCustomColor?: boolean,\n    }) => void;\n    updateCardStyles: (cardStyles: Partial<CardStore['cardStyles']>) => void;\n    updateBackgroundStyles: (backgroundStyles: any) => void;\n    resetAll: () => void;\n\n}\n\nexport const useCardStore = create<CardStore>(\n    (set, get) => ({\n        loadedImages: {},\n        setLoadedImages: (loadedImages) => set({ loadedImages }),\n        addLoadedImage: (src, status) => {\n            set({\n                loadedImages: {\n                    ...get().loadedImages,\n                    [src]: status\n                }\n            })\n        },\n        xConfig: [{\n            authorUrl:'',\n            username: '@FeigelC35583',\n            images: [],\n            // images: ['https://pbs.twimg.com/media/GTzQPUhbQAA0mwk?format=jpg&name=large', 'https://pbs.twimg.com/media/GUa0UwYaoAAYPCY?format=jpg'],\n            // images: ['https://pbs.twimg.com/media/GUa0UwYaoAAYPCY?format=jpg'],\n            url: '',\n            avatar: 'https://pbs.twimg.com/media/GUa0UwYaoAAYPCY?format=jpg',\n            text: `hello`,\n            replies: 6,\n            likes: 6,\n            shares: 6,\n            time: 1722090490\n        },\n        ],\n        setXConfig: (config) => {\n            set({\n                xConfig: [...config]\n            })\n\n\n        },\n        colorIndex: 0,\n        setColorIndex: (colorIndex) => set({ colorIndex }),\n        tabConfig: {\n            openCustomColor: false\n        },\n        setTabConfig: (tab) => {\n            set({\n                tabConfig: {\n                    ...get().tabConfig,\n                    ...tab\n                }\n            })\n        },\n        frame: 'none',\n        setFrame: (frame) => set({ frame }),\n        noise: 0,\n        setNoise: (noise) => set({ noise }),\n        backgroundStyles: {\n            padding: 16,\n            backgroundColor: '',\n            backgroundWidth: 100,\n            backgroundImage: '',\n            // 透明度和模糊\n            backgroundOpacity: 1,\n            backgroundBlur: 0,\n            // 混合模式\n            backgroundBlendMode: 'normal', // 'multiply', 'screen', 'overlay'等\n            // 渐变\n            backgroundGradient: '',\n            backgroundGradientAngle: 0,\n            backgroundStartColor: '#fff',\n            backgroundEndColor: '#fff',\n            backgroundRepeat: 'no-repeat',\n\n            // 滤镜\n            backgroundFilter: {\n                brightness: 100,\n                contrast: 100,\n                saturate: 100,\n                hueRotate: 0,\n                invert: 0,\n            },\n        },\n        cardStyles: {\n            style: 'posts',\n            // backgroundColor: 'rgba(255, 255, 255, 1)',\n            width: 582.547,\n            // width: 1920,\n            height: 1080,\n            borderRadius: 20,\n            fontSize: 16,\n            scale: 100,\n            texture: '',\n            fontFamily: 'sans-serif',\n            hasNoiseTexture: false,\n            noiseTextureOpacity: 0.05,\n            texturePosition: 'center',\n            borderWidth: 0,\n            controls: {\n                showUser: true,\n                showActions: true,\n                showTime: true,\n                showFooter: true,\n                showLogo: true,\n            }\n\n        },\n        updateCardStyles: (partCardStyles) => {\n            set({\n                cardStyles: {\n                    ...get().cardStyles,\n                    ...partCardStyles\n                }\n            })\n        },\n        updateBackgroundStyles: (partBackgroundStyles) => {\n            set({\n                backgroundStyles: {\n                    ...get().backgroundStyles,\n                    ...partBackgroundStyles\n                }\n            })\n        },\n        resetAll: () => set({\n            colorIndex: 0,\n\n            fontStyles: {\n                fontFamily: 'sans-serif',\n            },\n\n            tabConfig: {\n                openCustomColor: false\n            },\n            backgroundStyles: {\n                backgroundColor: '',\n                backgroundWidth: 100,\n                backgroundImage: '',\n                // 透明度和模糊\n                backgroundOpacity: 1,\n                backgroundBlur: 0,\n                backgroundGradientAngle: 0,\n                backgroundStartColor: '#fff',\n                backgroundEndColor: '#fff',\n                backgroundRepeat: 'no-repeat',\n\n                // 滤镜\n                backgroundFilter: {\n                    brightness: 100,\n                    contrast: 100,\n                    saturate: 100,\n                    hueRotate: 0,\n                    invert: 0,\n                },\n            },\n        })\n    })\n)"
  },
  {
    "path": "src/hooks/useTemplatesStore.tsx",
    "content": "import { create } from 'zustand'\nimport { persist, createJSONStorage, type StateStorage } from 'zustand/middleware'\nimport { get, set, del } from 'idb-keyval' // can use anything: IndexedDB, Ionic Storage, etc.\nimport { type CardStore } from './useCardStore'\n\n\n\nexport type Template = Pick<CardStore, 'colorIndex' | 'backgroundStyles' | 'cardStyles' | 'tabConfig'> & {\n    name: string;\n}\n\n\nconst storage: StateStorage = {\n    getItem: async (name: string): Promise<string | null> => {\n        return (await get(name)) || null\n    },\n    setItem: async (name: string, value: string): Promise<void> => {\n        await set(name, value)\n    },\n    removeItem: async (name: string): Promise<void> => {\n        await del(name)\n    },\n}\n\ninterface TemplatesStore {\n    templates: Template[];\n    setTemplates?: (templates: Template[]) => void;\n    addTemplate?: (template: Template) => void;\n    delTemplate?: (index: number) => void;\n}\n\nexport const useTemplatesStore = create(\n    persist<TemplatesStore>((set, get) => ({\n        templates: [],\n        setTemplates: (templates) => {\n            set({\n                templates\n            })\n        },\n        addTemplate: (template) => {\n            set({\n                templates: [...get().templates, template]\n            })\n        },\n        delTemplate: (index) => {\n            set({\n                templates: get().templates.filter((_, i) => i !== index)\n            })\n        }\n    }), {\n        name: 'templates',\n        storage: createJSONStorage(() => storage),\n        partialize: (state) => ({\n            templates: state.templates\n        })\n    })\n)"
  },
  {
    "path": "src/lib/BlurGradientBg.module.js",
    "content": "function t(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,r=Array(e);i<e;i++)r[i]=t[i];return r}function e(t,e,i){return e=a(e),u(t,o()?Reflect.construct(e,i||[],a(t).constructor):e.apply(t,i))}function i(t,e){if(!(t instanceof e))throw new TypeError(\"Cannot call a class as a function\")}function r(t,e){for(var i=0;i<e.length;i++){var r=e[i];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(t,f(r.key),r)}}function n(t,e,i){return e&&r(t.prototype,e),i&&r(t,i),Object.defineProperty(t,\"prototype\",{writable:!1}),t}function s(){return s=\"undefined\"!=typeof Reflect&&Reflect.get?Reflect.get.bind():function(t,e,i){var r=function(t,e){for(;!{}.hasOwnProperty.call(t,e)&&null!==(t=a(t)););return t}(t,e);if(r){var n=Object.getOwnPropertyDescriptor(r,e);return n.get?n.get.call(arguments.length<3?t:i):n.value}},s.apply(null,arguments)}function a(t){return a=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},a(t)}function h(t,e){if(\"function\"!=typeof e&&null!==e)throw new TypeError(\"Super expression must either be null or a function\");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,\"prototype\",{writable:!1}),e&&l(t,e)}function o(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(t){}return(o=function(){return!!t})()}function u(t,e){if(e&&(\"object\"==typeof e||\"function\"==typeof e))return e;if(void 0!==e)throw new TypeError(\"Derived constructors may only return object or undefined\");return function(t){if(void 0===t)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return t}(t)}function l(t,e){return l=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},l(t,e)}function c(e){return function(e){if(Array.isArray(e))return t(e)}(e)||function(t){if(\"undefined\"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t[\"@@iterator\"])return Array.from(t)}(e)||g(e)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}()}function f(t){var e=function(t,e){if(\"object\"!=typeof t||!t)return t;var i=t[Symbol.toPrimitive];if(void 0!==i){var r=i.call(t,e);if(\"object\"!=typeof r)return r;throw new TypeError(\"@@toPrimitive must return a primitive value.\")}return String(t)}(t,\"string\");return\"symbol\"==typeof e?e:e+\"\"}function d(t){return d=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&\"function\"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?\"symbol\":typeof t},d(t)}function g(e,i){if(e){if(\"string\"==typeof e)return t(e,i);var r={}.toString.call(e).slice(8,-1);return\"Object\"===r&&e.constructor&&(r=e.constructor.name),\"Map\"===r||\"Set\"===r?Array.from(e):\"Arguments\"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?t(e,i):void 0}}function v(t){var e=\"function\"==typeof Map?new Map:void 0;return v=function(t){if(null===t||!function(t){try{return-1!==Function.toString.call(t).indexOf(\"[native code]\")}catch(e){return\"function\"==typeof t}}(t))return t;if(\"function\"!=typeof t)throw new TypeError(\"Super expression must either be null or a function\");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,i)}function i(){return function(t,e,i){if(o())return Reflect.construct.apply(null,arguments);var r=[null];r.push.apply(r,e);var n=new(t.bind.apply(t,r));return i&&l(n,i.prototype),n}(t,arguments,a(this).constructor)}return i.prototype=Object.create(t.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),l(i,t)},v(t)}function p(t){var e=t[0],i=t[1],r=t[2];return Math.sqrt(e*e+i*i+r*r)}function m(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t}function y(t,e,i){return t[0]=e[0]+i[0],t[1]=e[1]+i[1],t[2]=e[2]+i[2],t}function _(t,e,i){return t[0]=e[0]-i[0],t[1]=e[1]-i[1],t[2]=e[2]-i[2],t}function b(t,e,i){return t[0]=e[0]*i,t[1]=e[1]*i,t[2]=e[2]*i,t}function x(t){var e=t[0],i=t[1],r=t[2];return e*e+i*i+r*r}function E(t,e){var i=e[0],r=e[1],n=e[2],s=i*i+r*r+n*n;return s>0&&(s=1/Math.sqrt(s)),t[0]=e[0]*s,t[1]=e[1]*s,t[2]=e[2]*s,t}function w(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function M(t,e,i){var r=e[0],n=e[1],s=e[2],a=i[0],h=i[1],o=i[2];return t[0]=n*o-s*h,t[1]=s*a-r*o,t[2]=r*h-n*a,t}var k,A,T=(k=[0,0,0],A=[0,0,0],function(t,e){m(k,t),m(A,e),E(k,k),E(A,A);var i=w(k,A);return i>1?0:i<-1?Math.PI:Math.acos(i)});var R=function(t){function r(){var t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:n,a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:n;return i(this,r),u(t=e(this,r,[n,s,a]),t)}return h(r,v(Array)),n(r,[{key:\"x\",get:function(){return this[0]},set:function(t){this[0]=t}},{key:\"y\",get:function(){return this[1]},set:function(t){this[1]=t}},{key:\"z\",get:function(){return this[2]},set:function(t){this[2]=t}},{key:\"set\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:t,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t;return t.length?this.copy(t):(function(t,e,i,r){t[0]=e,t[1]=i,t[2]=r}(this,t,e,i),this)}},{key:\"copy\",value:function(t){return m(this,t),this}},{key:\"add\",value:function(t,e){return e?y(this,t,e):y(this,this,t),this}},{key:\"sub\",value:function(t,e){return e?_(this,t,e):_(this,this,t),this}},{key:\"multiply\",value:function(t){var e,i,r;return t.length?(i=this,r=t,(e=this)[0]=i[0]*r[0],e[1]=i[1]*r[1],e[2]=i[2]*r[2]):b(this,this,t),this}},{key:\"divide\",value:function(t){var e,i,r;return t.length?(i=this,r=t,(e=this)[0]=i[0]/r[0],e[1]=i[1]/r[1],e[2]=i[2]/r[2]):b(this,this,1/t),this}},{key:\"inverse\",value:function(){var t,e;return e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this,(t=this)[0]=1/e[0],t[1]=1/e[1],t[2]=1/e[2],this}},{key:\"len\",value:function(){return p(this)}},{key:\"distance\",value:function(t){return t?(e=this,r=(i=t)[0]-e[0],n=i[1]-e[1],s=i[2]-e[2],Math.sqrt(r*r+n*n+s*s)):p(this);var e,i,r,n,s}},{key:\"squaredLen\",value:function(){return x(this)}},{key:\"squaredDistance\",value:function(t){return t?(e=this,r=(i=t)[0]-e[0],n=i[1]-e[1],s=i[2]-e[2],r*r+n*n+s*s):x(this);var e,i,r,n,s}},{key:\"negate\",value:function(){var t,e;return e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this,(t=this)[0]=-e[0],t[1]=-e[1],t[2]=-e[2],this}},{key:\"cross\",value:function(t,e){return e?M(this,t,e):M(this,this,t),this}},{key:\"scale\",value:function(t){return b(this,this,t),this}},{key:\"normalize\",value:function(){return E(this,this),this}},{key:\"dot\",value:function(t){return w(this,t)}},{key:\"equals\",value:function(t){return i=t,(e=this)[0]===i[0]&&e[1]===i[1]&&e[2]===i[2];var e,i}},{key:\"applyMatrix3\",value:function(t){var e,i,r,n,s,a;return e=this,r=t,n=(i=this)[0],s=i[1],a=i[2],e[0]=n*r[0]+s*r[3]+a*r[6],e[1]=n*r[1]+s*r[4]+a*r[7],e[2]=n*r[2]+s*r[5]+a*r[8],this}},{key:\"applyMatrix4\",value:function(t){var e,i,r,n,s,a,h;return e=this,r=t,n=(i=this)[0],s=i[1],a=i[2],h=(h=r[3]*n+r[7]*s+r[11]*a+r[15])||1,e[0]=(r[0]*n+r[4]*s+r[8]*a+r[12])/h,e[1]=(r[1]*n+r[5]*s+r[9]*a+r[13])/h,e[2]=(r[2]*n+r[6]*s+r[10]*a+r[14])/h,this}},{key:\"scaleRotateMatrix4\",value:function(t){var e,i,r,n,s,a,h;return e=this,r=t,n=(i=this)[0],s=i[1],a=i[2],h=(h=r[3]*n+r[7]*s+r[11]*a+r[15])||1,e[0]=(r[0]*n+r[4]*s+r[8]*a)/h,e[1]=(r[1]*n+r[5]*s+r[9]*a)/h,e[2]=(r[2]*n+r[6]*s+r[10]*a)/h,this}},{key:\"applyQuaternion\",value:function(t){return function(t,e,i){var r=e[0],n=e[1],s=e[2],a=i[0],h=i[1],o=i[2],u=h*s-o*n,l=o*r-a*s,c=a*n-h*r,f=h*c-o*l,d=o*u-a*c,g=a*l-h*u,v=2*i[3];u*=v,l*=v,c*=v,f*=2,d*=2,g*=2,t[0]=r+u+f,t[1]=n+l+d,t[2]=s+c+g}(this,this,t),this}},{key:\"angle\",value:function(t){return T(this,t)}},{key:\"lerp\",value:function(t,e){return function(t,e,i,r){var n=e[0],s=e[1],a=e[2];t[0]=n+r*(i[0]-n),t[1]=s+r*(i[1]-s),t[2]=a+r*(i[2]-a)}(this,this,t,e),this}},{key:\"clone\",value:function(){return new r(this[0],this[1],this[2])}},{key:\"fromArray\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return this[0]=t[e],this[1]=t[e+1],this[2]=t[e+2],this}},{key:\"toArray\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return t[e]=this[0],t[e+1]=this[1],t[e+2]=this[2],t}},{key:\"transformDirection\",value:function(t){var e=this[0],i=this[1],r=this[2];return this[0]=t[0]*e+t[4]*i+t[8]*r,this[1]=t[1]*e+t[5]*i+t[9]*r,this[2]=t[2]*e+t[6]*i+t[10]*r,this.normalize()}}])}(),F=new R,S=1,C=1,P=!1,O=function(){return n((function t(e){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};for(var n in i(this,t),e.canvas,this.gl=e,this.attributes=r,this.id=S++,this.VAOs={},this.drawRange={start:0,count:0},this.instancedCount=0,this.gl.renderer.bindVertexArray(null),this.gl.renderer.currentGeometry=null,this.glState=this.gl.renderer.state,r)this.addAttribute(n,r[n])}),[{key:\"addAttribute\",value:function(t,e){this.attributes[t]=e;var i=this;if(e.id=C++,e.size=e.size||1,e.type=e.type||(e.data.constructor===Float32Array?this.gl.FLOAT:e.data.constructor===Uint16Array?this.gl.UNSIGNED_SHORT:this.gl.UNSIGNED_INT),e.target=\"index\"===t?this.gl.ELEMENT_ARRAY_BUFFER:this.gl.ARRAY_BUFFER,e.normalized=e.normalized||!1,e.stride=e.stride||0,e.offset=e.offset||0,e.count=e.count||(e.stride?e.data.byteLength/e.stride:e.data.length/e.size),e.divisor=e.instanced||0,e.needsUpdate=!1,e.usage=e.usage||this.gl.STATIC_DRAW,e.getX=function(t){return this.data[t*this.size]},e.getY=function(t){return this.data[t*this.size+1]},e.getZ=function(t){return this.data[t*this.size+2]},e.setXYZ=function(t,r,n,s){t*=this.size,this.data[t+0]=r,this.data[t+1]=n,this.data[t+2]=s,i.updateAttribute(e)},e.buffer||this.updateAttribute(e),e.divisor){if(this.isInstanced=!0,this.instancedCount&&this.instancedCount!==e.count*e.divisor)return this.instancedCount=Math.min(this.instancedCount,e.count*e.divisor);this.instancedCount=e.count*e.divisor}else\"index\"===t?this.drawRange.count=e.count:this.attributes.index||(this.drawRange.count=Math.max(this.drawRange.count,e.count))}},{key:\"updateAttribute\",value:function(t){var e=!t.buffer;e&&(t.buffer=this.gl.createBuffer()),this.glState.boundBuffer!==t.buffer&&(this.gl.bindBuffer(t.target,t.buffer),this.glState.boundBuffer=t.buffer),e?this.gl.bufferData(t.target,t.data,t.usage):this.gl.bufferSubData(t.target,0,t.data),t.needsUpdate=!1}},{key:\"setIndex\",value:function(t){this.addAttribute(\"index\",t)}},{key:\"setDrawRange\",value:function(t,e){this.drawRange.start=t,this.drawRange.count=e}},{key:\"setInstancedCount\",value:function(t){this.instancedCount=t}},{key:\"createVAO\",value:function(t){this.VAOs[t.attributeOrder]=this.gl.renderer.createVertexArray(),this.gl.renderer.bindVertexArray(this.VAOs[t.attributeOrder]),this.bindAttributes(t)}},{key:\"bindAttributes\",value:function(t){var e=this;t.attributeLocations.forEach((function(t,i){var r=i.name,n=i.type;if(e.attributes[r]){var s=e.attributes[r];e.gl.bindBuffer(s.target,s.buffer),e.glState.boundBuffer=s.buffer;var a=1;35674===n&&(a=2),35675===n&&(a=3),35676===n&&(a=4);for(var h=s.size/a,o=1===a?0:a*a*4,u=1===a?0:4*a,l=0;l<a;l++)e.gl.vertexAttribPointer(t+l,h,s.type,s.normalized,s.stride+o,s.offset+l*u),e.gl.enableVertexAttribArray(t+l),e.gl.renderer.vertexAttribDivisor(t+l,s.divisor)}})),this.attributes.index&&this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,this.attributes.index.buffer)}},{key:\"draw\",value:function(t){var e,i=this,r=t.program,n=t.mode,s=void 0===n?this.gl.TRIANGLES:n;this.gl.renderer.currentGeometry!==\"\".concat(this.id,\"_\").concat(r.attributeOrder)&&(this.VAOs[r.attributeOrder]||this.createVAO(r),this.gl.renderer.bindVertexArray(this.VAOs[r.attributeOrder]),this.gl.renderer.currentGeometry=\"\".concat(this.id,\"_\").concat(r.attributeOrder)),r.attributeLocations.forEach((function(t,e){var r=e.name,n=i.attributes[r];n.needsUpdate&&i.updateAttribute(n)}));var a=2;(null===(e=this.attributes.index)||void 0===e?void 0:e.type)===this.gl.UNSIGNED_INT&&(a=4),this.isInstanced?this.attributes.index?this.gl.renderer.drawElementsInstanced(s,this.drawRange.count,this.attributes.index.type,this.attributes.index.offset+this.drawRange.start*a,this.instancedCount):this.gl.renderer.drawArraysInstanced(s,this.drawRange.start,this.drawRange.count,this.instancedCount):this.attributes.index?this.gl.drawElements(s,this.drawRange.count,this.attributes.index.type,this.attributes.index.offset+this.drawRange.start*a):this.gl.drawArrays(s,this.drawRange.start,this.drawRange.count)}},{key:\"getPosition\",value:function(){var t=this.attributes.position;return t.data?t:P?void 0:P=!0}},{key:\"computeBoundingBox\",value:function(t){t||(t=this.getPosition());var e=t.data,i=t.size;this.bounds||(this.bounds={min:new R,max:new R,center:new R,scale:new R,radius:1/0});var r=this.bounds.min,n=this.bounds.max,s=this.bounds.center,a=this.bounds.scale;r.set(1/0),n.set(-1/0);for(var h=0,o=e.length;h<o;h+=i){var u=e[h],l=e[h+1],c=e[h+2];r.x=Math.min(u,r.x),r.y=Math.min(l,r.y),r.z=Math.min(c,r.z),n.x=Math.max(u,n.x),n.y=Math.max(l,n.y),n.z=Math.max(c,n.z)}a.sub(n,r),s.add(r,n).divide(2)}},{key:\"computeBoundingSphere\",value:function(t){t||(t=this.getPosition());var e=t.data,i=t.size;this.bounds||this.computeBoundingBox(t);for(var r=0,n=0,s=e.length;n<s;n+=i)F.fromArray(e,n),r=Math.max(r,this.bounds.center.squaredDistance(F));this.bounds.radius=Math.sqrt(r)}},{key:\"remove\",value:function(){for(var t in this.VAOs)this.gl.renderer.deleteVertexArray(this.VAOs[t]),delete this.VAOs[t];for(var e in this.attributes)this.gl.deleteBuffer(this.attributes[e].buffer),delete this.attributes[e]}}])}(),B=1,N={},D=function(){return n((function t(e){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=r.vertex,s=r.fragment,a=r.uniforms,h=void 0===a?{}:a,o=r.transparent,u=void 0!==o&&o,l=r.cullFace,c=void 0===l?e.BACK:l,f=r.frontFace,d=void 0===f?e.CCW:f,g=r.depthTest,v=void 0===g||g,p=r.depthWrite,m=void 0===p||p,y=r.depthFunc,_=void 0===y?e.LEQUAL:y;i(this,t),e.canvas,this.gl=e,this.uniforms=h,this.id=B++,this.transparent=u,this.cullFace=c,this.frontFace=d,this.depthTest=v,this.depthWrite=m,this.depthFunc=_,this.blendFunc={},this.blendEquation={},this.transparent&&!this.blendFunc.src&&(this.gl.renderer.premultipliedAlpha?this.setBlendFunc(this.gl.ONE,this.gl.ONE_MINUS_SRC_ALPHA):this.setBlendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA)),this.vertexShader=e.createShader(e.VERTEX_SHADER),this.fragmentShader=e.createShader(e.FRAGMENT_SHADER),this.program=e.createProgram(),e.attachShader(this.program,this.vertexShader),e.attachShader(this.program,this.fragmentShader),this.setShaders({vertex:n,fragment:s})}),[{key:\"setShaders\",value:function(t){var e=t.vertex,i=t.fragment;if(e&&(this.gl.shaderSource(this.vertexShader,e),this.gl.compileShader(this.vertexShader),this.gl.getShaderInfoLog(this.vertexShader)),i&&(this.gl.shaderSource(this.fragmentShader,i),this.gl.compileShader(this.fragmentShader),this.gl.getShaderInfoLog(this.fragmentShader)),this.gl.linkProgram(this.program),this.gl.getProgramParameter(this.program,this.gl.LINK_STATUS)){this.uniformLocations=new Map;for(var r=this.gl.getProgramParameter(this.program,this.gl.ACTIVE_UNIFORMS),n=0;n<r;n++){var s=this.gl.getActiveUniform(this.program,n);this.uniformLocations.set(s,this.gl.getUniformLocation(this.program,s.name));var a=s.name.match(/(\\w+)/g);s.uniformName=a[0],s.nameComponents=a.slice(1)}this.attributeLocations=new Map;for(var h=[],o=this.gl.getProgramParameter(this.program,this.gl.ACTIVE_ATTRIBUTES),u=0;u<o;u++){var l=this.gl.getActiveAttrib(this.program,u),c=this.gl.getAttribLocation(this.program,l.name);-1!==c&&(h[c]=l.name,this.attributeLocations.set(l,c))}this.attributeOrder=h.join(\"\")}}},{key:\"setBlendFunc\",value:function(t,e,i,r){this.blendFunc.src=t,this.blendFunc.dst=e,this.blendFunc.srcAlpha=i,this.blendFunc.dstAlpha=r,t&&(this.transparent=!0)}},{key:\"setBlendEquation\",value:function(t,e){this.blendEquation.modeRGB=t,this.blendEquation.modeAlpha=e}},{key:\"applyState\",value:function(){this.depthTest?this.gl.renderer.enable(this.gl.DEPTH_TEST):this.gl.renderer.disable(this.gl.DEPTH_TEST),this.cullFace?this.gl.renderer.enable(this.gl.CULL_FACE):this.gl.renderer.disable(this.gl.CULL_FACE),this.blendFunc.src?this.gl.renderer.enable(this.gl.BLEND):this.gl.renderer.disable(this.gl.BLEND),this.cullFace&&this.gl.renderer.setCullFace(this.cullFace),this.gl.renderer.setFrontFace(this.frontFace),this.gl.renderer.setDepthMask(this.depthWrite),this.gl.renderer.setDepthFunc(this.depthFunc),this.blendFunc.src&&this.gl.renderer.setBlendFunc(this.blendFunc.src,this.blendFunc.dst,this.blendFunc.srcAlpha,this.blendFunc.dstAlpha),this.gl.renderer.setBlendEquation(this.blendEquation.modeRGB,this.blendEquation.modeAlpha)}},{key:\"use\",value:function(){var t=this,e=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).flipFaces,i=void 0!==e&&e,r=-1;this.gl.renderer.state.currentProgram===this.id||(this.gl.useProgram(this.program),this.gl.renderer.state.currentProgram=this.id),this.uniformLocations.forEach((function(e,i){var n,s=t.uniforms[i.uniformName],a=function(t,e){var i=\"undefined\"!=typeof Symbol&&t[Symbol.iterator]||t[\"@@iterator\"];if(!i){if(Array.isArray(t)||(i=g(t))||e){i&&(t=i);var r=0,n=function(){};return{s:n,n:function(){return r>=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:n}}throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}var s,a=!0,h=!1;return{s:function(){i=i.call(t)},n:function(){var t=i.next();return a=t.done,t},e:function(t){h=!0,s=t},f:function(){try{a||null==i.return||i.return()}finally{if(h)throw s}}}}(i.nameComponents);try{for(a.s();!(n=a.n()).done;){var h=n.value;if(!s)break;if(!(h in s)){if(Array.isArray(s.value))break;s=void 0;break}s=s[h]}}catch(t){a.e(t)}finally{a.f()}if(!s)return L(\"Active uniform \".concat(i.name,\" has not been supplied\"));if(s&&void 0===s.value)return L(\"\".concat(i.name,\" uniform is missing a value parameter\"));if(s.value.texture)return r+=1,s.value.update(r),U(t.gl,i.type,e,r);if(s.value.length&&s.value[0].texture){var o=[];return s.value.forEach((function(t){r+=1,t.update(r),o.push(r)})),U(t.gl,i.type,e,o)}U(t.gl,i.type,e,s.value)})),this.applyState(),i&&this.gl.renderer.setFrontFace(this.frontFace===this.gl.CCW?this.gl.CW:this.gl.CCW)}},{key:\"remove\",value:function(){this.gl.deleteProgram(this.program)}}])}();function U(t,e,i,r){r=r.length?function(t){var e=t.length,i=t[0].length;if(void 0===i)return t;var r=e*i,n=N[r];n||(N[r]=n=new Float32Array(r));for(var s=0;s<e;s++)n.set(t[s],s*i);return n}(r):r;var n=t.renderer.state.uniformLocations.get(i);if(r.length)if(void 0===n||n.length!==r.length)t.renderer.state.uniformLocations.set(i,r.slice(0));else{if(function(t,e){if(t.length!==e.length)return!1;for(var i=0,r=t.length;i<r;i++)if(t[i]!==e[i])return!1;return!0}(n,r))return;n.set?n.set(r):function(t,e){for(var i=0,r=t.length;i<r;i++)t[i]=e[i]}(n,r),t.renderer.state.uniformLocations.set(i,n)}else{if(n===r)return;t.renderer.state.uniformLocations.set(i,r)}switch(e){case 5126:return r.length?t.uniform1fv(i,r):t.uniform1f(i,r);case 35664:return t.uniform2fv(i,r);case 35665:return t.uniform3fv(i,r);case 35666:return t.uniform4fv(i,r);case 35670:case 5124:case 35678:case 35680:return r.length?t.uniform1iv(i,r):t.uniform1i(i,r);case 35671:case 35667:return t.uniform2iv(i,r);case 35672:case 35668:return t.uniform3iv(i,r);case 35673:case 35669:return t.uniform4iv(i,r);case 35674:return t.uniformMatrix2fv(i,!1,r);case 35675:return t.uniformMatrix3fv(i,!1,r);case 35676:return t.uniformMatrix4fv(i,!1,r)}}var I=0;function L(t){I>100||I++}var z=new R,q=1,j=function(){return n((function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=e.canvas,n=void 0===r?document.createElement(\"canvas\"):r,s=e.width,a=void 0===s?300:s,h=e.height,o=void 0===h?150:h,u=e.dpr,l=void 0===u?1:u,c=e.alpha,f=void 0!==c&&c,d=e.depth,g=void 0===d||d,v=e.stencil,p=void 0!==v&&v,m=e.antialias,y=void 0===m||m,_=e.premultipliedAlpha,b=void 0!==_&&_,x=e.preserveDrawingBuffer,E=void 0===x||x,w=e.powerPreference,M=void 0===w?\"default\":w,k=e.autoClear,A=void 0===k||k,T=e.webgl,R=void 0===T?2:T;i(this,t);var F={alpha:f,depth:g,stencil:p,antialias:y,premultipliedAlpha:b,preserveDrawingBuffer:E,powerPreference:M};this.dpr=l,this.alpha=f,this.color=!0,this.depth=g,this.stencil=p,this.premultipliedAlpha=b,this.autoClear=A,this.id=q++,2===R&&(this.gl=n.getContext(\"webgl2\",F)),this.isWebgl2=!!this.gl,this.gl||(this.gl=n.getContext(\"webgl\",F)),this.gl,this.gl.renderer=this,this.setSize(a,o),this.state={},this.state.blendFunc={src:this.gl.ONE,dst:this.gl.ZERO},this.state.blendEquation={modeRGB:this.gl.FUNC_ADD},this.state.cullFace=!1,this.state.frontFace=this.gl.CCW,this.state.depthMask=!0,this.state.depthFunc=this.gl.LEQUAL,this.state.premultiplyAlpha=!1,this.state.flipY=!1,this.state.unpackAlignment=4,this.state.framebuffer=null,this.state.viewport={x:0,y:0,width:null,height:null},this.state.textureUnits=[],this.state.activeTextureUnit=0,this.state.boundBuffer=null,this.state.uniformLocations=new Map,this.state.currentProgram=null,this.extensions={},this.isWebgl2?(this.getExtension(\"EXT_color_buffer_float\"),this.getExtension(\"OES_texture_float_linear\")):(this.getExtension(\"OES_texture_float\"),this.getExtension(\"OES_texture_float_linear\"),this.getExtension(\"OES_texture_half_float\"),this.getExtension(\"OES_texture_half_float_linear\"),this.getExtension(\"OES_element_index_uint\"),this.getExtension(\"OES_standard_derivatives\"),this.getExtension(\"EXT_sRGB\"),this.getExtension(\"WEBGL_depth_texture\"),this.getExtension(\"WEBGL_draw_buffers\")),this.getExtension(\"WEBGL_compressed_texture_astc\"),this.getExtension(\"EXT_texture_compression_bptc\"),this.getExtension(\"WEBGL_compressed_texture_s3tc\"),this.getExtension(\"WEBGL_compressed_texture_etc1\"),this.getExtension(\"WEBGL_compressed_texture_pvrtc\"),this.getExtension(\"WEBKIT_WEBGL_compressed_texture_pvrtc\"),this.vertexAttribDivisor=this.getExtension(\"ANGLE_instanced_arrays\",\"vertexAttribDivisor\",\"vertexAttribDivisorANGLE\"),this.drawArraysInstanced=this.getExtension(\"ANGLE_instanced_arrays\",\"drawArraysInstanced\",\"drawArraysInstancedANGLE\"),this.drawElementsInstanced=this.getExtension(\"ANGLE_instanced_arrays\",\"drawElementsInstanced\",\"drawElementsInstancedANGLE\"),this.createVertexArray=this.getExtension(\"OES_vertex_array_object\",\"createVertexArray\",\"createVertexArrayOES\"),this.bindVertexArray=this.getExtension(\"OES_vertex_array_object\",\"bindVertexArray\",\"bindVertexArrayOES\"),this.deleteVertexArray=this.getExtension(\"OES_vertex_array_object\",\"deleteVertexArray\",\"deleteVertexArrayOES\"),this.drawBuffers=this.getExtension(\"WEBGL_draw_buffers\",\"drawBuffers\",\"drawBuffersWEBGL\"),this.parameters={},this.parameters.maxTextureUnits=this.gl.getParameter(this.gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS),this.parameters.maxAnisotropy=this.getExtension(\"EXT_texture_filter_anisotropic\")?this.gl.getParameter(this.getExtension(\"EXT_texture_filter_anisotropic\").MAX_TEXTURE_MAX_ANISOTROPY_EXT):0}),[{key:\"setSize\",value:function(t,e){this.width=t,this.height=e,this.gl.canvas.width=t*this.dpr,this.gl.canvas.height=e*this.dpr,this.gl.canvas.style&&Object.assign(this.gl.canvas.style,{width:t+\"px\",height:e+\"px\"})}},{key:\"setViewport\",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0;this.state.viewport.width===t&&this.state.viewport.height===e||(this.state.viewport.width=t,this.state.viewport.height=e,this.state.viewport.x=i,this.state.viewport.y=r,this.gl.viewport(i,r,t,e))}},{key:\"setScissor\",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0;this.gl.scissor(i,r,t,e)}},{key:\"enable\",value:function(t){!0!==this.state[t]&&(this.gl.enable(t),this.state[t]=!0)}},{key:\"disable\",value:function(t){!1!==this.state[t]&&(this.gl.disable(t),this.state[t]=!1)}},{key:\"setBlendFunc\",value:function(t,e,i,r){this.state.blendFunc.src===t&&this.state.blendFunc.dst===e&&this.state.blendFunc.srcAlpha===i&&this.state.blendFunc.dstAlpha===r||(this.state.blendFunc.src=t,this.state.blendFunc.dst=e,this.state.blendFunc.srcAlpha=i,this.state.blendFunc.dstAlpha=r,void 0!==i?this.gl.blendFuncSeparate(t,e,i,r):this.gl.blendFunc(t,e))}},{key:\"setBlendEquation\",value:function(t,e){t=t||this.gl.FUNC_ADD,this.state.blendEquation.modeRGB===t&&this.state.blendEquation.modeAlpha===e||(this.state.blendEquation.modeRGB=t,this.state.blendEquation.modeAlpha=e,void 0!==e?this.gl.blendEquationSeparate(t,e):this.gl.blendEquation(t))}},{key:\"setCullFace\",value:function(t){this.state.cullFace!==t&&(this.state.cullFace=t,this.gl.cullFace(t))}},{key:\"setFrontFace\",value:function(t){this.state.frontFace!==t&&(this.state.frontFace=t,this.gl.frontFace(t))}},{key:\"setDepthMask\",value:function(t){this.state.depthMask!==t&&(this.state.depthMask=t,this.gl.depthMask(t))}},{key:\"setDepthFunc\",value:function(t){this.state.depthFunc!==t&&(this.state.depthFunc=t,this.gl.depthFunc(t))}},{key:\"activeTexture\",value:function(t){this.state.activeTextureUnit!==t&&(this.state.activeTextureUnit=t,this.gl.activeTexture(this.gl.TEXTURE0+t))}},{key:\"bindFramebuffer\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.target,i=void 0===e?this.gl.FRAMEBUFFER:e,r=t.buffer,n=void 0===r?null:r;this.state.framebuffer!==n&&(this.state.framebuffer=n,this.gl.bindFramebuffer(i,n))}},{key:\"getExtension\",value:function(t,e,i){return e&&this.gl[e]?this.gl[e].bind(this.gl):(this.extensions[t]||(this.extensions[t]=this.gl.getExtension(t)),e?this.extensions[t]?this.extensions[t][i].bind(this.extensions[t]):null:this.extensions[t])}},{key:\"sortOpaque\",value:function(t,e){return t.renderOrder!==e.renderOrder?t.renderOrder-e.renderOrder:t.program.id!==e.program.id?t.program.id-e.program.id:t.zDepth!==e.zDepth?t.zDepth-e.zDepth:e.id-t.id}},{key:\"sortTransparent\",value:function(t,e){return t.renderOrder!==e.renderOrder?t.renderOrder-e.renderOrder:t.zDepth!==e.zDepth?e.zDepth-t.zDepth:e.id-t.id}},{key:\"sortUI\",value:function(t,e){return t.renderOrder!==e.renderOrder?t.renderOrder-e.renderOrder:t.program.id!==e.program.id?t.program.id-e.program.id:e.id-t.id}},{key:\"getRenderList\",value:function(t){var e=t.scene,i=t.camera,r=t.frustumCull,n=t.sort,s=[];if(i&&r&&i.updateFrustum(),e.traverse((function(t){if(!t.visible)return!0;t.draw&&(r&&t.frustumCulled&&i&&!i.frustumIntersectsMesh(t)||s.push(t))})),n){var a=[],h=[],o=[];s.forEach((function(t){t.program.transparent?t.program.depthTest?h.push(t):o.push(t):a.push(t),t.zDepth=0,0===t.renderOrder&&t.program.depthTest&&i&&(t.worldMatrix.getTranslation(z),z.applyMatrix4(i.projectionViewMatrix),t.zDepth=z.z)})),a.sort(this.sortOpaque),h.sort(this.sortTransparent),o.sort(this.sortUI),s=a.concat(h,o)}return s}},{key:\"render\",value:function(t){var e=t.scene,i=t.camera,r=t.target,n=void 0===r?null:r,s=t.update,a=void 0===s||s,h=t.sort,o=void 0===h||h,u=t.frustumCull,l=void 0===u||u,c=t.clear;null===n?(this.bindFramebuffer(),this.setViewport(this.width*this.dpr,this.height*this.dpr)):(this.bindFramebuffer(n),this.setViewport(n.width,n.height)),(c||this.autoClear&&!1!==c)&&(!this.depth||n&&!n.depth||(this.enable(this.gl.DEPTH_TEST),this.setDepthMask(!0)),this.gl.clear((this.color?this.gl.COLOR_BUFFER_BIT:0)|(this.depth?this.gl.DEPTH_BUFFER_BIT:0)|(this.stencil?this.gl.STENCIL_BUFFER_BIT:0))),a&&e.updateMatrixWorld(),i&&i.updateMatrixWorld(),this.getRenderList({scene:e,camera:i,frustumCull:l,sort:o}).forEach((function(t){t.draw({camera:i})}))}}])}();function G(t,e,i){var r=e[0],n=e[1],s=e[2],a=e[3],h=i[0],o=i[1],u=i[2],l=i[3];return t[0]=r*l+a*h+n*u-s*o,t[1]=n*l+a*o+s*h-r*u,t[2]=s*l+a*u+r*o-n*h,t[3]=a*l-r*h-n*o-s*u,t}var X=function(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t},V=function(t,e,i,r,n){return t[0]=e,t[1]=i,t[2]=r,t[3]=n,t},W=function(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]+t[3]*e[3]},H=function(t,e){var i=e[0],r=e[1],n=e[2],s=e[3],a=i*i+r*r+n*n+s*s;return a>0&&(a=1/Math.sqrt(a)),t[0]=i*a,t[1]=r*a,t[2]=n*a,t[3]=s*a,t},Y=function(t){function r(){var t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,h=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1;i(this,r),(t=e(this,r,[n,s,a,h])).onChange=function(){},t._target=t;var o=[\"0\",\"1\",\"2\",\"3\"];return u(t,new Proxy(t,{set:function(t,e){var i=Reflect.set.apply(Reflect,arguments);return i&&o.includes(e)&&t.onChange(),i}}))}return h(r,v(Array)),n(r,[{key:\"x\",get:function(){return this[0]},set:function(t){this._target[0]=t,this.onChange()}},{key:\"y\",get:function(){return this[1]},set:function(t){this._target[1]=t,this.onChange()}},{key:\"z\",get:function(){return this[2]},set:function(t){this._target[2]=t,this.onChange()}},{key:\"w\",get:function(){return this[3]},set:function(t){this._target[3]=t,this.onChange()}},{key:\"identity\",value:function(){var t;return(t=this._target)[0]=0,t[1]=0,t[2]=0,t[3]=1,this.onChange(),this}},{key:\"set\",value:function(t,e,i,r){return t.length?this.copy(t):(V(this._target,t,e,i,r),this.onChange(),this)}},{key:\"rotateX\",value:function(t){return function(t,e,i){i*=.5;var r=e[0],n=e[1],s=e[2],a=e[3],h=Math.sin(i),o=Math.cos(i);t[0]=r*o+a*h,t[1]=n*o+s*h,t[2]=s*o-n*h,t[3]=a*o-r*h}(this._target,this._target,t),this.onChange(),this}},{key:\"rotateY\",value:function(t){return function(t,e,i){i*=.5;var r=e[0],n=e[1],s=e[2],a=e[3],h=Math.sin(i),o=Math.cos(i);t[0]=r*o-s*h,t[1]=n*o+a*h,t[2]=s*o+r*h,t[3]=a*o-n*h}(this._target,this._target,t),this.onChange(),this}},{key:\"rotateZ\",value:function(t){return function(t,e,i){i*=.5;var r=e[0],n=e[1],s=e[2],a=e[3],h=Math.sin(i),o=Math.cos(i);t[0]=r*o+n*h,t[1]=n*o-r*h,t[2]=s*o+a*h,t[3]=a*o-s*h}(this._target,this._target,t),this.onChange(),this}},{key:\"inverse\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this._target;return function(t,e){var i=e[0],r=e[1],n=e[2],s=e[3],a=i*i+r*r+n*n+s*s,h=a?1/a:0;t[0]=-i*h,t[1]=-r*h,t[2]=-n*h,t[3]=s*h}(this._target,t),this.onChange(),this}},{key:\"conjugate\",value:function(){var t,e,i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this._target;return t=this._target,e=i,t[0]=-e[0],t[1]=-e[1],t[2]=-e[2],t[3]=e[3],this.onChange(),this}},{key:\"copy\",value:function(t){return X(this._target,t),this.onChange(),this}},{key:\"normalize\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this._target;return H(this._target,t),this.onChange(),this}},{key:\"multiply\",value:function(t,e){return e?G(this._target,t,e):G(this._target,this._target,t),this.onChange(),this}},{key:\"dot\",value:function(t){return W(this._target,t)}},{key:\"fromMatrix3\",value:function(t){return function(t,e){var i,r=e[0]+e[4]+e[8];if(r>0)i=Math.sqrt(r+1),t[3]=.5*i,i=.5/i,t[0]=(e[5]-e[7])*i,t[1]=(e[6]-e[2])*i,t[2]=(e[1]-e[3])*i;else{var n=0;e[4]>e[0]&&(n=1),e[8]>e[3*n+n]&&(n=2);var s=(n+1)%3,a=(n+2)%3;i=Math.sqrt(e[3*n+n]-e[3*s+s]-e[3*a+a]+1),t[n]=.5*i,i=.5/i,t[3]=(e[3*s+a]-e[3*a+s])*i,t[s]=(e[3*s+n]+e[3*n+s])*i,t[a]=(e[3*a+n]+e[3*n+a])*i}}(this._target,t),this.onChange(),this}},{key:\"fromEuler\",value:function(t,e){return function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:\"YXZ\",r=Math.sin(.5*e[0]),n=Math.cos(.5*e[0]),s=Math.sin(.5*e[1]),a=Math.cos(.5*e[1]),h=Math.sin(.5*e[2]),o=Math.cos(.5*e[2]);\"XYZ\"===i?(t[0]=r*a*o+n*s*h,t[1]=n*s*o-r*a*h,t[2]=n*a*h+r*s*o,t[3]=n*a*o-r*s*h):\"YXZ\"===i?(t[0]=r*a*o+n*s*h,t[1]=n*s*o-r*a*h,t[2]=n*a*h-r*s*o,t[3]=n*a*o+r*s*h):\"ZXY\"===i?(t[0]=r*a*o-n*s*h,t[1]=n*s*o+r*a*h,t[2]=n*a*h+r*s*o,t[3]=n*a*o-r*s*h):\"ZYX\"===i?(t[0]=r*a*o-n*s*h,t[1]=n*s*o+r*a*h,t[2]=n*a*h-r*s*o,t[3]=n*a*o+r*s*h):\"YZX\"===i?(t[0]=r*a*o+n*s*h,t[1]=n*s*o+r*a*h,t[2]=n*a*h-r*s*o,t[3]=n*a*o-r*s*h):\"XZY\"===i&&(t[0]=r*a*o-n*s*h,t[1]=n*s*o-r*a*h,t[2]=n*a*h+r*s*o,t[3]=n*a*o+r*s*h)}(this._target,t,t.order),e||this.onChange(),this}},{key:\"fromAxisAngle\",value:function(t,e){return function(t,e,i){i*=.5;var r=Math.sin(i);t[0]=r*e[0],t[1]=r*e[1],t[2]=r*e[2],t[3]=Math.cos(i)}(this._target,t,e),this.onChange(),this}},{key:\"slerp\",value:function(t,e){return function(t,e,i,r){var n,s,a,h,o,u=e[0],l=e[1],c=e[2],f=e[3],d=i[0],g=i[1],v=i[2],p=i[3];(s=u*d+l*g+c*v+f*p)<0&&(s=-s,d=-d,g=-g,v=-v,p=-p),1-s>1e-6?(n=Math.acos(s),a=Math.sin(n),h=Math.sin((1-r)*n)/a,o=Math.sin(r*n)/a):(h=1-r,o=r),t[0]=h*u+o*d,t[1]=h*l+o*g,t[2]=h*c+o*v,t[3]=h*f+o*p}(this._target,this._target,t,e),this.onChange(),this}},{key:\"fromArray\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return this._target[0]=t[e],this._target[1]=t[e+1],this._target[2]=t[e+2],this._target[3]=t[e+3],this.onChange(),this}},{key:\"toArray\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return t[e]=this[0],t[e+1]=this[1],t[e+2]=this[2],t[e+3]=this[3],t}}])}();function Z(t){var e=t[0],i=t[1],r=t[2],n=t[3],s=t[4],a=t[5],h=t[6],o=t[7],u=t[8],l=t[9],c=t[10],f=t[11],d=t[12],g=t[13],v=t[14],p=t[15];return(e*a-i*s)*(c*p-f*v)-(e*h-r*s)*(l*p-f*g)+(e*o-n*s)*(l*v-c*g)+(i*h-r*a)*(u*p-f*d)-(i*o-n*a)*(u*v-c*d)+(r*o-n*h)*(u*g-l*d)}function Q(t,e,i){var r=e[0],n=e[1],s=e[2],a=e[3],h=e[4],o=e[5],u=e[6],l=e[7],c=e[8],f=e[9],d=e[10],g=e[11],v=e[12],p=e[13],m=e[14],y=e[15],_=i[0],b=i[1],x=i[2],E=i[3];return t[0]=_*r+b*h+x*c+E*v,t[1]=_*n+b*o+x*f+E*p,t[2]=_*s+b*u+x*d+E*m,t[3]=_*a+b*l+x*g+E*y,_=i[4],b=i[5],x=i[6],E=i[7],t[4]=_*r+b*h+x*c+E*v,t[5]=_*n+b*o+x*f+E*p,t[6]=_*s+b*u+x*d+E*m,t[7]=_*a+b*l+x*g+E*y,_=i[8],b=i[9],x=i[10],E=i[11],t[8]=_*r+b*h+x*c+E*v,t[9]=_*n+b*o+x*f+E*p,t[10]=_*s+b*u+x*d+E*m,t[11]=_*a+b*l+x*g+E*y,_=i[12],b=i[13],x=i[14],E=i[15],t[12]=_*r+b*h+x*c+E*v,t[13]=_*n+b*o+x*f+E*p,t[14]=_*s+b*u+x*d+E*m,t[15]=_*a+b*l+x*g+E*y,t}function K(t,e){var i=e[0],r=e[1],n=e[2],s=e[4],a=e[5],h=e[6],o=e[8],u=e[9],l=e[10];return t[0]=Math.hypot(i,r,n),t[1]=Math.hypot(s,a,h),t[2]=Math.hypot(o,u,l),t}var $,J=($=[1,1,1],function(t,e){var i=$;K(i,e);var r=1/i[0],n=1/i[1],s=1/i[2],a=e[0]*r,h=e[1]*n,o=e[2]*s,u=e[4]*r,l=e[5]*n,c=e[6]*s,f=e[8]*r,d=e[9]*n,g=e[10]*s,v=a+l+g,p=0;return v>0?(p=2*Math.sqrt(v+1),t[3]=.25*p,t[0]=(c-d)/p,t[1]=(f-o)/p,t[2]=(h-u)/p):a>l&&a>g?(p=2*Math.sqrt(1+a-l-g),t[3]=(c-d)/p,t[0]=.25*p,t[1]=(h+u)/p,t[2]=(f+o)/p):l>g?(p=2*Math.sqrt(1+l-a-g),t[3]=(f-o)/p,t[0]=(h+u)/p,t[1]=.25*p,t[2]=(c+d)/p):(p=2*Math.sqrt(1+g-a-l),t[3]=(h-u)/p,t[0]=(f+o)/p,t[1]=(c+d)/p,t[2]=.25*p),t});function tt(t,e,i){return t[0]=e[0]+i[0],t[1]=e[1]+i[1],t[2]=e[2]+i[2],t[3]=e[3]+i[3],t[4]=e[4]+i[4],t[5]=e[5]+i[5],t[6]=e[6]+i[6],t[7]=e[7]+i[7],t[8]=e[8]+i[8],t[9]=e[9]+i[9],t[10]=e[10]+i[10],t[11]=e[11]+i[11],t[12]=e[12]+i[12],t[13]=e[13]+i[13],t[14]=e[14]+i[14],t[15]=e[15]+i[15],t}function et(t,e,i){return t[0]=e[0]-i[0],t[1]=e[1]-i[1],t[2]=e[2]-i[2],t[3]=e[3]-i[3],t[4]=e[4]-i[4],t[5]=e[5]-i[5],t[6]=e[6]-i[6],t[7]=e[7]-i[7],t[8]=e[8]-i[8],t[9]=e[9]-i[9],t[10]=e[10]-i[10],t[11]=e[11]-i[11],t[12]=e[12]-i[12],t[13]=e[13]-i[13],t[14]=e[14]-i[14],t[15]=e[15]-i[15],t}var it=function(t){function r(){var t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,h=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0,l=arguments.length>5&&void 0!==arguments[5]?arguments[5]:1,c=arguments.length>6&&void 0!==arguments[6]?arguments[6]:0,f=arguments.length>7&&void 0!==arguments[7]?arguments[7]:0,d=arguments.length>8&&void 0!==arguments[8]?arguments[8]:0,g=arguments.length>9&&void 0!==arguments[9]?arguments[9]:0,v=arguments.length>10&&void 0!==arguments[10]?arguments[10]:1,p=arguments.length>11&&void 0!==arguments[11]?arguments[11]:0,m=arguments.length>12&&void 0!==arguments[12]?arguments[12]:0,y=arguments.length>13&&void 0!==arguments[13]?arguments[13]:0,_=arguments.length>14&&void 0!==arguments[14]?arguments[14]:0,b=arguments.length>15&&void 0!==arguments[15]?arguments[15]:1;return i(this,r),u(t=e(this,r,[n,s,a,h,o,l,c,f,d,g,v,p,m,y,_,b]),t)}return h(r,v(Array)),n(r,[{key:\"x\",get:function(){return this[12]},set:function(t){this[12]=t}},{key:\"y\",get:function(){return this[13]},set:function(t){this[13]=t}},{key:\"z\",get:function(){return this[14]},set:function(t){this[14]=t}},{key:\"w\",get:function(){return this[15]},set:function(t){this[15]=t}},{key:\"set\",value:function(t,e,i,r,n,s,a,h,o,u,l,c,f,d,g,v){return t.length?this.copy(t):(function(t,e,i,r,n,s,a,h,o,u,l,c,f,d,g,v,p){t[0]=e,t[1]=i,t[2]=r,t[3]=n,t[4]=s,t[5]=a,t[6]=h,t[7]=o,t[8]=u,t[9]=l,t[10]=c,t[11]=f,t[12]=d,t[13]=g,t[14]=v,t[15]=p}(this,t,e,i,r,n,s,a,h,o,u,l,c,f,d,g,v),this)}},{key:\"translate\",value:function(t){return function(t,e,i){var r,n,s,a,h,o,u,l,c,f,d,g,v=i[0],p=i[1],m=i[2];e===t?(t[12]=e[0]*v+e[4]*p+e[8]*m+e[12],t[13]=e[1]*v+e[5]*p+e[9]*m+e[13],t[14]=e[2]*v+e[6]*p+e[10]*m+e[14],t[15]=e[3]*v+e[7]*p+e[11]*m+e[15]):(r=e[0],n=e[1],s=e[2],a=e[3],h=e[4],o=e[5],u=e[6],l=e[7],c=e[8],f=e[9],d=e[10],g=e[11],t[0]=r,t[1]=n,t[2]=s,t[3]=a,t[4]=h,t[5]=o,t[6]=u,t[7]=l,t[8]=c,t[9]=f,t[10]=d,t[11]=g,t[12]=r*v+h*p+c*m+e[12],t[13]=n*v+o*p+f*m+e[13],t[14]=s*v+u*p+d*m+e[14],t[15]=a*v+l*p+g*m+e[15])}(this,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,t),this}},{key:\"rotate\",value:function(t,e){return function(t,e,i,r){var n,s,a,h,o,u,l,c,f,d,g,v,p,m,y,_,b,x,E,w,M,k,A,T,R=r[0],F=r[1],S=r[2],C=Math.hypot(R,F,S);Math.abs(C)<1e-6||(R*=C=1/C,F*=C,S*=C,n=Math.sin(i),a=1-(s=Math.cos(i)),h=e[0],o=e[1],u=e[2],l=e[3],c=e[4],f=e[5],d=e[6],g=e[7],v=e[8],p=e[9],m=e[10],y=e[11],_=R*R*a+s,b=F*R*a+S*n,x=S*R*a-F*n,E=R*F*a-S*n,w=F*F*a+s,M=S*F*a+R*n,k=R*S*a+F*n,A=F*S*a-R*n,T=S*S*a+s,t[0]=h*_+c*b+v*x,t[1]=o*_+f*b+p*x,t[2]=u*_+d*b+m*x,t[3]=l*_+g*b+y*x,t[4]=h*E+c*w+v*M,t[5]=o*E+f*w+p*M,t[6]=u*E+d*w+m*M,t[7]=l*E+g*w+y*M,t[8]=h*k+c*A+v*T,t[9]=o*k+f*A+p*T,t[10]=u*k+d*A+m*T,t[11]=l*k+g*A+y*T,e!==t&&(t[12]=e[12],t[13]=e[13],t[14]=e[14],t[15]=e[15]))}(this,arguments.length>2&&void 0!==arguments[2]?arguments[2]:this,t,e),this}},{key:\"scale\",value:function(t){return function(t,e,i){var r=i[0],n=i[1],s=i[2];t[0]=e[0]*r,t[1]=e[1]*r,t[2]=e[2]*r,t[3]=e[3]*r,t[4]=e[4]*n,t[5]=e[5]*n,t[6]=e[6]*n,t[7]=e[7]*n,t[8]=e[8]*s,t[9]=e[9]*s,t[10]=e[10]*s,t[11]=e[11]*s,t[12]=e[12],t[13]=e[13],t[14]=e[14],t[15]=e[15]}(this,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,\"number\"==typeof t?[t,t,t]:t),this}},{key:\"add\",value:function(t,e){return e?tt(this,t,e):tt(this,this,t),this}},{key:\"sub\",value:function(t,e){return e?et(this,t,e):et(this,this,t),this}},{key:\"multiply\",value:function(t,e){var i,r,n;return t.length?e?Q(this,t,e):Q(this,this,t):(r=this,n=t,(i=this)[0]=r[0]*n,i[1]=r[1]*n,i[2]=r[2]*n,i[3]=r[3]*n,i[4]=r[4]*n,i[5]=r[5]*n,i[6]=r[6]*n,i[7]=r[7]*n,i[8]=r[8]*n,i[9]=r[9]*n,i[10]=r[10]*n,i[11]=r[11]*n,i[12]=r[12]*n,i[13]=r[13]*n,i[14]=r[14]*n,i[15]=r[15]*n),this}},{key:\"identity\",value:function(){var t;return(t=this)[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,this}},{key:\"copy\",value:function(t){var e,i;return i=t,(e=this)[0]=i[0],e[1]=i[1],e[2]=i[2],e[3]=i[3],e[4]=i[4],e[5]=i[5],e[6]=i[6],e[7]=i[7],e[8]=i[8],e[9]=i[9],e[10]=i[10],e[11]=i[11],e[12]=i[12],e[13]=i[13],e[14]=i[14],e[15]=i[15],this}},{key:\"fromPerspective\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return function(t,e,i,r,n){var s=1/Math.tan(e/2),a=1/(r-n);t[0]=s/i,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=s,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=(n+r)*a,t[11]=-1,t[12]=0,t[13]=0,t[14]=2*n*r*a,t[15]=0}(this,t.fov,t.aspect,t.near,t.far),this}},{key:\"fromOrthogonal\",value:function(t){return function(t,e,i,r,n,s,a){var h=1/(e-i),o=1/(r-n),u=1/(s-a);t[0]=-2*h,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=-2*o,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=2*u,t[11]=0,t[12]=(e+i)*h,t[13]=(n+r)*o,t[14]=(a+s)*u,t[15]=1}(this,t.left,t.right,t.bottom,t.top,t.near,t.far),this}},{key:\"fromQuaternion\",value:function(t){return function(t,e){var i=e[0],r=e[1],n=e[2],s=e[3],a=i+i,h=r+r,o=n+n,u=i*a,l=r*a,c=r*h,f=n*a,d=n*h,g=n*o,v=s*a,p=s*h,m=s*o;t[0]=1-c-g,t[1]=l+m,t[2]=f-p,t[3]=0,t[4]=l-m,t[5]=1-u-g,t[6]=d+v,t[7]=0,t[8]=f+p,t[9]=d-v,t[10]=1-u-c,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1}(this,t),this}},{key:\"setPosition\",value:function(t){return this.x=t[0],this.y=t[1],this.z=t[2],this}},{key:\"inverse\",value:function(){var t,e,i,r,n,s,a,h,o,u,l,c,f,d,g,v,p,m,y,_,b,x,E,w,M,k,A,T,R,F,S;return t=this,i=(e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this)[0],r=e[1],n=e[2],s=e[3],a=e[4],h=e[5],o=e[6],u=e[7],l=e[8],c=e[9],f=e[10],d=e[11],g=e[12],v=e[13],p=e[14],m=e[15],(S=(y=i*h-r*a)*(F=f*m-d*p)-(_=i*o-n*a)*(R=c*m-d*v)+(b=i*u-s*a)*(T=c*p-f*v)+(x=r*o-n*h)*(A=l*m-d*g)-(E=r*u-s*h)*(k=l*p-f*g)+(w=n*u-s*o)*(M=l*v-c*g))&&(S=1/S,t[0]=(h*F-o*R+u*T)*S,t[1]=(n*R-r*F-s*T)*S,t[2]=(v*w-p*E+m*x)*S,t[3]=(f*E-c*w-d*x)*S,t[4]=(o*A-a*F-u*k)*S,t[5]=(i*F-n*A+s*k)*S,t[6]=(p*b-g*w-m*_)*S,t[7]=(l*w-f*b+d*_)*S,t[8]=(a*R-h*A+u*M)*S,t[9]=(r*A-i*R-s*M)*S,t[10]=(g*E-v*b+m*y)*S,t[11]=(c*b-l*E-d*y)*S,t[12]=(h*k-a*T-o*M)*S,t[13]=(i*T-r*k+n*M)*S,t[14]=(v*_-g*x-p*y)*S,t[15]=(l*x-c*_+f*y)*S),this}},{key:\"compose\",value:function(t,e,i){var r,n,s,a,h,o,u,l,c,f,d,g,v,p,m,y,_,b,x,E,w,M,k;return n=e,s=i,a=this,h=(r=t)[0],o=r[1],u=r[2],l=r[3],g=h*(c=h+h),v=h*(f=o+o),p=h*(d=u+u),m=o*f,y=o*d,_=u*d,b=l*c,x=l*f,E=l*d,w=s[0],M=s[1],k=s[2],a[0]=(1-(m+_))*w,a[1]=(v+E)*w,a[2]=(p-x)*w,a[3]=0,a[4]=(v-E)*M,a[5]=(1-(g+_))*M,a[6]=(y+b)*M,a[7]=0,a[8]=(p+x)*k,a[9]=(y-b)*k,a[10]=(1-(g+m))*k,a[11]=0,a[12]=n[0],a[13]=n[1],a[14]=n[2],a[15]=1,this}},{key:\"decompose\",value:function(t,e,i){return function(t,e,i,r){var n=p([t[0],t[1],t[2]]),s=p([t[4],t[5],t[6]]),a=p([t[8],t[9],t[10]]);Z(t)<0&&(n=-n),i[0]=t[12],i[1]=t[13],i[2]=t[14];var h=t.slice(),o=1/n,u=1/s,l=1/a;h[0]*=o,h[1]*=o,h[2]*=o,h[4]*=u,h[5]*=u,h[6]*=u,h[8]*=l,h[9]*=l,h[10]*=l,J(e,h),r[0]=n,r[1]=s,r[2]=a}(this,t,e,i),this}},{key:\"getRotation\",value:function(t){return J(t,this),this}},{key:\"getTranslation\",value:function(t){var e,i;return i=this,(e=t)[0]=i[12],e[1]=i[13],e[2]=i[14],this}},{key:\"getScaling\",value:function(t){return K(t,this),this}},{key:\"getMaxScaleOnAxis\",value:function(){return e=(t=this)[0],i=t[1],r=t[2],n=t[4],s=t[5],a=t[6],h=t[8],o=t[9],u=t[10],l=e*e+i*i+r*r,c=n*n+s*s+a*a,f=h*h+o*o+u*u,Math.sqrt(Math.max(l,c,f));var t,e,i,r,n,s,a,h,o,u,l,c,f}},{key:\"lookAt\",value:function(t,e,i){return function(t,e,i,r){var n=e[0],s=e[1],a=e[2],h=r[0],o=r[1],u=r[2],l=n-i[0],c=s-i[1],f=a-i[2],d=l*l+c*c+f*f;0===d?f=1:(l*=d=1/Math.sqrt(d),c*=d,f*=d);var g=o*f-u*c,v=u*l-h*f,p=h*c-o*l;0==(d=g*g+v*v+p*p)&&(u?h+=1e-6:o?u+=1e-6:o+=1e-6,d=(g=o*f-u*c)*g+(v=u*l-h*f)*v+(p=h*c-o*l)*p),g*=d=1/Math.sqrt(d),v*=d,p*=d,t[0]=g,t[1]=v,t[2]=p,t[3]=0,t[4]=c*p-f*v,t[5]=f*g-l*p,t[6]=l*v-c*g,t[7]=0,t[8]=l,t[9]=c,t[10]=f,t[11]=0,t[12]=n,t[13]=s,t[14]=a,t[15]=1}(this,t,e,i),this}},{key:\"determinant\",value:function(){return Z(this)}},{key:\"fromArray\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return this[0]=t[e],this[1]=t[e+1],this[2]=t[e+2],this[3]=t[e+3],this[4]=t[e+4],this[5]=t[e+5],this[6]=t[e+6],this[7]=t[e+7],this[8]=t[e+8],this[9]=t[e+9],this[10]=t[e+10],this[11]=t[e+11],this[12]=t[e+12],this[13]=t[e+13],this[14]=t[e+14],this[15]=t[e+15],this}},{key:\"toArray\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return t[e]=this[0],t[e+1]=this[1],t[e+2]=this[2],t[e+3]=this[3],t[e+4]=this[4],t[e+5]=this[5],t[e+6]=this[6],t[e+7]=this[7],t[e+8]=this[8],t[e+9]=this[9],t[e+10]=this[10],t[e+11]=this[11],t[e+12]=this[12],t[e+13]=this[13],t[e+14]=this[14],t[e+15]=this[15],t}}])}();var rt=new it,nt=function(t){function r(){var t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:n,a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:n,h=arguments.length>3&&void 0!==arguments[3]?arguments[3]:\"YXZ\";i(this,r),(t=e(this,r,[n,s,a])).order=h,t.onChange=function(){},t._target=t;var o=[\"0\",\"1\",\"2\"];return u(t,new Proxy(t,{set:function(t,e){var i=Reflect.set.apply(Reflect,arguments);return i&&o.includes(e)&&t.onChange(),i}}))}return h(r,v(Array)),n(r,[{key:\"x\",get:function(){return this[0]},set:function(t){this._target[0]=t,this.onChange()}},{key:\"y\",get:function(){return this[1]},set:function(t){this._target[1]=t,this.onChange()}},{key:\"z\",get:function(){return this[2]},set:function(t){this._target[2]=t,this.onChange()}},{key:\"set\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:t,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t;return t.length?this.copy(t):(this._target[0]=t,this._target[1]=e,this._target[2]=i,this.onChange(),this)}},{key:\"copy\",value:function(t){return this._target[0]=t[0],this._target[1]=t[1],this._target[2]=t[2],this.onChange(),this}},{key:\"reorder\",value:function(t){return this._target.order=t,this.onChange(),this}},{key:\"fromRotationMatrix\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.order;return function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:\"YXZ\";\"XYZ\"===i?(t[1]=Math.asin(Math.min(Math.max(e[8],-1),1)),Math.abs(e[8])<.99999?(t[0]=Math.atan2(-e[9],e[10]),t[2]=Math.atan2(-e[4],e[0])):(t[0]=Math.atan2(e[6],e[5]),t[2]=0)):\"YXZ\"===i?(t[0]=Math.asin(-Math.min(Math.max(e[9],-1),1)),Math.abs(e[9])<.99999?(t[1]=Math.atan2(e[8],e[10]),t[2]=Math.atan2(e[1],e[5])):(t[1]=Math.atan2(-e[2],e[0]),t[2]=0)):\"ZXY\"===i?(t[0]=Math.asin(Math.min(Math.max(e[6],-1),1)),Math.abs(e[6])<.99999?(t[1]=Math.atan2(-e[2],e[10]),t[2]=Math.atan2(-e[4],e[5])):(t[1]=0,t[2]=Math.atan2(e[1],e[0]))):\"ZYX\"===i?(t[1]=Math.asin(-Math.min(Math.max(e[2],-1),1)),Math.abs(e[2])<.99999?(t[0]=Math.atan2(e[6],e[10]),t[2]=Math.atan2(e[1],e[0])):(t[0]=0,t[2]=Math.atan2(-e[4],e[5]))):\"YZX\"===i?(t[2]=Math.asin(Math.min(Math.max(e[1],-1),1)),Math.abs(e[1])<.99999?(t[0]=Math.atan2(-e[9],e[5]),t[1]=Math.atan2(-e[2],e[0])):(t[0]=0,t[1]=Math.atan2(e[8],e[10]))):\"XZY\"===i&&(t[2]=Math.asin(-Math.min(Math.max(e[4],-1),1)),Math.abs(e[4])<.99999?(t[0]=Math.atan2(e[6],e[5]),t[1]=Math.atan2(e[8],e[0])):(t[0]=Math.atan2(-e[9],e[10]),t[1]=0))}(this._target,t,e),this.onChange(),this}},{key:\"fromQuaternion\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.order,i=arguments.length>2?arguments[2]:void 0;return rt.fromQuaternion(t),this._target.fromRotationMatrix(rt,e),i||this.onChange(),this}},{key:\"fromArray\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return this._target[0]=t[e],this._target[1]=t[e+1],this._target[2]=t[e+2],this}},{key:\"toArray\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return t[e]=this[0],t[e+1]=this[1],t[e+2]=this[2],t}}])}(),st=function(){return n((function t(){var e=this;i(this,t),this.parent=null,this.children=[],this.visible=!0,this.matrix=new it,this.worldMatrix=new it,this.matrixAutoUpdate=!0,this.worldMatrixNeedsUpdate=!1,this.position=new R,this.quaternion=new Y,this.scale=new R(1),this.rotation=new nt,this.up=new R(0,1,0),this.rotation._target.onChange=function(){return e.quaternion.fromEuler(e.rotation,!0)},this.quaternion._target.onChange=function(){return e.rotation.fromQuaternion(e.quaternion,void 0,!0)}}),[{key:\"setParent\",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];this.parent&&t!==this.parent&&this.parent.removeChild(this,!1),this.parent=t,e&&t&&t.addChild(this,!1)}},{key:\"addChild\",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];~this.children.indexOf(t)||this.children.push(t),e&&t.setParent(this,!1)}},{key:\"removeChild\",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];~this.children.indexOf(t)&&this.children.splice(this.children.indexOf(t),1),e&&t.setParent(null,!1)}},{key:\"updateMatrixWorld\",value:function(t){this.matrixAutoUpdate&&this.updateMatrix(),(this.worldMatrixNeedsUpdate||t)&&(null===this.parent?this.worldMatrix.copy(this.matrix):this.worldMatrix.multiply(this.parent.worldMatrix,this.matrix),this.worldMatrixNeedsUpdate=!1,t=!0);for(var e=0,i=this.children.length;e<i;e++)this.children[e].updateMatrixWorld(t)}},{key:\"updateMatrix\",value:function(){this.matrix.compose(this.quaternion,this.position,this.scale),this.worldMatrixNeedsUpdate=!0}},{key:\"traverse\",value:function(t){if(!t(this))for(var e=0,i=this.children.length;e<i;e++)this.children[e].traverse(t)}},{key:\"decompose\",value:function(){this.matrix.decompose(this.quaternion._target,this.position,this.scale),this.rotation.fromQuaternion(this.quaternion)}},{key:\"lookAt\",value:function(t){arguments.length>1&&void 0!==arguments[1]&&arguments[1]?this.matrix.lookAt(this.position,t,this.up):this.matrix.lookAt(t,this.position,this.up),this.matrix.getRotation(this.quaternion._target),this.rotation.fromQuaternion(this.quaternion)}}])}(),at=new it,ht=new R,ot=new R,ut=function(t){function r(t){var n,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},a=s.near,h=void 0===a?.1:a,o=s.far,u=void 0===o?100:o,l=s.fov,c=void 0===l?45:l,f=s.aspect,d=void 0===f?1:f,g=s.left,v=s.right,p=s.bottom,m=s.top,y=s.zoom,_=void 0===y?1:y;return i(this,r),n=e(this,r),Object.assign(n,{near:h,far:u,fov:c,aspect:d,left:g,right:v,bottom:p,top:m,zoom:_}),n.projectionMatrix=new it,n.viewMatrix=new it,n.projectionViewMatrix=new it,n.worldPosition=new R,n.type=g||v?\"orthographic\":\"perspective\",\"orthographic\"===n.type?n.orthographic():n.perspective(),n}return h(r,st),n(r,[{key:\"perspective\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.near,i=void 0===e?this.near:e,r=t.far,n=void 0===r?this.far:r,s=t.fov,a=void 0===s?this.fov:s,h=t.aspect,o=void 0===h?this.aspect:h;return Object.assign(this,{near:i,far:n,fov:a,aspect:o}),this.projectionMatrix.fromPerspective({fov:a*(Math.PI/180),aspect:o,near:i,far:n}),this.type=\"perspective\",this}},{key:\"orthographic\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.near,i=void 0===e?this.near:e,r=t.far,n=void 0===r?this.far:r,s=t.left,a=void 0===s?this.left||-1:s,h=t.right,o=void 0===h?this.right||1:h,u=t.bottom,l=void 0===u?this.bottom||-1:u,c=t.top,f=void 0===c?this.top||1:c,d=t.zoom,g=void 0===d?this.zoom:d;return Object.assign(this,{near:i,far:n,left:a,right:o,bottom:l,top:f,zoom:g}),a/=g,o/=g,l/=g,f/=g,this.projectionMatrix.fromOrthogonal({left:a,right:o,bottom:l,top:f,near:i,far:n}),this.type=\"orthographic\",this}},{key:\"updateMatrixWorld\",value:function(){return s(a(r.prototype),\"updateMatrixWorld\",this).call(this),this.viewMatrix.inverse(this.worldMatrix),this.worldMatrix.getTranslation(this.worldPosition),this.projectionViewMatrix.multiply(this.projectionMatrix,this.viewMatrix),this}},{key:\"lookAt\",value:function(t){return s(a(r.prototype),\"lookAt\",this).call(this,t,!0),this}},{key:\"project\",value:function(t){return t.applyMatrix4(this.viewMatrix),t.applyMatrix4(this.projectionMatrix),this}},{key:\"unproject\",value:function(t){return t.applyMatrix4(at.inverse(this.projectionMatrix)),t.applyMatrix4(this.worldMatrix),this}},{key:\"updateFrustum\",value:function(){this.frustum||(this.frustum=[new R,new R,new R,new R,new R,new R]);var t=this.projectionViewMatrix;this.frustum[0].set(t[3]-t[0],t[7]-t[4],t[11]-t[8]).constant=t[15]-t[12],this.frustum[1].set(t[3]+t[0],t[7]+t[4],t[11]+t[8]).constant=t[15]+t[12],this.frustum[2].set(t[3]+t[1],t[7]+t[5],t[11]+t[9]).constant=t[15]+t[13],this.frustum[3].set(t[3]-t[1],t[7]-t[5],t[11]-t[9]).constant=t[15]-t[13],this.frustum[4].set(t[3]-t[2],t[7]-t[6],t[11]-t[10]).constant=t[15]-t[14],this.frustum[5].set(t[3]+t[2],t[7]+t[6],t[11]+t[10]).constant=t[15]+t[14];for(var e=0;e<6;e++){var i=1/this.frustum[e].distance();this.frustum[e].multiply(i),this.frustum[e].constant*=i}}},{key:\"frustumIntersectsMesh\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:t.worldMatrix;if(!t.geometry.attributes.position)return!0;if(t.geometry.bounds&&t.geometry.bounds.radius!==1/0||t.geometry.computeBoundingSphere(),!t.geometry.bounds)return!0;var i=ht;i.copy(t.geometry.bounds.center),i.applyMatrix4(e);var r=t.geometry.bounds.radius*e.getMaxScaleOnAxis();return this.frustumIntersectsSphere(i,r)}},{key:\"frustumIntersectsSphere\",value:function(t,e){for(var i=ot,r=0;r<6;r++){var n=this.frustum[r];if(i.copy(n).dot(t)+n.constant<-e)return!1}return!0}}])}();function lt(t,e,i){var r=e[0],n=e[1],s=e[2],a=e[3],h=e[4],o=e[5],u=e[6],l=e[7],c=e[8],f=i[0],d=i[1],g=i[2],v=i[3],p=i[4],m=i[5],y=i[6],_=i[7],b=i[8];return t[0]=f*r+d*a+g*u,t[1]=f*n+d*h+g*l,t[2]=f*s+d*o+g*c,t[3]=v*r+p*a+m*u,t[4]=v*n+p*h+m*l,t[5]=v*s+p*o+m*c,t[6]=y*r+_*a+b*u,t[7]=y*n+_*h+b*l,t[8]=y*s+_*o+b*c,t}var ct=function(t){function r(){var t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,h=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:1,l=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0,c=arguments.length>6&&void 0!==arguments[6]?arguments[6]:0,f=arguments.length>7&&void 0!==arguments[7]?arguments[7]:0,d=arguments.length>8&&void 0!==arguments[8]?arguments[8]:1;return i(this,r),u(t=e(this,r,[n,s,a,h,o,l,c,f,d]),t)}return h(r,v(Array)),n(r,[{key:\"set\",value:function(t,e,i,r,n,s,a,h,o){return t.length?this.copy(t):(function(t,e,i,r,n,s,a,h,o,u){t[0]=e,t[1]=i,t[2]=r,t[3]=n,t[4]=s,t[5]=a,t[6]=h,t[7]=o,t[8]=u}(this,t,e,i,r,n,s,a,h,o),this)}},{key:\"translate\",value:function(t){return function(t,e,i){var r=e[0],n=e[1],s=e[2],a=e[3],h=e[4],o=e[5],u=e[6],l=e[7],c=e[8],f=i[0],d=i[1];t[0]=r,t[1]=n,t[2]=s,t[3]=a,t[4]=h,t[5]=o,t[6]=f*r+d*a+u,t[7]=f*n+d*h+l,t[8]=f*s+d*o+c}(this,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,t),this}},{key:\"rotate\",value:function(t){var e,i,r,n,s,a,h,o,u,l,c,f,d,g;return e=this,r=t,n=(i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this)[0],s=i[1],a=i[2],h=i[3],o=i[4],u=i[5],l=i[6],c=i[7],f=i[8],d=Math.sin(r),g=Math.cos(r),e[0]=g*n+d*h,e[1]=g*s+d*o,e[2]=g*a+d*u,e[3]=g*h-d*n,e[4]=g*o-d*s,e[5]=g*u-d*a,e[6]=l,e[7]=c,e[8]=f,this}},{key:\"scale\",value:function(t){return function(t,e,i){var r=i[0],n=i[1];t[0]=r*e[0],t[1]=r*e[1],t[2]=r*e[2],t[3]=n*e[3],t[4]=n*e[4],t[5]=n*e[5],t[6]=e[6],t[7]=e[7],t[8]=e[8]}(this,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,t),this}},{key:\"multiply\",value:function(t,e){return e?lt(this,t,e):lt(this,this,t),this}},{key:\"identity\",value:function(){var t;return(t=this)[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=0,t[7]=0,t[8]=1,this}},{key:\"copy\",value:function(t){var e,i;return i=t,(e=this)[0]=i[0],e[1]=i[1],e[2]=i[2],e[3]=i[3],e[4]=i[4],e[5]=i[5],e[6]=i[6],e[7]=i[7],e[8]=i[8],this}},{key:\"fromMatrix4\",value:function(t){var e,i;return i=t,(e=this)[0]=i[0],e[1]=i[1],e[2]=i[2],e[3]=i[4],e[4]=i[5],e[5]=i[6],e[6]=i[8],e[7]=i[9],e[8]=i[10],this}},{key:\"fromQuaternion\",value:function(t){return function(t,e){var i=e[0],r=e[1],n=e[2],s=e[3],a=i+i,h=r+r,o=n+n,u=i*a,l=r*a,c=r*h,f=n*a,d=n*h,g=n*o,v=s*a,p=s*h,m=s*o;t[0]=1-c-g,t[3]=l-m,t[6]=f+p,t[1]=l+m,t[4]=1-u-g,t[7]=d-v,t[2]=f-p,t[5]=d+v,t[8]=1-u-c}(this,t),this}},{key:\"fromBasis\",value:function(t,e,i){return this.set(t[0],t[1],t[2],e[0],e[1],e[2],i[0],i[1],i[2]),this}},{key:\"inverse\",value:function(){var t,e,i,r,n,s,a,h,o,u,l,c,f,d,g;return t=this,i=(e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this)[0],r=e[1],n=e[2],s=e[3],a=e[4],h=e[5],o=e[6],u=e[7],l=e[8],(g=i*(c=l*a-h*u)+r*(f=-l*s+h*o)+n*(d=u*s-a*o))&&(g=1/g,t[0]=c*g,t[1]=(-l*r+n*u)*g,t[2]=(h*r-n*a)*g,t[3]=f*g,t[4]=(l*i-n*o)*g,t[5]=(-h*i+n*s)*g,t[6]=d*g,t[7]=(-u*i+r*o)*g,t[8]=(a*i-r*s)*g),this}},{key:\"getNormalMatrix\",value:function(t){var e,i,r,n,s,a,h,o,u,l,c,f,d,g,v,p,m,y,_,b,x,E,w,M,k,A,T,R,F,S,C;return e=this,r=(i=t)[0],n=i[1],s=i[2],a=i[3],h=i[4],o=i[5],u=i[6],l=i[7],c=i[8],f=i[9],d=i[10],g=i[11],v=i[12],p=i[13],m=i[14],y=i[15],(C=(_=r*o-n*h)*(S=d*y-g*m)-(b=r*u-s*h)*(F=f*y-g*p)+(x=r*l-a*h)*(R=f*m-d*p)+(E=n*u-s*o)*(T=c*y-g*v)-(w=n*l-a*o)*(A=c*m-d*v)+(M=s*l-a*u)*(k=c*p-f*v))&&(C=1/C,e[0]=(o*S-u*F+l*R)*C,e[1]=(u*T-h*S-l*A)*C,e[2]=(h*F-o*T+l*k)*C,e[3]=(s*F-n*S-a*R)*C,e[4]=(r*S-s*T+a*A)*C,e[5]=(n*T-r*F-a*k)*C,e[6]=(p*M-m*w+y*E)*C,e[7]=(m*x-v*M-y*b)*C,e[8]=(v*w-p*x+y*_)*C),this}}])}(),ft=0,dt=function(t){function r(t){var n,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},a=s.geometry,h=s.program,o=s.mode,u=void 0===o?t.TRIANGLES:o,l=s.frustumCulled,c=void 0===l||l,f=s.renderOrder,d=void 0===f?0:f;return i(this,r),n=e(this,r),t.canvas,n.gl=t,n.id=ft++,n.geometry=a,n.program=h,n.mode=u,n.frustumCulled=c,n.renderOrder=d,n.modelViewMatrix=new it,n.normalMatrix=new ct,n.beforeRenderCallbacks=[],n.afterRenderCallbacks=[],n}return h(r,st),n(r,[{key:\"onBeforeRender\",value:function(t){return this.beforeRenderCallbacks.push(t),this}},{key:\"onAfterRender\",value:function(t){return this.afterRenderCallbacks.push(t),this}},{key:\"draw\",value:function(){var t=this,e=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).camera;e&&(this.program.uniforms.modelMatrix||Object.assign(this.program.uniforms,{modelMatrix:{value:null},viewMatrix:{value:null},modelViewMatrix:{value:null},normalMatrix:{value:null},projectionMatrix:{value:null},cameraPosition:{value:null}}),this.program.uniforms.projectionMatrix.value=e.projectionMatrix,this.program.uniforms.cameraPosition.value=e.worldPosition,this.program.uniforms.viewMatrix.value=e.viewMatrix,this.modelViewMatrix.multiply(e.viewMatrix,this.worldMatrix),this.normalMatrix.getNormalMatrix(this.modelViewMatrix),this.program.uniforms.modelMatrix.value=this.worldMatrix,this.program.uniforms.modelViewMatrix.value=this.modelViewMatrix,this.program.uniforms.normalMatrix.value=this.normalMatrix),this.beforeRenderCallbacks.forEach((function(i){return i&&i({mesh:t,camera:e})}));var i=this.program.cullFace&&this.worldMatrix.determinant()<0;this.program.use({flipFaces:i}),this.geometry.draw({mode:this.mode,program:this.program}),this.afterRenderCallbacks.forEach((function(i){return i&&i({mesh:t,camera:e})}))}}])}(),gt=new Uint8Array(4);function vt(t){return!(t&t-1)}var pt=1,mt=function(){return n((function t(e){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=r.image,s=r.target,a=void 0===s?e.TEXTURE_2D:s,h=r.type,o=void 0===h?e.UNSIGNED_BYTE:h,u=r.format,l=void 0===u?e.RGBA:u,c=r.internalFormat,f=void 0===c?l:c,d=r.wrapS,g=void 0===d?e.CLAMP_TO_EDGE:d,v=r.wrapT,p=void 0===v?e.CLAMP_TO_EDGE:v,m=r.generateMipmaps,y=void 0===m||m,_=r.minFilter,b=void 0===_?y?e.NEAREST_MIPMAP_LINEAR:e.LINEAR:_,x=r.magFilter,E=void 0===x?e.LINEAR:x,w=r.premultiplyAlpha,M=void 0!==w&&w,k=r.unpackAlignment,A=void 0===k?4:k,T=r.flipY,R=void 0===T?a==e.TEXTURE_2D:T,F=r.anisotropy,S=void 0===F?0:F,C=r.level,P=void 0===C?0:C,O=r.width,B=r.height,N=void 0===B?O:B;i(this,t),this.gl=e,this.id=pt++,this.image=n,this.target=a,this.type=o,this.format=l,this.internalFormat=f,this.minFilter=b,this.magFilter=E,this.wrapS=g,this.wrapT=p,this.generateMipmaps=y,this.premultiplyAlpha=M,this.unpackAlignment=A,this.flipY=R,this.anisotropy=Math.min(S,this.gl.renderer.parameters.maxAnisotropy),this.level=P,this.width=O,this.height=N,this.texture=this.gl.createTexture(),this.store={image:null},this.glState=this.gl.renderer.state,this.state={},this.state.minFilter=this.gl.NEAREST_MIPMAP_LINEAR,this.state.magFilter=this.gl.LINEAR,this.state.wrapS=this.gl.REPEAT,this.state.wrapT=this.gl.REPEAT,this.state.anisotropy=0}),[{key:\"bind\",value:function(){this.glState.textureUnits[this.glState.activeTextureUnit]!==this.id&&(this.gl.bindTexture(this.target,this.texture),this.glState.textureUnits[this.glState.activeTextureUnit]=this.id)}},{key:\"update\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=!(this.image===this.store.image&&!this.needsUpdate);if((e||this.glState.textureUnits[t]!==this.id)&&(this.gl.renderer.activeTexture(t),this.bind()),e){if(this.needsUpdate=!1,this.flipY!==this.glState.flipY&&(this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,this.flipY),this.glState.flipY=this.flipY),this.premultiplyAlpha!==this.glState.premultiplyAlpha&&(this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL,this.premultiplyAlpha),this.glState.premultiplyAlpha=this.premultiplyAlpha),this.unpackAlignment!==this.glState.unpackAlignment&&(this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,this.unpackAlignment),this.glState.unpackAlignment=this.unpackAlignment),this.minFilter!==this.state.minFilter&&(this.gl.texParameteri(this.target,this.gl.TEXTURE_MIN_FILTER,this.minFilter),this.state.minFilter=this.minFilter),this.magFilter!==this.state.magFilter&&(this.gl.texParameteri(this.target,this.gl.TEXTURE_MAG_FILTER,this.magFilter),this.state.magFilter=this.magFilter),this.wrapS!==this.state.wrapS&&(this.gl.texParameteri(this.target,this.gl.TEXTURE_WRAP_S,this.wrapS),this.state.wrapS=this.wrapS),this.wrapT!==this.state.wrapT&&(this.gl.texParameteri(this.target,this.gl.TEXTURE_WRAP_T,this.wrapT),this.state.wrapT=this.wrapT),this.anisotropy&&this.anisotropy!==this.state.anisotropy&&(this.gl.texParameterf(this.target,this.gl.renderer.getExtension(\"EXT_texture_filter_anisotropic\").TEXTURE_MAX_ANISOTROPY_EXT,this.anisotropy),this.state.anisotropy=this.anisotropy),this.image){if(this.image.width&&(this.width=this.image.width,this.height=this.image.height),this.target===this.gl.TEXTURE_CUBE_MAP)for(var i=0;i<6;i++)this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X+i,this.level,this.internalFormat,this.format,this.type,this.image[i]);else if(ArrayBuffer.isView(this.image))this.gl.texImage2D(this.target,this.level,this.internalFormat,this.width,this.height,0,this.format,this.type,this.image);else if(this.image.isCompressedTexture)for(var r=0;r<this.image.length;r++)this.gl.compressedTexImage2D(this.target,r,this.internalFormat,this.image[r].width,this.image[r].height,0,this.image[r].data);else this.gl.texImage2D(this.target,this.level,this.internalFormat,this.format,this.type,this.image);this.generateMipmaps&&(this.gl.renderer.isWebgl2||vt(this.image.width)&&vt(this.image.height)?this.gl.generateMipmap(this.target):(this.generateMipmaps=!1,this.wrapS=this.wrapT=this.gl.CLAMP_TO_EDGE,this.minFilter=this.gl.LINEAR)),this.onUpdate&&this.onUpdate()}else if(this.target===this.gl.TEXTURE_CUBE_MAP)for(var n=0;n<6;n++)this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X+n,0,this.gl.RGBA,1,1,0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,gt);else this.width?this.gl.texImage2D(this.target,this.level,this.internalFormat,this.width,this.height,0,this.format,this.type,null):this.gl.texImage2D(this.target,0,this.gl.RGBA,1,1,0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,gt);this.store.image=this.image}}}])}(),yt=function(){return n((function t(e){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=r.width,s=void 0===n?e.canvas.width:n,a=r.height,h=void 0===a?e.canvas.height:a,o=r.target,u=void 0===o?e.FRAMEBUFFER:o,l=r.color,c=void 0===l?1:l,f=r.depth,d=void 0===f||f,g=r.stencil,v=void 0!==g&&g,p=r.depthTexture,m=void 0!==p&&p,y=r.wrapS,_=void 0===y?e.CLAMP_TO_EDGE:y,b=r.wrapT,x=void 0===b?e.CLAMP_TO_EDGE:b,E=r.minFilter,w=void 0===E?e.LINEAR:E,M=r.magFilter,k=void 0===M?w:M,A=r.type,T=void 0===A?e.UNSIGNED_BYTE:A,R=r.format,F=void 0===R?e.RGBA:R,S=r.internalFormat,C=void 0===S?F:S,P=r.unpackAlignment,O=r.premultiplyAlpha;i(this,t),this.gl=e,this.width=s,this.height=h,this.depth=d,this.buffer=this.gl.createFramebuffer(),this.target=u,this.gl.renderer.bindFramebuffer(this),this.textures=[];for(var B=[],N=0;N<c;N++)this.textures.push(new mt(e,{width:s,height:h,wrapS:_,wrapT:x,minFilter:w,magFilter:k,type:T,format:F,internalFormat:C,unpackAlignment:P,premultiplyAlpha:O,flipY:!1,generateMipmaps:!1})),this.textures[N].update(),this.gl.framebufferTexture2D(this.target,this.gl.COLOR_ATTACHMENT0+N,this.gl.TEXTURE_2D,this.textures[N].texture,0),B.push(this.gl.COLOR_ATTACHMENT0+N);B.length>1&&this.gl.renderer.drawBuffers(B),this.texture=this.textures[0],m&&(this.gl.renderer.isWebgl2||this.gl.renderer.getExtension(\"WEBGL_depth_texture\"))?(this.depthTexture=new mt(e,{width:s,height:h,minFilter:this.gl.NEAREST,magFilter:this.gl.NEAREST,format:this.gl.DEPTH_COMPONENT,internalFormat:e.renderer.isWebgl2?this.gl.DEPTH_COMPONENT16:this.gl.DEPTH_COMPONENT,type:this.gl.UNSIGNED_INT}),this.depthTexture.update(),this.gl.framebufferTexture2D(this.target,this.gl.DEPTH_ATTACHMENT,this.gl.TEXTURE_2D,this.depthTexture.texture,0)):(d&&!v&&(this.depthBuffer=this.gl.createRenderbuffer(),this.gl.bindRenderbuffer(this.gl.RENDERBUFFER,this.depthBuffer),this.gl.renderbufferStorage(this.gl.RENDERBUFFER,this.gl.DEPTH_COMPONENT16,s,h),this.gl.framebufferRenderbuffer(this.target,this.gl.DEPTH_ATTACHMENT,this.gl.RENDERBUFFER,this.depthBuffer)),v&&!d&&(this.stencilBuffer=this.gl.createRenderbuffer(),this.gl.bindRenderbuffer(this.gl.RENDERBUFFER,this.stencilBuffer),this.gl.renderbufferStorage(this.gl.RENDERBUFFER,this.gl.STENCIL_INDEX8,s,h),this.gl.framebufferRenderbuffer(this.target,this.gl.STENCIL_ATTACHMENT,this.gl.RENDERBUFFER,this.stencilBuffer)),d&&v&&(this.depthStencilBuffer=this.gl.createRenderbuffer(),this.gl.bindRenderbuffer(this.gl.RENDERBUFFER,this.depthStencilBuffer),this.gl.renderbufferStorage(this.gl.RENDERBUFFER,this.gl.DEPTH_STENCIL,s,h),this.gl.framebufferRenderbuffer(this.target,this.gl.DEPTH_STENCIL_ATTACHMENT,this.gl.RENDERBUFFER,this.depthStencilBuffer))),this.gl.renderer.bindFramebuffer({target:this.target})}),[{key:\"setSize\",value:function(t,e){if(this.width!==t||this.height!==e){this.width=t,this.height=e,this.gl.renderer.bindFramebuffer(this);for(var i=0;i<this.textures.length;i++)this.textures[i].width=t,this.textures[i].height=e,this.textures[i].needsUpdate=!0,this.textures[i].update(),this.gl.framebufferTexture2D(this.target,this.gl.COLOR_ATTACHMENT0+i,this.gl.TEXTURE_2D,this.textures[i].texture,0);this.depthTexture?(this.depthTexture.width=t,this.depthTexture.height=e,this.depthTexture.needsUpdate=!0,this.depthTexture.update(),this.gl.framebufferTexture2D(this.target,this.gl.DEPTH_ATTACHMENT,this.gl.TEXTURE_2D,this.depthTexture.texture,0)):(this.depthBuffer&&(this.gl.bindRenderbuffer(this.gl.RENDERBUFFER,this.depthBuffer),this.gl.renderbufferStorage(this.gl.RENDERBUFFER,this.gl.DEPTH_COMPONENT16,t,e)),this.stencilBuffer&&(this.gl.bindRenderbuffer(this.gl.RENDERBUFFER,this.stencilBuffer),this.gl.renderbufferStorage(this.gl.RENDERBUFFER,this.gl.STENCIL_INDEX8,t,e)),this.depthStencilBuffer&&(this.gl.bindRenderbuffer(this.gl.RENDERBUFFER,this.depthStencilBuffer),this.gl.renderbufferStorage(this.gl.RENDERBUFFER,this.gl.DEPTH_STENCIL,t,e))),this.gl.renderer.bindFramebuffer({target:this.target})}}}])}(),_t={black:\"#000000\",white:\"#ffffff\",red:\"#ff0000\",green:\"#00ff00\",blue:\"#0000ff\",fuchsia:\"#ff00ff\",cyan:\"#00ffff\",yellow:\"#ffff00\",orange:\"#ff8000\"};function bt(t){4===t.length&&(t=t[0]+t[1]+t[1]+t[2]+t[2]+t[3]+t[3]);var e=/^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(t);return[parseInt(e[1],16)/255,parseInt(e[2],16)/255,parseInt(e[3],16)/255]}function xt(t){return[((t=parseInt(t))>>16&255)/255,(t>>8&255)/255,(255&t)/255]}function Et(t){return void 0===t?[0,0,0]:3===arguments.length?arguments:isNaN(t)?\"#\"===t[0]?bt(t):_t[t.toLowerCase()]?bt(_t[t.toLowerCase()]):[0,0,0]:xt(t)}var wt=Object.freeze({__proto__:null,hexToRGB:bt,numberToRGB:xt,parseColor:Et}),Mt=function(t){function r(t){var n;return i(this,r),Array.isArray(t)?u(n,n=e(this,r,c(t))):u(n,n=e(this,r,c(Et.apply(wt,arguments))))}return h(r,v(Array)),n(r,[{key:\"r\",get:function(){return this[0]},set:function(t){this[0]=t}},{key:\"g\",get:function(){return this[1]},set:function(t){this[1]=t}},{key:\"b\",get:function(){return this[2]},set:function(t){this[2]=t}},{key:\"set\",value:function(t){return Array.isArray(t)?this.copy(t):this.copy(Et.apply(wt,arguments))}},{key:\"copy\",value:function(t){return this[0]=t[0],this[1]=t[1],this[2]=t[2],this}}])}();function kt(t,e,i){return t[0]=e[0]+i[0],t[1]=e[1]+i[1],t}function At(t,e,i){return t[0]=e[0]-i[0],t[1]=e[1]-i[1],t}function Tt(t,e,i){return t[0]=e[0]*i,t[1]=e[1]*i,t}function Rt(t){var e=t[0],i=t[1];return Math.sqrt(e*e+i*i)}function Ft(t,e){return t[0]*e[1]-t[1]*e[0]}var St=function(t){function r(){var t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:n;return i(this,r),u(t=e(this,r,[n,s]),t)}return h(r,v(Array)),n(r,[{key:\"x\",get:function(){return this[0]},set:function(t){this[0]=t}},{key:\"y\",get:function(){return this[1]},set:function(t){this[1]=t}},{key:\"set\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:t;return t.length?this.copy(t):(function(t,e,i){t[0]=e,t[1]=i}(this,t,e),this)}},{key:\"copy\",value:function(t){var e,i;return i=t,(e=this)[0]=i[0],e[1]=i[1],this}},{key:\"add\",value:function(t,e){return e?kt(this,t,e):kt(this,this,t),this}},{key:\"sub\",value:function(t,e){return e?At(this,t,e):At(this,this,t),this}},{key:\"multiply\",value:function(t){var e,i,r;return t.length?(i=this,r=t,(e=this)[0]=i[0]*r[0],e[1]=i[1]*r[1]):Tt(this,this,t),this}},{key:\"divide\",value:function(t){var e,i,r;return t.length?(i=this,r=t,(e=this)[0]=i[0]/r[0],e[1]=i[1]/r[1]):Tt(this,this,1/t),this}},{key:\"inverse\",value:function(){var t,e;return e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this,(t=this)[0]=1/e[0],t[1]=1/e[1],this}},{key:\"len\",value:function(){return Rt(this)}},{key:\"distance\",value:function(t){return t?(e=this,r=(i=t)[0]-e[0],n=i[1]-e[1],Math.sqrt(r*r+n*n)):Rt(this);var e,i,r,n}},{key:\"squaredLen\",value:function(){return this.squaredDistance()}},{key:\"squaredDistance\",value:function(t){return t?(e=this,r=(i=t)[0]-e[0],n=i[1]-e[1],r*r+n*n):function(t){var e=t[0],i=t[1];return e*e+i*i}(this);var e,i,r,n}},{key:\"negate\",value:function(){var t,e;return e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this,(t=this)[0]=-e[0],t[1]=-e[1],this}},{key:\"cross\",value:function(t,e){return e?Ft(t,e):Ft(this,t)}},{key:\"scale\",value:function(t){return Tt(this,this,t),this}},{key:\"normalize\",value:function(){var t,e,i,r,n;return t=this,i=(e=this)[0],r=e[1],(n=i*i+r*r)>0&&(n=1/Math.sqrt(n)),t[0]=e[0]*n,t[1]=e[1]*n,this}},{key:\"dot\",value:function(t){return i=t,(e=this)[0]*i[0]+e[1]*i[1];var e,i}},{key:\"equals\",value:function(t){return i=t,(e=this)[0]===i[0]&&e[1]===i[1];var e,i}},{key:\"applyMatrix3\",value:function(t){var e,i,r,n,s;return e=this,r=t,n=(i=this)[0],s=i[1],e[0]=r[0]*n+r[3]*s+r[6],e[1]=r[1]*n+r[4]*s+r[7],this}},{key:\"applyMatrix4\",value:function(t){var e,i,r,n,s;return e=this,r=t,n=(i=this)[0],s=i[1],e[0]=r[0]*n+r[4]*s+r[12],e[1]=r[1]*n+r[5]*s+r[13],this}},{key:\"lerp\",value:function(t,e){return function(t,e,i,r){var n=e[0],s=e[1];t[0]=n+r*(i[0]-n),t[1]=s+r*(i[1]-s)}(this,this,t,e),this}},{key:\"clone\",value:function(){return new r(this[0],this[1])}},{key:\"fromArray\",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return this[0]=t[e],this[1]=t[e+1],this}},{key:\"toArray\",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return t[e]=this[0],t[e+1]=this[1],t}}])}(),Ct=function(t){function r(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},s=n.width,a=void 0===s?1:s,h=n.height,o=void 0===h?1:h,u=n.widthSegments,l=void 0===u?1:u,c=n.heightSegments,f=void 0===c?1:c,d=n.attributes,g=void 0===d?{}:d;i(this,r);var v=l,p=f,m=(v+1)*(p+1),y=v*p*6,_=new Float32Array(3*m),b=new Float32Array(3*m),x=new Float32Array(2*m),E=y>65536?new Uint32Array(y):new Uint16Array(y);return r.buildPlane(_,b,x,E,a,o,0,v,p),Object.assign(g,{position:{size:3,data:_},normal:{size:3,data:b},uv:{size:2,data:x},index:{data:E}}),e(this,r,[t,g])}return h(r,O),n(r,null,[{key:\"buildPlane\",value:function(t,e,i,r,n,s,a,h,o){for(var u=arguments.length>9&&void 0!==arguments[9]?arguments[9]:0,l=arguments.length>10&&void 0!==arguments[10]?arguments[10]:1,c=arguments.length>11&&void 0!==arguments[11]?arguments[11]:2,f=arguments.length>12&&void 0!==arguments[12]?arguments[12]:1,d=arguments.length>13&&void 0!==arguments[13]?arguments[13]:-1,g=arguments.length>14&&void 0!==arguments[14]?arguments[14]:0,v=arguments.length>15&&void 0!==arguments[15]?arguments[15]:0,p=g,m=n/h,y=s/o,_=0;_<=o;_++)for(var b=_*y-s/2,x=0;x<=h;x++,g++){var E=x*m-n/2;if(t[3*g+u]=E*f,t[3*g+l]=b*d,t[3*g+c]=a/2,e[3*g+u]=0,e[3*g+l]=0,e[3*g+c]=a>=0?1:-1,i[2*g]=x/h,i[2*g+1]=1-_/o,_!==o&&x!==h){var w=p+x+_*(h+1),M=p+x+(_+1)*(h+1),k=p+x+(_+1)*(h+1)+1,A=p+x+_*(h+1)+1;r[6*v]=w,r[6*v+1]=M,r[6*v+2]=A,r[6*v+3]=M,r[6*v+4]=k,r[6*v+5]=A,v++}}}}])}();!function(){!function(t,e,i){var r,n=256,s=\"random\",a=i.pow(n,6),h=i.pow(2,52),o=2*h,u=n-1;function l(u,l,d){var m=[],y=v(g((l=1==l?{entropy:!0}:l||{}).entropy?[u,p(e)]:null==u?function(){try{var i;return r&&(i=r.randomBytes)?i=i(n):(i=new Uint8Array(n),(t.crypto||t.msCrypto).getRandomValues(i)),p(i)}catch(i){var s=t.navigator,a=s&&s.plugins;return[+new Date,t,a,t.screen,p(e)]}}():u,3),m),_=new c(m),b=function(){for(var t=_.g(6),e=a,i=0;t<h;)t=(t+i)*n,e*=n,i=_.g(1);for(;t>=o;)t/=2,e/=2,i>>>=1;return(t+i)/e};return b.int32=function(){return 0|_.g(4)},b.quick=function(){return _.g(4)/4294967296},b.double=b,v(p(_.S),e),(l.pass||d||function(t,e,r,n){return n&&(n.S&&f(n,_),t.state=function(){return f(_,{})}),r?(i[s]=t,e):t})(b,y,\"global\"in l?l.global:this==i,l.state)}function c(t){var e,i=t.length,r=this,s=0,a=r.i=r.j=0,h=r.S=[];for(i||(t=[i++]);s<n;)h[s]=s++;for(s=0;s<n;s++)h[s]=h[a=u&a+t[s%i]+(e=h[s])],h[a]=e;(r.g=function(t){for(var e,i=0,s=r.i,a=r.j,h=r.S;t--;)e=h[s=u&s+1],i=i*n+h[u&(h[s]=h[a=u&a+e])+(h[a]=e)];return r.i=s,r.j=a,i})(n)}function f(t,e){return e.i=t.i,e.j=t.j,e.S=t.S.slice(),e}function g(t,e){var i,r=[],n=d(t);if(e&&\"object\"==n)for(i in t)try{r.push(g(t[i],e-1))}catch(t){}return r.length?r:\"string\"==n?t:t+\"\\0\"}function v(t,e){for(var i,r=t+\"\",n=0;n<r.length;)e[u&n]=u&(i^=19*e[u&n])+r.charCodeAt(n++);return p(e)}function p(t){return String.fromCharCode.apply(0,t)}if(v(i.random(),e),\"object\"==(\"undefined\"==typeof module?\"undefined\":d(module))&&module.exports){module.exports=l;try{r=require(\"crypto\")}catch(t){}}else\"function\"==typeof define&&define.amd?define((function(){return l})):i[\"seed\"+s]=l}(\"undefined\"!=typeof self?self:this,[],Math)}();var Pt=function(){return n((function t(){var e,r,n,s=this,a=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},h=arguments.length>1?arguments[1]:void 0;i(this,t),e=this,n=function(){requestAnimationFrame(s._update),s.loop&&(s.frame++,s._animate()),s.gl.clearColor(0,0,0,1),s.renderer.render({scene:s.scene,camera:s.camera}),s.isRenderTarget&&(s.gl.clearColor(0,0,0,1),s.renderer.render({scene:s.rttPlane,camera:s.rttCamera,target:s.rtt}))},(r=f(r=\"_update\"))in e?Object.defineProperty(e,r,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[r]=n,this.params=a,this.options={},this.loop=a.loop||!1,this.colors_num=h,this.colors_init=a.colors||[],this.palette=[],this.colors(this.colors_init),this.seed=a.seed||1e3,this.rng=new Math.seedrandom(this.seed),this.frame=0,this.parentDom=a.dom?document.getElementById(a.dom):document.body,\"static\"===window.getComputedStyle(this.parentDom).position&&(this.parentDom.style.position=\"relative\");var o=this._getParentRect(this.parentDom);this.canvasW=o.w,this.canvasH=o.h,this.renderer=new j,this.renderer.setSize(this.canvasW,this.canvasH),this.gl=this.renderer.gl,this.gl.canvas.id=\"colorbgcanvas\",this.gl.canvas.style.position=\"absolute\",this.gl.canvas.style.top=0,this.gl.canvas.style.left=0,this.gl.canvas.style.zIndex=0,this.parentDom.appendChild(this.gl.canvas),this.camera=new ut(this.gl,{near:.1,far:10001,left:-this.canvasW/2,right:this.canvasW/2,bottom:-this.canvasH/2,top:this.canvasH/2,zoom:1}),this.camera.position.z=8e3,this.isRenderTarget=!1,this.scene=new st}),[{key:\"_getParentRect\",value:function(t){var e=t;return{w:e.getBoundingClientRect().width,h:e.getBoundingClientRect().height}}},{key:\"colors\",value:function(t){var e=!!this.palette.length;if(this.palette=[],0==t.length)this.palette=[\"#F00911\",\"#F3AA00\",\"#F6EE0B\",\"#39E90D\",\"#195ED2\",\"#F00911\"];else if(t.length<this.colors_num){for(var i=c(t),r=i.length;r<6;r++){var n=r%i.length;t.push(i[n])}this.palette=t}else for(var s=0;s<this.colors_num;s++)this.palette.push(t[s]);e&&this._resetColors()}},{key:\"start\",value:function(){this._size(),this._initRtt(),this._resetSeed(),this._makeMaterial(),this._make(),requestAnimationFrame(this._update)}},{key:\"resize\",value:function(){var t=this._getParentRect(this.parentDom);this.canvasW=t.w,this.canvasH=t.h,this.renderer.setSize(this.canvasW,this.canvasH),this.camera.orthographic({near:.1,far:10001,left:-this.canvasW/2,right:this.canvasW/2,bottom:-this.canvasH/2,top:this.canvasH/2,zoom:1}),this._size(),this.reset()}},{key:\"reset\",value:function(t){this.rng=t?new Math.seedrandom(t):new Math.seedrandom(this.seed),this._delete(),this._resetSeed(),this._make()}},{key:\"_delete\",value:function(){for(var t=this.scene.children.length-1;t>=0;t--)this.scene.removeChild(this.scene.children[t])}},{key:\"_size\",value:function(){}},{key:\"_initRtt\",value:function(){}},{key:\"_resetSeed\",value:function(){}},{key:\"_animate\",value:function(){}},{key:\"destroy\",value:function(){this._delete(),this.parentDom.removeChild(this.gl.canvas)}}])}(),Ot=function(t){function r(){var t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return i(this,r),(t=e(this,r,[n,4])).start(),t}return h(r,Pt),n(r,[{key:\"_initRtt\",value:function(){this.rtt=new yt(this.gl,{width:512,height:512}),this.rttCamera=new ut(this.gl,{left:-.5,right:.5,bottom:-.5,top:.5,zoom:1}),this.rttCamera.position.z=1,this.rttPlaneGeo=new Ct(this.gl,{}),this.rttProgram=new D(this.gl,{vertex:\"\\n                attribute vec3 position;\\n                attribute vec2 uv;\\n                uniform mat4 modelViewMatrix;\\n                uniform mat4 projectionMatrix;\\n                varying vec2 vUv;\\n                void main() {\\n                    vUv = uv;\\n                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\\n                }\\n            \",fragment:\"\\n                #ifdef GL_ES\\n                precision mediump float;\\n                #endif\\n        \\n                uniform float u_time;\\n                uniform vec2 u_resolution;\\n        \\n                uniform vec3 u_color_0;\\n                uniform vec3 u_color_1;\\n                uniform vec3 u_color_2;\\n                uniform vec3 u_color_3;\\n        \\n                uniform float u_scale;\\n                uniform float u_rand_1;\\n                uniform float u_rand_2;\\n        \\n                #define S(a,b,t) smoothstep(a,b,t)\\n        \\n                varying vec2 vUv;\\n        \\n                mat2 Rot(float a)\\n                {\\n                    float s = sin(a);\\n                    float c = cos(a);\\n                    return mat2(c, -s, s, c);\\n                }\\n        \\n                vec2 hash( vec2 p )\\n                {\\n                    p = vec2( dot(p,vec2(2127.1,81.17)), dot(p,vec2(1269.5,283.37)) );\\n                    return fract(sin(p)*43758.5453);\\n                }\\n        \\n                float noise( in vec2 p )\\n                {\\n                    vec2 i = floor( p );\\n                    vec2 f = fract( p );\\n                    \\n                    vec2 u = f*f*(3.0-2.0*f);\\n            \\n                    float n = mix( mix( dot( -1.0+2.0*hash( i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ), \\n                                        dot( -1.0+2.0*hash( i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x),\\n                                    mix( dot( -1.0+2.0*hash( i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ), \\n                                        dot( -1.0+2.0*hash( i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y);\\n                    return 0.5 + 0.5*n;\\n                }\\n        \\n        \\n                void main()\\n                {\\n                    vec2 uv = gl_FragCoord.xy/u_resolution.xy;\\n                    float ratio = u_resolution.x / u_resolution.y;\\n        \\n                    vec2 tuv = vUv;\\n                    tuv *= u_scale;\\n                    tuv -= .5;\\n        \\n                    // rotate with Noise\\n                    float degree = noise(vec2(u_time*.1, tuv.x*tuv.y));\\n        \\n                    tuv *= Rot( radians( ( degree - 0.5 ) * 720. + 180. ));\\n        \\n                    // Wave warp with sin\\n                    float frequency = 5.;\\n                    float amplitude = 130.;\\n                    float speed = 2.;\\n                    tuv.x += sin( tuv.y * frequency + speed ) / amplitude;\\n                    tuv.y += sin( tuv.x * frequency * 1.5 + speed ) / ( amplitude * .5 );\\n        \\n                    vec3 layer1 = mix( u_color_0, u_color_1, S( -0.3, .2, ( tuv * Rot( radians( u_rand_1 ))).x));\\n                    vec3 layer2 = mix( u_color_2, u_color_3, S( -0.3, .2, ( tuv * Rot( radians( u_rand_2 ))).x));\\n                    \\n                    vec3 finalComp = mix( layer1, layer2, S( 0.5, -0.3, tuv.y ) );\\n                    \\n                    gl_FragColor = vec4( finalComp, 1.0 );\\n                }\\n            \",uniforms:{u_time:{value:0},u_resolution:{value:new St(2*this.canvasW,2*this.canvasH)},u_color_0:{value:new Mt(this.palette[0])},u_color_1:{value:new Mt(this.palette[1])},u_color_2:{value:new Mt(this.palette[2])},u_color_3:{value:new Mt(this.palette[3])},u_scale:{value:1.3},u_rand_1:{value:0},u_rand_2:{value:0}}}),this.rttPlane=new dt(this.gl,{geometry:this.rttPlaneGeo,program:this.rttProgram}),this.isRenderTarget=!0}},{key:\"_resetSeed\",value:function(){this.rttProgram.uniforms.u_rand_1.value=200*this.rng()-100,this.rttProgram.uniforms.u_rand_2.value=200*this.rng()-100}},{key:\"_makeMaterial\",value:function(){this._planeShader=new D(this.gl,{vertex:\"\\n                attribute vec3 position;\\n                attribute vec2 uv;\\n                uniform mat4 modelViewMatrix;\\n                uniform mat4 projectionMatrix;\\n                varying vec2 vUv;\\n                void main() {\\n                    vUv = uv;\\n                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\\n                }\\n            \",fragment:\"\\n                precision highp float;\\n                uniform sampler2D tMap;\\n                uniform float uNoiseFactor;\\n                uniform float uTime;\\n\\n                float random(vec2 co) {\\n                    return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);\\n                }\\n\\n                varying vec2 vUv;\\n                \\n                void main() {\\n                    vec4 color = texture2D(tMap, vUv);\\n\\n                    float noise = (random(vUv) - 0.5) * uNoiseFactor;\\n                    color.rgb = color.rgb + color.rgb * noise;\\n\\n                    gl_FragColor = color;\\n                }\\n            \",uniforms:{tMap:{value:this.rtt.texture},uNoiseFactor:{value:.1},uTime:{value:0}}})}},{key:\"_make\",value:function(){var t=new Ct(this.gl,{width:this.canvasW,height:this.canvasH});new dt(this.gl,{geometry:t,program:this._planeShader}).setParent(this.scene)}},{key:\"_resetColors\",value:function(){this.rttProgram.uniforms.u_color_0.value=new Mt(this.palette[0]),this.rttProgram.uniforms.u_color_1.value=new Mt(this.palette[1]),this.rttProgram.uniforms.u_color_2.value=new Mt(this.palette[2]),this.rttProgram.uniforms.u_color_3.value=new Mt(this.palette[3])}},{key:\"_animate\",value:function(){this.rttProgram.uniforms.u_time.value=10*Math.sin(this.frame/200)}},{key:\"update\",value:function(t,e){if(\"noise\"===t)this._planeShader.uniforms.uNoiseFactor.value=parseFloat(e)}}])}();export{Ot as BlurGradientBg};\n"
  },
  {
    "path": "src/sandbox.tsx",
    "content": ""
  },
  {
    "path": "tailwind.config.ts",
    "content": "import type { Config } from \"tailwindcss\";\nconst svgToDataUri = require(\"mini-svg-data-uri\");\nconst colors = require(\"tailwindcss/colors\");\nconst {\n  default: flattenColorPalette,\n} = require(\"tailwindcss/lib/util/flattenColorPalette\");\n// import  fontFamily from \"tailwindcss/defaultTheme\"\nconst config = {\n  darkMode: [\"class\"],\n  content: [\n    \"./pages/**/*.{ts,tsx}\",\n    \"./components/**/*.{ts,tsx}\",\n    \"./app/**/*.{ts,tsx}\",\n    \"./src/**/*.{ts,tsx}\",\n    '**/*.{ts,tsx}'\n  ],\n  prefix: \"\",\n  theme: {\n    container: {\n      center: true,\n      padding: \"2rem\",\n      screens: {\n        \"2xl\": \"1400px\",\n      },\n    },\n    extend: {\n      colors: {\n        border: \"hsl(var(--border))\",\n        input: \"hsl(var(--input))\",\n        ring: \"hsl(var(--ring))\",\n        background: \"hsl(var(--background))\",\n        foreground: \"hsl(var(--foreground))\",\n        soft: \"hsl(var(--soft))\",\n        primary: {\n          DEFAULT: \"hsl(var(--primary))\",\n          foreground: \"hsl(var(--primary-foreground))\",\n        },\n        secondary: {\n          DEFAULT: \"hsl(var(--secondary))\",\n          foreground: \"hsl(var(--secondary-foreground))\",\n        },\n        destructive: {\n          DEFAULT: \"hsl(var(--destructive))\",\n          foreground: \"hsl(var(--destructive-foreground))\",\n        },\n        muted: {\n          DEFAULT: \"hsl(var(--muted))\",\n          foreground: \"hsl(var(--muted-foreground))\",\n        },\n        accent: {\n          DEFAULT: \"hsl(var(--accent))\",\n          foreground: \"hsl(var(--accent-foreground))\",\n        },\n        popover: {\n          DEFAULT: \"hsl(var(--popover))\",\n          foreground: \"hsl(var(--popover-foreground))\",\n        },\n        card: {\n          DEFAULT: \"hsl(var(--card))\",\n          foreground: \"hsl(var(--card-foreground))\",\n        },\n      },\n      borderRadius: {\n        lg: \"var(--radius)\",\n        md: \"calc(var(--radius) - 2px)\",\n        sm: \"calc(var(--radius) - 4px)\",\n      },\n      keyframes: {\n        marquee: {\n          from: { transform: \"translateX(0)\" },\n          to: { transform: \"translateX(calc(-100% - var(--gap)))\" },\n        },\n        \"marquee-vertical\": {\n          from: { transform: \"translateY(0)\" },\n          to: { transform: \"translateY(calc(-100% - var(--gap)))\" },\n        },\n        \"accordion-down\": {\n          from: { height: \"0\" },\n          to: { height: \"var(--radix-accordion-content-height)\" },\n        },\n        \"accordion-up\": {\n          from: { height: \"var(--radix-accordion-content-height)\" },\n          to: { height: \"0\" },\n        },\n        scroll: {\n          to: {\n            transform: \"translate(calc(-50% - 0.5rem))\",\n          },\n        },\n        \"highlight-animation\": {\n          '0%': { backgroundColor: 'transparent' },\n          '50%': { backgroundColor: 'aquamarine' },\n          '100%': { backgroundColor: 'transparent' },\n        },\n        \"spin-around\": {\n          \"0%\": {\n            transform: \"translateZ(0) rotate(0)\",\n          },\n          \"15%, 35%\": {\n            transform: \"translateZ(0) rotate(90deg)\",\n          },\n          \"65%, 85%\": {\n            transform: \"translateZ(0) rotate(270deg)\",\n          },\n          \"100%\": {\n            transform: \"translateZ(0) rotate(360deg)\",\n          },\n        },\n        slide: {\n          to: {\n            transform: \"translate(calc(100cqw - 100%), 0)\",\n          },\n        },\n      },\n      animation: {\n        marquee: \"marquee var(--duration) linear infinite\",\n        \"marquee-vertical\": \"marquee-vertical var(--duration) linear infinite\",\n        \"spin-around\": \"spin-around calc(var(--speed) * 2) infinite linear\",\n        slide: \"slide var(--speed) ease-in-out infinite alternate\",\n        \"accordion-down\": \"accordion-down 0.2s ease-out\",\n        \"accordion-up\": \"accordion-up 0.2s ease-out\",\n        scroll:\n          \"scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite\",\n        highlight: 'highlight-animation 0.1s',\n      },\n      // fontFamily: {\n      //   sans: [\"var(--font-sans)\", ...fontFamily.sans],\n      // },\n    },\n  },\n  plugins: [\n    addVariablesForColors,\n    function ({ matchUtilities, theme }: any) {\n      matchUtilities(\n        {\n          \"bg-grid\": (value: any) => ({\n            backgroundImage: `url(\"${svgToDataUri(\n              `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\" width=\"32\" height=\"32\" fill=\"none\" stroke=\"${value}\"><path d=\"M0 .5H31.5V32\"/></svg>`\n            )}\")`,\n          }),\n          \"bg-grid-small\": (value: any) => ({\n            backgroundImage: `url(\"${svgToDataUri(\n              `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\" width=\"8\" height=\"8\" fill=\"none\" stroke=\"${value}\"><path d=\"M0 .5H31.5V32\"/></svg>`\n            )}\")`,\n          }),\n          \"bg-dot\": (value: any) => ({\n            backgroundImage: `url(\"${svgToDataUri(\n              `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\" width=\"16\" height=\"16\" fill=\"none\"><circle fill=\"${value}\" id=\"pattern-circle\" cx=\"10\" cy=\"10\" r=\"1.6257413380501518\"></circle></svg>`\n            )}\")`,\n          }),\n        },\n        { values: flattenColorPalette(theme(\"backgroundColor\")), type: \"color\" }\n      );\n    },\n  ],\n} satisfies Config;\n\nfunction addVariablesForColors({ addBase, theme }: any) {\n  let allColors = flattenColorPalette(theme(\"colors\"));\n  let newVars = Object.fromEntries(\n    Object.entries(allColors).map(([key, val]) => [`--${key}`, val])\n  );\n\n  addBase({\n    \":root\": newVars,\n  });\n}\n\nexport default config;\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"plasmo/templates/tsconfig.base\",\n  \"exclude\": [\"node_modules\"],\n  \"include\": [\n    \".plasmo/index.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \"src/components/ui/**/*.ts\",\n    \"src/components/ui/**/*.tsx\",\n    \".next/types/**/*.ts\"\n  ],\n  \n  \"compilerOptions\": {\n    \"paths\": {\n      \"@*\": [\"./*\"]\n    },\n    \"baseUrl\": \".\",\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "vercel.json",
    "content": "{\n    \"functions\": {\n        \"app/api/**/*\": {\n            \"maxDuration\": 60\n        }\n    }\n}"
  }
]