[
  {
    "path": ".dockerignore",
    "content": "# Ignore node_modules — let Docker install fresh inside the image\nnode_modules\n\n# Ignore environment files (not needed inside build context)\n.env\n.env.*\n\n\n\n# OS-specific\n.DS_Store\n\n# Git stuff\n.git\n.gitignore\n\n\n\n\n# Build artifacts\n.next\ndist\nout\n\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [jnsahaj]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Smartphone (please complete the following information):**\n - Device: [e.g. iPhone6]\n - OS: [e.g. iOS8.1]\n - Browser [e.g. stock browser, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# bun lock\nbun.lock\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n.cursor\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# env files (can opt-in for committing if needed)\n.env\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n\n# build artifacts\npublic/r/themes\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "npm run lint\n"
  },
  {
    "path": ".prettierignore",
    "content": "# Builds\n.next/\ndist/\nbuild/\nout/\n\n# Dependencies\nnode_modules/\n\n# Coverage and tests\ncoverage/\n.nyc_output/\n\n# Misc\n.git/\n.github/\n.vscode/\n.cursor/\n.husky/\n\n# Package files\npnpm-lock.yaml\npackage-lock.json\nyarn.lock\n\n# Generated files\npublic/\nstubs/\ndrizzle/\n\n# Config files that should maintain their format\n*.config.js\n*.config.mjs\n*.config.cjs\n*.config.ts\n\n# Environment files\n.env*\n\n# Markdown files\n*.md "
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"trailingComma\": \"es5\",\n  \"tabWidth\": 2,\n  \"printWidth\": 100,\n  \"bracketSpacing\": true,\n  \"arrowParens\": \"always\",\n  \"endOfLine\": \"lf\",\n  \"plugins\": [\"prettier-plugin-tailwindcss\"],\n  \"pluginSearchDirs\": false\n} "
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to tweakcn.com\n\nThanks for your interest in contributing to tweakcn.com! We're excited to have you here.\n\nPlease take a moment to review this document before submitting your first pull request. We also strongly recommend checking open [Issues](https://github.com/jnsahaj/tweakcn/issues) and [Pull Requests](https://github.com/jnsahaj/tweakcn/pulls) to see if someone else is working on something similar.\n\nIf you need any help or want to discuss ideas, feel free to join our community on [Discord](https://discord.com/invite/Phs4u2NM3n).\n\n## About This Project\n\ntweakcn.com is a powerful Visual Theme Editor designed for Tailwind CSS & shadcn/ui components. Websites built with shadcn/ui often share a similar look; tweakcn helps you visually customize these components to make your projects stand out.\n\n## Project Structure\n\nThis repository contains the Next.js application for tweakcn.com. Here's a simplified overview of the project's directory structure:\n\n```\n├── actions/          # Next.js Server Actions\n├── app/\n    ├── (auth)/       # Authentication routes\n    ├── (legal)/      # Legal pages (privacy policy)\n    ├── api/          # Public API endpoints\n    ├── dashboard/    # User dashboard (saved themes)\n    ├── editor/       # Main theme editor route\n    ├── layout.tsx    # Root application layout\n    └── page.tsx      # Landing page route\n├── components/\n    ├── editor/       # Theme editor interface components\n    ├── examples/     # Demo components for theme previews\n    ├── home/         # Landing page components\n    └── ui/           # Base shadcn/ui components\n├── config/           # App configuration & default values\n├── db/               # Database schema & logic (Drizzle ORM)\n├── hooks/            # Custom React hooks\n├── lib/              # 3rd-party library integrations & helpers\n├── public/\n    └── r/            # Holds JSON files for the theme registry\n├── scripts/          # Utility scripts used during development\n├── store/            # Global state management (Zustand)\n└── utils/            # General utility functions and helpers\n```\n\n## How to Contribute\n\n### Non-Technical\n\nEven if you don't plan to write code, there are many ways to contribute:\n\n- **Create an Issue:** If you find a bug, have an idea for a new feature, or want to suggest an improvement, please [create an issue on GitHub](https://github.com/jnsahaj/tweakcn/issues). This helps us track and prioritize feedback.\n- **Spread the Word:** If you like tweakcn.com, please share it with your friends, colleagues, and on social media. Helping grow the community makes the tool better for everyone.\n- **Use tweakcn.com:** The best feedback comes from real-world usage! As you use the editor, if you encounter any issues or have ideas for improvement, please let us know by creating an issue or reaching out on [Discord](https://discord.com/invite/Phs4u2NM3n).\n\n### Prerequisites\n\n- Node.js 18+\n- npm / yarn / pnpm\n\n### Installation\n\n1.  **Fork the Repository:** Start by creating your own copy of the [tweakcn repository](https://github.com/jnsahaj/tweakcn) on GitHub. Click the \"Fork\" button in the top-right corner.\n\n2.  **Clone Your Fork:** Clone the repository you just forked to your local machine:\n\n    ```bash\n    git clone https://github.com/YOUR_USERNAME/tweakcn.git\n    cd tweakcn\n    ```\n\n    Replace `YOUR_USERNAME` with your actual GitHub username.\n\n3.  **Install Dependencies:** Install the necessary project dependencies:\n\n    ```bash\n    npm install\n    ```\n\n### Set up the development environment (follow closely)\n\n1.  **Configure Environment Variables:** \n\n    ```bash\n    cp .env.example .env.local # Copy the example environment file\n    ```\n    - Open the `.env.local` file and replace the placeholder values with your actual credentials obtained from the services.\n\n2.  **Apply Database Schema:** Push the database schema defined in `db/schema.ts` to your Neon database using Drizzle Kit:\n\n    ```bash\n    npx drizzle-kit push\n    ```\n\n    - _(Optional)_ You can view your database structure using Drizzle Studio by running `npx drizzle-kit studio`.\n\n3.  **Create a New Branch:** Before making changes, create a dedicated branch for your feature or bug fix:\n\n    ```bash\n    git checkout -b your-descriptive-branch-name\n    ```\n\n    (e.g., `feature/add-community-gallery`, `fix/login-button-style`)\n\n4.  **Start the Development Server:**\n\n    ```bash\n    npm run dev\n    ```\n\n5.  Open [http://localhost:3000](http://localhost:3000) in your browser.\n\nYou're now ready to start coding!\n\n### Troubleshooting Setup\n\nIf you encounter unexpected issues, especially after pulling new changes or related to database/auth setup, try resetting your local environment:\n\n1.  Stop the development server (Ctrl+C).\n\n2.  Delete the `node_modules` and `.next` directories:\n\n    ```bash\n    # On macOS / Linux:\n    rm -rf node_modules .next\n\n    # On Windows (PowerShell):\n    Remove-Item -Recurse -Force node_modules, .next\n    ```\n\n3.  Reinstall dependencies:\n\n    ```bash\n    npm install\n    ```\n\n4.  Re-run the database push command (optional, but good practice if schema might have changed):\n\n    ```bash\n    npx drizzle-kit push\n    ```\n\n5.  Restart the development server:\n\n    ```bash\n    npm run dev\n    ```\n\n## Submitting Your Changes (Pull Request Workflow)\n\nOnce you've made your changes and tested them locally, follow these steps to submit them for review:\n\n1.  **Stage Your Changes:** Add the files you've modified to the Git staging area.\n\n    ```bash\n    git add .\n    ```\n\n2.  **Commit Your Changes:** Commit your staged changes with a descriptive message that follows the **Conventional Commits** specification. This helps automate releases and makes the commit history easier to understand.\n\n    ```bash\n    git commit -m \"feat(editor): Add contrast checker component\"\n    ```\n\n    - **Format:** `type(scope): description` (e.g., `fix(auth): Correct GitHub redirect URL`, `docs(readme): Update setup instructions`).\n    - **Common Types:** `feat` (new feature), `fix` (bug fix), `docs` (documentation), `style` (code style), `chore` (build process, tooling).\n    - Refer to the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) for more details.\n\n3.  **Push to Your Fork:** Push your committed changes to the branch on your forked repository on GitHub.\n\n    ```bash\n    git push origin your-descriptive-branch-name\n    ```\n\n    Replace `your-descriptive-branch-name` with the actual name of your branch.\n\n4.  **Open a Pull Request (PR):**\n\n    - Go to the original [tweakcn repository](https://github.com/jnsahaj/tweakcn) on GitHub.\n    - You should see a prompt suggesting you create a Pull Request from your recently pushed branch. Click on it. If not, navigate to the \"Pull requests\" tab and click \"New pull request\".\n    - Ensure the base repository is `jnsahaj/tweakcn` and the base branch is `main` (or the appropriate target branch).\n    - Ensure the head repository is your fork and the compare branch is `your-descriptive-branch-name`.\n    - **Write a Clear Description:** Fill out the pull request template (if one exists). Provide a clear title and a detailed description of the changes you've made. Explain _why_ you made the changes and link to any relevant GitHub Issues (e.g., \"Closes #123\").\n\n5.  **Review Process:**\n\n    - Once submitted, maintainers will review your pull request.\n    - Maintainers may provide feedback or request changes directly on the pull request. Please address these comments by pushing further commits to your branch.\n    - Once approved, a maintainer will merge your changes into the main project.\n\nThank you for contributing!"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:20-alpine\n\nWORKDIR /app\n\nCOPY package*.json ./\nRUN npm install\n\nCOPY . .\n\nEXPOSE 3000\n\nCMD [\"npm\", \"run\", \"dev\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <h1>tweakcn.com</h1>\n</div>\n\n<div align=\"center\">\n  <a href=\"https://vercel.com/oss\">\n    <img alt=\"Vercel OSS Program\" src=\"https://vercel.com/oss/program-badge.svg\" />\n  </a>\n  <br />\n  <br />\n  <a href=\"https://discord.gg/Phs4u2NM3n\" target=\"_blank\">\n    <img alt=\"Discord\" src=\"https://img.shields.io/discord/1353416868769173576?style=for-the-badge&logo=discord&logoColor=%23ffffff\">\n  </a>\n  <img alt=\"GitHub Repo stars\" src=\"https://img.shields.io/github/stars/jnsahaj/tweakcn?style=for-the-badge&logo=github\">\n  <a href=\"https://x.com/iamsahaj_xyz\">\n    <img alt=\"X (formerly Twitter) URL\" src=\"https://img.shields.io/twitter/url?url=https%3A%2F%2Fx.com%2Fiamsahaj_xyz&style=for-the-badge&logo=x&label=%40iamsahaj_xyz&color=%2300000000\" />\n  </a>\n</div>\n\n<br />\n\n**[tweakcn](https://tweakcn.com)** is a powerful Visual Theme Editor for tailwind CSS & shadcn/ui components. It comes with Beautiful theme presets to get started, while aiming to offer advanced customisation for each aspect of your UI\n\n![tweakcn.com](public/og-image.v050725.png)\n\n## Motivation\n\nWebsites made with shadcn/ui famously look the same. tweakcn is a tool that helps you customize shadcn/ui components visually, to make your components stand-out. The goal is to build a platform where a user can discover endless customization options and then have the ability to put their own twist on it.\n\n## Current Features\n\nYou can find the full feature list here: https://tweakcn.com/#features\n\n## Run Locally\n\n**IMPORTANT: For contributions, please see [CONTRIBUTING.md](CONTRIBUTING.md).**\n\n### Prerequisites\n\n- Node.js 18+\n- npm / yarn / pnpm\n\n### Installation\n\n1. Clone the repository:\n\n```bash\ngit clone https://github.com/jnsahaj/tweakcn.git\ncd tweakcn\n```\n\n2. Install dependencies:\n\n```bash\nnpm install\n```\n\n3. Start the development server:\n\n```bash\nnpm run dev\n```\n\n4. Open [http://localhost:3000](http://localhost:3000) in your browser.\n\n## Contributors\n\n<a href=\"https://github.com/jnsahaj/tweakcn/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=jnsahaj/tweakcn\" />\n</a>\n\nMade with [contrib.rocks](https://contrib.rocks).\n\n### Interested in Contributing?\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n# Star History\n\n<p align=\"center\">\n  <a target=\"_blank\" href=\"https://star-history.com/#jnsahaj/tweakcn&Date\">\n    <picture>\n      <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=jnsahaj/tweakcn&type=Date&theme=dark\">\n      <img alt=\"GitHub Star History for jnsahaj/tweakcn\" src=\"https://api.star-history.com/svg?repos=jnsahaj/tweakcn&type=Date\">\n    </picture>\n  </a>\n</p>\n\n<!-- GitAds-Verify: HX84XPI5OQ816367AROGJ9SROARUHQER -->\n"
  },
  {
    "path": "actions/account.ts",
    "content": "\"use server\";\n\nimport { db } from \"@/db\";\nimport { user as userTable, subscription } from \"@/db/schema\";\nimport { eq } from \"drizzle-orm\";\nimport { getCurrentUserId } from \"@/lib/shared\";\nimport { logError } from \"@/lib/shared\";\nimport { actionError, actionSuccess, ErrorCode, type ActionResult } from \"@/types/errors\";\nimport { polar } from \"@/lib/polar\";\n\nexport async function deleteAccount(): Promise<ActionResult<boolean>> {\n  try {\n    const userId = await getCurrentUserId();\n\n    // Try to delete Polar customer (cancels subscriptions + revokes benefits)\n    // Free users won't have a Polar customer, so we catch and ignore errors\n    try {\n      await polar.customers.deleteExternal({ externalId: userId });\n    } catch (_e) {\n      // Expected for free users — no Polar customer exists\n    }\n\n    // Delete subscription records (no CASCADE on this table)\n    await db.delete(subscription).where(eq(subscription.userId, userId));\n\n    // Delete user — CASCADE handles: sessions, accounts, themes,\n    // communityThemes, communityThemeTags, themeLikes, aiUsage,\n    // oauthAuthorizationCode, oauthToken\n    await db.delete(userTable).where(eq(userTable.id, userId));\n\n    return actionSuccess(true);\n  } catch (error) {\n    logError(error as Error, { action: \"deleteAccount\" });\n    return actionError(ErrorCode.UNKNOWN_ERROR, \"Failed to delete account. Please try again.\");\n  }\n}\n"
  },
  {
    "path": "actions/ai-usage.ts",
    "content": "\"use server\";\n\nimport { db } from \"@/db\";\nimport { aiUsage } from \"@/db/schema\";\nimport { getCurrentUserId } from \"@/lib/shared\";\nimport { ValidationError } from \"@/types/errors\";\nimport cuid from \"cuid\";\nimport { and, count, eq, gte } from \"drizzle-orm\";\nimport { z } from \"zod\";\n\nconst getDaysSinceEpoch = (daysAgo: number = 0) =>\n  Math.floor(Date.now() / (24 * 60 * 60 * 1000)) - daysAgo;\n\n// Schema for recording usage events (internal use - still tracks tokens)\nconst recordUsageSchema = z.object({\n  promptTokens: z.number().min(0).default(0),\n  completionTokens: z.number().min(0).default(0),\n  modelId: z.string(),\n});\n\n// Schema for timeframe validation\nconst timeframeSchema = z.union([z.literal(\"1d\"), z.literal(\"7d\"), z.literal(\"30d\")]);\n\n// Types\ntype Timeframe = z.infer<typeof timeframeSchema>;\n\n// Simplified user-facing interface - only shows requests\ninterface UsageStats {\n  requests: number;\n  timeframe: Timeframe;\n}\n\ninterface ChartDataPoint {\n  daysSinceEpoch?: number;\n  hoursSinceEpoch?: number;\n  date: string;\n  totalRequests: number;\n}\n\nexport async function recordAIUsage(input: {\n  modelId: string;\n  promptTokens?: number;\n  completionTokens?: number;\n}) {\n  try {\n    const userId = await getCurrentUserId();\n\n    const validation = recordUsageSchema.safeParse(input);\n    if (!validation.success) {\n      throw new ValidationError(\"Invalid usage data\", validation.error.format());\n    }\n\n    const { promptTokens, completionTokens, modelId } = validation.data;\n    const daysSinceEpoch = getDaysSinceEpoch(0);\n\n    const [insertedUsage] = await db\n      .insert(aiUsage)\n      .values({\n        id: cuid(),\n        userId,\n        modelId,\n        promptTokens: promptTokens.toString(),\n        completionTokens: completionTokens.toString(),\n        daysSinceEpoch: daysSinceEpoch.toString(),\n        createdAt: new Date(),\n      })\n      .returning();\n\n    return insertedUsage;\n  } catch (error) {\n    console.error(\"Error recording usage:\", error);\n    throw error;\n  }\n}\n\nexport async function getMyUsageStats(timeframe: Timeframe): Promise<UsageStats> {\n  try {\n    const userId = await getCurrentUserId();\n\n    const validation = timeframeSchema.safeParse(timeframe);\n    if (!validation.success) {\n      throw new ValidationError(\"Invalid timeframe\");\n    }\n\n    const days = timeframe === \"1d\" ? 1 : timeframe === \"7d\" ? 7 : 30;\n    const startDay = getDaysSinceEpoch(days);\n\n    // Get user's events in time range\n    const events = await db\n      .select()\n      .from(aiUsage)\n      .where(and(eq(aiUsage.userId, userId), gte(aiUsage.daysSinceEpoch, startDay.toString())));\n\n    return {\n      requests: events.length,\n      timeframe,\n    };\n  } catch (error) {\n    console.error(\"Error getting usage stats:\", error);\n    throw error;\n  }\n}\n\nexport async function getMyAllTimeRequestCount(userId: string): Promise<number> {\n  try {\n    const result = await db\n      .select({ count: count() })\n      .from(aiUsage)\n      .where(eq(aiUsage.userId, userId));\n\n    return result[0]?.count ?? 0;\n  } catch (error) {\n    console.error(\"Error getting all-time request count:\", error);\n    throw error;\n  }\n}\n\nexport async function getMyUsageChartData(timeframe: Timeframe): Promise<ChartDataPoint[]> {\n  try {\n    const userId = await getCurrentUserId();\n\n    const validation = timeframeSchema.safeParse(timeframe);\n    if (!validation.success) {\n      throw new ValidationError(\"Invalid timeframe\");\n    }\n\n    // For 1d, we want hourly granularity\n    if (timeframe === \"1d\") {\n      const hours = 24;\n      const startTime = Date.now() - hours * 60 * 60 * 1000;\n\n      // Get user's events in the last 24 hours\n      const events = await db\n        .select()\n        .from(aiUsage)\n        .where(and(eq(aiUsage.userId, userId), gte(aiUsage.createdAt, new Date(startTime))));\n\n      // Group by hour\n      const chartData: ChartDataPoint[] = [];\n      for (let i = hours - 1; i >= 0; i--) {\n        const hourStart = Date.now() - i * 60 * 60 * 1000;\n        const hourEnd = Date.now() - (i - 1) * 60 * 60 * 1000;\n        const hourEvents = events.filter(\n          (e) => e.createdAt.getTime() >= hourStart && e.createdAt.getTime() < hourEnd\n        );\n\n        const totalRequests = hourEvents.length;\n\n        chartData.push({\n          hoursSinceEpoch: Math.floor(hourStart / (60 * 60 * 1000)),\n          date: new Date(hourStart).toISOString(),\n          totalRequests,\n        });\n      }\n\n      return chartData;\n    }\n\n    // Daily logic for 7d and 30d\n    const days = timeframe === \"7d\" ? 7 : 30;\n    const startDay = getDaysSinceEpoch(days);\n\n    // Get user's events in time range\n    const events = await db\n      .select()\n      .from(aiUsage)\n      .where(and(eq(aiUsage.userId, userId), gte(aiUsage.daysSinceEpoch, startDay.toString())));\n\n    // Group by day\n    const chartData: ChartDataPoint[] = [];\n    for (let i = days - 1; i >= 0; i--) {\n      const daysSince = getDaysSinceEpoch(i);\n      const dayEvents = events.filter((e) => parseInt(e.daysSinceEpoch) === daysSince);\n\n      const totalRequests = dayEvents.length;\n\n      chartData.push({\n        daysSinceEpoch: daysSince,\n        date: new Date(daysSince * 24 * 60 * 60 * 1000).toISOString().split(\"T\")[0],\n        totalRequests,\n      });\n    }\n\n    return chartData;\n  } catch (error) {\n    console.error(\"Error getting usage chart data:\", error);\n    throw error;\n  }\n}\n\n// Internal function for detailed usage (including tokens) - not exposed to users\nexport async function getDetailedUsageStats(\n  timeframe: Timeframe,\n  modelId: string\n): Promise<{\n  requests: number;\n  promptTokens: number;\n  completionTokens: number;\n  totalTokens: number;\n  timeframe: Timeframe;\n}> {\n  try {\n    const userId = await getCurrentUserId();\n\n    const validation = timeframeSchema.safeParse(timeframe);\n    if (!validation.success) {\n      throw new ValidationError(\"Invalid timeframe\");\n    }\n\n    const days = timeframe === \"1d\" ? 1 : timeframe === \"7d\" ? 7 : 30;\n    const startDay = getDaysSinceEpoch(days);\n\n    // Get user's events for the model\n    const events = await db\n      .select()\n      .from(aiUsage)\n      .where(\n        and(\n          eq(aiUsage.userId, userId),\n          eq(aiUsage.modelId, modelId),\n          gte(aiUsage.daysSinceEpoch, startDay.toString())\n        )\n      );\n\n    const requests = events.length;\n    const promptTokens = events.reduce((sum, e) => sum + parseInt(e.promptTokens), 0);\n    const completionTokens = events.reduce((sum, e) => sum + parseInt(e.completionTokens), 0);\n    const totalTokens = promptTokens + completionTokens;\n\n    return {\n      requests,\n      promptTokens,\n      completionTokens,\n      totalTokens,\n      timeframe,\n    };\n  } catch (error) {\n    console.error(\"Error getting detailed usage stats:\", error);\n    throw error;\n  }\n}\n"
  },
  {
    "path": "actions/checkout.ts",
    "content": "\"use server\";\n\nimport { polar } from \"@/lib/polar\";\nimport { getCurrentUser, logError } from \"@/lib/shared\";\nimport { getOrCreateCustomer } from \"./customer\";\n\nexport const createCheckout = async () => {\n  try {\n    const user = await getCurrentUser();\n    const customer = await getOrCreateCustomer(user);\n    const checkout = await polar.checkouts.create({\n      products: [process.env.NEXT_PUBLIC_TWEAKCN_PRO_PRODUCT_ID!],\n      customerId: customer?.id,\n      successUrl: `${process.env.BASE_URL}/success?checkout_id={CHECKOUT_ID}`,\n    });\n\n    return { url: checkout.url };\n  } catch (error) {\n    logError(error as Error, { action: \"createCheckout\" });\n    return { error: \"Failed to create checkout\" };\n  }\n};\n"
  },
  {
    "path": "actions/community-themes.ts",
    "content": "\"use server\";\n\nimport { z } from \"zod\";\nimport { db } from \"@/db\";\nimport {\n  communityTheme,\n  communityThemeTag,\n  themeLike,\n  theme as themeTable,\n  user as userTable,\n} from \"@/db/schema\";\nimport { eq, and, desc, asc, sql, count, inArray } from \"drizzle-orm\";\nimport cuid from \"cuid\";\nimport { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport {\n  UnauthorizedError,\n  ValidationError,\n  ThemeNotFoundError,\n  ErrorCode,\n  actionError,\n  actionSuccess,\n  type ActionResult,\n} from \"@/types/errors\";\nimport {\n  COMMUNITY_THEMES_PAGE_SIZE,\n  COMMUNITY_THEME_TAGS,\n  MAX_TAGS_PER_THEME,\n} from \"@/lib/constants\";\nimport type {\n  CommunityTheme,\n  CommunitySortOption,\n  CommunityFilterOption,\n  CommunityTimeRange,\n  CommunityThemesResponse,\n} from \"@/types/community\";\nimport { unstable_cache, revalidateTag } from \"next/cache\";\nimport { Ratelimit } from \"@upstash/ratelimit\";\nimport { kv } from \"@vercel/kv\";\n\nconst ratelimit = new Ratelimit({\n  redis: kv,\n  limiter: Ratelimit.fixedWindow(5, \"3600s\"),\n});\n\nasync function getOptionalUserId(): Promise<string | null> {\n  try {\n    const session = await auth.api.getSession({\n      headers: await headers(),\n    });\n    return session?.user?.id ?? null;\n  } catch {\n    return null;\n  }\n}\n\nasync function getCurrentUserId(): Promise<string> {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  if (!session?.user?.id) {\n    throw new UnauthorizedError();\n  }\n\n  return session.user.id;\n}\n\nfunction logError(error: Error, context: Record<string, unknown>) {\n  if (error.name === \"UnauthorizedError\" || error.name === \"ValidationError\") {\n    console.warn(\"Expected error:\", { error: error.message, context });\n  } else {\n    console.error(\"Unexpected error:\", {\n      error: error.message,\n      stack: error.stack,\n      context,\n    });\n  }\n}\n\nconst getCommunityThemesSchema = z.object({\n  sort: z.enum([\"popular\", \"newest\", \"oldest\"]).default(\"popular\"),\n  cursor: z.union([z.string(), z.number()]).optional(),\n  limit: z.number().min(1).max(50).default(COMMUNITY_THEMES_PAGE_SIZE),\n  filter: z.enum([\"all\", \"mine\", \"liked\"]).default(\"all\"),\n  tags: z.array(z.string()).default([]),\n  timeRange: z.enum([\"weekly\", \"monthly\", \"all\"]).default(\"all\"),\n});\n\n// Core query logic for community themes (no headers() access — cacheable)\nasync function fetchCommunityThemesCore(\n  sort: string,\n  cursor: string | number | null,\n  limit: number,\n  filter: string,\n  tags: string[],\n  userId: string | null,\n  timeRange: string = \"all\"\n): Promise<CommunityThemesResponse> {\n  const fetchLimit = limit + 1;\n  const conditions = [];\n\n  if (filter === \"mine\") {\n    if (!userId) throw new UnauthorizedError();\n    conditions.push(eq(communityTheme.userId, userId));\n  }\n\n  if (filter === \"liked\") {\n    if (!userId) throw new UnauthorizedError();\n    conditions.push(\n      sql`exists(select 1 from theme_like where theme_like.theme_id = ${communityTheme.id} and theme_like.user_id = ${userId})`\n    );\n  }\n\n  if (tags.length > 0) {\n    conditions.push(\n      sql`exists(select 1 from community_theme_tag where community_theme_tag.community_theme_id = ${communityTheme.id} and community_theme_tag.tag in (${sql.join(\n        tags.map((t) => sql`${t}`),\n        sql`, `\n      )}))`\n    );\n  }\n\n  // For weekly/monthly popular sort, filter to themes published within the time range\n  if (sort === \"popular\" && timeRange !== \"all\") {\n    const intervalSql =\n      timeRange === \"weekly\"\n        ? sql`interval '7 days'`\n        : sql`interval '30 days'`;\n    conditions.push(\n      sql`${communityTheme.publishedAt} > now() - ${intervalSql}`\n    );\n  }\n\n  const selectFields = {\n    id: communityTheme.id,\n    themeId: communityTheme.themeId,\n    publishedAt: communityTheme.publishedAt,\n    themeName: themeTable.name,\n    themeStyles: themeTable.styles,\n    authorId: userTable.id,\n    authorName: userTable.name,\n    authorImage: userTable.image,\n    likeCount: communityTheme.likeCount,\n    ...(userId\n      ? {\n          isLikedByMe: sql<boolean>`exists(\n            select 1 from theme_like\n            where theme_like.theme_id = ${communityTheme.id}\n            and theme_like.user_id = ${userId}\n          )`.as(\"is_liked_by_me\"),\n        }\n      : {}),\n  };\n\n  const baseQuery = db\n    .select(selectFields)\n    .from(communityTheme)\n    .innerJoin(themeTable, eq(communityTheme.themeId, themeTable.id))\n    .innerJoin(userTable, eq(communityTheme.userId, userTable.id));\n\n  let results;\n\n  if (sort === \"popular\") {\n    const offset = typeof cursor === \"number\" ? cursor : 0;\n    results = await baseQuery\n      .where(conditions.length > 0 ? and(...conditions) : undefined)\n      .orderBy(desc(communityTheme.likeCount), desc(communityTheme.publishedAt))\n      .limit(fetchLimit)\n      .offset(offset);\n  } else if (sort === \"newest\") {\n    if (cursor && typeof cursor === \"string\") {\n      conditions.push(sql`${communityTheme.publishedAt} < ${cursor}`);\n    }\n    results = await baseQuery\n      .where(conditions.length > 0 ? and(...conditions) : undefined)\n      .orderBy(desc(communityTheme.publishedAt))\n      .limit(fetchLimit);\n  } else {\n    if (cursor && typeof cursor === \"string\") {\n      conditions.push(sql`${communityTheme.publishedAt} > ${cursor}`);\n    }\n    results = await baseQuery\n      .where(conditions.length > 0 ? and(...conditions) : undefined)\n      .orderBy(asc(communityTheme.publishedAt))\n      .limit(fetchLimit);\n  }\n\n  const hasMore = results.length > limit;\n  const themes = results.slice(0, limit);\n\n  let nextCursor: string | number | null = null;\n  if (hasMore) {\n    if (sort === \"popular\") {\n      nextCursor = (typeof cursor === \"number\" ? cursor : 0) + limit;\n    } else {\n      const lastTheme = themes[themes.length - 1];\n      nextCursor = lastTheme.publishedAt.toISOString();\n    }\n  }\n\n  const communityThemeIds = themes.map((t) => t.id);\n  const tagsRows =\n    communityThemeIds.length > 0\n      ? await db\n          .select({\n            communityThemeId: communityThemeTag.communityThemeId,\n            tag: communityThemeTag.tag,\n          })\n          .from(communityThemeTag)\n          .where(\n            inArray(communityThemeTag.communityThemeId, communityThemeIds)\n          )\n      : [];\n\n  const tagsMap = new Map<string, string[]>();\n  for (const row of tagsRows) {\n    const existing = tagsMap.get(row.communityThemeId) ?? [];\n    existing.push(row.tag);\n    tagsMap.set(row.communityThemeId, existing);\n  }\n\n  const mappedThemes: CommunityTheme[] = themes.map((row) => ({\n    id: row.id,\n    themeId: row.themeId,\n    name: row.themeName,\n    styles: row.themeStyles,\n    author: {\n      id: row.authorId,\n      name: row.authorName,\n      image: row.authorImage,\n    },\n    likeCount: Number(row.likeCount),\n    isLikedByMe: \"isLikedByMe\" in row ? Boolean(row.isLikedByMe) : false,\n    publishedAt: row.publishedAt.toISOString(),\n    tags: tagsMap.get(row.id) ?? [],\n  }));\n\n  return { themes: mappedThemes, nextCursor };\n}\n\nconst getCachedCommunityThemes = unstable_cache(\n  fetchCommunityThemesCore,\n  [\"community-themes\"],\n  { revalidate: 60, tags: [\"community-themes\"] }\n);\n\nexport async function getCommunityThemes(\n  sort: CommunitySortOption = \"popular\",\n  cursor?: string | number,\n  limit: number = COMMUNITY_THEMES_PAGE_SIZE,\n  filter: CommunityFilterOption = \"all\",\n  tags: string[] = [],\n  timeRange: CommunityTimeRange = \"all\"\n): Promise<CommunityThemesResponse> {\n  try {\n    const validation = getCommunityThemesSchema.safeParse({\n      sort,\n      cursor,\n      limit,\n      filter,\n      tags,\n      timeRange,\n    });\n    if (!validation.success) {\n      throw new ValidationError(\"Invalid input\", validation.error.format());\n    }\n\n    const userId = await getOptionalUserId();\n    return getCachedCommunityThemes(\n      sort,\n      cursor ?? null,\n      limit,\n      filter,\n      tags,\n      userId,\n      timeRange\n    );\n  } catch (error) {\n    logError(error as Error, { action: \"getCommunityThemes\", sort, cursor });\n    throw error;\n  }\n}\n\nexport async function publishTheme(\n  themeId: string,\n  tags: string[] = []\n): Promise<ActionResult<{ id: string }>> {\n  try {\n    const userId = await getCurrentUserId();\n\n    if (!themeId) {\n      throw new ValidationError(\"Theme ID required\");\n    }\n\n    // Validate tags\n    if (tags.length > MAX_TAGS_PER_THEME) {\n      throw new ValidationError(\n        `You can select at most ${MAX_TAGS_PER_THEME} tags`\n      );\n    }\n    const validTags = tags.filter((t): t is string =>\n      (COMMUNITY_THEME_TAGS as readonly string[]).includes(t)\n    );\n\n    // Verify theme ownership\n    const [existingTheme] = await db\n      .select()\n      .from(themeTable)\n      .where(and(eq(themeTable.id, themeId), eq(themeTable.userId, userId)))\n      .limit(1);\n\n    if (!existingTheme) {\n      throw new ThemeNotFoundError(\"Theme not found or not owned by user\");\n    }\n\n    // Check not already published\n    const [existing] = await db\n      .select()\n      .from(communityTheme)\n      .where(eq(communityTheme.themeId, themeId))\n      .limit(1);\n\n    if (existing) {\n      return actionError(\n        ErrorCode.ALREADY_PUBLISHED,\n        \"This theme is already published to the community.\"\n      );\n    }\n\n    // Rate limit\n    if (process.env.NODE_ENV !== \"development\") {\n      const { success } = await ratelimit.limit(`publish:${userId}`);\n      if (!success) {\n        return actionError(\n          ErrorCode.UNKNOWN_ERROR,\n          \"You're publishing too fast. Please try again later.\"\n        );\n      }\n    }\n\n    const id = cuid();\n    await db.insert(communityTheme).values({\n      id,\n      themeId,\n      userId,\n      publishedAt: new Date(),\n    });\n\n    if (validTags.length > 0) {\n      try {\n        await db.insert(communityThemeTag).values(\n          validTags.map((tag) => ({\n            communityThemeId: id,\n            tag,\n          }))\n        );\n      } catch (tagError) {\n        // Roll back the community theme row if tags insert fails\n        await db.delete(communityTheme).where(eq(communityTheme.id, id));\n        throw tagError;\n      }\n    }\n\n    revalidateTag(\"community-themes\");\n    revalidateTag(\"community-tag-counts\");\n\n    return actionSuccess({ id });\n  } catch (error) {\n    logError(error as Error, { action: \"publishTheme\", themeId });\n    throw error;\n  }\n}\n\nexport async function unpublishTheme(\n  themeId: string\n): Promise<ActionResult<{ success: boolean }>> {\n  try {\n    const userId = await getCurrentUserId();\n\n    if (!themeId) {\n      throw new ValidationError(\"Theme ID required\");\n    }\n\n    const [deleted] = await db\n      .delete(communityTheme)\n      .where(\n        and(\n          eq(communityTheme.themeId, themeId),\n          eq(communityTheme.userId, userId)\n        )\n      )\n      .returning({ id: communityTheme.id });\n\n    if (!deleted) {\n      throw new ThemeNotFoundError(\n        \"Published theme not found or not owned by user\"\n      );\n    }\n\n    revalidateTag(\"community-themes\");\n    revalidateTag(\"community-tag-counts\");\n\n    return actionSuccess({ success: true });\n  } catch (error) {\n    logError(error as Error, { action: \"unpublishTheme\", themeId });\n    throw error;\n  }\n}\n\nexport async function toggleLikeTheme(\n  communityThemeId: string\n): Promise<ActionResult<{ liked: boolean; likeCount: number }>> {\n  try {\n    const userId = await getCurrentUserId();\n\n    if (!communityThemeId) {\n      throw new ValidationError(\"Community theme ID required\");\n    }\n\n    // Check if already liked\n    const [existingLike] = await db\n      .select()\n      .from(themeLike)\n      .where(\n        and(\n          eq(themeLike.userId, userId),\n          eq(themeLike.themeId, communityThemeId)\n        )\n      )\n      .limit(1);\n\n    if (existingLike) {\n      // Unlike: delete + decrement\n      await db\n        .delete(themeLike)\n        .where(\n          and(\n            eq(themeLike.userId, userId),\n            eq(themeLike.themeId, communityThemeId)\n          )\n        );\n      const [updated] = await db\n        .update(communityTheme)\n        .set({\n          likeCount: sql`GREATEST(${communityTheme.likeCount} - 1, 0)`,\n        })\n        .where(eq(communityTheme.id, communityThemeId))\n        .returning({ likeCount: communityTheme.likeCount });\n\n      revalidateTag(\"community-themes\");\n\n      return actionSuccess({\n        liked: false,\n        likeCount: updated.likeCount,\n      });\n    } else {\n      // Like: insert + increment\n      await db.insert(themeLike).values({\n        userId,\n        themeId: communityThemeId,\n        createdAt: new Date(),\n      });\n      const [updated] = await db\n        .update(communityTheme)\n        .set({ likeCount: sql`${communityTheme.likeCount} + 1` })\n        .where(eq(communityTheme.id, communityThemeId))\n        .returning({ likeCount: communityTheme.likeCount });\n\n      revalidateTag(\"community-themes\");\n\n      return actionSuccess({\n        liked: true,\n        likeCount: updated.likeCount,\n      });\n    }\n  } catch (error) {\n    logError(error as Error, {\n      action: \"toggleLikeTheme\",\n      communityThemeId,\n    });\n    throw error;\n  }\n}\n\nasync function fetchCommunityDataForThemeCore(\n  themeId: string,\n  userId: string | null\n): Promise<{\n  communityThemeId: string;\n  author: { id: string; name: string; image: string | null };\n  likeCount: number;\n  isLikedByMe: boolean;\n  publishedAt: string;\n  tags: string[];\n} | null> {\n  const [result] = await db\n    .select({\n      id: communityTheme.id,\n      publishedAt: communityTheme.publishedAt,\n      authorId: userTable.id,\n      authorName: userTable.name,\n      authorImage: userTable.image,\n      likeCount: communityTheme.likeCount,\n      ...(userId\n        ? {\n            isLikedByMe: sql<boolean>`exists(\n              select 1 from theme_like\n              where theme_like.theme_id = ${communityTheme.id}\n              and theme_like.user_id = ${userId}\n            )`.as(\"is_liked_by_me\"),\n          }\n        : {}),\n    })\n    .from(communityTheme)\n    .innerJoin(userTable, eq(communityTheme.userId, userTable.id))\n    .where(eq(communityTheme.themeId, themeId))\n    .limit(1);\n\n  if (!result) return null;\n\n  const tagRows = await db\n    .select({ tag: communityThemeTag.tag })\n    .from(communityThemeTag)\n    .where(eq(communityThemeTag.communityThemeId, result.id));\n\n  return {\n    communityThemeId: result.id,\n    author: {\n      id: result.authorId,\n      name: result.authorName,\n      image: result.authorImage,\n    },\n    likeCount: Number(result.likeCount),\n    isLikedByMe:\n      \"isLikedByMe\" in result ? Boolean(result.isLikedByMe) : false,\n    publishedAt: result.publishedAt.toISOString(),\n    tags: tagRows.map((r) => r.tag),\n  };\n}\n\nconst getCachedCommunityDataForTheme = unstable_cache(\n  fetchCommunityDataForThemeCore,\n  [\"community-theme-data\"],\n  { revalidate: 60, tags: [\"community-themes\"] }\n);\n\nexport async function getCommunityDataForTheme(\n  themeId: string\n): Promise<{\n  communityThemeId: string;\n  author: { id: string; name: string; image: string | null };\n  likeCount: number;\n  isLikedByMe: boolean;\n  publishedAt: string;\n  tags: string[];\n} | null> {\n  try {\n    const userId = await getOptionalUserId();\n    return getCachedCommunityDataForTheme(themeId, userId);\n  } catch (error) {\n    logError(error as Error, {\n      action: \"getCommunityDataForTheme\",\n      themeId,\n    });\n    return null;\n  }\n}\n\nexport async function getMyPublishedThemeIds(): Promise<string[]> {\n  try {\n    const userId = await getCurrentUserId();\n\n    const published = await db\n      .select({ themeId: communityTheme.themeId })\n      .from(communityTheme)\n      .where(eq(communityTheme.userId, userId));\n\n    return published.map((p) => p.themeId);\n  } catch (error) {\n    logError(error as Error, { action: \"getMyPublishedThemeIds\" });\n    throw error;\n  }\n}\n\nexport async function updateCommunityThemeTags(\n  themeId: string,\n  tags: string[]\n): Promise<ActionResult<{ tags: string[] }>> {\n  try {\n    const userId = await getCurrentUserId();\n\n    if (!themeId) {\n      throw new ValidationError(\"Theme ID required\");\n    }\n\n    if (tags.length > MAX_TAGS_PER_THEME) {\n      throw new ValidationError(\n        `You can select at most ${MAX_TAGS_PER_THEME} tags`\n      );\n    }\n\n    const validTags = tags.filter((t): t is string =>\n      (COMMUNITY_THEME_TAGS as readonly string[]).includes(t)\n    );\n\n    // Verify ownership via the community_theme row\n    const [ct] = await db\n      .select({ id: communityTheme.id })\n      .from(communityTheme)\n      .where(\n        and(\n          eq(communityTheme.themeId, themeId),\n          eq(communityTheme.userId, userId)\n        )\n      )\n      .limit(1);\n\n    if (!ct) {\n      throw new ThemeNotFoundError(\n        \"Published theme not found or not owned by user\"\n      );\n    }\n\n    // Delete existing tags and insert new ones\n    await db\n      .delete(communityThemeTag)\n      .where(eq(communityThemeTag.communityThemeId, ct.id));\n\n    if (validTags.length > 0) {\n      await db.insert(communityThemeTag).values(\n        validTags.map((tag) => ({\n          communityThemeId: ct.id,\n          tag,\n        }))\n      );\n    }\n\n    revalidateTag(\"community-themes\");\n    revalidateTag(\"community-tag-counts\");\n\n    return actionSuccess({ tags: validTags });\n  } catch (error) {\n    logError(error as Error, {\n      action: \"updateCommunityThemeTags\",\n      themeId,\n    });\n    throw error;\n  }\n}\n\nconst getCachedTagCounts = unstable_cache(\n  async () => {\n    const rows = await db\n      .select({\n        tag: communityThemeTag.tag,\n        count: count().as(\"tag_count\"),\n      })\n      .from(communityThemeTag)\n      .groupBy(communityThemeTag.tag)\n      .orderBy(sql`tag_count desc`);\n\n    return rows.map((r) => ({ tag: r.tag, count: Number(r.count) }));\n  },\n  [\"community-tag-counts\"],\n  { revalidate: 300, tags: [\"community-tag-counts\"] }\n);\n\nexport async function getCommunityTagCounts(): Promise<\n  { tag: string; count: number }[]\n> {\n  try {\n    return getCachedTagCounts();\n  } catch (error) {\n    logError(error as Error, { action: \"getCommunityTagCounts\" });\n    return [];\n  }\n}\n"
  },
  {
    "path": "actions/customer.ts",
    "content": "import \"server-only\";\n\nimport { polar } from \"@/lib/polar\";\nimport { logError } from \"@/lib/shared\";\nimport { Customer } from \"@polar-sh/sdk/models/components/customer.js\";\nimport { User } from \"better-auth\";\n\nexport const getOrCreateCustomer = async (user: User) => {\n  let customer: Customer | null = null;\n\n  try {\n    customer = await polar.customers.getExternal({ externalId: user.id });\n  } catch (_e) {\n    customer = null;\n  }\n\n  if (customer) return customer;\n\n  try {\n    const newCustomer = await polar.customers.create({\n      email: user.email,\n      externalId: user.id,\n      name: user.name,\n    });\n\n    return newCustomer;\n  } catch (err) {\n    logError(err as Error, { action: \"createCustomer\", user });\n  }\n\n  return null;\n};\n"
  },
  {
    "path": "actions/themes.ts",
    "content": "\"use server\";\n\nimport { z } from \"zod\";\nimport { db } from \"@/db\";\nimport { theme as themeTable, communityTheme } from \"@/db/schema\";\nimport { eq, and, sql } from \"drizzle-orm\";\nimport cuid from \"cuid\";\nimport { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport { themeStylesSchema, type ThemeStyles } from \"@/types/theme\";\nimport { cache } from \"react\";\nimport {\n  UnauthorizedError,\n  ValidationError,\n  ThemeNotFoundError,\n  ErrorCode,\n  actionError,\n  actionSuccess,\n  type ActionResult,\n} from \"@/types/errors\";\nimport { MAX_FREE_THEMES } from \"@/lib/constants\";\nimport { getMyActiveSubscription } from \"@/lib/subscription\";\n\n// Helper to get user ID with better error handling\nasync function getCurrentUserId(): Promise<string> {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  if (!session?.user?.id) {\n    throw new UnauthorizedError();\n  }\n\n  return session.user.id;\n}\n\n// Log errors for observability\nfunction logError(error: Error, context: Record<string, any>) {\n  console.error(\"Theme action error:\", error, context);\n\n  // TODO: Add server-side error reporting to PostHog or your preferred service\n  // For production, you'd want to send critical errors to an external service\n  if (error.name === \"UnauthorizedError\" || error.name === \"ValidationError\") {\n    // These are expected errors, log but don't report\n    console.warn(\"Expected error:\", { error: error.message, context });\n  } else {\n    // Unexpected errors should be reported\n    console.error(\"Unexpected error:\", { error: error.message, stack: error.stack, context });\n  }\n}\n\nconst createThemeSchema = z.object({\n  name: z.string().min(1, \"Theme name cannot be empty\").max(50, \"Theme name too long\"),\n  styles: themeStylesSchema,\n});\n\nconst updateThemeSchema = z.object({\n  id: z.string().min(1, \"Theme ID required\"),\n  name: z.string().min(1, \"Theme name cannot be empty\").max(50, \"Theme name too long\").optional(),\n  styles: themeStylesSchema.optional(),\n});\n\n// Layer 1: Clean server actions with proper error handling\nexport async function getThemes() {\n  try {\n    const userId = await getCurrentUserId();\n    const userThemes = await db\n      .select({\n        id: themeTable.id,\n        userId: themeTable.userId,\n        name: themeTable.name,\n        styles: themeTable.styles,\n        createdAt: themeTable.createdAt,\n        updatedAt: themeTable.updatedAt,\n        isPublished: sql<boolean>`${communityTheme.id} is not null`.as(\n          \"is_published\"\n        ),\n      })\n      .from(themeTable)\n      .leftJoin(communityTheme, eq(themeTable.id, communityTheme.themeId))\n      .where(eq(themeTable.userId, userId));\n    return userThemes;\n  } catch (error) {\n    logError(error as Error, { action: \"getThemes\" });\n    throw error;\n  }\n}\n\nexport const getTheme = cache(async (themeId: string) => {\n  try {\n    if (!themeId) {\n      throw new ValidationError(\"Theme ID required\");\n    }\n\n    const [theme] = await db.select().from(themeTable).where(eq(themeTable.id, themeId)).limit(1);\n\n    if (!theme) {\n      throw new ThemeNotFoundError();\n    }\n\n    return theme;\n  } catch (error) {\n    logError(error as Error, { action: \"getTheme\", themeId });\n    throw error;\n  }\n});\n\nexport async function createTheme(formData: { name: string; styles: ThemeStyles }) {\n  try {\n    const userId = await getCurrentUserId();\n\n    const validation = createThemeSchema.safeParse(formData);\n    if (!validation.success) {\n      throw new ValidationError(\"Invalid input\", validation.error.format());\n    }\n\n    // Check theme limit\n    const userThemes = await db.select().from(themeTable).where(eq(themeTable.userId, userId));\n\n    if (userThemes.length >= MAX_FREE_THEMES) {\n      const activeSubscription = await getMyActiveSubscription(userId);\n      const isSubscribed =\n        !!activeSubscription &&\n        activeSubscription?.productId === process.env.NEXT_PUBLIC_TWEAKCN_PRO_PRODUCT_ID;\n\n      if (!isSubscribed) {\n        return actionError(\n          ErrorCode.THEME_LIMIT_REACHED,\n          `You have reached the limit of ${MAX_FREE_THEMES} themes.`\n        );\n      }\n    }\n\n    const { name, styles } = validation.data;\n    const newThemeId = cuid();\n    const now = new Date();\n\n    const [insertedTheme] = await db\n      .insert(themeTable)\n      .values({\n        id: newThemeId,\n        userId: userId,\n        name: name,\n        styles: styles,\n        createdAt: now,\n        updatedAt: now,\n      })\n      .returning();\n\n    return actionSuccess(insertedTheme);\n  } catch (error) {\n    logError(error as Error, { action: \"createTheme\", formData: { name: formData.name } });\n    throw error;\n  }\n}\n\nexport async function updateTheme(formData: { id: string; name?: string; styles?: ThemeStyles }) {\n  try {\n    const userId = await getCurrentUserId();\n\n    const validation = updateThemeSchema.safeParse(formData);\n    if (!validation.success) {\n      throw new ValidationError(\"Invalid input\", validation.error.format());\n    }\n\n    const { id: themeId, name, styles } = validation.data;\n\n    if (!name && !styles) {\n      throw new ValidationError(\"No update data provided\");\n    }\n\n    const updateData: Partial<typeof themeTable.$inferInsert> = {\n      updatedAt: new Date(),\n    };\n    if (name) updateData.name = name;\n    if (styles) updateData.styles = styles;\n\n    const [updatedTheme] = await db\n      .update(themeTable)\n      .set(updateData)\n      .where(and(eq(themeTable.id, themeId), eq(themeTable.userId, userId)))\n      .returning();\n\n    if (!updatedTheme) {\n      throw new ThemeNotFoundError(\"Theme not found or not owned by user\");\n    }\n\n    return updatedTheme;\n  } catch (error) {\n    logError(error as Error, { action: \"updateTheme\", themeId: formData.id });\n    throw error;\n  }\n}\n\nexport async function deleteTheme(themeId: string) {\n  try {\n    const userId = await getCurrentUserId();\n\n    if (!themeId) {\n      throw new ValidationError(\"Theme ID required\");\n    }\n\n    const [deletedTheme] = await db\n      .delete(themeTable)\n      .where(and(eq(themeTable.id, themeId), eq(themeTable.userId, userId)))\n      .returning({ id: themeTable.id, name: themeTable.name });\n\n    if (!deletedTheme) {\n      throw new ThemeNotFoundError(\"Theme not found or not owned by user\");\n    }\n\n    return deletedTheme;\n  } catch (error) {\n    logError(error as Error, { action: \"deleteTheme\", themeId });\n    throw error;\n  }\n}\n"
  },
  {
    "path": "app/(auth)/components/auth-dialog.tsx",
    "content": "\"use client\";\n\nimport Github from \"@/assets/github.svg\";\nimport Google from \"@/assets/google.svg\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n  ResponsiveDialogTrigger,\n} from \"@/components/ui/revola\";\nimport { PostLoginActionType } from \"@/hooks/use-post-login-action\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { Loader2 } from \"lucide-react\";\nimport { usePathname, useSearchParams } from \"next/navigation\";\nimport { useEffect, useState } from \"react\";\n\ninterface AuthDialogProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  initialMode?: \"signin\" | \"signup\";\n  trigger?: React.ReactNode; // Optional trigger element\n  postLoginActionType?: PostLoginActionType | null;\n}\n\n// Get contextual copy based on the post-login action\nfunction getContextualCopy(actionType?: PostLoginActionType | null) {\n  switch (actionType) {\n    case \"SAVE_THEME\":\n      return {\n        title: \"Sign in to Save\",\n        description: \"Sign in to save your theme and access it from anywhere\",\n      };\n    case \"SAVE_THEME_FOR_SHARE\":\n      return {\n        title: \"Sign in to Share\",\n        description: \"Sign in to save and share your theme with others\",\n      };\n    case \"SAVE_THEME_FOR_V0\":\n      return {\n        title: \"Sign in to open in v0\",\n        description: \"Sign in to save your theme and open it in v0\",\n      };\n    case \"AI_GENERATE_FROM_PAGE\":\n    case \"AI_GENERATE_FROM_CHAT\":\n    case \"AI_GENERATE_FROM_CHAT_SUGGESTION\":\n    case \"AI_GENERATE_EDIT\":\n    case \"AI_GENERATE_RETRY\":\n      return {\n        title: \"Sign in for AI\",\n        description: \"Sign in to use AI-powered theme generation\",\n      };\n    case \"CHECKOUT\":\n      return {\n        title: \"Sign in to continue\",\n        description: \"Sign in to complete your purchase\",\n      };\n    default:\n      return null;\n  }\n}\n\nexport function AuthDialog({\n  open,\n  onOpenChange,\n  initialMode = \"signin\",\n  trigger,\n  postLoginActionType,\n}: AuthDialogProps) {\n  const pathname = usePathname();\n  const searchParams = useSearchParams();\n  const [isSignIn, setIsSignIn] = useState(initialMode === \"signin\");\n  const [isGoogleLoading, setIsGoogleLoading] = useState(false);\n  const [isGithubLoading, setIsGithubLoading] = useState(false);\n\n  const contextualCopy = getContextualCopy(postLoginActionType);\n\n  const getCallbackUrl = () => {\n    const baseUrl = pathname || \"/editor/theme\";\n    const queryString = searchParams.toString();\n    return queryString ? `${baseUrl}?${queryString}` : baseUrl;\n  };\n\n  useEffect(() => {\n    if (open) {\n      setIsSignIn(initialMode === \"signin\");\n    }\n  }, [open, initialMode]);\n\n  const handleGoogleSignIn = async () => {\n    setIsGoogleLoading(true);\n    try {\n      await authClient.signIn.social({\n        provider: \"google\",\n        callbackURL: getCallbackUrl(),\n      });\n    } catch (error) {\n      console.error(\"Google Sign In Error:\", error);\n      // Handle error appropriately (e.g., show a toast notification)\n    }\n  };\n\n  const handleGithubSignIn = async () => {\n    setIsGithubLoading(true);\n    try {\n      await authClient.signIn.social({\n        provider: \"github\",\n        callbackURL: getCallbackUrl(),\n      });\n    } catch (error) {\n      console.error(\"GitHub Sign In Error:\", error);\n      // Handle error appropriately\n    }\n  };\n\n  const toggleMode = () => {\n    setIsSignIn(!isSignIn);\n  };\n\n  return (\n    <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n      {trigger && <ResponsiveDialogTrigger asChild>{trigger}</ResponsiveDialogTrigger>}\n      <ResponsiveDialogContent className=\"overflow-hidden sm:max-w-100\">\n        <div className=\"space-y-4\">\n          <ResponsiveDialogHeader className=\"sm:pt-8\">\n            <ResponsiveDialogTitle className=\"text-center text-2xl font-bold\">\n              {contextualCopy?.title ?? (isSignIn ? \"Welcome back\" : \"Create account\")}\n            </ResponsiveDialogTitle>\n            <p className=\"text-muted-foreground text-center\">\n              {contextualCopy?.description ??\n                (isSignIn\n                  ? \"Sign in to your account to continue\"\n                  : \"Sign up to get started with tweakcn\")}\n            </p>\n          </ResponsiveDialogHeader>\n\n          <div className=\"space-y-6 p-6 pt-2\">\n            <div className=\"space-y-3\">\n              <Button\n                size=\"lg\"\n                variant=\"outline\"\n                onClick={handleGoogleSignIn}\n                className=\"hover:bg-primary/10 hover:text-foreground flex w-full items-center justify-center gap-2\"\n                disabled={isGoogleLoading || isGithubLoading}\n              >\n                <Google className=\"h-5 w-5\" />\n                <span className=\"font-medium\">Continue with Google</span>\n                {isGoogleLoading && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n              </Button>\n\n              <Button\n                variant=\"outline\"\n                onClick={handleGithubSignIn}\n                size=\"lg\"\n                className=\"hover:bg-primary/10 hover:text-foreground flex w-full items-center justify-center gap-2\"\n                disabled={isGoogleLoading || isGithubLoading}\n              >\n                <Github className=\"h-5 w-5\" />\n                <span className=\"font-medium\">Continue with GitHub</span>\n                {isGithubLoading && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n              </Button>\n            </div>\n\n            <div className=\"pt-2\">\n              <div className=\"relative\">\n                <div className=\"absolute inset-0 flex items-center\">\n                  <span className=\"w-full border-t\" />\n                </div>\n                <div className=\"relative flex justify-center text-xs uppercase\">\n                  <span className=\"bg-background text-muted-foreground px-2\">\n                    {isSignIn ? \"New to tweakcn?\" : \"Already have an account?\"}\n                  </span>\n                </div>\n              </div>\n\n              <div className=\"mt-6 text-center\">\n                <button\n                  onClick={toggleMode}\n                  className=\"text-primary focus:ring-primary text-sm font-medium hover:underline focus:ring-2 focus:ring-offset-2 focus:outline-none\"\n                >\n                  {isSignIn ? \"Create an account\" : \"Sign in to your account\"}\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n}\n"
  },
  {
    "path": "app/(legal)/layout.tsx",
    "content": "import React from \"react\";\nimport { Header } from \"@/components/header\";\nimport { Footer } from \"@/components/footer\";\n\ninterface LegalLayoutProps {\n  children: React.ReactNode;\n}\n\nexport default function LegalLayout({ children }: LegalLayoutProps) {\n  return (\n    <div className=\"flex min-h-screen flex-col\">\n      <Header />\n      <main className=\"flex-1\">{children}</main>\n      <Footer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(legal)/privacy-policy/page.tsx",
    "content": "import { Metadata } from \"next\";\n\nexport const metadata: Metadata = {\n  title: \"Privacy Policy | tweakcn\",\n  description: \"Privacy Policy for tweakcn.\",\n};\n\nexport default function PrivacyPolicyPage() {\n  return (\n    <div className=\"container mx-auto px-4 py-12 md:px-6 md:py-20 lg:max-w-4xl\">\n      <h1 className=\"mb-6 text-3xl font-bold\">Privacy Policy</h1>\n      <p className=\"text-muted-foreground mb-8 text-sm\">Last Updated: 24 Apr 2025</p>\n\n      <section className=\"mb-8 space-y-4\">\n        <h2 className=\"text-xl font-semibold\">1. Introduction</h2>\n        <p className=\"text-muted-foreground\">\n          We value your privacy and are committed to safeguarding your personal data. This privacy\n          policy explains how we collect, use, and protect your information when you use our\n          website, as well as your privacy rights and how they are protected by law.\n        </p>\n      </section>\n\n      <section className=\"mb-8 space-y-4\">\n        <h2 className=\"text-xl font-semibold\">2. Data Collection</h2>\n        <p className=\"text-muted-foreground\">\n          When you use our website, we collect and process the following types of data:\n        </p>\n        <ul className=\"text-muted-foreground list-inside list-disc space-y-2 pl-4\">\n          <li>\n            <strong>Web Analytics:</strong> Anonymous user data is collecting using PostHog.\n          </li>\n          <li>\n            <strong>Authentication Data:</strong> When you sign up, we collect necessary information\n            such as your email address.\n          </li>\n        </ul>\n      </section>\n\n      <section className=\"mb-8 space-y-4\">\n        <h2 className=\"text-xl font-semibold\">3. Sharing and Transferring Your Data</h2>\n        <p className=\"text-muted-foreground\">\n          We do not sell, lease, or trade your personal information. However, we may share your data\n          with trusted third parties like to process payments or provide other services on our\n          behalf. We ensure that all third-party providers we work with adhere to data protection\n          standards, in compliance with relevant laws such as the Information Technology Act 2000\n          and Digital Personal Data Protection Act 2023 under Indian law or any other applicable\n          laws.\n        </p>\n      </section>\n\n      <section className=\"mb-8 space-y-4\">\n        <h2 className=\"text-xl font-semibold\">4. Data Security</h2>\n        <p className=\"text-muted-foreground\">\n          We have implemented suitable technical and organizational measures as per the level of\n          risk to protect your personal data from unauthorized access, loss, or misuse.\n        </p>\n      </section>\n\n      <section className=\"mb-8 space-y-4\">\n        <h2 className=\"text-xl font-semibold\">5. Data Protection</h2>\n        <p className=\"text-muted-foreground\">\n          You have the following rights concerning your personal data:\n        </p>\n        <ul className=\"text-muted-foreground list-inside list-disc space-y-2 pl-4\">\n          <li>\n            <strong>Access:</strong> You have the right to request a copy of the personal data we\n            hold about you.\n          </li>\n          <li>\n            <strong>Correction:</strong> If any of your data is incorrect or incomplete, you can\n            request to have it updated.\n          </li>\n          <li>\n            <strong>Erasure:</strong> You can request that we delete your personal data, subject to\n            applicable legal exceptions.\n          </li>\n          <li>\n            <strong>Objection:</strong> You can object to the processing of your personal data for\n            certain purposes.\n          </li>\n          <li>\n            <strong>Restriction:</strong> You can ask us to restrict the use of your data under\n            certain conditions.\n          </li>\n          <li>\n            <strong>Data Portability:</strong> You have the right to transfer your personal data to\n            another service provider, if applicable.\n          </li>\n          <li>\n            <strong>Withdrawal of Consent:</strong> You can withdraw your consent to process your\n            personal data at any time.\n          </li>\n        </ul>\n      </section>\n\n      <section className=\"mb-8 space-y-4\">\n        <h2 className=\"text-xl font-semibold\">6. Cookies</h2>\n        <p className=\"text-muted-foreground\">\n          Our website uses essential cookies necessary for user authentication and session\n          management. These cookies help maintain your login status and ensure secure access to your\n          account. We do not use cookies for tracking or advertising purposes.\n        </p>\n      </section>\n\n      <section className=\"mb-8 space-y-4\">\n        <h2 className=\"text-xl font-semibold\">7. Modifications to This Privacy Policy</h2>\n        <p className=\"text-muted-foreground\">\n          We may update this privacy policy occasionally. Any changes will be posted here, and the\n          effective date will be updated at this page. It shall be assumed that you are aware of any\n          changes and accept the same.\n        </p>\n      </section>\n\n      <section className=\"mb-8 space-y-4\">\n        <h2 className=\"text-xl font-semibold\">8. Contact Us</h2>\n        <p className=\"text-muted-foreground\">\n          If you have any questions or concerns about this privacy policy, please reach out at{\" \"}\n          <a href=\"mailto:sahaj@tweakcn.com\" className=\"text-primary hover:underline\">\n            sahaj@tweakcn.com\n          </a>\n        </p>\n      </section>\n\n      <p className=\"text-muted-foreground mt-12 text-sm\">\n        By continuing to use this website, you confirm that you have read and understood this\n        Privacy Policy.\n      </p>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/ai/components/ai-announcement.tsx",
    "content": "\"use client\";\n\nimport { useSubscription } from \"@/hooks/use-subscription\";\nimport { ArrowRight } from \"lucide-react\";\nimport Link from \"next/link\";\n\nexport function AIAnnouncement() {\n  const { subscriptionStatus, isPending } = useSubscription();\n  const isPro = subscriptionStatus?.isSubscribed ?? false;\n\n  if (isPending || isPro) {\n    return null;\n  }\n\n  return (\n    <div className=\"mx-auto max-w-3xl\">\n      <Link\n        href=\"/pricing\"\n        className=\"group bg-muted flex items-center justify-between gap-2 rounded-full px-2 py-1.5 shadow-sm transition-all duration-200 hover:shadow-md\"\n      >\n        <span className=\"text-muted-foreground group-hover:text-foreground text-sm font-medium transition-colors\">\n          Upgrade to Pro for unlimited requests\n        </span>\n\n        <ArrowRight className=\"text-muted-foreground group-hover:text-foreground size-4 -rotate-45 transition-all group-hover:rotate-0\" />\n      </Link>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/ai/components/ai-chat-form.tsx",
    "content": "\"use client\";\n\nimport { AIChatFormBody } from \"@/components/editor/ai/ai-chat-form-body\";\nimport { AlertBanner } from \"@/components/editor/ai/alert-banner\";\nimport { EnhancePromptButton } from \"@/components/editor/ai/enhance-prompt-button\";\nimport { ImageUploader } from \"@/components/editor/ai/image-uploader\";\nimport ThemePresetSelect from \"@/components/editor/theme-preset-select\";\nimport { Button } from \"@/components/ui/button\";\nimport { useAIChatForm } from \"@/hooks/use-ai-chat-form\";\nimport { useAIEnhancePrompt } from \"@/hooks/use-ai-enhance-prompt\";\nimport { useGuards } from \"@/hooks/use-guards\";\nimport { useSubscription } from \"@/hooks/use-subscription\";\nimport { MAX_IMAGE_FILES } from \"@/lib/constants\";\nimport { cn } from \"@/lib/utils\";\nimport { AIPromptData } from \"@/types/ai\";\nimport { ArrowUp, Loader, StopCircle } from \"lucide-react\";\n\nexport function AIChatForm({\n  onThemeGeneration,\n  isGeneratingTheme,\n  onCancelThemeGeneration,\n}: {\n  onThemeGeneration: (promptData: AIPromptData) => void;\n  isGeneratingTheme: boolean;\n  onCancelThemeGeneration: () => void;\n}) {\n  const {\n    editorContentDraft,\n    handleContentChange,\n    promptData,\n    isEmptyPrompt,\n    clearLocalDraft,\n    uploadedImages,\n    fileInputRef,\n    handleImagesUpload,\n    handleImageRemove,\n    isSomeImageUploading,\n    isUserDragging,\n    isInitializing,\n  } = useAIChatForm();\n\n  const { checkValidSession, checkValidSubscription } = useGuards();\n  const { subscriptionStatus } = useSubscription();\n  const isPro = subscriptionStatus?.isSubscribed ?? false;\n  const hasFreeRequestsLeft = (subscriptionStatus?.requestsRemaining ?? 0) > 0;\n\n  const { startEnhance, stopEnhance, enhancedPromptAsJsonContent, isEnhancingPrompt } =\n    useAIEnhancePrompt();\n\n  const handleEnhancePrompt = () => {\n    if (!checkValidSession() || !checkValidSubscription()) return;\n\n    // Only send images that are not loading, and strip loading property\n    const images = uploadedImages.filter((img) => !img.loading).map(({ url }) => ({ url }));\n    startEnhance({ ...promptData, images });\n  };\n\n  const handleGenerate = async () => {\n    if (!checkValidSession() || !checkValidSubscription()) return; // Act as an early return\n\n    // Only send images that are not loading, and strip loading property\n    const images = uploadedImages.filter((img) => !img.loading).map(({ url }) => ({ url }));\n\n    // Proceed only if there is text, or at least one image\n    if (isEmptyPrompt && images.length === 0) return;\n\n    onThemeGeneration({\n      ...promptData,\n      content: promptData?.content ?? \"\",\n      mentions: promptData?.mentions ?? [],\n      images,\n    });\n\n    clearLocalDraft();\n  };\n\n  return (\n    <div className=\"@container/form relative transition-all contain-layout\">\n      <AlertBanner />\n\n      <div className=\"bg-background relative z-10 flex size-full min-h-[100px] flex-1 flex-col gap-2 overflow-hidden rounded-lg border p-2 shadow-xs\">\n        <AIChatFormBody\n          isUserDragging={isUserDragging}\n          disabled={isEnhancingPrompt}\n          canSubmit={\n            !isGeneratingTheme &&\n            !isEnhancingPrompt &&\n            !isEmptyPrompt &&\n            !isSomeImageUploading &&\n            !isInitializing\n          }\n          uploadedImages={uploadedImages}\n          handleImagesUpload={handleImagesUpload}\n          handleImageRemove={handleImageRemove}\n          handleContentChange={handleContentChange}\n          handleGenerate={handleGenerate}\n          initialEditorContent={editorContentDraft ?? undefined}\n          textareaKey={editorContentDraft ? \"with-draft\" : \"no-draft\"}\n          externalEditorContent={enhancedPromptAsJsonContent}\n          isStreamingContent={isEnhancingPrompt}\n        />\n\n        <div className=\"flex items-center justify-between gap-2\">\n          <div className=\"flex w-full max-w-64 items-center gap-2 overflow-hidden\">\n            <ThemePresetSelect\n              disabled={isGeneratingTheme || isEnhancingPrompt || isInitializing}\n              withCycleThemes={false}\n              variant=\"outline\"\n              size=\"sm\"\n              className=\"shadow-none\"\n            />\n          </div>\n\n          <div className=\"flex items-center gap-2\">\n            {(isPro || hasFreeRequestsLeft) && promptData?.content ? (\n              <EnhancePromptButton\n                isEnhancing={isEnhancingPrompt}\n                onStart={handleEnhancePrompt}\n                onStop={stopEnhance}\n                disabled={isGeneratingTheme || isInitializing}\n              />\n            ) : null}\n\n            <ImageUploader\n              fileInputRef={fileInputRef}\n              onImagesUpload={handleImagesUpload}\n              onClick={() => fileInputRef.current?.click()}\n              disabled={\n                isGeneratingTheme ||\n                isEnhancingPrompt ||\n                isInitializing ||\n                uploadedImages.some((img) => img.loading) ||\n                uploadedImages.length >= MAX_IMAGE_FILES\n              }\n            />\n\n            {isGeneratingTheme ? (\n              <Button\n                variant=\"destructive\"\n                size=\"sm\"\n                onClick={onCancelThemeGeneration}\n                className={cn(\"flex items-center gap-1\", \"@max-[350px]/form:w-8\")}\n              >\n                <StopCircle />\n                <span className=\"hidden @[350px]/form:inline-flex\">Stop</span>\n              </Button>\n            ) : (\n              <Button\n                size=\"icon\"\n                className=\"size-8 shadow-none\"\n                onClick={handleGenerate}\n                disabled={\n                  isEmptyPrompt ||\n                  isSomeImageUploading ||\n                  isGeneratingTheme ||\n                  isEnhancingPrompt ||\n                  isInitializing\n                }\n              >\n                {isGeneratingTheme ? <Loader className=\"animate-spin\" /> : <ArrowUp />}\n              </Button>\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/ai/components/ai-chat-hero.tsx",
    "content": "\"use client\";\n\nimport { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { useChatContext } from \"@/hooks/use-chat-context\";\nimport { useAIThemeGenerationCore } from \"@/hooks/use-ai-theme-generation-core\";\nimport { useGuards } from \"@/hooks/use-guards\";\nimport { usePostLoginAction } from \"@/hooks/use-post-login-action\";\nimport { usePreferencesStore } from \"@/store/preferences-store\";\nimport { AIPromptData } from \"@/types/ai\";\nimport { useRouter } from \"next/navigation\";\nimport { AIChatForm } from \"./ai-chat-form\";\nimport { ChatHeading } from \"./chat-heading\";\nimport { SuggestedPillActions } from \"./suggested-pill-actions\";\n\nexport function AIChatHero() {\n  const { startNewChat } = useChatContext();\n  const { generateThemeCore, isGeneratingTheme, cancelThemeGeneration } =\n    useAIThemeGenerationCore();\n  const { checkValidSession, checkValidSubscription } = useGuards();\n  const router = useRouter();\n\n  const { setChatSuggestionsOpen } = usePreferencesStore();\n\n  const handleRedirectAndThemeGeneration = (promptData: AIPromptData) => {\n    if (!checkValidSession(\"signup\", \"AI_GENERATE_FROM_PAGE\", { promptData })) return;\n    if (!checkValidSubscription()) return;\n\n    startNewChat();\n    setChatSuggestionsOpen(true);\n\n    generateThemeCore(promptData);\n    router.push(\"/editor/theme?tab=ai\");\n  };\n\n  usePostLoginAction(\"AI_GENERATE_FROM_PAGE\", ({ promptData }) => {\n    handleRedirectAndThemeGeneration(promptData);\n  });\n\n  return (\n    <div className=\"relative isolate flex w-full flex-1\">\n      <div className=\"@container relative isolate z-1 mx-auto flex max-w-[49rem] flex-1 flex-col justify-center px-4\">\n        <ChatHeading isGeneratingTheme={isGeneratingTheme} />\n\n        {/* Chat form input and suggestions */}\n        <div className=\"relative mx-auto flex w-full flex-col gap-2\">\n          <div className=\"relative isolate z-10 w-full\">\n            <AIChatForm\n              onThemeGeneration={handleRedirectAndThemeGeneration}\n              isGeneratingTheme={isGeneratingTheme}\n              onCancelThemeGeneration={cancelThemeGeneration}\n            />\n          </div>\n\n          {/* Quick suggestions */}\n          <HorizontalScrollArea className=\"mx-auto py-2\">\n            <SuggestedPillActions\n              onThemeGeneration={handleRedirectAndThemeGeneration}\n              isGeneratingTheme={isGeneratingTheme}\n            />\n          </HorizontalScrollArea>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/ai/components/chat-heading.tsx",
    "content": "export function ChatHeading({ isGeneratingTheme }: { isGeneratingTheme: boolean }) {\n  return (\n    <h1\n      style={\n        {\n          \"--gradient-accent\": isGeneratingTheme ? \"var(--foreground)\" : \"var(--foreground)\",\n          \"--gradient-base\": isGeneratingTheme ? \"var(--muted-foreground)\" : \"var(--foreground)\",\n        } as React.CSSProperties\n      }\n      className=\"animate-text bg-gradient-to-r from-(--gradient-base) via-(--gradient-accent) to-(--gradient-base) bg-[200%_auto] bg-clip-text pb-4 text-center text-[clamp(24px,7cqw,46px)] font-semibold tracking-tighter text-pretty text-transparent\"\n    >\n      What can I help you theme?\n    </h1>\n  );\n}\n"
  },
  {
    "path": "app/ai/components/community-theme-card.tsx",
    "content": "\"use client\";\n\nimport Logo from \"@/assets/logo.svg\";\nimport { Button } from \"@/components/ui/button\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { cn } from \"@/lib/utils\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { Theme, ThemePreset } from \"@/types/theme\";\nimport { Moon, MoreVertical, Sun } from \"lucide-react\";\n\n// This is repeating from `dashboard/components/theme-card.tsx`\ntype SwatchDefinition = {\n  name: string; // Text to display on hover\n  bgKey: keyof Theme[\"styles\"][\"light\" | \"dark\"]; // Key for background color\n  fgKey: keyof Theme[\"styles\"][\"light\" | \"dark\"]; // Key for text color\n};\n\n// This is repeating from `dashboard/components/theme-card.tsx`\nconst swatchDefinitions: SwatchDefinition[] = [\n  { name: \"Primary\", bgKey: \"primary\", fgKey: \"primary-foreground\" },\n  { name: \"Secondary\", bgKey: \"secondary\", fgKey: \"secondary-foreground\" },\n  { name: \"Accent\", bgKey: \"accent\", fgKey: \"accent-foreground\" },\n  { name: \"Muted\", bgKey: \"muted\", fgKey: \"muted-foreground\" },\n  // Special case: Background swatch shows \"Foreground\" text using the main foreground color\n  { name: \"Background\", bgKey: \"background\", fgKey: \"foreground\" },\n];\n\nexport function CommunityThemeCard({ themePreset }: { themePreset: ThemePreset }) {\n  const { themeState } = useEditorStore();\n  const mode = themeState.currentMode;\n\n  return (\n    <div className=\"group/card relative flex flex-col gap-2\">\n      <div className=\"group/preview relative flex h-56 overflow-hidden rounded-lg border\">\n        <div className=\"from-foreground/20 to-foreground-10 absolute inset-0 z-1 flex flex-col items-center justify-center gap-2 bg-gradient-to-b opacity-0 transition-opacity duration-300 ease-in-out group-hover/preview:opacity-100\">\n          <Button variant=\"outline\" size=\"sm\" className=\"w-28 drop-shadow\">\n            View Details\n          </Button>\n          <Button size=\"sm\" className=\"w-28 drop-shadow\">\n            View in Editor\n          </Button>\n        </div>\n\n        {/* TEMPORARY CARD IMPLEMENTATION: Based on `dashboard/components/theme-card.tsx` */}\n        <div className=\"relative isolate flex flex-1 flex-col\">\n          {/* Light mode swatches */}\n          <div className=\"group relative flex flex-1\">\n            <div className=\"absolute top-2 left-2 z-10 size-6\">\n              <Sun className=\"size-full opacity-80 drop-shadow-2xl transition-opacity group-hover/preview:opacity-100\" />\n            </div>\n            {swatchDefinitions.map((swatch) => (\n              <div\n                key={swatch.name + swatch.bgKey + \"light\"}\n                className={cn(\n                  \"group/swatch relative h-full flex-1 transition-all duration-300 ease-in-out\"\n                )}\n                style={{ backgroundColor: themePreset.styles.light[swatch.bgKey] }}\n              ></div>\n            ))}\n          </div>\n\n          {/* Dark mode swatches */}\n          <div className=\"group relative flex flex-1\">\n            <div className=\"absolute right-2 bottom-2 z-10 size-6\">\n              <Moon className=\"size-full opacity-80 drop-shadow-2xl transition-opacity group-hover/preview:opacity-100\" />\n            </div>\n            {swatchDefinitions.map((swatch) => (\n              <div\n                key={swatch.name + swatch.bgKey + \"dark\"}\n                className={cn(\n                  \"group/swatch relative h-full flex-1 transition-all duration-300 ease-in-out\"\n                )}\n                style={{ backgroundColor: themePreset.styles.dark[swatch.bgKey] }}\n              ></div>\n            ))}\n          </div>\n        </div>\n      </div>\n\n      <div className=\"flex items-center gap-2\">\n        <div\n          className=\"from-primary to-secondary aspect-square size-10 rounded-full border bg-linear-150/oklch\"\n          style={\n            {\n              \"--primary\": themePreset.styles[mode].primary,\n              \"--primary-foreground\": themePreset.styles[mode][\"primary-foreground\"],\n              \"--secondary\": themePreset.styles[mode].secondary,\n            } as React.CSSProperties\n          }\n        >\n          <Logo className=\"text-primary-foreground size-full p-2 drop-shadow-lg\" />\n        </div>\n        <div className=\"flex flex-col gap-1\">\n          <h3 className=\"line-clamp-1 text-sm leading-none font-medium\">{themePreset.label}</h3>\n          <p className=\"text-muted-foreground line-clamp-1 text-xs\">\n            {themePreset.createdAt ?? \"Unknown creation date\"}\n          </p>\n        </div>\n\n        <Button variant=\"ghost\" size=\"icon\" className=\"ml-auto\">\n          <MoreVertical />\n        </Button>\n      </div>\n    </div>\n  );\n}\n\nexport function CommunityThemeCardSkeleton() {\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <Skeleton className=\"h-56 w-full rounded-lg\" />\n\n      <div className=\"flex items-center gap-2\">\n        <Skeleton className=\"aspect-square size-10 rounded-full\" />\n        <div className=\"flex w-full flex-col gap-1.5\">\n          <Skeleton className=\"line-clamp-1 h-4 w-1/2 rounded-lg leading-none font-medium\" />\n          <Skeleton className=\"text-muted-foreground h-3 w-1/3 rounded-lg\" />\n        </div>\n\n        <MoreVertical className=\"text-muted ml-auto animate-pulse\" />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/ai/components/community-themes.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport { ChevronRight } from \"lucide-react\";\nimport { Suspense } from \"react\";\nimport { CommunityThemeCard, CommunityThemeCardSkeleton } from \"./community-theme-card\";\nimport { ThemePreset } from \"@/types/theme\";\nimport { defaultPresets } from \"@/utils/theme-presets\";\n\n// TODO: Remove this once we have a real API to fetch the community themes\nconst getDefaultThemePresets = async () => {\n  await new Promise((resolve) => setTimeout(resolve, 300));\n  return defaultPresets;\n};\n\nexport async function CommunityThemes() {\n  const themePresetsPromise = getDefaultThemePresets();\n\n  return (\n    <>\n      <div className=\"flex flex-col gap-1\">\n        <div className=\"flex items-center justify-between\">\n          <h2 className=\"font-semibold\">From the Community</h2>\n          <Button variant=\"link\" className=\"h-fit gap-1 p-0 [&>svg]:size-3\">\n            View All <ChevronRight />\n          </Button>\n        </div>\n        <p className=\"text-muted-foreground text-sm\">\n          Explore the themes the community is creating with tweakcn.\n        </p>\n      </div>\n\n      <div className=\"grid grid-cols-1 justify-center gap-6 sm:grid-cols-2 lg:grid-cols-3\">\n        <Suspense\n          fallback={\n            <>\n              <CommunityThemeCardSkeleton />\n              <CommunityThemeCardSkeleton />\n              <CommunityThemeCardSkeleton />\n            </>\n          }\n        >\n          <CommunityThemeCards themePresetsPromise={themePresetsPromise} />\n        </Suspense>\n      </div>\n    </>\n  );\n}\n\ninterface CommunityThemeCardsProps {\n  themePresetsPromise: Promise<Record<string, ThemePreset>>;\n}\n\nexport async function CommunityThemeCards({ themePresetsPromise }: CommunityThemeCardsProps) {\n  const themePresets = await themePresetsPromise;\n  const presets = Object.entries(themePresets).reduce(\n    (acc, [id, preset]) => {\n      acc[id] = {\n        label: preset.label,\n        styles: preset.styles,\n      };\n      return acc;\n    },\n    {} as Record<string, ThemePreset>\n  );\n\n  return (\n    <>\n      {Object.values(presets).map((preset) => (\n        <CommunityThemeCard key={preset.label} themePreset={preset} />\n      ))}\n    </>\n  );\n}\n"
  },
  {
    "path": "app/ai/components/suggested-pill-actions.tsx",
    "content": "\"use client\";\n\nimport { PillActionButton } from \"@/components/editor/ai/pill-action-button\";\nimport { useImageUpload } from \"@/hooks/use-image-upload\";\nimport { imageUploadReducer } from \"@/hooks/use-image-upload-reducer\";\nimport { MAX_IMAGE_FILE_SIZE } from \"@/lib/constants\";\nimport { AIPromptData } from \"@/types/ai\";\nimport { createCurrentThemePrompt } from \"@/utils/ai/ai-prompt\";\nimport { PROMPTS } from \"@/utils/ai/prompts\";\nimport { ImageIcon, Sparkles } from \"lucide-react\";\nimport { useEffect, useReducer } from \"react\";\n\nexport function SuggestedPillActions({\n  onThemeGeneration,\n  isGeneratingTheme,\n}: {\n  onThemeGeneration: (promptData: AIPromptData) => void;\n  isGeneratingTheme: boolean;\n}) {\n  const [uploadedImages, dispatch] = useReducer(imageUploadReducer, []);\n\n  const { fileInputRef, handleImagesUpload, canUploadMore, isSomeImageUploading } = useImageUpload({\n    maxFiles: 1,\n    maxFileSize: MAX_IMAGE_FILE_SIZE,\n    images: uploadedImages,\n    dispatch,\n  });\n\n  // Automatically send prompt when an image is selected and loaded\n  useEffect(() => {\n    if (uploadedImages.length > 0 && !isSomeImageUploading) {\n      onThemeGeneration({\n        content: \"\", // No text prompt\n        mentions: [], // No mentions\n        images: [uploadedImages[0]],\n      });\n    }\n\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [uploadedImages, isSomeImageUploading]);\n\n  const handleSetPrompt = async (prompt: string) => {\n    const promptData = createCurrentThemePrompt({ prompt });\n    onThemeGeneration(promptData);\n  };\n\n  const handleImageButtonClick = () => {\n    if (canUploadMore && fileInputRef.current) {\n      fileInputRef.current.click();\n    }\n  };\n\n  const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {\n    const fileList = event.target.files;\n    if (!fileList) return;\n\n    const files = Array.from(fileList);\n    handleImagesUpload(files);\n  };\n\n  return (\n    <>\n      <PillActionButton onClick={handleImageButtonClick} disabled={isGeneratingTheme}>\n        <input\n          type=\"file\"\n          accept=\"image/*\"\n          multiple={false}\n          ref={fileInputRef}\n          onChange={handleImageUpload}\n          disabled={isGeneratingTheme}\n          style={{ display: \"none\" }}\n        />\n        <ImageIcon /> From an Image\n      </PillActionButton>\n\n      {Object.entries(PROMPTS).map(([key, { label, prompt }]) => (\n        <PillActionButton\n          key={key}\n          onClick={() => handleSetPrompt(prompt)}\n          disabled={isGeneratingTheme}\n        >\n          <Sparkles /> {label}\n        </PillActionButton>\n      ))}\n    </>\n  );\n}\n"
  },
  {
    "path": "app/ai/layout.tsx",
    "content": "import { Header } from \"@/components/header\";\n\nexport default function AiLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"relative isolate flex min-h-svh flex-col\">\n      <Header />\n      <main className=\"isolate flex flex-1 flex-col overflow-y-auto\">{children}</main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/ai/loading.tsx",
    "content": "import { Loading } from \"@/components/loading\";\n\nexport default function AiLoading() {\n  return <Loading className=\"flex-1\" />;\n}\n"
  },
  {
    "path": "app/ai/page.tsx",
    "content": "import { type Metadata } from \"next\";\nimport { AIAnnouncement } from \"./components/ai-announcement\";\nimport { AIChatHero } from \"./components/ai-chat-hero\";\n\nexport const metadata: Metadata = {\n  title: \"Image to shadcn/ui theme. Generate with AI — tweakcn\",\n  description:\n    \"Transform images into stunning shadcn/ui themes instantly with tweakcn's AI theme generator. Upload any image or describe your vision—our AI creates custom Tailwind CSS themes with real-time preview. Perfect for developers who want beautiful, production-ready themes in seconds.\",\n  keywords:\n    \"ai theme generator, image to theme, shadcn/ui themes, tailwind css generator, ai design tool, theme from image, ui customization, tweakcn, visual theme creator, color palette generator, design system ai, frontend theming, web design automation\",\n  robots: \"index, follow\",\n};\n\nexport default function AiPage() {\n  return (\n    <div className=\"relative isolate container mx-auto flex flex-1 flex-col gap-24 overflow-x-hidden overflow-y-auto px-4 md:px-6\">\n      {/* AI Chat entry point section */}\n      <section className=\"relative isolate flex flex-col gap-4 pt-28 lg:pt-44\">\n        <AIAnnouncement />\n        <AIChatHero />\n      </section>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/api/auth/[...all]/route.ts",
    "content": "import { auth } from \"@/lib/auth\";\nimport { toNextJsHandler } from \"better-auth/next-js\";\n\nexport const { GET, POST } = toNextJsHandler(auth.handler);\n"
  },
  {
    "path": "app/api/enhance-prompt/route.ts",
    "content": "import { ENHANCE_PROMPT_SYSTEM } from \"@/lib/ai/prompts\";\nimport { baseProviderOptions, myProvider } from \"@/lib/ai/providers\";\nimport { handleError } from \"@/lib/error-response\";\nimport { requireSubscriptionOrFreeUsage } from \"@/lib/subscription\";\nimport { AIPromptData } from \"@/types/ai\";\nimport { buildUserContentPartsFromPromptData } from \"@/utils/ai/message-converter\";\nimport { smoothStream, streamText } from \"ai\";\nimport { NextRequest } from \"next/server\";\n\nexport async function POST(req: NextRequest) {\n  try {\n    await requireSubscriptionOrFreeUsage(req);\n\n    const body = await req.json();\n    const { prompt: _prompt, promptData }: { prompt: string; promptData: AIPromptData } = body;\n    const userContentParts = buildUserContentPartsFromPromptData(promptData);\n\n    const result = streamText({\n      system: ENHANCE_PROMPT_SYSTEM,\n      messages: [\n        {\n          role: \"user\",\n          content: userContentParts,\n        },\n      ],\n      model: myProvider.languageModel(\"prompt-enhancement\"),\n      providerOptions: baseProviderOptions,\n      experimental_transform: smoothStream({\n        delayInMs: 10,\n        chunking: \"word\",\n      }),\n    });\n\n    return result.toUIMessageStreamResponse();\n  } catch (error) {\n    return handleError(error, { route: \"/api/enhance-prompt\" });\n  }\n}\n"
  },
  {
    "path": "app/api/generate-theme/route.ts",
    "content": "import { recordAIUsage } from \"@/actions/ai-usage\";\nimport { THEME_GENERATION_TOOLS } from \"@/lib/ai/generate-theme/tools\";\nimport { GENERATE_THEME_SYSTEM } from \"@/lib/ai/prompts\";\nimport { baseProviderOptions, myProvider } from \"@/lib/ai/providers\";\nimport { handleError } from \"@/lib/error-response\";\nimport { getCurrentUserId, logError } from \"@/lib/shared\";\nimport { validateSubscriptionAndUsage } from \"@/lib/subscription\";\nimport { AdditionalAIContext, ChatMessage } from \"@/types/ai\";\nimport { SubscriptionRequiredError } from \"@/types/errors\";\nimport { convertMessagesToModelMessages } from \"@/utils/ai/message-converter\";\nimport { Ratelimit } from \"@upstash/ratelimit\";\nimport { kv } from \"@vercel/kv\";\nimport { createUIMessageStream, createUIMessageStreamResponse, stepCountIs, streamText } from \"ai\";\nimport { headers } from \"next/headers\";\nimport { NextRequest } from \"next/server\";\n\nconst ratelimit = new Ratelimit({\n  redis: kv,\n  limiter: Ratelimit.fixedWindow(5, \"60s\"),\n});\n\nexport async function POST(req: NextRequest) {\n  try {\n    const userId = await getCurrentUserId(req);\n    const headersList = await headers();\n\n    if (process.env.NODE_ENV !== \"development\") {\n      const ip = headersList.get(\"x-forwarded-for\") ?? \"anonymous\";\n      const { success, limit, reset, remaining } = await ratelimit.limit(ip);\n\n      if (!success) {\n        return new Response(\"Rate limit exceeded. Please try again later.\", {\n          status: 429,\n          headers: {\n            \"X-RateLimit-Limit\": limit.toString(),\n            \"X-RateLimit-Remaining\": remaining.toString(),\n            \"X-RateLimit-Reset\": reset.toString(),\n          },\n        });\n      }\n    }\n\n    const subscriptionCheck = await validateSubscriptionAndUsage(userId);\n\n    if (!subscriptionCheck.canProceed) {\n      throw new SubscriptionRequiredError(subscriptionCheck.error, {\n        requestsRemaining: subscriptionCheck.requestsRemaining,\n      });\n    }\n\n    const { messages }: { messages: ChatMessage[] } = await req.json();\n    const modelMessages = await convertMessagesToModelMessages(messages);\n\n    const stream = createUIMessageStream<ChatMessage>({\n      execute: ({ writer }) => {\n        const context: AdditionalAIContext = { writer };\n        const model = myProvider.languageModel(\"base\");\n\n        const result = streamText({\n          abortSignal: req.signal,\n          model: model,\n          providerOptions: baseProviderOptions,\n          system: GENERATE_THEME_SYSTEM,\n          messages: modelMessages,\n          tools: THEME_GENERATION_TOOLS,\n          stopWhen: stepCountIs(5),\n          onError: (error) => {\n            if (error instanceof Error) console.error(error);\n          },\n          onFinish: async (result) => {\n            const { totalUsage } = result;\n            try {\n              await recordAIUsage({\n                modelId: model.modelId,\n                promptTokens: totalUsage.inputTokens,\n                completionTokens: totalUsage.outputTokens,\n              });\n            } catch (error) {\n              logError(error as Error, { action: \"recordAIUsage\", totalUsage });\n            }\n          },\n          experimental_context: context,\n        });\n\n        writer.merge(\n          result.toUIMessageStream({\n            messageMetadata: ({ part }) => {\n              // `toolName` is not typed for some reason, must be kept in sync with the actual tool names\n              if (part.type === \"tool-result\" && part.toolName === \"generateTheme\") {\n                return { themeStyles: part.output };\n              }\n            },\n          })\n        );\n      },\n    });\n\n    return createUIMessageStreamResponse({ stream });\n  } catch (error) {\n    if (\n      error instanceof Error &&\n      (error.name === \"AbortError\" || error.name === \"ResponseAborted\")\n    ) {\n      return new Response(\"Request aborted by user\", { status: 499 });\n    }\n\n    return handleError(error, { route: \"/api/generate-theme\" });\n  }\n}\n"
  },
  {
    "path": "app/api/google-fonts/route.ts",
    "content": "import { PaginatedFontsResponse } from \"@/types/fonts\";\nimport { FALLBACK_FONTS } from \"@/utils/fonts\";\nimport { fetchGoogleFonts } from \"@/utils/fonts/google-fonts\";\nimport { unstable_cache } from \"next/cache\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\nconst cachedFetchGoogleFonts = unstable_cache(fetchGoogleFonts, [\"google-fonts-catalogue\"], {\n  tags: [\"google-fonts-catalogue\"],\n});\n\nexport async function GET(request: NextRequest) {\n  try {\n    const { searchParams } = new URL(request.url);\n    const query = searchParams.get(\"q\")?.toLowerCase() || \"\";\n    const category = searchParams.get(\"category\")?.toLowerCase();\n    const limit = Math.min(Number(searchParams.get(\"limit\")) || 50, 100);\n    const offset = Number(searchParams.get(\"offset\")) || 0;\n\n    let googleFonts = FALLBACK_FONTS;\n\n    try {\n      googleFonts = await cachedFetchGoogleFonts(process.env.GOOGLE_FONTS_API_KEY);\n    } catch (error) {\n      console.error(\"Error fetching Google Fonts:\", error);\n      console.log(\"Using fallback fonts\");\n    }\n\n    // Filter fonts based on search query and category\n    let filteredFonts = googleFonts;\n\n    if (query) {\n      filteredFonts = filteredFonts.filter((font) => font.family.toLowerCase().includes(query));\n    }\n\n    if (category && category !== \"all\") {\n      filteredFonts = filteredFonts.filter((font) => font.category === category);\n    }\n\n    const paginatedFonts = filteredFonts.slice(offset, offset + limit);\n\n    const response: PaginatedFontsResponse = {\n      fonts: paginatedFonts,\n      total: filteredFonts.length,\n      offset,\n      limit,\n      hasMore: offset + limit < filteredFonts.length,\n    };\n\n    return NextResponse.json(response);\n  } catch (error) {\n    console.error(\"Error in Google Fonts API:\", error);\n    return NextResponse.json({ error: \"Failed to fetch fonts\" }, { status: 500 });\n  }\n}\n"
  },
  {
    "path": "app/api/oauth/app-info/route.ts",
    "content": "import { db } from \"@/db\";\nimport { oauthApp } from \"@/db/schema\";\nimport { oauthError } from \"@/lib/oauth\";\nimport { eq, and } from \"drizzle-orm\";\nimport { NextRequest } from \"next/server\";\n\nexport async function GET(req: NextRequest) {\n  const clientId = req.nextUrl.searchParams.get(\"client_id\");\n\n  if (!clientId) {\n    return oauthError(\"invalid_request\", \"Missing client_id\");\n  }\n\n  const [app] = await db\n    .select({ name: oauthApp.name, description: oauthApp.description })\n    .from(oauthApp)\n    .where(and(eq(oauthApp.clientId, clientId), eq(oauthApp.isActive, true)))\n    .limit(1);\n\n  if (!app) {\n    return oauthError(\"invalid_client\", \"Unknown client_id\");\n  }\n\n  return Response.json({ name: app.name, description: app.description });\n}\n"
  },
  {
    "path": "app/api/oauth/authorize/route.ts",
    "content": "import { db } from \"@/db\";\nimport { oauthApp, oauthAuthorizationCode } from \"@/db/schema\";\nimport { OAUTH_AUTHORIZATION_CODE_EXPIRY_SECONDS } from \"@/lib/constants\";\nimport {\n  generateSecureToken,\n  oauthError,\n  parseScopes,\n  validateRedirectUri,\n  validateScopes,\n} from \"@/lib/oauth\";\nimport { auth } from \"@/lib/auth\";\nimport { eq, and } from \"drizzle-orm\";\nimport { headers } from \"next/headers\";\nimport cuid from \"cuid\";\nimport { NextRequest } from \"next/server\";\n\nexport async function GET(req: NextRequest) {\n  const params = req.nextUrl.searchParams;\n\n  const clientId = params.get(\"client_id\");\n  const redirectUri = params.get(\"redirect_uri\");\n  const responseType = params.get(\"response_type\");\n  const scopeParam = params.get(\"scope\");\n  const state = params.get(\"state\");\n  const codeChallenge = params.get(\"code_challenge\");\n  const codeChallengeMethod = params.get(\"code_challenge_method\") ?? \"S256\";\n\n  // Validate required params\n  if (!clientId || !redirectUri || !responseType || !scopeParam) {\n    return oauthError(\n      \"invalid_request\",\n      \"Missing required parameters: client_id, redirect_uri, response_type, scope\"\n    );\n  }\n\n  if (responseType !== \"code\") {\n    return oauthError(\n      \"unsupported_response_type\",\n      \"Only response_type=code is supported\"\n    );\n  }\n\n  // Look up OAuth app\n  const [app] = await db\n    .select({ id: oauthApp.id, redirectUris: oauthApp.redirectUris })\n    .from(oauthApp)\n    .where(and(eq(oauthApp.clientId, clientId), eq(oauthApp.isActive, true)))\n    .limit(1);\n\n  if (!app) {\n    return oauthError(\"invalid_client\", \"Unknown client_id\");\n  }\n\n  // Validate redirect URI\n  if (!validateRedirectUri(redirectUri, app.redirectUris)) {\n    return oauthError(\"invalid_request\", \"redirect_uri not registered\");\n  }\n\n  // Validate scopes\n  const scopes = parseScopes(scopeParam);\n  if (!validateScopes(scopes)) {\n    return oauthError(\"invalid_scope\", \"Invalid or unsupported scope\");\n  }\n\n  // Check that the user is logged in\n  const session = await auth.api.getSession({ headers: await headers() });\n  if (!session?.user?.id) {\n    // Redirect to the OAuth authorize page which handles sign-in\n    const pageUrl = new URL(\"/oauth/authorize\", req.nextUrl.origin);\n    req.nextUrl.searchParams.forEach((value, key) => {\n      pageUrl.searchParams.set(key, value);\n    });\n    return Response.redirect(pageUrl.toString(), 302);\n  }\n\n  // Generate authorization code\n  const code = generateSecureToken();\n  const now = new Date();\n\n  await db.insert(oauthAuthorizationCode).values({\n    id: cuid(),\n    code,\n    appId: app.id,\n    userId: session.user.id,\n    scopes,\n    redirectUri,\n    codeChallenge: codeChallenge ?? null,\n    codeChallengeMethod: codeChallenge ? codeChallengeMethod : null,\n    expiresAt: new Date(\n      now.getTime() + OAUTH_AUTHORIZATION_CODE_EXPIRY_SECONDS * 1000\n    ),\n    createdAt: now,\n  });\n\n  // Redirect back to the app with the code\n  const redirectUrl = new URL(redirectUri);\n  redirectUrl.searchParams.set(\"code\", code);\n  if (state) redirectUrl.searchParams.set(\"state\", state);\n\n  return Response.redirect(redirectUrl.toString(), 302);\n}\n"
  },
  {
    "path": "app/api/oauth/revoke/route.ts",
    "content": "import { db } from \"@/db\";\nimport { oauthToken } from \"@/db/schema\";\nimport { hashToken, oauthError } from \"@/lib/oauth\";\nimport { eq, or } from \"drizzle-orm\";\nimport { NextRequest } from \"next/server\";\n\nexport async function POST(req: NextRequest) {\n  const body = await req.formData().catch(() => null);\n  if (!body) {\n    return oauthError(\"invalid_request\", \"Request body must be form-encoded\");\n  }\n\n  const token = body.get(\"token\") as string | null;\n  if (!token) {\n    return oauthError(\"invalid_request\", \"Missing required parameter: token\");\n  }\n\n  const tokenHash = hashToken(token);\n\n  // Try to match as access token or refresh token\n  const [record] = await db\n    .select({ id: oauthToken.id })\n    .from(oauthToken)\n    .where(\n      or(\n        eq(oauthToken.accessTokenHash, tokenHash),\n        eq(oauthToken.refreshTokenHash, tokenHash)\n      )\n    )\n    .limit(1);\n\n  if (record) {\n    await db\n      .update(oauthToken)\n      .set({ revokedAt: new Date(), updatedAt: new Date() })\n      .where(eq(oauthToken.id, record.id));\n  }\n\n  // RFC 7009: always return 200 even if token not found\n  return new Response(null, { status: 200 });\n}\n"
  },
  {
    "path": "app/api/oauth/token/route.ts",
    "content": "import { db } from \"@/db\";\nimport { oauthAuthorizationCode, oauthToken } from \"@/db/schema\";\nimport {\n  authenticateClient,\n  createTokenPair,\n  hashToken,\n  oauthError,\n  verifyCodeChallenge,\n} from \"@/lib/oauth\";\nimport { eq, and, isNull } from \"drizzle-orm\";\nimport { NextRequest } from \"next/server\";\n\nexport async function POST(req: NextRequest) {\n  const body = await req.formData().catch(() => null);\n  if (!body) {\n    return oauthError(\"invalid_request\", \"Request body must be form-encoded\");\n  }\n\n  const grantType = body.get(\"grant_type\") as string | null;\n\n  if (grantType === \"authorization_code\") {\n    return handleAuthorizationCode(body);\n  }\n\n  if (grantType === \"refresh_token\") {\n    return handleRefreshToken(body);\n  }\n\n  return oauthError(\"unsupported_grant_type\", \"Supported: authorization_code, refresh_token\");\n}\n\nasync function handleAuthorizationCode(body: FormData) {\n  const clientId = body.get(\"client_id\") as string | null;\n  const clientSecret = body.get(\"client_secret\") as string | null;\n  const code = body.get(\"code\") as string | null;\n  const redirectUri = body.get(\"redirect_uri\") as string | null;\n  const codeVerifier = body.get(\"code_verifier\") as string | null;\n\n  if (!clientId || !clientSecret || !code || !redirectUri) {\n    return oauthError(\n      \"invalid_request\",\n      \"Missing required parameters: client_id, client_secret, code, redirect_uri\"\n    );\n  }\n\n  // Authenticate the client\n  const app = await authenticateClient(clientId, clientSecret);\n  if (!app) {\n    return oauthError(\"invalid_client\", \"Invalid client credentials\", 401);\n  }\n\n  // Look up the authorization code\n  const [authCode] = await db\n    .select({\n      id: oauthAuthorizationCode.id,\n      expiresAt: oauthAuthorizationCode.expiresAt,\n      redirectUri: oauthAuthorizationCode.redirectUri,\n      codeChallenge: oauthAuthorizationCode.codeChallenge,\n      codeChallengeMethod: oauthAuthorizationCode.codeChallengeMethod,\n      userId: oauthAuthorizationCode.userId,\n      scopes: oauthAuthorizationCode.scopes,\n    })\n    .from(oauthAuthorizationCode)\n    .where(\n      and(\n        eq(oauthAuthorizationCode.code, code),\n        eq(oauthAuthorizationCode.appId, app.id),\n        isNull(oauthAuthorizationCode.usedAt)\n      )\n    )\n    .limit(1);\n\n  if (!authCode) {\n    return oauthError(\"invalid_grant\", \"Invalid or already used authorization code\");\n  }\n\n  // Check expiry\n  if (new Date() > authCode.expiresAt) {\n    return oauthError(\"invalid_grant\", \"Authorization code expired\");\n  }\n\n  // Check redirect URI matches\n  if (authCode.redirectUri !== redirectUri) {\n    return oauthError(\"invalid_grant\", \"redirect_uri mismatch\");\n  }\n\n  // Verify PKCE if code challenge was provided during authorization\n  if (authCode.codeChallenge) {\n    if (!codeVerifier) {\n      return oauthError(\"invalid_request\", \"code_verifier required for PKCE\");\n    }\n    if (\n      !verifyCodeChallenge(\n        codeVerifier,\n        authCode.codeChallenge,\n        authCode.codeChallengeMethod ?? \"S256\"\n      )\n    ) {\n      return oauthError(\"invalid_grant\", \"PKCE verification failed\");\n    }\n  }\n\n  // Mark code as used\n  await db\n    .update(oauthAuthorizationCode)\n    .set({ usedAt: new Date() })\n    .where(eq(oauthAuthorizationCode.id, authCode.id));\n\n  // Create tokens\n  const tokenResponse = await createTokenPair(\n    app.id,\n    authCode.userId,\n    authCode.scopes\n  );\n\n  return Response.json(tokenResponse);\n}\n\nasync function handleRefreshToken(body: FormData) {\n  const clientId = body.get(\"client_id\") as string | null;\n  const clientSecret = body.get(\"client_secret\") as string | null;\n  const refreshToken = body.get(\"refresh_token\") as string | null;\n\n  if (!clientId || !clientSecret || !refreshToken) {\n    return oauthError(\n      \"invalid_request\",\n      \"Missing required parameters: client_id, client_secret, refresh_token\"\n    );\n  }\n\n  const app = await authenticateClient(clientId, clientSecret);\n  if (!app) {\n    return oauthError(\"invalid_client\", \"Invalid client credentials\", 401);\n  }\n\n  // Look up the refresh token\n  const refreshTokenHash = hashToken(refreshToken);\n  const [tokenRecord] = await db\n    .select({\n      id: oauthToken.id,\n      userId: oauthToken.userId,\n      scopes: oauthToken.scopes,\n      refreshTokenExpiresAt: oauthToken.refreshTokenExpiresAt,\n    })\n    .from(oauthToken)\n    .where(\n      and(\n        eq(oauthToken.refreshTokenHash, refreshTokenHash),\n        eq(oauthToken.appId, app.id),\n        isNull(oauthToken.revokedAt)\n      )\n    )\n    .limit(1);\n\n  if (!tokenRecord) {\n    return oauthError(\"invalid_grant\", \"Invalid refresh token\");\n  }\n\n  if (\n    tokenRecord.refreshTokenExpiresAt &&\n    new Date() > tokenRecord.refreshTokenExpiresAt\n  ) {\n    return oauthError(\"invalid_grant\", \"Refresh token expired\");\n  }\n\n  // Revoke the old token pair\n  await db\n    .update(oauthToken)\n    .set({ revokedAt: new Date(), updatedAt: new Date() })\n    .where(eq(oauthToken.id, tokenRecord.id));\n\n  // Issue new token pair\n  const tokenResponse = await createTokenPair(\n    app.id,\n    tokenRecord.userId,\n    tokenRecord.scopes\n  );\n\n  return Response.json(tokenResponse);\n}\n"
  },
  {
    "path": "app/api/oauth/userinfo/route.ts",
    "content": "import { db } from \"@/db\";\nimport { user as userTable } from \"@/db/schema\";\nimport { oauthError, requireScope, resolveUserFromBearerToken } from \"@/lib/oauth\";\nimport { eq } from \"drizzle-orm\";\nimport { NextRequest } from \"next/server\";\n\n/**\n * OpenID Connect-style userinfo endpoint.\n * Returns flat user fields for compatibility with generic OAuth clients\n * (e.g. Better Auth's genericOAuth plugin).\n */\nexport async function GET(req: NextRequest) {\n  const tokenData = await resolveUserFromBearerToken(\n    req.headers.get(\"authorization\")\n  );\n\n  if (!tokenData) {\n    return oauthError(\"invalid_token\", \"Invalid or expired access token\", 401);\n  }\n\n  if (!requireScope(tokenData.scopes, \"profile:read\")) {\n    return oauthError(\"insufficient_scope\", \"Requires profile:read scope\", 403);\n  }\n\n  const [profile] = await db\n    .select({\n      id: userTable.id,\n      name: userTable.name,\n      email: userTable.email,\n      image: userTable.image,\n    })\n    .from(userTable)\n    .where(eq(userTable.id, tokenData.userId))\n    .limit(1);\n\n  if (!profile) {\n    return oauthError(\"invalid_token\", \"User not found\", 401);\n  }\n\n  return Response.json({\n    sub: profile.id,\n    name: profile.name,\n    email: profile.email,\n    picture: profile.image,\n  });\n}\n"
  },
  {
    "path": "app/api/subscription/route.ts",
    "content": "import { getCurrentUserId, logError } from \"@/lib/shared\";\nimport { validateSubscriptionAndUsage } from \"@/lib/subscription\";\nimport { SubscriptionStatus } from \"@/types/subscription\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\nexport async function GET(request: NextRequest) {\n  try {\n    const userId = await getCurrentUserId(request);\n    const { isSubscribed, requestsRemaining, requestsUsed } =\n      await validateSubscriptionAndUsage(userId);\n\n    const response: SubscriptionStatus = {\n      isSubscribed,\n      requestsRemaining,\n      requestsUsed,\n    };\n\n    return NextResponse.json(response);\n  } catch (error) {\n    logError(error as Error);\n    return NextResponse.json({ error: \"Internal Server Error\" }, { status: 500 });\n  }\n}\n"
  },
  {
    "path": "app/api/v1/me/route.ts",
    "content": "import { db } from \"@/db\";\nimport { user as userTable } from \"@/db/schema\";\nimport { oauthError, requireAuth } from \"@/lib/oauth\";\nimport { eq } from \"drizzle-orm\";\nimport { NextRequest } from \"next/server\";\n\nexport async function GET(req: NextRequest) {\n  const auth = await requireAuth(req, \"profile:read\");\n  if (auth.error) return auth.error;\n\n  const [profile] = await db\n    .select({\n      id: userTable.id,\n      name: userTable.name,\n      email: userTable.email,\n      image: userTable.image,\n    })\n    .from(userTable)\n    .where(eq(userTable.id, auth.tokenData.userId))\n    .limit(1);\n\n  if (!profile) {\n    return oauthError(\"invalid_token\", \"User not found\", 401);\n  }\n\n  return Response.json({ data: profile });\n}\n"
  },
  {
    "path": "app/api/v1/themes/[themeId]/route.ts",
    "content": "import { db } from \"@/db\";\nimport { theme as themeTable } from \"@/db/schema\";\nimport { oauthError, requireAuth } from \"@/lib/oauth\";\nimport { eq, and } from \"drizzle-orm\";\nimport { NextRequest } from \"next/server\";\n\nexport async function GET(\n  req: NextRequest,\n  { params }: { params: Promise<{ themeId: string }> }\n) {\n  const auth = await requireAuth(req, \"themes:read\");\n  if (auth.error) return auth.error;\n\n  const { themeId } = await params;\n\n  const [theme] = await db\n    .select({\n      id: themeTable.id,\n      name: themeTable.name,\n      styles: themeTable.styles,\n      createdAt: themeTable.createdAt,\n      updatedAt: themeTable.updatedAt,\n    })\n    .from(themeTable)\n    .where(\n      and(eq(themeTable.id, themeId), eq(themeTable.userId, auth.tokenData.userId))\n    )\n    .limit(1);\n\n  if (!theme) {\n    return oauthError(\"not_found\", \"Theme not found\", 404);\n  }\n\n  return Response.json({ data: theme });\n}\n"
  },
  {
    "path": "app/api/v1/themes/route.ts",
    "content": "import { db } from \"@/db\";\nimport { theme as themeTable } from \"@/db/schema\";\nimport { requireAuth } from \"@/lib/oauth\";\nimport { eq } from \"drizzle-orm\";\nimport { NextRequest } from \"next/server\";\n\nexport async function GET(req: NextRequest) {\n  const auth = await requireAuth(req, \"themes:read\");\n  if (auth.error) return auth.error;\n\n  const themes = await db\n    .select({\n      id: themeTable.id,\n      name: themeTable.name,\n      styles: themeTable.styles,\n      createdAt: themeTable.createdAt,\n      updatedAt: themeTable.updatedAt,\n    })\n    .from(themeTable)\n    .where(eq(themeTable.userId, auth.tokenData.userId));\n\n  return Response.json({ data: themes });\n}\n"
  },
  {
    "path": "app/api/webhook/polar/route.ts",
    "content": "import { db } from \"@/db\";\nimport { subscription } from \"@/db/schema\";\nimport { Webhooks } from \"@polar-sh/nextjs\";\n\nfunction safeParseDate(value: string | Date | null | undefined): Date | null {\n  if (!value) return null;\n  if (value instanceof Date) return value;\n  return new Date(value);\n}\n\nif (!process.env.POLAR_WEBHOOK_SECRET) {\n  throw new Error(\"POLAR_WEBHOOK_SECRET environment variable is required\");\n}\n\nexport const POST = Webhooks({\n  webhookSecret: process.env.POLAR_WEBHOOK_SECRET,\n  onPayload: async ({ data, type }) => {\n    if (\n      type === \"subscription.created\" ||\n      type === \"subscription.active\" ||\n      type === \"subscription.canceled\" ||\n      type === \"subscription.revoked\" ||\n      type === \"subscription.uncanceled\" ||\n      type === \"subscription.updated\"\n    ) {\n      console.log(\"🎯 Processing subscription webhook:\", type);\n      console.log(\"📦 Payload data:\", JSON.stringify(data, null, 2));\n      try {\n        const userId = data.customer?.externalId;\n\n        const subscriptionData = {\n          id: data.id,\n          createdAt: new Date(data.createdAt),\n          modifiedAt: safeParseDate(data.modifiedAt),\n          amount: data.amount,\n          currency: data.currency,\n          recurringInterval: data.recurringInterval,\n          status: data.status,\n          currentPeriodStart: safeParseDate(data.currentPeriodStart) || new Date(),\n          currentPeriodEnd: safeParseDate(data.currentPeriodEnd) || new Date(),\n          cancelAtPeriodEnd: data.cancelAtPeriodEnd || false,\n          canceledAt: safeParseDate(data.canceledAt),\n          startedAt: safeParseDate(data.startedAt) || new Date(),\n          endsAt: safeParseDate(data.endsAt),\n          endedAt: safeParseDate(data.endedAt),\n          customerId: data.customerId,\n          productId: data.productId,\n          discountId: data.discountId || null,\n          checkoutId: data.checkoutId || \"\",\n          customerCancellationReason: data.customerCancellationReason || null,\n          customerCancellationComment: data.customerCancellationComment || null,\n          metadata: data.metadata ? JSON.stringify(data.metadata) : null,\n          customFieldData: data.customFieldData ? JSON.stringify(data.customFieldData) : null,\n          userId: userId as string | null,\n        };\n\n        console.log(\"💾 Final subscription data:\", {\n          id: subscriptionData.id,\n          status: subscriptionData.status,\n          userId: subscriptionData.userId,\n          amount: subscriptionData.amount,\n        });\n\n        await db\n          .insert(subscription)\n          .values(subscriptionData)\n          .onConflictDoUpdate({\n            target: subscription.id,\n            set: {\n              modifiedAt: subscriptionData.modifiedAt || new Date(),\n              amount: subscriptionData.amount,\n              currency: subscriptionData.currency,\n              recurringInterval: subscriptionData.recurringInterval,\n              status: subscriptionData.status,\n              currentPeriodStart: subscriptionData.currentPeriodStart,\n              currentPeriodEnd: subscriptionData.currentPeriodEnd,\n              cancelAtPeriodEnd: subscriptionData.cancelAtPeriodEnd,\n              canceledAt: subscriptionData.canceledAt,\n              startedAt: subscriptionData.startedAt,\n              endsAt: subscriptionData.endsAt,\n              endedAt: subscriptionData.endedAt,\n              customerId: subscriptionData.customerId,\n              productId: subscriptionData.productId,\n              discountId: subscriptionData.discountId,\n              checkoutId: subscriptionData.checkoutId,\n              customerCancellationReason: subscriptionData.customerCancellationReason,\n              customerCancellationComment: subscriptionData.customerCancellationComment,\n              metadata: subscriptionData.metadata,\n              customFieldData: subscriptionData.customFieldData,\n              userId: subscriptionData.userId,\n            },\n          });\n\n        console.log(\"🎉 Subscription data upserted successfully\");\n      } catch (error) {\n        console.error(\"❌ Error processing subscription webhook:\", error);\n      }\n    }\n  },\n});\n"
  },
  {
    "path": "app/community/components/community-sidebar.tsx",
    "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { useCommunityTagCounts } from \"@/hooks/themes\";\nimport { useSessionGuard } from \"@/hooks/use-guards\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport type { CommunityFilterOption } from \"@/types/community\";\n\ninterface CommunitySidebarProps {\n  filter: CommunityFilterOption;\n  selectedTags: string[];\n  onFilterChange: (filter: CommunityFilterOption) => void;\n  onTagToggle: (tag: string) => void;\n}\n\nconst filterItems = [\n  { value: \"all\" as const, label: \"All Themes\" },\n  { value: \"mine\" as const, label: \"My Themes\" },\n  { value: \"liked\" as const, label: \"Liked Themes\" },\n];\n\nexport function CommunitySidebarContent({\n  filter,\n  selectedTags,\n  onFilterChange,\n  onTagToggle,\n}: CommunitySidebarProps) {\n  const { data: tagCounts = [], isLoading: isLoadingTags } =\n    useCommunityTagCounts();\n  const { checkValidSession } = useSessionGuard();\n\n  const handleFilterClick = (value: CommunityFilterOption) => {\n    if (value === \"mine\" || value === \"liked\") {\n      if (!checkValidSession(\"signin\")) return;\n    }\n    onFilterChange(value);\n  };\n\n  return (\n    <nav className=\"space-y-1\">\n      {filterItems.map((item) => (\n        <button\n          key={item.value}\n          onClick={() => handleFilterClick(item.value)}\n          className={cn(\n            \"flex w-full items-center rounded-md px-3 py-1.5 text-sm font-medium transition-colors\",\n            filter === item.value\n              ? \"bg-foreground/10 text-foreground\"\n              : \"text-muted-foreground hover:bg-foreground/5 hover:text-foreground\"\n          )}\n        >\n          {item.label}\n        </button>\n      ))}\n\n      <Separator className=\"!my-3\" />\n      <div className=\"px-3 py-1\">\n        <span className=\"text-muted-foreground text-xs font-semibold uppercase tracking-wider\">\n          Tags\n        </span>\n      </div>\n\n      {isLoadingTags ? (\n        <div className=\"space-y-1 px-3\">\n          {Array.from({ length: 5 }).map((_, i) => (\n            <Skeleton key={i} className=\"h-7 w-full rounded-lg\" />\n          ))}\n        </div>\n      ) : tagCounts.length > 0 ? (\n        <div className=\"space-y-0.5\">\n          {tagCounts.map(({ tag, count }) => (\n            <button\n              key={tag}\n              onClick={() => onTagToggle(tag)}\n              className={cn(\n                \"flex w-full items-center justify-between rounded-lg px-3 py-1.5 text-sm transition-colors\",\n                selectedTags.includes(tag)\n                  ? \"bg-foreground/10 text-foreground font-medium\"\n                  : \"text-muted-foreground hover:bg-foreground/5 hover:text-foreground\"\n              )}\n            >\n              <span>{tag}</span>\n              <span\n                className={cn(\n                  \"text-xs tabular-nums\",\n                  selectedTags.includes(tag)\n                    ? \"text-foreground/70\"\n                    : \"text-muted-foreground/60\"\n                )}\n              >\n                {count}\n              </span>\n            </button>\n          ))}\n        </div>\n      ) : null}\n    </nav>\n  );\n}\n"
  },
  {
    "path": "app/community/components/community-theme-card.tsx",
    "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Avatar, AvatarImage, AvatarFallback } from \"@/components/ui/avatar\";\nimport { cn } from \"@/lib/utils\";\nimport { Heart } from \"lucide-react\";\n\nimport { useTheme } from \"@/components/theme-provider\";\nimport { useToggleLike } from \"@/hooks/themes\";\nimport { useSessionGuard } from \"@/hooks/use-guards\";\nimport { usePostLoginAction } from \"@/hooks/use-post-login-action\";\nimport { ThemePreview } from \"@/components/theme-preview\";\nimport type { CommunityTheme } from \"@/types/community\";\n\ninterface CommunityThemeCardProps {\n  theme: CommunityTheme;\n  onPreview: (theme: CommunityTheme) => void;\n}\n\nexport function CommunityThemeCard({ theme, onPreview }: CommunityThemeCardProps) {\n  const { theme: currentTheme } = useTheme();\n  const toggleLike = useToggleLike();\n  const { checkValidSession } = useSessionGuard();\n\n  usePostLoginAction(\"LIKE_THEME\", (data?: { communityThemeId: string }) => {\n    if (data?.communityThemeId === theme.id) {\n      toggleLike.mutate(theme.id);\n    }\n  });\n\n  const handleLike = (e: React.MouseEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n\n    if (\n      !checkValidSession(\"signin\", \"LIKE_THEME\", {\n        communityThemeId: theme.id,\n      })\n    ) {\n      return;\n    }\n\n    toggleLike.mutate(theme.id);\n  };\n\n  const authorInitials = theme.author.name\n    ?.split(\" \")\n    .map((n) => n[0])\n    .join(\"\")\n    .slice(0, 2)\n    .toUpperCase();\n\n  const publishedDate = new Date(theme.publishedAt).toLocaleDateString(\n    \"en-US\",\n    { day: \"numeric\", month: \"short\" }\n  );\n\n  return (\n    <div\n      role=\"button\"\n      tabIndex={0}\n      onClick={() => onPreview(theme)}\n      className=\"group cursor-pointer\"\n    >\n      <div className=\"relative h-44 w-full overflow-hidden rounded-xl border shadow-sm transition-all duration-200 group-hover:shadow-md group-hover:border-foreground/20\">\n        <ThemePreview\n          styles={theme.styles[currentTheme]}\n          name={theme.name}\n          className=\"transition-transform duration-300 group-hover:scale-102\"\n        />\n        {theme.tags.length > 0 && (\n          <div className=\"pointer-events-none absolute top-2 left-2 flex items-center gap-1\">\n            {theme.tags.slice(0, 2).map((tag) => (\n              <Badge\n                key={tag}\n                className=\"border-0 bg-background/80 px-1.5 py-0 text-[10px] text-foreground shadow-sm backdrop-blur-sm\"\n              >\n                {tag}\n              </Badge>\n            ))}\n            {theme.tags.length > 2 && (\n              <Badge className=\"border-0 bg-background/80 px-1.5 py-0 text-[10px] text-foreground shadow-sm backdrop-blur-sm\">\n                +{theme.tags.length - 2}\n              </Badge>\n            )}\n          </div>\n        )}\n      </div>\n\n      <div className=\"flex items-start justify-between gap-2 px-1 pt-2\">\n        <div className=\"min-w-0 flex-1\">\n          <div className=\"mt-1 flex items-center gap-3\">\n            <div className=\"flex min-w-0 max-w-[120px] items-center gap-1.5\">\n              <Avatar className=\"h-4 w-4 shrink-0\">\n                {theme.author.image && (\n                  <AvatarImage\n                    src={theme.author.image}\n                    alt={theme.author.name}\n                  />\n                )}\n                <AvatarFallback className=\"text-[8px]\">\n                  {authorInitials}\n                </AvatarFallback>\n              </Avatar>\n              <span className=\"truncate text-xs text-muted-foreground\">\n                {theme.author.name}\n              </span>\n            </div>\n            <span className=\"text-xs text-muted-foreground/60\">\n              {publishedDate}\n            </span>\n          </div>\n        </div>\n        <button\n          onClick={handleLike}\n          className={cn(\n            \"flex shrink-0 items-center gap-1 rounded-full px-2.5 py-1 text-xs font-medium transition-colors\",\n            theme.isLikedByMe\n              ? \"bg-red-500/10 text-red-500\"\n              : \"text-muted-foreground hover:bg-accent hover:text-foreground\"\n          )}\n        >\n          <Heart\n            className={cn(\n              \"h-3.5 w-3.5\",\n              theme.isLikedByMe && \"fill-current\"\n            )}\n          />\n          {theme.likeCount > 0 && <span>{theme.likeCount}</span>}\n        </button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/community/components/community-theme-preview-dialog.tsx",
    "content": "\"use client\";\n\nimport { lazy, useEffect, useCallback } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { cn } from \"@/lib/utils\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { useToggleLike } from \"@/hooks/themes\";\nimport { useSessionGuard } from \"@/hooks/use-guards\";\nimport { usePostLoginAction } from \"@/hooks/use-post-login-action\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Avatar, AvatarImage, AvatarFallback } from \"@/components/ui/avatar\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport ExamplesPreviewContainer from \"@/components/editor/theme-preview/examples-preview-container\";\nimport { Heart, Moon, Sun, ArrowUpRight, ChevronLeft, ChevronRight, Share2 } from \"lucide-react\";\nimport { toast } from \"@/components/ui/use-toast\";\nimport type { CommunityTheme } from \"@/types/community\";\n\nconst DemoCards = lazy(() => import(\"@/components/examples/cards\"));\n\ninterface CommunityThemePreviewDialogProps {\n  theme: CommunityTheme | null;\n  themes: CommunityTheme[];\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  onNavigate: (theme: CommunityTheme) => void;\n}\n\nexport function CommunityThemePreviewDialog({\n  theme,\n  themes,\n  open,\n  onOpenChange,\n  onNavigate,\n}: CommunityThemePreviewDialogProps) {\n  const router = useRouter();\n  const { themeState, setThemeState } = useEditorStore();\n  const toggleLike = useToggleLike();\n  const { checkValidSession } = useSessionGuard();\n\n  const currentIndex = theme ? themes.findIndex((t) => t.id === theme.id) : -1;\n  const hasPrev = currentIndex > 0;\n  const hasNext = currentIndex >= 0 && currentIndex < themes.length - 1;\n\n  const goToPrev = useCallback(() => {\n    if (hasPrev) onNavigate(themes[currentIndex - 1]);\n  }, [hasPrev, themes, currentIndex, onNavigate]);\n\n  const goToNext = useCallback(() => {\n    if (hasNext) onNavigate(themes[currentIndex + 1]);\n  }, [hasNext, themes, currentIndex, onNavigate]);\n\n  useEffect(() => {\n    if (!open) return;\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === \"ArrowLeft\") {\n        e.preventDefault();\n        goToPrev();\n      } else if (e.key === \"ArrowRight\") {\n        e.preventDefault();\n        goToNext();\n      }\n    };\n    window.addEventListener(\"keydown\", handleKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleKeyDown);\n  }, [open, goToPrev, goToNext]);\n\n  usePostLoginAction(\"LIKE_THEME\", (data?: { communityThemeId: string }) => {\n    if (theme && data?.communityThemeId === theme.id) {\n      toggleLike.mutate(theme.id);\n    }\n  });\n\n  if (!theme) return null;\n\n  const currentMode = themeState.currentMode;\n\n  const handleToggleTheme = () => {\n    setThemeState({\n      ...themeState,\n      currentMode: currentMode === \"light\" ? \"dark\" : \"light\",\n    });\n  };\n\n  const handleLike = () => {\n    if (\n      !checkValidSession(\"signin\", \"LIKE_THEME\", {\n        communityThemeId: theme.id,\n      })\n    ) {\n      return;\n    }\n    toggleLike.mutate(theme.id);\n  };\n\n  const handleViewDetails = () => {\n    onOpenChange(false);\n    router.push(`/themes/${theme.themeId}`);\n  };\n\n  const handleShare = () => {\n    const url = `https://tweakcn.com/themes/${theme.themeId}`;\n    navigator.clipboard.writeText(url);\n    toast({\n      title: \"Theme URL copied to clipboard!\",\n    });\n  };\n\n  const authorInitials = theme.author.name\n    ?.split(\" \")\n    .map((n) => n[0])\n    .join(\"\")\n    .slice(0, 2)\n    .toUpperCase();\n\n  return (\n    <Dialog open={open} onOpenChange={onOpenChange}>\n      <DialogContent\n        className=\"flex h-[80vh] w-[95vw] max-w-6xl flex-col gap-0 overflow-visible p-0\"\n        style={{\n          \"--tw-enter-translate-x\": \"0\",\n          \"--tw-enter-translate-y\": \"0\",\n          \"--tw-exit-translate-x\": \"0\",\n          \"--tw-exit-translate-y\": \"0\",\n        } as React.CSSProperties}\n      >\n        {/* Navigation arrows — positioned outside the dialog box */}\n        {hasPrev && (\n          <button\n            onClick={goToPrev}\n            className=\"absolute -left-14 top-1/2 -translate-y-1/2 rounded-full border bg-background/80 p-2 text-foreground shadow-lg backdrop-blur-sm transition-all hover:bg-background hover:scale-110\"\n            aria-label=\"Previous theme\"\n          >\n            <ChevronLeft className=\"size-5\" />\n          </button>\n        )}\n        {hasNext && (\n          <button\n            onClick={goToNext}\n            className=\"absolute -right-14 top-1/2 -translate-y-1/2 rounded-full border bg-background/80 p-2 text-foreground shadow-lg backdrop-blur-sm transition-all hover:bg-background hover:scale-110\"\n            aria-label=\"Next theme\"\n          >\n            <ChevronRight className=\"size-5\" />\n          </button>\n        )}\n        {/* Inner content wrapper — clips content within dialog bounds */}\n        <div className=\"flex min-h-0 flex-1 flex-col overflow-hidden rounded-[inherit]\">\n          {/* Header: name + author */}\n          <DialogHeader className=\"shrink-0 px-4 pt-4 pb-3\">\n            <div className=\"flex items-center gap-3\">\n              <DialogTitle className=\"text-base font-semibold\">\n                {theme.name}\n              </DialogTitle>\n              <span className=\"text-muted-foreground/40\">|</span>\n              <div className=\"flex items-center gap-1.5 text-muted-foreground\">\n                <Avatar className=\"h-5 w-5\">\n                  {theme.author.image && (\n                    <AvatarImage\n                      src={theme.author.image}\n                      alt={theme.author.name}\n                    />\n                  )}\n                  <AvatarFallback className=\"text-[9px]\">\n                    {authorInitials}\n                  </AvatarFallback>\n                </Avatar>\n                <span className=\"text-xs\">{theme.author.name}</span>\n              </div>\n            </div>\n          </DialogHeader>\n\n          {/* Toolbar */}\n          <div className=\"flex shrink-0 items-center justify-between px-4 py-1\">\n            <span className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n              Preview\n            </span>\n            <div className=\"flex items-center gap-1\">\n              <button\n                onClick={handleLike}\n                className={cn(\n                  \"flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-all\",\n                  theme.isLikedByMe\n                    ? \"bg-red-500/10 text-red-500 hover:bg-red-500/20\"\n                    : \"text-muted-foreground hover:bg-accent hover:text-foreground\"\n                )}\n                title={theme.isLikedByMe ? \"Unlike theme\" : \"Like theme\"}\n              >\n                <Heart\n                  className={cn(\n                    \"size-4\",\n                    theme.isLikedByMe && \"fill-current\"\n                  )}\n                />\n                {theme.likeCount > 0 && <span>{theme.likeCount}</span>}\n              </button>\n              <button\n                onClick={handleShare}\n                className=\"rounded-md p-2 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\"\n                title=\"Share theme\"\n              >\n                <Share2 className=\"size-4\" />\n              </button>\n              <button\n                onClick={handleToggleTheme}\n                className=\"rounded-md p-2 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\"\n                title=\"Toggle light/dark mode\"\n              >\n                {currentMode === \"dark\" ? (\n                  <Sun className=\"size-4\" />\n                ) : (\n                  <Moon className=\"size-4\" />\n                )}\n              </button>\n              <div className=\"h-4 w-px bg-border mr-2\" />\n              <Button variant=\"link\" className=\"p-0 text-foreground font-semibold\" onClick={handleViewDetails}>\n                View\n                <ArrowUpRight className=\"size-4\" />\n              </Button>\n            </div>\n          </div>\n\n          {/* Card preview — scrollable */}\n          <div className=\"min-h-0 flex-1\">\n            <ScrollArea className=\"h-full\">\n              <ExamplesPreviewContainer className=\"size-full\">\n                <DemoCards />\n              </ExamplesPreviewContainer>\n            </ScrollArea>\n          </div>\n        </div>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "app/community/components/community-themes-content.tsx",
    "content": "\"use client\";\n\nimport { useCommunityThemes } from \"@/hooks/themes\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport type {\n  CommunityFilterOption,\n  CommunityTimeRange,\n  CommunityTheme,\n} from \"@/types/community\";\nimport {\n  useQueryState,\n  parseAsStringLiteral,\n  parseAsArrayOf,\n  parseAsString,\n} from \"nuqs\";\nimport { useEffect, useRef, useState, useCallback } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetHeader,\n  SheetTitle,\n  SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuLabel,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Flame, Loader2, Info, SlidersHorizontal, ChevronDown, Check } from \"lucide-react\";\nimport { CommunityThemeCard } from \"./community-theme-card\";\nimport { CommunityThemePreviewDialog } from \"./community-theme-preview-dialog\";\nimport { CommunitySidebarContent } from \"./community-sidebar\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport Link from \"next/link\";\n\nconst popularOptions: {\n  timeRange: CommunityTimeRange;\n  label: string;\n}[] = [\n    { timeRange: \"all\", label: \"All Time\" },\n    { timeRange: \"monthly\", label: \"This Month\" },\n    { timeRange: \"weekly\", label: \"This Week\" },\n  ];\n\nconst otherSortOptions: {\n  sort: \"newest\" | \"oldest\";\n  label: string;\n}[] = [\n    { sort: \"newest\", label: \"Newest\" },\n    { sort: \"oldest\", label: \"Oldest\" },\n  ];\n\nexport function CommunityThemesContent() {\n  const [sort, setSort] = useQueryState(\n    \"sort\",\n    parseAsStringLiteral([\"popular\", \"newest\", \"oldest\"] as const).withDefault(\n      \"popular\"\n    )\n  );\n  const [filter, setFilter] = useQueryState(\n    \"filter\",\n    parseAsStringLiteral([\"all\", \"mine\", \"liked\"] as const).withDefault(\"all\")\n  );\n  const [tags, setTags] = useQueryState(\n    \"tags\",\n    parseAsArrayOf(parseAsString, \",\").withDefault([])\n  );\n  const [timeRange, setTimeRange] = useQueryState(\n    \"t\",\n    parseAsStringLiteral([\"weekly\", \"monthly\", \"all\"] as const).withDefault(\n      \"all\"\n    )\n  );\n  const [sheetOpen, setSheetOpen] = useState(false);\n  const [previewThemeId, setPreviewThemeId] = useState<string | null>(null);\n  const { themeState, setThemeState } = useEditorStore();\n\n  const handlePreview = useCallback(\n    (theme: CommunityTheme) => {\n      setThemeState({ ...themeState, styles: theme.styles });\n      setPreviewThemeId(theme.id);\n    },\n    [themeState, setThemeState]\n  );\n\n  const handleFilterChange = useCallback(\n    (newFilter: CommunityFilterOption) => {\n      setFilter(newFilter);\n      setSheetOpen(false);\n    },\n    [setFilter]\n  );\n\n  const handleTagToggle = useCallback(\n    (tag: string) => {\n      setTags((prev) => (prev.includes(tag) ? [] : [tag]));\n    },\n    [setTags]\n  );\n\n  const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =\n    useCommunityThemes(sort, filter, tags, sort === \"popular\" ? timeRange : \"all\");\n\n  const themes = data?.pages.flatMap((page) => page.themes) ?? [];\n  const previewTheme = previewThemeId\n    ? themes.find((t) => t.id === previewThemeId) ?? null\n    : null;\n\n  const sentinelRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const sentinel = sentinelRef.current;\n    if (!sentinel) return;\n\n    const observer = new IntersectionObserver(\n      (entries) => {\n        if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {\n          fetchNextPage();\n        }\n      },\n      { rootMargin: \"200px\" }\n    );\n\n    observer.observe(sentinel);\n    return () => observer.disconnect();\n  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);\n\n  const hasActiveFilters = filter !== \"all\" || tags.length > 0;\n\n  const sidebarProps = {\n    filter,\n    selectedTags: tags,\n    onFilterChange: handleFilterChange,\n    onTagToggle: handleTagToggle,\n  };\n\n  return (\n    <div className=\"flex flex-1\">\n      {/* Desktop sidebar */}\n      <aside className=\"hidden w-56 shrink-0 border-r lg:block\">\n        <div className=\"p-4\">\n          <Link href=\"/community\" className=\"block\">\n            <h1 className=\"text-lg font-semibold tracking-tight\">\n              Community Themes\n            </h1>\n            <p className=\"text-muted-foreground mt-1 text-xs\">\n              Discover themes by the community\n            </p>\n          </Link>\n        </div>\n\n        <div className=\"p-4\">\n          <CommunitySidebarContent {...sidebarProps} />\n        </div>\n      </aside>\n\n      {/* Main content */}\n      <div className=\"min-w-0 flex-1\">\n        <div className=\"space-y-6 p-4\">\n          <div className=\"flex items-center justify-between gap-4\">\n            <div className=\"flex items-center gap-2\">\n              {/* Mobile filter button */}\n              <Sheet open={sheetOpen} onOpenChange={setSheetOpen}>\n                <SheetTrigger asChild>\n                  <Button\n                    variant=\"outline\"\n                    size=\"sm\"\n                    className={cn(\n                      \"gap-1.5 lg:hidden\",\n                      hasActiveFilters && \"border-primary text-primary\"\n                    )}\n                  >\n                    <SlidersHorizontal className=\"size-3.5\" />\n                    Filters\n                    {hasActiveFilters && (\n                      <span className=\"bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full text-[10px] font-bold\">\n                        {(filter !== \"all\" ? 1 : 0) + tags.length}\n                      </span>\n                    )}\n                  </Button>\n                </SheetTrigger>\n                <SheetContent side=\"left\" className=\"w-72 p-0\">\n                  <SheetHeader className=\"border-b px-6 py-4\">\n                    <SheetTitle>Community Themes</SheetTitle>\n                  </SheetHeader>\n                  <div className=\"p-4\">\n                    <CommunitySidebarContent {...sidebarProps} />\n                  </div>\n                </SheetContent>\n              </Sheet>\n\n              <DropdownMenu>\n                <DropdownMenuTrigger asChild>\n                  <Button\n                    variant=\"outline\"\n                    size=\"sm\"\n                    className=\"gap-1.5\"\n                  >\n                    {sort === \"popular\"\n                      ? `Popular / ${popularOptions.find((o) => o.timeRange === timeRange)?.label ?? \"All Time\"}`\n                      : sort === \"newest\"\n                        ? \"Newest\"\n                        : \"Oldest\"}\n                    <ChevronDown className=\"size-3.5 opacity-50\" />\n                  </Button>\n                </DropdownMenuTrigger>\n                <DropdownMenuContent align=\"start\" className=\"w-44\">\n                  <DropdownMenuLabel>Popular</DropdownMenuLabel>\n                  {popularOptions.map((option) => {\n                    const isActive =\n                      sort === \"popular\" && timeRange === option.timeRange;\n                    return (\n                      <DropdownMenuItem\n                        key={option.timeRange}\n                        onClick={() => {\n                          setSort(\"popular\");\n                          setTimeRange(option.timeRange);\n                        }}\n                        className=\"cursor-pointer justify-between\"\n                      >\n                        {option.label}\n                        {isActive && <Check className=\"size-3.5\" />}\n                      </DropdownMenuItem>\n                    );\n                  })}\n                  <DropdownMenuSeparator />\n                  {otherSortOptions.map((option) => {\n                    const isActive = sort === option.sort;\n                    return (\n                      <DropdownMenuItem\n                        key={option.sort}\n                        onClick={() => {\n                          setSort(option.sort);\n                        }}\n                        className=\"cursor-pointer justify-between\"\n                      >\n                        {option.label}\n                        {isActive && <Check className=\"size-3.5\" />}\n                      </DropdownMenuItem>\n                    );\n                  })}\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </div>\n\n            <Popover>\n              <PopoverTrigger asChild>\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  className=\"text-muted-foreground gap-1.5 text-xs\"\n                >\n                  <Info className=\"size-3.5\" />\n                  <span className=\"hidden sm:inline\">How to publish</span>\n                </Button>\n              </PopoverTrigger>\n              <PopoverContent align=\"end\" className=\"w-72 p-0\">\n                <div className=\"p-4\">\n                  <p className=\"text-sm font-medium\">Publish your theme</p>\n                  <p className=\"text-muted-foreground mt-1.5 text-xs leading-relaxed\">\n                    After saving a theme, click the{\" \"}\n                    <span className=\"text-foreground font-medium\">\n                      Publish\n                    </span>{\" \"}\n                    button in the editor to share it.\n                  </p>\n                </div>\n                <Separator />\n                <div className=\"p-4\">\n                  <p className=\"text-muted-foreground text-xs leading-relaxed\">\n                    You can also manage all your saved themes from{\" \"}\n                    <Link\n                      href=\"/settings/themes\"\n                      className=\"text-foreground font-medium underline underline-offset-2\"\n                    >\n                      Settings\n                    </Link>\n                    .\n                  </p>\n                </div>\n              </PopoverContent>\n            </Popover>\n          </div>\n\n          {isLoading ? (\n            <div className=\"grid gap-5 grid-cols-[repeat(auto-fill,minmax(280px,1fr))]\">\n              {Array.from({ length: 8 }).map((_, i) => (\n                <div\n                  key={i}\n                  className=\"space-y-0 overflow-hidden rounded-xl border\"\n                >\n                  <Skeleton className=\"h-36 rounded-none\" />\n                  <div className=\"space-y-2 p-3\">\n                    <Skeleton className=\"h-4 w-2/3\" />\n                    <Skeleton className=\"h-3 w-1/3\" />\n                  </div>\n                </div>\n              ))}\n            </div>\n          ) : themes.length === 0 ? (\n            <div className=\"py-24 text-center\">\n              <div className=\"bg-muted mx-auto mb-4 flex size-16 items-center justify-center rounded-full\">\n                <Flame className=\"text-muted-foreground size-8\" />\n              </div>\n              <h3 className=\"mb-2 text-lg font-semibold\">\n                {filter === \"mine\"\n                  ? \"No published themes\"\n                  : filter === \"liked\"\n                    ? \"No liked themes\"\n                    : \"No themes yet\"}\n              </h3>\n              <p className=\"text-muted-foreground mx-auto max-w-sm\">\n                {filter === \"mine\"\n                  ? \"You haven't published any themes yet. Save a theme in the editor, then publish it.\"\n                  : filter === \"liked\"\n                    ? \"You haven't liked any themes yet. Browse community themes and like your favorites.\"\n                    : \"Be the first to publish a theme to the community! Save a theme in the editor, then publish it from your settings.\"}\n              </p>\n            </div>\n          ) : (\n            <>\n              <div className=\"grid gap-5 gap-y-8 grid-cols-[repeat(auto-fill,minmax(280px,1fr))]\">\n                {themes.map((theme) => (\n                  <CommunityThemeCard key={theme.id} theme={theme} onPreview={handlePreview} />\n                ))}\n              </div>\n\n              <div ref={sentinelRef} className=\"flex justify-center pt-4\">\n                {isFetchingNextPage && (\n                  <Loader2 className=\"text-muted-foreground h-6 w-6 animate-spin\" />\n                )}\n              </div>\n            </>\n          )}\n        </div>\n      </div>\n      <CommunityThemePreviewDialog\n        theme={previewTheme}\n        themes={themes}\n        open={!!previewThemeId}\n        onOpenChange={(open) => {\n          if (!open) setPreviewThemeId(null);\n        }}\n        onNavigate={handlePreview}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/community/layout.tsx",
    "content": "import { Footer } from \"@/components/footer\";\nimport { Header } from \"@/components/header\";\n\nexport default function CommunityLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <div className=\"flex min-h-screen flex-col\">\n      <Header />\n      <main className=\"flex flex-1 flex-col\">{children}</main>\n      <Footer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/community/page.tsx",
    "content": "import { Metadata } from \"next\";\nimport { CommunityThemesContent } from \"./components/community-themes-content\";\nimport { COMMUNITY_THEME_TAGS } from \"@/lib/constants\";\n\nexport const metadata: Metadata = {\n  title: \"Community Themes - tweakcn\",\n  description:\n    \"Discover and explore beautiful shadcn/ui themes created by the community.\",\n  keywords: [...COMMUNITY_THEME_TAGS, \"shadcn\", \"theme\", \"ui\"],\n  openGraph: {\n    title: \"Community Themes - tweakcn\",\n    description:\n      \"Discover and explore beautiful shadcn/ui themes created by the community.\",\n    type: \"website\",\n  },\n  twitter: {\n    card: \"summary_large_image\",\n    title: \"Community Themes - tweakcn\",\n    description:\n      \"Discover and explore beautiful shadcn/ui themes created by the community.\",\n  },\n};\n\nexport default function CommunityPage() {\n  return <CommunityThemesContent />;\n}\n"
  },
  {
    "path": "app/dashboard/layout.tsx",
    "content": "import { Footer } from \"@/components/footer\";\nimport { Header } from \"@/components/header\";\n\nexport default function DashboardLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"flex flex-col\">\n      <Header />\n      <main className=\"flex min-h-screen flex-1 flex-col\">{children}</main>\n      <Footer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/dashboard/loading.tsx",
    "content": "import { Loading } from \"@/components/loading\";\n\nexport default function DashboardLoading() {\n  return <Loading className=\"flex-1\" />;\n}\n"
  },
  {
    "path": "app/dashboard/page.tsx",
    "content": "import { redirect } from \"next/navigation\";\n\n// This page is being moved to settings/themes\nexport default function DashboardRedirect() {\n  redirect(\"/settings/themes\");\n}\n"
  },
  {
    "path": "app/editor/theme/[[...themeId]]/layout.tsx",
    "content": "import { Header } from \"@/components/header\";\n\nexport default function EditorLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"relative isolate flex h-svh flex-col overflow-hidden\">\n      <Header />\n      <main className=\"isolate flex flex-1 flex-col overflow-hidden\">{children}</main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/editor/theme/[[...themeId]]/loading.tsx",
    "content": "import { Loading } from \"@/components/loading\";\n\nexport default function EditorLoading() {\n  return <Loading className=\"flex-1\" />;\n}\n"
  },
  {
    "path": "app/editor/theme/[[...themeId]]/page.tsx",
    "content": "import { getTheme } from \"@/actions/themes\";\nimport Editor from \"@/components/editor/editor\";\nimport { Metadata } from \"next\";\n\nexport const metadata: Metadata = {\n  title: \"tweakcn — Theme Generator for shadcn/ui\",\n  description:\n    \"Easily customize and preview your shadcn/ui theme with tweakcn. Modify colors, fonts, and styles in real-time.\",\n};\n\nexport default async function EditorPage({ params }: { params: Promise<{ themeId: string[] }> }) {\n  const { themeId } = await params;\n  const themePromise = themeId?.length > 0 ? getTheme(themeId?.[0]) : Promise.resolve(null);\n\n  return <Editor themePromise={themePromise} />;\n}\n"
  },
  {
    "path": "app/figma/layout.tsx",
    "content": "import { Metadata } from \"next\";\n\nexport const metadata: Metadata = {\n  title: \"Apply Your tweakcn Theme to Shadcraft Figma UI Kit | Professional Design System\",\n  description:\n    \"Transform your tweakcn themes into stunning Figma designs with Shadcraft's premium UI kit. 51 components, 44 blocks, dark mode support, and 1500+ icons. Professional Figma design system for shadcn/ui themes.\",\n  keywords:\n    \"figma ui kit, shadcn ui figma, tweakcn themes, figma design system, ui components figma, design tokens figma, figma plugin, shadcraft, figma templates, design system integration\",\n  authors: [{ name: \"tweakcn Team\" }],\n  openGraph: {\n    title: \"Apply Your tweakcn Theme to Shadcraft Figma UI Kit\",\n    description:\n      \"Professional Figma UI kit with 51 components, 44 blocks, and seamless tweakcn theme integration. Get the ultimate design system for your projects.\",\n    url: \"https://tweakcn.com/figma\",\n    siteName: \"tweakcn\",\n    images: [\n      {\n        url: \"https://tweakcn.com/figma-onboarding/shadcraft-preview.jpg\",\n        width: 1200,\n        height: 630,\n        alt: \"Shadcraft Figma UI Kit Preview\",\n      },\n    ],\n    type: \"website\",\n  },\n  twitter: {\n    card: \"summary_large_image\",\n    title: \"Apply Your tweakcn Theme to Shadcraft Figma UI Kit\",\n    description:\n      \"Professional Figma UI kit with 51 components, 44 blocks, and seamless tweakcn theme integration.\",\n    images: [\"https://tweakcn.com/figma-onboarding/shadcraft-preview.jpg\"],\n  },\n  robots: \"index, follow\",\n  alternates: {\n    canonical: \"https://tweakcn.com/figma\",\n  },\n};\n\nexport default function FigmaLayout({ children }: { children: React.ReactNode }) {\n  return <>{children}</>;\n}\n"
  },
  {
    "path": "app/figma/page.tsx",
    "content": "\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { FigmaHeader } from \"@/components/figma-header\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport Logo from \"@/assets/logo.svg\";\nimport Shadcraft from \"@/assets/shadcraft.svg\";\nimport FigmaIcon from \"@/assets/figma.svg\";\nimport { Check, X, ArrowUpRight, Figma, Cable, Paintbrush } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { FIGMA_CONSTANTS, redirectToShadcraft } from \"@/lib/figma-constants\";\n\nexport default function FigmaPage() {\n  const [isScrolled, setIsScrolled] = useState(false);\n  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n\n  const steps = FIGMA_CONSTANTS.steps.map((step, index) => ({\n    ...step,\n    icon:\n      index === 0 ? (\n        <Figma className=\"h-6 w-6\" />\n      ) : index === 1 ? (\n        <Cable className=\"h-6 w-6\" />\n      ) : (\n        <Paintbrush className=\"h-6 w-6\" />\n      ),\n  }));\n\n  const handleGetStarted = () => {\n    redirectToShadcraft();\n  };\n\n  useEffect(() => {\n    const handleScroll = () => {\n      if (window.scrollY > 10) {\n        setIsScrolled(true);\n      } else {\n        setIsScrolled(false);\n      }\n    };\n\n    window.addEventListener(\"scroll\", handleScroll);\n    return () => window.removeEventListener(\"scroll\", handleScroll);\n  }, []);\n\n  // Structured data for SEO\n  const structuredData = {\n    \"@context\": \"https://schema.org\",\n    \"@type\": \"Product\",\n    name: \"Shadcraft Figma UI Kit - tweakcn Integration\",\n    description:\n      \"Professional Figma UI kit with 51 components, 44 blocks, dark mode support, and seamless tweakcn theme integration\",\n    image: \"https://tweakcn.com/figma-onboarding/shadcraft-preview.jpg\",\n    brand: {\n      \"@type\": \"Brand\",\n      name: \"Shadcraft\",\n    },\n    offers: {\n      \"@type\": \"Offer\",\n      price: \"89\",\n      priceCurrency: \"USD\",\n      priceValidUntil: \"2025-12-31\",\n      availability: \"https://schema.org/InStock\",\n      url: FIGMA_CONSTANTS.shadcraftUrl,\n      seller: {\n        \"@type\": \"Organization\",\n        name: \"Shadcraft\",\n      },\n    },\n    aggregateRating: {\n      \"@type\": \"AggregateRating\",\n      ratingValue: \"5\",\n      reviewCount: \"100+\",\n    },\n    category: \"Design Software\",\n    additionalProperty: [\n      {\n        \"@type\": \"PropertyValue\",\n        name: \"Components\",\n        value: \"51\",\n      },\n      {\n        \"@type\": \"PropertyValue\",\n        name: \"Blocks\",\n        value: \"44\",\n      },\n      {\n        \"@type\": \"PropertyValue\",\n        name: \"Icons\",\n        value: \"1500+\",\n      },\n    ],\n  };\n\n  return (\n    <>\n      {/* Structured Data */}\n      <script\n        type=\"application/ld+json\"\n        dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}\n      />\n\n      <div className=\"relative min-h-screen\">\n        {/* Gradient Background using CSS variables */}\n        <div className=\"from-background via-muted/20 to-accent/10 fixed inset-0 -z-10 bg-gradient-to-br\" />\n        <div className=\"from-primary/5 to-secondary/10 fixed inset-0 -z-10 bg-gradient-to-tr via-transparent\" />\n\n        {/* Header */}\n        <FigmaHeader\n          isScrolled={isScrolled}\n          mobileMenuOpen={mobileMenuOpen}\n          setMobileMenuOpen={setMobileMenuOpen}\n        />\n\n        {/* Main Content */}\n        <main className=\"container mx-auto max-w-[600px] p-4 pt-8\">\n          {/* Header Section */}\n          <header className=\"p-8 pb-5\">\n            <div className=\"flex items-center justify-center gap-2\">\n              <div className=\"flex items-center gap-2\">\n                <Logo className=\"h-6 w-6\" alt=\"tweakcn logo\" />\n                <div className=\"text-lg font-bold\">tweakcn</div>\n              </div>\n              <X className=\"h-4 w-4\" aria-hidden=\"true\" />\n              <Link href={FIGMA_CONSTANTS.shadcraftUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n                <div className=\"flex items-center gap-2\">\n                  <Shadcraft className=\"h-6 w-6\" alt=\"Shadcraft logo\" />\n                  <div className=\"text-lg font-bold\">shadcraft</div>\n                </div>\n              </Link>\n            </div>\n          </header>\n\n          <div className=\"space-y-16 px-8 pb-32\">\n            {/* Hero Section */}\n            <section className=\"space-y-6 text-center\">\n              <h1 className=\"text-5xl leading-12 font-semibold tracking-tight\">\n                Apply your theme to the ultimate Figma UI kit\n              </h1>\n\n              <div className=\"flex justify-center gap-3.5\">\n                <Button size=\"lg\" className=\"h-10 px-8\" onClick={handleGetStarted}>\n                  Get started\n                </Button>\n                <Link href={FIGMA_CONSTANTS.previewUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n                  <Button variant=\"outline\" size=\"lg\" className=\"h-10 gap-2 px-8\">\n                    <FigmaIcon className=\"h-4 w-4\" />\n                    Preview\n                  </Button>\n                </Link>\n              </div>\n\n              <div className=\"space-y-1.5 pt-1\">\n                <p className=\"text-muted-foreground text-sm\">Trusted by top designers</p>\n                <div\n                  className=\"flex justify-center -space-x-3\"\n                  role=\"group\"\n                  aria-label=\"Designer avatars\"\n                >\n                  {FIGMA_CONSTANTS.designers.map((designer, index) => (\n                    <Avatar key={index} className=\"border-background h-8 w-8 border-2\">\n                      <AvatarImage src={designer.avatar} alt={`${designer.name} avatar`} />\n                      <AvatarFallback className=\"text-xs\">{designer.fallback}</AvatarFallback>\n                    </Avatar>\n                  ))}\n                </div>\n              </div>\n            </section>\n\n            {/* How it works */}\n            <section className=\"space-y-4\">\n              <h2 className=\"text-center text-2xl font-semibold\">How it works</h2>\n              <div className=\"border-border rounded-2xl border px-6\">\n                <div className=\"divide-border grid grid-cols-3 divide-x\">\n                  {steps.map((step, index) => (\n                    <article\n                      key={index}\n                      className=\"space-y-2 px-6 py-6 text-center first:pl-0 last:pr-0\"\n                    >\n                      <div className=\"text-foreground mb-2 flex justify-center\" aria-hidden=\"true\">\n                        {step.icon}\n                      </div>\n                      <p className=\"text-muted-foreground text-sm\">{step.step}</p>\n                      <h3 className=\"font-semibold\">{step.title}</h3>\n                      <p className=\"text-muted-foreground sr-only text-sm\">{step.description}</p>\n                    </article>\n                  ))}\n                </div>\n              </div>\n            </section>\n\n            {/* Feature Description */}\n            <section className=\"space-y-6 text-center\">\n              <div className=\"mx-auto max-w-sm space-y-1.5\">\n                <h2 className=\"text-2xl font-semibold\">\n                  Top quality Figma UI kit for professionals\n                </h2>\n                <p className=\"text-muted-foreground\">\n                  Shadcraft is packed with top quality components, true to the shadcn/ui ethos.\n                </p>\n              </div>\n\n              {/* Demo UI Preview */}\n              <figure className=\"border-border relative overflow-hidden rounded-2xl border\">\n                <img\n                  src=\"/figma-onboarding/shadcraft-preview.jpg\"\n                  alt=\"Shadcraft Figma UI Kit Preview showing various components and design blocks\"\n                  className=\"h-auto w-full\"\n                  loading=\"lazy\"\n                  width=\"600\"\n                  height=\"400\"\n                />\n              </figure>\n\n              <Link href={FIGMA_CONSTANTS.shadcraftUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n                <Button variant=\"link\" className=\"gap-1 text-sm\">\n                  More on Shadcraft\n                  <ArrowUpRight className=\"h-3 w-3\" />\n                </Button>\n              </Link>\n            </section>\n\n            {/* Pricing */}\n            <section className=\"space-y-6\">\n              <h2 className=\"text-center text-2xl font-semibold\">Pricing</h2>\n\n              <Card className=\"p-6\">\n                <h3 className=\"mb-4 font-semibold\">What you get with Shadcraft</h3>\n                <div className=\"grid gap-7 md:grid-cols-2\">\n                  <div className=\"space-y-4\">\n                    <ul className=\"space-y-2\" role=\"list\">\n                      {FIGMA_CONSTANTS.features.map((feature, index) => (\n                        <li key={index} className=\"flex items-center gap-1.5\">\n                          <Check className=\"h-4 w-4 text-green-600\" aria-hidden=\"true\" />\n                          <span className=\"text-sm\">{feature}</span>\n                        </li>\n                      ))}\n                    </ul>\n                  </div>\n\n                  <div className=\"mt-auto space-y-4\">\n                    <div className=\"space-y-1.5\">\n                      <div className=\"flex items-end gap-1\">\n                        <span className=\"text-5xl font-semibold\">$119</span>\n                      </div>\n                    </div>\n                    <div className=\"flex gap-2\">\n                      <Button className=\"flex-1\" onClick={handleGetStarted}>\n                        Get started\n                      </Button>\n                      <Link\n                        href={FIGMA_CONSTANTS.previewUrl}\n                        target=\"_blank\"\n                        rel=\"noopener noreferrer\"\n                      >\n                        <Button variant=\"outline\" className=\"gap-2\">\n                          <FigmaIcon className=\"h-4 w-4\" />\n                          Preview\n                        </Button>\n                      </Link>\n                    </div>\n                  </div>\n                </div>\n              </Card>\n              <p className=\"text-muted-foreground text-center text-xs\">Prices in USD</p>\n            </section>\n          </div>\n        </main>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "app/globals.css",
    "content": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n@import \"streamdown/styles.css\";\n@import \"./loaders.css\";\n\n@source \"../node_modules/streamdown/dist\";\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --color-chart-1: var(--chart-1);\n  --color-chart-2: var(--chart-2);\n  --color-chart-3: var(--chart-3);\n  --color-chart-4: var(--chart-4);\n  --color-chart-5: var(--chart-5);\n  --color-sidebar: var(--sidebar);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-ring: var(--sidebar-ring);\n\n  --font-sans: var(--font-sans);\n  --font-mono: var(--font-mono);\n  --font-serif: var(--font-serif);\n\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n\n  /* Computed Shadow Variants */\n  --shadow-2xs: var(--shadow-2xs);\n  --shadow-xs: var(--shadow-xs);\n  --shadow-sm: var(--shadow-sm);\n  --shadow: var(--shadow);\n  --shadow-md: var(--shadow-md);\n  --shadow-lg: var(--shadow-lg);\n  --shadow-xl: var(--shadow-xl);\n  --shadow-2xl: var(--shadow-2xl);\n\n  --tracking-tighter: calc(var(--letter-spacing) - 0.05em);\n  --tracking-tight: calc(var(--letter-spacing) - 0.025em);\n  --tracking-normal: var(--letter-spacing);\n  --tracking-wide: calc(var(--letter-spacing) + 0.025em);\n  --tracking-wider: calc(var(--letter-spacing) + 0.05em);\n  --tracking-widest: calc(var(--letter-spacing) + 0.1em);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n\n* {\n  color-scheme: light dark;\n  border-color: var(--color-border);\n}\n\nbody {\n  background-color: var(--color-background);\n  color: var(--color-foreground);\n  -webkit-font-smoothing: antialiased;\n  letter-spacing: var(--letter-spacing);\n}\n\n@layer base {\n  button:not(:disabled),\n  [role=\"button\"]:not(:disabled) {\n    cursor: pointer;\n  }\n}\n\n/* View Transition Wave Effect */\n::view-transition-old(root),\n::view-transition-new(root) {\n  animation: none;\n  mix-blend-mode: normal;\n}\n\n::view-transition-old(root) {\n  /* Ensure the outgoing view (old theme) is beneath */\n  z-index: 0;\n}\n\n::view-transition-new(root) {\n  /* Ensure the incoming view (new theme) is always on top */\n  z-index: 1;\n}\n\n@keyframes reveal {\n  from {\n    /* Use CSS variables for the origin, defaulting to center if not set */\n    clip-path: circle(0% at var(--x, 50%) var(--y, 50%));\n    opacity: 0.7;\n  }\n  to {\n    /* Use CSS variables for the origin, defaulting to center if not set */\n    clip-path: circle(150% at var(--x, 50%) var(--y, 50%));\n    opacity: 1;\n  }\n}\n\n::view-transition-new(root) {\n  /* Apply the reveal animation */\n  animation: reveal 0.4s ease-in-out forwards;\n}\n\n/* Mention styles */\n.mention {\n  @apply bg-primary/10 rounded-md px-1 font-mono text-sm font-semibold;\n}\n\n.code-inline {\n  @apply bg-muted text-muted-foreground px-1.5 py-0.5 font-mono text-xs font-medium;\n}\n\n@keyframes text {\n  from {\n    background-position: 0% center;\n  }\n  to {\n    background-position: -200% center;\n  }\n}\n\n.animate-text {\n  animation: text 3s linear infinite;\n}\n\n@utility scrollbar-thin {\n  scrollbar-width: thin;\n  scrollbar-color: var(--color-border) transparent;\n\n  &::-webkit-scrollbar {\n    width: 8px;\n  }\n}\n\n@utility scrollbar-gutter-stable {\n  scrollbar-gutter: stable;\n}\n\n@utility scrollbar-gutter-both {\n  scrollbar-gutter: stable both-edges;\n}\n"
  },
  {
    "path": "app/layout.tsx",
    "content": "import { AuthDialogWrapper } from \"@/components/auth-dialog-wrapper\";\nimport { DynamicFontLoader } from \"@/components/dynamic-font-loader\";\nimport { GetProDialogWrapper } from \"@/components/get-pro-dialog-wrapper\";\nimport { PostHogInit } from \"@/components/posthog-init\";\nimport { ThemeProvider } from \"@/components/theme-provider\";\nimport { ThemeScript } from \"@/components/theme-script\";\nimport { Toaster } from \"@/components/ui/toaster\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport { ChatProvider } from \"@/hooks/use-chat-context\";\nimport { QueryProvider } from \"@/lib/query-client\";\nimport type { Metadata, Viewport } from \"next\";\nimport { NuqsAdapter } from \"nuqs/adapters/next/app\";\nimport { Suspense } from \"react\";\nimport \"./globals.css\";\n\nexport const metadata: Metadata = {\n  title: \"Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator\",\n  description:\n    \"Customize theme for shadcn/ui with tweakcn's interactive editor. Supports Tailwind CSS v4, Shadcn UI, and custom styles. Modify properties, preview changes, and get the code in real time.\",\n  keywords:\n    \"theme editor, theme generator, shadcn, ui, components, react, tailwind, button, editor, visual editor, component editor, web development, frontend, design system, UI components, React components, Tailwind CSS, shadcn/ui themes\",\n  authors: [{ name: \"Sahaj Jain\" }],\n  openGraph: {\n    title: \"Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator\",\n    description:\n      \"Customize theme for shadcn/ui with tweakcn's interactive editor. Supports Tailwind CSS v4, Shadcn UI, and custom styles. Modify properties, preview changes, and get the code in real time.\",\n    url: \"https://tweakcn.com/\",\n    siteName: \"tweakcn\",\n    images: [\n      {\n        url: \"https://tweakcn.com/og-image.v050725.png\",\n        width: 1200,\n        height: 630,\n      },\n    ],\n    locale: \"en_US\",\n    type: \"website\",\n  },\n  twitter: {\n    card: \"summary_large_image\",\n    title: \"Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator\",\n    description:\n      \"Customize theme for shadcn/ui with tweakcn's interactive editor. Supports Tailwind CSS v4, Shadcn UI, and custom styles. Modify properties, preview changes, and get the code in real time.\",\n    images: [\"https://tweakcn.com/og-image.v050725.png\"],\n  },\n  robots: \"index, follow\",\n};\n\nexport const viewport: Viewport = {\n  width: \"device-width\",\n  initialScale: 1.0,\n};\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <ThemeScript />\n        <DynamicFontLoader />\n        <link rel=\"apple-touch-icon\" href=\"/apple-touch-icon.png\" />\n        <link rel=\"icon\" href=\"/favicon.ico\" sizes=\"any\" />\n        <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\" />\n        <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\" />\n        <link\n          rel=\"apple-touch-icon\"\n          href=\"/apple-touch-icon.png\"\n          type=\"image/png\"\n          sizes=\"180x180\"\n        />\n        <link rel=\"manifest\" href=\"/site.webmanifest\" />\n\n        {/* PRELOAD FONTS USED BY BUILT-IN THEMES */}\n        <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n        <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossOrigin=\"anonymous\" />\n        <link\n          href=\"https://fonts.googleapis.com/css2?family=Architects+Daughter&family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Fira+Code:wght@300..700&family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Lora:ital,wght@0,400..700;1,400..700&family=Merriweather:ital,opsz,wght@0,18..144,300..900;1,18..144,300..900&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Outfit:wght@100..900&family=Oxanium:wght@200..800&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Roboto:ital,wght@0,100..900;1,100..900&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&family=Space+Grotesk:wght@300..700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap\"\n          rel=\"stylesheet\"\n        />\n        <meta name=\"darkreader-lock\" />\n      </head>\n      <body>\n        <NuqsAdapter>\n          <Suspense>\n            <QueryProvider>\n              <ThemeProvider defaultTheme=\"light\">\n                <TooltipProvider>\n                  <AuthDialogWrapper />\n                  <GetProDialogWrapper />\n                  <Toaster />\n                  <ChatProvider>{children}</ChatProvider>\n                </TooltipProvider>\n              </ThemeProvider>\n            </QueryProvider>\n          </Suspense>\n        </NuqsAdapter>\n        <PostHogInit />\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "app/loaders.css",
    "content": "/* keyframes for loaders */\n@theme {\n  @keyframes typing {\n    0%,\n    100% {\n      transform: translateY(0);\n      opacity: 0.5;\n    }\n    50% {\n      transform: translateY(-2px);\n      opacity: 1;\n    }\n  }\n\n  @keyframes loading-dots {\n    0%,\n    100% {\n      opacity: 0;\n    }\n    50% {\n      opacity: 1;\n    }\n  }\n\n  @keyframes wave {\n    0%,\n    100% {\n      transform: scaleY(1);\n    }\n    50% {\n      transform: scaleY(0.6);\n    }\n  }\n\n  @keyframes blink {\n    0%,\n    100% {\n      opacity: 1;\n    }\n    50% {\n      opacity: 0;\n    }\n  }\n\n  @keyframes text-blink {\n    0%,\n    100% {\n      color: var(--primary);\n    }\n    50% {\n      color: var(--muted-foreground);\n    }\n  }\n\n  @keyframes bounce-dots {\n    0%,\n    100% {\n      transform: scale(0.8);\n      opacity: 0.5;\n    }\n    50% {\n      transform: scale(1.2);\n      opacity: 1;\n    }\n  }\n\n  @keyframes thin-pulse {\n    0%,\n    100% {\n      transform: scale(0.95);\n      opacity: 0.8;\n    }\n    50% {\n      transform: scale(1.05);\n      opacity: 0.4;\n    }\n  }\n\n  @keyframes pulse-dot {\n    0%,\n    100% {\n      transform: scale(1);\n      opacity: 0.8;\n    }\n    50% {\n      transform: scale(1.5);\n      opacity: 1;\n    }\n  }\n\n  @keyframes shimmer-text {\n    0% {\n      background-position: 150% center;\n    }\n    100% {\n      background-position: -150% center;\n    }\n  }\n\n  @keyframes wave-bars {\n    0%,\n    100% {\n      transform: scaleY(1);\n      opacity: 0.5;\n    }\n    50% {\n      transform: scaleY(0.6);\n      opacity: 1;\n    }\n  }\n\n  @keyframes shimmer {\n    0% {\n      background-position: 200% 50%;\n    }\n    100% {\n      background-position: -200% 50%;\n    }\n  }\n\n  @keyframes spinner-fade {\n    0% {\n      opacity: 0;\n    }\n    100% {\n      opacity: 1;\n    }\n  }\n}\n"
  },
  {
    "path": "app/not-found.tsx",
    "content": "\"use client\";\n\nimport { ThemePresetButtons } from \"@/components/home/theme-preset-buttons\";\nimport { useTheme } from \"@/components/theme-provider\";\nimport { Button } from \"@/components/ui/button\";\nimport { Tooltip, TooltipTrigger, TooltipContent } from \"@/components/ui/tooltip\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { defaultPresets } from \"@/utils/theme-presets\";\nimport { Sun, Moon } from \"lucide-react\";\nimport Link from \"next/link\";\n\nexport default function NotFound() {\n  const { theme, toggleTheme } = useTheme();\n  const { themeState, applyThemePreset } = useEditorStore();\n  const mode = themeState.currentMode;\n  const presetNames = Object.keys(defaultPresets);\n  return (\n    <div className=\"bg-background flex min-h-screen flex-col items-center justify-center px-4\">\n      <div className=\"fixed top-4 right-4 z-50\">\n        <Tooltip>\n          <TooltipTrigger asChild>\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              aria-label=\"Toggle theme\"\n              onClick={(e) => toggleTheme({ x: e.clientX, y: e.clientY })}\n            >\n              {theme === \"light\" ? <Sun className=\"h-5 w-5\" /> : <Moon className=\"h-5 w-5\" />}\n            </Button>\n          </TooltipTrigger>\n          <TooltipContent side=\"bottom\">\n            <p className=\"text-xs\">Toggle theme</p>\n          </TooltipContent>\n        </Tooltip>\n      </div>\n\n      <span className=\"text-muted-foreground mb-6 text-[6rem] leading-none font-extrabold select-none\">\n        404\n      </span>\n      <h1 className=\"text-foreground mb-2 text-3xl font-bold\">Oops, Lost in Space?</h1>\n      <p className=\"text-muted-foreground mb-8 max-w-md text-center text-lg\">\n        Go home or try switching the theme!\n      </p>\n\n      <Link\n        href=\"/\"\n        className=\"bg-primary text-primary-foreground hover:bg-primary/80 mb-10 rounded-md px-6 py-2 font-semibold shadow transition-colors\"\n      >\n        Back to Home\n      </Link>\n\n      <div className=\"flex w-full justify-center\">\n        <ThemePresetButtons\n          presetNames={presetNames}\n          mode={mode}\n          themeState={themeState}\n          applyThemePreset={applyThemePreset}\n        />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/oauth/authorize/page.tsx",
    "content": "\"use client\";\n\nimport Github from \"@/assets/github.svg\";\nimport Google from \"@/assets/google.svg\";\nimport { Button } from \"@/components/ui/button\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { Loader2 } from \"lucide-react\";\nimport { useSearchParams } from \"next/navigation\";\nimport { useEffect, useState } from \"react\";\n\nconst SCOPE_LABELS: Record<string, string> = {\n  \"themes:read\": \"Read your saved themes\",\n  \"profile:read\": \"Read your profile (name, email)\",\n};\n\nexport default function OAuthAuthorizePage() {\n  const searchParams = useSearchParams();\n  const { data: session, isPending } = authClient.useSession();\n\n  const [loadingProvider, setLoadingProvider] = useState<string | null>(null);\n  const [error, setError] = useState<string | null>(null);\n  const [appName, setAppName] = useState<string | null>(null);\n  const [redirecting, setRedirecting] = useState(false);\n\n  const clientId = searchParams.get(\"client_id\");\n  const scopes =\n    searchParams\n      .get(\"scope\")\n      ?.split(/[\\s,]+/)\n      .filter(Boolean) ?? [];\n\n  useEffect(() => {\n    if (!clientId) {\n      setError(\"Missing client_id parameter\");\n      return;\n    }\n\n    fetch(`/api/oauth/app-info?client_id=${encodeURIComponent(clientId)}`)\n      .then((res) => res.json())\n      .then((data) => {\n        if (data.error) {\n          setError(data.error_description ?? \"Invalid application\");\n        } else {\n          setAppName(data.name);\n        }\n      })\n      .catch(() => setError(\"Failed to validate application\"));\n  }, [clientId]);\n\n  useEffect(() => {\n    if (!session || redirecting || error) return;\n\n    setRedirecting(true);\n    const apiUrl = `/api/oauth/authorize?${searchParams.toString()}`;\n    window.location.href = apiUrl;\n  }, [session, searchParams, redirecting, error]);\n\n  const callbackURL = `/oauth/authorize?${searchParams.toString()}`;\n\n  const handleSignIn = async (provider: \"google\" | \"github\") => {\n    setLoadingProvider(provider);\n    try {\n      await authClient.signIn.social({ provider, callbackURL });\n    } catch {\n      setLoadingProvider(null);\n    }\n  };\n\n  const isLoading = loadingProvider !== null;\n\n  if (error) {\n    return (\n      <div className=\"flex min-h-svh items-center justify-center bg-background px-4\">\n        <div className=\"w-full max-w-sm\">\n          <div className=\"rounded-md border border-destructive/20 bg-destructive/5 px-4 py-3 text-sm text-destructive\">\n            {error}\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  if (isPending || redirecting || !appName) {\n    return (\n      <div className=\"flex min-h-svh items-center justify-center bg-background\">\n        <Loader2 className=\"h-5 w-5 animate-spin text-muted-foreground\" />\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex min-h-svh items-center justify-center bg-background px-4\">\n      <div className=\"w-full max-w-sm\">\n        <div className=\"mb-8 text-center\">\n          <p className=\"text-sm text-muted-foreground\">Sign in to</p>\n          <h1 className=\"mt-1 text-lg font-semibold text-foreground\">\n            {appName}\n          </h1>\n        </div>\n\n        <div className=\"space-y-3\">\n          <Button\n            variant=\"outline\"\n            onClick={() => handleSignIn(\"google\")}\n            className=\"h-10 w-full justify-center gap-2\"\n            disabled={isLoading}\n          >\n            <Google className=\"h-4 w-4\" />\n            Continue with Google\n            {loadingProvider === \"google\" && <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />}\n          </Button>\n\n          <Button\n            variant=\"outline\"\n            onClick={() => handleSignIn(\"github\")}\n            className=\"h-10 w-full justify-center gap-2\"\n            disabled={isLoading}\n          >\n            <Github className=\"h-4 w-4\" />\n            Continue with GitHub\n            {loadingProvider === \"github\" && <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />}\n          </Button>\n        </div>\n\n        {scopes.length > 0 && (\n          <div className=\"mt-6 border-t pt-4\">\n            <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n              Permissions requested\n            </p>\n            <ul className=\"space-y-1.5 text-sm text-muted-foreground\">\n              {scopes.map((scope) => (\n                <li key={scope}>{SCOPE_LABELS[scope] ?? scope}</li>\n              ))}\n            </ul>\n          </div>\n        )}\n\n        <p className=\"mt-6 text-center text-xs text-muted-foreground/60\">\n          Authorizing will grant {appName} access to the permissions above.\n        </p>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/page.tsx",
    "content": "\"use client\";\n\nimport { Footer } from \"@/components/footer\";\nimport { AIGenerationCTA } from \"@/components/home/ai-generation-cta\";\nimport { CTA } from \"@/components/home/cta\";\nimport { FAQ } from \"@/components/home/faq\";\nimport { Features } from \"@/components/home/features\";\nimport { Header } from \"@/components/home/header\";\nimport { Hero } from \"@/components/home/hero\";\nimport { HowItWorks } from \"@/components/home/how-it-works\";\nimport { Testimonials } from \"@/components/home/testimonials\";\n// import { ThemePresetSelector } from \"@/components/home/theme-preset-selector\";\nimport { useEffect, useState } from \"react\";\n\nexport default function Home() {\n  const [isScrolled, setIsScrolled] = useState(false);\n  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n\n  useEffect(() => {\n    const handleScroll = () => {\n      if (window.scrollY > 10) {\n        setIsScrolled(true);\n      } else {\n        setIsScrolled(false);\n      }\n    };\n\n    window.addEventListener(\"scroll\", handleScroll);\n    return () => window.removeEventListener(\"scroll\", handleScroll);\n  }, []);\n\n  return (\n    <div className=\"bg-background text-foreground flex min-h-[100dvh] flex-col items-center justify-items-center\">\n      <Header\n        isScrolled={isScrolled}\n        mobileMenuOpen={mobileMenuOpen}\n        setMobileMenuOpen={setMobileMenuOpen}\n      />\n      <main className=\"w-full flex-1\">\n        <Hero />\n        {/* <ThemePresetSelector /> */}\n        <Testimonials />\n        <Features />\n        <AIGenerationCTA />\n        <HowItWorks />\n        <FAQ />\n        <CTA />\n      </main>\n      <Footer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/pricing/components/checkout-button.tsx",
    "content": "\"use client\";\n\nimport { createCheckout } from \"@/actions/checkout\";\nimport { Button } from \"@/components/ui/button\";\nimport { usePostLoginAction } from \"@/hooks/use-post-login-action\";\nimport { SUBSCRIPTION_STATUS_QUERY_KEY, useSubscription } from \"@/hooks/use-subscription\";\nimport { toast } from \"@/hooks/use-toast\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { cn } from \"@/lib/utils\";\nimport { useAuthStore } from \"@/store/auth-store\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { Gem, Loader } from \"lucide-react\";\nimport { useRouter } from \"next/navigation\";\nimport { ComponentProps, useTransition } from \"react\";\n\ninterface CheckoutButtonProps extends ComponentProps<typeof Button> {}\n\nexport function CheckoutButton({ disabled, className, ...props }: CheckoutButtonProps) {\n  const router = useRouter();\n  const [isPending, startTransition] = useTransition();\n  const { data: session } = authClient.useSession();\n  const { openAuthDialog } = useAuthStore();\n\n  usePostLoginAction(\"CHECKOUT\", () => {\n    handleOpenCheckout();\n  });\n\n  const queryClient = useQueryClient();\n  const { subscriptionStatus } = useSubscription();\n  const isPro = subscriptionStatus?.isSubscribed ?? false;\n\n  const handleOpenCheckout = async () => {\n    if (!session) {\n      openAuthDialog(\"signup\", \"CHECKOUT\");\n      return;\n    }\n\n    if (subscriptionStatus?.isSubscribed) {\n      router.push(\"/settings\");\n      return;\n    }\n\n    startTransition(async () => {\n      const res = await createCheckout();\n\n      if (\"error\" in res || !res.url) {\n        toast({\n          title: \"Error\",\n          description: res.error || \"Failed to create checkout\",\n          variant: \"destructive\",\n        });\n        return;\n      }\n\n      queryClient.invalidateQueries({ queryKey: [SUBSCRIPTION_STATUS_QUERY_KEY] });\n      router.push(res.url);\n    });\n  };\n\n  return (\n    <Button\n      variant={isPro ? \"ghost\" : \"default\"}\n      disabled={isPending || disabled}\n      className={cn(isPro ? \"border\" : \"\", className)}\n      {...props}\n      onClick={handleOpenCheckout}\n    >\n      {isPending ? (\n        <div className=\"flex items-center gap-2\">\n          <Loader className=\"size-4 animate-spin\" />\n          Redirecting to Checkout\n        </div>\n      ) : isPro ? (\n        <span className=\"flex items-center gap-1.5\">\n          <Gem />\n          {`You're Subscribed to Pro`}\n        </span>\n      ) : (\n        \"Upgrade to Pro\"\n      )}\n    </Button>\n  );\n}\n"
  },
  {
    "path": "app/pricing/layout.tsx",
    "content": "import { Footer } from \"@/components/footer\";\nimport { Header } from \"@/components/header\";\n\nexport default function PricingLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"flex min-h-screen flex-col\">\n      <Header />\n      <main className=\"flex flex-grow flex-col\">{children}</main>\n      <Footer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/pricing/page.tsx",
    "content": "import { NoiseEffect } from \"@/components/effects/noise-effect\";\nimport {\n  Accordion,\n  AccordionContent,\n  AccordionItem,\n  AccordionTrigger,\n} from \"@/components/ui/accordion\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardFooter, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { AI_REQUEST_FREE_TIER_LIMIT } from \"@/lib/constants\";\nimport { cn } from \"@/lib/utils\";\nimport { FREE_SUB_FEATURES, PRO_SUB_FEATURES } from \"@/utils/subscription\";\nimport { Calendar, Check, Circle, Mail } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { CheckoutButton } from \"./components/checkout-button\";\nimport { Metadata } from \"next\";\nimport { Testimonials } from \"@/components/home/testimonials\";\n\nexport const metadata: Metadata = {\n  title: \"Pricing — tweakcn\",\n  robots: \"index, follow\",\n};\n\nexport default function PricingPage() {\n  return (\n    <div className=\"from-background via-background to-muted/20 relative isolate min-h-screen bg-gradient-to-br\">\n      {/* Background decoration */}\n      <div className=\"absolute inset-0 overflow-hidden\">\n        <div className=\"bg-primary/10 absolute top-0 right-0 size-80 translate-x-1/2 -translate-y-1/2 rounded-full blur-3xl\" />\n        <div className=\"bg-secondary/10 absolute bottom-0 left-0 size-80 -translate-x-1/2 translate-y-1/2 rounded-full blur-3xl\" />\n      </div>\n\n      <div className=\"relative container mx-auto space-y-28 px-4 py-20 md:px-6\">\n        {/* Header Section */}\n        <section className=\"space-y-2 text-center\">\n          <h1 className=\"from-foreground to-foreground/50 bg-gradient-to-r bg-clip-text text-5xl font-bold tracking-tight text-pretty text-transparent md:text-6xl\">\n            Choose your perfect plan\n          </h1>\n          <p className=\"text-muted-foreground mx-auto max-w-3xl text-base text-balance md:text-lg\">\n            Start building beautiful themes for free. Upgrade to Pro when you&apos;re ready.\n          </p>\n        </section>\n\n        {/* Pricing Cards */}\n        <section className=\"mx-auto grid max-w-5xl gap-8 md:grid-cols-2 lg:gap-12\">\n          {/* Free Plan */}\n          <Card className=\"group relative flex flex-col overflow-hidden border-2 transition-all duration-300\">\n            <CardHeader className=\"space-y-2 border-b\">\n              <div className=\"flex items-center gap-3\">\n                <CardTitle className=\"text-4xl font-medium\">Free</CardTitle>\n              </div>\n              <div className=\"flex items-baseline\">\n                <span className=\"text-4xl font-bold tracking-tight lg:text-5xl\">$0</span>\n                <span className=\"text-muted-foreground text-lg\">/month</span>\n              </div>\n              <p className=\"text-muted-foreground text-sm\">No credit card required</p>\n            </CardHeader>\n            <CardContent className=\"flex-1 pt-6\">\n              <ul className=\"space-y-3\">\n                {FREE_SUB_FEATURES.map((feature, index) => (\n                  <li key={index} className=\"flex items-start gap-3\">\n                    <div className=\"bg-primary/15 flex items-center justify-center rounded-full p-1\">\n                      {feature.status === \"done\" ? (\n                        <Check className=\"text-primary size-3 stroke-2\" />\n                      ) : (\n                        <Circle className=\"text-muted-foreground size-3 stroke-2\" />\n                      )}\n                    </div>\n                    <span className=\"text-sm\">{feature.description}</span>\n                  </li>\n                ))}\n              </ul>\n            </CardContent>\n            <CardFooter>\n              <Button\n                asChild\n                variant=\"outline\"\n                className=\"hover:bg-muted/50 h-12 w-full text-base font-medium transition-all duration-200\"\n                size=\"lg\"\n              >\n                <Link href=\"/editor/theme\">Get Started Free</Link>\n              </Button>\n            </CardFooter>\n          </Card>\n\n          {/* Pro Plan */}\n          <Card className=\"group ring-primary/50 from-card to-primary/5 relative border-2 bg-gradient-to-b ring-2 transition-all duration-300\">\n            <div className=\"relative flex h-full flex-col\">\n              <CardHeader className=\"relative space-y-2 border-b\">\n                <NoiseEffect />\n\n                <div className=\"flex items-center gap-3\">\n                  <CardTitle className=\"text-4xl font-medium\">Pro</CardTitle>\n                </div>\n                <div className=\"flex items-baseline\">\n                  <span className=\"text-4xl font-bold tracking-tight lg:text-5xl\">$8</span>\n                  <span className=\"text-muted-foreground text-lg\">/month</span>\n                </div>\n                <p className=\"text-muted-foreground text-sm\">Billed monthly • Cancel anytime</p>\n              </CardHeader>\n              <CardContent className=\"flex-1 pt-6\">\n                <p className=\"text-muted-foreground mb-4 text-sm font-medium\">\n                  Everything in Free, plus:\n                </p>\n                <ul className=\"space-y-3\">\n                  {PRO_SUB_FEATURES.map((feature, index) => (\n                    <li key={index} className=\"flex items-start gap-3\">\n                      <div\n                        className={cn(\n                          \"flex items-center justify-center rounded-full p-1\",\n                          feature.status === \"done\" ? \"bg-primary/15\" : \"bg-muted-foreground/25\"\n                        )}\n                      >\n                        {feature.status === \"done\" ? (\n                          <Check className=\"text-primary size-3 stroke-2\" />\n                        ) : (\n                          <Calendar className=\"text-muted-foreground size-3 stroke-2\" />\n                        )}\n                      </div>\n                      <span\n                        className={cn(\n                          \"text-sm font-medium\",\n                          feature.status === \"done\" ? \"text-foreground\" : \"text-muted-foreground\"\n                        )}\n                      >\n                        {feature.description}\n                      </span>\n                    </li>\n                  ))}\n                </ul>\n              </CardContent>\n              <CardFooter>\n                <CheckoutButton size=\"lg\" className=\"h-12 w-full text-base font-medium\" />\n              </CardFooter>\n            </div>\n          </Card>\n        </section>\n\n        <div className=\"-mt-8\">\n          <Testimonials />\n        </div>\n\n        {/* FAQs Section */}\n        <section className=\"mx-auto max-w-3xl space-y-8\">\n          <div className=\"space-y-2 text-center\">\n            <h2 className=\"from-foreground to-foreground/80 bg-gradient-to-r bg-clip-text text-3xl font-bold tracking-tight text-transparent md:text-4xl\">\n              FAQs\n            </h2>\n            <p className=\"text-muted-foreground mx-auto max-w-2xl text-base text-balance md:text-lg\">\n              Here&apos;s everything you may want to know. For any other info, just{\" \"}\n              <Link href=\"mailto:sahaj@tweakcn.com\" className=\"text-primary hover:underline\">\n                reach us\n              </Link>\n              .\n            </p>\n          </div>\n\n          <Accordion type=\"single\" collapsible className=\"w-full\">\n            {PRICING_FAQS.map((faq, i) => (\n              <AccordionItem\n                key={i}\n                value={`item-${i}`}\n                className=\"border-border/40 group border-b py-2\"\n              >\n                <AccordionTrigger className=\"group-hover:text-primary text-left font-medium transition-colors hover:no-underline\">\n                  {faq.question}\n                </AccordionTrigger>\n                <AccordionContent className=\"text-muted-foreground\">{faq.answer}</AccordionContent>\n              </AccordionItem>\n            ))}\n          </Accordion>\n        </section>\n        {/* Bottom Section */}\n        <div className=\"text-center\">\n          <div className=\"mx-auto max-w-2xl space-y-2\">\n            <p className=\"text-muted-foreground text-pretty\">\n              Need something custom or have questions?\n            </p>\n            <Link href=\"mailto:sahaj@tweakcn.com\">\n              <Button variant=\"link\">\n                <Mail className=\"size-4\" />\n                Get in touch\n              </Button>\n            </Link>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nconst PRICING_FAQS = [\n  {\n    question: \"What do I get when I upgrade to Pro?\",\n    answer: `You get unlimited AI-generated themes, AI theme generation from images, unlimited saved themes, priority support, and more features coming soon. We're developing new features for Pro users!`,\n  },\n  {\n    question: \"Can I still use tweakcn for free?\",\n    answer: `Yes! tweakcn provides a comprehensive free tier that includes theme customization, access to preset themes, and up to ${AI_REQUEST_FREE_TIER_LIMIT} free AI-generated themes. You can build and export themes without any payment required.`,\n  },\n  {\n    question: \"Does tweakcn offer a free trial for the Pro plan?\",\n    answer: `No, there are no free trials. However, you get access to generate up to ${AI_REQUEST_FREE_TIER_LIMIT} themes with AI, plus unlimited manual theme customization using the free visual editor.`,\n  },\n  {\n    question: \"What happens to saved themes when downgrading to free?\",\n    answer:\n      \"All your created themes remain yours forever. When you downgrade from Pro, you keep full access to all themes you've built, but you'll be limited to the free tier's AI generation quota and features.\",\n  },\n  {\n    question: \"Can I cancel or switch at any time?\",\n    answer:\n      \"Yes! You have complete control over your subscription. Cancel anytime through your account settings, and you'll retain Pro access until your current billing period ends before automatically switching to the free tier.\",\n  },\n  {\n    question: \"How secure is the payment?\",\n    answer:\n      \"We use Polar for secure payment processing, which handles all transactions with industry-standard encryption. Your payment details are never stored on our servers.\",\n  },\n];\n"
  },
  {
    "path": "app/r/themes/[id]/route.ts",
    "content": "import { NextResponse } from \"next/server\";\n\nimport { getTheme } from \"@/actions/themes\";\nimport { generateThemeRegistryItemFromStyles } from \"@/utils/registry/themes\";\nimport { registryItemSchema } from \"shadcn/schema\";\nimport { getBuiltInThemeStyles } from \"@/utils/theme-preset-helper\";\nimport { ThemeStyles } from \"@/types/theme\";\n\nexport const dynamic = \"force-static\";\n\nexport async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params;\n\n  try {\n    // First, check if this is a built-in theme\n    let themeName: string;\n    let themeStyles: ThemeStyles;\n\n    const builtInTheme = getBuiltInThemeStyles(id.replace(/\\.json$/, \"\"));\n    if (builtInTheme) {\n      themeName = builtInTheme.name;\n      themeStyles = builtInTheme.styles;\n    } else {\n      // Fall back to database lookup for user-saved themes\n      const theme = await getTheme(id);\n      themeName = theme.name;\n      themeStyles = theme.styles;\n    }\n\n    const generatedRegistryItem = generateThemeRegistryItemFromStyles(themeName, themeStyles);\n\n    // Validate the generated registry item against the official shadcn registry item schema\n    // https://ui.shadcn.com/docs/registry/registry-item-json\n    const parsedRegistryItem = registryItemSchema.safeParse(generatedRegistryItem);\n    if (!parsedRegistryItem.success) {\n      console.error(\n        \"Could not parse the registry item from the database:\",\n        parsedRegistryItem.error.format()\n      );\n\n      return new NextResponse(\"Unexpected registry item format.\", {\n        status: 500,\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n      });\n    }\n\n    // If the parsing is successful, we can safely access the data property\n    // and return it as the registry item json being sure it's in a correct format.\n    const registryItem = parsedRegistryItem.data;\n    return new NextResponse(JSON.stringify(registryItem), {\n      status: 200,\n      headers: {\n        \"Access-Control-Allow-Origin\": \"*\",\n        \"Content-Type\": \"application/json\",\n      },\n    });\n  } catch (e) {\n    console.error(\"Error fetching the theme registry item:\", e);\n\n    return new NextResponse(\"Failed to fetch the theme registry item.\", {\n      status: 500,\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "app/r/v0/[id]/route.ts",
    "content": "import { NextResponse } from \"next/server\";\nimport { getTheme } from \"@/actions/themes\";\nimport { generateV0RegistryPayload } from \"@/utils/registry/v0\";\nimport { getBuiltInThemeStyles } from \"@/utils/theme-preset-helper\";\n\nexport const dynamic = \"force-static\";\n\nexport async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params;\n\n  try {\n    // First, check if this is a built-in theme\n    const builtInTheme = getBuiltInThemeStyles(id.replace(/\\.json$/, \"\"));\n    if (builtInTheme) {\n      const payload = generateV0RegistryPayload(builtInTheme.name, builtInTheme.styles);\n      return new NextResponse(JSON.stringify(payload), {\n        status: 200,\n        headers: {\n          \"Access-Control-Allow-Origin\": \"*\",\n          \"Content-Type\": \"application/json\",\n        },\n      });\n    }\n\n    // Fall back to database lookup for user-saved themes\n    const theme = await getTheme(id);\n    const payload = generateV0RegistryPayload(theme.name, theme.styles);\n\n    return new NextResponse(JSON.stringify(payload), {\n      status: 200,\n      headers: {\n        \"Access-Control-Allow-Origin\": \"*\",\n        \"Content-Type\": \"application/json\",\n      },\n    });\n  } catch (error) {\n    console.error(\"Error generating v0 registry payload:\", error);\n\n    const isNotFound =\n      error instanceof Error &&\n      (error.name === \"ThemeNotFoundError\" || error.message.includes(\"not found\"));\n\n    return new NextResponse(isNotFound ? \"Theme not found\" : \"Failed to generate v0 payload\", {\n      status: isNotFound ? 404 : 500,\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "app/settings/account/components/delete-account-section.tsx",
    "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { usePostHog } from \"posthog-js/react\";\nimport { AlertTriangle, Loader2 } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  AlertDialog,\n  AlertDialogContent,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogCancel,\n} from \"@/components/ui/alert-dialog\";\nimport { Input } from \"@/components/ui/input\";\nimport { toast } from \"@/hooks/use-toast\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { deleteAccount } from \"@/actions/account\";\n\nconst CONFIRMATION_TEXT = \"DELETE\";\n\nexport function DeleteAccountSection() {\n  const [open, setOpen] = useState(false);\n  const [confirmText, setConfirmText] = useState(\"\");\n  const [isDeleting, setIsDeleting] = useState(false);\n  const router = useRouter();\n  const posthog = usePostHog();\n\n  const isConfirmed = confirmText === CONFIRMATION_TEXT;\n\n  const handleDelete = async () => {\n    if (!isConfirmed) return;\n\n    setIsDeleting(true);\n    const result = await deleteAccount();\n\n    if (result.success) {\n      posthog.reset();\n      await authClient.signOut();\n      router.push(\"/\");\n    } else {\n      toast({\n        title: \"Failed to delete account\",\n        description: result.error.message,\n        variant: \"destructive\",\n      });\n      setIsDeleting(false);\n    }\n  };\n\n  return (\n    <>\n      <div className=\"border-destructive/50 rounded-lg border p-6\">\n        <h3 className=\"text-lg font-semibold text-destructive\">Danger Zone</h3>\n        <p className=\"text-muted-foreground mt-1 text-sm\">\n          Permanently delete your account and all associated data. This action\n          cannot be undone.\n        </p>\n        <Button\n          variant=\"destructive\"\n          className=\"mt-4\"\n          onClick={() => setOpen(true)}\n        >\n          Delete Account\n        </Button>\n      </div>\n\n      <AlertDialog open={open} onOpenChange={(v) => {\n        if (!isDeleting) {\n          setOpen(v);\n          if (!v) setConfirmText(\"\");\n        }\n      }}>\n        <AlertDialogContent>\n          <AlertDialogHeader>\n            <AlertDialogTitle className=\"flex items-center gap-2\">\n              <AlertTriangle className=\"size-5 text-destructive\" />\n              Delete your account\n            </AlertDialogTitle>\n            <AlertDialogDescription asChild>\n              <div className=\"space-y-3\">\n                <p>\n                  This will permanently delete your account and all associated\n                  data, including:\n                </p>\n                <ul className=\"list-disc space-y-1 pl-5\">\n                  <li>All saved themes</li>\n                  <li>Community published themes</li>\n                  <li>AI usage history</li>\n                  <li>Active subscription (if any)</li>\n                </ul>\n                <p className=\"font-medium\">This action cannot be undone.</p>\n                <div className=\"pt-1\">\n                  <label\n                    htmlFor=\"confirm-delete\"\n                    className=\"text-sm font-medium\"\n                  >\n                    Type <span className=\"font-mono font-bold\">{CONFIRMATION_TEXT}</span> to\n                    confirm\n                  </label>\n                  <Input\n                    id=\"confirm-delete\"\n                    className=\"mt-1.5\"\n                    value={confirmText}\n                    onChange={(e) => setConfirmText(e.target.value)}\n                    placeholder={CONFIRMATION_TEXT}\n                    disabled={isDeleting}\n                    autoComplete=\"off\"\n                  />\n                </div>\n              </div>\n            </AlertDialogDescription>\n          </AlertDialogHeader>\n          <AlertDialogFooter>\n            <AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>\n            <Button\n              variant=\"destructive\"\n              onClick={handleDelete}\n              disabled={!isConfirmed || isDeleting}\n            >\n              {isDeleting && <Loader2 className=\"mr-2 size-4 animate-spin\" />}\n              Delete Account\n            </Button>\n          </AlertDialogFooter>\n        </AlertDialogContent>\n      </AlertDialog>\n    </>\n  );\n}\n"
  },
  {
    "path": "app/settings/account/page.tsx",
    "content": "import { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { SettingsHeader } from \"../components/settings-header\";\nimport { DeleteAccountSection } from \"./components/delete-account-section\";\n\nexport default async function AccountPage() {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  if (!session) redirect(\"/editor/theme\");\n\n  return (\n    <div>\n      <SettingsHeader\n        title=\"Account\"\n        description=\"Manage your account settings\"\n      />\n      <DeleteAccountSection />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/settings/components/customer-portal-link.tsx",
    "content": "import { auth } from \"@/lib/auth\";\nimport { polar } from \"@/lib/polar\";\nimport { cn } from \"@/lib/utils\";\nimport { headers } from \"next/headers\";\nimport Link from \"next/link\";\n\nexport async function CustomerPortalLink() {\n  const session = await auth.api.getSession({ headers: await headers() });\n\n  if (!session?.user.id) {\n    return null;\n  }\n\n  const result = await polar.customerSessions.create({\n    externalCustomerId: session?.user.id,\n  });\n\n  const customerPortalLink = result.customerPortalUrl;\n\n  return (\n    <Link\n      key=\"customer-portal\"\n      href={customerPortalLink}\n      className={cn(\n        \"hover:bg-muted flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors\"\n      )}\n    >\n      Manage Subscription\n    </Link>\n  );\n}\n"
  },
  {
    "path": "app/settings/components/settings-header.tsx",
    "content": "export function SettingsHeader({ title, description }: { title: string; description?: string }) {\n  return (\n    <div className=\"mb-4 flex flex-col\">\n      <h1 className=\"text-xl font-bold md:text-2xl\">{title}</h1>\n      {description && <p className=\"text-muted-foreground text-sm md:text-base\">{description}</p>}\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/settings/components/settings-sidebar.tsx",
    "content": "\"use client\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { useSubscription } from \"@/hooks/use-subscription\";\nimport { cn } from \"@/lib/utils\";\nimport { ChartNoAxesCombined, CreditCard, ExternalLink, LucideIcon, Palette, UserCog } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport { useMemo } from \"react\";\n\ntype NavItem =\n  | {\n      type: \"link\";\n      href: string;\n      label: string;\n      icon?: LucideIcon;\n      isExternal?: boolean;\n    }\n  | {\n      type: \"separator\";\n      id: string;\n    };\n\nconst BASE_NAV_ITEMS: NavItem[] = [\n  { type: \"link\", href: \"/settings/themes\", label: \"Themes\", icon: Palette },\n  { type: \"link\", href: \"/settings/usage\", label: \"AI Usage\", icon: ChartNoAxesCombined },\n  { type: \"separator\", id: \"account-separator\" },\n  { type: \"link\", href: \"/settings/account\", label: \"Account\", icon: UserCog },\n];\n\nconst getSubscriptionNavItems = (): NavItem[] => [\n  { type: \"separator\", id: \"subscription-separator\" },\n  {\n    type: \"link\",\n    href: \"/settings/portal\",\n    label: \"Manage Subscription\",\n    icon: CreditCard,\n    isExternal: true,\n  },\n];\n\nexport function SettingsSidebar() {\n  const pathname = usePathname();\n  const { subscriptionStatus } = useSubscription();\n\n  const navItems = useMemo(() => {\n    if (subscriptionStatus?.isSubscribed) {\n      return [...BASE_NAV_ITEMS, ...getSubscriptionNavItems()];\n    }\n    return BASE_NAV_ITEMS;\n  }, [subscriptionStatus?.isSubscribed]);\n\n  return (\n    <aside className=\"w-64 shrink-0\">\n      <nav className=\"space-y-1\">\n        {navItems.map((item) => {\n          if (item.type === \"separator\") {\n            return <Separator key={item.id} className=\"my-2\" />;\n          }\n\n          const isActive = pathname === item.href;\n          return (\n            <Link\n              key={item.href}\n              href={item.href}\n              className={cn(\n                \"hover:bg-muted flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors\",\n                isActive && \"bg-muted\"\n              )}\n            >\n              {item.icon && <item.icon className=\"size-4\" />}\n              {item.label}\n              {item.isExternal && <ExternalLink className=\"ml-auto size-4\" />}\n            </Link>\n          );\n        })}\n      </nav>\n    </aside>\n  );\n}\n"
  },
  {
    "path": "app/settings/components/theme-card.tsx",
    "content": "\"use client\";\n\nimport { Theme } from \"@/types/theme\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n  DropdownMenuSeparator,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n} from \"@/components/ui/alert-dialog\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { TagSelector } from \"@/components/tag-selector\";\nimport {\n  MoreVertical,\n  Trash2,\n  Edit,\n  Loader2,\n  Zap,\n  ExternalLink,\n  Copy,\n  Globe,\n  GlobeLock,\n  Tag,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { useState } from \"react\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { useDeleteTheme } from \"@/hooks/themes\";\nimport {\n  usePublishTheme,\n  useUnpublishTheme,\n  useUpdateCommunityThemeTags,\n} from \"@/hooks/themes\";\nimport Link from \"next/link\";\nimport { toast } from \"@/components/ui/use-toast\";\nimport { ThemePreview } from \"@/components/theme-preview\";\n\ninterface ThemeCardProps {\n  theme: Theme;\n  isPublished?: boolean;\n  className?: string;\n}\n\nexport function ThemeCard({\n  theme,\n  isPublished = false,\n  className,\n}: ThemeCardProps) {\n  const { themeState, setThemeState } = useEditorStore();\n  const deleteThemeMutation = useDeleteTheme();\n  const publishMutation = usePublishTheme();\n  const unpublishMutation = useUnpublishTheme();\n  const updateTagsMutation = useUpdateCommunityThemeTags();\n  const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n  const [showPublishDialog, setShowPublishDialog] = useState(false);\n  const [showEditTagsDialog, setShowEditTagsDialog] = useState(false);\n  const [selectedTags, setSelectedTags] = useState<string[]>([]);\n  const mode = themeState.currentMode;\n\n  const handleDelete = () => {\n    setShowDeleteDialog(true);\n  };\n\n  const handleConfirmDelete = () => {\n    deleteThemeMutation.mutate(theme.id, {\n      onSuccess: () => {\n        setShowDeleteDialog(false);\n      },\n    });\n  };\n\n  const handleQuickApply = () => {\n    setThemeState({\n      ...themeState,\n      styles: theme.styles,\n    });\n  };\n\n  const handleShare = () => {\n    const url = `https://tweakcn.com/themes/${theme.id}`;\n    navigator.clipboard.writeText(url);\n    toast({\n      title: \"Theme URL copied to clipboard!\",\n    });\n  };\n\n  const handlePublish = () => {\n    setShowPublishDialog(true);\n  };\n\n  const handleConfirmPublish = () => {\n    publishMutation.mutate(\n      { themeId: theme.id, tags: selectedTags },\n      {\n        onSuccess: () => {\n          setShowPublishDialog(false);\n          setSelectedTags([]);\n        },\n      }\n    );\n  };\n\n  const handleEditTags = () => {\n    setSelectedTags([]);\n    setShowEditTagsDialog(true);\n  };\n\n  const handleConfirmEditTags = () => {\n    updateTagsMutation.mutate(\n      { themeId: theme.id, tags: selectedTags },\n      {\n        onSuccess: () => {\n          setShowEditTagsDialog(false);\n          setSelectedTags([]);\n        },\n      }\n    );\n  };\n\n  const handleUnpublish = () => {\n    unpublishMutation.mutate(theme.id);\n  };\n\n  return (\n    <Card\n      className={cn(\n        \"group overflow-hidden border shadow-sm transition-all duration-300 hover:shadow-md\",\n        className\n      )}\n    >\n      <div className=\"relative h-36 w-full overflow-hidden bg-muted\">\n        <ThemePreview\n          styles={theme.styles[mode]}\n          name={theme.name}\n          className=\"transition-transform duration-300 group-hover:scale-105\"\n        />\n      </div>\n\n      <div className=\"bg-background flex items-center justify-between p-4\">\n        <div className=\"flex items-center gap-2\">\n          <div>\n            <div className=\"flex items-center gap-2\">\n              <h3 className={cn(\"text-foreground text-sm font-medium\")}>\n                {theme.name}\n              </h3>\n              {isPublished && (\n                <Badge\n                  variant=\"secondary\"\n                  className=\"text-[10px] px-1.5 py-0\"\n                >\n                  Published\n                </Badge>\n              )}\n            </div>\n            <p className=\"text-muted-foreground text-xs\">\n              {new Date(theme.createdAt).toLocaleDateString(\"en-US\", {\n                day: \"numeric\",\n                month: \"short\",\n                year: \"numeric\",\n              })}\n            </p>\n          </div>\n        </div>\n        <DropdownMenu>\n          <DropdownMenuTrigger>\n            <div className=\"hover:bg-accent rounded-md p-2\">\n              <MoreVertical className=\"text-muted-foreground h-4 w-4\" />\n            </div>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent align=\"end\" className=\"bg-popover w-48\">\n            <DropdownMenuItem onClick={handleQuickApply} className=\"gap-2\">\n              <Zap className=\"h-4 w-4\" />\n              Quick Apply\n            </DropdownMenuItem>\n            <DropdownMenuItem asChild className=\"gap-2\">\n              <Link href={`/themes/${theme.id}`} target=\"_blank\">\n                <ExternalLink className=\"h-4 w-4\" />\n                Open Theme\n              </Link>\n            </DropdownMenuItem>\n            <DropdownMenuItem asChild className=\"gap-2\">\n              <Link href={`/editor/theme/${theme.id}`}>\n                <Edit className=\"h-4 w-4\" />\n                Edit Theme\n              </Link>\n            </DropdownMenuItem>\n            <DropdownMenuItem onClick={handleShare} className=\"gap-2\">\n              <Copy className=\"h-4 w-4\" />\n              Copy URL\n            </DropdownMenuItem>\n            <DropdownMenuSeparator className=\"mx-2\" />\n            {isPublished ? (\n              <>\n                <DropdownMenuItem\n                  onClick={handleEditTags}\n                  className=\"gap-2\"\n                >\n                  <Tag className=\"h-4 w-4\" />\n                  Edit Tags\n                </DropdownMenuItem>\n                <DropdownMenuItem\n                  onClick={handleUnpublish}\n                  className=\"gap-2\"\n                  disabled={unpublishMutation.isPending}\n                >\n                  {unpublishMutation.isPending ? (\n                    <Loader2 className=\"h-4 w-4 animate-spin\" />\n                  ) : (\n                    <GlobeLock className=\"h-4 w-4\" />\n                  )}\n                  Unpublish from Community\n                </DropdownMenuItem>\n              </>\n            ) : (\n              <DropdownMenuItem\n                onClick={handlePublish}\n                className=\"gap-2\"\n                disabled={publishMutation.isPending}\n              >\n                {publishMutation.isPending ? (\n                  <Loader2 className=\"h-4 w-4 animate-spin\" />\n                ) : (\n                  <Globe className=\"h-4 w-4\" />\n                )}\n                Publish to Community\n              </DropdownMenuItem>\n            )}\n            <DropdownMenuSeparator className=\"mx-2\" />\n            <DropdownMenuItem\n              onClick={handleDelete}\n              className=\"text-destructive focus:text-destructive gap-2\"\n              disabled={deleteThemeMutation.isPending}\n            >\n              {deleteThemeMutation.isPending ? (\n                <Loader2 className=\"h-4 w-4 animate-spin\" />\n              ) : (\n                <Trash2 className=\"h-4 w-4\" />\n              )}\n              Delete Theme\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </div>\n\n      <AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>\n        <AlertDialogContent>\n          <AlertDialogHeader>\n            <AlertDialogTitle>\n              Are you sure you want to delete your {theme.name} theme?\n            </AlertDialogTitle>\n            <AlertDialogDescription>\n              This action cannot be undone. This will permanently delete your\n              theme.\n              {isPublished &&\n                \" It will also be removed from the community.\"}\n            </AlertDialogDescription>\n          </AlertDialogHeader>\n          <AlertDialogFooter>\n            <AlertDialogCancel>Cancel</AlertDialogCancel>\n            <AlertDialogAction\n              onClick={handleConfirmDelete}\n              className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n              disabled={deleteThemeMutation.isPending}\n            >\n              {deleteThemeMutation.isPending ? (\n                <>\n                  <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n                  Deleting...\n                </>\n              ) : (\n                \"Delete\"\n              )}\n            </AlertDialogAction>\n          </AlertDialogFooter>\n        </AlertDialogContent>\n      </AlertDialog>\n\n      <Dialog open={showPublishDialog} onOpenChange={setShowPublishDialog}>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>\n              Publish &quot;{theme.name}&quot; to the community?\n            </DialogTitle>\n            <DialogDescription>\n              Your theme will be publicly visible on the community page. You can\n              unpublish it at any time.\n            </DialogDescription>\n          </DialogHeader>\n          <TagSelector\n            selectedTags={selectedTags}\n            onTagsChange={setSelectedTags}\n            disabled={publishMutation.isPending}\n          />\n          <DialogFooter>\n            <Button\n              variant=\"outline\"\n              onClick={() => setShowPublishDialog(false)}\n            >\n              Cancel\n            </Button>\n            <Button\n              onClick={handleConfirmPublish}\n              disabled={publishMutation.isPending}\n            >\n              {publishMutation.isPending ? (\n                <>\n                  <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n                  Publishing...\n                </>\n              ) : (\n                \"Publish\"\n              )}\n            </Button>\n          </DialogFooter>\n        </DialogContent>\n      </Dialog>\n\n      <Dialog open={showEditTagsDialog} onOpenChange={setShowEditTagsDialog}>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>Edit Tags</DialogTitle>\n            <DialogDescription>\n              Update the tags for &quot;{theme.name}&quot;.\n            </DialogDescription>\n          </DialogHeader>\n          <TagSelector\n            selectedTags={selectedTags}\n            onTagsChange={setSelectedTags}\n            disabled={updateTagsMutation.isPending}\n          />\n          <DialogFooter>\n            <Button\n              variant=\"outline\"\n              onClick={() => setShowEditTagsDialog(false)}\n            >\n              Cancel\n            </Button>\n            <Button\n              onClick={handleConfirmEditTags}\n              disabled={updateTagsMutation.isPending}\n            >\n              {updateTagsMutation.isPending ? (\n                <>\n                  <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n                  Saving...\n                </>\n              ) : (\n                \"Save Tags\"\n              )}\n            </Button>\n          </DialogFooter>\n        </DialogContent>\n      </Dialog>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "app/settings/components/themes-list.tsx",
    "content": "\"use client\";\n\nimport { Card } from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport type { Theme } from \"@/types/theme\";\nimport { ArrowUpDown, Search } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { ThemeCard } from \"./theme-card\";\n\ninterface ThemeWithPublished extends Theme {\n  isPublished: boolean;\n}\n\ninterface ThemesListProps {\n  themes: ThemeWithPublished[];\n}\n\nexport function ThemesList({ themes }: ThemesListProps) {\n  const [filteredThemes, setFilteredThemes] =\n    useState<ThemeWithPublished[]>(themes);\n  const [searchTerm, setSearchTerm] = useState(\"\");\n  const [sortOption, setSortOption] = useState(\"newest\");\n  const isMobile = useIsMobile();\n\n  useEffect(() => {\n    const filtered = themes.filter((theme) =>\n      theme.name?.toLowerCase().includes(searchTerm.toLowerCase())\n    );\n\n    // Sort based on selected option\n    const sorted = [...filtered].sort((a, b) => {\n      switch (sortOption) {\n        case \"newest\":\n          return (b.createdAt?.getTime() || 0) - (a.createdAt?.getTime() || 0);\n        case \"oldest\":\n          return (a.createdAt?.getTime() || 0) - (b.createdAt?.getTime() || 0);\n        case \"name\":\n          return (a.name || \"\").localeCompare(b.name || \"\");\n        default:\n          return 0;\n      }\n    });\n\n    setFilteredThemes(sorted);\n  }, [themes, searchTerm, sortOption]);\n\n  return (\n    <section className=\"space-y-4\">\n      <div className=\"ml-auto flex w-fit flex-row gap-2\">\n        <div className=\"relative w-fit\">\n          <Search className=\"text-muted-foreground absolute top-2.5 left-2.5 size-4\" />\n          <Input\n            placeholder=\"Search themes...\"\n            className=\"w-full pl-8\"\n            value={searchTerm}\n            onChange={(e) => setSearchTerm(e.target.value)}\n          />\n        </div>\n        <Select value={sortOption} onValueChange={setSortOption}>\n          <SelectTrigger className=\"w-[80px] gap-2 md:w-[180px]\">\n            <ArrowUpDown className=\"text-muted-foreground h-4 w-4\" />\n            {!isMobile && <SelectValue placeholder=\"Sort by\" />}\n          </SelectTrigger>\n          <SelectContent>\n            <SelectItem value=\"newest\">Newest First</SelectItem>\n            <SelectItem value=\"oldest\">Oldest First</SelectItem>\n            <SelectItem value=\"name\">Name (A-Z)</SelectItem>\n          </SelectContent>\n        </Select>\n      </div>\n\n      <Card className=\"space-y-4 p-4\">\n        {filteredThemes.length === 0 && searchTerm ? (\n          <div className=\"py-12 text-center\">\n            <Search className=\"text-muted-foreground mx-auto mb-4 size-12\" />\n            <h3 className=\"mb-1 text-lg font-medium\">No themes found</h3>\n            <p className=\"text-muted-foreground text-pretty\">\n              No themes match your search term &quot;{searchTerm}&quot;.\n            </p>\n          </div>\n        ) : (\n          <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n            {filteredThemes.map((theme) => (\n              <ThemeCard\n                key={theme.id}\n                theme={theme}\n                isPublished={theme.isPublished}\n              />\n            ))}\n          </div>\n        )}\n      </Card>\n    </section>\n  );\n}\n"
  },
  {
    "path": "app/settings/components/usage-stats.tsx",
    "content": "\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { Card, CardContent, CardHeader } from \"@/components/ui/card\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { ChartContainer, ChartTooltip, ChartTooltipContent } from \"@/components/ui/chart\";\nimport { BarChart, Bar, XAxis, YAxis } from \"recharts\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { getMyUsageStats, getMyUsageChartData } from \"@/actions/ai-usage\";\n\ntype Timeframe = \"1d\" | \"7d\" | \"30d\";\n\ninterface UsageStats {\n  requests: number;\n  timeframe: Timeframe;\n}\n\ninterface ChartDataPoint {\n  daysSinceEpoch?: number;\n  hoursSinceEpoch?: number;\n  date: string;\n  totalRequests: number;\n}\n\nconst timeframeLabels = {\n  \"1d\": \"Last 24 hours\",\n  \"7d\": \"Last 7 days\",\n  \"30d\": \"Last 30 days\",\n};\n\nconst chartConfig = {\n  totalRequests: {\n    label: \"Requests\",\n    color: \"var(--primary)\",\n  },\n};\n\nexport function UsageStats() {\n  const [timeframe, setTimeframe] = useState<Timeframe>(\"7d\");\n  const [stats, setStats] = useState<UsageStats | null>(null);\n  const [chartData, setChartData] = useState<ChartDataPoint[]>([]);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    const fetchData = async () => {\n      setLoading(true);\n      try {\n        const [statsData, chartDataResponse] = await Promise.all([\n          getMyUsageStats(timeframe),\n          getMyUsageChartData(timeframe),\n        ]);\n        setStats(statsData);\n        setChartData(chartDataResponse);\n      } catch (error) {\n        console.error(\"Error fetching usage data:\", error);\n      } finally {\n        setLoading(false);\n      }\n    };\n\n    fetchData();\n  }, [timeframe]);\n\n  const formatDate = (dateString: string, timeframe: Timeframe) => {\n    const date = new Date(dateString);\n    if (timeframe === \"1d\") {\n      return date.toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" });\n    }\n    return date.toLocaleDateString([], { month: \"short\", day: \"numeric\" });\n  };\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"flex items-center justify-end\">\n        <Select value={timeframe} onValueChange={(value: Timeframe) => setTimeframe(value)}>\n          <SelectTrigger className=\"w-[160px]\">\n            <SelectValue />\n          </SelectTrigger>\n          <SelectContent>\n            <SelectItem value=\"1d\">Last 24 hours</SelectItem>\n            <SelectItem value=\"7d\">Last 7 days</SelectItem>\n            <SelectItem value=\"30d\">Last 30 days</SelectItem>\n          </SelectContent>\n        </Select>\n      </div>\n\n      <Card>\n        <CardHeader className=\"pb-4\">\n          {loading ? (\n            <div className=\"space-y-2\">\n              <Skeleton className=\"h-8 w-24\" />\n              <Skeleton className=\"h-4 w-48\" />\n            </div>\n          ) : (\n            <div className=\"space-y-1\">\n              <div className=\"text-3xl font-bold tracking-tight\">{stats?.requests || 0}</div>\n              <p className=\"text-muted-foreground text-sm\">\n                requests in {timeframeLabels[timeframe].toLowerCase()}\n              </p>\n            </div>\n          )}\n        </CardHeader>\n        <CardContent className=\"pt-0\">\n          {loading ? (\n            <div className=\"space-y-3\">\n              <div className=\"flex justify-between\">\n                <Skeleton className=\"h-4 w-12\" />\n                <Skeleton className=\"h-4 w-12\" />\n                <Skeleton className=\"h-4 w-12\" />\n                <Skeleton className=\"h-4 w-12\" />\n              </div>\n              <Skeleton className=\"h-[200px] w-full rounded-md\" />\n            </div>\n          ) : chartData.length > 0 ? (\n            <ChartContainer config={chartConfig} className=\"h-[200px] w-full\">\n              <BarChart data={chartData}>\n                <XAxis\n                  dataKey=\"date\"\n                  tickFormatter={(value) => formatDate(value, timeframe)}\n                  axisLine={false}\n                  tickLine={false}\n                  className=\"text-xs\"\n                />\n                <YAxis hide />\n                <ChartTooltip\n                  content={<ChartTooltipContent />}\n                  labelFormatter={(value) => formatDate(value, timeframe)}\n                />\n                <Bar\n                  dataKey=\"totalRequests\"\n                  fill=\"var(--color-totalRequests)\"\n                  radius={[4, 4, 0, 0]}\n                />\n              </BarChart>\n            </ChartContainer>\n          ) : (\n            <div className=\"flex h-[200px] items-center justify-center\">\n              <div className=\"text-center\">\n                <p className=\"text-muted-foreground text-sm\">No usage data available</p>\n                <p className=\"text-muted-foreground mt-1 text-xs\">\n                  Make some AI requests to see your usage statistics\n                </p>\n              </div>\n            </div>\n          )}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/settings/components/user-info.tsx",
    "content": "\"use client\";\n\nimport { useSubscription } from \"@/hooks/use-subscription\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { Gem } from \"lucide-react\";\n\nexport function UserInfo() {\n  const { data: session } = authClient.useSession();\n  const { subscriptionStatus } = useSubscription();\n  const isPro = subscriptionStatus?.isSubscribed ?? false;\n\n  return (\n    <div className=\"flex flex-col space-y-0.5\">\n      <p className=\"text-sm leading-tight font-medium\">\n        {session?.user.name}{\" \"}\n        {isPro && (\n          <span className=\"bg-accent text-accent-foreground inline-flex w-fit items-center gap-1 rounded-md px-1 py-0.5 text-xs leading-tight font-medium\">\n            <Gem className=\"size-2.5\" /> Pro\n          </span>\n        )}\n      </p>\n      <p className=\"text-muted-foreground text-xs leading-tight\">{session?.user.email}</p>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/settings/layout.tsx",
    "content": "import { Header } from \"@/components/header\";\nimport { ThemeToggle } from \"@/components/theme-toggle\";\nimport { Button } from \"@/components/ui/button\";\nimport { ArrowLeft } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { SettingsSidebar } from \"./components/settings-sidebar\";\n\nexport default async function SettingsLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"flex flex-col\">\n      <Header />\n\n      <div className=\"container mx-auto flex w-full max-w-7xl items-center justify-between gap-2 px-4 pt-8 pb-4 md:justify-start md:px-6\">\n        <Button variant=\"ghost\" asChild>\n          <Link href=\"/editor/theme\">\n            <ArrowLeft /> Back to Editor\n          </Link>\n        </Button>\n\n        <ThemeToggle variant=\"ghost\" size=\"icon\" />\n      </div>\n\n      <main className=\"container mx-auto flex w-full max-w-7xl flex-1 flex-col gap-4 px-4 py-4 md:flex-row md:px-6 md:py-8\">\n        <SettingsSidebar />\n        <div className=\"mx-auto w-full max-w-4xl flex-1\">{children}</div>\n      </main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/settings/page.tsx",
    "content": "import { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\n\nexport default async function SettingsIndex() {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  if (!session) redirect(\"/editor/theme\");\n\n  redirect(\"/settings/themes\");\n}\n"
  },
  {
    "path": "app/settings/portal/route.ts",
    "content": "import { polar } from \"@/lib/polar\";\nimport { getCurrentUserId } from \"@/lib/shared\";\nimport { CustomerPortal } from \"@polar-sh/nextjs\";\n\nexport const GET = CustomerPortal({\n  accessToken: process.env.POLAR_ACCESS_TOKEN!,\n  server: process.env.NODE_ENV === \"production\" ? \"production\" : \"sandbox\",\n  getCustomerId: async (req) => {\n    const userId = await getCurrentUserId(req);\n    const customer = await polar.customers.getExternal({ externalId: userId });\n    return customer.id;\n  },\n});\n"
  },
  {
    "path": "app/settings/themes/page.tsx",
    "content": "import { getThemes } from \"@/actions/themes\";\nimport { ThemesList } from \"@/app/settings/components/themes-list\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport { auth } from \"@/lib/auth\";\nimport { Palette, Plus } from \"lucide-react\";\nimport { headers } from \"next/headers\";\nimport Link from \"next/link\";\nimport { redirect } from \"next/navigation\";\nimport { SettingsHeader } from \"../components/settings-header\";\n\nexport default async function ThemesPage() {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  if (!session) redirect(\"/editor/theme\");\n\n  const themes = await getThemes();\n  const sortedThemes = themes.sort((a, b) => {\n    return (b.createdAt?.getTime() || 0) - (a.createdAt?.getTime() || 0);\n  });\n\n  return (\n    <div>\n      <SettingsHeader title=\"Your Themes\" description=\"View and manage your themes\" />\n      {sortedThemes.length === 0 ? (\n        <Card className=\"flex flex-col items-center justify-center p-4 py-12 text-center\">\n          <div className=\"bg-primary/10 mb-6 rounded-full p-4\">\n            <Palette className=\"text-primary size-12\" />\n          </div>\n          <h2 className=\"mb-2 text-xl font-semibold md:text-2xl\">No themes created yet</h2>\n          <p className=\"text-muted-foreground mb-6 max-w-md text-pretty\">\n            Create your first custom theme to personalize your projects with unique color palettes.\n          </p>\n          <div className=\"w-full max-w-md\">\n            <Link href=\"/editor/theme\">\n              <Button size=\"lg\" className=\"w-full gap-2\">\n                <Plus className=\"size-4\" />\n                Create Your First Theme\n              </Button>\n            </Link>\n          </div>\n        </Card>\n      ) : (\n        <ThemesList themes={sortedThemes} />\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/settings/usage/page.tsx",
    "content": "import { UsageStats } from \"@/app/settings/components/usage-stats\";\nimport { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { SettingsHeader } from \"../components/settings-header\";\n\nexport default async function UsagePage() {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  if (!session) redirect(\"/editor/theme\");\n\n  return (\n    <div>\n      <SettingsHeader title=\"AI Usage\" description=\"Track your AI theme generation requests\" />\n      <UsageStats />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/sitemap.ts",
    "content": "import { MetadataRoute } from \"next\";\n\nexport default function sitemap(): MetadataRoute.Sitemap {\n  const baseUrl = process.env.BASE_URL ?? \"https://tweakcn.com\";\n\n  return [\n    {\n      url: baseUrl,\n      lastModified: new Date(),\n      changeFrequency: \"weekly\",\n      priority: 1,\n    },\n    {\n      url: `${baseUrl}/editor/theme`,\n      lastModified: new Date(),\n      changeFrequency: \"weekly\",\n      priority: 0.8,\n    },\n    {\n      url: `${baseUrl}/ai`,\n      lastModified: new Date(),\n      changeFrequency: \"weekly\",\n      priority: 0.8,\n    },\n    {\n      url: `${baseUrl}/pricing`,\n      lastModified: new Date(),\n      changeFrequency: \"weekly\",\n      priority: 0.6,\n    },\n    {\n      url: `${baseUrl}/community`,\n      lastModified: new Date(),\n      changeFrequency: \"daily\",\n      priority: 0.8,\n    },\n    {\n      url: `${baseUrl}/figma`,\n      lastModified: new Date(),\n      changeFrequency: \"monthly\",\n      priority: 0.7,\n    },\n  ];\n}\n"
  },
  {
    "path": "app/success/layout.tsx",
    "content": "export default function SuccessLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"flex min-h-screen flex-col\">\n      <main className=\"flex flex-grow flex-col\">{children}</main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/success/page.tsx",
    "content": "import { NoiseEffect } from \"@/components/effects/noise-effect\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { ArrowRight, CheckCircle } from \"lucide-react\";\nimport Link from \"next/link\";\n\nexport default function SuccessPage() {\n  return (\n    <div className=\"from-background to-muted/20 flex min-h-screen items-center justify-center bg-gradient-to-br p-4\">\n      <Card className=\"w-full max-w-lg overflow-hidden border-0 shadow-2xl\">\n        <CardHeader className=\"relative flex flex-col items-center space-y-4\">\n          {/* Success Icon */}\n          <div className=\"relative\">\n            <div className=\"absolute inset-0 animate-pulse rounded-full bg-green-500/20 blur-xl\" />\n            <div className=\"relative rounded-full bg-green-500/10 p-4\">\n              <CheckCircle className=\"size-12 text-green-600 dark:text-green-400\" />\n            </div>\n          </div>\n\n          <CardTitle>\n            <h1 className=\"text-foreground text-xl font-bold tracking-tight md:text-3xl\">\n              Payment Successful!\n            </h1>\n          </CardTitle>\n          <CardDescription>\n            <p className=\"text-muted-foreground text-center text-base text-pretty md:text-lg\">\n              Welcome to <span className=\"text-foreground font-semibold\">tweakcn Pro</span>! Your\n              subscription is now active and you have access to all premium features.\n            </p>\n          </CardDescription>\n        </CardHeader>\n\n        <CardContent className=\"space-y-4 text-center\">\n          <Button asChild size=\"lg\" className=\"group w-full\">\n            <Link href=\"/editor/theme\" className=\"flex items-center justify-center gap-2\">\n              Continue Editing\n              <ArrowRight className=\"h-4 w-4 transition-transform group-hover:translate-x-1\" />\n            </Link>\n          </Button>\n\n          <Button asChild size=\"lg\" variant=\"outline\" className=\"group w-full\">\n            <Link href=\"/settings\">\n              Go to Settings\n              <ArrowRight className=\"h-4 w-4 transition-transform group-hover:translate-x-1\" />\n            </Link>\n          </Button>\n        </CardContent>\n\n        <CardFooter className=\"relative border-t pt-6\">\n          <NoiseEffect />\n          <p className=\"text-muted-foreground w-full text-center text-sm\">\n            Need help?{\" \"}\n            <Link href=\"mailto:sahaj@tweakcn.com\" className=\"text-primary hover:underline\">\n              Contact us\n            </Link>\n          </p>\n        </CardFooter>\n      </Card>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/themes/[themeId]/error.tsx",
    "content": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport Link from \"next/link\";\nimport { Button } from \"@/components/ui/button\";\n\nexport default function ThemeError({\n  error,\n  reset,\n}: {\n  error: Error & { digest?: string };\n  reset: () => void;\n}) {\n  useEffect(() => {\n    console.error(error);\n  }, [error]);\n\n  return (\n    <main className=\"bg-background flex min-h-screen items-center justify-center\">\n      <div className=\"text-center\">\n        <h1 className=\"mb-4 text-4xl font-bold\">Something went wrong!</h1>\n        <p className=\"text-muted-foreground mb-8\">\n          There was an error loading this theme. Please try again later.\n        </p>\n        <div className=\"space-x-4\">\n          <Button onClick={reset}>Try again</Button>\n          <Button variant=\"outline\" asChild>\n            <Link href=\"/settings\">Return to Settings</Link>\n          </Button>\n        </div>\n      </div>\n    </main>\n  );\n}\n"
  },
  {
    "path": "app/themes/[themeId]/layout.tsx",
    "content": "import { Footer } from \"@/components/footer\";\nimport { Header } from \"@/components/header\";\n\nexport default function ThemeLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"flex flex-col\">\n      <Header />\n      <main className=\"flex min-h-screen flex-1 flex-col\">{children}</main>\n      <Footer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/themes/[themeId]/loading.tsx",
    "content": "import { Loading } from \"@/components/loading\";\n\nexport default function ThemeLoading() {\n  return <Loading className=\"flex-1\" />;\n}\n"
  },
  {
    "path": "app/themes/[themeId]/not-found.tsx",
    "content": "import Link from \"next/link\";\nimport { Button } from \"@/components/ui/button\";\n\nexport default function ThemeNotFound() {\n  return (\n    <main className=\"min-h-screen bg-background flex items-center justify-center\">\n      <div className=\"text-center\">\n        <h1 className=\"text-4xl font-bold mb-4\">Theme Not Found</h1>\n        <p className=\"text-muted-foreground mb-8\">\n          The theme you&apos;re looking for doesn&apos;t exist or you don&apos;t\n          have permission to view it.\n        </p>\n        <Button asChild>\n          <Link href=\"/editor/theme\">Return to Editor</Link>\n        </Button>\n      </div>\n    </main>\n  );\n}\n"
  },
  {
    "path": "app/themes/[themeId]/opengraph-image.alt.txt",
    "content": "Theme preview for tweakcn "
  },
  {
    "path": "app/themes/[themeId]/opengraph-image.tsx",
    "content": "import { ImageResponse } from \"next/og\";\nimport { getTheme } from \"@/actions/themes\";\n\n// Image metadata\nexport const size = {\n  width: 1200,\n  height: 630,\n};\n\nexport const contentType = \"image/png\";\n\n// Dynamic route params\nexport default async function Image({\n  params,\n}: {\n  params: { themeId: string };\n}) {\n  const theme = await getTheme(params.themeId);\n\n  // Set default colors if theme doesn't exist\n  const primaryColor = theme?.styles?.light?.primary || \"#000000\";\n  const secondaryColor = theme?.styles?.light?.secondary || \"#ffffff\";\n  const accentColor = theme?.styles?.light?.accent || \"#0070f3\";\n  const mutedColor = theme?.styles?.light?.muted || \"#f5f5f5\";\n  const borderColor = theme?.styles?.light?.border || \"#e5e5e5\";\n  const backgroundColor = theme?.styles?.light?.background || \"#ffffff\";\n  const foregroundColor = theme?.styles?.light?.foreground || \"#000000\";\n  const themeName = theme?.name || \"Theme\";\n\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          background: backgroundColor,\n          width: \"100%\",\n          height: \"100%\",\n          display: \"flex\",\n          flexDirection: \"column\",\n          color: foregroundColor,\n        }}\n      >\n        {/* Top section for theme name */}\n        <div\n          style={{\n            height: \"40%\",\n            display: \"flex\",\n            flexDirection: \"column\",\n            alignItems: \"center\",\n            justifyContent: \"center\",\n            padding: \"20px\",\n          }}\n        >\n          <div\n            style={{\n              fontSize: 32,\n              opacity: 0.8,\n              marginBottom: \"10px\",\n            }}\n          >\n            tweakcn.com\n          </div>\n          <div\n            style={{\n              fontSize: 80,\n              fontWeight: \"bold\",\n              textAlign: \"center\",\n            }}\n          >\n            {themeName}\n          </div>\n        </div>\n\n        {/* Bottom section with horizontal color swatches */}\n        <div\n          style={{\n            height: \"60%\",\n            display: \"flex\",\n            flexDirection: \"row\",\n            width: \"100%\",\n          }}\n        >\n          {/* Primary */}\n          <div\n            style={{\n              flex: 1,\n              background: primaryColor,\n              borderTopLeftRadius: \"16px\",\n              borderTopRightRadius: \"16px\",\n            }}\n          />\n\n          {/* Secondary */}\n          <div\n            style={{\n              flex: 1,\n              background: secondaryColor,\n              borderTopLeftRadius: \"16px\",\n              borderTopRightRadius: \"16px\",\n            }}\n          />\n\n          {/* Accent */}\n          <div\n            style={{\n              flex: 1,\n              background: accentColor,\n              borderTopLeftRadius: \"16px\",\n              borderTopRightRadius: \"16px\",\n            }}\n          />\n\n          {/* Muted */}\n          <div\n            style={{\n              flex: 1,\n              background: mutedColor,\n              borderTopLeftRadius: \"16px\",\n              borderTopRightRadius: \"16px\",\n            }}\n          />\n\n          {/* Border */}\n          <div\n            style={{\n              flex: 1,\n              background: borderColor,\n              borderTopLeftRadius: \"16px\",\n              borderTopRightRadius: \"16px\",\n            }}\n          />\n        </div>\n      </div>\n    ),\n    {\n      ...size,\n    }\n  );\n}\n"
  },
  {
    "path": "app/themes/[themeId]/page.tsx",
    "content": "import { getTheme } from \"@/actions/themes\";\nimport { getCommunityDataForTheme } from \"@/actions/community-themes\";\nimport ThemeView from \"@/components/theme-view\";\nimport { Metadata } from \"next\";\n\ninterface ThemePageProps {\n  params: Promise<{\n    themeId: string;\n  }>;\n}\n\nexport async function generateMetadata({ params }: ThemePageProps): Promise<Metadata> {\n  const { themeId } = await params;\n  const [theme, communityData] = await Promise.all([\n    getTheme(themeId),\n    getCommunityDataForTheme(themeId),\n  ]);\n\n  const tags = communityData?.tags ?? [];\n  const authorName = communityData?.author?.name;\n  const description =\n    tags.length > 0 && authorName\n      ? `A ${tags.join(\", \")} shadcn/ui theme by ${authorName}`\n      : `Discover shadcn/ui themes - ${theme?.name} theme`;\n\n  return {\n    title: theme?.name + \" - tweakcn\",\n    description,\n    keywords: tags.length > 0 ? tags : undefined,\n    openGraph: {\n      title: `${theme?.name} - tweakcn`,\n      description,\n      type: \"website\",\n    },\n    twitter: {\n      card: \"summary_large_image\",\n      title: `${theme?.name} - tweakcn`,\n      description,\n    },\n    robots: {\n      index: !!communityData,\n      follow: true,\n    },\n  };\n}\n\nexport default async function ThemePage({ params }: ThemePageProps) {\n  const { themeId } = await params;\n  const [theme, communityData] = await Promise.all([\n    getTheme(themeId),\n    getCommunityDataForTheme(themeId),\n  ]);\n\n  return (\n    <div className=\"flex flex-1 flex-col\">\n      <div className=\"container mx-auto px-4 py-8\">\n        <ThemeView theme={theme} communityData={communityData} />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/ai-elements/code-block.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport type { ComponentProps, HTMLAttributes, ReactNode } from \"react\";\nimport { createContext, useContext, useState } from \"react\";\nimport { Prism as SyntaxHighlighter } from \"react-syntax-highlighter\";\nimport { oneDark, oneLight } from \"react-syntax-highlighter/dist/esm/styles/prism\";\nimport { useTheme } from \"../theme-provider\";\n\ntype CodeBlockContextType = {\n  code: string;\n};\n\nconst CodeBlockContext = createContext<CodeBlockContextType>({\n  code: \"\",\n});\n\nexport type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {\n  code: string;\n  language: string;\n  showLineNumbers?: boolean;\n  children?: ReactNode;\n};\n\nexport const CodeBlock = ({\n  code,\n  language,\n  showLineNumbers = false,\n  className,\n  children,\n  ...props\n}: CodeBlockProps) => {\n  const { theme } = useTheme();\n  return (\n    <CodeBlockContext.Provider value={{ code }}>\n      <div\n        className={cn(\n          \"bg-background text-foreground relative w-full overflow-hidden rounded-md border\",\n          className\n        )}\n        {...props}\n      >\n        <div className=\"relative\">\n          <SyntaxHighlighter\n            className=\"overflow-hidden\"\n            codeTagProps={{\n              className: \"font-mono text-sm\",\n            }}\n            customStyle={{\n              margin: 0,\n              padding: \"1rem\",\n              fontSize: \"0.875rem\",\n              background: \"hsl(var(--background))\",\n              color: \"hsl(var(--foreground))\",\n            }}\n            language={language}\n            lineNumberStyle={{\n              color: \"hsl(var(--muted-foreground))\",\n              paddingRight: \"1rem\",\n              minWidth: \"2.5rem\",\n            }}\n            showLineNumbers={showLineNumbers}\n            style={theme === \"light\" ? oneLight : oneDark}\n          >\n            {code}\n          </SyntaxHighlighter>\n          {children && (\n            <div className=\"absolute top-2 right-2 flex items-center gap-2\">{children}</div>\n          )}\n        </div>\n      </div>\n    </CodeBlockContext.Provider>\n  );\n};\n\nexport type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {\n  onCopy?: () => void;\n  onError?: (error: Error) => void;\n  timeout?: number;\n};\n\nexport const CodeBlockCopyButton = ({\n  onCopy,\n  onError,\n  timeout = 2000,\n  children,\n  className,\n  ...props\n}: CodeBlockCopyButtonProps) => {\n  const [isCopied, setIsCopied] = useState(false);\n  const { code } = useContext(CodeBlockContext);\n\n  const copyToClipboard = async () => {\n    if (typeof window === \"undefined\" || !navigator.clipboard.writeText) {\n      onError?.(new Error(\"Clipboard API not available\"));\n      return;\n    }\n\n    try {\n      await navigator.clipboard.writeText(code);\n      setIsCopied(true);\n      onCopy?.();\n      setTimeout(() => setIsCopied(false), timeout);\n    } catch (error) {\n      onError?.(error as Error);\n    }\n  };\n\n  const Icon = isCopied ? CheckIcon : CopyIcon;\n\n  return (\n    <Button\n      className={cn(\"shrink-0\", className)}\n      onClick={copyToClipboard}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ?? <Icon size={14} />}\n    </Button>\n  );\n};\n"
  },
  {
    "path": "components/ai-elements/conversation.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { ArrowDownIcon } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\nimport { useCallback } from \"react\";\nimport { StickToBottom, useStickToBottomContext } from \"use-stick-to-bottom\";\n\nexport type ConversationProps = ComponentProps<typeof StickToBottom>;\n\nexport const Conversation = ({ className, ...props }: ConversationProps) => (\n  <StickToBottom\n    className={cn(\"relative overflow-y-auto\", className)}\n    initial=\"instant\"\n    resize=\"instant\"\n    role=\"log\"\n    {...props}\n  />\n);\n\nexport type ConversationContentProps = ComponentProps<typeof StickToBottom.Content>;\n\nexport const ConversationContent = ({ className, ...props }: ConversationContentProps) => (\n  <StickToBottom.Content className={cn(\"\", className)} {...props} />\n);\n\nexport type ConversationScrollButtonProps = ComponentProps<typeof Button>;\n\nexport const ConversationScrollButton = ({\n  className,\n  ...props\n}: ConversationScrollButtonProps) => {\n  const { isAtBottom, scrollToBottom } = useStickToBottomContext();\n\n  const handleScrollToBottom = useCallback(() => {\n    scrollToBottom();\n  }, [scrollToBottom]);\n\n  return (\n    !isAtBottom && (\n      <Button\n        className={cn(\"absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full\", className)}\n        onClick={handleScrollToBottom}\n        size=\"icon\"\n        type=\"button\"\n        variant=\"outline\"\n        {...props}\n      >\n        <ArrowDownIcon className=\"size-4\" />\n      </Button>\n    )\n  );\n};\n"
  },
  {
    "path": "components/ai-elements/response.tsx",
    "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { type ComponentProps, memo } from \"react\";\nimport { Streamdown } from \"streamdown\";\n\ntype ResponseProps = ComponentProps<typeof Streamdown>;\n\nexport const Response = memo(\n  ({ className, ...props }: ResponseProps) => (\n    <Streamdown\n      className={cn(\n        \"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_li>p]:my-0\",\n        className\n      )}\n      {...props}\n    />\n  ),\n  (prevProps, nextProps) =>\n    prevProps.children === nextProps.children &&\n    prevProps.isAnimating === nextProps.isAnimating\n);\n\nResponse.displayName = \"Response\";\n"
  },
  {
    "path": "components/auth-dialog-wrapper.tsx",
    "content": "\"use client\";\n\nimport { AuthDialog } from \"@/app/(auth)/components/auth-dialog\";\nimport { useAuthStore } from \"@/store/auth-store\";\nimport { useEffect } from \"react\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { executePostLoginAction } from \"@/hooks/use-post-login-action\";\nimport { usePostHog } from \"posthog-js/react\";\n\nexport function AuthDialogWrapper() {\n  const { isOpen, mode, closeAuthDialog, postLoginAction, clearPostLoginAction } = useAuthStore();\n  const { data: session } = authClient.useSession();\n  const posthog = usePostHog();\n\n  useEffect(() => {\n    if (isOpen && session) {\n      closeAuthDialog();\n    }\n\n    if (session && session.user && session.user.email) {\n      // Identify user with PostHog\n      posthog.identify(session.user.email, {\n        name: session.user.name,\n        email: session.user.email,\n      });\n    }\n\n    if (session && postLoginAction) {\n      // Execute action immediately - the system will now handle waiting for handlers\n      executePostLoginAction(postLoginAction);\n      clearPostLoginAction();\n    }\n  }, [session, isOpen, closeAuthDialog, postLoginAction, clearPostLoginAction, posthog]);\n\n  return (\n    <AuthDialog\n      open={isOpen}\n      onOpenChange={closeAuthDialog}\n      initialMode={mode}\n      postLoginActionType={postLoginAction?.type}\n    />\n  );\n}\n"
  },
  {
    "path": "components/block-viewer.tsx",
    "content": "\"use client\";\nimport { Monitor, Smartphone, Tablet } from \"lucide-react\";\nimport React from \"react\";\nimport { PanelImperativeHandle } from \"react-resizable-panels\";\n\nimport { ComponentErrorBoundary } from \"@/components/error-boundary\";\nimport { ResizableHandle, ResizablePanel, ResizablePanelGroup } from \"@/components/ui/resizable\";\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\nimport { cn } from \"@/lib/utils\";\n\ntype BlockViewerContext = {\n  resizablePanelRef: React.RefObject<PanelImperativeHandle | null>;\n  toggleValue: string;\n  setToggleValue: (value: string) => void;\n};\n\nconst BlockViewerContext = React.createContext<BlockViewerContext | null>(null);\n\nfunction useBlockViewer() {\n  const context = React.useContext(BlockViewerContext);\n  if (!context) {\n    throw new Error(\"useBlockViewer must be used within a BlockViewerProvider.\");\n  }\n  return context;\n}\n\nfunction BlockViewerProvider({ children }: { children: React.ReactNode }) {\n  const resizablePanelRef = React.useRef<PanelImperativeHandle>(null);\n  const [toggleValue, setToggleValue] = React.useState(\"100\");\n\n  return (\n    <BlockViewerContext.Provider\n      value={{\n        resizablePanelRef,\n        toggleValue,\n        setToggleValue,\n      }}\n    >\n      {children}\n    </BlockViewerContext.Provider>\n  );\n}\n\nexport function BlockViewer({\n  className,\n  name,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  name: string;\n}) {\n  return (\n    <BlockViewerProvider>\n      <div\n        data-slot=\"block-viewer\"\n        className={cn(\n          \"group/block-viewer bg-background @container isolate flex size-full min-w-0 flex-col overflow-clip\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </BlockViewerProvider>\n  );\n}\n\nexport function BlockViewerToolbar({\n  name,\n  className,\n  toolbarControls,\n}: React.ComponentProps<\"div\"> & {\n  name: string;\n  className?: string;\n  toolbarControls?: React.ReactNode;\n}) {\n  const { resizablePanelRef, toggleValue, setToggleValue } = useBlockViewer();\n\n  return (\n    <div data-slot=\"block-viewer-toolbar\" className={cn(\"h-12 w-full border-b p-2\", className)}>\n      <div className=\"flex size-full items-center justify-between gap-4\">\n        <div className=\"flex-1\">\n          {!!toolbarControls ? (\n            toolbarControls\n          ) : (\n            <span className=\"text-sm font-medium capitalize\">{name}</span>\n          )}\n        </div>\n\n        <div className=\"hidden items-center justify-between lg:group-has-data-[slot=block-viewer-display]/block-viewer:flex\">\n          <ToggleGroup\n            className=\"bg-background flex gap-0.5 rounded-lg border p-0.5\"\n            type=\"single\"\n            value={toggleValue}\n            onValueChange={(value) => {\n              if (value && resizablePanelRef?.current) {\n                resizablePanelRef.current.resize(parseInt(value));\n                setToggleValue(value);\n              }\n            }}\n          >\n            <ToggleGroupItem value=\"100\" className=\"flex size-7 items-center justify-center p-1\">\n              <Monitor className=\"size-4\" />\n            </ToggleGroupItem>\n\n            <ToggleGroupItem value=\"60\" className=\"flex size-7 items-center justify-center p-1\">\n              <Tablet className=\"size-4\" />\n            </ToggleGroupItem>\n\n            <ToggleGroupItem value=\"30\" className=\"flex size-7 items-center justify-center p-1\">\n              <Smartphone className=\"size-4\" />\n            </ToggleGroupItem>\n          </ToggleGroup>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport function BlockViewerDisplay({\n  name,\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  name: string;\n}) {\n  const { resizablePanelRef, setToggleValue } = useBlockViewer();\n\n  // Auto-resize to full width when screen goes under lg breakpoint (1024px)\n  React.useEffect(() => {\n    const mql = window.matchMedia(\"(max-width: 1023px)\");\n    const resizePanel = () => {\n      if (window.innerWidth < 1024 && resizablePanelRef?.current) {\n        resizablePanelRef.current.resize(100);\n        setToggleValue(\"100\");\n      }\n    };\n\n    resizePanel();\n    mql.addEventListener(\"change\", resizePanel);\n    return () => mql.removeEventListener(\"change\", resizePanel);\n  }, [resizablePanelRef, setToggleValue]);\n\n  return (\n    <ComponentErrorBoundary name={name}>\n      <div\n        id={name}\n        data-slot=\"block-viewer-display\"\n        data-name={name.toLowerCase()}\n        className={cn(\"size-full min-h-0 gap-4 overflow-hidden\", className)}\n        {...props}\n      >\n        <ResizablePanelGroup orientation=\"horizontal\" className=\"relative isolate z-10 size-full\">\n          <div className=\"bg-muted absolute inset-0 right-3 [background-image:radial-gradient(var(--muted-foreground),transparent_1px)] [background-size:1rem_1rem] opacity-60 dark:opacity-35\" />\n\n          <ResizablePanel\n            panelRef={resizablePanelRef}\n            className=\"bg-background relative min-w-[350px] lg:aspect-auto\"\n            defaultSize=\"100%\"\n            minSize=\"30%\"\n          >\n            {children}\n          </ResizablePanel>\n\n          <ResizableHandle className=\"after:bg-border relative inset-x-0 mx-auto hidden w-3 border-l bg-transparent after:absolute after:top-1/2 after:h-8 after:w-[4px] after:-translate-y-1/2 after:rounded-full after:transition-all hover:after:h-12 active:after:h-12 lg:block\" />\n          <ResizablePanel defaultSize=\"0%\" minSize=\"0%\" />\n        </ResizablePanelGroup>\n      </div>\n    </ComponentErrorBoundary>\n  );\n}\n"
  },
  {
    "path": "components/copy-button.tsx",
    "content": "\"use client\";\n\nimport { useCopyToClipboard } from \"@/hooks/use-copy-to-clipboard\";\nimport { cn } from \"@/lib/utils\";\nimport { Copy, CopyCheck } from \"lucide-react\";\nimport { ComponentProps } from \"react\";\nimport { TooltipWrapper } from \"./tooltip-wrapper\";\nimport { Button } from \"./ui/button\";\n\ninterface CopyButtonProps extends ComponentProps<typeof Button> {\n  textToCopy: string;\n  successMessage?: {\n    title?: string;\n    description?: string;\n  };\n}\n\nexport function CopyButton({ textToCopy, successMessage, className, ...props }: CopyButtonProps) {\n  const { copyToClipboard, hasCopied } = useCopyToClipboard();\n\n  return (\n    <TooltipWrapper label=\"Copy\" asChild>\n      <Button\n        size=\"icon\"\n        variant=\"ghost\"\n        className={cn(\"size-6 [&>svg]:size-3.5\", className)}\n        onClick={() => copyToClipboard(textToCopy, successMessage)}\n        {...props}\n      >\n        {hasCopied ? <CopyCheck /> : <Copy />}\n        <span className=\"sr-only\">Copy</span>\n      </Button>\n    </TooltipWrapper>\n  );\n}\n"
  },
  {
    "path": "components/debug-button.tsx",
    "content": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { Bug } from \"lucide-react\";\n\ninterface DebugButtonProps extends React.ComponentProps<typeof Button> {\n  debug?: boolean;\n}\n\nconst isDevMode = process.env.NODE_ENV === \"development\";\n\nexport function DebugButton({ className, debug = isDevMode, ...props }: DebugButtonProps) {\n  if (!debug) return null;\n\n  return (\n    <TooltipWrapper label=\"Debug\" asChild>\n      <Button variant=\"ghost\" size=\"icon\" className={cn(\"\", className)} {...props}>\n        <Bug />\n      </Button>\n    </TooltipWrapper>\n  );\n}\n"
  },
  {
    "path": "components/dynamic-font-loader.tsx",
    "content": "\"use client\";\n\nimport { useMounted } from \"@/hooks/use-mounted\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { extractFontFamily, getDefaultWeights } from \"@/utils/fonts\";\nimport { loadGoogleFont } from \"@/utils/fonts/google-fonts\";\nimport { useEffect, useMemo } from \"react\";\n\nexport function DynamicFontLoader() {\n  const { themeState } = useEditorStore();\n  const isMounted = useMounted();\n\n  const fontSans = themeState.styles.light[\"font-sans\"];\n  const fontSerif = themeState.styles.light[\"font-serif\"];\n  const fontMono = themeState.styles.light[\"font-mono\"];\n\n  const currentFonts = useMemo(() => {\n    return {\n      sans: fontSans,\n      serif: fontSerif,\n      mono: fontMono,\n    } as const;\n  }, [fontSans, fontSerif, fontMono]);\n\n  useEffect(() => {\n    if (!isMounted) return;\n\n    try {\n      Object.entries(currentFonts).forEach(([_type, fontValue]) => {\n        const fontFamily = extractFontFamily(fontValue);\n        if (fontFamily) {\n          const weights = getDefaultWeights([\"400\", \"500\", \"600\", \"700\"]);\n          loadGoogleFont(fontFamily, weights);\n        }\n      });\n    } catch (e) {\n      console.warn(\"DynamicFontLoader: Failed to load Google fonts:\", e);\n    }\n  }, [isMounted, currentFonts]);\n\n  return null;\n}\n"
  },
  {
    "path": "components/dynamic-website-preview.tsx",
    "content": "\"use client\";\n\nimport Logo from \"@/assets/logo.svg\";\nimport { CodeBlock, CodeBlockCopyButton } from \"@/components/ai-elements/code-block\";\nimport { BlockViewer, BlockViewerDisplay, BlockViewerToolbar } from \"@/components/block-viewer\";\nimport { LoadingLogo } from \"@/components/editor/ai/loading-logo\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from \"@/components/ui/hover-card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { useIframeThemeInjector } from \"@/hooks/use-iframe-theme-injector\";\nimport { useWebsitePreview } from \"@/hooks/use-website-preview\";\nimport { cn } from \"@/lib/utils\";\nimport { IframeStatus } from \"@/types/live-preview-embed\";\nimport {\n  AlertCircle,\n  CheckCircle,\n  CloudAlert,\n  ExternalLink,\n  Globe,\n  Info,\n  Loader,\n  RefreshCw,\n  X,\n  XCircle,\n} from \"lucide-react\";\nimport { usePostHog } from \"posthog-js/react\";\nimport React, { useEffect, useRef } from \"react\";\n\n/**\n * Dynamic Website Preview - Load and theme external websites\n *\n * Usage Examples:\n *\n * // Same-origin mode (default) - direct DOM theme injection\n * <DynamicWebsitePreview name=\"Local Preview\" />\n *\n * // Cross-origin mode - requires external sites to include embed script\n * <DynamicWebsitePreview name=\"External Preview\" allowCrossOrigin />\n *\n * The allowCrossOrigin flag must be explicitly set to true to enable\n * external website theming via the embed script.\n */\n\nconst SCRIPT_URL = \"https://tweakcn.com/live-preview.min.js\";\n\n// Code snippets for quick installation across common setups\nconst HTML_SNIPPET = `<!-- Add inside <head> -->\\n<script src=\"${SCRIPT_URL}\"></script>`;\n\nconst NEXT_APP_SNIPPET = `// app/layout.tsx\\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode\n}) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <script\n          async\n          crossOrigin=\"anonymous\"\n          src=\"${SCRIPT_URL}\"\n        />\n      </head>\n      <body>{children}</body>\n    </html>\n  )\n}`;\n\nconst NEXT_PAGES_SNIPPET = `// pages/_document.tsx\\n\nimport { Html, Head, Main, NextScript } from 'next/document';\n\nexport default function Document() {\n  return (\n    <Html lang=\"en\">\n      <Head>\n        <script\n          async\n          crossOrigin=\"anonymous\"\n          src=\"${SCRIPT_URL}\"\n        />\n      </Head>\n      <body>\n        <Main />\n        <NextScript />\n      </body>\n    </Html>\n  );\n}`;\n\nconst VITE_SNIPPET = `<!-- index.html -->\\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <script\n      crossOrigin=\"anonymous\"\n      src=\"${SCRIPT_URL}\"\n    />\n  </head>\n  <body>\n    <!-- ... -->\n  </body>\n</html>`;\n\nconst REMIX_SNIPPET = `// app/root.tsx\\nimport { Links, Meta, Outlet, Scripts } from \"@remix-run/react\";\n\nexport default function App() {\n  return (\n    <html>\n      <head>\n        <link\n          rel=\"icon\"\n          href=\"data:image/x-icon;base64,AA\"\n        />\n        <Meta />\n        <script\n          crossOrigin=\"anonymous\"\n          src=\"${SCRIPT_URL}\"\n        />\n        <Links />\n      </head>\n      <body>\n        <Outlet />\n        <Scripts />\n      </body>\n    </html>\n  );\n}`;\n\ntype DynamicWebsitePreviewContextType = ReturnType<typeof useWebsitePreview> &\n  Omit<ReturnType<typeof useIframeThemeInjector>, \"ref\">;\n\nconst DynamicWebsitePreviewContext = React.createContext<DynamicWebsitePreviewContextType | null>(\n  null\n);\n\nfunction useDynamicWebsitePreview() {\n  const context = React.useContext(DynamicWebsitePreviewContext);\n  if (!context) {\n    throw new Error(\n      \"useDynamicWebsitePreview must be used within a DynamicWebsitePreviewProvider.\"\n    );\n  }\n  return context;\n}\n\nfunction DynamicWebsitePreviewProvider({\n  children,\n  allowCrossOrigin = false,\n}: {\n  children: React.ReactNode;\n  allowCrossOrigin?: boolean;\n}) {\n  const websitePreviewState = useWebsitePreview({ allowCrossOrigin });\n  const posthog = usePostHog();\n\n  const { status, retryValidation, themeInjectionError } = useIframeThemeInjector({\n    allowCrossOrigin: allowCrossOrigin && !!websitePreviewState.currentUrl,\n    iframeRef: websitePreviewState.iframeRef,\n  });\n\n  const statusRef = useRef<IframeStatus>(status);\n  // eslint-disable-next-line\n  statusRef.current = status;\n\n  useEffect(() => {\n    if (websitePreviewState.currentUrl) {\n      setTimeout(() => {\n        // capturing after 1s delay so status is finalized\n        posthog.capture(\"DYNAMIC_PREVIEW_LOADED\", {\n          previewUrl: websitePreviewState.currentUrl,\n          status: statusRef.current,\n        });\n      }, 1000);\n    }\n  }, [websitePreviewState.currentUrl, posthog]);\n\n  const contextValue = {\n    ...websitePreviewState,\n    status,\n    retryValidation,\n    themeInjectionError,\n  };\n\n  return (\n    <DynamicWebsitePreviewContext.Provider value={contextValue}>\n      {children}\n    </DynamicWebsitePreviewContext.Provider>\n  );\n}\n\nexport function DynamicWebsitePreview({\n  className,\n  name,\n  allowCrossOrigin = false,\n  ...props\n}: React.ComponentPropsWithoutRef<\"div\"> & {\n  name: string;\n  allowCrossOrigin?: boolean;\n}) {\n  return (\n    <DynamicWebsitePreviewProvider allowCrossOrigin={allowCrossOrigin}>\n      <BlockViewer\n        name={name}\n        className={cn(\n          \"group/block-view-wrapper bg-background @container isolate flex size-full min-w-0 flex-col overflow-clip\",\n          className\n        )}\n        {...props}\n      >\n        <BlockViewerToolbar name={name} toolbarControls={<Controls />} className=\"bg-muted h-fit\" />\n        <DynamicWebsitePreviewContent name={name} />\n      </BlockViewer>\n    </DynamicWebsitePreviewProvider>\n  );\n}\n\nfunction DynamicWebsitePreviewContent({ name }: { name: string }) {\n  const { currentUrl, error: previewError } = useDynamicWebsitePreview();\n\n  if (!currentUrl && !previewError) {\n    return <NoWebsitePreviewLoaded />;\n  }\n\n  if (previewError) {\n    return <WebsitePreviewError error={previewError} />;\n  }\n\n  return <WebsitePreview name={name} />;\n}\n\nfunction Controls() {\n  const {\n    inputUrl,\n    setInputUrl,\n    currentUrl,\n    isLoading: previewIsLoading,\n    loadUrl,\n    refreshIframe,\n    openInNewTab,\n    reset,\n    allowCrossOrigin,\n  } = useDynamicWebsitePreview();\n\n  const handleReset = () => {\n    if (currentUrl) {\n      reset();\n      setInputUrl(\"\");\n    } else {\n      setInputUrl(\"\");\n    }\n  };\n\n  return (\n    <div className=\"flex size-full items-center gap-1.5\">\n      <div className=\"relative max-w-xl flex-1\">\n        <Input\n          type=\"url\"\n          placeholder={\n            !allowCrossOrigin\n              ? \"Enter same-origin URL for direct theme injection\"\n              : \"Enter website URL (e.g. http://localhost:3000/login)\"\n          }\n          value={inputUrl}\n          onChange={(e) => setInputUrl(e.target.value)}\n          onKeyDown={(e) => {\n            if (!inputUrl.trim()) return;\n            if (e.key === \"Enter\") {\n              loadUrl();\n            }\n          }}\n          className={cn(\n            \"peer bg-background text-foreground h-8 pl-8 text-sm shadow-none transition-all duration-200\",\n            \"focus:bg-input/50 hover:bg-input/20\",\n            currentUrl && \"pr-8\"\n          )}\n        />\n\n        <Globe\n          className={cn(\n            \"text-muted-foreground absolute top-0 left-2 size-4 translate-y-1/2 transition-colors\",\n            \"peer-focus:text-foreground/70\"\n          )}\n        />\n\n        {(currentUrl || inputUrl) && (\n          <TooltipWrapper asChild label=\"Reset\">\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              onClick={handleReset}\n              className=\"absolute top-0 right-0 size-8 translate-y-0 hover:bg-transparent\"\n            >\n              <X className=\"text-muted-foreground hover:text-foreground size-3.5 transition-colors\" />\n            </Button>\n          </TooltipWrapper>\n        )}\n      </div>\n\n      <TooltipWrapper asChild label=\"Refresh website\">\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          onClick={refreshIframe}\n          disabled={previewIsLoading || !currentUrl}\n          className=\"size-8 shadow-none transition-all hover:scale-105\"\n        >\n          <RefreshCw\n            className={cn(\"size-3.5 transition-transform\", previewIsLoading && \"animate-spin\")}\n          />\n        </Button>\n      </TooltipWrapper>\n\n      <TooltipWrapper asChild label=\"Open in new tab\">\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          onClick={openInNewTab}\n          disabled={!currentUrl || previewIsLoading}\n          className=\"size-8 px-2 shadow-none transition-all hover:scale-105\"\n        >\n          <ExternalLink className=\"size-3.5\" />\n        </Button>\n      </TooltipWrapper>\n    </div>\n  );\n}\n\nfunction NoWebsitePreviewLoaded() {\n  return (\n    <div className=\"scrollbar-thin scrollbar-gutter-both relative flex size-full flex-col overflow-y-auto p-4 py-8\">\n      <div className=\"text-muted-foreground mx-auto my-auto flex w-full max-w-xl min-w-0 flex-col items-center justify-center space-y-6\">\n        <div className=\"flex items-center gap-1\">\n          <div className=\"bg-muted outline-border/50 flex size-16 flex-col items-center justify-center rounded-full outline\">\n            <Globe className=\"text-foreground size-7\" />\n          </div>\n          <X className=\"text-muted-foreground size-6\" />\n          <div className=\"bg-muted outline-border/50 flex size-16 flex-col items-center justify-center rounded-full outline\">\n            <Logo className=\"text-foreground size-7\" />\n          </div>\n        </div>\n\n        <h3 className=\"text-foreground text-center text-lg font-medium md:text-2xl\">\n          Preview your Website in tweakcn\n        </h3>\n\n        <div className=\"text-muted-foreground space-y-2 text-left text-sm\">\n          <div className=\"flex gap-2\">\n            <span className=\"text-foreground font-semibold\">1.</span>\n            <span>Add the script below to your website based on your framework</span>\n          </div>\n          <div className=\"flex gap-2\">\n            <span className=\"text-foreground font-semibold\">2.</span>\n            <span>\n              Paste your website&apos;s URL (e.g.,{\" \"}\n              <code className=\"code-inline\">http://localhost:3000</code>) above to preview it with\n              the theme applied in real-time\n            </span>\n          </div>\n        </div>\n\n        <Card className=\"w-full p-2\">\n          <Tabs\n            defaultValue=\"script\"\n            className=\"flex min-h-0 flex-1 flex-col gap-2 overflow-hidden\"\n          >\n            <div className=\"scrollbar-thin flex items-center justify-between overflow-x-auto rounded-lg border p-1\">\n              <TabsList className=\"m-0 h-fit bg-transparent p-0\">\n                <TabsTrigger value=\"script\" className=\"h-7 px-3 text-xs font-medium\">\n                  Script Tag\n                </TabsTrigger>\n                <TabsTrigger value=\"next-app\" className=\"h-7 px-3 text-xs font-medium\">\n                  Next.js (App)\n                </TabsTrigger>\n                <TabsTrigger value=\"next-pages\" className=\"h-7 px-3 text-xs font-medium\">\n                  Next.js (Pages)\n                </TabsTrigger>\n                <TabsTrigger value=\"vite\" className=\"h-7 px-3 text-xs font-medium\">\n                  Vite\n                </TabsTrigger>\n                <TabsTrigger value=\"remix\" className=\"h-7 px-3 text-xs font-medium\">\n                  Remix\n                </TabsTrigger>\n              </TabsList>\n            </div>\n\n            <div className=\"bg-background scrollbar-thin max-h-76 overflow-y-auto rounded-lg border\">\n              <TabsContent value=\"script\" className=\"m-0\">\n                <CodeBlock\n                  code={HTML_SNIPPET}\n                  language=\"html\"\n                  className=\"rounded-none border-none bg-transparent\"\n                >\n                  <CodeBlockCopyButton aria-label=\"Copy HTML snippet\" />\n                </CodeBlock>\n              </TabsContent>\n\n              <TabsContent value=\"next-app\" className=\"m-0\">\n                <CodeBlock\n                  code={NEXT_APP_SNIPPET}\n                  language=\"tsx\"\n                  className=\"rounded-none border-none bg-transparent\"\n                >\n                  <CodeBlockCopyButton aria-label=\"Copy Next.js App snippet\" />\n                </CodeBlock>\n              </TabsContent>\n\n              <TabsContent value=\"next-pages\" className=\"m-0\">\n                <CodeBlock\n                  code={NEXT_PAGES_SNIPPET}\n                  language=\"tsx\"\n                  className=\"rounded-none border-none bg-transparent\"\n                >\n                  <CodeBlockCopyButton aria-label=\"Copy Next.js Pages snippet\" />\n                </CodeBlock>\n              </TabsContent>\n\n              <TabsContent value=\"vite\" className=\"m-0\">\n                <CodeBlock\n                  code={VITE_SNIPPET}\n                  language=\"html\"\n                  className=\"rounded-none border-none bg-transparent\"\n                >\n                  <CodeBlockCopyButton aria-label=\"Copy Vite snippet\" />\n                </CodeBlock>\n              </TabsContent>\n\n              <TabsContent value=\"remix\" className=\"m-0\">\n                <CodeBlock\n                  code={REMIX_SNIPPET}\n                  language=\"tsx\"\n                  className=\"rounded-none border-none bg-transparent\"\n                >\n                  <CodeBlockCopyButton aria-label=\"Copy Remix snippet\" />\n                </CodeBlock>\n              </TabsContent>\n            </div>\n          </Tabs>\n        </Card>\n      </div>\n    </div>\n  );\n}\n\nfunction WebsitePreviewLoading() {\n  return (\n    <div className=\"bg-muted flex size-full items-center justify-center\">\n      <div className=\"flex flex-col items-center gap-4\">\n        <div className=\"relative isolate size-10\">\n          <LoadingLogo />\n        </div>\n\n        <span className=\"text-muted-foreground animate-pulse text-sm\">Loading website</span>\n      </div>\n    </div>\n  );\n}\n\nfunction WebsitePreviewError({ error }: { error: string }) {\n  return (\n    <div className=\"bg-muted relative size-full overflow-hidden p-4\">\n      <div className=\"flex h-full flex-col items-center justify-center space-y-6 p-4 text-center\">\n        <div className=\"bg-destructive/80 outline-border/50 flex size-16 flex-col items-center justify-center rounded-full outline\">\n          <CloudAlert className=\"text-destructive-foreground size-7\" />\n        </div>\n\n        <div className=\"space-y-2\">\n          <h3 className=\"text-foreground text-center text-lg font-medium md:text-2xl\">\n            Error Loading Website Preview\n          </h3>\n          <span className=\"text-muted-foreground max-w-md text-sm text-balance\">{error}</span>\n        </div>\n      </div>\n    </div>\n  );\n}\n\n/**\n * Content component that manages the iframe and its loading states\n * Theme injection is now handled entirely by the useIframeThemeInjector hook\n */\nfunction WebsitePreview({ name }: { name: string }) {\n  const {\n    currentUrl,\n    isLoading: previewIsLoading,\n    status,\n    retryValidation,\n    allowCrossOrigin,\n    iframeRef,\n    handleIframeLoad,\n    handleIframeError,\n    themeInjectionError,\n  } = useDynamicWebsitePreview();\n\n  return (\n    <BlockViewerDisplay name={name} className=\"relative\">\n      {previewIsLoading && (\n        <div className=\"absolute inset-0\">\n          <WebsitePreviewLoading />\n        </div>\n      )}\n\n      <iframe\n        ref={iframeRef}\n        src={currentUrl}\n        title=\"Dynamic Website Preview\"\n        className=\"size-full\"\n        onLoad={handleIframeLoad}\n        onError={handleIframeError}\n        sandbox={\n          allowCrossOrigin\n            ? \"allow-scripts allow-same-origin allow-forms allow-popups\"\n            : \"allow-scripts allow-same-origin\"\n        }\n        loading=\"lazy\"\n      />\n\n      {!previewIsLoading && !!status && allowCrossOrigin && (\n        <div className=\"absolute bottom-2 left-2 z-10\">\n          <ConnectionStatus\n            status={status}\n            retryValidation={retryValidation}\n            isLoading={previewIsLoading}\n            errorMsg={themeInjectionError}\n          />\n        </div>\n      )}\n    </BlockViewerDisplay>\n  );\n}\n\nconst ConnectionStatus = React.memo(\n  ({\n    status,\n    retryValidation,\n    isLoading,\n    errorMsg,\n  }: {\n    status: IframeStatus;\n    retryValidation: () => void;\n    isLoading: boolean;\n    errorMsg?: string | null;\n  }) => {\n    const [isVisible, setIsVisible] = React.useState(false);\n    const [displayedStatus, setDisplayedStatus] = React.useState<IframeStatus>(status);\n    const showTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);\n    const hideTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);\n    const hasShownSupportedRef = React.useRef(false);\n\n    React.useEffect(() => {\n      // Clear any existing timeouts\n      if (showTimeoutRef.current) {\n        clearTimeout(showTimeoutRef.current);\n        showTimeoutRef.current = null;\n      }\n      if (hideTimeoutRef.current) {\n        clearTimeout(hideTimeoutRef.current);\n        hideTimeoutRef.current = null;\n      }\n\n      // If we've already shown \"supported\" and hidden it, don't show it again\n      // unless there was an error state in between\n      if (status === \"supported\" && hasShownSupportedRef.current) {\n        return;\n      }\n\n      // Reset the flag if we hit an error state\n      if (status === \"missing\" || status === \"unsupported\" || status === \"error\") {\n        hasShownSupportedRef.current = false;\n      }\n\n      // Debounce: Wait 1s before showing the status to avoid flashing\n      showTimeoutRef.current = setTimeout(() => {\n        setDisplayedStatus(status);\n        setIsVisible(true);\n\n        // Auto-hide after delay only for \"supported\" status\n        if (status === \"supported\") {\n          hasShownSupportedRef.current = true;\n          hideTimeoutRef.current = setTimeout(() => {\n            setIsVisible(false);\n          }, 2000);\n        }\n      }, 500);\n\n      return () => {\n        if (showTimeoutRef.current) {\n          clearTimeout(showTimeoutRef.current);\n        }\n        if (hideTimeoutRef.current) {\n          clearTimeout(hideTimeoutRef.current);\n        }\n      };\n    }, [status]);\n\n    if (isLoading || status === \"unknown\" || !isVisible) return null;\n\n    return (\n      <div className=\"bg-popover/90 outline-border/50 animate-in fade-in slide-in-from-bottom-2 flex h-8 items-center gap-2 rounded-lg px-2 shadow-sm outline backdrop-blur-lg duration-200\">\n        <div className=\"flex items-center gap-1\">\n          <span className=\"text-foreground/90\">\n            {errorMsg ? (\n              <HoverCard>\n                <HoverCardTrigger>{ICONS[displayedStatus]}</HoverCardTrigger>\n                <HoverCardContent\n                  align=\"start\"\n                  side=\"top\"\n                  className=\"size-fit max-w-[280px] min-w-[140px] p-2\"\n                >\n                  <div className=\"space-y-2\">\n                    <div className=\"flex items-center gap-1\">\n                      <Info className=\"size-3\" />\n                      <p className=\"text-xs font-medium\">Error details:</p>\n                    </div>\n\n                    <p className=\"text-muted-foreground text-xs text-pretty\">{errorMsg}</p>\n                  </div>\n                </HoverCardContent>\n              </HoverCard>\n            ) : (\n              ICONS[displayedStatus]\n            )}\n          </span>\n          <span className=\"text-foreground/90 flex items-center gap-1 text-sm font-medium\">\n            {TEXTS[displayedStatus]}\n          </span>\n        </div>\n\n        {(displayedStatus === \"missing\" ||\n          displayedStatus === \"unsupported\" ||\n          displayedStatus === \"error\") && (\n          <div className=\"flex items-center gap-1\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              className=\"h-6 px-2 text-xs shadow-none\"\n              onClick={retryValidation}\n            >\n              Retry\n            </Button>\n          </div>\n        )}\n      </div>\n    );\n  }\n);\n\nConnectionStatus.displayName = \"ConnectionStatus\";\n\nconst ICONS: Record<IframeStatus, React.ReactNode> = {\n  unknown: null,\n  checking: <Loader className=\"size-4 animate-spin\" />,\n  connected: <CheckCircle className=\"size-4\" />,\n  supported: <CheckCircle className=\"size-4\" />,\n  unsupported: <AlertCircle className=\"size-4\" />,\n  missing: <XCircle className=\"text-destructive size-4\" />,\n  error: <XCircle className=\"text-destructive size-4\" />,\n};\n\nconst TEXTS: Record<IframeStatus, string> = {\n  unknown: \"\",\n  checking: \"Checking connection\",\n  connected: \"Connected\",\n  supported: \"Live preview enabled\",\n  unsupported: \"Unsupported site\",\n  missing: \"Script not found\",\n  error: \"An error occurred\",\n};\n"
  },
  {
    "path": "components/editor/action-bar/action-bar.tsx",
    "content": "\"use client\";\n\nimport { ActionBarButtons } from \"@/components/editor/action-bar/components/action-bar-buttons\";\nimport { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { useDialogActions } from \"@/hooks/use-dialog-actions\";\n\nexport function ActionBar() {\n  const { isCreatingTheme, handleSaveClick, handleShareClick, setCssImportOpen, setCodePanelOpen } =\n    useDialogActions();\n\n  return (\n    <div className=\"border-b\">\n      <HorizontalScrollArea className=\"flex h-14 w-full items-center justify-end gap-4 px-4\">\n        <ActionBarButtons\n          onImportClick={() => setCssImportOpen(true)}\n          onCodeClick={() => setCodePanelOpen(true)}\n          onSaveClick={() => handleSaveClick()}\n          isSaving={isCreatingTheme}\n          onShareClick={handleShareClick}\n        />\n      </HorizontalScrollArea>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/action-bar-buttons.tsx",
    "content": "import { Separator } from \"@/components/ui/separator\";\nimport { useAIThemeGenerationCore } from \"@/hooks/use-ai-theme-generation-core\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { useThemePresetStore } from \"@/store/theme-preset-store\";\nimport { useThemesData } from \"@/hooks/themes\";\nimport { CodeButton } from \"./code-button\";\nimport { ImportButton } from \"./import-button\";\nimport { MoreOptions } from \"./more-options\";\nimport { PublishButton } from \"./publish-button\";\nimport { ResetButton } from \"./reset-button\";\nimport { SaveButton } from \"./save-button\";\nimport { ShareButton } from \"./share-button\";\nimport { ThemeToggle } from \"./theme-toggle\";\nimport { UndoRedoButtons } from \"./undo-redo-buttons\";\nimport { useMemo } from \"react\";\n\ninterface ActionBarButtonsProps {\n  onImportClick: () => void;\n  onCodeClick: () => void;\n  onSaveClick: () => void;\n  onShareClick: (id?: string) => void;\n  isSaving: boolean;\n}\n\nexport function ActionBarButtons({\n  onImportClick,\n  onCodeClick,\n  onSaveClick,\n  onShareClick,\n  isSaving,\n}: ActionBarButtonsProps) {\n  const { themeState, resetToCurrentPreset, hasUnsavedChanges } = useEditorStore();\n  const { isGeneratingTheme } = useAIThemeGenerationCore();\n  const { getPreset } = useThemePresetStore();\n  const { data: themes } = useThemesData();\n  const currentPreset = themeState?.preset ? getPreset(themeState?.preset) : undefined;\n  const isSavedPreset = !!currentPreset && currentPreset.source === \"SAVED\";\n\n  const isPublished = useMemo(() => {\n    if (!isSavedPreset || !themes || !themeState.preset) return false;\n    const theme = themes.find((t) => t.id === themeState.preset);\n    return theme?.isPublished ?? false;\n  }, [isSavedPreset, themes, themeState.preset]);\n\n  const handleReset = () => {\n    resetToCurrentPreset();\n  };\n\n  return (\n    <div className=\"flex items-center gap-1\">\n      <MoreOptions disabled={isGeneratingTheme} />\n      <Separator orientation=\"vertical\" className=\"mx-1 h-8\" />\n      <ThemeToggle />\n      <Separator orientation=\"vertical\" className=\"mx-1 h-8\" />\n      <UndoRedoButtons disabled={isGeneratingTheme} />\n      <Separator orientation=\"vertical\" className=\"mx-1 h-8\" />\n      <ResetButton onClick={handleReset} disabled={!hasUnsavedChanges() || isGeneratingTheme} />\n      <div className=\"hidden items-center gap-1 md:flex\">\n        <ImportButton onClick={onImportClick} disabled={isGeneratingTheme} />\n      </div>\n      <Separator orientation=\"vertical\" className=\"mx-1 h-8\" />\n      <ShareButton onClick={() => onShareClick(themeState.preset)} disabled={isGeneratingTheme} />\n      {isSavedPreset && !hasUnsavedChanges() ? (\n        <PublishButton\n          themeId={themeState.preset as string}\n          isPublished={isPublished}\n          disabled={isGeneratingTheme}\n        />\n      ) : (\n        <SaveButton onClick={onSaveClick} isSaving={isSaving} disabled={isGeneratingTheme} />\n      )}\n      <CodeButton onClick={onCodeClick} disabled={isGeneratingTheme} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/ai-generate-button.tsx",
    "content": "import { Sparkles } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\ninterface AIGenerateButtonProps {\n  onClick: () => void;\n}\n\nexport function AIGenerateButton({ onClick }: AIGenerateButtonProps) {\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          onClick={onClick}\n          className=\"h-8 px-2 gap-1.5 text-muted-foreground hover:text-foreground hover:bg-accent/50\"\n        >\n          <Sparkles className=\"size-3.5\" />\n          <span className=\"text-sm hidden md:block animate-text bg-gradient-to-r from-muted-foreground via-foreground to-muted-foreground bg-[200%_auto] bg-clip-text text-transparent\">\n            Generate\n          </span>\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>Generate theme with AI</TooltipContent>\n    </Tooltip>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/code-button.tsx",
    "content": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { Braces } from \"lucide-react\";\n\ninterface CodeButtonProps extends React.ComponentProps<typeof Button> {}\n\nexport function CodeButton({ className, ...props }: CodeButtonProps) {\n  return (\n    <TooltipWrapper label=\"View theme code\" asChild>\n      <Button variant=\"ghost\" size=\"sm\" className={cn(className)} {...props}>\n        <Braces className=\"size-3.5\" />\n        <span className=\"hidden text-sm md:block\">Code</span>\n      </Button>\n    </TooltipWrapper>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/import-button.tsx",
    "content": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { FileCode } from \"lucide-react\";\n\ninterface ImportButtonProps extends React.ComponentProps<typeof Button> {}\n\nexport function ImportButton({ className, ...props }: ImportButtonProps) {\n  return (\n    <TooltipWrapper label=\"Import CSS variables\" asChild>\n      <Button variant=\"ghost\" size=\"sm\" className={cn(className)} {...props}>\n        <FileCode className=\"size-3.5\" />\n        <span className=\"hidden text-sm md:block\">Import</span>\n      </Button>\n    </TooltipWrapper>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/mcp-dialog.tsx",
    "content": "import {\n  Tabs,\n  TabsContent,\n  TabsIndicator,\n  TabsList,\n  TabsTrigger,\n} from \"@/components/ui/base-ui-tabs\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n} from \"@/components/ui/revola\";\nimport { useCopyToClipboard } from \"@/hooks/use-copy-to-clipboard\";\nimport { Check, Copy } from \"lucide-react\";\nimport { usePostHog } from \"posthog-js/react\";\n\ninterface MCPDialogProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n}\n\nconst mcpConfig = {\n  mcpServers: {\n    shadcn: {\n      command: \"npx\",\n      args: [\"-y\", \"shadcn@canary\", \"registry:mcp\"],\n      env: {\n        REGISTRY_URL: \"https://tweakcn.com/r/themes/registry.json\",\n      },\n    },\n  },\n};\n\nexport function MCPDialog({ open, onOpenChange }: MCPDialogProps) {\n  const { hasCopied, copyToClipboard } = useCopyToClipboard();\n  const posthog = usePostHog();\n\n  const handleCopy = async (config: typeof mcpConfig) => {\n    copyToClipboard(JSON.stringify(config, null, 2));\n    posthog.capture(\"COPY_MCP_SETUP\");\n  };\n\n  return (\n    <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n      <ResponsiveDialogContent className=\"flex max-h-[90dvh] flex-col overflow-hidden shadow-lg sm:w-[calc(100%-2rem)] sm:max-w-2xl\">\n        <div className=\"space-y-6 p-6 pt-0 sm:p-6\">\n          <ResponsiveDialogHeader className=\"text-left\">\n            <ResponsiveDialogTitle>Setup MCP</ResponsiveDialogTitle>\n            <ResponsiveDialogDescription>\n              Use the code below to configure the registry in your IDE.\n            </ResponsiveDialogDescription>\n          </ResponsiveDialogHeader>\n\n          <Tabs defaultValue=\"cursor\">\n            <TabsList className=\"bg-background mb-2 inline-flex w-fit items-center justify-center rounded-full px-0\">\n              <TabsTrigger\n                className=\"data-selected:text-secondary-foreground hover:text-muted-foreground/70 rounded-full\"\n                value=\"cursor\"\n              >\n                Cursor\n              </TabsTrigger>\n              <TabsTrigger\n                className=\"data-selected:text-secondary-foreground hover:text-muted-foreground/70 rounded-full\"\n                value=\"windsurf\"\n              >\n                Windsurf\n              </TabsTrigger>\n              <TabsIndicator />\n            </TabsList>\n\n            <div className=\"rounded-lg border\">\n              <div className=\"bg-muted/50 flex items-center justify-between border-b px-4 py-2\">\n                <TabsContent value=\"cursor\" className=\"contents\">\n                  <p className=\"text-muted-foreground text-sm font-medium\">\n                    Copy and paste the code into{\" \"}\n                    <span className=\"bg-muted text-foreground rounded-md px-1\">\n                      .cursor/mcp.json\n                    </span>\n                  </p>\n                </TabsContent>\n                <TabsContent value=\"windsurf\" className=\"contents\">\n                  <p className=\"text-muted-foreground text-sm font-medium\">\n                    Copy and paste the code into{\" \"}\n                    <span className=\"bg-muted text-foreground rounded-md px-1\">\n                      .codeium/windsurf/mcp_config.json\n                    </span>\n                  </p>\n                </TabsContent>\n                <Button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  onClick={() => handleCopy(mcpConfig)}\n                  className=\"h-8\"\n                  aria-label={hasCopied ? \"Copied to clipboard\" : \"Copy to clipboard\"}\n                >\n                  {hasCopied ? (\n                    <>\n                      <Check className=\"size-4\" />\n                      <span className=\"sr-only md:not-sr-only\">Copied</span>\n                    </>\n                  ) : (\n                    <>\n                      <Copy className=\"size-4\" />\n                      <span className=\"sr-only md:not-sr-only\">Copy</span>\n                    </>\n                  )}\n                </Button>\n              </div>\n\n              <pre className=\"size-full overflow-auto p-4 text-sm\">\n                <code>{JSON.stringify(mcpConfig, null, 2)}</code>\n              </pre>\n            </div>\n          </Tabs>\n        </div>\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/more-options.tsx",
    "content": "import McpIcon from \"@/assets/mcp.svg\";\nimport ContrastChecker from \"@/components/editor/contrast-checker\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { MoreVertical } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { MCPDialog } from \"./mcp-dialog\";\n\ninterface MoreOptionsProps extends React.ComponentProps<typeof DropdownMenuTrigger> {}\n\nexport function MoreOptions({ ...props }: MoreOptionsProps) {\n  const [mcpDialogOpen, setMcpDialogOpen] = useState(false);\n  const { themeState } = useEditorStore();\n\n  return (\n    <>\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild {...props}>\n          <Button variant=\"ghost\" size=\"icon\">\n            <MoreVertical className=\"h-4 w-4\" />\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent align=\"end\" className=\"text-foreground\">\n          <DropdownMenuItem onClick={() => setMcpDialogOpen(true)} asChild>\n            <Button variant=\"ghost\" size=\"sm\" className=\"w-full justify-start\">\n              <McpIcon className=\"h-4 w-4\" />\n              <span className=\"text-sm\">MCP</span>\n            </Button>\n          </DropdownMenuItem>\n          <DropdownMenuItem onSelect={(e) => e.preventDefault()} asChild>\n            <ContrastChecker currentStyles={themeState.styles[themeState.currentMode]} />\n          </DropdownMenuItem>\n        </DropdownMenuContent>\n      </DropdownMenu>\n\n      <MCPDialog open={mcpDialogOpen} onOpenChange={setMcpDialogOpen} />\n    </>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/publish-button.tsx",
    "content": "\"use client\";\n\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { TagSelector } from \"@/components/tag-selector\";\nimport { cn } from \"@/lib/utils\";\nimport { Globe, Loader2 } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { usePublishTheme } from \"@/hooks/themes\";\n\ninterface PublishButtonProps {\n  themeId: string;\n  isPublished: boolean;\n  disabled?: boolean;\n  className?: string;\n}\n\nexport function PublishButton({\n  themeId,\n  isPublished,\n  disabled,\n  className,\n}: PublishButtonProps) {\n  const publishMutation = usePublishTheme();\n  const [showDialog, setShowDialog] = useState(false);\n  const [selectedTags, setSelectedTags] = useState<string[]>([]);\n\n  if (isPublished) {\n    return (\n      <TooltipWrapper label=\"Published to community\" asChild>\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          className={cn(\"text-muted-foreground\", className)}\n          disabled\n        >\n          <Globe className=\"size-3.5\" />\n          <span className=\"hidden text-sm md:block\">Published</span>\n        </Button>\n      </TooltipWrapper>\n    );\n  }\n\n  const handleConfirmPublish = () => {\n    publishMutation.mutate(\n      { themeId, tags: selectedTags },\n      {\n        onSuccess: () => {\n          setShowDialog(false);\n          setSelectedTags([]);\n        },\n      }\n    );\n  };\n\n  return (\n    <>\n      <TooltipWrapper label=\"Publish to community\" asChild>\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          className={cn(className)}\n          disabled={disabled || publishMutation.isPending}\n          onClick={() => setShowDialog(true)}\n        >\n          {publishMutation.isPending ? (\n            <Loader2 className=\"size-3.5 animate-spin\" />\n          ) : (\n            <Globe className=\"size-3.5\" />\n          )}\n          <span className=\"hidden text-sm md:block\">Publish</span>\n        </Button>\n      </TooltipWrapper>\n\n      <Dialog open={showDialog} onOpenChange={setShowDialog}>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>Publish to the community?</DialogTitle>\n            <DialogDescription>\n              Your theme will be publicly visible on the community page. You can\n              unpublish it at any time.\n            </DialogDescription>\n          </DialogHeader>\n          <TagSelector\n            selectedTags={selectedTags}\n            onTagsChange={setSelectedTags}\n            disabled={publishMutation.isPending}\n          />\n          <DialogFooter>\n            <Button variant=\"outline\" onClick={() => setShowDialog(false)}>\n              Cancel\n            </Button>\n            <Button\n              onClick={handleConfirmPublish}\n              disabled={publishMutation.isPending}\n            >\n              {publishMutation.isPending ? (\n                <>\n                  <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n                  Publishing...\n                </>\n              ) : (\n                \"Publish\"\n              )}\n            </Button>\n          </DialogFooter>\n        </DialogContent>\n      </Dialog>\n    </>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/reset-button.tsx",
    "content": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { RefreshCw } from \"lucide-react\";\n\ninterface ResetButtonProps extends React.ComponentProps<typeof Button> {}\n\nexport function ResetButton({ className, ...props }: ResetButtonProps) {\n  return (\n    <TooltipWrapper label=\"Reset to preset defaults\" asChild>\n      <Button variant=\"ghost\" size=\"sm\" className={cn(className)} {...props}>\n        <RefreshCw className=\"size-3.5\" />\n        <span className=\"hidden text-sm md:block\">Reset</span>\n      </Button>\n    </TooltipWrapper>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/save-button.tsx",
    "content": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { Heart, Loader2 } from \"lucide-react\";\n\ninterface SaveButtonProps extends React.ComponentProps<typeof Button> {\n  isSaving: boolean;\n}\n\nexport function SaveButton({ isSaving, disabled, className, ...props }: SaveButtonProps) {\n  return (\n    <TooltipWrapper label=\"Save theme\" asChild>\n      <Button\n        variant=\"ghost\"\n        size=\"sm\"\n        className={cn(className)}\n        disabled={isSaving || disabled}\n        {...props}\n      >\n        {isSaving ? <Loader2 className=\"size-3.5 animate-spin\" /> : <Heart className=\"size-3.5\" />}\n        <span className=\"hidden text-sm md:block\">Save</span>\n      </Button>\n    </TooltipWrapper>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/share-button.tsx",
    "content": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { Loader2, Share2 } from \"lucide-react\";\n\ninterface ShareButtonProps extends React.ComponentProps<typeof Button> {\n  isSharing?: boolean;\n}\n\nexport function ShareButton({\n  onClick,\n  isSharing,\n  disabled,\n  className,\n  ...props\n}: ShareButtonProps) {\n  return (\n    <TooltipWrapper label=\"Share theme\" asChild>\n      <Button\n        variant=\"ghost\"\n        size=\"sm\"\n        className={cn(className)}\n        onClick={onClick}\n        disabled={isSharing || disabled}\n        {...props}\n      >\n        {isSharing ? (\n          <Loader2 className=\"size-3.5 animate-spin\" />\n        ) : (\n          <Share2 className=\"size-3.5\" />\n        )}\n        <span className=\"hidden text-sm md:block\">Share</span>\n      </Button>\n    </TooltipWrapper>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/theme-toggle.tsx",
    "content": "import { useTheme } from \"@/components/theme-provider\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { cn } from \"@/lib/utils\";\nimport { Switch as SwitchPrimitives } from \"radix-ui\";\nimport { Moon, Sun } from \"lucide-react\";\n\nexport function ThemeToggle() {\n  const { theme, toggleTheme } = useTheme();\n\n  const handleThemeToggle = (event: React.MouseEvent<HTMLButtonElement>) => {\n    const { clientX: x, clientY: y } = event;\n    toggleTheme({ x, y });\n  };\n\n  return (\n    <div className=\"px-2\">\n      <TooltipWrapper label=\"Toggle light/dark mode\" asChild>\n        <SwitchPrimitives.Root\n          checked={theme === \"dark\"}\n          onClick={handleThemeToggle}\n          className={cn(\n            \"peer focus-visible:ring-ring focus-visible:ring-offset-background inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50\",\n            theme === \"dark\" ? \"bg-primary\" : \"bg-input\"\n          )}\n        >\n          <SwitchPrimitives.Thumb\n            className={cn(\n              \"bg-background pointer-events-none flex size-5 items-center justify-center rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0\"\n            )}\n          >\n            {theme === \"dark\" ? <Moon className=\"size-3\" /> : <Sun className=\"size-3\" />}\n          </SwitchPrimitives.Thumb>\n        </SwitchPrimitives.Root>\n      </TooltipWrapper>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/action-bar/components/undo-redo-buttons.tsx",
    "content": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { Redo, Undo } from \"lucide-react\";\n\ninterface UndoRedoButtonsProps extends React.ComponentProps<typeof Button> {}\n\nexport function UndoRedoButtons({ disabled, ...props }: UndoRedoButtonsProps) {\n  const { undo, redo, canUndo, canRedo } = useEditorStore();\n\n  return (\n    <div className=\"flex items-center gap-1\">\n      <TooltipWrapper label=\"Undo\" asChild>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          disabled={disabled || !canUndo()}\n          {...props}\n          onClick={undo}\n        >\n          <Undo className=\"h-4 w-4\" />\n        </Button>\n      </TooltipWrapper>\n\n      <TooltipWrapper label=\"Redo\" asChild>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          disabled={disabled || !canRedo()}\n          {...props}\n          onClick={redo}\n        >\n          <Redo className=\"h-4 w-4\" />\n        </Button>\n      </TooltipWrapper>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/ai-chat-form-body.tsx",
    "content": "\"use client\";\n\nimport { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { AI_PROMPT_CHARACTER_LIMIT } from \"@/lib/constants\";\nimport { cn } from \"@/lib/utils\";\nimport { JSONContent } from \"@tiptap/react\";\nimport dynamic from \"next/dynamic\";\nimport { DragAndDropImageUploader } from \"./drag-and-drop-image-uploader\";\nimport { UploadedImagePreview } from \"./uploaded-image-preview\";\n\nconst CustomTextarea = dynamic(() => import(\"@/components/editor/custom-textarea\"), {\n  ssr: false,\n  loading: () => (\n    <div className=\"flex min-h-[50px] w-full flex-col gap-1 p-1\">\n      <div className=\"bg-muted h-3 w-full animate-pulse rounded-md text-sm\" />\n      <div className=\"bg-muted h-3 w-full animate-pulse rounded-md text-sm\" />\n    </div>\n  ),\n});\n\ninterface AIChatFormBodyProps {\n  isUserDragging: boolean;\n  disabled: boolean;\n  canSubmit: boolean;\n  uploadedImages: { url: string; loading: boolean }[];\n  handleImagesUpload: (files: File[]) => void;\n  handleImageRemove: (index: number) => void;\n  handleContentChange: (jsonContent: JSONContent) => void;\n  handleGenerate: () => void;\n  initialEditorContent: JSONContent | undefined;\n  externalEditorContent?: JSONContent;\n  textareaKey?: string | number;\n  isStreamingContent?: boolean;\n}\n\nexport function AIChatFormBody({\n  isUserDragging,\n  disabled,\n  canSubmit,\n  uploadedImages,\n  handleImagesUpload,\n  handleImageRemove,\n  handleContentChange,\n  handleGenerate,\n  initialEditorContent,\n  externalEditorContent,\n  textareaKey,\n  isStreamingContent = false,\n}: AIChatFormBodyProps) {\n  return (\n    <>\n      {isUserDragging && (\n        <div className={cn(\"flex h-16 items-center rounded-lg\")}>\n          <DragAndDropImageUploader\n            onDrop={handleImagesUpload}\n            disabled={disabled || uploadedImages.some((img) => img.loading)}\n          />\n        </div>\n      )}\n      {uploadedImages.length > 0 && !isUserDragging && (\n        <div\n          className={cn(\n            \"relative flex h-16 items-center rounded-lg\",\n            disabled && \"pointer-events-none opacity-75\"\n          )}\n        >\n          <HorizontalScrollArea className=\"w-full\">\n            {uploadedImages.map((img, idx) => (\n              <UploadedImagePreview\n                key={idx}\n                src={img.url}\n                isImageLoading={img.loading}\n                handleImageRemove={() => handleImageRemove(idx)}\n              />\n            ))}\n          </HorizontalScrollArea>\n        </div>\n      )}\n      <div>\n        <label className=\"sr-only\">Chat Input</label>\n        <div className=\"relative isolate min-h-[50px] overflow-hidden\" aria-disabled={disabled}>\n          <CustomTextarea\n            key={textareaKey}\n            onContentChange={handleContentChange}\n            onSubmit={handleGenerate}\n            disabled={disabled}\n            canSubmit={canSubmit}\n            characterLimit={AI_PROMPT_CHARACTER_LIMIT}\n            onImagesPaste={handleImagesUpload}\n            initialEditorContent={initialEditorContent}\n            externalEditorContent={externalEditorContent}\n            isStreamingContent={isStreamingContent}\n          />\n        </div>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/alert-banner.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { useSubscription } from \"@/hooks/use-subscription\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { AI_REQUEST_FREE_TIER_LIMIT } from \"@/lib/constants\";\nimport { cn } from \"@/lib/utils\";\nimport { X } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { useEffect, useState } from \"react\";\n\nexport function AlertBanner() {\n  const [showBanner, setShowBanner] = useState(false);\n\n  const { data: session } = authClient.useSession();\n  const isLoggedIn = !!session?.user.id;\n  const { subscriptionStatus, isPending } = useSubscription();\n\n  const isPro = subscriptionStatus?.isSubscribed ?? false;\n  const freeProMessagesLeft = subscriptionStatus?.requestsRemaining ?? 0;\n\n  useEffect(() => {\n    let timer: NodeJS.Timeout;\n\n    const shouldShowBanner =\n      isLoggedIn && !isPro && freeProMessagesLeft <= AI_REQUEST_FREE_TIER_LIMIT;\n\n    if (shouldShowBanner) {\n      if (showBanner) return;\n\n      timer = setTimeout(() => setShowBanner(true), 1000);\n    } else {\n      setShowBanner(false);\n    }\n\n    return () => clearTimeout(timer);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [isLoggedIn, isPro, freeProMessagesLeft]);\n\n  const getBannerContent = () => {\n    if (isLoggedIn && !isPro && freeProMessagesLeft > 0) {\n      return (\n        <span>\n          You have {freeProMessagesLeft} Free\n          <span className=\"text-primary font-medium\">{` Pro `}</span>\n          messages left.\n        </span>\n      );\n    }\n\n    if (isLoggedIn && !isPro && freeProMessagesLeft <= 0) {\n      return (\n        <span>\n          Upgrade to <span className=\"text-primary font-medium\">Pro</span> to unlock unlimited\n          requests.\n        </span>\n      );\n    }\n  };\n\n  if (isPro || isPending) return null;\n\n  return (\n    <BannerWrapper show={showBanner}>\n      <div className=\"flex size-full items-center justify-between gap-2\">\n        <p className={cn(\"line-clamp-1 text-pretty @2xl/alert-banner:text-sm\")}>\n          {getBannerContent()}\n        </p>\n        <div className=\"ml-auto flex items-center gap-1\">\n          <Link href=\"/pricing\">\n            <Button variant=\"link\" size=\"sm\" className=\"h-fit @2xl/alert-banner:text-sm\">\n              Upgrade\n            </Button>\n          </Link>\n\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-4 [&>svg]:size-3\"\n            onClick={() => setShowBanner(false)}\n          >\n            <X />\n          </Button>\n        </div>\n      </div>\n    </BannerWrapper>\n  );\n}\n\nexport function BannerWrapper({ children, show }: { children: React.ReactNode; show: boolean }) {\n  return (\n    <div className={cn(\"@container/alert-banner\")}>\n      <div\n        className={cn(\n          \"relative w-full origin-bottom transition-all duration-300 ease-in-out\",\n          show\n            ? \"pointer-events-auto max-h-6 @2xl/alert-banner:max-h-7.5\"\n            : \"pointer-events-none max-h-0 translate-y-full\"\n        )}\n        style={{\n          willChange: \"transform, max-height\",\n        }}\n      >\n        <div className=\"bg-muted text-muted-foreground flex h-6 items-center rounded-t-lg px-3 text-xs @2xl/alert-banner:h-7.5\">\n          {children}\n        </div>\n\n        <div className=\"bg-muted h-4 w-full\"></div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/chat-image-preview.tsx",
    "content": "import {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n  ResponsiveDialogTrigger,\n} from \"@/components/ui/revola\";\nimport { cn } from \"@/lib/utils\";\nimport { ImageIcon } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { ComponentProps } from \"react\";\n\ninterface ChatImagePreviewProps extends ComponentProps<typeof Image> {\n  name?: string;\n}\n\nexport function ChatImagePreview({ name, src, className, alt, ...props }: ChatImagePreviewProps) {\n  return (\n    <ResponsiveDialog onlyDialog>\n      <ResponsiveDialogTrigger asChild>\n        <div className=\"group/preview relative isolate size-full cursor-pointer overflow-hidden rounded-lg border\">\n          <Image\n            width={250}\n            height={250}\n            src={src}\n            className={cn(\n              \"h-auto max-h-[250px] w-auto max-w-[250px] object-cover object-center\",\n              className\n            )}\n            alt={alt || \"Image preview\"}\n            title={name}\n            {...props}\n          />\n\n          <div className=\"bg-accent/75 text-accent-foreground border-border/50! absolute right-2 bottom-2 z-1 flex items-center justify-end rounded-lg border p-1 opacity-0 backdrop-blur transition-opacity group-hover/preview:opacity-100\">\n            <ImageIcon className=\"size-3.5\" />\n          </div>\n        </div>\n      </ResponsiveDialogTrigger>\n\n      <ResponsiveDialogContent\n        closeButtonClassName=\"bg-white/10 backdrop-blur-md\"\n        className=\"size-fit max-h-[80dvh] overflow-hidden sm:max-w-[80vw]\"\n      >\n        <ResponsiveDialogHeader className=\"sr-only\">\n          <ResponsiveDialogTitle>Image Preview</ResponsiveDialogTitle>\n        </ResponsiveDialogHeader>\n        <Image\n          src={src}\n          width={500}\n          height={500}\n          alt=\"Full image preview\"\n          className=\"size-auto max-h-[80vh] max-w-[80vw] object-contain\"\n        />\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/chat-input.tsx",
    "content": "\"use client\";\n\nimport { Loader } from \"@/components/loader\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { useAIChatForm } from \"@/hooks/use-ai-chat-form\";\nimport { useAIEnhancePrompt } from \"@/hooks/use-ai-enhance-prompt\";\nimport { useChatContext } from \"@/hooks/use-chat-context\";\nimport { useGuards } from \"@/hooks/use-guards\";\nimport { useSubscription } from \"@/hooks/use-subscription\";\nimport { usePostLoginAction } from \"@/hooks/use-post-login-action\";\nimport { MAX_IMAGE_FILES } from \"@/lib/constants\";\nimport { cn } from \"@/lib/utils\";\nimport { AIPromptData } from \"@/types/ai\";\nimport { ArrowUp, Loader as LoaderIcon, Plus, StopCircle } from \"lucide-react\";\nimport { AIChatFormBody } from \"./ai-chat-form-body\";\nimport { AlertBanner, BannerWrapper } from \"./alert-banner\";\nimport { EnhancePromptButton } from \"./enhance-prompt-button\";\nimport { ImageUploader } from \"./image-uploader\";\n\ntype ThemeGenerationPayload = {\n  promptData: AIPromptData;\n  options: {\n    shouldClearLocalDraft?: boolean;\n  };\n};\n\ninterface ChatInputProps {\n  onThemeGeneration: (promptData: AIPromptData) => Promise<void>;\n  isGeneratingTheme: boolean;\n  onCancelThemeGeneration: () => void;\n}\n\nexport function ChatInput({\n  onThemeGeneration,\n  isGeneratingTheme,\n  onCancelThemeGeneration,\n}: ChatInputProps) {\n  const { messages, startNewChat } = useChatContext();\n  const { checkValidSession, checkValidSubscription } = useGuards();\n  const { subscriptionStatus } = useSubscription();\n  const isPro = subscriptionStatus?.isSubscribed ?? false;\n  const hasFreeRequestsLeft = (subscriptionStatus?.requestsRemaining ?? 0) > 0;\n\n  const {\n    editorContentDraft,\n    handleContentChange,\n    promptData,\n    isEmptyPrompt,\n    clearLocalDraft,\n    uploadedImages,\n    fileInputRef,\n    handleImagesUpload,\n    handleImageRemove,\n    clearUploadedImages,\n    isSomeImageUploading,\n    isUserDragging,\n    isInitializing,\n  } = useAIChatForm();\n\n  const handleNewChat = () => {\n    startNewChat();\n    clearLocalDraft();\n    clearUploadedImages();\n  };\n\n  const { startEnhance, stopEnhance, enhancedPromptAsJsonContent, isEnhancingPrompt } =\n    useAIEnhancePrompt();\n\n  const handleEnhancePrompt = () => {\n    if (!checkValidSession() || !checkValidSubscription()) return;\n\n    // Only send images that are not loading, and strip loading property\n    const images = uploadedImages.filter((img) => !img.loading).map(({ url }) => ({ url }));\n    startEnhance({ ...promptData, images });\n  };\n\n  const generateTheme = async (payload: ThemeGenerationPayload) => {\n    const { promptData, options } = payload;\n\n    if (options.shouldClearLocalDraft) {\n      clearLocalDraft();\n      clearUploadedImages();\n    }\n\n    onThemeGeneration(promptData);\n  };\n\n  const handleGenerateSubmit = async () => {\n    // Only send images that are not loading, and strip loading property\n    const images = uploadedImages.filter((img) => !img.loading).map(({ url }) => ({ url }));\n\n    // Proceed only if there is text, or at least one image\n    if (isEmptyPrompt && images.length === 0) return;\n\n    const payload: ThemeGenerationPayload = {\n      promptData: {\n        ...promptData,\n        images,\n      },\n      options: {\n        shouldClearLocalDraft: true,\n      },\n    };\n\n    if (!checkValidSession(\"signup\", \"AI_GENERATE_FROM_CHAT\", payload)) return;\n    if (!checkValidSubscription()) return;\n\n    generateTheme(payload);\n  };\n\n  usePostLoginAction(\"AI_GENERATE_FROM_CHAT\", (payload) => {\n    generateTheme(payload);\n  });\n\n  return (\n    <div className=\"relative transition-all contain-layout\">\n      <BannerWrapper show={isGeneratingTheme}>\n        <div className=\"flex size-full items-center gap-1.5\">\n          <LoaderIcon className=\"size-2.5 animate-spin\" />\n          <Loader variant=\"text-shimmer\" text=\"Generating...\" size=\"sm\" />\n        </div>\n      </BannerWrapper>\n\n      <AlertBanner />\n      <div className=\"bg-background relative isolate z-10 flex size-full min-h-[100px] flex-1 flex-col gap-2 overflow-hidden rounded-lg border p-2 shadow-xs\">\n        <AIChatFormBody\n          isUserDragging={isUserDragging}\n          disabled={isEnhancingPrompt}\n          canSubmit={\n            !isGeneratingTheme &&\n            !isEnhancingPrompt &&\n            !isEmptyPrompt &&\n            !isSomeImageUploading &&\n            !isInitializing\n          }\n          uploadedImages={uploadedImages}\n          handleImagesUpload={handleImagesUpload}\n          handleImageRemove={handleImageRemove}\n          handleContentChange={handleContentChange}\n          handleGenerate={handleGenerateSubmit}\n          initialEditorContent={editorContentDraft ?? undefined}\n          textareaKey={editorContentDraft ? \"with-draft\" : \"no-draft\"}\n          externalEditorContent={enhancedPromptAsJsonContent}\n          isStreamingContent={isEnhancingPrompt}\n        />\n        <div className=\"@container/form flex items-center justify-between gap-2\">\n          <TooltipWrapper label=\"Create new chat\" asChild>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={handleNewChat}\n              disabled={\n                isGeneratingTheme || isEnhancingPrompt || isInitializing || messages.length === 0\n              }\n              className=\"flex items-center gap-1.5 shadow-none\"\n            >\n              <Plus />\n              <span>New chat</span>\n            </Button>\n          </TooltipWrapper>\n\n          <div className=\"flex items-center gap-2\">\n            {(isPro || hasFreeRequestsLeft) && promptData?.content ? (\n              <EnhancePromptButton\n                isEnhancing={isEnhancingPrompt}\n                onStart={handleEnhancePrompt}\n                onStop={stopEnhance}\n                disabled={isGeneratingTheme || isInitializing}\n              />\n            ) : null}\n\n            <ImageUploader\n              fileInputRef={fileInputRef}\n              onImagesUpload={handleImagesUpload}\n              onClick={() => fileInputRef.current?.click()}\n              disabled={\n                isGeneratingTheme ||\n                isEnhancingPrompt ||\n                isInitializing ||\n                uploadedImages.some((img) => img.loading) ||\n                uploadedImages.length >= MAX_IMAGE_FILES\n              }\n            />\n\n            {isGeneratingTheme ? (\n              <TooltipWrapper label=\"Cancel generation\" asChild>\n                <Button\n                  variant=\"destructive\"\n                  size=\"sm\"\n                  onClick={onCancelThemeGeneration}\n                  className={cn(\"flex items-center gap-1.5 shadow-none\", \"@max-[350px]/form:w-8\")}\n                >\n                  <StopCircle />\n                  <span className=\"hidden @[350px]/form:inline-flex\">Stop</span>\n                </Button>\n              </TooltipWrapper>\n            ) : (\n              <TooltipWrapper label=\"Send message\" asChild>\n                <Button\n                  size=\"sm\"\n                  className=\"size-8 shadow-none\"\n                  onClick={handleGenerateSubmit}\n                  disabled={\n                    isEmptyPrompt ||\n                    isSomeImageUploading ||\n                    isGeneratingTheme ||\n                    isEnhancingPrompt ||\n                    isInitializing\n                  }\n                >\n                  {isGeneratingTheme ? <LoaderIcon className=\"animate-spin\" /> : <ArrowUp />}\n                </Button>\n              </TooltipWrapper>\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/chat-interface.tsx",
    "content": "\"use client\";\n\nimport { useChatContext } from \"@/hooks/use-chat-context\";\nimport { useAIThemeGenerationCore } from \"@/hooks/use-ai-theme-generation-core\";\nimport { useGuards } from \"@/hooks/use-guards\";\nimport { usePostLoginAction } from \"@/hooks/use-post-login-action\";\nimport { toast } from \"@/hooks/use-toast\";\nimport { cn } from \"@/lib/utils\";\nimport { AIPromptData } from \"@/types/ai\";\nimport dynamic from \"next/dynamic\";\nimport React from \"react\";\nimport { ChatInput } from \"./chat-input\";\nimport { ClosableSuggestedPillActions } from \"./closeable-suggested-pill-actions\";\n\nconst Messages = dynamic(() => import(\"./messages\").then((mod) => mod.Messages), {\n  ssr: false,\n});\n\nconst NoMessagesPlaceholder = dynamic(\n  () => import(\"./no-messages-placeholder\").then((mod) => mod.NoMessagesPlaceholder),\n  {\n    ssr: false,\n  }\n);\n\nexport function ChatInterface() {\n  const { messages, regenerate, resetMessagesUpToIndex } = useChatContext();\n  const { isGeneratingTheme, generateThemeCore, cancelThemeGeneration } =\n    useAIThemeGenerationCore();\n\n  const { checkValidSession, checkValidSubscription } = useGuards();\n\n  const hasMessages = messages.length > 0;\n  const [editingMessageIndex, setEditingMessageIndex] = React.useState<number | null>(null);\n\n  const handleGenerateFromSuggestion = (promptData: AIPromptData | undefined) => {\n    if (!checkValidSession(\"signup\", \"AI_GENERATE_FROM_CHAT_SUGGESTION\", { promptData })) return;\n    if (!checkValidSubscription()) return;\n\n    generateThemeCore(promptData);\n  };\n\n  const handleRetry = (messageIndex: number) => {\n    if (!checkValidSession(\"signup\", \"AI_GENERATE_RETRY\", { messageIndex })) return;\n    if (!checkValidSubscription()) return;\n\n    setEditingMessageIndex(null);\n    const messageToRetry = messages[messageIndex];\n\n    if (messageToRetry) {\n      regenerate({ messageId: messageToRetry.id });\n    } else {\n      toast({\n        title: \"Cannot retry this message\",\n        description: \"Seems like this message is not longer available.\",\n        variant: \"destructive\",\n      });\n    }\n  };\n\n  const handleEdit = (messageIndex: number) => {\n    if (!checkValidSession()) return; // Simply act as an early return\n\n    setEditingMessageIndex(messageIndex);\n  };\n\n  const handleEditCancel = () => {\n    setEditingMessageIndex(null);\n  };\n\n  const handleEditSubmit = (messageIndex: number, promptData: AIPromptData) => {\n    if (!checkValidSession(\"signup\", \"AI_GENERATE_EDIT\", { messageIndex, promptData })) {\n      return;\n    }\n    if (!checkValidSubscription()) return;\n\n    // Reset messages up to the edited message\n    resetMessagesUpToIndex(messageIndex);\n    setEditingMessageIndex(null);\n    generateThemeCore(promptData);\n  };\n\n  usePostLoginAction(\"AI_GENERATE_FROM_CHAT_SUGGESTION\", ({ promptData }) => {\n    handleGenerateFromSuggestion(promptData);\n  });\n\n  usePostLoginAction(\"AI_GENERATE_RETRY\", ({ messageIndex }) => {\n    handleRetry(messageIndex);\n  });\n\n  usePostLoginAction(\"AI_GENERATE_EDIT\", ({ messageIndex, promptData }) => {\n    handleEditSubmit(messageIndex, promptData);\n  });\n\n  return (\n    <section className=\"@container relative isolate z-1 mx-auto flex size-full max-w-[49rem] flex-1 flex-col justify-center\">\n      <div\n        className={cn(\n          \"relative flex size-full flex-1 flex-col overflow-hidden transition-all duration-300 ease-out\"\n        )}\n      >\n        {hasMessages ? (\n          <Messages\n            messages={messages}\n            onRetry={handleRetry}\n            onEdit={handleEdit}\n            onEditSubmit={handleEditSubmit}\n            onEditCancel={handleEditCancel}\n            editingMessageIndex={editingMessageIndex}\n            isGeneratingTheme={isGeneratingTheme}\n          />\n        ) : (\n          <div className=\"animate-in fade-in-50 zoom-in-95 relative isolate px-4 pt-8 duration-300 ease-out sm:pt-16 md:pt-24\">\n            <NoMessagesPlaceholder\n              onGenerateTheme={handleGenerateFromSuggestion}\n              isGeneratingTheme={isGeneratingTheme}\n            />\n          </div>\n        )}\n      </div>\n\n      {/* Chat form input and suggestions */}\n      <div className=\"relative z-1 mx-auto w-full px-4 pb-4\">\n        <div\n          className={cn(\n            \"transition-all ease-out\",\n            hasMessages ? \"scale-100 opacity-100\" : \"h-0 scale-80 opacity-0\"\n          )}\n        >\n          <ClosableSuggestedPillActions\n            onGenerateTheme={handleGenerateFromSuggestion}\n            isGeneratingTheme={isGeneratingTheme}\n          />\n        </div>\n\n        <ChatInput\n          onThemeGeneration={generateThemeCore}\n          isGeneratingTheme={isGeneratingTheme}\n          onCancelThemeGeneration={cancelThemeGeneration}\n        />\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/chat-theme-preview.tsx",
    "content": "import { Loader } from \"@/components/loader\";\nimport { useTheme } from \"@/components/theme-provider\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport { useFeedbackText } from \"@/hooks/use-feedback-text\";\nimport { cn } from \"@/lib/utils\";\nimport { ThemeStyles } from \"@/types/theme\";\nimport { applyGeneratedTheme } from \"@/utils/ai/apply-theme\";\nimport { AlertCircle, CheckCheck, ChevronsUpDown, Loader2, Zap } from \"lucide-react\";\nimport { ComponentProps, useState } from \"react\";\n\ntype ChatThemePreviewProps = ComponentProps<\"div\"> & ChatThemePreviewPropsBase;\n\ntype ChatThemePreviewPropsBase =\n  | {\n      status: \"loading\";\n      expanded?: boolean;\n      themeStyles?: Partial<ThemeStyles>;\n    }\n  | {\n      status: \"error\";\n      expanded?: boolean;\n      errorText?: string;\n      themeStyles?: Partial<ThemeStyles>;\n    }\n  | {\n      status: \"complete\";\n      expanded?: boolean;\n      themeStyles: ThemeStyles;\n    };\n\nexport function ChatThemePreview({\n  status,\n  expanded = false,\n  themeStyles,\n  className,\n  children,\n  ...props\n}: ChatThemePreviewProps) {\n  const [isExpanded, setIsExpanded] = useState(expanded);\n  const { theme: mode } = useTheme();\n  const loading = status === \"loading\";\n\n  const feedbackText = useFeedbackText({\n    showFeedbackText: loading,\n    feedbackMessages: FEEDBACK_MESSAGES,\n    rotationIntervalInSeconds: 8,\n  });\n\n  if (loading) {\n    return (\n      <Card className={cn(\"w-full max-w-[550px] gap-0 overflow-hidden rounded-lg py-0 shadow-none\")}>\n        <div className=\"flex size-full h-10 items-center gap-2 p-1.5\">\n          <div className=\"bg-muted flex size-7 items-center justify-center rounded-sm\">\n            <Loader2 className=\"text-muted-foreground size-4 animate-spin\" />\n          </div>\n\n          <Loader variant=\"text-shimmer\" text={feedbackText} size=\"md\" />\n        </div>\n      </Card>\n    );\n  }\n\n  if (status === \"error\")\n    return (\n      <Card className={cn(\"max-w-[550px] gap-0 overflow-hidden rounded-lg py-0 shadow-none\")}>\n        <div className=\"flex size-full h-10 items-center gap-2 p-1.5\">\n          <div className=\"bg-destructive flex size-7 items-center justify-center rounded-sm\">\n            <AlertCircle className=\"text-destructive-foreground size-4\" />\n          </div>\n          <span className=\"text-foreground/90 text-sm\">Generation cancelled or failed.</span>\n        </div>\n      </Card>\n    );\n\n  const handleApplyTheme = (e: React.MouseEvent<HTMLButtonElement>) => {\n    e.preventDefault();\n    e.stopPropagation();\n    if (status === \"complete\") applyGeneratedTheme(themeStyles);\n  };\n\n  if (status === \"complete\")\n    return (\n      <Card className={cn(\"max-w-[550px] gap-0 overflow-hidden rounded-lg py-0 shadow-none\")}>\n        <div\n          className={cn(\n            \"group/control hover:bg-background/50 flex h-10 w-full shrink-0 cursor-pointer items-center gap-2 p-1.5 pr-2 transition-colors duration-300 ease-in-out\",\n            isExpanded ? \"border-b\" : \"border-transparent\"\n          )}\n          onClick={() => setIsExpanded(!isExpanded)}\n        >\n          <div className=\"bg-primary/15 flex size-7 items-center justify-center rounded-sm\">\n            <CheckCheck className=\"text-primary size-4\" />\n          </div>\n\n          <div className=\"flex gap-1\">\n            <div\n              className=\"ring-border size-3 rounded-sm ring-1 @sm:size-4\"\n              style={{ backgroundColor: themeStyles[mode].primary }}\n            />\n            <div\n              className=\"ring-border size-3 rounded-sm ring-1 @sm:size-4\"\n              style={{ backgroundColor: themeStyles[mode].secondary }}\n            />\n            <div\n              className=\"ring-border size-3 rounded-sm ring-1 @sm:size-4\"\n              style={{ backgroundColor: themeStyles[mode].background }}\n            />\n            <div\n              className=\"ring-border size-3 rounded-sm ring-1 @sm:size-4\"\n              style={{ backgroundColor: themeStyles[mode].muted }}\n            />\n            <div\n              className=\"ring-border size-3 rounded-sm ring-1 @sm:size-4\"\n              style={{ backgroundColor: themeStyles[mode].accent }}\n            />\n            <div\n              className=\"ring-border size-3 rounded-sm ring-1 @sm:size-4\"\n              style={{ backgroundColor: themeStyles[mode].border }}\n            />\n          </div>\n\n          <div className=\"ml-auto flex items-center gap-1\">\n            <Button\n              variant=\"ghost\"\n              className=\"h-7 gap-1.5 px-2 shadow-none\"\n              onClick={handleApplyTheme}\n            >\n              <Zap className=\"size-3.5!\" />\n              Apply\n            </Button>\n\n            <button\n              type=\"button\"\n              className=\"text-foreground/75 group-hover/control:text-foreground ml-auto transition-colors\"\n              aria-label={isExpanded ? \"Collapse section\" : \"Expand section\"}\n            >\n              <ChevronsUpDown className=\"size-4\" />\n            </button>\n          </div>\n        </div>\n\n        <div\n          className={cn(\n            \"grid transition-all duration-300 ease-in-out\",\n            isExpanded ? \"grid-rows-[1fr]\" : \"grid-rows-[0fr]\"\n          )}\n        >\n          <div className=\"overflow-hidden\">\n            <div className={cn(\"space-y-2 p-2.5\", className)} {...props}>\n              {children}\n            </div>\n          </div>\n        </div>\n      </Card>\n    );\n\n  return null;\n}\n\nconst FEEDBACK_MESSAGES = [\n  \"Generating your theme...\",\n  \"Tweaking color tokens...\",\n  \"Making a good theme takes time...\",\n  \"Still working on your theme...\",\n  \"Almost there...\",\n];\n"
  },
  {
    "path": "components/editor/ai/closeable-suggested-pill-actions.tsx",
    "content": "\"use client\";\n\nimport { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { Button } from \"@/components/ui/button\";\nimport { usePreferencesStore } from \"@/store/preferences-store\";\nimport { AIPromptData } from \"@/types/ai\";\nimport { createCurrentThemePrompt } from \"@/utils/ai/ai-prompt\";\nimport { PROMPTS } from \"@/utils/ai/prompts\";\nimport { Sparkles, X } from \"lucide-react\";\nimport { PillActionButton } from \"./pill-action-button\";\n\nexport function ClosableSuggestedPillActions({\n  onGenerateTheme,\n  isGeneratingTheme,\n}: {\n  onGenerateTheme: (promptData: AIPromptData) => void;\n  isGeneratingTheme: boolean;\n}) {\n  const { chatSuggestionsOpen, setChatSuggestionsOpen } = usePreferencesStore();\n\n  const handleSetPrompt = async (prompt: string) => {\n    const promptData = createCurrentThemePrompt({ prompt });\n    onGenerateTheme(promptData);\n  };\n\n  if (!chatSuggestionsOpen) return null;\n\n  return (\n    <div className=\"relative flex flex-col items-center justify-center\">\n      <div className=\"text-muted-foreground flex w-full items-center justify-between gap-4\">\n        <h3 className=\"text-xs\">Suggestions</h3>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"size-6 [&>svg]:size-3\"\n          onClick={() => setChatSuggestionsOpen(false)}\n        >\n          <X />\n        </Button>\n      </div>\n\n      <HorizontalScrollArea className=\"pt-1 pb-2\">\n        {Object.entries(PROMPTS).map(([key, { label, prompt }]) => (\n          <PillActionButton\n            key={key}\n            onClick={() => handleSetPrompt(prompt)}\n            disabled={isGeneratingTheme}\n          >\n            <Sparkles /> {label}\n          </PillActionButton>\n        ))}\n      </HorizontalScrollArea>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/drag-and-drop-image-uploader.tsx",
    "content": "import { cn } from \"@/lib/utils\";\nimport { Upload } from \"lucide-react\";\nimport { useDropzone } from \"react-dropzone\";\n\ninterface DragAndDropImageUploaderProps {\n  onDrop: (files: File[]) => void;\n  disabled?: boolean;\n  className?: string;\n}\n\nexport function DragAndDropImageUploader({\n  onDrop,\n  disabled,\n  className,\n}: DragAndDropImageUploaderProps) {\n  const { getRootProps, getInputProps, isDragActive } = useDropzone({\n    onDrop,\n    multiple: true,\n    noClick: true,\n    disabled,\n    accept: {\n      \"image/jpeg\": [],\n      \"image/jpg\": [],\n      \"image/png\": [],\n      \"image/webp\": [],\n      \"image/svg+xml\": [],\n    },\n  });\n\n  return (\n    <>\n      <div className=\"absolute inset-0 z-10\" {...getRootProps()} />\n\n      <div\n        className={cn(\n          \"relative flex size-full cursor-pointer items-center justify-center rounded-lg border-2 border-dashed transition-colors\",\n          isDragActive ? \"border-primary! bg-muted\" : \"bg-muted/40\",\n          className\n        )}\n      >\n        <input {...getInputProps()} />\n        <div className=\"flex w-full items-center justify-center gap-2\">\n          <Upload className=\"text-muted-foreground size-4\" />\n          <span className=\"text-muted-foreground text-sm font-medium\">Drop images here</span>\n        </div>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/enhance-prompt-button.tsx",
    "content": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { CircleStop, WandSparkles } from \"lucide-react\";\n\ninterface EnhancePromptButtonProps extends React.ComponentProps<typeof Button> {\n  isEnhancing: boolean;\n  onStart: () => void;\n  onStop: () => void;\n}\n\nexport function EnhancePromptButton({\n  className,\n  disabled,\n  isEnhancing,\n  onStart,\n  onStop,\n  ...props\n}: EnhancePromptButtonProps) {\n  return (\n    <TooltipWrapper label={isEnhancing ? \"Stop\" : \"Enhance prompt\"} asChild>\n      <Button\n        size=\"icon\"\n        variant={isEnhancing ? \"destructive\" : \"outline\"}\n        className={cn(\"relative size-8 shadow-none\", className)}\n        onClick={isEnhancing ? onStop : onStart}\n        disabled={disabled}\n        {...props}\n      >\n        {isEnhancing ? <CircleStop /> : <WandSparkles />}\n      </Button>\n    </TooltipWrapper>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/image-uploader.tsx",
    "content": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { MAX_IMAGE_FILE_SIZE, MAX_IMAGE_FILES } from \"@/lib/constants\";\nimport { cn } from \"@/lib/utils\";\nimport { ALLOWED_IMAGE_TYPES } from \"@/utils/ai/image-upload\";\nimport { ImagePlus } from \"lucide-react\";\nimport { ComponentProps } from \"react\";\n\ninterface ImageUploaderProps extends ComponentProps<typeof Button> {\n  fileInputRef: React.RefObject<HTMLInputElement | null>;\n  onImagesUpload: (files: File[]) => void;\n}\n\nexport function ImageUploader({\n  fileInputRef,\n  onImagesUpload,\n  disabled,\n  className,\n  ...props\n}: ImageUploaderProps) {\n  const handleImagesUpload = (event: React.ChangeEvent<HTMLInputElement>) => {\n    const fileList = event.target.files;\n    if (!fileList) return;\n\n    const files = Array.from(fileList);\n    onImagesUpload(files);\n  };\n\n  return (\n    <>\n      <input\n        type=\"file\"\n        multiple\n        max={MAX_IMAGE_FILES}\n        size={MAX_IMAGE_FILE_SIZE}\n        accept={ALLOWED_IMAGE_TYPES.join(\",\")}\n        className=\"hidden\"\n        aria-label=\"Upload image for theme generation\"\n        ref={fileInputRef}\n        onChange={handleImagesUpload}\n        disabled={disabled}\n      />\n      <TooltipWrapper label=\"Attach image\" asChild>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          className={cn(\n            \"flex items-center gap-1.5 shadow-none\",\n            \"@max-[350px]/form:w-8\",\n            className\n          )}\n          disabled={disabled}\n          {...props}\n        >\n          <ImagePlus /> <span className=\"hidden @[350px]/form:inline-flex\">Image</span>\n        </Button>\n      </TooltipWrapper>\n    </>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/loading-logo.tsx",
    "content": "import Logo from \"@/assets/logo.svg\";\nimport { Sparkle } from \"lucide-react\";\n\nexport function LoadingLogo() {\n  return (\n    <>\n      <div className=\"bg-primary/90 absolute inset-0 -z-1 m-auto size-[65%] animate-ping rounded-full\" />\n      <div className=\"bg-primary/30 absolute inset-0 -z-1 m-auto size-[85%] animate-ping rounded-full delay-100\" />\n      <div className=\"bg-background relative isolate mx-auto size-full rounded-full\">\n        <div className=\"absolute top-0 left-0 size-[30%]\">\n          <Sparkle className=\"size-full animate-pulse fill-current\" />\n        </div>\n        <Logo className=\"size-full animate-pulse p-0.5 delay-150\" />\n        <div className=\"absolute right-0 bottom-0 size-[20%]\">\n          <Sparkle className=\"size-full animate-pulse fill-current delay-300\" />\n        </div>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/message-actions.tsx",
    "content": "import { CopyButton } from \"@/components/copy-button\";\nimport { DebugButton } from \"@/components/debug-button\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { type ChatMessage } from \"@/types/ai\";\nimport { Edit, RefreshCw } from \"lucide-react\";\n\ntype MessageActionsProps = {\n  message: ChatMessage;\n  onRetry?: () => void;\n  onEdit?: () => void;\n  isGeneratingTheme: boolean;\n  isEditing?: boolean;\n};\n\nexport function MessageActions({\n  message,\n  onRetry,\n  onEdit,\n  isGeneratingTheme,\n  isEditing,\n}: MessageActionsProps) {\n  const isUser = message.role === \"user\";\n  const isAssistant = message.role === \"assistant\";\n\n  const getCopyContent = () => {\n    const convertTextPartsToString = (message: ChatMessage) => {\n      return (\n        message.parts\n          .filter((part) => part.type === \"text\")\n          .map((part) => part.text)\n          .join(\"\\n\") ?? \"\"\n      );\n    };\n\n    if (isUser && message.metadata) {\n      return message.metadata.promptData?.content ?? convertTextPartsToString(message);\n    }\n\n    return convertTextPartsToString(message);\n  };\n\n  return (\n    <div\n      className={cn(\n        \"invisible flex gap-1 transition-opacity duration-300 ease-out group-hover/message:visible\",\n        isUser && \"justify-end\",\n        isAssistant && \"justify-start pl-7.5\"\n      )}\n    >\n      {onRetry && (\n        <TooltipWrapper label=\"Retry\" asChild>\n          <Button\n            size=\"icon\"\n            variant=\"ghost\"\n            className=\"size-7 [&>svg]:size-3.5\"\n            disabled={isGeneratingTheme}\n            onClick={onRetry}\n          >\n            <RefreshCw />\n          </Button>\n        </TooltipWrapper>\n      )}\n\n      {onEdit && isUser && (\n        <TooltipWrapper label=\"Edit\" asChild>\n          <Button\n            size=\"icon\"\n            variant=\"ghost\"\n            className=\"size-7 [&>svg]:size-3.5\"\n            disabled={isGeneratingTheme || isEditing}\n            onClick={onEdit}\n          >\n            <Edit />\n          </Button>\n        </TooltipWrapper>\n      )}\n\n      <CopyButton textToCopy={getCopyContent()} className=\"size-7 [&>svg]:size-3.5\" />\n\n      <DebugButton\n        className=\"size-7 [&>svg]:size-3.5\"\n        onClick={() => {\n          console.log(\"----- 🐛 Debugging Message -----\");\n          console.dir(message, { depth: null });\n        }}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/message-edit-form.tsx",
    "content": "import { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { useDocumentDragAndDropIntent } from \"@/hooks/use-document-drag-and-drop-intent\";\nimport { useImageUpload } from \"@/hooks/use-image-upload\";\nimport { imageUploadReducer } from \"@/hooks/use-image-upload-reducer\";\nimport { AI_PROMPT_CHARACTER_LIMIT, MAX_IMAGE_FILES, MAX_IMAGE_FILE_SIZE } from \"@/lib/constants\";\nimport { cn } from \"@/lib/utils\";\nimport { AIPromptData, type ChatMessage } from \"@/types/ai\";\nimport {\n  convertJSONContentToPromptData,\n  convertPromptDataToJSONContent,\n  isEmptyPromptData,\n} from \"@/utils/ai/ai-prompt\";\nimport { JSONContent } from \"@tiptap/react\";\nimport { Check, X } from \"lucide-react\";\nimport { useMemo, useReducer, useState } from \"react\";\nimport CustomTextarea from \"../custom-textarea\";\nimport { DragAndDropImageUploader } from \"./drag-and-drop-image-uploader\";\nimport { ImageUploader } from \"./image-uploader\";\nimport { UploadedImagePreview } from \"./uploaded-image-preview\";\n\ninterface MessageEditFormProps {\n  message: ChatMessage;\n  onEditSubmit: (newPromptData: AIPromptData) => void;\n  onEditCancel: () => void;\n  disabled: boolean;\n}\n\nexport function MessageEditForm({\n  message,\n  onEditSubmit,\n  onEditCancel,\n  disabled,\n}: MessageEditFormProps) {\n  const promptData = message.metadata?.promptData;\n\n  const [editJsonContent, setEditJsonContent] = useState<JSONContent>(() => {\n    if (!promptData) return { type: \"doc\", content: [] };\n    return convertPromptDataToJSONContent(promptData);\n  });\n\n  const [uploadedImages, dispatch] = useReducer(\n    imageUploadReducer,\n    promptData?.images ? promptData.images.map((img) => ({ ...img, loading: false })) : []\n  );\n\n  const {\n    fileInputRef,\n    handleImagesUpload,\n    handleImageRemove,\n    isSomeImageUploading,\n    canUploadMore,\n  } = useImageUpload({\n    maxFiles: MAX_IMAGE_FILES,\n    maxFileSize: MAX_IMAGE_FILE_SIZE,\n    images: uploadedImages,\n    dispatch,\n  });\n\n  const newPromptData = useMemo(\n    () => convertJSONContentToPromptData(editJsonContent),\n    [editJsonContent]\n  );\n  const isEmptyPrompt = isEmptyPromptData(newPromptData, uploadedImages);\n\n  const handleEditConfirm = () => {\n    if (isEmptyPrompt) return;\n\n    onEditSubmit({\n      ...promptData,\n      ...newPromptData,\n      images: uploadedImages.filter((img) => !img.loading).map(({ url }) => ({ url })),\n    });\n  };\n\n  const { isUserDragging } = useDocumentDragAndDropIntent();\n\n  return (\n    <div className=\"bg-card/75 text-card-foreground/90 relative isolate flex size-full flex-col gap-2 self-end rounded-lg border border-dashed p-2\">\n      {isUserDragging && (\n        <div className={cn(\"flex h-16 items-center rounded-lg\")}>\n          <DragAndDropImageUploader\n            onDrop={handleImagesUpload}\n            disabled={uploadedImages.some((img) => img.loading)}\n          />\n        </div>\n      )}\n      {uploadedImages.length > 0 && !isUserDragging && (\n        <div className={cn(\"relative flex h-16 items-center rounded-lg\")}>\n          <HorizontalScrollArea className=\"w-full\">\n            {uploadedImages.map((img, idx) => (\n              <UploadedImagePreview\n                key={idx}\n                src={img.url}\n                isImageLoading={img.loading}\n                handleImageRemove={() => handleImageRemove(idx)}\n                showPreviewOnHover={false}\n              />\n            ))}\n          </HorizontalScrollArea>\n        </div>\n      )}\n\n      <CustomTextarea\n        onContentChange={setEditJsonContent}\n        onSubmit={handleEditConfirm}\n        disabled={disabled}\n        characterLimit={AI_PROMPT_CHARACTER_LIMIT}\n        onImagesPaste={handleImagesUpload}\n        initialEditorContent={editJsonContent}\n        className=\"min-h-none size-full max-h-[300px] bg-transparent\"\n      />\n\n      <div className=\"@container/form flex items-center justify-between gap-2\">\n        <ImageUploader\n          fileInputRef={fileInputRef}\n          onImagesUpload={handleImagesUpload}\n          onClick={() => fileInputRef.current?.click()}\n          disabled={!canUploadMore}\n        />\n\n        <div className=\"flex items-center gap-2\">\n          <TooltipWrapper label=\"Cancel edit\" asChild>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={onEditCancel}\n              className=\"size-8 shadow-none\"\n            >\n              <X />\n            </Button>\n          </TooltipWrapper>\n\n          <TooltipWrapper label=\"Confirm edit\" asChild>\n            <Button\n              variant=\"secondary\"\n              size=\"sm\"\n              className=\"size-8 shadow-none\"\n              onClick={handleEditConfirm}\n              disabled={isSomeImageUploading || isEmptyPrompt || disabled}\n            >\n              <Check />\n            </Button>\n          </TooltipWrapper>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/message.tsx",
    "content": "import Logo from \"@/assets/logo.svg\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { cn } from \"@/lib/utils\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { AIPromptData, type ChatMessage } from \"@/types/ai\";\nimport { buildAIPromptRender } from \"@/utils/ai/ai-prompt\";\nimport ColorPreview from \"../theme-preview/color-preview\";\nimport { ChatImagePreview } from \"./chat-image-preview\";\nimport { ChatThemePreview } from \"./chat-theme-preview\";\nimport { LoadingLogo } from \"./loading-logo\";\nimport { MessageActions } from \"./message-actions\";\nimport { MessageEditForm } from \"./message-edit-form\";\nimport { StreamText } from \"./stream-text\";\n\ntype MessageProps = {\n  message: ChatMessage;\n  onRetry: () => void;\n  isEditing: boolean;\n  onEdit: () => void;\n  onEditSubmit: (newPromptData: AIPromptData) => void;\n  onEditCancel: () => void;\n  isLastMessageStreaming: boolean;\n  isGeneratingTheme: boolean;\n};\n\nexport default function Message({\n  message,\n  onRetry,\n  isEditing,\n  onEdit,\n  onEditSubmit,\n  onEditCancel,\n  isLastMessageStreaming,\n  isGeneratingTheme,\n}: MessageProps) {\n  const isUser = message.role === \"user\";\n  const isAssistant = message.role === \"assistant\";\n\n  const showMessageActions = !isLastMessageStreaming;\n\n  return (\n    <div className={cn(\"flex w-full items-start gap-4\", isUser ? \"justify-end\" : \"justify-start\")}>\n      <div className={cn(\"flex w-full max-w-[90%] items-start\", isUser ? \"justify-end\" : \"\")}>\n        <div\n          className={cn(\n            \"group/message relative flex w-full flex-col gap-2 wrap-anywhere whitespace-pre-wrap\"\n          )}\n        >\n          {isUser && (\n            <UserMessage\n              message={message}\n              isEditing={isEditing}\n              onRetry={onRetry}\n              onEdit={onEdit}\n              onEditSubmit={onEditSubmit}\n              onEditCancel={onEditCancel}\n              isGeneratingTheme={isGeneratingTheme}\n            />\n          )}\n\n          {isAssistant && (\n            <AssistantMessage message={message} isLastMessageStreaming={isLastMessageStreaming} />\n          )}\n\n          {showMessageActions && (\n            <MessageActions\n              message={message}\n              onRetry={onRetry}\n              onEdit={onEdit}\n              isEditing={isEditing}\n              isGeneratingTheme={isGeneratingTheme}\n            />\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n\ninterface AssistantMessageProps {\n  message: ChatMessage;\n  isLastMessageStreaming: boolean;\n}\n\nfunction AssistantMessage({ message, isLastMessageStreaming }: AssistantMessageProps) {\n  const { themeState } = useEditorStore();\n\n  return (\n    <div className=\"flex items-start gap-3\">\n      {isLastMessageStreaming ? (\n        <div className=\"relative flex size-8 shrink-0 items-center justify-center\">\n          <LoadingLogo />\n        </div>\n      ) : (\n        <div\n          className={cn(\n            \"border-border/40 bg-background shadow-sm relative flex size-8 shrink-0 items-center justify-center rounded-full border select-none\"\n          )}\n        >\n          <Logo className={cn(\"text-foreground size-5\")} />\n        </div>\n      )}\n\n      <div className=\"relative flex w-full flex-col gap-3 mt-1.5\">\n        {message.parts.map((part, idx) => {\n          const { type } = part;\n          const key = `message-${message.id}-part-${idx}`;\n\n          if (type === \"text\") {\n            return (\n              <StreamText\n                key={key}\n                text={part.text}\n                className=\"w-fit text-sm leading-relaxed\"\n                animate={isLastMessageStreaming}\n                markdown\n              />\n            );\n          }\n\n          if (type === \"tool-generateTheme\") {\n            const { state } = part;\n\n            if (state === \"output-available\") {\n              const themeStyles = part.output;\n              return (\n                <ChatThemePreview\n                  key={key}\n                  status=\"complete\"\n                  themeStyles={themeStyles}\n                  className=\"p-0 border-border/40 shadow-sm overflow-hidden rounded-xl\"\n                >\n                  <ScrollArea className=\"h-48\">\n                    <div className=\"p-3\">\n                      <ColorPreview styles={themeStyles} currentMode={themeState.currentMode} />\n                    </div>\n                  </ScrollArea>\n                </ChatThemePreview>\n              );\n            }\n\n            if (state === \"output-error\") {\n              return <ChatThemePreview key={key} status=\"error\" className=\"p-0\" />;\n            }\n\n            return <ChatThemePreview key={key} status=\"loading\" className=\"p-0\" />;\n          }\n        })}\n      </div>\n    </div>\n  );\n}\n\ninterface UserMessageProps {\n  message: ChatMessage;\n  isEditing: boolean;\n  onRetry: () => void;\n  onEdit: () => void;\n  onEditSubmit: (newPromptData: AIPromptData) => void;\n  onEditCancel: () => void;\n  isGeneratingTheme: boolean;\n}\n\nfunction UserMessage({\n  message,\n  isEditing,\n  onEditSubmit,\n  onEditCancel,\n  isGeneratingTheme,\n}: UserMessageProps) {\n  const promptData = message.metadata?.promptData;\n  const shouldDisplayMsgContent = promptData?.content?.trim() != \"\";\n\n  const getDisplayContent = () => {\n    if (promptData) {\n      return buildAIPromptRender(promptData);\n    }\n\n    return message.parts.map((part) => (part.type === \"text\" ? part.text : \"\")).join(\"\");\n  };\n\n  const msgContent = getDisplayContent();\n\n  const getImagesToDisplay = () => {\n    const images = promptData?.images ?? [];\n\n    if (images.length === 1) {\n      return (\n        <div className=\"self-end mb-2\">\n          <ChatImagePreview src={images[0].url} alt=\"Image preview\" className=\"rounded-xl border border-border/20 shadow-sm\" />\n        </div>\n      );\n    } else if (images.length > 1) {\n      return (\n        <div className=\"flex flex-row items-center justify-end gap-2 self-end mb-2\">\n          {images.map((image, idx) => (\n            <div key={idx} className=\"aspect-square size-full max-w-32 flex-1\">\n              <ChatImagePreview\n                className=\"size-full object-cover rounded-xl border border-border/20 shadow-sm\"\n                src={image.url}\n                alt=\"Image preview\"\n              />\n            </div>\n          ))}\n        </div>\n      );\n    }\n\n    return null;\n  };\n\n  const msgImages = getImagesToDisplay();\n\n  if (isEditing) {\n    return (\n      <MessageEditForm\n        key={message.id}\n        message={message}\n        onEditSubmit={onEditSubmit}\n        onEditCancel={onEditCancel}\n        disabled={isGeneratingTheme}\n      />\n    );\n  }\n\n  return (\n    <div className=\"relative flex flex-col items-end gap-1\">\n      {msgImages}\n\n      {shouldDisplayMsgContent && (\n        <div\n          className={cn(\n            \"bg-primary text-primary-foreground w-fit self-end rounded-2xl rounded-tr-sm px-4 py-2.5 text-sm shadow-sm leading-relaxed\"\n          )}\n        >\n          {msgContent}\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/messages.tsx",
    "content": "import Logo from \"@/assets/logo.svg\";\nimport {\n  Conversation,\n  ConversationContent,\n  ConversationScrollButton,\n} from \"@/components/ai-elements/conversation\";\nimport { Loader } from \"@/components/loader\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { useChatContext } from \"@/hooks/use-chat-context\";\nimport { useScrollStartEnd } from \"@/hooks/use-scroll-start-end\";\nimport { cn } from \"@/lib/utils\";\nimport { AIPromptData, type ChatMessage } from \"@/types/ai\";\nimport {\n  filterMessagesToDisplay,\n  getLastAssistantMessage,\n  getUserMessages,\n} from \"@/utils/ai/messages\";\nimport { parseAiSdkTransportError } from \"@/lib/ai/parse-ai-sdk-transport-error\";\nimport { X } from \"lucide-react\";\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { LoadingLogo } from \"./loading-logo\";\nimport Message from \"./message\";\n\ntype ChatMessagesProps = {\n  messages: ChatMessage[];\n  onRetry: (messageIndex: number) => void;\n  onEdit: (messageIndex: number) => void;\n  onEditSubmit: (messageIndex: number, newPromptData: AIPromptData) => void;\n  onEditCancel: () => void;\n  editingMessageIndex?: number | null;\n  isGeneratingTheme: boolean;\n};\n\nexport function Messages({\n  messages,\n  onRetry,\n  onEdit,\n  onEditSubmit,\n  onEditCancel,\n  editingMessageIndex,\n  isGeneratingTheme,\n}: ChatMessagesProps) {\n  const { status, error, clearError } = useChatContext();\n  const { isScrollStart, isScrollEnd, scrollStartRef, scrollEndRef } = useScrollStartEnd();\n\n  const previousUserMsgLength = useRef<number>(\n    messages.filter((message) => message.role === \"user\").length\n  );\n\n  // Scroll to the bottom of the conversation when a new user message is added\n  useEffect(() => {\n    const scrollEndElement = scrollEndRef.current;\n    if (!scrollEndElement) return;\n\n    const currentUserMsgCount = getUserMessages(messages).length;\n    const didUserMsgCountChange = previousUserMsgLength.current !== currentUserMsgCount;\n\n    if (!didUserMsgCountChange && status === \"streaming\") return;\n\n    previousUserMsgLength.current = currentUserMsgCount;\n    requestAnimationFrame(() => {\n      console.log(\"scrolling to end\");\n      scrollEndElement.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\n    });\n  }, [messages, status]);\n\n  const visibleMessages = useMemo(() => filterMessagesToDisplay(messages), [messages]);\n\n  const showLoadingMessage = useMemo(() => {\n    const isSubmitted = status === \"submitted\";\n    const isStreaming = status === \"streaming\";\n    const isError = status === \"error\";\n    const lastAssistantMsgHasText = getLastAssistantMessage(messages)?.parts.some(\n      (part) => part.type === \"text\" && Boolean(part.text)\n    );\n\n    return !isError && (isSubmitted || (isStreaming && !lastAssistantMsgHasText));\n  }, [status, messages]);\n\n  const errorText = useMemo(() => {\n    if (!error) return undefined;\n    const defaultMessage = \"Failed to generate theme. Please try again.\";\n    const normalized = parseAiSdkTransportError(error, defaultMessage);\n    return normalized.message ?? defaultMessage;\n  }, [error]);\n\n  return (\n    <div className=\"relative size-full\">\n      {/* Top fade out effect when scrolling */}\n      <div\n        className={cn(\n          \"via-background/50 from-background pointer-events-none absolute top-0 right-0 left-0 z-20 h-6 bg-gradient-to-b to-transparent opacity-100 transition-opacity ease-out\",\n          isScrollStart ? \"opacity-0\" : \"opacity-100\"\n        )}\n      />\n\n      <Conversation className=\"[&>div]:scrollbar-thin relative size-full overflow-hidden\">\n        <ConversationContent className=\"relative flex w-full flex-col p-4\">\n          <div ref={scrollStartRef} />\n          <div className=\"flex flex-col gap-8 pb-8 wrap-anywhere whitespace-pre-wrap\">\n            {visibleMessages.map((message, index) => {\n              const isLastMessage = index === messages.length - 1;\n              const isStreaming = status === \"submitted\" || status === \"streaming\";\n              const isLastMessageStreaming =\n                message.role === \"assistant\" && isStreaming && isLastMessage;\n              return (\n                <Message\n                  key={message.id}\n                  message={message}\n                  onRetry={() => onRetry(index)}\n                  isEditing={editingMessageIndex === index}\n                  onEdit={() => onEdit(index)}\n                  onEditSubmit={(newPromptData) => onEditSubmit(index, newPromptData)}\n                  onEditCancel={onEditCancel}\n                  isLastMessageStreaming={isLastMessageStreaming}\n                  isGeneratingTheme={isGeneratingTheme}\n                />\n              );\n            })}\n\n            {/* Loading message when AI is generating */}\n            {showLoadingMessage && (\n              <div className=\"flex items-center gap-1.5\">\n                <div className=\"relative flex size-6 items-center justify-center\">\n                  <LoadingLogo />\n                </div>\n\n                <Loader variant=\"dots\" size=\"sm\" />\n              </div>\n            )}\n\n            {/* Error message when generating theme fails */}\n            {status === \"error\" && error && (\n              <div className=\"flex w-[90%] items-start gap-1.5\">\n                <div\n                  className={cn(\n                    \"border-border/50! bg-destructive relative flex size-6 shrink-0 items-center justify-center rounded-full border select-none\"\n                  )}\n                >\n                  <Logo className={cn(\"text-destructive-foreground size-full p-0.5\")} />\n                </div>\n\n                <div\n                  className={cn(\n                    \"bg-destructive/50 text-foreground group/error-banner relative flex w-full gap-2 rounded-lg p-3\"\n                  )}\n                >\n                  <p className=\"text-xs\">{errorText}</p>\n\n                  <TooltipWrapper label=\"Clear error\" asChild>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      className=\"invisible ml-auto size-4 shrink-0 group-hover/error-banner:visible [&>svg]:size-3\"\n                      onClick={clearError}\n                    >\n                      <X />\n                    </Button>\n                  </TooltipWrapper>\n                </div>\n              </div>\n            )}\n          </div>\n          <div ref={scrollEndRef} />\n        </ConversationContent>\n        <ConversationScrollButton />\n      </Conversation>\n\n      {/* Bottom fade out effect when scrolling */}\n      <div\n        className={cn(\n          \"via-background/50 from-background pointer-events-none absolute right-0 bottom-0 left-0 z-20 h-6 bg-gradient-to-t to-transparent opacity-100 transition-opacity ease-out\",\n          isScrollEnd ? \"opacity-0\" : \"opacity-100\"\n        )}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/no-messages-placeholder.tsx",
    "content": "import { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { Button } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Tabs, TabsContent, TabsList } from \"@/components/ui/tabs\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { cn } from \"@/lib/utils\";\nimport { AIPromptData } from \"@/types/ai\";\nimport { createCurrentThemePrompt, createPromptDataFromPreset } from \"@/utils/ai/ai-prompt\";\nimport { CREATE_PROMPTS, REMIX_PROMPTS, VARIANT_PROMPTS } from \"@/utils/ai/prompts\";\nimport { Blend, PaintRoller, WandSparkles } from \"lucide-react\";\nimport { ComponentProps, Fragment } from \"react\";\nimport TabsTriggerPill from \"../theme-preview/tabs-trigger-pill\";\n\nexport function NoMessagesPlaceholder({\n  onGenerateTheme,\n  isGeneratingTheme,\n}: {\n  onGenerateTheme: (promptData: AIPromptData) => void;\n  isGeneratingTheme: boolean;\n}) {\n  const { data: session } = authClient.useSession();\n  const userName = session?.user.name?.split(\" \")[0];\n  const heading = `What can I help you theme${userName ? `, ${userName}` : \"\"}?`;\n\n  return (\n    <div className=\"mx-auto flex max-w-lg flex-col gap-4\">\n      <h2 className=\"text-[clamp(18px,5cqw,28px)] leading-tight font-semibold tracking-tighter text-pretty\">\n        {heading}\n      </h2>\n\n      <Tabs defaultValue=\"create-prompts\">\n        <HorizontalScrollArea className=\"mb-1\">\n          <TabsList className=\"m-0 bg-transparent p-0\">\n            <TabsTriggerPill value=\"create-prompts\" className=\"flex items-center gap-1\">\n              <PaintRoller className=\"size-3.5\" aria-hidden=\"true\" />\n              Create\n            </TabsTriggerPill>\n            <TabsTriggerPill value=\"variant-prompts\" className=\"flex items-center gap-1\">\n              <Blend className=\"size-3.5\" aria-hidden=\"true\" />\n              Remix\n            </TabsTriggerPill>\n            <TabsTriggerPill value=\"tweak-prompts\" className=\"flex items-center gap-1\">\n              <WandSparkles className=\"size-3.5\" aria-hidden=\"true\" />\n              Tweak\n            </TabsTriggerPill>\n          </TabsList>\n        </HorizontalScrollArea>\n\n        <TabsContent value=\"create-prompts\">\n          {CREATE_PROMPTS.map((prompt, index) => (\n            <Fragment key={`create-${index}`}>\n              <PromptButton\n                disabled={isGeneratingTheme}\n                onClick={() =>\n                  onGenerateTheme({\n                    content: prompt.prompt,\n                    mentions: [],\n                  })\n                }\n              >\n                {prompt.displayContent}\n              </PromptButton>\n              {index < CREATE_PROMPTS.length - 1 && <Separator className=\"bg-border/50\" />}\n            </Fragment>\n          ))}\n        </TabsContent>\n\n        <TabsContent value=\"variant-prompts\">\n          {REMIX_PROMPTS.map((prompt, index) => (\n            <Fragment key={`variant-${index}`}>\n              <PromptButton\n                disabled={isGeneratingTheme}\n                onClick={() =>\n                  onGenerateTheme(createPromptDataFromPreset(prompt.prompt, prompt.basePreset))\n                }\n              >\n                {prompt.displayContent}\n              </PromptButton>\n              {index < REMIX_PROMPTS.length - 1 && <Separator className=\"bg-border/50\" />}\n            </Fragment>\n          ))}\n        </TabsContent>\n\n        <TabsContent value=\"tweak-prompts\">\n          {VARIANT_PROMPTS.map((prompt, index) => (\n            <Fragment key={`variant-${index}`}>\n              <PromptButton\n                disabled={isGeneratingTheme}\n                onClick={() => onGenerateTheme(createCurrentThemePrompt({ prompt: prompt.prompt }))}\n              >\n                {prompt.displayContent}\n              </PromptButton>\n              {index < VARIANT_PROMPTS.length - 1 && <Separator className=\"bg-border/50\" />}\n            </Fragment>\n          ))}\n        </TabsContent>\n      </Tabs>\n    </div>\n  );\n}\n\ninterface PromptButtonProps extends ComponentProps<typeof Button> {}\n\nfunction PromptButton({ className, children, ...props }: PromptButtonProps) {\n  return (\n    <Button\n      variant=\"ghost\"\n      className={cn(\"text-muted-foreground w-full justify-start font-normal\", className)}\n      {...props}\n    >\n      <span className=\"truncate\">{children}</span>\n    </Button>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/pill-action-button.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { ComponentProps } from \"react\";\n\nexport interface AIPillActionButtonProps extends ComponentProps<typeof Button> {}\n\nexport function PillActionButton({\n  className,\n  children,\n  disabled,\n  ...props\n}: AIPillActionButtonProps) {\n  return (\n    <div className={cn(\"group/pill relative active:scale-95\", disabled && \"pointer-events-none\")}>\n      <div className=\"from-primary to-background absolute inset-0 z-[-1] rounded-lg bg-gradient-to-br opacity-0 transition-all duration-150 ease-in group-hover/pill:opacity-30\" />\n      <Button\n        variant=\"ghost\"\n        size=\"sm\"\n        className={cn(\n          \"hover:bg-muted/50 text-muted-foreground hover:text-foreground border-border/80! rounded-lg border bg-transparent font-medium text-nowrap whitespace-nowrap backdrop-blur-md transition-all duration-150 ease-in select-none focus:outline-none\",\n          \"group-hover/pill:inset-shadow-primary/50 gap-1.5 inset-shadow-2xs inset-shadow-transparent [&>svg]:size-3\",\n          className\n        )}\n        disabled={disabled}\n        {...props}\n      >\n        {children}\n      </Button>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/ai/stream-text.tsx",
    "content": "import { Response } from \"@/components/ai-elements/response\";\n\ninterface StreamTextProps {\n  text: string;\n  animate?: boolean;\n  markdown?: boolean;\n  className?: string;\n}\n\nexport function StreamText({\n  text,\n  animate = false,\n  markdown = false,\n  className,\n}: StreamTextProps) {\n  if (markdown)\n    return (\n      <Response className={className} isAnimating={animate} animated>\n        {text}\n      </Response>\n    );\n\n  return <span className={className}>{text}</span>;\n}\n"
  },
  {
    "path": "components/editor/ai/uploaded-image-preview.tsx",
    "content": "\"use client\";\n\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from \"@/components/ui/hover-card\";\nimport { cn } from \"@/lib/utils\";\nimport { Loader, X } from \"lucide-react\";\nimport Image from \"next/image\";\n\ninterface ImagePreviewProps {\n  src: string;\n  isImageLoading: boolean;\n  handleImageRemove: () => void;\n  showPreviewOnHover?: boolean;\n}\n\nexport function UploadedImagePreview({\n  src,\n  isImageLoading,\n  handleImageRemove,\n  showPreviewOnHover = true,\n}: ImagePreviewProps) {\n  if (isImageLoading) {\n    return (\n      <div className=\"bg-muted flex size-14 items-center justify-center rounded-md border\">\n        <Loader className=\"text-muted-foreground size-4 animate-spin\" />\n      </div>\n    );\n  }\n\n  return (\n    <HoverCard openDelay={150} closeDelay={150}>\n      <HoverCardTrigger asChild>\n        <div\n          className={cn(\n            \"group/preview animate-in fade-in-0 relative size-14 shrink-0 rounded-md border p-0.5 transition-all\",\n            \"hover:bg-accent\"\n          )}\n        >\n          <Image\n            width={40}\n            height={40}\n            src={src}\n            alt=\"Image preview\"\n            className=\"size-full rounded-sm object-cover\"\n          />\n\n          <TooltipWrapper label=\"Remove image\" asChild>\n            <Button\n              variant=\"destructive\"\n              size=\"icon\"\n              className={cn(\"absolute top-1 right-1 size-4 rounded-full transition-all\")}\n              onClick={handleImageRemove}\n            >\n              <X className=\"size-3!\" />\n            </Button>\n          </TooltipWrapper>\n        </div>\n      </HoverCardTrigger>\n\n      {showPreviewOnHover && (\n        <HoverCardContent className=\"size-fit overflow-hidden p-0\" align=\"center\" side=\"top\">\n          <div className=\"size-full overflow-hidden\">\n            <Image\n              width={300}\n              height={300}\n              src={src}\n              alt=\"Image preview\"\n              className=\"h-auto max-h-[300px] w-auto max-w-[300px] object-contain\"\n            />\n          </div>\n        </HoverCardContent>\n      )}\n    </HoverCard>\n  );\n}\n"
  },
  {
    "path": "components/editor/code-panel-dialog.tsx",
    "content": "import CodePanel from \"./code-panel\";\nimport { ThemeEditorState } from \"@/types/editor\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n} from \"@/components/ui/revola\";\n\ninterface CodePanelDialogProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  themeEditorState: ThemeEditorState;\n  themeId?: string;\n}\n\nexport function CodePanelDialog({ open, onOpenChange, themeEditorState, themeId }: CodePanelDialogProps) {\n  return (\n    <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n      <ResponsiveDialogContent className=\"h-[90dvh] max-h-[90dvh] overflow-hidden shadow-lg sm:h-[80dvh] sm:max-h-[min(700px,90dvh)] sm:w-[calc(100%-2rem)] sm:max-w-4xl\">\n        <div className=\"h-full space-y-6 overflow-auto px-6 pb-6 sm:py-6\">\n          <ResponsiveDialogHeader className=\"sr-only\">\n            <ResponsiveDialogTitle>Theme Code</ResponsiveDialogTitle>\n            <ResponsiveDialogDescription>\n              View and copy the code for your theme.\n            </ResponsiveDialogDescription>\n          </ResponsiveDialogHeader>\n          <CodePanel themeEditorState={themeEditorState} themeId={themeId} />\n        </div>\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n}\n"
  },
  {
    "path": "components/editor/code-panel.tsx",
    "content": "import { useMemo, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Copy, Check, Heart } from \"lucide-react\";\nimport { ThemeEditorState } from \"@/types/editor\";\nimport { ScrollArea, ScrollBar } from \"../ui/scroll-area\";\nimport { CodeBlock } from \"@/components/ai-elements/code-block\";\nimport {\n  Tabs,\n  TabsList,\n  TabsTrigger,\n  TabsContent,\n  TabsIndicator,\n} from \"@/components/ui/base-ui-tabs\";\nimport {\n  Select,\n  SelectContent,\n  SelectTrigger,\n  SelectValue,\n  SelectItem,\n} from \"@/components/ui/select\";\nimport { usePostHog } from \"posthog-js/react\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { usePreferencesStore } from \"@/store/preferences-store\";\nimport {\n  generateThemeCode,\n  generateTailwindConfigCode,\n  generateLayoutCode,\n} from \"@/utils/theme-style-generator\";\nimport { useThemePresetStore } from \"@/store/theme-preset-store\";\nimport { useDialogActions } from \"@/hooks/use-dialog-actions\";\nimport { ColorFormat } from \"@/types\";\n\ninterface CodePanelProps {\n  themeEditorState: ThemeEditorState;\n  themeId?: string;\n}\n\nconst CodePanel: React.FC<CodePanelProps> = ({ themeEditorState, themeId }) => {\n  const [registryCopied, setRegistryCopied] = useState(false);\n  const [copied, setCopied] = useState(false);\n  const [activeTab, setActiveTab] = useState<string>(\"index.css\");\n  const posthog = usePostHog();\n  const { handleSaveClick } = useDialogActions();\n\n  const preset = useEditorStore((state) => state.themeState.preset);\n  const colorFormat = usePreferencesStore((state) => state.colorFormat);\n  const tailwindVersion = usePreferencesStore((state) => state.tailwindVersion);\n  const packageManager = usePreferencesStore((state) => state.packageManager);\n  const setColorFormat = usePreferencesStore((state) => state.setColorFormat);\n  const setTailwindVersion = usePreferencesStore((state) => state.setTailwindVersion);\n  const setPackageManager = usePreferencesStore((state) => state.setPackageManager);\n  const hasUnsavedChanges = useEditorStore((state) => state.hasUnsavedChanges);\n\n  const isSavedPreset = useThemePresetStore(\n    (state) => preset && state.getPreset(preset)?.source === \"SAVED\"\n  );\n  const getAvailableColorFormats = usePreferencesStore((state) => state.getAvailableColorFormats);\n\n  const code = generateThemeCode(themeEditorState, colorFormat, tailwindVersion);\n  const configCode = generateTailwindConfigCode(themeEditorState, colorFormat, tailwindVersion);\n  const layoutCode = generateLayoutCode(themeEditorState);\n\n  const getRegistryCommand = (id: string, isSaved: boolean) => {\n    const url = isSaved\n      ? `https://tweakcn.com/r/themes/${id}`\n      : `https://tweakcn.com/r/themes/${id}.json`;\n    switch (packageManager) {\n      case \"pnpm\":\n        return `pnpm dlx shadcn@latest add ${url}`;\n      case \"npm\":\n        return `npx shadcn@latest add ${url}`;\n      case \"yarn\":\n        return `yarn dlx shadcn@latest add ${url}`;\n      case \"bun\":\n        return `bunx shadcn@latest add ${url}`;\n    }\n  };\n\n  const registryId = themeId ?? preset;\n  const isRegistrySaved = !!themeId || !!isSavedPreset;\n\n  const copyRegistryCommand = async () => {\n    try {\n      await navigator.clipboard.writeText(getRegistryCommand(registryId ?? \"default\", isRegistrySaved));\n      setRegistryCopied(true);\n      setTimeout(() => setRegistryCopied(false), 2000);\n      captureCopyEvent(\"COPY_REGISTRY_COMMAND\");\n    } catch (err) {\n      console.error(\"Failed to copy text:\", err);\n    }\n  };\n\n  const captureCopyEvent = (event: string) => {\n    posthog.capture(event, {\n      editorType: \"theme\",\n      preset,\n      colorFormat,\n      tailwindVersion,\n    });\n  };\n\n  const copyToClipboard = async (text: string) => {\n    try {\n      await navigator.clipboard.writeText(text);\n      setCopied(true);\n      setTimeout(() => setCopied(false), 2000);\n      captureCopyEvent(\"COPY_CODE\");\n    } catch (err) {\n      console.error(\"Failed to copy text:\", err);\n    }\n  };\n\n  const showRegistryCommand = useMemo(() => {\n    if (themeId) return true;\n    return preset && preset !== \"default\" && !hasUnsavedChanges();\n  }, [themeId, preset, hasUnsavedChanges]);\n\n  const PackageManagerHeader = ({ actionButton }: { actionButton: React.ReactNode }) => (\n    <div className=\"flex border-b\">\n      {([\"pnpm\", \"npm\", \"yarn\", \"bun\"] as const).map((pm) => (\n        <button\n          key={pm}\n          onClick={() => setPackageManager(pm)}\n          className={`px-3 py-1.5 text-sm font-medium ${\n            packageManager === pm\n              ? \"bg-muted text-foreground\"\n              : \"text-muted-foreground hover:text-foreground\"\n          }`}\n        >\n          {pm}\n        </button>\n      ))}\n      {actionButton}\n    </div>\n  );\n\n  return (\n    <div className=\"flex h-full flex-col\">\n      <div className=\"mb-4 flex-none\">\n        <div className=\"flex items-center justify-between gap-2\">\n          <h2 className=\"text-lg font-semibold\">Theme Code</h2>\n        </div>\n        <div className=\"mt-4 overflow-hidden rounded-md border\">\n          <PackageManagerHeader\n            actionButton={\n              showRegistryCommand ? (\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  onClick={copyRegistryCommand}\n                  className=\"ml-auto h-8\"\n                  aria-label={registryCopied ? \"Copied to clipboard\" : \"Copy to clipboard\"}\n                >\n                  {registryCopied ? <Check className=\"size-4\" /> : <Copy className=\"size-4\" />}\n                </Button>\n              ) : (\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  onClick={() => handleSaveClick()}\n                  className=\"ml-auto h-8 gap-1\"\n                  aria-label=\"Save theme\"\n                >\n                  <Heart className=\"size-4\" />\n                  <span className=\"sr-only sm:not-sr-only\">Save</span>\n                </Button>\n              )\n            }\n          />\n          <div className=\"bg-muted/50 flex items-center justify-between p-2\">\n            {showRegistryCommand ? (\n              <ScrollArea className=\"w-full\">\n                <div className=\"overflow-y-hidden pb-2 whitespace-nowrap\">\n                  <code className=\"font-mono text-sm\">{getRegistryCommand(registryId as string, isRegistrySaved)}</code>\n                </div>\n                <ScrollBar orientation=\"horizontal\" />\n              </ScrollArea>\n            ) : (\n              <div className=\"text-muted-foreground text-sm\">\n                Save your theme to get the registry command\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n      <div className=\"mb-4 flex items-center gap-2\">\n        <Select\n          value={tailwindVersion}\n          onValueChange={(value: \"3\" | \"4\") => {\n            setTailwindVersion(value);\n            if (value === \"4\" && colorFormat === \"hsl\") {\n              setColorFormat(\"oklch\");\n            }\n            if (activeTab === \"tailwind.config.ts\") {\n              setActiveTab(\"index.css\");\n            }\n          }}\n        >\n          <SelectTrigger className=\"bg-muted/50 w-fit gap-1 border-none outline-hidden focus:border-none focus:ring-transparent\">\n            <SelectValue className=\"focus:ring-transparent\" />\n          </SelectTrigger>\n          <SelectContent className=\"z-99999\">\n            <SelectItem value=\"3\">Tailwind v3</SelectItem>\n            <SelectItem value=\"4\">Tailwind v4</SelectItem>\n          </SelectContent>\n        </Select>\n        <Select value={colorFormat} onValueChange={(value: ColorFormat) => setColorFormat(value)}>\n          <SelectTrigger className=\"bg-muted/50 w-fit gap-1 border-none outline-hidden focus:border-none focus:ring-transparent\">\n            <SelectValue className=\"focus:ring-transparent\" />\n          </SelectTrigger>\n          <SelectContent className=\"z-99999\">\n            {getAvailableColorFormats().map((colorFormat) => (\n              <SelectItem key={colorFormat} value={colorFormat}>\n                {colorFormat}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n      </div>\n      <Tabs\n        value={activeTab}\n        onValueChange={setActiveTab}\n        defaultValue=\"index.css\"\n        className=\"flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border\"\n      >\n        <div className=\"bg-muted/50 flex flex-none items-center justify-between border-b px-4 py-2\">\n          <TabsList className=\"h-8 bg-transparent p-0\">\n            <TabsTrigger value=\"index.css\" className=\"h-7 px-3 text-sm font-medium\">\n              index.css\n            </TabsTrigger>\n            {tailwindVersion === \"3\" && (\n              <TabsTrigger value=\"tailwind.config.ts\" className=\"h-7 px-3 text-sm font-medium\">\n                tailwind.config.ts\n              </TabsTrigger>\n            )}\n            <TabsTrigger value=\"layout.tsx\" className=\"h-7 px-3 text-sm font-medium\">\n              layout.tsx (Next.js)\n            </TabsTrigger>\n            <TabsIndicator className=\"bg-background rounded-sm\" />\n          </TabsList>\n\n          <div className=\"flex items-center gap-2\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() =>\n                copyToClipboard(\n                  activeTab === \"index.css\"\n                    ? code\n                    : activeTab === \"layout.tsx\"\n                      ? layoutCode\n                      : configCode\n                )\n              }\n              className=\"h-8\"\n              aria-label={copied ? \"Copied to clipboard\" : \"Copy to clipboard\"}\n            >\n              {copied ? (\n                <>\n                  <Check className=\"size-4\" />\n                  <span className=\"sr-only md:not-sr-only\">Copied</span>\n                </>\n              ) : (\n                <>\n                  <Copy className=\"size-4\" />\n                  <span className=\"sr-only md:not-sr-only\">Copy</span>\n                </>\n              )}\n            </Button>\n          </div>\n        </div>\n\n        <TabsContent value=\"index.css\" className=\"overflow-hidden\">\n          <ScrollArea className=\"relative h-full\">\n            <CodeBlock code={code} language=\"css\" className=\"h-full rounded-none border-0\" />\n            <ScrollBar orientation=\"horizontal\" />\n            <ScrollBar orientation=\"vertical\" />\n          </ScrollArea>\n        </TabsContent>\n\n        {tailwindVersion === \"3\" && (\n          <TabsContent value=\"tailwind.config.ts\" className=\"overflow-hidden\">\n            <ScrollArea className=\"relative h-full\">\n              <CodeBlock\n                code={configCode}\n                language=\"typescript\"\n                className=\"h-full rounded-none border-0\"\n              />\n              <ScrollBar orientation=\"horizontal\" />\n              <ScrollBar orientation=\"vertical\" />\n            </ScrollArea>\n          </TabsContent>\n        )}\n\n        <TabsContent value=\"layout.tsx\" className=\"overflow-hidden\">\n          <ScrollArea className=\"relative h-full\">\n            <CodeBlock\n              code={layoutCode}\n              language=\"tsx\"\n              className=\"h-full rounded-none border-0\"\n            />\n            <ScrollBar orientation=\"horizontal\" />\n            <ScrollBar orientation=\"vertical\" />\n          </ScrollArea>\n        </TabsContent>\n      </Tabs>\n    </div>\n  );\n};\n\nexport default CodePanel;\n"
  },
  {
    "path": "components/editor/color-picker.tsx",
    "content": "import { DEBOUNCE_DELAY } from \"@/lib/constants\";\nimport { cn } from \"@/lib/utils\";\nimport { useColorControlFocus } from \"@/store/color-control-focus-store\";\nimport { ColorPickerProps } from \"@/types\";\nimport { debounce } from \"@/utils/debounce\";\nimport React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from \"react\";\nimport { ColorSelectorPopover } from \"./color-selector-popover\";\nimport { SectionContext } from \"./section-context\";\n\nconst ColorPicker = ({ color, onChange, label, name }: ColorPickerProps) => {\n  const [isOpen, setIsOpen] = useState(false);\n  const [shouldAnimate, setShouldAnimate] = useState(false);\n  const rootRef = useRef<HTMLDivElement>(null);\n  const textInputRef = useRef<HTMLInputElement>(null);\n  const animationTimerRef = useRef<NodeJS.Timeout | null>(null);\n  const sectionCtx = useContext(SectionContext);\n  const { registerColor, unregisterColor, highlightTarget } = useColorControlFocus();\n\n  useEffect(() => {\n    if (!name) return;\n    registerColor(name, rootRef.current);\n    return () => unregisterColor(name);\n  }, [name, registerColor, unregisterColor]);\n\n  useEffect(() => {\n    if (textInputRef.current) {\n      textInputRef.current.value = color;\n    }\n  }, [color]);\n\n  const debouncedOnChange = useMemo(\n    () =>\n      debounce((value: string) => {\n        onChange(value);\n      }, DEBOUNCE_DELAY),\n    [onChange]\n  );\n\n  const handleColorChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const newColor = e.target.value;\n      debouncedOnChange(newColor);\n    },\n    [debouncedOnChange]\n  );\n\n  const handleTextInputChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const colorString = e.target.value;\n      debouncedOnChange(colorString);\n    },\n    [debouncedOnChange]\n  );\n\n  useEffect(() => {\n    return () => debouncedOnChange.cancel();\n  }, [debouncedOnChange]);\n\n  const isHighlighted = name && highlightTarget === name;\n\n  useEffect(() => {\n    if (animationTimerRef.current) {\n      clearTimeout(animationTimerRef.current);\n      animationTimerRef.current = null;\n    }\n\n    if (isHighlighted) {\n      setShouldAnimate(true);\n      sectionCtx?.setIsExpanded(true);\n\n      setTimeout(\n        () => {\n          rootRef.current?.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n        },\n        sectionCtx?.isExpanded ? 0 : 100\n      );\n\n      animationTimerRef.current = setTimeout(() => {\n        setShouldAnimate(false);\n        animationTimerRef.current = null;\n      }, 1500);\n    } else {\n      setShouldAnimate(false);\n    }\n\n    return () => {\n      if (animationTimerRef.current) {\n        clearTimeout(animationTimerRef.current);\n        animationTimerRef.current = null;\n      }\n    };\n  }, [isHighlighted, sectionCtx]);\n\n  return (\n    <div\n      ref={rootRef}\n      className={cn(\n        \"group hover:bg-muted/50 -mx-1 flex items-center gap-2.5 rounded-lg px-2 py-0.5 transition-all duration-200\",\n        shouldAnimate && \"bg-muted ring-primary ring-2\"\n      )}\n    >\n      <div\n        className=\"relative flex size-7 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-md border shadow-sm\"\n        style={{ backgroundColor: color }}\n        onClick={() => setIsOpen(!isOpen)}\n      >\n        <input\n          type=\"color\"\n          id={`color-${label.replace(/\\s+/g, \"-\").toLowerCase()}`}\n          value={color}\n          onChange={handleColorChange}\n          className=\"absolute inset-0 h-full w-full cursor-pointer opacity-0\"\n        />\n      </div>\n\n      <span className=\"text-foreground min-w-0 shrink-0 text-[13px] font-medium\">{label}</span>\n\n      <div className=\"flex min-w-0 flex-1 items-center justify-end gap-1\">\n        <input\n          ref={textInputRef}\n          type=\"text\"\n          defaultValue={color}\n          onChange={handleTextInputChange}\n          className=\"bg-muted/50 text-muted-foreground focus:text-foreground focus:border-ring h-7 w-full min-w-0 rounded border px-2 text-xs font-mono transition-colors outline-none\"\n          placeholder=\"hex or tailwind\"\n        />\n        <ColorSelectorPopover currentColor={color} onChange={onChange} />\n      </div>\n    </div>\n  );\n};\n\nexport default ColorPicker;\n"
  },
  {
    "path": "components/editor/color-selector-popover.tsx",
    "content": "\"use client\";\n\nimport TailwindCSS from \"@/components/icons/tailwind-css\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n} from \"@/components/ui/command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { cn } from \"@/lib/utils\";\nimport { ColorSelectorTab, usePreferencesStore } from \"@/store/preferences-store\";\nimport { TAILWIND_PALETTE } from \"@/utils/registry/tailwind-colors\";\nimport { Check, LayoutGrid, List } from \"lucide-react\";\nimport { useCallback } from \"react\";\nimport { formatHex, parse } from \"culori\";\nimport { Separator } from \"../ui/separator\";\n\ntype ColorSelectorPopoverProps = {\n  currentColor: string;\n  onChange: (color: string) => void;\n};\n\nexport function ColorSelectorPopover({ currentColor, onChange }: ColorSelectorPopoverProps) {\n  const handleColorSelect = useCallback(\n    (color: string) => {\n      onChange(color);\n    },\n    [onChange]\n  );\n\n  const { setColorSelectorTab, colorSelectorTab } = usePreferencesStore();\n\n  const handleTabChange = useCallback(\n    (value: string) => {\n      setColorSelectorTab(value as ColorSelectorTab);\n    },\n    [setColorSelectorTab]\n  );\n\n  const toHex = (c: string) => formatHex(parse(c));\n  const isColorSelected = useCallback(\n    (color: string) => {\n      try {\n        return toHex(currentColor) === toHex(color);\n      } catch {\n        return currentColor === color;\n      }\n    },\n    [currentColor]\n  );\n\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <TooltipWrapper asChild label=\"Tailwind Colors\">\n          <Button\n            variant=\"ghost\"\n            size=\"sm\"\n            className=\"group bg-input/25 size-7 rounded border shadow-none\"\n          >\n            <TailwindCSS className=\"text-foreground group-hover:text-accent-foreground size-4 transition-colors\" />\n          </Button>\n        </TooltipWrapper>\n      </PopoverTrigger>\n\n      <PopoverContent className=\"size-auto gap-0 overflow-hidden p-0\" align=\"end\">\n        <Tabs defaultValue={colorSelectorTab} onValueChange={handleTabChange}>\n          <div className=\"flex items-center justify-between gap-4\">\n            <div className=\"ml-2 flex items-center gap-1.5\">\n              <TailwindCSS className=\"size-4\" />\n              <span className=\"text-muted-foreground text-sm tabular-nums\">Tailwind v4</span>\n            </div>\n\n            <TabsList className=\"bg-transparent\">\n              <TabsTrigger\n                value=\"list\"\n                className=\"data-[state=active]:bg-input/25 size-8 p-0 data-[state=active]:shadow-none\"\n              >\n                <List className=\"size-4\" />\n              </TabsTrigger>\n              <TabsTrigger\n                value=\"palette\"\n                className=\"data-[state=active]:bg-input/25 size-8 p-0 data-[state=active]:shadow-none\"\n              >\n                <LayoutGrid className=\"size-4\" />\n              </TabsTrigger>\n            </TabsList>\n          </div>\n          <Separator />\n\n          <TabsContent value=\"list\" className=\"my-0 min-w-[300px]\">\n            <Command className=\"flex h-84 flex-col\">\n              <CommandInput className=\"h-10\" placeholder=\"Search Tailwind colors...\" />\n              <ScrollArea className=\"flex-1 overflow-hidden\">\n                <CommandEmpty className=\"text-muted-foreground p-4 text-center\">\n                  No Tailwind color found.\n                </CommandEmpty>\n\n                {Object.entries(TAILWIND_PALETTE).map(([key, colors]) => {\n                  const colorName = key.charAt(0).toUpperCase() + key.slice(1);\n                  return (\n                    <CommandGroup heading={colorName} key={key}>\n                      {Object.entries(colors).map(([shade, color]) => {\n                        const isSelected = isColorSelected(color);\n\n                        return (\n                          <CommandItem\n                            key={color}\n                            onSelect={() => handleColorSelect(color)}\n                            className=\"flex items-center gap-2\"\n                          >\n                            <ColorSwatch\n                              color={color}\n                              name={shade === \"DEFAULT\" ? key : `${key}-${shade}`}\n                              isSelected={isSelected}\n                              size=\"md\"\n                            />\n                            <span>{shade === \"DEFAULT\" ? key : `${key}-${shade}`}</span>\n                            {isSelected && <Check className=\"ml-auto size-4 opacity-70\" />}\n                          </CommandItem>\n                        );\n                      })}\n                    </CommandGroup>\n                  );\n                })}\n              </ScrollArea>\n            </Command>\n          </TabsContent>\n\n          <TabsContent value=\"palette\" className=\"my-0 w-full\">\n            <ScrollArea className=\"h-84 w-full\">\n              <div className=\"flex flex-col gap-0.5 p-1\">\n                {Object.entries(TAILWIND_PALETTE).map(([key, colors]) => {\n                  return (\n                    <div key={key} className=\"flex gap-0.5\">\n                      {Object.entries(colors).map(([shade, color]) => {\n                        return (\n                          <ColorSwatch\n                            key={`${key}-${shade}`}\n                            name={shade === \"DEFAULT\" ? key : `${key}-${shade}`}\n                            color={color}\n                            isSelected={isColorSelected(color)}\n                            onClick={() => handleColorSelect(color)}\n                            className=\"rounded-none\"\n                            size=\"md\"\n                          />\n                        );\n                      })}\n                    </div>\n                  );\n                })}\n              </div>\n            </ScrollArea>\n          </TabsContent>\n        </Tabs>\n      </PopoverContent>\n    </Popover>\n  );\n}\n\ninterface ColorSwatchProps extends React.HTMLAttributes<HTMLButtonElement> {\n  isSelected: boolean;\n  color: string;\n  name: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}\n\nfunction ColorSwatch({\n  color,\n  name,\n  className,\n  isSelected,\n  size = \"sm\",\n  ...props\n}: ColorSwatchProps) {\n  const sizeClasses = {\n    sm: \"size-5\",\n    md: \"size-6\",\n    lg: \"size-8\",\n  };\n\n  const isTransparent = color === \"transparent\";\n\n  return (\n    <button\n      aria-label={`Select color ${name}`}\n      title={name}\n      className={cn(\n        \"group relative cursor-pointer rounded-md border transition-all hover:z-10 hover:scale-110 hover:shadow-lg\",\n        isTransparent\n          ? \"[background-image:linear-gradient(45deg,#ccc_25%,transparent_25%),linear-gradient(-45deg,#ccc_25%,transparent_25%),linear-gradient(45deg,transparent_75%,#ccc_75%),linear-gradient(-45deg,transparent_75%,#ccc_75%)] [background-size:8px_8px] [background-position:0_0,0_4px,4px_-4px,-4px_0px]\"\n          : \"bg-(--color)\",\n        sizeClasses[size],\n        isSelected && (isTransparent ? \"ring-2 ring-border\" : \"ring-2 ring-(--color)\"),\n        className\n      )}\n      style={!isTransparent ? ({ \"--color\": color } as React.CSSProperties) : undefined}\n      {...props}\n    >\n      <div className=\"group-hover:ring-foreground/50 absolute inset-0 rounded-[inherit] ring-2 ring-transparent transition-all duration-200\" />\n    </button>\n  );\n}"
  },
  {
    "path": "components/editor/colors-tab-content.tsx",
    "content": "\"use client\";\n\nimport React, { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { RefreshCw, Search, X } from \"lucide-react\";\nimport ColorPicker from \"@/components/editor/color-picker\";\nimport ControlSection from \"@/components/editor/control-section\";\nimport { Button } from \"@/components/ui/button\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { cn } from \"@/lib/utils\";\nimport { FocusColorId } from \"@/store/color-control-focus-store\";\nimport { ThemeStyleProps } from \"@/types/theme\";\n\ntype ColorEntry = {\n  key: keyof ThemeStyleProps;\n  name: FocusColorId;\n  label: string;\n};\n\ntype ColorGroup = {\n  title: string;\n  expanded?: boolean;\n  colors: ColorEntry[];\n};\n\nconst COLOR_GROUPS: ColorGroup[] = [\n  {\n    title: \"Primary\",\n    expanded: true,\n    colors: [\n      { key: \"primary\", name: \"primary\", label: \"Background\" },\n      { key: \"primary-foreground\", name: \"primary-foreground\", label: \"Foreground\" },\n    ],\n  },\n  {\n    title: \"Secondary\",\n    expanded: true,\n    colors: [\n      { key: \"secondary\", name: \"secondary\", label: \"Background\" },\n      { key: \"secondary-foreground\", name: \"secondary-foreground\", label: \"Foreground\" },\n    ],\n  },\n  {\n    title: \"Accent\",\n    colors: [\n      { key: \"accent\", name: \"accent\", label: \"Background\" },\n      { key: \"accent-foreground\", name: \"accent-foreground\", label: \"Foreground\" },\n    ],\n  },\n  {\n    title: \"Base\",\n    colors: [\n      { key: \"background\", name: \"background\", label: \"Background\" },\n      { key: \"foreground\", name: \"foreground\", label: \"Foreground\" },\n    ],\n  },\n  {\n    title: \"Card\",\n    colors: [\n      { key: \"card\", name: \"card\", label: \"Background\" },\n      { key: \"card-foreground\", name: \"card-foreground\", label: \"Foreground\" },\n    ],\n  },\n  {\n    title: \"Popover\",\n    colors: [\n      { key: \"popover\", name: \"popover\", label: \"Background\" },\n      { key: \"popover-foreground\", name: \"popover-foreground\", label: \"Foreground\" },\n    ],\n  },\n  {\n    title: \"Muted\",\n    colors: [\n      { key: \"muted\", name: \"muted\", label: \"Background\" },\n      { key: \"muted-foreground\", name: \"muted-foreground\", label: \"Foreground\" },\n    ],\n  },\n  {\n    title: \"Destructive\",\n    colors: [\n      { key: \"destructive\", name: \"destructive\", label: \"Background\" },\n      { key: \"destructive-foreground\", name: \"destructive-foreground\", label: \"Foreground\" },\n    ],\n  },\n  {\n    title: \"Border & Input\",\n    colors: [\n      { key: \"border\", name: \"border\", label: \"Border\" },\n      { key: \"input\", name: \"input\", label: \"Input\" },\n      { key: \"ring\", name: \"ring\", label: \"Ring\" },\n    ],\n  },\n  {\n    title: \"Chart\",\n    colors: [\n      { key: \"chart-1\", name: \"chart-1\", label: \"Chart 1\" },\n      { key: \"chart-2\", name: \"chart-2\", label: \"Chart 2\" },\n      { key: \"chart-3\", name: \"chart-3\", label: \"Chart 3\" },\n      { key: \"chart-4\", name: \"chart-4\", label: \"Chart 4\" },\n      { key: \"chart-5\", name: \"chart-5\", label: \"Chart 5\" },\n    ],\n  },\n  {\n    title: \"Sidebar\",\n    colors: [\n      { key: \"sidebar\", name: \"sidebar\", label: \"Background\" },\n      { key: \"sidebar-foreground\", name: \"sidebar-foreground\", label: \"Foreground\" },\n      { key: \"sidebar-primary\", name: \"sidebar-primary\", label: \"Primary\" },\n      { key: \"sidebar-primary-foreground\", name: \"sidebar-primary-foreground\", label: \"Primary FG\" },\n      { key: \"sidebar-accent\", name: \"sidebar-accent\", label: \"Accent\" },\n      { key: \"sidebar-accent-foreground\", name: \"sidebar-accent-foreground\", label: \"Accent FG\" },\n      { key: \"sidebar-border\", name: \"sidebar-border\", label: \"Border\" },\n      { key: \"sidebar-ring\", name: \"sidebar-ring\", label: \"Ring\" },\n    ],\n  },\n];\n\n// Maps sidebar color keys to their base counterparts\nconst SIDEBAR_SYNC_MAP: Partial<Record<keyof ThemeStyleProps, keyof ThemeStyleProps>> = {\n  sidebar: \"background\",\n  \"sidebar-foreground\": \"foreground\",\n  \"sidebar-primary\": \"primary\",\n  \"sidebar-primary-foreground\": \"primary-foreground\",\n  \"sidebar-accent\": \"accent\",\n  \"sidebar-accent-foreground\": \"accent-foreground\",\n  \"sidebar-border\": \"border\",\n  \"sidebar-ring\": \"ring\",\n};\n\n// Reverse map: base key → sidebar key\nconst BASE_TO_SIDEBAR_MAP = Object.fromEntries(\n  Object.entries(SIDEBAR_SYNC_MAP).map(([sidebar, base]) => [base, sidebar])\n) as Partial<Record<keyof ThemeStyleProps, keyof ThemeStyleProps>>;\n\ninterface ColorsTabContentProps {\n  currentStyles: ThemeStyleProps;\n  updateStyle: <K extends keyof ThemeStyleProps>(key: K, value: ThemeStyleProps[K]) => void;\n  updateStyles: (updates: Partial<ThemeStyleProps>) => void;\n}\n\nexport function ColorsTabContent({ currentStyles, updateStyle, updateStyles }: ColorsTabContentProps) {\n  const [search, setSearch] = useState(\"\");\n  const [sidebarSyncEnabled, setSidebarSyncEnabled] = useState(false);\n\n  // Sync all sidebar colors to their base counterparts in a single batch\n  const syncSidebarToBase = useCallback(() => {\n    const updates: Partial<ThemeStyleProps> = {};\n    for (const [sidebarKey, baseKey] of Object.entries(SIDEBAR_SYNC_MAP)) {\n      const baseValue = currentStyles[baseKey as keyof ThemeStyleProps];\n      if (baseValue !== undefined) {\n        (updates as Record<string, unknown>)[sidebarKey] = baseValue;\n      }\n    }\n    updateStyles(updates);\n  }, [currentStyles, updateStyles]);\n\n  const toggleSidebarSync = useCallback(() => {\n    setSidebarSyncEnabled((prev) => !prev);\n  }, []);\n\n  // Sync sidebar colors when toggle is turned on\n  useEffect(() => {\n    if (sidebarSyncEnabled) {\n      syncSidebarToBase();\n    }\n    // Only run when sync is toggled on, not when syncSidebarToBase changes\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [sidebarSyncEnabled]);\n\n  // Keep sidebar in sync when base colors change while sync is enabled\n  const wrappedUpdateStyle = useCallback(\n    <K extends keyof ThemeStyleProps>(key: K, value: ThemeStyleProps[K]) => {\n      // If sync is on and a base color changed, batch both updates together\n      if (sidebarSyncEnabled && key in BASE_TO_SIDEBAR_MAP) {\n        const sidebarKey = BASE_TO_SIDEBAR_MAP[key]!;\n        updateStyles({\n          [key]: value,\n          [sidebarKey]: value,\n        } as Partial<ThemeStyleProps>);\n      } else {\n        updateStyle(key, value);\n      }\n    },\n    [updateStyle, updateStyles, sidebarSyncEnabled]\n  );\n\n  const filteredGroups = useMemo(() => {\n    if (!search.trim()) return COLOR_GROUPS;\n\n    const query = search.toLowerCase();\n    return COLOR_GROUPS.map((group) => ({\n      ...group,\n      expanded: true,\n      colors: group.colors.filter(\n        (c) =>\n          c.label.toLowerCase().includes(query) ||\n          c.name.toLowerCase().includes(query) ||\n          group.title.toLowerCase().includes(query)\n      ),\n    })).filter((group) => group.colors.length > 0);\n  }, [search]);\n\n  return (\n    <div className=\"flex min-h-0 h-full flex-col\">\n      <div className=\"px-4 pb-3\">\n        <div className=\"bg-muted/50 flex items-center gap-2.5 rounded-lg border px-3\">\n          <Search className=\"text-muted-foreground size-4 shrink-0\" />\n          <input\n            type=\"text\"\n            value={search}\n            onChange={(e) => setSearch(e.target.value)}\n            placeholder=\"Search colors...\"\n            className=\"text-foreground placeholder:text-muted-foreground h-9 min-w-0 flex-1 bg-transparent text-sm outline-none\"\n          />\n          {search && (\n            <button\n              onClick={() => setSearch(\"\")}\n              className=\"text-muted-foreground hover:text-foreground shrink-0 transition-colors\"\n            >\n              <X className=\"size-4\" />\n            </button>\n          )}\n        </div>\n      </div>\n\n      <ScrollArea className=\"min-h-0 flex-1 px-4\">\n        {filteredGroups.length === 0 && (\n          <p className=\"text-muted-foreground py-8 text-center text-xs\">No colors found</p>\n        )}\n        {filteredGroups.map((group) => (\n          <ControlSection\n            key={group.title}\n            title={group.title}\n            expanded={group.expanded}\n            headerAction={\n              group.title === \"Sidebar\" ? (\n                <TooltipWrapper\n                  label=\"Sync sidebar colors to their base counterparts (e.g. sidebar background → background)\"\n                  asChild\n                >\n                  <Button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    onClick={toggleSidebarSync}\n                    className={cn(\n                      \"group h-auto px-1.5 py-0.5 text-[11px]\",\n                      sidebarSyncEnabled\n                        ? \"bg-accent text-accent-foreground\"\n                        : \"text-muted-foreground\"\n                    )}\n                  >\n                    <RefreshCw\n                      className={cn(\n                        \"size-3 transition-all group-hover:scale-110\",\n                        sidebarSyncEnabled && \"animate-none\"\n                      )}\n                    />\n                    <span className=\"uppercase tracking-wider\">\n                      {sidebarSyncEnabled ? \"Sync on\" : \"Sync\"}\n                    </span>\n                  </Button>\n                </TooltipWrapper>\n              ) : undefined\n            }\n          >\n            {group.colors.map((color) => (\n              <ColorPicker\n                key={color.name}\n                name={color.name}\n                color={currentStyles[color.key] as string}\n                onChange={(value) => wrappedUpdateStyle(color.key, value as ThemeStyleProps[typeof color.key])}\n                label={color.label}\n              />\n            ))}\n          </ControlSection>\n        ))}\n      </ScrollArea>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/contrast-checker.tsx",
    "content": "import { useTheme } from \"@/components/theme-provider\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n  ResponsiveDialogTrigger,\n} from \"@/components/ui/revola\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\nimport { ThemeStyleProps } from \"@/types/theme\";\nimport { AlertTriangle, Check, Contrast, Moon, Sun } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { useContrastChecker } from \"../../hooks/use-contrast-checker\";\nimport { Button } from \"../ui/button\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../ui/tooltip\";\n\ntype ContrastCheckerProps = {\n  currentStyles: ThemeStyleProps;\n};\n\nconst MIN_CONTRAST_RATIO = 4.5;\n\ntype ColorCategory = \"content\" | \"interactive\" | \"functional\";\n\ntype ColorPair = {\n  id: string;\n  foregroundId: keyof ThemeStyleProps;\n  backgroundId: keyof ThemeStyleProps;\n  foreground: string | undefined;\n  background: string | undefined;\n  label: string;\n  category: ColorCategory;\n};\n\nconst ContrastChecker = ({ currentStyles }: ContrastCheckerProps) => {\n  const [filter, setFilter] = useState<\"all\" | \"issues\">(\"all\");\n  const { theme, toggleTheme } = useTheme();\n\n  const colorPairsToCheck: ColorPair[] = [\n    // Content - Base, background, cards, containers\n    {\n      id: \"base\",\n      foregroundId: \"foreground\",\n      backgroundId: \"background\",\n      foreground: currentStyles?.[\"foreground\"],\n      background: currentStyles?.[\"background\"],\n      label: \"Base\",\n      category: \"content\",\n    },\n    {\n      id: \"card\",\n      foregroundId: \"card-foreground\",\n      backgroundId: \"card\",\n      foreground: currentStyles?.[\"card-foreground\"],\n      background: currentStyles?.[\"card\"],\n      label: \"Card\",\n      category: \"content\",\n    },\n    {\n      id: \"popover\",\n      foregroundId: \"popover-foreground\",\n      backgroundId: \"popover\",\n      foreground: currentStyles?.[\"popover-foreground\"],\n      background: currentStyles?.[\"popover\"],\n      label: \"Popover\",\n      category: \"content\",\n    },\n    {\n      id: \"muted\",\n      foregroundId: \"muted-foreground\",\n      backgroundId: \"muted\",\n      foreground: currentStyles?.[\"muted-foreground\"],\n      background: currentStyles?.[\"muted\"],\n      label: \"Muted\",\n      category: \"content\",\n    },\n\n    // Interactive - Buttons, links, actions\n    {\n      id: \"primary\",\n      foregroundId: \"primary-foreground\",\n      backgroundId: \"primary\",\n      foreground: currentStyles?.[\"primary-foreground\"],\n      background: currentStyles?.[\"primary\"],\n      label: \"Primary\",\n      category: \"interactive\",\n    },\n    {\n      id: \"secondary\",\n      foregroundId: \"secondary-foreground\",\n      backgroundId: \"secondary\",\n      foreground: currentStyles?.[\"secondary-foreground\"],\n      background: currentStyles?.[\"secondary\"],\n      label: \"Secondary\",\n      category: \"interactive\",\n    },\n    {\n      id: \"accent\",\n      foregroundId: \"accent-foreground\",\n      backgroundId: \"accent\",\n      foreground: currentStyles?.[\"accent-foreground\"],\n      background: currentStyles?.[\"accent\"],\n      label: \"Accent\",\n      category: \"interactive\",\n    },\n\n    // Functional - Sidebar, destructive, special purposes\n    {\n      id: \"destructive\",\n      foregroundId: \"destructive-foreground\",\n      backgroundId: \"destructive\",\n      foreground: currentStyles?.[\"destructive-foreground\"],\n      background: currentStyles?.[\"destructive\"],\n      label: \"Destructive\",\n      category: \"functional\",\n    },\n    {\n      id: \"sidebar\",\n      foregroundId: \"sidebar-foreground\",\n      backgroundId: \"sidebar\",\n      foreground: currentStyles?.[\"sidebar-foreground\"],\n      background: currentStyles?.[\"sidebar\"],\n      label: \"Sidebar Base\",\n      category: \"functional\",\n    },\n    {\n      id: \"sidebar-primary\",\n      foregroundId: \"sidebar-primary-foreground\",\n      backgroundId: \"sidebar-primary\",\n      foreground: currentStyles?.[\"sidebar-primary-foreground\"],\n      background: currentStyles?.[\"sidebar-primary\"],\n      label: \"Sidebar Primary\",\n      category: \"functional\",\n    },\n    {\n      id: \"sidebar-accent\",\n      foregroundId: \"sidebar-accent-foreground\",\n      backgroundId: \"sidebar-accent\",\n      foreground: currentStyles?.[\"sidebar-accent-foreground\"],\n      background: currentStyles?.[\"sidebar-accent\"],\n      label: \"Sidebar Accent\",\n      category: \"functional\",\n    },\n  ];\n\n  const validColorPairsToCheck = colorPairsToCheck.filter(\n    (pair): pair is ColorPair & { foreground: string; background: string } =>\n      !!pair.foreground && !!pair.background\n  );\n  const contrastResults = useContrastChecker(validColorPairsToCheck);\n\n  const getContrastResult = (pairId: string) => {\n    return contrastResults?.find((res) => res.id === pairId);\n  };\n\n  const totalIssues = contrastResults?.filter(\n    (result) => result.contrastRatio < MIN_CONTRAST_RATIO\n  ).length;\n\n  const filteredPairs =\n    filter === \"all\"\n      ? colorPairsToCheck\n      : colorPairsToCheck.filter((pair) => {\n          const result = getContrastResult(pair.id);\n          return result && result.contrastRatio < MIN_CONTRAST_RATIO;\n        });\n\n  // Group color pairs by category\n  const categoryLabels: Record<ColorCategory, string> = {\n    content: \"Content & Containers\",\n    interactive: \"Interactive Elements\",\n    functional: \"Navigation & Functional\",\n  };\n\n  const categories: ColorCategory[] = [\"content\", \"interactive\", \"functional\"];\n  const groupedPairs = categories\n    .map((category) => ({\n      category,\n      label: categoryLabels[category],\n      pairs: filteredPairs.filter((pair) => pair.category === category),\n    }))\n    .filter((group) => group.pairs.length > 0);\n\n  return (\n    <ResponsiveDialog>\n      <ResponsiveDialogTrigger asChild>\n        <Button variant=\"ghost\" size=\"sm\" className=\"w-full justify-start px-2\">\n          <Contrast className=\"h-4 w-4\" />\n          <span className=\"text-sm\">Contrast</span>\n        </Button>\n      </ResponsiveDialogTrigger>\n      <ResponsiveDialogContent className=\"flex max-h-[95dvh] flex-col gap-0 space-y-6 overflow-hidden shadow-lg sm:max-h-[min(700px,85dvh)] sm:w-[calc(100%-2rem)] sm:max-w-4xl sm:pt-6\">\n        <div className=\"flex flex-col items-end justify-between gap-4 px-6 sm:flex-row\">\n          <ResponsiveDialogHeader className=\"text-left\">\n            <ResponsiveDialogTitle>Contrast Checker</ResponsiveDialogTitle>\n            <ResponsiveDialogDescription>\n              WCAG 2.0 AA requires a contrast ratio of at least {MIN_CONTRAST_RATIO}:1{\" • \"}\n              <a\n                href=\"https://www.w3.org/TR/WCAG21/\"\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                className=\"text-primary hover:text-primary/80 underline transition-colors\"\n              >\n                Learn more\n              </a>\n            </ResponsiveDialogDescription>\n          </ResponsiveDialogHeader>\n\n          <div className=\"hidden items-center gap-2 md:flex\">\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  onClick={(e) => toggleTheme({ x: e.clientX, y: e.clientY })}\n                >\n                  {theme === \"light\" ? (\n                    <Sun className=\"h-3.5 w-3.5\" />\n                  ) : (\n                    <Moon className=\"h-3.5 w-3.5\" />\n                  )}\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent side=\"bottom\">\n                <p className=\"text-xs\">Toggle theme</p>\n              </TooltipContent>\n            </Tooltip>\n            <Button\n              variant={filter === \"all\" ? \"default\" : \"outline\"}\n              size=\"sm\"\n              onClick={() => setFilter(\"all\")}\n            >\n              All\n            </Button>\n            <Button\n              size=\"sm\"\n              disabled={totalIssues === 0}\n              variant={filter === \"issues\" ? \"default\" : \"outline\"}\n              onClick={() => setFilter(\"issues\")}\n            >\n              <AlertTriangle className={cn(\"mr-1 h-3 w-3\")} />\n              Issues ({totalIssues})\n            </Button>\n          </div>\n        </div>\n\n        <ScrollArea className=\"relative flex flex-1 flex-col\">\n          <div className=\"space-y-6 px-6\">\n            {groupedPairs.map((group) => (\n              <div key={group.category} className=\"\">\n                <div className=\"bg-background sticky -top-px z-10 flex items-center gap-2 pb-4 sm:rounded-b-xl\">\n                  <h2 className=\"text-md font-semibold\">{group.label}</h2>\n                  <Separator className=\"flex-1\" />\n                </div>\n\n                <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n                  {group.pairs.map((pair) => {\n                    const result = getContrastResult(pair.id);\n                    const isValid =\n                      result?.contrastRatio !== undefined &&\n                      result?.contrastRatio >= MIN_CONTRAST_RATIO;\n                    const contrastRatio = result?.contrastRatio?.toFixed(2) ?? \"N/A\";\n                    return (\n                      <Card\n                        key={pair.id}\n                        className={cn(\"transition-all duration-200\", !isValid && \"border-dashed\")}\n                      >\n                        <CardContent className=\"p-4\">\n                          <div className=\"mb-3 flex items-center justify-between\">\n                            <h3\n                              className={cn(\n                                \"flex items-center font-medium\",\n                                !isValid && \"text-destructive\"\n                              )}\n                            >\n                              {pair.label}\n                              {!isValid && <AlertTriangle className=\"ml-1 size-3.5\" />}\n                            </h3>\n                            <Badge\n                              variant={isValid ? \"default\" : \"destructive\"}\n                              className={cn(\n                                \"flex items-center gap-1 text-xs\",\n                                isValid\n                                  ? \"bg-muted text-muted-foreground\"\n                                  : \"bg-destructive text-destructive-foreground\"\n                              )}\n                            >\n                              {isValid ? (\n                                <>\n                                  <Check className=\"h-3 w-3\" />\n                                  {contrastRatio}\n                                </>\n                              ) : (\n                                <>\n                                  <AlertTriangle className=\"h-3 w-3\" />\n                                  {contrastRatio}\n                                </>\n                              )}\n                            </Badge>\n                          </div>\n                          <div className=\"flex items-center gap-2\">\n                            <div className=\"flex flex-1 flex-col items-center gap-3\">\n                              <div className=\"flex w-full items-center gap-3\">\n                                <div\n                                  style={{\n                                    backgroundColor: pair.background ?? \"#000000\",\n                                  }}\n                                  className=\"h-12 w-12 flex-shrink-0 rounded-md border shadow-sm\"\n                                ></div>\n                                <div className=\"flex flex-col\">\n                                  <span className=\"text-xs font-medium\">Background</span>\n                                  <span className=\"text-muted-foreground font-mono text-xs\">\n                                    {pair.background}\n                                  </span>\n                                </div>\n                              </div>\n                              <div className=\"flex w-full items-center gap-3\">\n                                <div\n                                  style={{\n                                    backgroundColor: pair.foreground ?? \"#ffffff\",\n                                  }}\n                                  className=\"h-12 w-12 flex-shrink-0 rounded-md border shadow-sm\"\n                                ></div>\n                                <div className=\"flex flex-col\">\n                                  <span className=\"text-xs font-medium\">Foreground</span>\n                                  <span className=\"text-muted-foreground font-mono text-xs\">\n                                    {pair.foreground}\n                                  </span>\n                                </div>\n                              </div>\n                            </div>\n                            <div\n                              style={{\n                                backgroundColor: pair.background ?? \"transparent\",\n                              }}\n                              className=\"flex h-full min-h-[120px] flex-1 items-center justify-center overflow-hidden rounded-lg border shadow-sm\"\n                            >\n                              {pair.foreground && pair.background ? (\n                                <div className=\"p-4 text-center\">\n                                  <p\n                                    style={{ color: pair.foreground }}\n                                    className=\"mb-2 text-4xl font-bold tracking-wider\"\n                                  >\n                                    Aa\n                                  </p>\n                                  <p\n                                    style={{ color: pair.foreground }}\n                                    className=\"text-sm font-medium\"\n                                  >\n                                    Sample Text\n                                  </p>\n                                </div>\n                              ) : (\n                                <p className=\"text-muted-foreground text-xs\">Preview</p>\n                              )}\n                            </div>\n                          </div>\n                        </CardContent>\n                      </Card>\n                    );\n                  })}\n                </div>\n              </div>\n            ))}\n          </div>\n        </ScrollArea>\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n};\n\nexport default ContrastChecker;\n"
  },
  {
    "path": "components/editor/control-section.tsx",
    "content": "import React, { useState } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { ControlSectionProps } from \"@/types\";\nimport { SectionContext } from \"./section-context\";\n\nconst ControlSection = ({ title, children, expanded = false, className, headerAction }: ControlSectionProps) => {\n  const [isExpanded, setIsExpanded] = useState(expanded);\n\n  return (\n    <SectionContext.Provider\n      value={{\n        isExpanded,\n        setIsExpanded,\n        toggleExpanded: () => setIsExpanded((prev) => !prev),\n      }}\n    >\n      <div className=\"group/accordion\">\n        <div className=\"flex items-center gap-1 py-1\">\n          <button\n            type=\"button\"\n            className=\"group/section flex items-center transition-colors\"\n            onClick={() => setIsExpanded(!isExpanded)}\n            aria-label={isExpanded ? \"Collapse section\" : \"Expand section\"}\n          >\n            <div className=\"bg-muted group-hover/section:bg-muted/80 group-has-focus-within/accordion:bg-muted/70 flex items-center gap-1 rounded-md border border-transparent px-2 py-0.5 transition-all group-has-focus-within/accordion:border-ring/50\">\n              <ChevronRight\n                className={cn(\n                  \"text-muted-foreground size-3 transition-transform duration-200\",\n                  isExpanded && \"rotate-90\"\n                )}\n              />\n              <span className=\"text-muted-foreground text-[11px] font-semibold uppercase tracking-wider\">\n                {title}\n              </span>\n            </div>\n          </button>\n          {headerAction}\n        </div>\n\n        <div\n          className={cn(\n            \"overflow-hidden transition-all duration-200\",\n            isExpanded ? \"max-h-[2000px] opacity-100\" : \"max-h-0 opacity-0\"\n          )}\n        >\n          <div className={cn(\"flex flex-col pt-1 pb-2\", className)}>{children}</div>\n        </div>\n      </div>\n    </SectionContext.Provider>\n  );\n};\n\nexport default ControlSection;\n"
  },
  {
    "path": "components/editor/css-import-dialog.tsx",
    "content": "import { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogFooter,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n} from \"@/components/ui/revola\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { AlertCircle } from \"lucide-react\";\nimport React, { useState } from \"react\";\n\ninterface CssImportDialogProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  onImport: (css: string) => void;\n}\n\nconst CssImportDialog: React.FC<CssImportDialogProps> = ({ open, onOpenChange, onImport }) => {\n  const [cssText, setCssText] = useState(\"\");\n  const [error, setError] = useState<string | null>(null);\n\n  const handleImport = () => {\n    // Basic validation - check if the CSS contains some expected variables\n    if (!cssText.trim()) {\n      setError(\"Please enter CSS content\");\n      return;\n    }\n\n    try {\n      // Here you would add more sophisticated CSS parsing validation\n      // For now we'll just do a simple check\n      if (!cssText.includes(\"--\") || !cssText.includes(\":\")) {\n        setError(\n          \"Invalid CSS format. CSS should contain variable definitions like --primary: #color\"\n        );\n        return;\n      }\n\n      onImport(cssText);\n      setCssText(\"\");\n      setError(null);\n      onOpenChange(false);\n    } catch {\n      setError(\"Failed to parse CSS. Please check your syntax.\");\n    }\n  };\n\n  const handleClose = () => {\n    setCssText(\"\");\n    setError(null);\n    onOpenChange(false);\n  };\n\n  return (\n    <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n      <ResponsiveDialogContent className=\"flex max-h-[90dvh] flex-col overflow-hidden shadow-lg sm:max-h-[min(640px,80dvh)] sm:max-w-[550px] sm:pt-6\">\n        <ScrollArea className=\"flex h-full flex-col [&>[data-slot=scroll-area-viewport]]:pb-2 [&>[data-slot=scroll-area-viewport]>div]:space-y-6\">\n          <ResponsiveDialogHeader className=\"px-6\">\n            <ResponsiveDialogTitle>Import Custom CSS</ResponsiveDialogTitle>\n            <ResponsiveDialogDescription>\n              Paste your CSS file below to customize the theme colors. Make sure to include\n              variables like --primary, --background, etc.\n            </ResponsiveDialogDescription>\n          </ResponsiveDialogHeader>\n\n          <div className=\"space-y-4 px-6\">\n            {error && (\n              <Alert variant=\"destructive\">\n                <AlertCircle className=\"mr-2 size-4\" />\n                <AlertDescription>{error}</AlertDescription>\n              </Alert>\n            )}\n            <Textarea\n              placeholder={`:root {\n  --background: 0 0% 100%;\n  --foreground: oklch(0.52 0.13 144.17);\n  --primary: #3e2723;\n  /* And more */\n}\n.dark {\n  --background: 222.2 84% 4.9%;\n  --foreground: hsl(37.50 36.36% 95.69%);\n  --primary: rgb(46, 125, 50);\n  /* And more */\n}\n            `}\n              className=\"text-foreground min-h-[300px] max-h-[400px] field-sizing-fixed overflow-y-auto resize-none font-mono text-sm\"\n              value={cssText}\n              onChange={(e) => {\n                setCssText(e.target.value);\n                if (error) setError(null);\n              }}\n            />\n          </div>\n        </ScrollArea>\n\n        <ResponsiveDialogFooter className=\"bg-muted/30 mt-4 border-t px-6 py-4 sm:mt-0\">\n          <Button variant=\"ghost\" onClick={handleClose} size=\"sm\" className=\"max-sm:w-full\">\n            Cancel\n          </Button>\n          <Button onClick={handleImport} size=\"sm\" className=\"max-sm:w-full\">\n            Import\n          </Button>\n        </ResponsiveDialogFooter>\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n};\n\nexport default CssImportDialog;\n"
  },
  {
    "path": "components/editor/custom-textarea.tsx",
    "content": "\"use client\";\n\nimport { suggestion } from \"@/components/editor/mention-suggestion\";\nimport { toast } from \"@/hooks/use-toast\";\nimport { cn } from \"@/lib/utils\";\nimport CharacterCount from \"@tiptap/extension-character-count\";\nimport Mention from \"@tiptap/extension-mention\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport { EditorContent, JSONContent, useEditor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { useEffect } from \"react\";\n\ninterface CustomTextareaProps {\n  className?: string;\n  disabled?: boolean;\n  canSubmit?: boolean;\n  onContentChange: (jsonContent: JSONContent) => void;\n  onSubmit: () => void;\n  onImagesPaste?: (files: File[]) => void;\n  characterLimit?: number;\n  initialEditorContent?: JSONContent | null;\n  externalEditorContent?: JSONContent | null;\n  isStreamingContent?: boolean;\n}\n\nexport default function CustomTextarea({\n  className,\n  disabled = false,\n  canSubmit = false,\n  onContentChange,\n  onSubmit,\n  onImagesPaste,\n  characterLimit,\n  initialEditorContent,\n  externalEditorContent,\n  isStreamingContent = false,\n}: CustomTextareaProps) {\n  const editor = useEditor({\n    immediatelyRender: false,\n    editable: !disabled,\n    extensions: [\n      StarterKit,\n      Mention.configure({\n        HTMLAttributes: {\n          class: \"mention\",\n        },\n        suggestion: suggestion,\n      }),\n      Placeholder.configure({\n        placeholder: \"Describe your theme...\",\n        emptyEditorClass:\n          \"cursor-text before:content-[attr(data-placeholder)] before:absolute before:inset-x-1 before:top-1 before:opacity-50 before-pointer-events-none\",\n      }),\n      CharacterCount.configure({\n        limit: characterLimit,\n      }),\n    ],\n    autofocus: !disabled,\n    editorProps: {\n      attributes: {\n        class: cn(\n          \"min-w-0 min-h-[50px] max-h-[120px] wrap-anywhere text-foreground/90 scrollbar-thin overflow-y-auto w-full bg-background p-1 text-sm focus-visible:outline-none max-sm:text-[16px]! transition-all\",\n          disabled && \"opacity-75 pointer-events-none\",\n          className\n        ),\n      },\n      handleKeyDown: (view, event) => {\n        if (disabled) {\n          event.preventDefault();\n          return true;\n        }\n\n        if (event.key === \"Enter\" && !event.shiftKey && !disabled && canSubmit) {\n          const mentionPluginKey = Mention.options.suggestion.pluginKey;\n\n          if (!mentionPluginKey) {\n            console.error(\"Mention plugin key not found.\");\n            return false;\n          }\n\n          const { state } = view;\n          const mentionState = mentionPluginKey.getState(state);\n\n          if (mentionState?.active) {\n            return false;\n          } else {\n            event.preventDefault();\n            onSubmit();\n            return true;\n          }\n        }\n        return false;\n      },\n      handlePaste: (_view, event) => {\n        if (disabled) {\n          event.preventDefault();\n          return true;\n        }\n\n        if (!characterLimit) return false;\n\n        const clipboardData = event.clipboardData;\n        if (!clipboardData) return false;\n\n        // Check for image files\n        if (onImagesPaste) {\n          const files = Array.from(clipboardData.files);\n          const imageFiles = files.filter((file) => file.type.startsWith(\"image/\"));\n\n          if (imageFiles.length > 0) {\n            event.preventDefault();\n            onImagesPaste(imageFiles);\n            return true;\n          }\n        }\n\n        const pastedText = clipboardData.getData(\"text/plain\");\n        const currentCharacterCount = editor?.storage.characterCount.characters() || 0;\n        const totalCharacters = currentCharacterCount + pastedText.length;\n\n        if (totalCharacters > characterLimit) {\n          event.preventDefault();\n          toast({\n            title: \"Text too long\",\n            description: `The pasted content would exceed the ${characterLimit} character limit.`,\n            variant: \"destructive\",\n          });\n          return true;\n        }\n\n        return false;\n      },\n    },\n    content: initialEditorContent || \"\",\n    onCreate: ({ editor }) => {\n      if (disabled) return;\n      editor.commands.focus(\"end\");\n    },\n    onUpdate: ({ editor }) => {\n      const jsonContent = editor.getJSON();\n      onContentChange(jsonContent);\n    },\n  });\n\n  useEffect(() => {\n    if (!editor) return;\n    editor.setEditable(!disabled);\n    if (disabled) editor.commands.blur();\n    else editor.commands.focus(\"end\");\n  }, [disabled, editor]);\n\n  // Stream external content into the editor\n  useEffect(() => {\n    if (!editor || !externalEditorContent || !isStreamingContent) return;\n\n    try {\n      const currentContent = JSON.stringify(editor.getJSON());\n      const nextContent = JSON.stringify(externalEditorContent);\n      if (currentContent === nextContent) return;\n\n      // Preserve cursor position at the end\n      editor.commands.setContent(externalEditorContent, false);\n      editor.commands.focus(\"end\");\n    } catch (_e) {\n      // If setContent fails for any reason, silently ignore; user can keep typing\n    }\n  }, [externalEditorContent, editor, isStreamingContent]);\n\n  if (!editor) {\n    return null;\n  }\n\n  const characterCount = editor.storage.characterCount.characters();\n  const isLimitExceeded = characterLimit && characterCount > characterLimit;\n  const shouldShowCount = characterLimit && characterCount >= characterLimit * 0.9;\n\n  return (\n    <div className=\"relative isolate\">\n      <EditorContent editor={editor} aria-disabled={disabled} />\n      {shouldShowCount && (\n        <div className=\"absolute right-3 bottom-2 z-10 flex text-xs hover:opacity-0\">\n          <span\n            className={cn(\n              \"bg-background/10 pointer-events-none rounded-full px-0.5 backdrop-blur-xs\",\n              isLimitExceeded ? \"text-destructive\" : \"text-muted-foreground\"\n            )}\n          >\n            {characterCount} / {characterLimit}\n          </span>\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/editor/editor.tsx",
    "content": "\"use client\";\n\nimport { ResizableHandle, ResizablePanel, ResizablePanelGroup } from \"@/components/ui/resizable\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { DialogActionsProvider } from \"@/hooks/use-dialog-actions\";\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { Theme, ThemeStyles } from \"@/types/theme\";\nimport { Sliders } from \"lucide-react\";\nimport React, { use, useEffect } from \"react\";\nimport { ActionBar } from \"./action-bar/action-bar\";\nimport ThemeControlPanel from \"./theme-control-panel\";\nimport ThemePreviewPanel from \"./theme-preview-panel\";\n\ninterface EditorProps {\n  themePromise: Promise<Theme | null>;\n}\n\nconst isThemeStyles = (styles: unknown): styles is ThemeStyles => {\n  return (\n    !!styles &&\n    typeof styles === \"object\" &&\n    styles !== null &&\n    \"light\" in styles &&\n    \"dark\" in styles\n  );\n};\n\nconst Editor: React.FC<EditorProps> = ({ themePromise }) => {\n  const themeState = useEditorStore((state) => state.themeState);\n  const setThemeState = useEditorStore((state) => state.setThemeState);\n  const isMobile = useIsMobile();\n\n  const initialTheme = themePromise ? use(themePromise) : null;\n\n  const handleStyleChange = React.useCallback(\n    (newStyles: ThemeStyles) => {\n      const prev = useEditorStore.getState().themeState;\n      setThemeState({ ...prev, styles: newStyles });\n    },\n    [setThemeState]\n  );\n\n  useEffect(() => {\n    if (initialTheme && isThemeStyles(initialTheme.styles)) {\n      const prev = useEditorStore.getState().themeState;\n      setThemeState({\n        ...prev,\n        styles: initialTheme.styles,\n        preset: initialTheme.id,\n      });\n    }\n  }, [initialTheme, setThemeState]);\n\n  if (initialTheme && !isThemeStyles(initialTheme.styles)) {\n    return (\n      <div className=\"text-destructive flex h-full items-center justify-center\">\n        Fetched theme data is invalid.\n      </div>\n    );\n  }\n\n  const styles = themeState.styles;\n\n  // Mobile layout\n  if (isMobile) {\n    return (\n      <DialogActionsProvider>\n        <div className=\"relative isolate flex flex-1 overflow-hidden\">\n          <div className=\"size-full flex-1 overflow-hidden\">\n            <Tabs defaultValue=\"controls\" className=\"h-full\">\n              <TabsList className=\"w-full rounded-none\">\n                <TabsTrigger value=\"controls\" className=\"flex-1\">\n                  <Sliders className=\"mr-2 h-4 w-4\" />\n                  Controls\n                </TabsTrigger>\n                <TabsTrigger value=\"preview\" className=\"flex-1\">\n                  Preview\n                </TabsTrigger>\n              </TabsList>\n              <TabsContent value=\"controls\" className=\"mt-0 h-[calc(100%-2.5rem)]\">\n                <div className=\"flex h-full flex-col\">\n                  <ThemeControlPanel\n                    styles={styles}\n                    onChange={handleStyleChange}\n                    currentMode={themeState.currentMode}\n                  />\n                </div>\n              </TabsContent>\n              <TabsContent value=\"preview\" className=\"mt-0 h-[calc(100%-2.5rem)]\">\n                <div className=\"flex h-full flex-col\">\n                  <ActionBar />\n                  <ThemePreviewPanel styles={styles} currentMode={themeState.currentMode} />\n                </div>\n              </TabsContent>\n            </Tabs>\n          </div>\n        </div>\n      </DialogActionsProvider>\n    );\n  }\n\n  // Desktop layout\n  return (\n    <DialogActionsProvider>\n      <div className=\"relative isolate flex flex-1 overflow-hidden\">\n        <div className=\"size-full\">\n          <ResizablePanelGroup orientation=\"horizontal\" className=\"isolate\">\n            <ResizablePanel\n              defaultSize=\"30%\"\n              minSize=\"20%\"\n              maxSize=\"40%\"\n              className=\"z-1\"\n            >\n              <div className=\"relative isolate flex h-full flex-1 flex-col overflow-hidden\">\n                <ThemeControlPanel\n                  styles={styles}\n                  onChange={handleStyleChange}\n                  currentMode={themeState.currentMode}\n                />\n              </div>\n            </ResizablePanel>\n            <ResizableHandle />\n            <ResizablePanel defaultSize=\"70%\">\n              <div className=\"flex h-full flex-col\">\n                <div className=\"flex min-h-0 flex-1 flex-col\">\n                  <ActionBar />\n                  <ThemePreviewPanel styles={styles} currentMode={themeState.currentMode} />\n                </div>\n              </div>\n            </ResizablePanel>\n          </ResizablePanelGroup>\n        </div>\n      </div>\n    </DialogActionsProvider>\n  );\n};\n\nexport default Editor;\n"
  },
  {
    "path": "components/editor/font-picker.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { useDebouncedCallback } from \"@/hooks/use-debounced-callback\";\nimport { FilterFontCategory, useFontSearch } from \"@/hooks/use-font-search\";\nimport { cn } from \"@/lib/utils\";\nimport { FontInfo } from \"@/types/fonts\";\nimport { buildFontFamily, getDefaultWeights, waitForFont } from \"@/utils/fonts\";\nimport { loadGoogleFont } from \"@/utils/fonts/google-fonts\";\nimport { Check, ChevronDown, FunnelX, Loader2 } from \"lucide-react\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { TooltipWrapper } from \"../tooltip-wrapper\";\n\nconst POPULAR_FONTS: Record<string, FontInfo[]> = {\n  \"sans-serif\": [\n    { family: \"Inter\", category: \"sans-serif\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"Roboto\", category: \"sans-serif\", variants: [\"100\", \"300\", \"400\", \"500\", \"700\", \"900\"], variable: false },\n    { family: \"Open Sans\", category: \"sans-serif\", variants: [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\"], variable: true },\n    { family: \"Poppins\", category: \"sans-serif\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: false },\n    { family: \"Montserrat\", category: \"sans-serif\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"Lato\", category: \"sans-serif\", variants: [\"100\", \"300\", \"400\", \"700\", \"900\"], variable: false },\n    { family: \"Nunito\", category: \"sans-serif\", variants: [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"Raleway\", category: \"sans-serif\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"DM Sans\", category: \"sans-serif\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"Plus Jakarta Sans\", category: \"sans-serif\", variants: [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"], variable: true },\n    { family: \"Geist\", category: \"sans-serif\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n  ],\n  serif: [\n    { family: \"Playfair Display\", category: \"serif\", variants: [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"Merriweather\", category: \"serif\", variants: [\"300\", \"400\", \"700\", \"900\"], variable: false },\n    { family: \"Lora\", category: \"serif\", variants: [\"400\", \"500\", \"600\", \"700\"], variable: true },\n    { family: \"PT Serif\", category: \"serif\", variants: [\"400\", \"700\"], variable: false },\n    { family: \"Noto Serif\", category: \"serif\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"Source Serif 4\", category: \"serif\", variants: [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"Libre Baskerville\", category: \"serif\", variants: [\"400\", \"700\"], variable: false },\n    { family: \"EB Garamond\", category: \"serif\", variants: [\"400\", \"500\", \"600\", \"700\", \"800\"], variable: true },\n    { family: \"Crimson Text\", category: \"serif\", variants: [\"400\", \"600\", \"700\"], variable: false },\n    { family: \"Bitter\", category: \"serif\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n  ],\n  monospace: [\n    { family: \"JetBrains Mono\", category: \"monospace\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"], variable: true },\n    { family: \"Fira Code\", category: \"monospace\", variants: [\"300\", \"400\", \"500\", \"600\", \"700\"], variable: true },\n    { family: \"Source Code Pro\", category: \"monospace\", variants: [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"Roboto Mono\", category: \"monospace\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"], variable: true },\n    { family: \"IBM Plex Mono\", category: \"monospace\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"], variable: false },\n    { family: \"Space Mono\", category: \"monospace\", variants: [\"400\", \"700\"], variable: false },\n    { family: \"Ubuntu Mono\", category: \"monospace\", variants: [\"400\", \"700\"], variable: false },\n    { family: \"Inconsolata\", category: \"monospace\", variants: [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"Geist Mono\", category: \"monospace\", variants: [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"], variable: true },\n    { family: \"Anonymous Pro\", category: \"monospace\", variants: [\"400\", \"700\"], variable: false },\n    { family: \"Red Hat Mono\", category: \"monospace\", variants: [\"300\", \"400\", \"500\", \"600\", \"700\"], variable: true },\n  ],\n  display: [\n    { family: \"Bebas Neue\", category: \"display\", variants: [\"400\"], variable: false },\n    { family: \"Abril Fatface\", category: \"display\", variants: [\"400\"], variable: false },\n    { family: \"Righteous\", category: \"display\", variants: [\"400\"], variable: false },\n    { family: \"Fredoka\", category: \"display\", variants: [\"300\", \"400\", \"500\", \"600\", \"700\"], variable: true },\n    { family: \"Lobster\", category: \"display\", variants: [\"400\"], variable: false },\n    { family: \"Comfortaa\", category: \"display\", variants: [\"300\", \"400\", \"500\", \"600\", \"700\"], variable: true },\n    { family: \"Alfa Slab One\", category: \"display\", variants: [\"400\"], variable: false },\n    { family: \"Bungee\", category: \"display\", variants: [\"400\"], variable: false },\n    { family: \"Lilita One\", category: \"display\", variants: [\"400\"], variable: false },\n    { family: \"Permanent Marker\", category: \"display\", variants: [\"400\"], variable: false },\n  ],\n  handwriting: [\n    { family: \"Dancing Script\", category: \"handwriting\", variants: [\"400\", \"500\", \"600\", \"700\"], variable: true },\n    { family: \"Pacifico\", category: \"handwriting\", variants: [\"400\"], variable: false },\n    { family: \"Caveat\", category: \"handwriting\", variants: [\"400\", \"500\", \"600\", \"700\"], variable: true },\n    { family: \"Satisfy\", category: \"handwriting\", variants: [\"400\"], variable: false },\n    { family: \"Great Vibes\", category: \"handwriting\", variants: [\"400\"], variable: false },\n    { family: \"Sacramento\", category: \"handwriting\", variants: [\"400\"], variable: false },\n    { family: \"Kalam\", category: \"handwriting\", variants: [\"300\", \"400\", \"700\"], variable: false },\n    { family: \"Patrick Hand\", category: \"handwriting\", variants: [\"400\"], variable: false },\n    { family: \"Indie Flower\", category: \"handwriting\", variants: [\"400\"], variable: false },\n    { family: \"Shadows Into Light\", category: \"handwriting\", variants: [\"400\"], variable: false },\n  ],\n};\n\ninterface FontPickerProps {\n  value?: string;\n  category?: FilterFontCategory;\n  onSelect: (font: FontInfo) => void;\n  placeholder?: string;\n  className?: string;\n}\n\nfunction FontItem({\n  font,\n  isSelected,\n  isLoading,\n  onSelect,\n  selectedRef,\n}: {\n  font: FontInfo;\n  isSelected: boolean;\n  isLoading: boolean;\n  onSelect: (font: FontInfo) => void;\n  selectedRef: React.Ref<HTMLDivElement> | null;\n}) {\n  const fontFamily = buildFontFamily(font.family, font.category);\n\n  return (\n    <CommandItem\n      className=\"flex cursor-pointer items-center justify-between gap-2 p-2\"\n      onSelect={() => onSelect(font)}\n      disabled={isLoading}\n      onMouseEnter={() => loadGoogleFont(font.family, [\"400\"])}\n      ref={selectedRef}\n    >\n      <div className=\"line-clamp-1 inline-flex w-full flex-1 flex-col justify-between\">\n        <span className=\"inline-flex items-center gap-2 truncate\" style={{ fontFamily }}>\n          {font.family}\n          {isLoading && <Loader2 className=\"size-3 animate-spin\" />}\n        </span>\n        <div className=\"flex items-center gap-1 text-xs font-normal opacity-70\">\n          <span>{font.category}</span>\n          {font.variable && (\n            <span className=\"inline-flex items-center gap-1\">\n              <span>•</span>\n              <span>Variable</span>\n            </span>\n          )}\n        </div>\n      </div>\n      {isSelected && <Check className=\"size-4 shrink-0 opacity-70\" />}\n    </CommandItem>\n  );\n}\n\nexport function FontPicker({\n  value,\n  category,\n  onSelect,\n  placeholder = \"Search fonts...\",\n  className,\n}: FontPickerProps) {\n  const [open, setOpen] = useState(false);\n  const [inputValue, setInputValue] = useState(\"\");\n  const [searchQuery, setSearchQuery] = useState(\"\");\n  const [selectedCategory, setSelectedCategory] = useState<FilterFontCategory>(category || \"all\");\n  const [loadingFont, setLoadingFont] = useState<string | null>(null);\n  const scrollRef = useRef<HTMLDivElement>(null);\n\n  const selectedFontRef = useRef<HTMLDivElement>(null);\n  const hasScrolledToSelectedFont = useRef(false);\n\n  const debouncedSetSearchQuery = useDebouncedCallback(setSearchQuery, 300);\n\n  useEffect(() => {\n    debouncedSetSearchQuery(inputValue);\n  }, [inputValue, debouncedSetSearchQuery]);\n\n  const fontQuery = useFontSearch({\n    query: searchQuery,\n    category: selectedCategory,\n    limit: 15,\n    enabled: open,\n  });\n\n  useEffect(() => {\n    if (!open) return;\n    scrollRef.current?.scrollTo({ top: 0 });\n  }, [selectedCategory, searchQuery, open]);\n\n  useEffect(() => {\n    if (open && fontQuery.data && !hasScrolledToSelectedFont.current) {\n      requestAnimationFrame(() => {\n        selectedFontRef.current?.scrollIntoView({\n          block: \"center\",\n          inline: \"nearest\",\n        });\n      });\n      hasScrolledToSelectedFont.current = true;\n    } else if (!open) {\n      hasScrolledToSelectedFont.current = false;\n    }\n  }, [open, fontQuery.data]);\n\n  // Flatten all pages into a single array\n  const allFonts = useMemo(() => {\n    if (!fontQuery.data) return [];\n    return fontQuery.data.pages.flatMap((page) => page.fonts);\n  }, [fontQuery.data]);\n\n  // Popular fonts for the current category (only shown when not searching)\n  const popularFonts = useMemo(() => {\n    if (searchQuery) return [];\n    if (selectedCategory === \"all\") {\n      // Show a mix from sans-serif, serif, monospace\n      return [\n        ...POPULAR_FONTS[\"sans-serif\"].slice(0, 2),\n        ...POPULAR_FONTS[\"serif\"].slice(0, 1),\n        ...POPULAR_FONTS[\"monospace\"].slice(0, 1),\n      ];\n    }\n    return POPULAR_FONTS[selectedCategory] || [];\n  }, [searchQuery, selectedCategory]);\n\n  // Filter out popular fonts from the main list to avoid duplicates\n  const remainingFonts = useMemo(() => {\n    if (popularFonts.length === 0) return allFonts;\n    const popularFamilies = new Set(popularFonts.map((f) => f.family));\n    return allFonts.filter((font) => !popularFamilies.has(font.family));\n  }, [allFonts, popularFonts]);\n\n  // Intersection Observer ref callback for infinite scroll\n  const loadMoreRefCallback = useCallback(\n    (node: HTMLDivElement | null) => {\n      if (!node) return;\n\n      const observer = new IntersectionObserver(\n        (entries) => {\n          const entry = entries[0];\n          if (entry.isIntersecting && fontQuery.hasNextPage && !fontQuery.isFetchingNextPage) {\n            fontQuery.fetchNextPage();\n          }\n        },\n        {\n          root: scrollRef.current,\n          rootMargin: \"100px\",\n          threshold: 0,\n        }\n      );\n\n      observer.observe(node);\n      return () => observer.unobserve(node);\n    },\n    [fontQuery.hasNextPage, fontQuery.isFetchingNextPage, fontQuery.fetchNextPage]\n  );\n\n  const handleFontSelect = useCallback(\n    async (font: FontInfo) => {\n      setLoadingFont(font.family);\n\n      try {\n        const weights = getDefaultWeights(font.variants);\n        loadGoogleFont(font.family, weights);\n        await waitForFont(font.family, weights[0]);\n      } catch (error) {\n        console.warn(`Failed to load font ${font.family}:`, error);\n      }\n\n      setLoadingFont(null);\n      onSelect(font);\n    },\n    [onSelect]\n  );\n\n  // Get current font info for display\n  const currentFont = useMemo(() => {\n    if (!value) return null;\n\n    // First try to find the font in the search results\n    const foundFont = allFonts.find((font: FontInfo) => font.family === value);\n    if (foundFont) return foundFont;\n\n    // If not found in search results, create a fallback FontInfo object\n    // This happens when a font is selected and then the search changes\n    const extractedFontName = value.split(\",\")[0].trim().replace(/['\"]/g, \"\");\n\n    return {\n      family: extractedFontName,\n      category: category || \"sans-serif\",\n      variants: [\"400\"],\n      variable: false,\n    } as FontInfo;\n  }, [value, allFonts, category]);\n\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      <PopoverTrigger asChild>\n        <Button\n          variant=\"outline\"\n          role=\"combobox\"\n          className={cn(\"bg-input/25 w-full justify-between\", className)}\n        >\n          <div className=\"flex items-center gap-2\">\n            {currentFont ? (\n              <span className=\"inline-flex items-center gap-2\">\n                <span\n                  style={{\n                    fontFamily: buildFontFamily(currentFont.family, currentFont.category),\n                  }}\n                >\n                  {currentFont.family}\n                </span>\n              </span>\n            ) : (\n              <span className=\"text-muted-foreground\">{placeholder}</span>\n            )}\n          </div>\n          <ChevronDown className=\"ml-2 size-4 shrink-0 opacity-50\" />\n        </Button>\n      </PopoverTrigger>\n\n      <PopoverContent className=\"w-[300px] p-0\" align=\"start\">\n        <Command shouldFilter={false} className=\"h-96 w-full overflow-hidden\">\n          <div className=\"flex flex-col\">\n            <div className=\"relative\">\n              <CommandInput\n                className=\"h-10 w-full border-none p-0 pr-10\"\n                placeholder=\"Search Google fonts...\"\n                value={inputValue}\n                onValueChange={setInputValue}\n              />\n\n              {inputValue && (\n                <TooltipWrapper asChild label=\"Clear search\">\n                  <Button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    onClick={() => setInputValue(\"\")}\n                    className=\"absolute top-2 right-2 size-6\"\n                  >\n                    <FunnelX className=\"size-4\" />\n                  </Button>\n                </TooltipWrapper>\n              )}\n            </div>\n\n            <div className=\"px-2 py-1\">\n              <Select\n                value={selectedCategory}\n                onValueChange={(value) => setSelectedCategory(value as FilterFontCategory)}\n              >\n                <SelectTrigger size=\"sm\" className=\"focus bg-input/25 px-2 text-xs outline-none\">\n                  <SelectValue />\n                </SelectTrigger>\n                <SelectContent>\n                  <SelectItem value=\"all\">All Fonts</SelectItem>\n                  <SelectItem value=\"sans-serif\">Sans Serif Fonts</SelectItem>\n                  <SelectItem value=\"serif\">Serif Fonts</SelectItem>\n                  <SelectItem value=\"monospace\">Monospace Fonts</SelectItem>\n                  <SelectItem value=\"display\">Display Fonts</SelectItem>\n                  <SelectItem value=\"handwriting\">Handwriting Fonts</SelectItem>\n                </SelectContent>\n              </Select>\n            </div>\n          </div>\n\n          <Separator />\n\n          <div className=\"relative isolate size-full\">\n            {fontQuery.isLoading ? (\n              <div className=\"absolute inset-0 flex size-full items-center justify-center gap-2 text-center\">\n                <Loader2 className=\"size-4 animate-spin\" />\n                <span className=\"text-muted-foreground text-sm\">Loading fonts...</span>\n              </div>\n            ) : allFonts.length === 0 ? (\n              <CommandEmpty>No fonts found.</CommandEmpty>\n            ) : (\n              <CommandList className=\"scrollbar-thin size-full p-1\" ref={scrollRef}>\n                {popularFonts.length > 0 && (\n                  <CommandGroup heading=\"Popular\">\n                    {popularFonts.map((font) => (\n                      <FontItem\n                        key={font.family}\n                        font={font}\n                        isSelected={font.family === value}\n                        isLoading={loadingFont === font.family}\n                        onSelect={handleFontSelect}\n                        selectedRef={font.family === value ? selectedFontRef : null}\n                      />\n                    ))}\n                  </CommandGroup>\n                )}\n\n                <CommandGroup heading={popularFonts.length > 0 ? \"All Fonts\" : undefined}>\n                  {remainingFonts.map((font: FontInfo) => (\n                    <FontItem\n                      key={font.family}\n                      font={font}\n                      isSelected={font.family === value}\n                      isLoading={loadingFont === font.family}\n                      onSelect={handleFontSelect}\n                      selectedRef={font.family === value ? selectedFontRef : null}\n                    />\n                  ))}\n                </CommandGroup>\n\n                {/* Load more trigger element */}\n                {fontQuery.hasNextPage && <div ref={loadMoreRefCallback} className=\"h-2 w-full\" />}\n\n                {/* Loading indicator for infinite scroll */}\n                {fontQuery.isFetchingNextPage && (\n                  <div className=\"flex items-center justify-center gap-2 p-2\">\n                    <Loader2 className=\"size-4 animate-spin\" />\n                    <span className=\"text-muted-foreground text-sm\">Loading more fonts...</span>\n                  </div>\n                )}\n              </CommandList>\n            )}\n          </div>\n        </Command>\n      </PopoverContent>\n    </Popover>\n  );\n}\n"
  },
  {
    "path": "components/editor/hsl-adjustment-controls.tsx",
    "content": "\"use client\";\n\nimport React, { useCallback, useMemo, useRef, useEffect, useState } from \"react\";\nimport { SliderWithInput } from \"./slider-with-input\";\nimport { useEditorStore } from \"../../store/editor-store\";\nimport { COMMON_STYLES, defaultThemeState } from \"../../config/theme\";\nimport { ThemeEditorState } from \"@/types/editor\";\nimport { converter, formatHex, Hsl } from \"culori\";\nimport { debounce } from \"@/utils/debounce\";\nimport { isDeepEqual } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { HslPresetButton } from \"./hsl-preset-button\";\nimport { ChevronDown } from \"lucide-react\";\n\n// Adjusts a color by modifying HSL values\nfunction adjustColorByHsl(\n  color: string,\n  hueShift: number,\n  saturationScale: number,\n  lightnessScale: number\n): string {\n  const hsl = converter(\"hsl\")(color);\n  const h = hsl?.h;\n  const s = hsl?.s;\n  const l = hsl?.l;\n\n  if (h === undefined || s === undefined || l === undefined) {\n    return color;\n  }\n\n  const adjustedHsl = {\n    h: (((h + hueShift) % 360) + 360) % 360,\n    s: Math.min(1, Math.max(0, s * saturationScale)),\n    l: Math.min(1, Math.max(0.1, l * lightnessScale)),\n  };\n\n  const out = converter(\"hsl\")(adjustedHsl as Hsl);\n  return formatHex(out);\n}\n\n// Preset HSL adjustment values\nconst HSL_PRESETS = [\n  // Hue Adjustments\n  { label: \"Hue (-120°)\", hueShift: -120, saturationScale: 1, lightnessScale: 1 },\n  { label: \"Hue (-60°)\", hueShift: -60, saturationScale: 1, lightnessScale: 1 },\n  { label: \"Hue (+60°)\", hueShift: 60, saturationScale: 1, lightnessScale: 1 },\n  { label: \"Hue (+120°)\", hueShift: 120, saturationScale: 1, lightnessScale: 1 },\n  { label: \"Hue Invert\", hueShift: 180, saturationScale: 1, lightnessScale: 1 },\n\n  // Saturation Adjustments\n  { label: \"Grayscale\", hueShift: 0, saturationScale: 0, lightnessScale: 1 },\n  { label: \"Muted\", hueShift: 0, saturationScale: 0.6, lightnessScale: 1 },\n  { label: \"Vibrant\", hueShift: 0, saturationScale: 1.4, lightnessScale: 1 },\n\n  // Lightness Adjustments\n  { label: \"Dimmer\", hueShift: 0, saturationScale: 1, lightnessScale: 0.8 },\n  { label: \"Brighter\", hueShift: 0, saturationScale: 1, lightnessScale: 1.2 },\n\n  // Combined Adjustments\n  { label: \"H(+30) S(-50) L(-5%)\", hueShift: 30, saturationScale: 0.5, lightnessScale: 0.95 },\n  { label: \"H(-20) S(+20) L(+5%)\", hueShift: -20, saturationScale: 1.2, lightnessScale: 1.05 },\n  { label: \"H(+20) S(-30) L(-5%)\", hueShift: 20, saturationScale: 0.7, lightnessScale: 0.95 },\n  { label: \"H(-10) S(-25) L(+10%)\", hueShift: -10, saturationScale: 0.75, lightnessScale: 1.1 },\n  { label: \"H(+60) S(+50) L(+10%)\", hueShift: 60, saturationScale: 1.5, lightnessScale: 1.1 },\n];\n\nconst HslAdjustmentControls = () => {\n  const { themeState, setThemeState, saveThemeCheckpoint, themeCheckpoint } = useEditorStore();\n  const debouncedUpdateRef = useRef<ReturnType<typeof debounce> | null>(null);\n  const [isExpanded, setIsExpanded] = useState(false);\n\n  // Get current HSL adjustments with fallback to defaults\n  const currentHslAdjustments = useMemo(\n    () => themeState.hslAdjustments ?? defaultThemeState.hslAdjustments!,\n    [themeState.hslAdjustments]\n  );\n\n  // Save checkpoint if HSL adjustments are at default values\n  useEffect(() => {\n    if (isDeepEqual(themeState.hslAdjustments, defaultThemeState.hslAdjustments)) {\n      saveThemeCheckpoint();\n    }\n  }, [themeState.hslAdjustments, saveThemeCheckpoint]);\n\n  // Setup debounced update function\n  useEffect(() => {\n    debouncedUpdateRef.current = debounce((hslAdjustments: ThemeEditorState[\"hslAdjustments\"]) => {\n      const {\n        hueShift = defaultThemeState.hslAdjustments!.hueShift,\n        saturationScale = defaultThemeState.hslAdjustments!.saturationScale,\n        lightnessScale = defaultThemeState.hslAdjustments!.lightnessScale,\n      } = hslAdjustments ?? {};\n\n      const adjustments = { hueShift, saturationScale, lightnessScale };\n      const state = themeCheckpoint ?? themeState;\n      const { light: lightStyles, dark: darkStyles } = state.styles;\n\n      const updatedLightStyles = Object.keys(lightStyles)\n        .filter((key) => !COMMON_STYLES.includes(key))\n        .reduce<Record<string, string>>((acc, key) => {\n          const colorKey = key as keyof typeof lightStyles;\n          return {\n            ...acc,\n            [key]: adjustColorByHsl(\n              lightStyles[colorKey] || \"\",\n              adjustments.hueShift,\n              adjustments.saturationScale,\n              adjustments.lightnessScale\n            ),\n          };\n        }, {});\n\n      const updatedDarkStyles = Object.keys(darkStyles)\n        .filter((key) => !COMMON_STYLES.includes(key))\n        .reduce<Record<string, string>>((acc, key) => {\n          const colorKey = key as keyof typeof darkStyles;\n          return {\n            ...acc,\n            [key]: adjustColorByHsl(\n              darkStyles[colorKey] || \"\",\n              adjustments.hueShift,\n              adjustments.saturationScale,\n              adjustments.lightnessScale\n            ),\n          };\n        }, {});\n\n      // Update theme state with all changes\n      setThemeState({\n        ...themeState,\n        hslAdjustments,\n        styles: {\n          light: { ...lightStyles, ...updatedLightStyles },\n          dark: { ...darkStyles, ...updatedDarkStyles },\n        },\n      });\n    }, 10);\n\n    return () => debouncedUpdateRef.current?.cancel();\n  }, [themeState, setThemeState, themeCheckpoint]);\n\n  // Handle HSL value changes\n  const handleHslChange = useCallback(\n    (property: keyof typeof currentHslAdjustments, value: number) => {\n      if (debouncedUpdateRef.current) {\n        debouncedUpdateRef.current({\n          ...currentHslAdjustments,\n          [property]: value,\n        });\n      }\n    },\n    [currentHslAdjustments]\n  );\n\n  const handleBatchHslChange = useCallback((value: typeof currentHslAdjustments) => {\n    if (debouncedUpdateRef.current) {\n      debouncedUpdateRef.current(value);\n    }\n  }, []);\n\n  const currentStyles = (themeCheckpoint ?? themeState).styles[themeState.currentMode];\n\n  return (\n    <div className=\"@container\">\n      {/* Responsive preset grid */}\n      <div\n        className={cn(\n          \"-m-1 mb-2 grid grid-cols-5 gap-2 overflow-hidden p-1 transition-all duration-300 ease-in-out @sm:grid-cols-7 @md:grid-cols-9 @lg:grid-cols-11 @xl:grid-cols-13\",\n          !isExpanded ? \"h-10\" : \"h-auto\"\n        )}\n      >\n        {HSL_PRESETS.map((preset) => (\n          <HslPresetButton\n            key={preset.label}\n            label={preset.label}\n            hueShift={preset.hueShift}\n            saturationScale={preset.saturationScale}\n            lightnessScale={preset.lightnessScale}\n            baseBg={currentStyles.background}\n            basePrimary={currentStyles.primary}\n            baseSecondary={currentStyles.secondary}\n            selected={\n              currentHslAdjustments.hueShift === preset.hueShift &&\n              currentHslAdjustments.saturationScale === preset.saturationScale &&\n              currentHslAdjustments.lightnessScale === preset.lightnessScale\n            }\n            adjustColorByHsl={adjustColorByHsl}\n            onClick={() => {\n              handleBatchHslChange(preset);\n            }}\n          />\n        ))}\n      </div>\n\n      {/* Show/Hide more button - shows if total presets exceed the smallest breakpoint's column count */}\n      {HSL_PRESETS.length > 5 && (\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          className=\"text-muted-foreground mb-2 flex w-full items-center justify-center text-xs\"\n          onClick={() => setIsExpanded(!isExpanded)}\n        >\n          {isExpanded ? \"Hide\" : \"Show more\"} presets\n          <ChevronDown\n            className={cn(\n              \"ml-1 h-4 w-4 transition-transform duration-200\",\n              isExpanded && \"rotate-180\"\n            )}\n          />\n        </Button>\n      )}\n\n      <SliderWithInput\n        value={currentHslAdjustments.hueShift}\n        onChange={(value) => handleHslChange(\"hueShift\", value)}\n        unit=\"deg\"\n        min={-180}\n        max={180}\n        step={1}\n        label=\"Hue\"\n      />\n      <SliderWithInput\n        value={currentHslAdjustments.saturationScale}\n        onChange={(value) => handleHslChange(\"saturationScale\", value)}\n        unit=\"x\"\n        min={0}\n        max={2}\n        step={0.01}\n        label=\"Saturation\"\n      />\n      <SliderWithInput\n        value={currentHslAdjustments.lightnessScale}\n        onChange={(value) => handleHslChange(\"lightnessScale\", value)}\n        unit=\"x\"\n        min={0.2}\n        max={2}\n        step={0.01}\n        label=\"Lightness\"\n      />\n    </div>\n  );\n};\n\nexport default HslAdjustmentControls;\n"
  },
  {
    "path": "components/editor/hsl-preset-button.tsx",
    "content": "\"use client\";\n\nimport type React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\n\ninterface HslPresetButtonProps {\n  label: string;\n  hueShift: number;\n  saturationScale: number;\n  lightnessScale: number;\n  baseBg: string;\n  basePrimary: string;\n  baseSecondary?: string;\n  onClick: () => void;\n  selected: boolean;\n  adjustColorByHsl: (\n    color: string,\n    hueShift: number,\n    saturationScale: number,\n    lightnessScale: number\n  ) => string;\n}\n\nexport const HslPresetButton: React.FC<HslPresetButtonProps> = ({\n  label,\n  hueShift,\n  saturationScale,\n  lightnessScale,\n  baseBg,\n  basePrimary,\n  baseSecondary = \"#888888\",\n  onClick,\n  selected,\n  adjustColorByHsl,\n}) => {\n  const previewBg = adjustColorByHsl(baseBg, hueShift, saturationScale, lightnessScale);\n  const previewPrimary = adjustColorByHsl(basePrimary, hueShift, saturationScale, lightnessScale);\n  const previewSecondary = adjustColorByHsl(\n    baseSecondary,\n    hueShift,\n    saturationScale,\n    lightnessScale\n  );\n\n  return (\n    <TooltipProvider>\n      <Tooltip>\n        <TooltipTrigger asChild>\n          <Button\n            type=\"button\"\n            onClick={onClick}\n            size=\"sm\"\n            variant=\"outline\"\n            className={cn(\n              \"relative h-8 w-full overflow-hidden rounded-md p-0 shadow-sm transition-all duration-200\",\n              \"hover:scale-105 hover:shadow-md\",\n              selected ? \"ring-primary ring-1 ring-offset-1\" : \"border-border border\"\n            )}\n            style={{ background: previewBg }}\n          >\n            <div className=\"absolute inset-0 flex items-center justify-center\">\n              <div className=\"flex h-full w-full\">\n                <div className=\"h-full w-1/2 rounded-l-md\" style={{ background: previewPrimary }} />\n                <div\n                  className=\"h-full w-1/2 rounded-r-md\"\n                  style={{ background: previewSecondary }}\n                />\n              </div>\n            </div>\n            {selected && (\n              <div className=\"bg-primary absolute right-0.5 bottom-0.5 h-2 w-2 rounded-full\" />\n            )}\n          </Button>\n        </TooltipTrigger>\n        <TooltipContent side=\"bottom\" className=\"text-xs font-medium\">\n          {label}\n        </TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  );\n};\n"
  },
  {
    "path": "components/editor/inspector-class-item.tsx",
    "content": "\"use client\";\n\nimport React, { memo, useCallback, useMemo } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { SquarePen } from \"lucide-react\";\nimport { FocusColorId, useColorControlFocus } from \"@/store/color-control-focus-store\";\nimport { segmentClassName } from \"@/lib/inspector/segment-classname\";\nimport { useEditorStore } from \"@/store/editor-store\";\n\ninterface InspectorClassItemProps {\n  className: string;\n}\n\nconst InspectorClassItem = memo(({ className }: InspectorClassItemProps) => {\n  const { focusColor } = useColorControlFocus();\n  const { themeState } = useEditorStore();\n  const styles = themeState.styles[themeState.currentMode];\n  const segments = useMemo(() => segmentClassName(className), [className]);\n\n  const handleClick = useCallback(\n    (e: React.MouseEvent) => {\n      e.stopPropagation();\n      e.preventDefault();\n      const color = segments.value;\n      if (color) {\n        focusColor(color as FocusColorId);\n      }\n    },\n    [segments.value, focusColor]\n  );\n\n  const renderSegmentedClassName = useCallback((): React.ReactNode => {\n    const parts = [];\n\n    if (segments.selector) {\n      parts.push(\n        <span key=\"selector\" className=\"text-foreground/60\">\n          {segments.selector}:\n        </span>\n      );\n    }\n\n    if (segments.prefix) {\n      parts.push(\n        <span key=\"prefix\" className=\"text-foreground\">\n          {segments.prefix}\n        </span>\n      );\n    }\n\n    if (segments.value) {\n      parts.push(\n        <span key=\"dash\" className=\"text-foreground/80\">\n          -\n        </span>,\n        <span key=\"value\" className=\"text-foreground font-bold\">\n          {segments.value}\n        </span>\n      );\n    }\n\n    if (segments.opacity) {\n      parts.push(\n        <span key=\"slash\" className=\"text-foreground/60\">\n          /\n        </span>,\n        <span key=\"opacity\" className=\"text-foreground/60\">\n          {segments.opacity}\n        </span>\n      );\n    }\n\n    return <>{parts}</>;\n  }, [segments]);\n\n  return (\n    <div\n      className=\"group hover:bg-foreground/10 flex cursor-pointer items-center justify-between gap-2 rounded-md p-1.5 transition-colors\"\n      onClick={handleClick}\n    >\n      <div className=\"flex items-center gap-1.5\">\n        <span\n          style={{\n            backgroundColor: styles[segments.value as keyof typeof styles],\n          }}\n          className={cn(\n            \"border-foreground ring-border block size-4 shrink-0 rounded-md border-1 ring-1\"\n          )}\n        />\n        <span className=\"font-mono text-xs\">{renderSegmentedClassName()}</span>\n      </div>\n      <SquarePen className=\"text-muted-foreground size-4 shrink-0 opacity-0 transition-opacity group-hover:opacity-100\" />\n    </div>\n  );\n});\n\nInspectorClassItem.displayName = \"InspectorClassItem\";\n\nexport default InspectorClassItem;\n"
  },
  {
    "path": "components/editor/inspector-overlay.tsx",
    "content": "\"use client\";\n\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from \"@/components/ui/hover-card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { useClassNames } from \"@/hooks/use-theme-inspector-classnames\";\nimport { cn } from \"@/lib/utils\";\nimport { Inspect } from \"lucide-react\";\nimport React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport InspectorClassItem from \"./inspector-class-item\";\n\ninterface InspectorState {\n  rect: DOMRect | null;\n  className: string;\n}\n\ninterface InspectorOverlayProps {\n  inspector: InspectorState;\n  enabled: boolean;\n  rootRef: React.RefObject<HTMLDivElement | null>;\n}\n\nconst InspectorOverlay = ({ inspector, enabled, rootRef }: InspectorOverlayProps) => {\n  const classNames = useClassNames(inspector.className);\n\n  if (!enabled || !inspector.rect || typeof window === \"undefined\" || !rootRef.current) {\n    return null;\n  }\n\n  // Get the container's bounding rect to convert from viewport coordinates to container-relative coordinates\n  const containerRect = rootRef.current.getBoundingClientRect();\n  const relativeRect = {\n    top: inspector.rect.top - containerRect.top,\n    left: inspector.rect.left - containerRect.left,\n    width: inspector.rect.width,\n    height: inspector.rect.height,\n  };\n\n  return createPortal(\n    <HoverCard open={true} defaultOpen={false}>\n      <HoverCardTrigger asChild>\n        <div\n          data-inspector-overlay\n          className={cn(\n            \"ring-primary ring-offset-background/90 pointer-events-none absolute z-50 ring-3 ring-offset-2\",\n            \"transition-all duration-100 ease-in-out\"\n          )}\n          style={{\n            top: relativeRect.top,\n            left: relativeRect.left,\n            width: relativeRect.width,\n            height: relativeRect.height,\n          }}\n        />\n      </HoverCardTrigger>\n\n      <HoverCardContent\n        data-inspector-overlay\n        side=\"top\"\n        align=\"start\"\n        className={cn(\n          \"bg-popover/85 text-popover-foreground pointer-events-auto relative w-auto max-w-[50vw] rounded-lg border p-0 shadow-xl backdrop-blur-lg\"\n        )}\n        sideOffset={8}\n      >\n        <div className=\"text-muted-foreground flex items-center gap-1.5 px-2 pt-2 text-sm\">\n          <Inspect className=\"size-4\" />\n          Inspector\n        </div>\n        <Separator className=\"my-1\" />\n        <div className=\"flex flex-col gap-1 px-1 pb-2\">\n          {classNames.map((cls) => (\n            <InspectorClassItem key={cls} className={cls} />\n          ))}\n        </div>\n      </HoverCardContent>\n    </HoverCard>,\n    rootRef.current\n  );\n};\n\nconst arePropsEqual = (\n  prevProps: InspectorOverlayProps,\n  nextProps: InspectorOverlayProps\n): boolean => {\n  if (prevProps.enabled !== nextProps.enabled) return false;\n  if (prevProps.rootRef !== nextProps.rootRef) return false;\n\n  const prevRect = prevProps.inspector.rect;\n  const nextRect = nextProps.inspector.rect;\n\n  if (!prevRect && !nextRect)\n    return prevProps.inspector.className === nextProps.inspector.className;\n  if (!prevRect || !nextRect) return false;\n\n  return (\n    prevRect.top === nextRect.top &&\n    prevRect.left === nextRect.left &&\n    prevRect.width === nextRect.width &&\n    prevRect.height === nextRect.height &&\n    prevProps.inspector.className === nextProps.inspector.className\n  );\n};\n\nexport default React.memo(InspectorOverlay, arePropsEqual);\n"
  },
  {
    "path": "components/editor/mention-list.tsx",
    "content": "\"use client\";\n\nimport React, { forwardRef, useEffect, useImperativeHandle, useState, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\n// Define the structure of the theme item object\ninterface ThemeItem {\n  id: string;\n  label: string;\n}\n\ninterface MentionListProps {\n  items: ThemeItem[]; // Update items type to ThemeItem[]\n  command: (item: { id: string; label: string }) => void; // Update command type if needed, here passing the whole object\n}\n\n// Use a type for the ref handle if needed, e.g., { onKeyDown: ... }\n// Using `any` for now as in the original code\nexport interface MentionListRef {\n  onKeyDown: (props: { event: KeyboardEvent }) => boolean;\n}\n\nexport const MentionList = forwardRef<MentionListRef, MentionListProps>((props, ref) => {\n  const [selectedIndex, setSelectedIndex] = useState(0);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const selectedItemRef = useRef<HTMLButtonElement>(null);\n\n  // Function to select item (adapted from reference)\n  const selectItem = (index: number) => {\n    const item = props.items[index];\n    if (item) {\n      // Pass the whole item object to the command function\n      props.command(item);\n    }\n  };\n\n  // Arrow key handlers using modulo (adapted from reference)\n  const upHandler = () => {\n    setSelectedIndex((prevIndex) => (prevIndex + props.items.length - 1) % props.items.length);\n  };\n\n  const downHandler = () => {\n    setSelectedIndex((prevIndex) => (prevIndex + 1) % props.items.length);\n  };\n\n  const enterHandler = () => {\n    selectItem(selectedIndex);\n  };\n\n  useEffect(() => setSelectedIndex(0), [props.items]);\n\n  // Auto-scroll effect when selectedIndex changes\n  useEffect(() => {\n    if (selectedItemRef.current && containerRef.current) {\n      selectedItemRef.current.scrollIntoView({\n        behavior: \"smooth\",\n        block: \"nearest\",\n      });\n    }\n  }, [selectedIndex]);\n\n  useImperativeHandle(ref, () => ({\n    onKeyDown: ({ event }) => {\n      if (event.key === \"ArrowUp\") {\n        // Use modulo handlers\n        upHandler();\n        return true;\n      }\n\n      if (event.key === \"ArrowDown\") {\n        // Use modulo handlers\n        downHandler();\n        return true;\n      }\n\n      if (event.key === \"Enter\") {\n        // Use enter handler\n        enterHandler();\n        return true;\n      }\n\n      if (event.key === \"Tab\") {\n        // Use tab handler\n        enterHandler();\n        return true;\n      }\n\n      return false;\n    },\n  }));\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn(\n        \"bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md\",\n        \"scrollbar-thin max-h-[180px] overflow-y-auto\"\n      )}\n    >\n      {props.items.length ? (\n        props.items.map((item, index) => (\n          <button\n            ref={index === selectedIndex ? selectedItemRef : null}\n            // Use Tailwind classes mimicking shadcn/ui DropdownMenuItem with cn utility\n            className={cn(\n              \"focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground relative flex w-full items-center rounded-sm p-1.5 text-xs transition-colors outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n              index === selectedIndex && \"bg-accent text-accent-foreground\"\n            )}\n            key={item.id} // Use item.id as the key\n            onClick={(e) => {\n              e.stopPropagation();\n              selectItem(index);\n            }}\n          >\n            {item.label}\n          </button>\n        ))\n      ) : (\n        <div\n          className={cn(\n            \"text-muted-foreground relative flex cursor-default items-center rounded-sm p-1.5 text-xs select-none\"\n          )}\n        >\n          No result\n        </div>\n      )}\n    </div>\n  );\n});\n\nMentionList.displayName = \"MentionList\";\n"
  },
  {
    "path": "components/editor/mention-suggestion.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { ReactRenderer } from \"@tiptap/react\";\nimport tippy from \"tippy.js\";\nimport { MentionList } from \"@/components/editor/mention-list\"; // We'll create this component next\nimport { useThemePresetStore } from \"@/store/theme-preset-store\"; // Import the theme store\n\nexport const suggestion = {\n  items: ({ query }: { query: string }) => {\n    // Get all presets from the store\n    const allPresets = useThemePresetStore.getState().getAllPresets();\n\n    // Convert presets object to the required array format { id: string, label: string }\n    const themeItems = Object.entries(allPresets).map(([id, preset]) => ({\n      id: id, // Use the preset key as the id\n      label: preset.label, // Use the preset label\n    }));\n\n    // Filter based on the query\n    return themeItems\n      .filter((item) => {\n        const labelWithoutSpaces =\n          item.label?.replace(/\\s+/g, \"\").toLowerCase() || \"\";\n        const queryWithoutSpaces = query.replace(/\\s+/g, \"\").toLowerCase();\n        return labelWithoutSpaces.includes(queryWithoutSpaces);\n      })\n      .slice(0, 7)\n      .concat({ id: \"editor:current-changes\", label: \"Current Theme\" }); // Limit to 5 suggestions\n  },\n\n  render: () => {\n    let component: ReactRenderer | null = null;\n    let popup: any | null = null;\n\n    return {\n      onStart: (props: any) => {\n        component = new ReactRenderer(MentionList, {\n          props,\n          editor: props.editor,\n        });\n\n        if (!props.clientRect) {\n          return;\n        }\n\n        popup = tippy(\"body\", {\n          getReferenceClientRect: props.clientRect,\n          appendTo: () => document.body,\n          content: component.element,\n          showOnCreate: true,\n          interactive: true,\n          trigger: \"manual\",\n          placement: \"bottom-start\",\n        });\n      },\n\n      onUpdate(props: any) {\n        component?.updateProps(props);\n\n        if (!props.clientRect) {\n          return;\n        }\n\n        popup?.[0]?.setProps({\n          getReferenceClientRect: props.clientRect,\n        });\n      },\n\n      onKeyDown(props: any) {\n        if (props.event.key === \"Escape\") {\n          popup?.[0]?.hide();\n          return true;\n        }\n\n        // @ts-expect-error - This is a valid way to access the component's methods\n        return component?.ref?.onKeyDown(props);\n      },\n\n      onExit() {\n        popup?.[0]?.destroy();\n        component?.destroy();\n        popup = null;\n        component = null;\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "components/editor/section-context.tsx",
    "content": "import { createContext } from \"react\";\n\ninterface SectionContextType {\n  /** Whether the parent ControlSection is currently expanded */\n  isExpanded: boolean;\n  /** Set the expanded state explicitly */\n  setIsExpanded: (expanded: boolean) => void;\n  /** Helper to toggle the expanded state */\n  toggleExpanded: () => void;\n}\n\n/**\n * Context that allows descendants of a ControlSection to query or mutate\n * the expanded / collapsed state of their parent section.\n */\nexport const SectionContext = createContext<SectionContextType | undefined>(undefined);\n"
  },
  {
    "path": "components/editor/shadow-control.tsx",
    "content": "import React from \"react\";\nimport { SliderWithInput } from \"./slider-with-input\";\nimport ColorPicker from \"./color-picker\";\n\ninterface ShadowControlProps {\n  shadowColor: string;\n  shadowOpacity: number;\n  shadowBlur: number;\n  shadowSpread: number;\n  shadowOffsetX: number;\n  shadowOffsetY: number;\n  onChange: (key: string, value: string | number) => void;\n}\n\nconst ShadowControl: React.FC<ShadowControlProps> = ({\n  shadowColor,\n  shadowOpacity,\n  shadowBlur,\n  shadowSpread,\n  shadowOffsetX,\n  shadowOffsetY,\n  onChange,\n}) => {\n  return (\n    <div>\n      <ColorPicker\n        color={shadowColor}\n        onChange={(color) => onChange(\"shadow-color\", color)}\n        label=\"Color\"\n      />\n\n      <SliderWithInput\n        value={shadowOpacity}\n        onChange={(value) => onChange(\"shadow-opacity\", value)}\n        min={0}\n        max={1}\n        step={0.01}\n        unit=\"\"\n        label=\"Opacity\"\n      />\n\n      <SliderWithInput\n        value={shadowBlur}\n        onChange={(value) => onChange(\"shadow-blur\", value)}\n        min={0}\n        max={50}\n        step={0.5}\n        unit=\"px\"\n        label=\"Blur\"\n      />\n\n      <SliderWithInput\n        value={shadowSpread}\n        onChange={(value) => onChange(\"shadow-spread\", value)}\n        min={-50}\n        max={50}\n        step={0.5}\n        unit=\"px\"\n        label=\"Spread\"\n      />\n\n      <SliderWithInput\n        value={shadowOffsetX}\n        onChange={(value) => onChange(\"shadow-offset-x\", value)}\n        min={-50}\n        max={50}\n        step={0.5}\n        unit=\"px\"\n        label=\"Offset X\"\n      />\n\n      <SliderWithInput\n        value={shadowOffsetY}\n        onChange={(value) => onChange(\"shadow-offset-y\", value)}\n        min={-50}\n        max={50}\n        step={0.5}\n        unit=\"px\"\n        label=\"Offset Y\"\n      />\n    </div>\n  );\n};\n\nexport default ShadowControl;\n"
  },
  {
    "path": "components/editor/share-dialog.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n} from \"@/components/ui/revola\";\nimport { useCopyToClipboard } from \"@/hooks/use-copy-to-clipboard\";\nimport { Check, Copy } from \"lucide-react\";\n\ninterface ShareDialogProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  url: string;\n}\n\nexport function ShareDialog({ open, onOpenChange, url }: ShareDialogProps) {\n  const { isCopying, hasCopied, copyToClipboard } = useCopyToClipboard();\n\n  const handleCopy = async () => {\n    await copyToClipboard(url, {\n      title: \"Theme URL copied to clipboard!\",\n    });\n  };\n\n  return (\n    <ResponsiveDialog open={open} onOpenChange={onOpenChange} onlyDialog>\n      <ResponsiveDialogContent className=\"overflow-hidden shadow-lg sm:max-w-100\">\n        <div className=\"space-y-6 p-6\">\n          <ResponsiveDialogHeader>\n            <ResponsiveDialogTitle>Share Theme</ResponsiveDialogTitle>\n            <ResponsiveDialogDescription>\n              Anyone with this URL will be able to view this theme.\n            </ResponsiveDialogDescription>\n          </ResponsiveDialogHeader>\n\n          <div className=\"flex items-center space-x-2\">\n            <Input\n              readOnly\n              value={url}\n              onClick={(e) => e.currentTarget.select()}\n              className=\"selection:bg-primary selection:text-primary-foreground flex-1\"\n            />\n            <Button size=\"icon\" disabled={isCopying} onClick={handleCopy} variant=\"outline\">\n              {hasCopied ? <Check className=\"h-4 w-4\" /> : <Copy className=\"h-4 w-4\" />}\n            </Button>\n          </div>\n        </div>\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n}\n"
  },
  {
    "path": "components/editor/slider-with-input.tsx",
    "content": "import { useEffect, useState } from \"react\";\nimport { Label } from \"../ui/label\";\nimport { Input } from \"../ui/input\";\nimport { Slider } from \"../ui/slider\";\n\nexport const SliderWithInput = ({\n  value,\n  onChange,\n  min,\n  max,\n  step = 1,\n  label,\n  unit = \"px\",\n}: {\n  value: number;\n  onChange: (value: number) => void;\n  min: number;\n  max: number;\n  step?: number;\n  label: string;\n  unit?: string;\n}) => {\n  const [localValue, setLocalValue] = useState(value.toString());\n\n  useEffect(() => {\n    setLocalValue(value.toString());\n  }, [value]);\n\n  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const raw = e.target.value;\n    setLocalValue(raw);\n    const num = parseFloat(raw.replace(',', '.'));\n    if (!isNaN(num)) {\n      onChange(Math.max(min, Math.min(max, num)));\n    }\n  };\n\n  return (\n    <div className=\"flex items-center gap-2 py-0.5\">\n      <Label\n        htmlFor={`slider-${label.replace(/\\s+/g, \"-\").toLowerCase()}`}\n        className=\"text-muted-foreground w-16 shrink-0 text-[11px] font-medium\"\n      >\n        {label}\n      </Label>\n      <Slider\n        id={`slider-${label.replace(/\\s+/g, \"-\").toLowerCase()}`}\n        value={[value]}\n        min={min}\n        max={max}\n        step={step}\n        onValueChange={(values) => {\n          const newValue = values[0];\n          setLocalValue(newValue.toString());\n          onChange(newValue);\n        }}\n        className=\"min-w-0 flex-1\"\n      />\n      <div className=\"flex shrink-0 items-center gap-1\">\n        <Input\n          id={`input-${label.replace(/\\s+/g, \"-\").toLowerCase()}`}\n          type=\"number\"\n          value={localValue}\n          onChange={handleChange}\n          onBlur={() => setLocalValue(value.toString())}\n          min={min}\n          max={max}\n          step={step}\n          className=\"h-6 w-14 px-1.5 text-xs\"\n        />\n        <span className=\"text-muted-foreground w-5 text-[11px]\">{unit}</span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "components/editor/theme-control-actions.tsx",
    "content": "import { FileCode, Palette, RefreshCw, LucideIcon, Undo2 } from \"lucide-react\";\nimport { Button } from \"../ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"../ui/dropdown-menu\";\nimport { cn } from \"@/lib/utils\";\n\ninterface MenuItemProps {\n  icon: LucideIcon;\n  label: string;\n  onClick: () => void;\n  disabled?: boolean;\n  title?: string;\n}\n\nconst MenuItem = ({\n  icon: Icon,\n  label,\n  onClick,\n  disabled,\n  title,\n}: MenuItemProps) => {\n  return (\n    <DropdownMenuItem\n      onClick={onClick}\n      disabled={disabled}\n      className={cn(\n        \"flex items-center gap-2 px-3 py-2 rounded-md transition-colors\",\n        disabled\n          ? \"opacity-50 cursor-not-allowed\"\n          : \"hover:bg-accent/50 cursor-pointer\"\n      )}\n      title={title}\n    >\n      <Icon className=\"h-4 w-4 text-muted-foreground\" />\n      <span>{label}</span>\n    </DropdownMenuItem>\n  );\n};\n\ninterface ThemeControlActionsProps {\n  hasChanges: boolean;\n  hasPresetChanges: boolean;\n  onReset: () => void;\n  onResetToPreset: () => void;\n  onImportClick: () => void;\n}\n\nconst ThemeControlActions = ({\n  hasChanges,\n  hasPresetChanges,\n  onReset,\n  onResetToPreset,\n  onImportClick,\n}: ThemeControlActionsProps) => {\n  const menuItems: MenuItemProps[] = [\n    {\n      icon: FileCode,\n      label: \"Import from CSS file\",\n      onClick: onImportClick,\n    },\n    {\n      icon: RefreshCw,\n      label: \"Reset to Current Preset\",\n      onClick: onResetToPreset,\n      disabled: !hasPresetChanges,\n      title: hasPresetChanges ? \"Reset to current preset\" : \"No changes from preset\",\n    },\n    {\n      icon: Undo2,\n      label: \"Reset to Default Theme\",\n      onClick: onReset,\n      disabled: !hasChanges,\n      title: hasChanges ? \"Reset to base theme\" : \"No changes to reset\",\n    },\n  ];\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          className=\"h-8 px-2 gap-1.5 text-muted-foreground hover:text-foreground hover:bg-accent/50\"\n        >\n          <Palette className=\"size-3.5\" />\n          <span className=\"text-sm\">Options</span>\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"w-fit p-2\">\n        {menuItems.map((item, index) => (\n          <MenuItem key={index} {...item} />\n        ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n};\n\nexport default ThemeControlActions;\n"
  },
  {
    "path": "components/editor/theme-control-panel.tsx",
    "content": "\"use client\";\n\nimport { AlertCircle, Sparkle } from \"lucide-react\";\nimport React from \"react\";\n\nimport { ChatInterface } from \"@/components/editor/ai/chat-interface\";\nimport { ColorsTabContent } from \"@/components/editor/colors-tab-content\";\nimport ControlSection from \"@/components/editor/control-section\";\nimport { FontPicker } from \"@/components/editor/font-picker\";\nimport HslAdjustmentControls from \"@/components/editor/hsl-adjustment-controls\";\nimport ShadowControl from \"@/components/editor/shadow-control\";\nimport { SliderWithInput } from \"@/components/editor/slider-with-input\";\nimport ThemePresetSelect from \"@/components/editor/theme-preset-select\";\nimport TabsTriggerPill from \"@/components/editor/theme-preview/tabs-trigger-pill\";\nimport { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { Label } from \"@/components/ui/label\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Tabs, TabsContent, TabsList } from \"@/components/ui/tabs\";\nimport { COMMON_STYLES, defaultThemeState } from \"@/config/theme\";\nimport { useAIThemeGenerationCore } from \"@/hooks/use-ai-theme-generation-core\";\nimport { useControlsTabFromUrl, type ControlTab } from \"@/hooks/use-controls-tab-from-url\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { type FontInfo } from \"@/types/fonts\";\nimport { ThemeStyles, ThemeStyleProps } from \"@/types/theme\";\nimport { buildFontFamily } from \"@/utils/fonts\";\nimport { getAppliedThemeFont } from \"@/utils/theme-fonts\";\n\ninterface ThemeControlPanelProps {\n  styles: ThemeStyles;\n  currentMode: \"light\" | \"dark\";\n  onChange: (styles: ThemeStyles) => void;\n}\n\nconst ThemeControlPanel = ({ styles, currentMode, onChange }: ThemeControlPanelProps) => {\n  const { themeState } = useEditorStore();\n  const { tab, handleSetTab } = useControlsTabFromUrl();\n  const { isGeneratingTheme } = useAIThemeGenerationCore();\n\n  const currentStyles = React.useMemo(\n    () => ({\n      ...defaultThemeState.styles[currentMode],\n      ...styles?.[currentMode],\n    }),\n    [currentMode, styles]\n  );\n\n  const updateStyle = React.useCallback(\n    <K extends keyof typeof currentStyles>(key: K, value: (typeof currentStyles)[K]) => {\n      // apply common styles to both light and dark modes\n      if (COMMON_STYLES.includes(key)) {\n        onChange({\n          ...styles,\n          light: { ...styles.light, [key]: value },\n          dark: { ...styles.dark, [key]: value },\n        });\n        return;\n      }\n\n      onChange({\n        ...styles,\n        [currentMode]: {\n          ...currentStyles,\n          [key]: value,\n        },\n      });\n    },\n    [onChange, styles, currentMode, currentStyles]\n  );\n\n  const updateStyles = React.useCallback(\n    (updates: Partial<ThemeStyleProps>) => {\n      onChange({\n        ...styles,\n        [currentMode]: {\n          ...currentStyles,\n          ...updates,\n        },\n      });\n    },\n    [onChange, styles, currentMode, currentStyles]\n  );\n\n  // Ensure we have valid styles for the current mode\n  if (!currentStyles) {\n    return null; // Or some fallback UI\n  }\n\n  const radius = parseFloat(currentStyles.radius.replace(\"rem\", \"\"));\n\n  return (\n    <>\n      <div className=\"border-b\">\n        <ThemePresetSelect className=\"h-14 rounded-none\" disabled={isGeneratingTheme} />\n      </div>\n      <div className=\"flex min-h-0 flex-1 flex-col space-y-4\">\n        <Tabs\n          value={tab}\n          onValueChange={(v) => handleSetTab(v as ControlTab)}\n          className=\"flex min-h-0 w-full flex-1 flex-col\"\n        >\n          <HorizontalScrollArea className=\"mt-2 mb-1 px-4\">\n            <TabsList className=\"bg-background text-muted-foreground inline-flex w-fit items-center justify-center rounded-full px-0\">\n              <TabsTriggerPill value=\"colors\">Colors</TabsTriggerPill>\n              <TabsTriggerPill value=\"typography\">Typography</TabsTriggerPill>\n              <TabsTriggerPill value=\"other\">Other</TabsTriggerPill>\n              <TabsTriggerPill\n                value=\"ai\"\n                className=\"data-[state=active]:[--effect:var(--secondary-foreground)] data-[state=active]:[--foreground:var(--muted-foreground)] data-[state=active]:[--muted-foreground:var(--effect)]\"\n              >\n                <Sparkle className=\"mr-1 size-3.5 text-current\" />\n                <span className=\"animate-text via-foreground from-muted-foreground to-muted-foreground flex items-center gap-1 bg-gradient-to-r from-50% via-60% to-100% bg-[200%_auto] bg-clip-text text-sm text-transparent\">\n                  Generate\n                </span>\n              </TabsTriggerPill>\n            </TabsList>\n          </HorizontalScrollArea>\n\n          <TabsContent value=\"colors\" className=\"mt-1 size-full overflow-hidden\">\n            <ColorsTabContent currentStyles={currentStyles} updateStyle={updateStyle} updateStyles={updateStyles} />\n          </TabsContent>\n\n          <TabsContent value=\"typography\" className=\"mt-1 size-full overflow-hidden\">\n            <ScrollArea className=\"h-full px-4\">\n              <div className=\"text-muted-foreground mb-2 flex items-center gap-2 text-[11px]\">\n                <AlertCircle className=\"size-3.5 shrink-0\" />\n                <p>\n                  Custom fonts require embedding.{\" \"}\n                  <a\n                    href=\"https://tailwindcss.com/docs/font-family\"\n                    target=\"_blank\"\n                    className=\"text-foreground/70 hover:text-foreground underline underline-offset-2 transition-colors\"\n                  >\n                    Learn more\n                  </a>\n                </p>\n              </div>\n\n              <ControlSection title=\"Font Family\" expanded>\n                <div className=\"space-y-1.5\">\n                  <div className=\"flex items-center gap-2\">\n                    <Label htmlFor=\"font-sans\" className=\"text-muted-foreground w-16 shrink-0 text-[11px] font-medium\">\n                      Sans-Serif\n                    </Label>\n                    <div className=\"min-w-0 flex-1\">\n                      <FontPicker\n                        value={getAppliedThemeFont(themeState, \"font-sans\") || undefined}\n                        category=\"sans-serif\"\n                        placeholder=\"Sans-serif font...\"\n                        onSelect={(font: FontInfo) => {\n                          const fontFamily = buildFontFamily(font.family, font.category);\n                          updateStyle(\"font-sans\", fontFamily);\n                        }}\n                      />\n                    </div>\n                  </div>\n\n                  <div className=\"flex items-center gap-2\">\n                    <Label htmlFor=\"font-serif\" className=\"text-muted-foreground w-16 shrink-0 text-[11px] font-medium\">\n                      Serif\n                    </Label>\n                    <div className=\"min-w-0 flex-1\">\n                      <FontPicker\n                        value={getAppliedThemeFont(themeState, \"font-serif\") || undefined}\n                        category=\"serif\"\n                        placeholder=\"Serif font...\"\n                        onSelect={(font: FontInfo) => {\n                          const fontFamily = buildFontFamily(font.family, font.category);\n                          updateStyle(\"font-serif\", fontFamily);\n                        }}\n                      />\n                    </div>\n                  </div>\n\n                  <div className=\"flex items-center gap-2\">\n                    <Label htmlFor=\"font-mono\" className=\"text-muted-foreground w-16 shrink-0 text-[11px] font-medium\">\n                      Mono\n                    </Label>\n                    <div className=\"min-w-0 flex-1\">\n                      <FontPicker\n                        value={getAppliedThemeFont(themeState, \"font-mono\") || undefined}\n                        category=\"monospace\"\n                        placeholder=\"Monospace font...\"\n                        onSelect={(font: FontInfo) => {\n                          const fontFamily = buildFontFamily(font.family, font.category);\n                          updateStyle(\"font-mono\", fontFamily);\n                        }}\n                      />\n                    </div>\n                  </div>\n                </div>\n              </ControlSection>\n\n              <ControlSection title=\"Letter Spacing\" expanded>\n                <SliderWithInput\n                  value={parseFloat(currentStyles[\"letter-spacing\"]?.replace(\"em\", \"\"))}\n                  onChange={(value) => updateStyle(\"letter-spacing\", `${value}em`)}\n                  min={-0.5}\n                  max={0.5}\n                  step={0.025}\n                  unit=\"em\"\n                  label=\"Tracking\"\n                />\n              </ControlSection>\n            </ScrollArea>\n          </TabsContent>\n\n          <TabsContent value=\"other\" className=\"mt-1 size-full overflow-hidden\">\n            <ScrollArea className=\"h-full px-4\">\n              <ControlSection title=\"HSL Adjustments\" expanded>\n                <HslAdjustmentControls />\n              </ControlSection>\n\n              <ControlSection title=\"Radius\" expanded>\n                <SliderWithInput\n                  value={radius}\n                  onChange={(value) => updateStyle(\"radius\", `${value}rem`)}\n                  min={0}\n                  max={5}\n                  step={0.025}\n                  unit=\"rem\"\n                  label=\"Radius\"\n                />\n              </ControlSection>\n\n              <ControlSection title=\"Spacing\">\n                <SliderWithInput\n                  value={parseFloat(currentStyles?.spacing?.replace(\"rem\", \"\") || \"0\")}\n                  onChange={(value) => updateStyle(\"spacing\", `${value}rem`)}\n                  min={0.15}\n                  max={0.35}\n                  step={0.01}\n                  unit=\"rem\"\n                  label=\"Spacing\"\n                />\n              </ControlSection>\n\n              <ControlSection title=\"Shadow\">\n                <ShadowControl\n                  shadowColor={currentStyles[\"shadow-color\"]}\n                  shadowOpacity={parseFloat(currentStyles[\"shadow-opacity\"])}\n                  shadowBlur={parseFloat(currentStyles[\"shadow-blur\"]?.replace(\"px\", \"\"))}\n                  shadowSpread={parseFloat(currentStyles[\"shadow-spread\"]?.replace(\"px\", \"\"))}\n                  shadowOffsetX={parseFloat(currentStyles[\"shadow-offset-x\"]?.replace(\"px\", \"\"))}\n                  shadowOffsetY={parseFloat(currentStyles[\"shadow-offset-y\"]?.replace(\"px\", \"\"))}\n                  onChange={(key, value) => {\n                    if (key === \"shadow-color\") {\n                      updateStyle(key, value as string);\n                    } else if (key === \"shadow-opacity\") {\n                      updateStyle(key, value.toString());\n                    } else {\n                      updateStyle(key as keyof ThemeStyleProps, `${value}px`);\n                    }\n                  }}\n                />\n              </ControlSection>\n            </ScrollArea>\n          </TabsContent>\n\n          <TabsContent value=\"ai\" className=\"mt-1 size-full overflow-hidden\">\n            <ChatInterface />\n          </TabsContent>\n        </Tabs>\n      </div>\n    </>\n  );\n};\n\nexport default ThemeControlPanel;\n"
  },
  {
    "path": "components/editor/theme-font-select.tsx",
    "content": "// THIS COMPONENT MIGHT BE REPLACED BY THE GOOGLE FONT PICKER\n\nimport React, { useMemo } from \"react\";\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\n\ninterface ThemeFontSelectProps {\n  fonts: Record<string, string>;\n  defaultValue: string;\n  currentFont: string | null;\n  onFontChange: (font: string) => void;\n}\n\nconst ThemeFontSelect: React.FC<ThemeFontSelectProps> = ({\n  fonts,\n  defaultValue,\n  currentFont,\n  onFontChange,\n}) => {\n  const fontNames = useMemo(() => [\"System\", ...Object.keys(fonts)], [fonts]);\n  const value = currentFont ? (fonts[currentFont] ?? defaultValue) : defaultValue;\n\n  return (\n    <Select value={value || \"\"} onValueChange={onFontChange}>\n      <div className=\"flex w-full items-center gap-1\">\n        <SelectTrigger className=\"bg-secondary text-secondary-foreground w-full\">\n          <SelectValue placeholder=\"Select theme font\" />\n        </SelectTrigger>\n      </div>\n      <SelectContent className=\"max-h-[400px]\">\n        <SelectGroup>\n          {fontNames.map((fontName) => (\n            <SelectItem key={fontName} value={fonts[fontName] ?? defaultValue}>\n              <span\n                style={{\n                  fontFamily: fonts[fontName] ?? defaultValue,\n                }}\n              >\n                {fontName}\n              </span>\n            </SelectItem>\n          ))}\n        </SelectGroup>\n      </SelectContent>\n    </Select>\n  );\n};\n\nexport default ThemeFontSelect;\n"
  },
  {
    "path": "components/editor/theme-preset-select.tsx",
    "content": "import { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Command, CommandEmpty, CommandGroup, CommandItem, CommandList } from \"@/components/ui/command\";\nimport { Input } from \"@/components/ui/input\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { cn } from \"@/lib/utils\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { useThemePresetStore } from \"@/store/theme-preset-store\";\nimport { ThemePreset } from \"@/types/theme\";\nimport { getPresetThemeStyles } from \"@/utils/theme-preset-helper\";\nimport {\n  ArrowLeft,\n  ArrowRight,\n  Check,\n  ChevronDown,\n  Heart,\n  Search,\n  Settings,\n  Shuffle,\n} from \"lucide-react\";\nimport Link from \"next/link\";\nimport React, { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { ThemeToggle } from \"../theme-toggle\";\nimport { TooltipWrapper } from \"../tooltip-wrapper\";\n\ninterface ThemePresetSelectProps extends React.ComponentProps<typeof Button> {\n  withCycleThemes?: boolean;\n}\n\ninterface ColorBoxProps {\n  color: string;\n}\n\nconst ColorBox: React.FC<ColorBoxProps> = ({ color }) => (\n  <div className=\"border-muted h-3 w-3 rounded-sm border\" style={{ backgroundColor: color }} />\n);\n\ninterface ThemeColorsProps {\n  presetName: string;\n  mode: \"light\" | \"dark\";\n}\n\nconst ThemeColors: React.FC<ThemeColorsProps> = ({ presetName, mode }) => {\n  const styles = getPresetThemeStyles(presetName)[mode];\n  return (\n    <div className=\"flex gap-0.5\">\n      <ColorBox color={styles.primary} />\n      <ColorBox color={styles.accent} />\n      <ColorBox color={styles.secondary} />\n      <ColorBox color={styles.border} />\n    </div>\n  );\n};\n\nconst isThemeNew = (preset: ThemePreset) => {\n  if (!preset.createdAt) return false;\n  const createdAt = new Date(preset.createdAt);\n  const timePeriod = new Date();\n  timePeriod.setDate(timePeriod.getDate() - 5);\n  return createdAt > timePeriod;\n};\n\nconst ThemeControls = () => {\n  const applyThemePreset = useEditorStore((store) => store.applyThemePreset);\n  const presets = useThemePresetStore((store) => store.getAllPresets());\n\n  const presetNames = useMemo(() => [\"default\", ...Object.keys(presets)], [presets]);\n\n  const randomize = useCallback(() => {\n    const random = Math.floor(Math.random() * presetNames.length);\n    applyThemePreset(presetNames[random]);\n  }, [presetNames, applyThemePreset]);\n\n  return (\n    <div className=\"flex gap-1\">\n      <ThemeToggle variant=\"ghost\" size=\"icon\" className=\"size-6 p-1\" />\n\n      <TooltipWrapper label=\"Random theme\" asChild>\n        <Button variant=\"ghost\" size=\"sm\" className=\"size-6 p-1\" onClick={randomize}>\n          <Shuffle className=\"h-3.5 w-3.5\" />\n        </Button>\n      </TooltipWrapper>\n    </div>\n  );\n};\n\ninterface ThemeCycleButtonProps extends React.ComponentProps<typeof Button> {\n  direction: \"prev\" | \"next\";\n}\n\nconst ThemeCycleButton: React.FC<ThemeCycleButtonProps> = ({\n  direction,\n  onClick,\n  className,\n  ...props\n}) => (\n  <Tooltip>\n    <TooltipTrigger asChild>\n      <Button\n        variant=\"ghost\"\n        size=\"icon\"\n        className={cn(\"aspect-square h-full shrink-0\", className)}\n        onClick={onClick}\n        {...props}\n      >\n        {direction === \"prev\" ? (\n          <ArrowLeft className=\"h-4 w-4\" />\n        ) : (\n          <ArrowRight className=\"h-4 w-4\" />\n        )}\n      </Button>\n    </TooltipTrigger>\n    <TooltipContent>{direction === \"prev\" ? \"Previous theme\" : \"Next theme\"}</TooltipContent>\n  </Tooltip>\n);\n\ninterface ThemePresetCycleControlsProps extends React.ComponentProps<typeof Button> {\n  filteredPresets: string[];\n  currentPresetName: string;\n  className?: string;\n}\n\nconst ThemePresetCycleControls: React.FC<ThemePresetCycleControlsProps> = ({\n  filteredPresets,\n  currentPresetName,\n  className,\n  ...props\n}) => {\n  const applyThemePreset = useEditorStore((store) => store.applyThemePreset);\n\n  const currentIndex =\n    useMemo(\n      () => filteredPresets.indexOf(currentPresetName || \"default\"),\n      [filteredPresets, currentPresetName]\n    ) ?? 0;\n\n  const cycleTheme = useCallback(\n    (direction: \"prev\" | \"next\") => {\n      const newIndex =\n        direction === \"next\"\n          ? (currentIndex + 1) % filteredPresets.length\n          : (currentIndex - 1 + filteredPresets.length) % filteredPresets.length;\n      applyThemePreset(filteredPresets[newIndex]);\n    },\n    [currentIndex, filteredPresets, applyThemePreset]\n  );\n  return (\n    <>\n      <Separator orientation=\"vertical\" className=\"min-h-8\" />\n\n      <ThemeCycleButton\n        direction=\"prev\"\n        size=\"icon\"\n        className={cn(\"aspect-square min-h-8 w-auto\", className)}\n        onClick={() => cycleTheme(\"prev\")}\n        {...props}\n      />\n\n      <Separator orientation=\"vertical\" className=\"min-h-8\" />\n\n      <ThemeCycleButton\n        direction=\"next\"\n        size=\"icon\"\n        className={cn(\"aspect-square min-h-8 w-auto\", className)}\n        onClick={() => cycleTheme(\"next\")}\n        {...props}\n      />\n    </>\n  );\n};\n\nconst ThemePresetSelect: React.FC<ThemePresetSelectProps> = ({\n  withCycleThemes = true,\n  className,\n  ...props\n}) => {\n  const themeState = useEditorStore((store) => store.themeState);\n  const applyThemePreset = useEditorStore((store) => store.applyThemePreset);\n  const hasUnsavedChanges = useEditorStore((store) => store.hasUnsavedChanges);\n  const currentPreset = themeState.preset;\n  const mode = themeState.currentMode;\n\n  const presets = useThemePresetStore((store) => store.getAllPresets());\n  const loadSavedPresets = useThemePresetStore((store) => store.loadSavedPresets);\n  const unloadSavedPresets = useThemePresetStore((store) => store.unloadSavedPresets);\n\n  const [search, setSearch] = useState(\"\");\n\n  const { data: session } = authClient.useSession();\n\n  useEffect(() => {\n    if (session?.user) {\n      loadSavedPresets();\n    } else {\n      unloadSavedPresets();\n    }\n  }, [loadSavedPresets, unloadSavedPresets, session?.user]);\n\n  const isSavedTheme = useCallback(\n    (presetId: string) => {\n      return presets[presetId]?.source === \"SAVED\";\n    },\n    [presets]\n  );\n\n  const presetNames = useMemo(() => [\"default\", ...Object.keys(presets)], [presets]);\n  const currentPresetName = presetNames?.find((name) => name === currentPreset);\n\n  const filteredPresets = useMemo(() => {\n    const filteredList =\n      search.trim() === \"\"\n        ? presetNames\n        : presetNames.filter((name) => {\n          if (name === \"default\") {\n            return \"default\".toLowerCase().includes(search.toLowerCase());\n          }\n          return presets[name]?.label?.toLowerCase().includes(search.toLowerCase());\n        });\n\n    // Separate saved and default themes\n    const savedThemesList = filteredList.filter((name) => name !== \"default\" && isSavedTheme(name));\n    const defaultThemesList = filteredList.filter((name) => !savedThemesList.includes(name));\n\n    // Sort each list, with \"default\" at the top for default themes\n    const sortThemes = (list: string[]) => {\n      const defaultTheme = list.filter((name) => name === \"default\");\n      const otherThemes = list\n        .filter((name) => name !== \"default\")\n        .sort((a, b) => {\n          const labelA = presets[a]?.label || a;\n          const labelB = presets[b]?.label || b;\n          return labelA.localeCompare(labelB);\n        });\n      return [...defaultTheme, ...otherThemes];\n    };\n\n    // Combine saved themes first, then default themes\n    return [...sortThemes(savedThemesList), ...sortThemes(defaultThemesList)];\n  }, [presetNames, search, presets, isSavedTheme]);\n\n  const filteredSavedThemes = useMemo(() => {\n    return filteredPresets.filter((name) => name !== \"default\" && isSavedTheme(name));\n  }, [filteredPresets, isSavedTheme]);\n\n  const filteredDefaultThemes = useMemo(() => {\n    return filteredPresets.filter((name) => name === \"default\" || !isSavedTheme(name));\n  }, [filteredPresets, isSavedTheme]);\n\n  return (\n    <div className=\"flex w-full items-center\">\n      <Popover>\n        <PopoverTrigger asChild>\n          <Button\n            variant=\"ghost\"\n            className={cn(\"group relative w-full justify-between md:min-w-56\", className)}\n            {...props}\n          >\n            <div className=\"flex w-full items-center gap-3 overflow-hidden\">\n              <div className=\"flex gap-0.5\">\n                <ColorBox color={themeState.styles[mode].primary} />\n                <ColorBox color={themeState.styles[mode].accent} />\n                <ColorBox color={themeState.styles[mode].secondary} />\n                <ColorBox color={themeState.styles[mode].border} />\n              </div>\n              {currentPresetName !== \"default\" &&\n                currentPresetName &&\n                isSavedTheme(currentPresetName) &&\n                !hasUnsavedChanges() && (\n                  <div className=\"bg-muted rounded-full p-1\">\n                    <Heart\n                      className=\"size-1\"\n                      stroke=\"var(--muted)\"\n                      fill=\"var(--muted-foreground)\"\n                    />\n                  </div>\n                )}\n              <span className=\"truncate text-left font-medium capitalize\">\n                {presets[currentPresetName || \"default\"]?.label ||\n                  currentPresetName ||\n                  \"default\"}\n                {hasUnsavedChanges() && \"*\"}\n              </span>\n            </div>\n            <ChevronDown className=\"size-4 shrink-0\" />\n          </Button>\n        </PopoverTrigger>\n        <PopoverContent className=\"w-[400px] p-0\" align=\"center\">\n          <Command className=\"w-full\">\n            <div className=\"flex w-full items-center\">\n              <div className=\"flex w-full items-center border-b px-3 py-1\">\n                <Search className=\"size-4 shrink-0 opacity-50\" />\n                <Input\n                  placeholder=\"Search themes...\"\n                  className=\"border-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0\"\n                  value={search}\n                  onChange={(e) => setSearch(e.target.value)}\n                />\n              </div>\n            </div>\n            <div className=\"flex items-center justify-between px-3 py-2\">\n              <div className=\"text-muted-foreground text-sm\">\n                {filteredPresets.length} theme\n                {filteredPresets.length !== 1 ? \"s\" : \"\"}\n              </div>\n              <ThemeControls />\n            </div>\n            <Separator />\n            <CommandList className=\"max-h-[500px]\">\n              <CommandEmpty>No themes found.</CommandEmpty>\n\n              {/* Saved Themes Group */}\n              {filteredSavedThemes.length > 0 && (\n                <>\n                  <CommandGroup\n                    heading={\n                      <div className=\"flex w-full items-center justify-between\">\n                        <span>Saved Themes</span>\n                        <Link href=\"/settings/themes\">\n                          <Button\n                            variant=\"link\"\n                            size=\"sm\"\n                            className=\"text-muted-foreground hover:text-foreground flex items-center gap-1.5 p-0 text-xs\"\n                          >\n                            <span>Manage</span>\n                            <Settings className=\"size-3.5!\" />\n                          </Button>\n                        </Link>\n                      </div>\n                    }\n                  >\n                    {filteredSavedThemes\n                      .filter((name) => name !== \"default\" && isSavedTheme(name))\n                      .map((presetName, index) => (\n                        <CommandItem\n                          key={`${presetName}-${index}`}\n                          value={`${presetName}-${index}`}\n                          onSelect={() => {\n                            applyThemePreset(presetName);\n                            setSearch(\"\");\n                          }}\n                          className=\"data-[highlighted]:bg-secondary/50 flex items-center gap-2 py-2\"\n                        >\n                          <ThemeColors presetName={presetName} mode={mode} />\n                          <div className=\"flex flex-1 items-center gap-2\">\n                            <span className=\"line-clamp-1 text-sm font-medium capitalize\">\n                              {presets[presetName]?.label || presetName}\n                            </span>\n                            {presets[presetName] && isThemeNew(presets[presetName]) && (\n                              <Badge variant=\"secondary\" className=\"rounded-full text-xs\">\n                                New\n                              </Badge>\n                            )}\n                          </div>\n                          {presetName === currentPresetName && (\n                            <Check className=\"h-4 w-4 shrink-0 opacity-70\" />\n                          )}\n                        </CommandItem>\n                      ))}\n                  </CommandGroup>\n                  <Separator className=\"my-2\" />\n                </>\n              )}\n\n              {filteredSavedThemes.length === 0 && search.trim() === \"\" && (\n                <>\n                  <div className=\"text-muted-foreground flex items-center gap-1.5 px-3 py-2 text-xs font-medium\">\n                    <div className=\"bg-muted flex items-center gap-1 rounded-md border px-2 py-0.5\">\n                      <Heart className=\"fill-muted-foreground size-3\" />\n                      <span>Save</span>\n                    </div>\n                    <span className=\"text-muted-foreground\">a theme to find it here.</span>\n                  </div>\n                  <Separator />\n                </>\n              )}\n\n              {/* Default Theme Group */}\n              {filteredDefaultThemes.length > 0 && (\n                <CommandGroup heading=\"Built-in Themes\">\n                  {filteredDefaultThemes.map((presetName, index) => (\n                    <CommandItem\n                      key={`${presetName}-${index}`}\n                      value={`${presetName}-${index}`}\n                      onSelect={() => {\n                        applyThemePreset(presetName);\n                        setSearch(\"\");\n                      }}\n                      className=\"data-[highlighted]:bg-secondary/50 flex items-center gap-2 py-2\"\n                    >\n                      <ThemeColors presetName={presetName} mode={mode} />\n                      <div className=\"flex flex-1 items-center gap-2\">\n                        <span className=\"text-sm font-medium capitalize\">\n                          {presets[presetName]?.label || presetName}\n                        </span>\n                        {presets[presetName] && isThemeNew(presets[presetName]) && (\n                          <Badge variant=\"secondary\" className=\"rounded-full text-xs\">\n                            New\n                          </Badge>\n                        )}\n                      </div>\n                      {presetName === currentPresetName && (\n                        <Check className=\"h-4 w-4 shrink-0 opacity-70\" />\n                      )}\n                    </CommandItem>\n                  ))}\n                </CommandGroup>\n              )}\n              <Separator className=\"my-1\" />\n              <div className=\"px-3 py-2\">\n                <Link href=\"/community\">\n                  <Button\n                    variant=\"link\"\n                    size=\"sm\"\n                    className=\"text-muted-foreground hover:text-foreground w-full justify-center text-xs\"\n                  >\n                    Discover more themes →\n                  </Button>\n                </Link>\n              </div>\n            </CommandList>\n          </Command>\n        </PopoverContent>\n      </Popover>\n\n      {withCycleThemes && (\n        <ThemePresetCycleControls\n          filteredPresets={filteredPresets}\n          currentPresetName={currentPresetName || \"default\"}\n          className={className}\n          disabled={props.disabled}\n        />\n      )}\n    </div>\n  );\n};\n\nexport default ThemePresetSelect;\n"
  },
  {
    "path": "components/editor/theme-preview/color-preview.tsx",
    "content": "import { CopyButton } from \"@/components/copy-button\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport { FocusColorId, useColorControlFocus } from \"@/store/color-control-focus-store\";\nimport { ThemeEditorPreviewProps } from \"@/types/theme\";\nimport { SquarePen } from \"lucide-react\";\n\ninterface ColorPreviewProps {\n  styles: ThemeEditorPreviewProps[\"styles\"];\n  currentMode: ThemeEditorPreviewProps[\"currentMode\"];\n}\n\nfunction ColorPreviewItem({ label, color, name }: { label: string; color: string; name: string }) {\n  const { focusColor } = useColorControlFocus();\n\n  return (\n    <div className=\"group/color-preview hover:bg-muted/60 relative flex items-center gap-2 rounded-md p-1 transition-colors\">\n      <div\n        className=\"size-14 shrink-0 rounded-md border @max-3xl:size-12\"\n        style={{ backgroundColor: color }}\n      />\n      <div className=\"flex-1 space-y-1 overflow-hidden\">\n        <p className=\"line-clamp-2 text-sm leading-tight font-medium @max-3xl:text-xs\">{label}</p>\n        <p className=\"text-muted-foreground truncate font-mono text-xs\">{color}</p>\n      </div>\n\n      <div className=\"hidden flex-col opacity-0 transition-opacity group-hover/color-preview:opacity-100 md:flex\">\n        <TooltipWrapper label=\"Edit color\" asChild>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            onClick={() => focusColor(name as FocusColorId)}\n            className=\"size-7 @max-3xl:size-6 [&>svg]:size-3.5\"\n          >\n            <SquarePen />\n          </Button>\n        </TooltipWrapper>\n        <CopyButton textToCopy={color} className=\"size-7 @max-3xl:size-6\" />\n      </div>\n    </div>\n  );\n}\n\nconst ColorPreview = ({ styles, currentMode }: ColorPreviewProps) => {\n  if (!styles || !styles[currentMode]) {\n    return null;\n  }\n\n  return (\n    <div className=\"@container grid grid-cols-1 gap-4 md:gap-8\">\n      {/* Primary Colors */}\n      <div className=\"space-y-4 @max-3xl:space-y-2\">\n        <h3 className=\"text-muted-foreground text-sm font-semibold\">Primary Theme Colors</h3>\n        <div className=\"@6xl grid grid-cols-1 gap-2 @sm:grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4\">\n          <ColorPreviewItem\n            label=\"Background\"\n            color={styles[currentMode].background}\n            name=\"background\"\n          />\n          <ColorPreviewItem\n            label=\"Foreground\"\n            color={styles[currentMode].foreground}\n            name=\"foreground\"\n          />\n          <ColorPreviewItem label=\"Primary\" color={styles[currentMode].primary} name=\"primary\" />\n          <ColorPreviewItem\n            label=\"Primary Foreground\"\n            color={styles[currentMode][\"primary-foreground\"]}\n            name=\"primary-foreground\"\n          />\n        </div>\n      </div>\n\n      {/* Secondary & Accent Colors */}\n      <div className=\"space-y-4 @max-3xl:space-y-2\">\n        <h3 className=\"text-muted-foreground text-sm font-semibold\">Secondary & Accent Colors</h3>\n        <div className=\"@6xl grid grid-cols-1 gap-4 @sm:grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4\">\n          <ColorPreviewItem\n            label=\"Secondary\"\n            color={styles[currentMode].secondary}\n            name=\"secondary\"\n          />\n          <ColorPreviewItem\n            label=\"Secondary Foreground\"\n            color={styles[currentMode][\"secondary-foreground\"]}\n            name=\"secondary-foreground\"\n          />\n          <ColorPreviewItem label=\"Accent\" color={styles[currentMode].accent} name=\"accent\" />\n          <ColorPreviewItem\n            label=\"Accent Foreground\"\n            color={styles[currentMode][\"accent-foreground\"]}\n            name=\"accent-foreground\"\n          />\n        </div>\n      </div>\n\n      {/* UI Component Colors */}\n      <div className=\"space-y-4 @max-3xl:space-y-2\">\n        <h3 className=\"text-muted-foreground text-sm font-semibold\">UI Component Colors</h3>\n        <div className=\"grid grid-cols-1 gap-4 @sm:grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4\">\n          <ColorPreviewItem label=\"Card\" color={styles[currentMode].card} name=\"card\" />\n          <ColorPreviewItem\n            label=\"Card Foreground\"\n            color={styles[currentMode][\"card-foreground\"]}\n            name=\"card-foreground\"\n          />\n          <ColorPreviewItem label=\"Popover\" color={styles[currentMode].popover} name=\"popover\" />\n          <ColorPreviewItem\n            label=\"Popover Foreground\"\n            color={styles[currentMode][\"popover-foreground\"]}\n            name=\"popover-foreground\"\n          />\n          <ColorPreviewItem label=\"Muted\" color={styles[currentMode].muted} name=\"muted\" />\n          <ColorPreviewItem\n            label=\"Muted Foreground\"\n            color={styles[currentMode][\"muted-foreground\"]}\n            name=\"muted-foreground\"\n          />\n        </div>\n      </div>\n\n      {/* Utility & Form Colors */}\n      <div className=\"space-y-4 @max-3xl:space-y-2\">\n        <h3 className=\"text-muted-foreground text-sm font-semibold\">Utility & Form Colors</h3>\n        <div className=\"grid grid-cols-1 gap-4 @sm:grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4\">\n          <ColorPreviewItem label=\"Border\" color={styles[currentMode].border} name=\"border\" />\n          <ColorPreviewItem label=\"Input\" color={styles[currentMode].input} name=\"input\" />\n          <ColorPreviewItem label=\"Ring\" color={styles[currentMode].ring} name=\"ring\" />\n        </div>\n      </div>\n\n      {/* Status & Feedback Colors */}\n      <div className=\"space-y-4 @max-3xl:space-y-2\">\n        <h3 className=\"text-muted-foreground text-sm font-semibold\">Status & Feedback Colors</h3>\n        <div className=\"grid grid-cols-1 gap-4 @sm:grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4\">\n          <ColorPreviewItem\n            label=\"Destructive\"\n            color={styles[currentMode].destructive}\n            name=\"destructive\"\n          />\n          <ColorPreviewItem\n            label=\"Destructive Foreground\"\n            color={styles[currentMode][\"destructive-foreground\"]}\n            name=\"destructive-foreground\"\n          />\n        </div>\n      </div>\n\n      {/* Chart & Data Visualization Colors */}\n      <div className=\"space-y-4 @max-3xl:space-y-2\">\n        <h3 className=\"text-muted-foreground text-sm font-semibold\">\n          Chart & Visualization Colors\n        </h3>\n        <div className=\"grid grid-cols-1 gap-4 @sm:grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4\">\n          <ColorPreviewItem label=\"Chart 1\" color={styles[currentMode][\"chart-1\"]} name=\"chart-1\" />\n          <ColorPreviewItem label=\"Chart 2\" color={styles[currentMode][\"chart-2\"]} name=\"chart-2\" />\n          <ColorPreviewItem label=\"Chart 3\" color={styles[currentMode][\"chart-3\"]} name=\"chart-3\" />\n          <ColorPreviewItem label=\"Chart 4\" color={styles[currentMode][\"chart-4\"]} name=\"chart-4\" />\n          <ColorPreviewItem label=\"Chart 5\" color={styles[currentMode][\"chart-5\"]} name=\"chart-5\" />\n        </div>\n      </div>\n\n      {/* Sidebar Colors */}\n      <div className=\"space-y-4 @max-3xl:space-y-2\">\n        <h3 className=\"text-muted-foreground text-sm font-semibold\">Sidebar & Navigation Colors</h3>\n        <div className=\"grid grid-cols-1 gap-4 @sm:grid-cols-2 @2xl:grid-cols-3 @4xl:grid-cols-4\">\n          <ColorPreviewItem\n            label=\"Sidebar Background\"\n            color={styles[currentMode].sidebar}\n            name=\"sidebar\"\n          />\n          <ColorPreviewItem\n            label=\"Sidebar Foreground\"\n            color={styles[currentMode][\"sidebar-foreground\"]}\n            name=\"sidebar-foreground\"\n          />\n          <ColorPreviewItem\n            label=\"Sidebar Primary\"\n            color={styles[currentMode][\"sidebar-primary\"]}\n            name=\"sidebar-primary\"\n          />\n          <ColorPreviewItem\n            label=\"Sidebar Primary Foreground\"\n            color={styles[currentMode][\"sidebar-primary-foreground\"]}\n            name=\"sidebar-primary-foreground\"\n          />\n          <ColorPreviewItem\n            label=\"Sidebar Accent\"\n            color={styles[currentMode][\"sidebar-accent\"]}\n            name=\"sidebar-accent\"\n          />\n          <ColorPreviewItem\n            label=\"Sidebar Accent Foreground\"\n            color={styles[currentMode][\"sidebar-accent-foreground\"]}\n            name=\"sidebar-accent-foreground\"\n          />\n          <ColorPreviewItem\n            label=\"Sidebar Border\"\n            color={styles[currentMode][\"sidebar-border\"]}\n            name=\"sidebar-border\"\n          />\n          <ColorPreviewItem\n            label=\"Sidebar Ring\"\n            color={styles[currentMode][\"sidebar-ring\"]}\n            name=\"sidebar-ring\"\n          />\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default ColorPreview;\n"
  },
  {
    "path": "components/editor/theme-preview/components-showcase.tsx",
    "content": "import { ThemeEditorPreviewProps } from \"@/types/theme\";\nimport { Settings, Info, AlertTriangle, Star } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\";\nimport {\n  Table,\n  TableHeader,\n  TableRow,\n  TableHead,\n  TableBody,\n  TableCell,\n} from \"@/components/ui/table\";\n\ninterface ComponentsShowcaseProps {\n  styles: ThemeEditorPreviewProps[\"styles\"];\n  currentMode: ThemeEditorPreviewProps[\"currentMode\"];\n}\n\nconst ComponentsShowcase = ({ styles, currentMode }: ComponentsShowcaseProps) => {\n  if (!styles || !styles[currentMode]) {\n    return null;\n  }\n\n  return (\n    <div className=\"space-y-6\">\n      {/* Button showcase */}\n      <section className=\"space-y-3\">\n        <h3 className=\"text-sm font-medium border-b pb-2\">\n          Buttons & Interactive Elements\n        </h3>\n        <div className=\"space-y-4\">\n          <div className=\"flex flex-wrap gap-3\">\n            <Button variant=\"default\">Primary</Button>\n            <Button variant=\"secondary\">Secondary</Button>\n            <Button variant=\"outline\">Outline</Button>\n            <Button variant=\"ghost\">Ghost</Button>\n            <Button variant=\"link\">Link</Button>\n            <Button variant=\"destructive\">Delete</Button>\n          </div>\n          <div className=\"flex items-center space-x-4\">\n            <div className=\"flex items-center space-x-2\">\n              <Switch id=\"notifications\" />\n              <label htmlFor=\"notifications\">Notifications</label>\n            </div>\n            <div className=\"flex items-center space-x-2\">\n              <Switch id=\"darkmode\" />\n              <label htmlFor=\"darkmode\">Dark Mode</label>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      {/* Cards & Containers */}\n      <section className=\"space-y-3\">\n        <h3 className=\"text-sm font-medium border-b pb-2\">Cards & Containers</h3>\n        <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n          <Card>\n            <CardHeader>\n              <CardTitle>Feature Card</CardTitle>\n              <CardDescription>\n                Card description with muted foreground color\n              </CardDescription>\n            </CardHeader>\n            <CardContent>\n              <p className=\"text-sm\">\n                This card demonstrates the card background and foreground colors,\n                with content showing regular text.\n              </p>\n            </CardContent>\n            <CardFooter className=\"flex justify-between\">\n              <Button variant=\"ghost\">Cancel</Button>\n              <Button>Continue</Button>\n            </CardFooter>\n          </Card>\n\n          <div className=\"space-y-3\">\n            <div\n              className=\"rounded-lg p-4\"\n              style={{\n                backgroundColor: styles[currentMode].popover,\n                color: styles[currentMode][\"popover-foreground\"],\n                border: `1px solid ${styles[currentMode].border}`,\n              }}\n            >\n              <h4 className=\"text-sm font-medium mb-2\">Popover Container</h4>\n              <p className=\"text-xs\">\n                This container shows popover colors and styling.\n              </p>\n            </div>\n\n            <div\n              className=\"rounded-lg p-4\"\n              style={{\n                backgroundColor: styles[currentMode].muted,\n                color: styles[currentMode][\"muted-foreground\"],\n              }}\n            >\n              <h4 className=\"text-sm font-medium mb-2\">Muted Container</h4>\n              <p className=\"text-xs\">\n                Container with muted background and foreground colors.\n              </p>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      {/* Status Indicators */}\n      <section className=\"space-y-3\">\n        <h3 className=\"text-sm font-medium border-b pb-2\">\n          Status Indicators & Alerts\n        </h3>\n        <div className=\"space-y-4\">\n          <div className=\"flex flex-wrap gap-2\">\n            <Badge>Default Badge</Badge>\n            <Badge variant=\"secondary\">Secondary</Badge>\n            <Badge variant=\"outline\">Outline</Badge>\n            <Badge variant=\"destructive\">Error</Badge>\n            <Badge className=\"bg-blue-500 hover:bg-blue-600\">Custom</Badge>\n          </div>\n\n          <div className=\"space-y-3\">\n            <Alert>\n              <Info className=\"h-4 w-4\" />\n              <AlertTitle>Information</AlertTitle>\n              <AlertDescription>\n                Standard alert with default styling.\n              </AlertDescription>\n            </Alert>\n\n            <Alert variant=\"destructive\">\n              <AlertTriangle className=\"h-4 w-4\" />\n              <AlertTitle>Error</AlertTitle>\n              <AlertDescription>\n                Destructive alert showcasing error state colors.\n              </AlertDescription>\n            </Alert>\n\n            <div\n              className=\"rounded-lg border p-4 flex items-start gap-3\"\n              style={{\n                borderColor: styles[currentMode].border,\n                backgroundColor: `${styles[currentMode].accent}20`,\n              }}\n            >\n              <Star className=\"h-5 w-5 text-yellow-500 shrink-0\" />\n              <div>\n                <h5 className=\"font-medium text-sm\">Success Alert</h5>\n                <p className=\"text-xs mt-1\">\n                  Custom alert using accent colors with an opacity modifier.\n                </p>\n              </div>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      {/* Data Display */}\n      <section className=\"space-y-3\">\n        <h3 className=\"text-sm font-medium border-b pb-2\">Data Display</h3>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>User</TableHead>\n              <TableHead>Status</TableHead>\n              <TableHead>Role</TableHead>\n              <TableHead className=\"text-right\">Actions</TableHead>\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            <TableRow>\n              <TableCell className=\"font-medium\">Alex Johnson</TableCell>\n              <TableCell>\n                <Badge variant=\"outline\" className=\"bg-green-500/10 text-green-600\">\n                  Active\n                </Badge>\n              </TableCell>\n              <TableCell>Admin</TableCell>\n              <TableCell className=\"text-right\">\n                <Button variant=\"ghost\" size=\"sm\">\n                  <Settings className=\"h-4 w-4\" />\n                </Button>\n              </TableCell>\n            </TableRow>\n            <TableRow>\n              <TableCell className=\"font-medium\">Sarah Chen</TableCell>\n              <TableCell>\n                <Badge\n                  variant=\"outline\"\n                  className=\"bg-destructive/10 text-destructive\"\n                >\n                  Inactive\n                </Badge>\n              </TableCell>\n              <TableCell>User</TableCell>\n              <TableCell className=\"text-right\">\n                <Button variant=\"ghost\" size=\"sm\">\n                  <Settings className=\"h-4 w-4\" />\n                </Button>\n              </TableCell>\n            </TableRow>\n          </TableBody>\n        </Table>\n      </section>\n    </div>\n  );\n};\n\nexport default ComponentsShowcase;\n"
  },
  {
    "path": "components/editor/theme-preview/examples-preview-container.tsx",
    "content": "import { Suspense } from \"react\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { cn } from \"@/lib/utils\";\nimport { Loading } from \"@/components/loading\";\n\nconst LoadingSkeleton = () => (\n  <div className=\"absolute inset-0 flex flex-col\">\n    <div className=\"flex flex-1 flex-col\">\n      <Skeleton className=\"w-full flex-1 opacity-50\" />\n    </div>\n\n    <div className=\"absolute inset-0 flex items-center justify-center\">\n      <Loading />\n    </div>\n  </div>\n);\n\nconst ExamplesPreviewContainer = ({\n  children,\n  className,\n}: {\n  children: React.ReactNode;\n  className?: string;\n}) => {\n  return (\n    <div className={cn(\"space-y-6\", className)}>\n      <div className=\"@container mt-0 h-full w-full space-y-6\">\n        <Suspense fallback={<LoadingSkeleton />}>{children}</Suspense>\n      </div>\n    </div>\n  );\n};\n\nexport default ExamplesPreviewContainer;\n"
  },
  {
    "path": "components/editor/theme-preview/tabs-trigger-pill.tsx",
    "content": "import { TabsTrigger } from \"@/components/ui/tabs\";\nimport { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\nconst TabsTriggerPill = ({\n  children,\n  className,\n  ...props\n}: React.ComponentPropsWithoutRef<typeof TabsTrigger>) => {\n  return (\n    <TabsTrigger\n      className={cn(\n        \"ring-offset-background focus-visible:ring-ring data-[state=active]:bg-secondary data-[state=active]:text-secondary-foreground hover:text-muted-foreground/70 inline-flex items-center justify-center rounded-full px-3 py-1 text-sm font-medium whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </TabsTrigger>\n  );\n};\n\nexport default TabsTriggerPill;\n"
  },
  {
    "path": "components/editor/theme-preview-panel.tsx",
    "content": "\"use client\";\n\nimport ShadcnBlocksLogo from \"@/assets/shadcnblocks.svg\";\nimport { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { ThemeToggle } from \"@/components/theme-toggle\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { ScrollArea, ScrollBar } from \"@/components/ui/scroll-area\";\nimport { Tabs, TabsContent, TabsList } from \"@/components/ui/tabs\";\nimport { useDialogActions } from \"@/hooks/use-dialog-actions\";\nimport { useFullscreen } from \"@/hooks/use-fullscreen\";\nimport { useThemeInspector } from \"@/hooks/use-theme-inspector\";\nimport { cn } from \"@/lib/utils\";\nimport { ThemeEditorPreviewProps } from \"@/types/theme\";\nimport { Inspect, Maximize, Minimize, MoreVertical } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { useQueryState } from \"nuqs\";\nimport { lazy } from \"react\";\nimport InspectorOverlay from \"./inspector-overlay\";\nimport ColorPreview from \"./theme-preview/color-preview\";\nimport ExamplesPreviewContainer from \"./theme-preview/examples-preview-container\";\nimport TabsTriggerPill from \"./theme-preview/tabs-trigger-pill\";\n\nconst DemoCards = lazy(() => import(\"@/components/examples/cards\"));\nconst DemoMail = lazy(() => import(\"@/components/examples/mail\"));\nconst DemoDashboard = lazy(() => import(\"@/components/examples/dashboard\"));\nconst DemoPricing = lazy(() => import(\"@/components/examples/pricing/pricing\"));\nconst TypographyDemo = lazy(() => import(\"@/components/examples/typography/typography-demo\"));\nconst CustomDemo = lazy(() => import(\"@/components/examples/custom\"));\n\nconst V0Logo = ({ className }: { className?: string }) => (\n  <svg viewBox=\"0 0 40 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className}>\n    <path\n      d=\"M23.3919 0H32.9188C36.7819 0 39.9136 3.13165 39.9136 6.99475V16.0805H36.0006V6.99475C36.0006 6.90167 35.9969 6.80925 35.9898 6.71766L26.4628 16.079C26.4949 16.08 26.5272 16.0805 26.5595 16.0805H36.0006V19.7762H26.5595C22.6964 19.7762 19.4788 16.6139 19.4788 12.7508V3.68923H23.3919V12.7508C23.3919 12.9253 23.4054 13.0977 23.4316 13.2668L33.1682 3.6995C33.0861 3.6927 33.003 3.68923 32.9188 3.68923H23.3919V0Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M13.7688 19.0956L0 3.68759H5.53933L13.6231 12.7337V3.68759H17.7535V17.5746C17.7535 19.6705 15.1654 20.6584 13.7688 19.0956Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nconst ThemePreviewPanel = ({\n  styles,\n  currentMode,\n  themeId,\n  themeName,\n}: ThemeEditorPreviewProps & { themeId?: string; themeName?: string }) => {\n  const { isFullscreen, toggleFullscreen } = useFullscreen();\n  const [activeTab, setActiveTab] = useQueryState(\"p\", {\n    defaultValue: \"cards\",\n  });\n  const { handleOpenInV0 } = useDialogActions();\n\n  const {\n    rootRef,\n    inspector,\n    inspectorEnabled,\n    handleMouseMove,\n    handleMouseLeave,\n    toggleInspector,\n  } = useThemeInspector();\n\n  if (!styles || !styles[currentMode]) {\n    return null;\n  }\n\n  const handleTabChange = (value: string) => {\n    setActiveTab(value);\n  };\n\n  return (\n    <>\n      <div\n        className={cn(\n          \"flex min-h-0 flex-1 flex-col\",\n          isFullscreen && \"bg-background fixed inset-0 z-50\"\n        )}\n      >\n        <Tabs\n          value={activeTab}\n          onValueChange={setActiveTab}\n          className=\"flex flex-1 flex-col overflow-hidden\"\n        >\n          <HorizontalScrollArea className=\"mt-2 mb-1 flex w-full items-center justify-between px-4\">\n            <TabsList className=\"bg-background text-muted-foreground inline-flex w-fit items-center justify-center rounded-full px-0\">\n              <TabsTriggerPill value=\"custom\">Custom</TabsTriggerPill>\n              <TabsTriggerPill value=\"cards\">Cards</TabsTriggerPill>\n\n              <div className=\"hidden md:flex\">\n                <TabsTriggerPill value=\"dashboard\">Dashboard</TabsTriggerPill>\n                <TabsTriggerPill value=\"mail\">Mail</TabsTriggerPill>\n              </div>\n              <TabsTriggerPill value=\"pricing\">Pricing</TabsTriggerPill>\n              <TabsTriggerPill value=\"colors\">Color Palette</TabsTriggerPill>\n\n              <DropdownMenu>\n                <DropdownMenuTrigger asChild>\n                  <TooltipWrapper label=\"More previews\" asChild>\n                    <Button variant=\"ghost\" size=\"icon\">\n                      <MoreVertical />\n                    </Button>\n                  </TooltipWrapper>\n                </DropdownMenuTrigger>\n                <DropdownMenuContent align=\"end\">\n                  <DropdownMenuItem onClick={() => handleTabChange(\"typography\")}>\n                    Typography\n                  </DropdownMenuItem>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </TabsList>\n\n            <div className=\"flex items-center gap-0.5\">\n              <TooltipWrapper label=\"Open theme in v0\" asChild>\n                <Button variant=\"ghost\" onClick={() => handleOpenInV0(themeId, themeName)} className=\"group px-2.5\">\n                  <span className=\"flex items-center justify-center gap-1 transition-all group-hover:scale-110\">\n                    Open in <V0Logo className=\"mb-0.5 !size-5\" />\n                  </span>\n                </Button>\n              </TooltipWrapper>\n              {isFullscreen && (\n                <ThemeToggle\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  className=\"group size-8 hover:[&>svg]:scale-120 hover:[&>svg]:transition-all\"\n                />\n              )}\n              {/* Inspector toggle button */}\n              <TooltipWrapper label=\"Toggle Inspector\" asChild>\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  onClick={toggleInspector}\n                  className={cn(\n                    \"group size-8\",\n                    inspectorEnabled && \"bg-accent text-accent-foreground w-auto\"\n                  )}\n                >\n                  <Inspect className=\"transition-all group-hover:scale-120\" />\n                  {inspectorEnabled && <span className=\"text-xs tracking-wide uppercase\">on</span>}\n                </Button>\n              </TooltipWrapper>\n              <TooltipWrapper\n                label={isFullscreen ? \"Exit full screen\" : \"Full screen\"}\n                className=\"hidden md:inline-flex\"\n                asChild\n              >\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  onClick={toggleFullscreen}\n                  className=\"group size-8\"\n                >\n                  {isFullscreen ? (\n                    <Minimize className=\"transition-all group-hover:scale-120\" />\n                  ) : (\n                    <Maximize className=\"transition-all group-hover:scale-120\" />\n                  )}\n                </Button>\n              </TooltipWrapper>\n            </div>\n          </HorizontalScrollArea>\n\n          <section className={cn(\"relative size-full overflow-hidden\", activeTab === \"cards\" ? \"pb-4\" : \"p-4 pt-1\")}>\n            <div\n              className={cn(\"relative isolate size-full overflow-hidden\", activeTab !== \"cards\" && \"rounded-lg\")}\n              ref={rootRef}\n              onMouseMove={handleMouseMove}\n              onMouseLeave={handleMouseLeave}\n            >\n              <TabsContent value=\"cards\" className=\"m-0 size-full\">\n                <ExamplesPreviewContainer className=\"size-full\">\n                  <ScrollArea className=\"size-full\">\n                    <DemoCards />\n                  </ScrollArea>\n                </ExamplesPreviewContainer>\n              </TabsContent>\n\n              <TabsContent value=\"custom\" className=\"@container m-0 size-full\">\n                <ExamplesPreviewContainer className=\"size-full\">\n                  <CustomDemo />\n                </ExamplesPreviewContainer>\n              </TabsContent>\n\n              <TabsContent value=\"dashboard\" className=\"@container m-0 size-full\">\n                <ExamplesPreviewContainer className=\"size-full\">\n                  <ScrollArea className=\"size-full\">\n                    <div className=\"size-full min-w-[1400px]\">\n                      <DemoDashboard />\n                    </div>\n                    <ScrollBar orientation=\"horizontal\" />\n                  </ScrollArea>\n                </ExamplesPreviewContainer>\n              </TabsContent>\n\n              <TabsContent value=\"pricing\" className=\"@container mt-0 h-full space-y-6\">\n                <ExamplesPreviewContainer className=\"size-full\">\n                  <div className=\"absolute top-4 right-4 z-10\">\n                    <Link\n                      href=\"https://shadcnblocks.com?utm_source=tweakcn&utm_medium=theme-editor-preview\"\n                      target=\"_blank\"\n                    >\n                      <Button variant=\"outline\" className=\"group h-12 shadow-sm\">\n                        <div className=\"flex items-center gap-2\">\n                          <ShadcnBlocksLogo\n                            className=\"shrink-0\"\n                            style={{ width: \"24px\", height: \"24px\" }}\n                          />\n                          <div className=\"text-left\">\n                            <div className=\"font-bold\">Shadcnblocks.com</div>\n                            <div className=\"text-muted-foreground group-hover:text-accent-foreground text-xs transition-colors\">\n                              600+ extra shadcn blocks\n                            </div>\n                          </div>\n                        </div>\n                      </Button>\n                    </Link>\n                  </div>\n                  <ScrollArea className=\"size-full\">\n                    <DemoPricing />\n                  </ScrollArea>\n                </ExamplesPreviewContainer>\n              </TabsContent>\n\n              <TabsContent value=\"mail\" className=\"@container m-0 size-full\">\n                <ExamplesPreviewContainer className=\"size-full\">\n                  <ScrollArea className=\"size-full\">\n                    <div className=\"size-full min-w-[1300px] rounded-lg border\">\n                      <DemoMail />\n                    </div>\n                    <ScrollBar orientation=\"horizontal\" />\n                  </ScrollArea>\n                </ExamplesPreviewContainer>\n              </TabsContent>\n\n              <TabsContent value=\"typography\" className=\"m-0 size-full\">\n                <ExamplesPreviewContainer className=\"size-full\">\n                  <ScrollArea className=\"size-full\">\n                    <TypographyDemo />\n                  </ScrollArea>\n                </ExamplesPreviewContainer>\n              </TabsContent>\n\n              <TabsContent value=\"colors\" className=\"m-0 size-full\">\n                <ScrollArea className=\"size-full\">\n                  <div className=\"p-4\">\n                    <ColorPreview styles={styles} currentMode={currentMode} />\n                  </div>\n                </ScrollArea>\n              </TabsContent>\n            </div>\n          </section>\n        </Tabs>\n      </div>\n\n      <InspectorOverlay inspector={inspector} enabled={inspectorEnabled} rootRef={rootRef} />\n    </>\n  );\n};\n\nexport default ThemePreviewPanel;\n"
  },
  {
    "path": "components/editor/theme-save-dialog.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { Loader2 } from \"lucide-react\";\nimport { useEffect } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport * as z from \"zod\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogFooter,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n} from \"../ui/revola\";\n\nconst formSchema = z.object({\n  themeName: z.string().min(1, \"Theme name cannot be empty.\"),\n});\n\ninterface ThemeSaveDialogProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  onSave: (themeName: string) => Promise<void>;\n  isSaving: boolean;\n  initialThemeName?: string;\n  ctaLabel?: string;\n  title?: string;\n  description?: string;\n  existingThemeName?: string;\n  onUpdateExisting?: () => Promise<void>;\n  isUpdating?: boolean;\n}\n\nexport function ThemeSaveDialog({\n  open,\n  onOpenChange,\n  onSave,\n  isSaving,\n  initialThemeName = \"\",\n  ctaLabel = \"Save Theme\",\n  title = \"Save Theme\",\n  description = \"Enter a name for your theme so you can find it later.\",\n  existingThemeName,\n  onUpdateExisting,\n  isUpdating = false,\n}: ThemeSaveDialogProps) {\n  const form = useForm<z.infer<typeof formSchema>>({\n    resolver: zodResolver(formSchema),\n    defaultValues: {\n      themeName: initialThemeName,\n    },\n  });\n\n  const onSubmit = (values: z.infer<typeof formSchema>) => {\n    onSave(values.themeName);\n  };\n\n  useEffect(() => {\n    if (open) {\n      form.reset({ themeName: initialThemeName });\n    }\n  }, [open, initialThemeName, form]);\n\n  const handleOpenChange = (newOpen: boolean) => {\n    onOpenChange(newOpen);\n  };\n\n  const hasUpdateOption = !!existingThemeName && !!onUpdateExisting;\n\n  return (\n    <ResponsiveDialog open={open} onOpenChange={handleOpenChange}>\n      <ResponsiveDialogContent className=\"overflow-hidden shadow-lg sm:max-w-100\">\n        <div className=\"space-y-6 p-6 pt-0 sm:pt-6 sm:pb-2\">\n          <ResponsiveDialogHeader>\n            <ResponsiveDialogTitle>{title}</ResponsiveDialogTitle>\n            <ResponsiveDialogDescription>{description}</ResponsiveDialogDescription>\n          </ResponsiveDialogHeader>\n\n          {hasUpdateOption && (\n            <>\n              <Button\n                className=\"w-full\"\n                disabled={isUpdating || isSaving}\n                onClick={onUpdateExisting}\n              >\n                {isUpdating ? (\n                  <>\n                    <Loader2 className=\"mr-1 size-4 animate-spin\" />\n                    Updating\n                  </>\n                ) : (\n                  <>Update &ldquo;{existingThemeName}&rdquo;</>\n                )}\n              </Button>\n\n              <div className=\"flex items-center gap-3\">\n                <Separator className=\"flex-1\" />\n                <span className=\"text-muted-foreground text-xs\">or save as a new theme</span>\n                <Separator className=\"flex-1\" />\n              </div>\n            </>\n          )}\n\n          <Form {...form}>\n            <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-6\">\n              <FormField\n                control={form.control}\n                name=\"themeName\"\n                render={({ field }) => (\n                  <FormItem className=\"grid\">\n                    <FormLabel>Name</FormLabel>\n                    <FormControl>\n                      <Input placeholder=\"My Awesome Theme\" {...field} />\n                    </FormControl>\n                    <FormMessage />\n                  </FormItem>\n                )}\n              />\n            </form>\n          </Form>\n        </div>\n        <ResponsiveDialogFooter className=\"bg-muted/30 border-t px-6 py-4\">\n          <Button\n            size=\"sm\"\n            disabled={isSaving || isUpdating}\n            variant=\"ghost\"\n            onClick={() => onOpenChange(false)}\n          >\n            Cancel\n          </Button>\n          <Button\n            size=\"sm\"\n            type=\"submit\"\n            disabled={\n              isSaving || isUpdating || !form.formState.isValid || form.formState.isSubmitting\n            }\n            onClick={form.handleSubmit(onSubmit)}\n          >\n            {isSaving || form.formState.isSubmitting ? (\n              <>\n                <Loader2 className=\"mr-1 size-4 animate-spin\" />\n                Saving\n              </>\n            ) : hasUpdateOption ? (\n              \"Save as New\"\n            ) : (\n              ctaLabel\n            )}\n          </Button>\n        </ResponsiveDialogFooter>\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n}\n"
  },
  {
    "path": "components/effects/frame-highlight.tsx",
    "content": "import { cn } from \"@/lib/utils\";\nimport { ComponentProps } from \"react\";\n\nexport function FrameHighlight({ children, className, ...props }: ComponentProps<\"span\">) {\n  return (\n    <>\n      {\" \"}\n      <span className=\"relative h-fit px-1 text-nowrap\">\n        <span className={cn(\"w-full\", className)} {...props}>\n          {children}\n        </span>\n        <span className=\"border-primary/60! bg-primary/15 group-hover:bg-primary/20 z dark:border-primary/40! absolute inset-0 h-full border border-dashed px-1.5\">\n          <Corner className=\"fill-primary dark:fill-primary/70 absolute top-[-2px] left-[-2px]\" />\n          <Corner className=\"fill-primary dark:fill-primary/70 absolute top-[-2px] right-[-2px]\" />\n          <Corner className=\"fill-primary dark:fill-primary/70 absolute bottom-[-2px] left-[-2px]\" />\n          <Corner className=\"fill-primary dark:fill-primary/70 absolute right-[-2px] bottom-[-2px]\" />\n        </span>\n      </span>{\" \"}\n    </>\n  );\n}\n\nfunction Corner({ className }: ComponentProps<\"svg\">) {\n  return (\n    <svg width=\"5\" height=\"5\" viewBox=\"0 0 5 5\" className={cn(\"absolute\", className)}>\n      <path d=\"M2 0h1v2h2v1h-2v2h-1v-2h-2v-1h2z\"></path>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/effects/noise-effect.tsx",
    "content": "export function NoiseEffect() {\n  return (\n    <svg\n      className=\"pointer-events-none absolute inset-0 z-10 opacity-[8%]\"\n      width=\"100%\"\n      height=\"100%\"\n    >\n      <filter id=\"noise\">\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.70\"\n          numOctaves=\"4\"\n          stitchTiles=\"stitch\"\n        ></feTurbulence>\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter=\"url(#noise)\"></rect>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/effects/spotlight.tsx",
    "content": "import { cn } from \"@/lib/utils\";\n\ntype SpotlightProps = {\n  className?: string;\n  fill?: string;\n};\n\nexport function Spotlight({ className, fill }: SpotlightProps) {\n  return (\n    <svg\n      className={cn(\n        \"animate-spotlight pointer-events-none absolute z-[1] h-[169%] w-[138%] lg:w-[84%]\",\n        className\n      )}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 3787 2842\"\n      fill=\"none\"\n    >\n      <g filter=\"url(#filter)\">\n        <ellipse\n          cx=\"1924.71\"\n          cy=\"273.501\"\n          rx=\"1924.71\"\n          ry=\"273.501\"\n          transform=\"matrix(-0.822377 -0.568943 -0.568943 0.822377 3631.88 2291.09)\"\n          fill={fill || \"white\"}\n          fillOpacity=\"0.21\"\n        ></ellipse>\n      </g>\n      <defs>\n        <filter\n          id=\"filter\"\n          x=\"0.860352\"\n          y=\"0.838989\"\n          width=\"3785.16\"\n          height=\"2840.26\"\n          filterUnits=\"userSpaceOnUse\"\n          colorInterpolationFilters=\"sRGB\"\n        >\n          <feFlood floodOpacity=\"0\" result=\"BackgroundImageFix\"></feFlood>\n          <feBlend\n            mode=\"normal\"\n            in=\"SourceGraphic\"\n            in2=\"BackgroundImageFix\"\n            result=\"shape\"\n          ></feBlend>\n          <feGaussianBlur\n            stdDeviation=\"151\"\n            result=\"effect1_foregroundBlur_1065_8\"\n          ></feGaussianBlur>\n        </filter>\n      </defs>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/error-boundary.tsx",
    "content": "import { FileWarning } from \"lucide-react\";\nimport React from \"react\";\n\nexport class ComponentErrorBoundary extends React.Component<\n  { children: React.ReactNode; name: string; fallback?: React.ReactNode },\n  { hasError: boolean }\n> {\n  constructor(props: { children: React.ReactNode; name: string; fallback: React.ReactNode }) {\n    super(props);\n    this.state = { hasError: false };\n  }\n\n  static getDerivedStateFromError() {\n    return { hasError: true };\n  }\n\n  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n    console.error(`Error in component ${this.props.name}:`, error, errorInfo);\n  }\n\n  render() {\n    if (this.state.hasError) {\n      if (this.props.fallback) {\n        return this.props.fallback;\n      }\n\n      return (\n        <div className=\"bg-destructive text-destructive-foreground flex size-full items-center justify-center gap-2 p-4\">\n          <FileWarning className=\"size-4\" />\n          <p className=\"text-sm text-pretty\">\n            Something went wrong in component:{\" \"}\n            <span className=\"font-medium\">{this.props.name}</span>\n          </p>\n        </div>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n"
  },
  {
    "path": "components/examples/ai-chat-demo.tsx",
    "content": "\"use client\";\n\nimport Message from \"@/components/editor/ai/message\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { cn } from \"@/lib/utils\";\nimport { ChatMessage } from \"@/types/ai\";\nimport { ThemeStyles } from \"@/types/theme\";\nimport { defaultPresets } from \"@/utils/theme-presets\";\nimport { useEffect, useRef } from \"react\";\n\nexport function AIChatDemo({\n  disabled = true,\n  className,\n}: {\n  disabled?: boolean;\n  className?: string;\n}) {\n  const ref = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    if (!ref.current) return;\n\n    // Always block tabbing: set tabIndex=\"-1\" on all focusable children\n    const focusables = ref.current.querySelectorAll(\n      'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n    );\n    focusables.forEach((el) => {\n      (el as HTMLElement).setAttribute(\"tabindex\", \"-1\");\n      // Only set disabled if supported and disabled is true\n      if (disabled && \"disabled\" in el) (el as HTMLButtonElement).disabled = true;\n      if (!disabled && \"disabled\" in el) (el as HTMLButtonElement).disabled = false;\n    });\n  }, [disabled]);\n\n  return (\n    <div\n      className={cn(\n        \"bg-transparent flex h-full w-full min-w-[350px] origin-top-left flex-col overflow-hidden\"\n      )}\n      aria-hidden=\"true\"\n      tabIndex={-1}\n    >\n      {/* Scrollable parent */}\n      <ScrollArea className=\"flex-1\">\n        {/* Non-interactive chat content */}\n        <div\n          ref={ref}\n          className={cn(\n            \"flex flex-col gap-6 p-6 select-none\",\n            disabled ? \"pointer-events-none\" : \"\",\n            className\n          )}\n          aria-hidden=\"true\"\n          tabIndex={-1}\n        >\n          {CHAT_PLACEHOLDER_MESSAGES.map((msg) => (\n            <Message\n              key={msg.id}\n              isGeneratingTheme={false}\n              isLastMessageStreaming={false}\n              message={msg}\n              onRetry={() => {}}\n              isEditing={false}\n              onEdit={() => {}}\n              onEditSubmit={() => {}}\n              onEditCancel={() => {}}\n            />\n          ))}\n        </div>\n      </ScrollArea>\n    </div>\n  );\n}\n\nconst CHAT_PLACEHOLDER_MESSAGES: ChatMessage[] = [\n  {\n    id: \"1\",\n    role: \"user\",\n    parts: [\n      {\n        type: \"text\",\n        text: \"Can you generate a theme from this image?\",\n      },\n    ],\n    metadata: {\n      promptData: {\n        content: \"Generate a theme from this image.\",\n        mentions: [],\n        images: [\n          {\n            url: \"/og-image.v050725.png\",\n          },\n        ],\n      },\n    },\n  },\n  {\n    id: \"2\",\n    role: \"assistant\",\n    parts: [\n      {\n        type: \"text\",\n        text: \"I've generated a Midnight Bloom theme based on your image. It features deep purples and blues for a calming, modern look.\",\n      },\n    ],\n    metadata: {\n      themeStyles: defaultPresets[\"midnight-bloom\"].styles as ThemeStyles,\n    },\n  },\n  {\n    id: \"3\",\n    role: \"user\",\n    parts: [\n      {\n        type: \"text\",\n        text: \"Can you generate a theme inspired by @Twitter?\",\n      },\n    ],\n  },\n  {\n    id: \"4\",\n    role: \"assistant\",\n    parts: [\n      {\n        type: \"text\",\n        text: \"Alright, I've whipped up a Twitter-inspired theme. Expect bright blues and clean contrasts for a social, energetic vibe.\",\n      },\n    ],\n    metadata: {\n      themeStyles: defaultPresets[\"twitter\"].styles as ThemeStyles,\n    },\n  },\n  {\n    id: \"5\",\n    role: \"user\",\n    parts: [\n      {\n        type: \"text\",\n        text: \"How about a @Supabase theme?\",\n      },\n    ],\n  },\n  {\n    id: \"6\",\n    role: \"assistant\",\n    parts: [\n      {\n        type: \"text\",\n        text: \"I've generated a Supabase theme for you. It uses fresh greens and dark backgrounds for a modern, developer-friendly feel.\",\n      },\n    ],\n    metadata: {\n      themeStyles: defaultPresets[\"supabase\"].styles as ThemeStyles,\n    },\n  },\n];\n"
  },
  {
    "path": "components/examples/cards/activity-goal.tsx",
    "content": "\"use client\";\n\nimport { MinusIcon, PlusIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Bar, BarChart } from \"recharts\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { ChartConfig, ChartContainer } from \"@/components/ui/chart\";\n\nconst data = [\n  {\n    goal: 400,\n  },\n  {\n    goal: 300,\n  },\n  {\n    goal: 200,\n  },\n  {\n    goal: 300,\n  },\n  {\n    goal: 200,\n  },\n  {\n    goal: 278,\n  },\n  {\n    goal: 189,\n  },\n  {\n    goal: 239,\n  },\n  {\n    goal: 300,\n  },\n  {\n    goal: 200,\n  },\n  {\n    goal: 278,\n  },\n  {\n    goal: 189,\n  },\n  {\n    goal: 349,\n  },\n];\n\nconst chartConfig = {\n  goal: {\n    label: \"Goal\",\n    color: \"var(--primary)\",\n  },\n} satisfies ChartConfig;\n\nexport function CardsActivityGoal() {\n  const [goal, setGoal] = React.useState(350);\n\n  function onClick(adjustment: number) {\n    setGoal(Math.max(200, Math.min(400, goal + adjustment)));\n  }\n\n  return (\n    <Card className=\"flex h-full flex-col\">\n      <CardHeader>\n        <CardTitle>Move Goal</CardTitle>\n        <CardDescription>Set your daily activity goal.</CardDescription>\n      </CardHeader>\n      <CardContent className=\"flex flex-1 flex-col\">\n        <div className=\"flex items-center justify-center gap-4\">\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"size-7 rounded-full\"\n            onClick={() => onClick(-10)}\n            disabled={goal <= 200}\n          >\n            <MinusIcon />\n            <span className=\"sr-only\">Decrease</span>\n          </Button>\n          <div className=\"text-center\">\n            <div className=\"text-4xl font-bold tracking-tighter tabular-nums\">{goal}</div>\n            <div className=\"text-muted-foreground text-xs uppercase\">Calories/day</div>\n          </div>\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"size-7 rounded-full\"\n            onClick={() => onClick(10)}\n            disabled={goal >= 400}\n          >\n            <PlusIcon />\n            <span className=\"sr-only\">Increase</span>\n          </Button>\n        </div>\n        <ChartContainer config={chartConfig} className=\"max-h-20 w-full\">\n          <BarChart data={data}>\n            <Bar dataKey=\"goal\" radius={4} fill=\"var(--color-goal)\" />\n          </BarChart>\n        </ChartContainer>\n      </CardContent>\n      <CardFooter>\n        <Button className=\"w-full\" variant=\"secondary\">\n          Set Goal\n        </Button>\n      </CardFooter>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/calendar.tsx",
    "content": "\"use client\";\n\nimport { addDays } from \"date-fns\";\n\nimport { Calendar } from \"@/components/ui/calendar\";\nimport { Card, CardContent } from \"@/components/ui/card\";\n\nconst start = new Date(2025, 5, 5);\n\nexport function CardsCalendar() {\n  return (\n    <Card className=\"hidden p-0 @2xl:flex\">\n      <CardContent className=\"p-0\">\n        <Calendar\n          numberOfMonths={1}\n          mode=\"range\"\n          defaultMonth={start}\n          selected={{\n            from: start,\n            to: addDays(start, 8),\n          }}\n        />\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/chat.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardFooter, CardHeader } from \"@/components/ui/card\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogFooter,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n} from \"@/components/ui/revola\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { ArrowUpIcon, CheckIcon, PlusIcon } from \"lucide-react\";\n\nconst users = [\n  {\n    name: \"Olivia Martin\",\n    email: \"m@example.com\",\n    avatar: \"/avatars/01.png\",\n  },\n  {\n    name: \"Isabella Nguyen\",\n    email: \"isabella.nguyen@email.com\",\n    avatar: \"/avatars/03.png\",\n  },\n  {\n    name: \"Emma Wilson\",\n    email: \"emma@example.com\",\n    avatar: \"/avatars/05.png\",\n  },\n  {\n    name: \"Jackson Lee\",\n    email: \"lee@example.com\",\n    avatar: \"/avatars/02.png\",\n  },\n  {\n    name: \"William Kim\",\n    email: \"will@email.com\",\n    avatar: \"/avatars/04.png\",\n  },\n] as const;\n\ntype User = (typeof users)[number];\n\nexport function CardsChat() {\n  const [open, setOpen] = React.useState(false);\n  const [selectedUsers, setSelectedUsers] = React.useState<User[]>([]);\n\n  const [messages, setMessages] = React.useState([\n    {\n      role: \"agent\",\n      content: \"Hi, how can I help you today?\",\n    },\n    {\n      role: \"user\",\n      content: \"Hey, I'm having trouble with my account.\",\n    },\n    {\n      role: \"agent\",\n      content: \"What seems to be the problem?\",\n    },\n    {\n      role: \"user\",\n      content: \"I can't log in.\",\n    },\n  ]);\n  const [input, setInput] = React.useState(\"\");\n  const inputLength = input.trim().length;\n\n  return (\n    <>\n      <Card>\n        <CardHeader className=\"flex flex-row items-center\">\n          <div className=\"flex items-center gap-4\">\n            <Avatar className=\"border\">\n              <AvatarImage src=\"/avatars/01.png\" alt=\"Image\" />\n              <AvatarFallback>S</AvatarFallback>\n            </Avatar>\n            <div className=\"flex flex-col gap-0.5\">\n              <p className=\"text-sm leading-none font-medium\">Sofia Davis</p>\n              <p className=\"text-muted-foreground text-xs\">m@example.com</p>\n            </div>\n          </div>\n          <TooltipProvider delayDuration={0}>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  size=\"icon\"\n                  variant=\"secondary\"\n                  className=\"ml-auto size-8 rounded-full\"\n                  onClick={() => setOpen(true)}\n                >\n                  <PlusIcon />\n                  <span className=\"sr-only\">New message</span>\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent sideOffset={10}>New message</TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n        </CardHeader>\n        <CardContent>\n          <div className=\"flex flex-col gap-4\">\n            {messages.map((message, index) => (\n              <div\n                key={index}\n                className={cn(\n                  \"flex w-max max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm\",\n                  message.role === \"user\"\n                    ? \"bg-primary text-primary-foreground ml-auto\"\n                    : \"bg-muted\"\n                )}\n              >\n                {message.content}\n              </div>\n            ))}\n          </div>\n        </CardContent>\n        <CardFooter>\n          <form\n            onSubmit={(event) => {\n              event.preventDefault();\n              if (inputLength === 0) return;\n              setMessages([\n                ...messages,\n                {\n                  role: \"user\",\n                  content: input,\n                },\n              ]);\n              setInput(\"\");\n            }}\n            className=\"relative w-full\"\n          >\n            <Input\n              id=\"message\"\n              placeholder=\"Type your message...\"\n              className=\"flex-1 pr-10\"\n              autoComplete=\"off\"\n              value={input}\n              onChange={(event) => setInput(event.target.value)}\n            />\n            <Button\n              type=\"submit\"\n              size=\"icon\"\n              className=\"absolute top-1/2 right-2 size-6 -translate-y-1/2 rounded-full\"\n              disabled={inputLength === 0}\n            >\n              <ArrowUpIcon className=\"size-3.5\" />\n              <span className=\"sr-only\">Send</span>\n            </Button>\n          </form>\n        </CardFooter>\n      </Card>\n      <ResponsiveDialog open={open} onOpenChange={setOpen}>\n        <ResponsiveDialogContent className=\"flex max-h-[85%] flex-col gap-0\">\n          <ResponsiveDialogHeader className=\"p-4 pt-0 sm:pt-5\">\n            <ResponsiveDialogTitle>New message</ResponsiveDialogTitle>\n            <ResponsiveDialogDescription>\n              Invite a user to this thread. This will create a new group message.\n            </ResponsiveDialogDescription>\n          </ResponsiveDialogHeader>\n\n          <Command className=\"overflow-hidden rounded-t-none border-t bg-transparent\">\n            <CommandInput placeholder=\"Search user...\" />\n            <CommandList>\n              <CommandEmpty>No users found.</CommandEmpty>\n              <CommandGroup>\n                {users.map((user) => (\n                  <CommandItem\n                    key={user.email}\n                    data-active={selectedUsers.includes(user)}\n                    className=\"gap-2 data-[active=true]:opacity-50\"\n                    onSelect={() => {\n                      if (selectedUsers.includes(user)) {\n                        return setSelectedUsers(\n                          selectedUsers.filter((selectedUser) => selectedUser !== user)\n                        );\n                      }\n                      return setSelectedUsers(\n                        [...users].filter((u) => [...selectedUsers, user].includes(u))\n                      );\n                    }}\n                  >\n                    <Avatar className=\"size-7.5 border\">\n                      <AvatarImage src={user.avatar} alt=\"Image\" />\n                      <AvatarFallback>{user.name[0]}</AvatarFallback>\n                    </Avatar>\n                    <div className=\"ml-2\">\n                      <p className=\"text-sm leading-none font-medium\">{user.name}</p>\n                      <p className=\"text-muted-foreground text-sm\">{user.email}</p>\n                    </div>\n                    {selectedUsers.includes(user) ? (\n                      <CheckIcon className=\"text-primary ml-auto flex size-4\" />\n                    ) : null}\n                  </CommandItem>\n                ))}\n              </CommandGroup>\n            </CommandList>\n          </Command>\n\n          <ResponsiveDialogFooter className=\"items-center border-t p-4 sm:justify-between\">\n            {selectedUsers.length > 0 ? (\n              <div className=\"flex -space-x-2 overflow-hidden\">\n                {selectedUsers.map((user) => (\n                  <Avatar key={user.email} className=\"inline-block size-7.5 border\">\n                    <AvatarImage src={user.avatar} />\n                    <AvatarFallback>{user.name[0]}</AvatarFallback>\n                  </Avatar>\n                ))}\n              </div>\n            ) : (\n              <p className=\"text-muted-foreground text-sm\">Select users to add to this thread.</p>\n            )}\n            <Button\n              onClick={() => {\n                setOpen(false);\n              }}\n              disabled={selectedUsers.length < 2}\n            >\n              Continue\n            </Button>\n          </ResponsiveDialogFooter>\n        </ResponsiveDialogContent>\n      </ResponsiveDialog>\n    </>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/cookie-settings.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Label } from \"@/components/ui/label\";\nimport { Switch } from \"@/components/ui/switch\";\n\nexport function CardsCookieSettings() {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Cookie Settings</CardTitle>\n        <CardDescription>Manage your cookie settings here.</CardDescription>\n      </CardHeader>\n      <CardContent className=\"grid gap-6\">\n        <div className=\"flex items-center justify-between gap-4\">\n          <Label htmlFor=\"necessary\" className=\"flex flex-col items-start\">\n            <span>Strictly Necessary</span>\n            <span className=\"text-muted-foreground leading-snug font-normal\">\n              These cookies are essential in order to use the website and use its features.\n            </span>\n          </Label>\n          <Switch id=\"necessary\" defaultChecked aria-label=\"Necessary\" />\n        </div>\n        <div className=\"flex items-center justify-between gap-4\">\n          <Label htmlFor=\"functional\" className=\"flex flex-col items-start\">\n            <span>Functional Cookies</span>\n            <span className=\"text-muted-foreground leading-snug font-normal\">\n              These cookies allow the website to provide personalized functionality.\n            </span>\n          </Label>\n          <Switch id=\"functional\" aria-label=\"Functional\" />\n        </div>\n      </CardContent>\n      <CardFooter>\n        <Button variant=\"outline\" className=\"w-full\">\n          Save preferences\n        </Button>\n      </CardFooter>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/create-account.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\n\nexport function CardsCreateAccount() {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle className=\"text-2xl\">Create an account</CardTitle>\n        <CardDescription>Enter your email below to create your account</CardDescription>\n      </CardHeader>\n      <CardContent className=\"flex flex-col gap-4\">\n        <div className=\"grid grid-cols-2 gap-6\">\n          <Button variant=\"outline\">\n            <svg viewBox=\"0 0 438.549 438.549\">\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              ></path>\n            </svg>\n            GitHub\n          </Button>\n          <Button variant=\"outline\">\n            <svg role=\"img\" viewBox=\"0 0 24 24\">\n              <path\n                fill=\"currentColor\"\n                d=\"M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z\"\n              />\n            </svg>\n            Google\n          </Button>\n        </div>\n        <div className=\"relative\">\n          <div className=\"absolute inset-0 flex items-center\">\n            <span className=\"w-full border-t\" />\n          </div>\n          <div className=\"relative flex justify-center text-xs uppercase\">\n            <span className=\"bg-card text-muted-foreground px-2\">Or continue with</span>\n          </div>\n        </div>\n        <div className=\"flex flex-col gap-3\">\n          <Label htmlFor=\"email-create-account\">Email</Label>\n          <Input id=\"email-create-account\" type=\"email\" placeholder=\"m@example.com\" />\n        </div>\n        <div className=\"flex flex-col gap-3\">\n          <Label htmlFor=\"password-create-account\">Password</Label>\n          <Input id=\"password-create-account\" type=\"password\" />\n        </div>\n      </CardContent>\n      <CardFooter>\n        <Button variant=\"default\" className=\"group bg-primary text-primary-foreground ring-primary before:from-primary-foreground/20 after:from-primary-foreground/10 relative isolate inline-flex w-full items-center justify-center overflow-hidden rounded-md px-3 text-left text-sm font-medium ring-1 transition duration-300 ease-[cubic-bezier(0.4,0.36,0,1)] before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:rounded-md before:bg-gradient-to-b before:opacity-80 before:transition-opacity before:duration-300 before:ease-[cubic-bezier(0.4,0.36,0,1)] after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:rounded-md after:bg-gradient-to-b after:to-transparent after:mix-blend-overlay\">\n          Create account\n        </Button>\n      </CardFooter>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/date-picker-with-range.tsx",
    "content": "\"use client\";\n\nimport { addDays, format } from \"date-fns\";\nimport { Calendar as CalendarIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { DateRange } from \"react-day-picker\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Calendar } from \"@/components/ui/calendar\";\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\n\nexport function DatePickerWithRange({ className }: React.HTMLAttributes<HTMLDivElement>) {\n  const [date, setDate] = React.useState<DateRange | undefined>({\n    from: new Date(2022, 0, 20),\n    to: addDays(new Date(2022, 0, 20), 20),\n  });\n\n  return (\n    <Card className={cn(\"grid gap-2\", className)}>\n      <CardHeader>\n        <CardTitle>Date picker with range</CardTitle>\n        <CardDescription>Select a date range.</CardDescription>\n      </CardHeader>\n\n      <CardContent>\n        <Popover>\n          <PopoverTrigger asChild>\n            <Button\n              id=\"date\"\n              variant={\"outline\"}\n              className={cn(\n                \"w-full max-w-[300px] justify-start text-left font-normal\",\n                !date && \"text-muted-foreground\"\n              )}\n            >\n              <CalendarIcon />\n              {date?.from ? (\n                date.to ? (\n                  <>\n                    {format(date.from, \"LLL dd, y\")} - {format(date.to, \"LLL dd, y\")}\n                  </>\n                ) : (\n                  format(date.from, \"LLL dd, y\")\n                )\n              ) : (\n                <span>Pick a date</span>\n              )}\n            </Button>\n          </PopoverTrigger>\n          <PopoverContent className=\"w-auto p-0\" align=\"start\">\n            <Calendar\n              mode=\"range\"\n              defaultMonth={date?.from}\n              selected={date}\n              onSelect={setDate}\n              numberOfMonths={2}\n            />\n          </PopoverContent>\n        </Popover>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/exercise-minutes.tsx",
    "content": "\"use client\";\n\nimport { CartesianGrid, Line, LineChart, XAxis } from \"recharts\";\n\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport {\n  ChartConfig,\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n} from \"@/components/ui/chart\";\n\nconst data = [\n  {\n    average: 400,\n    today: 240,\n    day: \"Monday\",\n  },\n  {\n    average: 300,\n    today: 139,\n    day: \"Tuesday\",\n  },\n  {\n    average: 200,\n    today: 980,\n    day: \"Wednesday\",\n  },\n  {\n    average: 278,\n    today: 390,\n    day: \"Thursday\",\n  },\n  {\n    average: 189,\n    today: 480,\n    day: \"Friday\",\n  },\n  {\n    average: 239,\n    today: 380,\n    day: \"Saturday\",\n  },\n  {\n    average: 349,\n    today: 430,\n    day: \"Sunday\",\n  },\n];\n\nconst chartConfig = {\n  today: {\n    label: \"Today\",\n    color: \"var(--primary)\",\n  },\n  average: {\n    label: \"Average\",\n    color: \"var(--primary)\",\n  },\n} satisfies ChartConfig;\n\nexport function CardsExerciseMinutes() {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Exercise Minutes</CardTitle>\n        <CardDescription>\n          Your exercise minutes are ahead of where you normally are.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <ChartContainer config={chartConfig} className=\"w-full @3xl:h-[200px]\">\n          <LineChart\n            accessibilityLayer\n            data={data}\n            margin={{\n              top: 5,\n              right: 10,\n              left: 16,\n              bottom: 0,\n            }}\n          >\n            <CartesianGrid vertical={false} />\n            <XAxis\n              dataKey=\"day\"\n              tickLine={false}\n              axisLine={false}\n              tickMargin={8}\n              tickFormatter={(value) => value.slice(0, 3)}\n            />\n            <Line\n              type=\"monotone\"\n              dataKey=\"today\"\n              strokeWidth={2}\n              stroke=\"var(--color-today)\"\n              dot={{\n                fill: \"var(--color-today)\",\n              }}\n              activeDot={{\n                r: 5,\n              }}\n            />\n            <Line\n              type=\"monotone\"\n              strokeWidth={2}\n              dataKey=\"average\"\n              stroke=\"var(--color-average)\"\n              strokeOpacity={0.5}\n              dot={{\n                fill: \"var(--color-average)\",\n                opacity: 0.5,\n              }}\n              activeDot={{\n                r: 5,\n              }}\n            />\n            <ChartTooltip content={<ChartTooltipContent />} />\n          </LineChart>\n        </ChartContainer>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/forms.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\nimport { Textarea } from \"@/components/ui/textarea\";\n\nconst plans = [\n  {\n    id: \"starter\",\n    name: \"Starter Plan\",\n    description: \"Perfect for small businesses.\",\n    price: \"$10\",\n  },\n  {\n    id: \"pro\",\n    name: \"Pro Plan\",\n    description: \"More features and storage.\",\n    price: \"$20\",\n  },\n] as const;\n\nexport function CardsForms() {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle className=\"text-lg\">Upgrade your subscription</CardTitle>\n        <CardDescription className=\"text-balance\">\n          You are currently on the free plan. Upgrade to the pro plan to get access to all features.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-6\">\n          <div className=\"flex flex-col gap-3 @3xl:flex-row\">\n            <div className=\"flex flex-1 flex-col gap-2\">\n              <Label htmlFor=\"name\">Name</Label>\n              <Input id=\"name\" placeholder=\"Evil Rabbit\" />\n            </div>\n            <div className=\"flex flex-1 flex-col gap-2\">\n              <Label htmlFor=\"email\">Email</Label>\n              <Input id=\"email\" placeholder=\"example@acme.com\" />\n            </div>\n          </div>\n          <div className=\"flex flex-col gap-2\">\n            <Label htmlFor=\"card-number\">Card Number</Label>\n            <div className=\"grid grid-cols-2 gap-3 @3xl:grid-cols-[1fr_80px_60px]\">\n              <Input\n                id=\"card-number\"\n                placeholder=\"1234 1234 1234 1234\"\n                className=\"col-span-2 @3xl:col-span-1\"\n              />\n              <Input id=\"card-number-expiry\" placeholder=\"MM/YY\" />\n              <Input id=\"card-number-cvc\" placeholder=\"CVC\" />\n            </div>\n          </div>\n          <fieldset className=\"flex flex-col gap-3\">\n            <legend className=\"text-sm font-medium\">Plan</legend>\n            <p className=\"text-muted-foreground text-sm\">\n              Select the plan that best fits your needs.\n            </p>\n            <RadioGroup defaultValue=\"starter\" className=\"grid gap-3 @3xl:grid-cols-2\">\n              {plans.map((plan) => (\n                <Label\n                  className=\"has-[[data-state=checked]]:border-ring has-[[data-state=checked]]:bg-input/20 flex items-start gap-3 rounded-lg border p-3\"\n                  key={plan.id}\n                >\n                  <RadioGroupItem\n                    value={plan.id}\n                    id={plan.name}\n                    className=\"data-[state=checked]:border-primary\"\n                  />\n                  <div className=\"grid gap-1 font-normal\">\n                    <div className=\"font-medium\">{plan.name}</div>\n                    <div className=\"text-muted-foreground text-xs leading-snug text-balance\">\n                      {plan.description}\n                    </div>\n                  </div>\n                </Label>\n              ))}\n            </RadioGroup>\n          </fieldset>\n          <div className=\"flex flex-col gap-2\">\n            <Label htmlFor=\"notes\">Notes</Label>\n            <Textarea id=\"notes\" placeholder=\"Enter notes\" />\n          </div>\n          <div className=\"flex flex-col gap-3\">\n            <div className=\"flex items-center gap-2\">\n              <Checkbox id=\"terms\" />\n              <Label htmlFor=\"terms\" className=\"font-normal\">\n                I agree to the terms and conditions\n              </Label>\n            </div>\n            <div className=\"flex items-center gap-2\">\n              <Checkbox id=\"newsletter\" defaultChecked />\n              <Label htmlFor=\"newsletter\" className=\"font-normal\">\n                Allow us to send you emails\n              </Label>\n            </div>\n          </div>\n        </div>\n      </CardContent>\n      <CardFooter className=\"flex justify-between\">\n        <Button variant=\"outline\" size=\"sm\">\n          Cancel\n        </Button>\n        <Button size=\"sm\">Upgrade Plan</Button>\n      </CardFooter>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/github-card.tsx",
    "content": "\"use client\";\n\nimport { Circle, Star } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport Link from \"next/link\";\n\nexport function GithubCard() {\n  return (\n    <Card>\n      <CardHeader>\n        <div className=\"flex items-start justify-between gap-4\">\n          <div className=\"space-y-1.5\">\n            <CardTitle>tweakcn</CardTitle>\n            <CardDescription>\n              A visual editor for shadcn/ui components with beautiful themes. Accessible.\n              Customizable. Open Source.\n            </CardDescription>\n          </div>\n          <div className=\"bg-secondary text-secondary-foreground flex min-w-20 shrink-0 items-center space-x-1 rounded-md\">\n            <Link href=\"https://github.com/jnsahaj/tweakcn\">\n              <Button variant=\"secondary\" className=\"flex items-center gap-2 px-3 shadow-none\">\n                <Star />\n                Star\n              </Button>\n            </Link>\n          </div>\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div className=\"text-muted-foreground flex space-x-4 text-sm\">\n          <div className=\"flex items-center\">\n            <Circle className=\"mr-1 h-3 w-3 fill-sky-400 text-sky-400\" />\n            TypeScript\n          </div>\n          <div className=\"flex items-center\">\n            <Star className=\"mr-1 h-3 w-3\" />\n            20k\n          </div>\n          <div>Updated April 2023</div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/index.tsx",
    "content": "import { CardsActivityGoal } from \"@/components/examples/cards/activity-goal\";\nimport { CardsCalendar } from \"@/components/examples/cards/calendar\";\nimport { CardsChat } from \"@/components/examples/cards/chat\";\nimport { CardsCookieSettings } from \"@/components/examples/cards/cookie-settings\";\nimport { CardsCreateAccount } from \"@/components/examples/cards/create-account\";\nimport { CardsExerciseMinutes } from \"@/components/examples/cards/exercise-minutes\";\nimport { CardsForms } from \"@/components/examples/cards/forms\";\nimport { CardsPayments } from \"@/components/examples/cards/payments\";\nimport { CardsReportIssue } from \"@/components/examples/cards/report-issue\";\nimport { CardsShare } from \"@/components/examples/cards/share\";\nimport { CardsStats } from \"@/components/examples/cards/stats\";\nimport { CardsTeamMembers } from \"@/components/examples/cards/team-members\";\nimport { DatePickerWithRange } from \"./date-picker-with-range\";\nimport { GithubCard } from \"./github-card\";\n\nexport default function CardsDemo() {\n  return (\n    <div className=\"@3xl:grids-col-2 grid p-2 **:data-[slot=card]:shadow-none md:p-4 md:pt-1 @3xl:gap-4 @5xl:grid-cols-10 @7xl:grid-cols-11\">\n      <div className=\"grid gap-4 @5xl:col-span-4 @7xl:col-span-6\">\n        <CardsStats />\n        <div className=\"grid gap-1 @2xl:grid-cols-[auto_1fr] @3xl:hidden\">\n          <CardsCalendar />\n          <div className=\"@2xl:pt-0 @2xl:pl-3 @7xl:pl-4\">\n            <CardsActivityGoal />\n          </div>\n          <div className=\"pt-3 @2xl:col-span-2 @7xl:pt-4\">\n            <CardsExerciseMinutes />\n          </div>\n        </div>\n        <div className=\"grid gap-4 @3xl:grid-cols-2 @5xl:grid-cols-1 @7xl:grid-cols-2\">\n          <div className=\"flex flex-col gap-4\">\n            <CardsForms />\n            <CardsTeamMembers />\n            <CardsCookieSettings />\n            <div className=\"hidden flex-col gap-4 @7xl:flex\">\n              <GithubCard />\n              <DatePickerWithRange />\n            </div>\n          </div>\n          <div className=\"flex flex-col gap-4 pb-4\">\n            <CardsCreateAccount />\n            <CardsChat />\n            <GithubCard />\n            <DatePickerWithRange />\n\n            <div className=\"hidden @7xl:block\">\n              <CardsReportIssue />\n            </div>\n          </div>\n        </div>\n      </div>\n      <div className=\"flex flex-col gap-4 @5xl:col-span-6 @7xl:col-span-5\">\n        <div className=\"hidden gap-1 @2xl:grid-cols-[auto_1fr] @3xl:grid\">\n          <CardsCalendar />\n          <div className=\"pt-3 @2xl:pt-0 @2xl:pl-3 @7xl:pl-4\">\n            <CardsActivityGoal />\n          </div>\n          <div className=\"pt-3 @2xl:col-span-2 @7xl:pt-3\">\n            <CardsExerciseMinutes />\n          </div>\n        </div>\n        <div className=\"hidden @3xl:block\">\n          <CardsPayments />\n        </div>\n        <CardsShare />\n        <div className=\"@7xl:hidden\">\n          <CardsReportIssue />\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/payment-method.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\n\nconst plans = [\n  {\n    id: \"starter\",\n    name: \"Starter Plan\",\n    description: \"Perfect for small businesses.\",\n    price: \"$10\",\n  },\n  {\n    id: \"pro\",\n    name: \"Pro Plan\",\n    description: \"Advanced features with more storage.\",\n    price: \"$20\",\n  },\n] as const;\n\nexport function CardsPaymentMethod() {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Payment Method</CardTitle>\n        <CardDescription>Add a new payment method to your account.</CardDescription>\n      </CardHeader>\n      <CardContent className=\"flex flex-col gap-6\">\n        <div className=\"flex flex-col gap-3\">\n          <Label htmlFor=\"name\">Name</Label>\n          <Input id=\"name\" placeholder=\"First Last\" />\n        </div>\n        <fieldset className=\"flex flex-col gap-3\">\n          <legend className=\"text-sm font-medium\">Plan</legend>\n          <p className=\"text-muted-foreground text-sm\">\n            Select the plan that best fits your needs.\n          </p>\n          <RadioGroup defaultValue=\"starter\" className=\"grid gap-3\">\n            {plans.map((plan) => (\n              <Label\n                className=\"has-[[data-state=checked]]:border-ring has-[[data-state=checked]]:bg-primary/5 flex items-start gap-3 rounded-lg border p-3\"\n                key={plan.id}\n              >\n                <RadioGroupItem\n                  value={plan.id}\n                  id={plan.name}\n                  className=\"data-[state=checked]:border-primary\"\n                />\n                <div className=\"grid gap-1 font-normal\">\n                  <div className=\"font-medium\">{plan.name}</div>\n                  <div className=\"text-muted-foreground pr-2 text-xs leading-snug text-balance\">\n                    {plan.description}\n                  </div>\n                </div>\n              </Label>\n            ))}\n          </RadioGroup>\n        </fieldset>\n        <div className=\"flex flex-col gap-3\">\n          <Label htmlFor=\"number\">Card number</Label>\n          <Input id=\"number\" placeholder=\"\" />\n        </div>\n        <div className=\"grid grid-cols-3 gap-4\">\n          <div className=\"flex flex-col gap-3\">\n            <Label htmlFor=\"month\">Expires</Label>\n            <Select>\n              <SelectTrigger id=\"month\" aria-label=\"Month\" className=\"w-full\">\n                <SelectValue placeholder=\"Month\" />\n              </SelectTrigger>\n              <SelectContent>\n                <SelectItem value=\"1\">January</SelectItem>\n                <SelectItem value=\"2\">February</SelectItem>\n                <SelectItem value=\"3\">March</SelectItem>\n                <SelectItem value=\"4\">April</SelectItem>\n                <SelectItem value=\"5\">May</SelectItem>\n                <SelectItem value=\"6\">June</SelectItem>\n                <SelectItem value=\"7\">July</SelectItem>\n                <SelectItem value=\"8\">August</SelectItem>\n                <SelectItem value=\"9\">September</SelectItem>\n                <SelectItem value=\"10\">October</SelectItem>\n                <SelectItem value=\"11\">November</SelectItem>\n                <SelectItem value=\"12\">December</SelectItem>\n              </SelectContent>\n            </Select>\n          </div>\n          <div className=\"flex flex-col gap-3\">\n            <Label htmlFor=\"year\">Year</Label>\n            <Select>\n              <SelectTrigger id=\"year\" aria-label=\"Year\" className=\"w-full\">\n                <SelectValue placeholder=\"Year\" />\n              </SelectTrigger>\n              <SelectContent>\n                {Array.from({ length: 10 }, (_, i) => (\n                  <SelectItem key={i} value={`${new Date().getFullYear() + i}`}>\n                    {new Date().getFullYear() + i}\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </div>\n          <div className=\"flex flex-col gap-3\">\n            <Label htmlFor=\"cvc\">CVC</Label>\n            <Input id=\"cvc\" placeholder=\"CVC\" />\n          </div>\n        </div>\n      </CardContent>\n      <CardFooter>\n        <Button className=\"w-full\">Continue</Button>\n      </CardFooter>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/payments.tsx",
    "content": "\"use client\";\n\nimport {\n  ColumnDef,\n  ColumnFiltersState,\n  flexRender,\n  getCoreRowModel,\n  getFilteredRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  SortingState,\n  useReactTable,\n  VisibilityState,\n} from \"@tanstack/react-table\";\nimport { MoreHorizontalIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\n\nconst data: Payment[] = [\n  {\n    id: \"m5gr84i9\",\n    amount: 316,\n    status: \"success\",\n    email: \"ken99@example.com\",\n  },\n  {\n    id: \"3u1reuv4\",\n    amount: 242,\n    status: \"success\",\n    email: \"Abe45@example.com\",\n  },\n  {\n    id: \"derv1ws0\",\n    amount: 837,\n    status: \"processing\",\n    email: \"Monserrat44@example.com\",\n  },\n  {\n    id: \"bhqecj4p\",\n    amount: 721,\n    status: \"failed\",\n    email: \"carmella@example.com\",\n  },\n  {\n    id: \"k9f2m3n4\",\n    amount: 450,\n    status: \"pending\",\n    email: \"jason78@example.com\",\n  },\n  {\n    id: \"p5q6r7s8\",\n    amount: 1280,\n    status: \"success\",\n    email: \"sarah23@example.com\",\n  },\n];\n\nexport type Payment = {\n  id: string;\n  amount: number;\n  status: \"pending\" | \"processing\" | \"success\" | \"failed\";\n  email: string;\n};\n\nexport const columns: ColumnDef<Payment>[] = [\n  {\n    id: \"select\",\n    header: ({ table }) => (\n      <Checkbox\n        checked={\n          table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && \"indeterminate\")\n        }\n        onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}\n        aria-label=\"Select all\"\n      />\n    ),\n    cell: ({ row }) => (\n      <Checkbox\n        checked={row.getIsSelected()}\n        onCheckedChange={(value) => row.toggleSelected(!!value)}\n        aria-label=\"Select row\"\n      />\n    ),\n    enableSorting: false,\n    enableHiding: false,\n  },\n  {\n    accessorKey: \"status\",\n    header: \"Status\",\n    cell: ({ row }) => <div className=\"capitalize\">{row.getValue(\"status\")}</div>,\n  },\n  {\n    accessorKey: \"email\",\n    header: \"Email\",\n    cell: ({ row }) => <div className=\"lowercase\">{row.getValue(\"email\")}</div>,\n  },\n  {\n    accessorKey: \"amount\",\n    header: () => <div className=\"text-right\">Amount</div>,\n    cell: ({ row }) => {\n      const amount = parseFloat(row.getValue(\"amount\"));\n\n      // Format the amount as a dollar amount\n      const formatted = new Intl.NumberFormat(\"en-US\", {\n        style: \"currency\",\n        currency: \"USD\",\n      }).format(amount);\n\n      return <div className=\"text-right font-medium\">{formatted}</div>;\n    },\n  },\n  {\n    id: \"actions\",\n    enableHiding: false,\n    cell: ({ row }) => {\n      const payment = row.original;\n\n      return (\n        <DropdownMenu>\n          <DropdownMenuTrigger asChild>\n            <Button variant=\"ghost\" className=\"size-8 p-0\">\n              <span className=\"sr-only\">Open menu</span>\n              <MoreHorizontalIcon />\n            </Button>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent align=\"end\">\n            <DropdownMenuLabel>Actions</DropdownMenuLabel>\n            <DropdownMenuItem onClick={() => navigator.clipboard.writeText(payment.id)}>\n              Copy payment ID\n            </DropdownMenuItem>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem>View customer</DropdownMenuItem>\n            <DropdownMenuItem>View payment details</DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      );\n    },\n  },\n];\n\nexport function CardsPayments() {\n  const [sorting, setSorting] = React.useState<SortingState>([]);\n  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);\n  const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});\n  const [rowSelection, setRowSelection] = React.useState({});\n\n  const table = useReactTable({\n    data,\n    columns,\n    onSortingChange: setSorting,\n    onColumnFiltersChange: setColumnFilters,\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    onColumnVisibilityChange: setColumnVisibility,\n    onRowSelectionChange: setRowSelection,\n    state: {\n      sorting,\n      columnFilters,\n      columnVisibility,\n      rowSelection,\n    },\n  });\n\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle className=\"text-xl\">Payments</CardTitle>\n        <CardDescription>Manage your payments.</CardDescription>\n      </CardHeader>\n      <CardContent className=\"flex flex-col gap-4\">\n        <div className=\"rounded-md border\">\n          <Table>\n            <TableHeader>\n              {table.getHeaderGroups().map((headerGroup) => (\n                <TableRow key={headerGroup.id}>\n                  {headerGroup.headers.map((header) => {\n                    return (\n                      <TableHead\n                        key={header.id}\n                        className=\"data-[name=actions]:w-10 data-[name=amount]:w-24 data-[name=select]:w-10 data-[name=status]:w-24 [&:has([role=checkbox])]:pl-3\"\n                        data-name={header.id}\n                      >\n                        {header.isPlaceholder\n                          ? null\n                          : flexRender(header.column.columnDef.header, header.getContext())}\n                      </TableHead>\n                    );\n                  })}\n                </TableRow>\n              ))}\n            </TableHeader>\n            <TableBody>\n              {table.getRowModel().rows?.length ? (\n                table.getRowModel().rows.map((row) => (\n                  <TableRow key={row.id} data-state={row.getIsSelected() && \"selected\"}>\n                    {row.getVisibleCells().map((cell) => (\n                      <TableCell\n                        key={cell.id}\n                        className=\"data-[name=actions]:w-10 data-[name=amount]:w-24 data-[name=select]:w-10 data-[name=status]:w-24 [&:has([role=checkbox])]:pl-3\"\n                        data-name={cell.column.id}\n                      >\n                        {flexRender(cell.column.columnDef.cell, cell.getContext())}\n                      </TableCell>\n                    ))}\n                  </TableRow>\n                ))\n              ) : (\n                <TableRow>\n                  <TableCell colSpan={columns.length} className=\"h-24 text-center\">\n                    No results.\n                  </TableCell>\n                </TableRow>\n              )}\n            </TableBody>\n          </Table>\n        </div>\n        <div className=\"flex items-center justify-end gap-2\">\n          <div className=\"text-muted-foreground flex-1 text-sm\">\n            {table.getFilteredSelectedRowModel().rows.length} of{\" \"}\n            {table.getFilteredRowModel().rows.length} row(s) selected.\n          </div>\n          <div className=\"flex gap-2\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => table.previousPage()}\n              disabled={!table.getCanPreviousPage()}\n            >\n              Previous\n            </Button>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => table.nextPage()}\n              disabled={!table.getCanNextPage()}\n            >\n              Next\n            </Button>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/report-issue.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { Textarea } from \"@/components/ui/textarea\";\n\nexport function CardsReportIssue() {\n  const id = React.useId();\n\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Report an issue</CardTitle>\n        <CardDescription>What area are you having problems with?</CardDescription>\n      </CardHeader>\n      <CardContent className=\"flex flex-col gap-6\">\n        <div className=\"grid gap-4 @2xl:grid-cols-2\">\n          <div className=\"flex flex-col gap-3\">\n            <Label htmlFor={`area-${id}`}>Area</Label>\n            <Select defaultValue=\"billing\">\n              <SelectTrigger id={`area-${id}`} aria-label=\"Area\" className=\"w-full\">\n                <SelectValue placeholder=\"Select\" />\n              </SelectTrigger>\n              <SelectContent>\n                <SelectItem value=\"team\">Team</SelectItem>\n                <SelectItem value=\"billing\">Billing</SelectItem>\n                <SelectItem value=\"account\">Account</SelectItem>\n                <SelectItem value=\"deployments\">Deployments</SelectItem>\n                <SelectItem value=\"support\">Support</SelectItem>\n              </SelectContent>\n            </Select>\n          </div>\n          <div className=\"flex flex-col gap-3\">\n            <Label htmlFor={`security-level-${id}`}>Security Level</Label>\n            <Select defaultValue=\"2\">\n              <SelectTrigger\n                id={`security-level-${id}`}\n                className=\"w-full [&_span]:!block [&_span]:truncate\"\n                aria-label=\"Security Level\"\n              >\n                <SelectValue placeholder=\"Select level\" />\n              </SelectTrigger>\n              <SelectContent>\n                <SelectItem value=\"1\">Severity 1 (Highest)</SelectItem>\n                <SelectItem value=\"2\">Severity 2</SelectItem>\n                <SelectItem value=\"3\">Severity 3</SelectItem>\n                <SelectItem value=\"4\">Severity 4 (Lowest)</SelectItem>\n              </SelectContent>\n            </Select>\n          </div>\n        </div>\n        <div className=\"flex flex-col gap-3\">\n          <Label htmlFor={`subject-${id}`}>Subject</Label>\n          <Input id={`subject-${id}`} placeholder=\"I need help with...\" />\n        </div>\n        <div className=\"flex flex-col gap-3\">\n          <Label htmlFor={`description-${id}`}>Description</Label>\n          <Textarea\n            id={`description-${id}`}\n            placeholder=\"Please include all information relevant to your issue.\"\n            className=\"min-h-28\"\n          />\n        </div>\n      </CardContent>\n      <CardFooter className=\"justify-end gap-2\">\n        <Button variant=\"ghost\" size=\"sm\">\n          Cancel\n        </Button>\n        <Button size=\"sm\">Submit</Button>\n      </CardFooter>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/share.tsx",
    "content": "\"use client\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { Separator } from \"@/components/ui/separator\";\n\nconst people = [\n  {\n    name: \"Olivia Martin\",\n    email: \"m@example.com\",\n    avatar: \"/avatars/03.png\",\n  },\n  {\n    name: \"Isabella Nguyen\",\n    email: \"b@example.com\",\n    avatar: \"/avatars/04.png\",\n  },\n  {\n    name: \"Sofia Davis\",\n    email: \"p@example.com\",\n    avatar: \"/avatars/05.png\",\n  },\n  {\n    name: \"Ethan Thompson\",\n    email: \"e@example.com\",\n    avatar: \"/avatars/01.png\",\n  },\n];\nexport function CardsShare() {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Share this document</CardTitle>\n        <CardDescription>Anyone with the link can view this document.</CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex items-center gap-2\">\n          <Label htmlFor=\"link\" className=\"sr-only\">\n            Link\n          </Label>\n          <Input id=\"link\" value=\"http://example.com/link/to/document\" className=\"h-8\" readOnly />\n          <Button size=\"sm\" variant=\"outline\" className=\"shadow-none\">\n            Copy Link\n          </Button>\n        </div>\n        <Separator className=\"my-4\" />\n        <div className=\"flex flex-col gap-4\">\n          <div className=\"text-sm font-medium\">People with access</div>\n          <div className=\"grid gap-6\">\n            {people.map((person) => (\n              <div key={person.email} className=\"flex items-center justify-between gap-4\">\n                <div className=\"flex items-center gap-4\">\n                  <Avatar>\n                    <AvatarImage src={person.avatar} alt=\"Image\" />\n                    <AvatarFallback>{person.name.charAt(0)}</AvatarFallback>\n                  </Avatar>\n                  <div>\n                    <p className=\"text-sm leading-none font-medium\">{person.name}</p>\n                    <p className=\"text-muted-foreground text-sm\">{person.email}</p>\n                  </div>\n                </div>\n                <Select defaultValue=\"edit\">\n                  <SelectTrigger className=\"ml-auto pr-2\" aria-label=\"Edit\">\n                    <SelectValue placeholder=\"Select\" />\n                  </SelectTrigger>\n                  <SelectContent align=\"end\">\n                    <SelectItem value=\"edit\">Can edit</SelectItem>\n                    <SelectItem value=\"view\">Can view</SelectItem>\n                  </SelectContent>\n                </Select>\n              </div>\n            ))}\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/stats.tsx",
    "content": "\"use client\";\n\nimport { Area, AreaChart, Line, LineChart } from \"recharts\";\n\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { ChartConfig, ChartContainer } from \"@/components/ui/chart\";\n\nconst data = [\n  {\n    revenue: 10400,\n    subscription: 40,\n  },\n  {\n    revenue: 14405,\n    subscription: 90,\n  },\n  {\n    revenue: 9400,\n    subscription: 200,\n  },\n  {\n    revenue: 8200,\n    subscription: 278,\n  },\n  {\n    revenue: 7000,\n    subscription: 89,\n  },\n  {\n    revenue: 9600,\n    subscription: 239,\n  },\n  {\n    revenue: 11244,\n    subscription: 78,\n  },\n  {\n    revenue: 26475,\n    subscription: 89,\n  },\n];\n\nconst chartConfig = {\n  revenue: {\n    label: \"Revenue\",\n    color: \"var(--primary)\",\n  },\n  subscription: {\n    label: \"Subscriptions\",\n    color: \"var(--primary)\",\n  },\n} satisfies ChartConfig;\n\nexport function CardsStats() {\n  return (\n    <div className=\"grid gap-4 @xl:grid-cols-2 @5xl:grid-cols-1 @7xl:grid-cols-2\">\n      <Card>\n        <CardHeader>\n          <CardDescription>Total Revenue</CardDescription>\n          <CardTitle className=\"text-3xl\">$15,231.89</CardTitle>\n          <CardDescription>+20.1% from last month</CardDescription>\n        </CardHeader>\n        <CardContent className=\"pb-0\">\n          <ChartContainer config={chartConfig} className=\"h-[90px] w-full\">\n            <LineChart\n              data={data}\n              margin={{\n                top: 5,\n                right: 10,\n                left: 10,\n                bottom: 0,\n              }}\n            >\n              <Line\n                type=\"monotone\"\n                strokeWidth={2}\n                dataKey=\"revenue\"\n                stroke=\"var(--color-revenue)\"\n                activeDot={{\n                  r: 6,\n                }}\n              />\n            </LineChart>\n          </ChartContainer>\n        </CardContent>\n      </Card>\n      <Card className=\"relative flex flex-col overflow-hidden pb-0 @5xl:hidden @7xl:flex\">\n        <CardHeader>\n          <CardDescription>Subscriptions</CardDescription>\n          <CardTitle className=\"text-3xl\">+2,350</CardTitle>\n          <CardDescription>+180.1% from last month</CardDescription>\n        </CardHeader>\n        <CardContent className=\"relative mt-auto flex-1 p-0\">\n          <ChartContainer config={chartConfig} className=\"relative size-full h-[90px]\">\n            <AreaChart\n              data={data}\n              margin={{\n                left: 0,\n                right: 0,\n              }}\n              className=\"size-fit\"\n            >\n              <Area\n                dataKey=\"subscription\"\n                fill=\"var(--color-subscription)\"\n                fillOpacity={0.05}\n                stroke=\"var(--color-subscription)\"\n                strokeWidth={2}\n                type=\"monotone\"\n              />\n            </AreaChart>\n          </ChartContainer>\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/cards/team-members.tsx",
    "content": "\"use client\";\n\nimport { ChevronDown } from \"lucide-react\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\n\nconst teamMembers = [\n  {\n    name: \"Sofia Davis\",\n    email: \"m@example.com\",\n    avatar: \"/avatars/01.png\",\n    role: \"Owner\",\n  },\n  {\n    name: \"Jackson Lee\",\n    email: \"p@example.com\",\n    avatar: \"/avatars/02.png\",\n    role: \"Developer\",\n  },\n  {\n    name: \"Isabella Nguyen\",\n    email: \"i@example.com\",\n    avatar: \"/avatars/03.png\",\n    role: \"Billing\",\n  },\n];\n\nconst roles = [\n  {\n    name: \"Viewer\",\n    description: \"Can view and comment.\",\n  },\n  {\n    name: \"Developer\",\n    description: \"Can view, comment and edit.\",\n  },\n  {\n    name: \"Billing\",\n    description: \"Can view, comment and manage billing.\",\n  },\n  {\n    name: \"Owner\",\n    description: \"Admin-level access to all resources.\",\n  },\n];\n\nexport function CardsTeamMembers() {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Team Members</CardTitle>\n        <CardDescription>Invite your team members to collaborate.</CardDescription>\n      </CardHeader>\n      <CardContent className=\"grid gap-6\">\n        {teamMembers.map((member) => (\n          <div key={member.name} className=\"flex items-center justify-between gap-4\">\n            <div className=\"flex items-center gap-4\">\n              <Avatar className=\"border\">\n                <AvatarImage src={member.avatar} alt=\"Image\" />\n                <AvatarFallback>{member.name.charAt(0)}</AvatarFallback>\n              </Avatar>\n              <div className=\"flex flex-col gap-0.5\">\n                <p className=\"text-sm leading-none font-medium\">{member.name}</p>\n                <p className=\"text-muted-foreground text-xs\">{member.email}</p>\n              </div>\n            </div>\n            <Popover>\n              <PopoverTrigger asChild>\n                <Button variant=\"outline\" size=\"sm\" className=\"ml-auto shadow-none\">\n                  {member.role} <ChevronDown />\n                </Button>\n              </PopoverTrigger>\n              <PopoverContent className=\"p-0\" align=\"end\">\n                <Command>\n                  <CommandInput placeholder=\"Select role...\" />\n                  <CommandList>\n                    <CommandEmpty>No roles found.</CommandEmpty>\n                    <CommandGroup>\n                      {roles.map((role) => (\n                        <CommandItem key={role.name}>\n                          <div className=\"flex flex-col\">\n                            <p className=\"text-sm font-medium\">{role.name}</p>\n                            <p className=\"text-muted-foreground\">{role.description}</p>\n                          </div>\n                        </CommandItem>\n                      ))}\n                    </CommandGroup>\n                  </CommandList>\n                </Command>\n              </PopoverContent>\n            </Popover>\n          </div>\n        ))}\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/custom/index.tsx",
    "content": "import { DynamicWebsitePreview } from \"@/components/dynamic-website-preview\";\n\nexport default function CustomDemo() {\n  return <DynamicWebsitePreview name=\"Custom Website Preview\" allowCrossOrigin />;\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/app-sidebar.tsx",
    "content": "import * as React from \"react\";\nimport {\n  ArrowUpCircleIcon,\n  BarChartIcon,\n  CameraIcon,\n  ClipboardListIcon,\n  DatabaseIcon,\n  FileCodeIcon,\n  FileIcon,\n  FileTextIcon,\n  FolderIcon,\n  HelpCircleIcon,\n  LayoutDashboardIcon,\n  ListIcon,\n  SearchIcon,\n  SettingsIcon,\n  UsersIcon,\n} from \"lucide-react\";\n\nimport { NavDocuments } from \"@/components/examples/dashboard/components/nav-documents\";\nimport { NavMain } from \"@/components/examples/dashboard/components/nav-main\";\nimport { NavSecondary } from \"@/components/examples/dashboard/components/nav-secondary\";\nimport { NavUser } from \"@/components/examples/dashboard/components/nav-user\";\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarHeader,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"@/components/ui/sidebar\";\n\nconst data = {\n  user: {\n    name: \"shadcn\",\n    email: \"m@example.com\",\n    avatar: \"/avatars/shadcn.jpg\",\n  },\n  navMain: [\n    {\n      title: \"Dashboard\",\n      url: \"#\",\n      icon: LayoutDashboardIcon,\n    },\n    {\n      title: \"Lifecycle\",\n      url: \"#\",\n      icon: ListIcon,\n    },\n    {\n      title: \"Analytics\",\n      url: \"#\",\n      icon: BarChartIcon,\n    },\n    {\n      title: \"Projects\",\n      url: \"#\",\n      icon: FolderIcon,\n    },\n    {\n      title: \"Team\",\n      url: \"#\",\n      icon: UsersIcon,\n    },\n  ],\n  navClouds: [\n    {\n      title: \"Capture\",\n      icon: CameraIcon,\n      isActive: true,\n      url: \"#\",\n      items: [\n        {\n          title: \"Active Proposals\",\n          url: \"#\",\n        },\n        {\n          title: \"Archived\",\n          url: \"#\",\n        },\n      ],\n    },\n    {\n      title: \"Proposal\",\n      icon: FileTextIcon,\n      url: \"#\",\n      items: [\n        {\n          title: \"Active Proposals\",\n          url: \"#\",\n        },\n        {\n          title: \"Archived\",\n          url: \"#\",\n        },\n      ],\n    },\n    {\n      title: \"Prompts\",\n      icon: FileCodeIcon,\n      url: \"#\",\n      items: [\n        {\n          title: \"Active Proposals\",\n          url: \"#\",\n        },\n        {\n          title: \"Archived\",\n          url: \"#\",\n        },\n      ],\n    },\n  ],\n  navSecondary: [\n    {\n      title: \"Settings\",\n      url: \"#\",\n      icon: SettingsIcon,\n    },\n    {\n      title: \"Get Help\",\n      url: \"#\",\n      icon: HelpCircleIcon,\n    },\n    {\n      title: \"Search\",\n      url: \"#\",\n      icon: SearchIcon,\n    },\n  ],\n  documents: [\n    {\n      name: \"Data Library\",\n      url: \"#\",\n      icon: DatabaseIcon,\n    },\n    {\n      name: \"Reports\",\n      url: \"#\",\n      icon: ClipboardListIcon,\n    },\n    {\n      name: \"Word Assistant\",\n      url: \"#\",\n      icon: FileIcon,\n    },\n  ],\n};\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n  return (\n    <Sidebar collapsible=\"offcanvas\" {...props}>\n      <SidebarHeader>\n        <SidebarMenu>\n          <SidebarMenuItem>\n            <SidebarMenuButton\n              asChild\n              className=\"data-[slot=sidebar-menu-button]:!p-1.5\"\n            >\n              <a href=\"#\">\n                <ArrowUpCircleIcon className=\"h-5 w-5\" />\n                <span className=\"text-base font-semibold\">Acme Inc.</span>\n              </a>\n            </SidebarMenuButton>\n          </SidebarMenuItem>\n        </SidebarMenu>\n      </SidebarHeader>\n      <SidebarContent>\n        <NavMain items={data.navMain} />\n        <NavDocuments items={data.documents} />\n        <NavSecondary items={data.navSecondary} className=\"mt-auto\" />\n      </SidebarContent>\n      <SidebarFooter>\n        <NavUser user={data.user} />\n      </SidebarFooter>\n    </Sidebar>\n  );\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/chart-area-interactive.tsx",
    "content": "import * as React from \"react\";\nimport { Area, AreaChart, CartesianGrid, XAxis } from \"recharts\";\n\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport {\n  ChartConfig,\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n} from \"@/components/ui/chart\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\n\nexport const description = \"An interactive area chart\";\n\nconst chartData = [\n  { date: \"2024-04-01\", desktop: 222, mobile: 150 },\n  { date: \"2024-04-02\", desktop: 97, mobile: 180 },\n  { date: \"2024-04-03\", desktop: 167, mobile: 120 },\n  { date: \"2024-04-04\", desktop: 242, mobile: 260 },\n  { date: \"2024-04-05\", desktop: 373, mobile: 290 },\n  { date: \"2024-04-06\", desktop: 301, mobile: 340 },\n  { date: \"2024-04-07\", desktop: 245, mobile: 180 },\n  { date: \"2024-04-08\", desktop: 409, mobile: 320 },\n  { date: \"2024-04-09\", desktop: 59, mobile: 110 },\n  { date: \"2024-04-10\", desktop: 261, mobile: 190 },\n  { date: \"2024-04-11\", desktop: 327, mobile: 350 },\n  { date: \"2024-04-12\", desktop: 292, mobile: 210 },\n  { date: \"2024-04-13\", desktop: 342, mobile: 380 },\n  { date: \"2024-04-14\", desktop: 137, mobile: 220 },\n  { date: \"2024-04-15\", desktop: 120, mobile: 170 },\n  { date: \"2024-04-16\", desktop: 138, mobile: 190 },\n  { date: \"2024-04-17\", desktop: 446, mobile: 360 },\n  { date: \"2024-04-18\", desktop: 364, mobile: 410 },\n  { date: \"2024-04-19\", desktop: 243, mobile: 180 },\n  { date: \"2024-04-20\", desktop: 89, mobile: 150 },\n  { date: \"2024-04-21\", desktop: 137, mobile: 200 },\n  { date: \"2024-04-22\", desktop: 224, mobile: 170 },\n  { date: \"2024-04-23\", desktop: 138, mobile: 230 },\n  { date: \"2024-04-24\", desktop: 387, mobile: 290 },\n  { date: \"2024-04-25\", desktop: 215, mobile: 250 },\n  { date: \"2024-04-26\", desktop: 75, mobile: 130 },\n  { date: \"2024-04-27\", desktop: 383, mobile: 420 },\n  { date: \"2024-04-28\", desktop: 122, mobile: 180 },\n  { date: \"2024-04-29\", desktop: 315, mobile: 240 },\n  { date: \"2024-04-30\", desktop: 454, mobile: 380 },\n  { date: \"2024-05-01\", desktop: 165, mobile: 220 },\n  { date: \"2024-05-02\", desktop: 293, mobile: 310 },\n  { date: \"2024-05-03\", desktop: 247, mobile: 190 },\n  { date: \"2024-05-04\", desktop: 385, mobile: 420 },\n  { date: \"2024-05-05\", desktop: 481, mobile: 390 },\n  { date: \"2024-05-06\", desktop: 498, mobile: 520 },\n  { date: \"2024-05-07\", desktop: 388, mobile: 300 },\n  { date: \"2024-05-08\", desktop: 149, mobile: 210 },\n  { date: \"2024-05-09\", desktop: 227, mobile: 180 },\n  { date: \"2024-05-10\", desktop: 293, mobile: 330 },\n  { date: \"2024-05-11\", desktop: 335, mobile: 270 },\n  { date: \"2024-05-12\", desktop: 197, mobile: 240 },\n  { date: \"2024-05-13\", desktop: 197, mobile: 160 },\n  { date: \"2024-05-14\", desktop: 448, mobile: 490 },\n  { date: \"2024-05-15\", desktop: 473, mobile: 380 },\n  { date: \"2024-05-16\", desktop: 338, mobile: 400 },\n  { date: \"2024-05-17\", desktop: 499, mobile: 420 },\n  { date: \"2024-05-18\", desktop: 315, mobile: 350 },\n  { date: \"2024-05-19\", desktop: 235, mobile: 180 },\n  { date: \"2024-05-20\", desktop: 177, mobile: 230 },\n  { date: \"2024-05-21\", desktop: 82, mobile: 140 },\n  { date: \"2024-05-22\", desktop: 81, mobile: 120 },\n  { date: \"2024-05-23\", desktop: 252, mobile: 290 },\n  { date: \"2024-05-24\", desktop: 294, mobile: 220 },\n  { date: \"2024-05-25\", desktop: 201, mobile: 250 },\n  { date: \"2024-05-26\", desktop: 213, mobile: 170 },\n  { date: \"2024-05-27\", desktop: 420, mobile: 460 },\n  { date: \"2024-05-28\", desktop: 233, mobile: 190 },\n  { date: \"2024-05-29\", desktop: 78, mobile: 130 },\n  { date: \"2024-05-30\", desktop: 340, mobile: 280 },\n  { date: \"2024-05-31\", desktop: 178, mobile: 230 },\n  { date: \"2024-06-01\", desktop: 178, mobile: 200 },\n  { date: \"2024-06-02\", desktop: 470, mobile: 410 },\n  { date: \"2024-06-03\", desktop: 103, mobile: 160 },\n  { date: \"2024-06-04\", desktop: 439, mobile: 380 },\n  { date: \"2024-06-05\", desktop: 88, mobile: 140 },\n  { date: \"2024-06-06\", desktop: 294, mobile: 250 },\n  { date: \"2024-06-07\", desktop: 323, mobile: 370 },\n  { date: \"2024-06-08\", desktop: 385, mobile: 320 },\n  { date: \"2024-06-09\", desktop: 438, mobile: 480 },\n  { date: \"2024-06-10\", desktop: 155, mobile: 200 },\n  { date: \"2024-06-11\", desktop: 92, mobile: 150 },\n  { date: \"2024-06-12\", desktop: 492, mobile: 420 },\n  { date: \"2024-06-13\", desktop: 81, mobile: 130 },\n  { date: \"2024-06-14\", desktop: 426, mobile: 380 },\n  { date: \"2024-06-15\", desktop: 307, mobile: 350 },\n  { date: \"2024-06-16\", desktop: 371, mobile: 310 },\n  { date: \"2024-06-17\", desktop: 475, mobile: 520 },\n  { date: \"2024-06-18\", desktop: 107, mobile: 170 },\n  { date: \"2024-06-19\", desktop: 341, mobile: 290 },\n  { date: \"2024-06-20\", desktop: 408, mobile: 450 },\n  { date: \"2024-06-21\", desktop: 169, mobile: 210 },\n  { date: \"2024-06-22\", desktop: 317, mobile: 270 },\n  { date: \"2024-06-23\", desktop: 480, mobile: 530 },\n  { date: \"2024-06-24\", desktop: 132, mobile: 180 },\n  { date: \"2024-06-25\", desktop: 141, mobile: 190 },\n  { date: \"2024-06-26\", desktop: 434, mobile: 380 },\n  { date: \"2024-06-27\", desktop: 448, mobile: 490 },\n  { date: \"2024-06-28\", desktop: 149, mobile: 200 },\n  { date: \"2024-06-29\", desktop: 103, mobile: 160 },\n  { date: \"2024-06-30\", desktop: 446, mobile: 400 },\n];\n\nconst chartConfig = {\n  visitors: {\n    label: \"Visitors\",\n  },\n  desktop: {\n    label: \"Desktop\",\n    color: \"var(--chart-1)\",\n  },\n  mobile: {\n    label: \"Mobile\",\n    color: \"var(--chart-2)\",\n  },\n} satisfies ChartConfig;\n\nexport function ChartAreaInteractive() {\n  const isMobile = useIsMobile();\n  const [timeRange, setTimeRange] = React.useState(\"30d\");\n\n  React.useEffect(() => {\n    if (isMobile) {\n      setTimeRange(\"7d\");\n    }\n  }, [isMobile]);\n\n  const filteredData = chartData.filter((item) => {\n    const date = new Date(item.date);\n    const referenceDate = new Date(\"2024-06-30\");\n    let daysToSubtract = 90;\n    if (timeRange === \"30d\") {\n      daysToSubtract = 30;\n    } else if (timeRange === \"7d\") {\n      daysToSubtract = 7;\n    }\n    const startDate = new Date(referenceDate);\n    startDate.setDate(startDate.getDate() - daysToSubtract);\n    return date >= startDate;\n  });\n\n  return (\n    <Card className=\"@container/card\">\n      <CardHeader className=\"relative\">\n        <CardTitle>Total Visitors</CardTitle>\n        <CardDescription>\n          <span className=\"@[540px]/card:block hidden\">\n            Total for the last 3 months\n          </span>\n          <span className=\"@[540px]/card:hidden\">Last 3 months</span>\n        </CardDescription>\n        <div className=\"absolute right-4 top-4\">\n          <ToggleGroup\n            type=\"single\"\n            value={timeRange}\n            onValueChange={setTimeRange}\n            variant=\"outline\"\n            className=\"@[767px]/card:flex hidden\"\n          >\n            <ToggleGroupItem value=\"90d\" className=\"h-8 px-2.5\">\n              Last 3 months\n            </ToggleGroupItem>\n            <ToggleGroupItem value=\"30d\" className=\"h-8 px-2.5\">\n              Last 30 days\n            </ToggleGroupItem>\n            <ToggleGroupItem value=\"7d\" className=\"h-8 px-2.5\">\n              Last 7 days\n            </ToggleGroupItem>\n          </ToggleGroup>\n          <Select value={timeRange} onValueChange={setTimeRange}>\n            <SelectTrigger\n              className=\"@[767px]/card:hidden flex w-40\"\n              aria-label=\"Select a value\"\n            >\n              <SelectValue placeholder=\"Last 3 months\" />\n            </SelectTrigger>\n            <SelectContent className=\"rounded-xl\">\n              <SelectItem value=\"90d\" className=\"rounded-lg\">\n                Last 3 months\n              </SelectItem>\n              <SelectItem value=\"30d\" className=\"rounded-lg\">\n                Last 30 days\n              </SelectItem>\n              <SelectItem value=\"7d\" className=\"rounded-lg\">\n                Last 7 days\n              </SelectItem>\n            </SelectContent>\n          </Select>\n        </div>\n      </CardHeader>\n      <CardContent className=\"px-2 pt-4 sm:px-6 sm:pt-6\">\n        <ChartContainer\n          config={chartConfig}\n          className=\"aspect-auto h-[250px] w-full\"\n        >\n          <AreaChart data={filteredData}>\n            <defs>\n              <linearGradient id=\"fillDesktop\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                <stop\n                  offset=\"5%\"\n                  stopColor=\"var(--color-desktop)\"\n                  stopOpacity={1.0}\n                />\n                <stop\n                  offset=\"95%\"\n                  stopColor=\"var(--color-desktop)\"\n                  stopOpacity={0.1}\n                />\n              </linearGradient>\n              <linearGradient id=\"fillMobile\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                <stop\n                  offset=\"5%\"\n                  stopColor=\"var(--color-mobile)\"\n                  stopOpacity={0.8}\n                />\n                <stop\n                  offset=\"95%\"\n                  stopColor=\"var(--color-mobile)\"\n                  stopOpacity={0.1}\n                />\n              </linearGradient>\n            </defs>\n            <CartesianGrid vertical={false} />\n            <XAxis\n              dataKey=\"date\"\n              tickLine={false}\n              axisLine={false}\n              tickMargin={8}\n              minTickGap={32}\n              tickFormatter={(value) => {\n                const date = new Date(value);\n                return date.toLocaleDateString(\"en-US\", {\n                  month: \"short\",\n                  day: \"numeric\",\n                });\n              }}\n            />\n            <ChartTooltip\n              cursor={false}\n              content={\n                <ChartTooltipContent\n                  labelFormatter={(value) => {\n                    return new Date(value).toLocaleDateString(\"en-US\", {\n                      month: \"short\",\n                      day: \"numeric\",\n                    });\n                  }}\n                  indicator=\"dot\"\n                />\n              }\n            />\n            <Area\n              dataKey=\"mobile\"\n              type=\"natural\"\n              fill=\"url(#fillMobile)\"\n              stroke=\"var(--color-mobile)\"\n              stackId=\"a\"\n            />\n            <Area\n              dataKey=\"desktop\"\n              type=\"natural\"\n              fill=\"url(#fillDesktop)\"\n              stroke=\"var(--color-desktop)\"\n              stackId=\"a\"\n            />\n          </AreaChart>\n        </ChartContainer>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/chart-bar-mixed.tsx",
    "content": "\"use client\"\n\nimport { TrendingUp } from \"lucide-react\"\nimport { Bar, BarChart, XAxis, YAxis } from \"recharts\"\n\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\"\nimport {\n  ChartConfig,\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n} from \"@/components/ui/chart\"\nconst chartData = [\n  { browser: \"chrome\", visitors: 275, fill: \"var(--color-chrome)\" },\n  { browser: \"safari\", visitors: 200, fill: \"var(--color-safari)\" },\n  { browser: \"firefox\", visitors: 187, fill: \"var(--color-firefox)\" },\n  { browser: \"edge\", visitors: 173, fill: \"var(--color-edge)\" },\n  { browser: \"other\", visitors: 90, fill: \"var(--color-other)\" },\n]\n\nconst chartConfig = {\n  visitors: {\n    label: \"Visitors\",\n  },\n  chrome: {\n    label: \"Chrome\",\n    color: \"var(--chart-1)\",\n  },\n  safari: {\n    label: \"Safari\",\n    color: \"var(--chart-2)\",\n  },\n  firefox: {\n    label: \"Firefox\",\n    color: \"var(--chart-3)\",\n  },\n  edge: {\n    label: \"Edge\",\n    color: \"var(--chart-4)\",\n  },\n  other: {\n    label: \"Other\",\n    color: \"var(--chart-5)\",\n  },\n} satisfies ChartConfig\n\nexport function ChartBarMixed() {\n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle>Bar Chart - Mixed</CardTitle>\n        <CardDescription>January - June 2024</CardDescription>\n      </CardHeader>\n      <CardContent>\n        <ChartContainer config={chartConfig}>\n          <BarChart\n            accessibilityLayer\n            data={chartData}\n            layout=\"vertical\"\n            margin={{\n              left: 0,\n            }}\n          >\n            <YAxis\n              dataKey=\"browser\"\n              type=\"category\"\n              tickLine={false}\n              tickMargin={10}\n              axisLine={false}\n              tickFormatter={(value) =>\n                chartConfig[value as keyof typeof chartConfig]?.label\n              }\n            />\n            <XAxis dataKey=\"visitors\" type=\"number\" hide />\n            <ChartTooltip\n              cursor={false}\n              content={<ChartTooltipContent hideLabel />}\n            />\n            <Bar dataKey=\"visitors\" layout=\"vertical\" radius={5} />\n          </BarChart>\n        </ChartContainer>\n      </CardContent>\n      <CardFooter className=\"flex-col items-start gap-2 text-sm\">\n        <div className=\"flex gap-2 font-medium leading-none\">\n          Trending up by 5.2% this month <TrendingUp className=\"h-4 w-4\" />\n        </div>\n        <div className=\"leading-none text-muted-foreground\">\n          Showing total visitors for the last 6 months\n        </div>\n      </CardFooter>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/chart-pie-donut.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { TrendingUp } from \"lucide-react\"\nimport { Label, Pie, PieChart } from \"recharts\"\n\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\"\nimport {\n  ChartConfig,\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n} from \"@/components/ui/chart\"\nconst chartData = [\n  { browser: \"chrome\", visitors: 275, fill: \"var(--color-chrome)\" },\n  { browser: \"safari\", visitors: 200, fill: \"var(--color-safari)\" },\n  { browser: \"firefox\", visitors: 287, fill: \"var(--color-firefox)\" },\n  { browser: \"edge\", visitors: 173, fill: \"var(--color-edge)\" },\n  { browser: \"other\", visitors: 190, fill: \"var(--color-other)\" },\n]\n\nconst chartConfig = {\n  visitors: {\n    label: \"Visitors\",\n  },\n  chrome: {\n    label: \"Chrome\",\n    color: \"var(--chart-1)\",\n  },\n  safari: {\n    label: \"Safari\",\n    color: \"var(--chart-2)\",\n  },\n  firefox: {\n    label: \"Firefox\",\n    color: \"var(--chart-3)\",\n  },\n  edge: {\n    label: \"Edge\",\n    color: \"var(--chart-4)\",\n  },\n  other: {\n    label: \"Other\",\n    color: \"var(--chart-5)\",\n  },\n} satisfies ChartConfig\n\nexport function ChartPieDonut() {\n  const totalVisitors = React.useMemo(() => {\n    return chartData.reduce((acc, curr) => acc + curr.visitors, 0)\n  }, [])\n\n  return (\n    <Card className=\"flex flex-col h-full\">\n      <CardHeader className=\"items-center pb-0\">\n        <CardTitle>Pie Chart - Donut with Text</CardTitle>\n        <CardDescription>January - June 2024</CardDescription>\n      </CardHeader>\n      <CardContent className=\"flex-1 pb-0\">\n        <ChartContainer\n          config={chartConfig}\n          className=\"mx-auto aspect-square max-h-[300px]\"\n        >\n          <PieChart>\n            <ChartTooltip\n              cursor={false}\n              content={<ChartTooltipContent hideLabel />}\n            />\n            <Pie\n              data={chartData}\n              dataKey=\"visitors\"\n              nameKey=\"browser\"\n              innerRadius={60}\n              strokeWidth={5}\n            >\n              <Label\n                content={({ viewBox }) => {\n                  if (viewBox && \"cx\" in viewBox && \"cy\" in viewBox) {\n                    return (\n                      <text\n                        x={viewBox.cx}\n                        y={viewBox.cy}\n                        textAnchor=\"middle\"\n                        dominantBaseline=\"middle\"\n                      >\n                        <tspan\n                          x={viewBox.cx}\n                          y={viewBox.cy}\n                          className=\"fill-foreground text-3xl font-bold\"\n                        >\n                          {totalVisitors.toLocaleString()}\n                        </tspan>\n                        <tspan\n                          x={viewBox.cx}\n                          y={(viewBox.cy || 0) + 24}\n                          className=\"fill-muted-foreground\"\n                        >\n                          Visitors\n                        </tspan>\n                      </text>\n                    )\n                  }\n                }}\n              />\n            </Pie>\n          </PieChart>\n        </ChartContainer>\n      </CardContent>\n      <CardFooter className=\"flex-col gap-2 text-sm\">\n        <div className=\"flex items-center gap-2 font-medium leading-none\">\n          Trending up by 5.2% this month <TrendingUp className=\"h-4 w-4\" />\n        </div>\n        <div className=\"leading-none text-muted-foreground\">\n          Showing total visitors for the last 6 months\n        </div>\n      </CardFooter>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/data-table.tsx",
    "content": "import * as React from \"react\";\nimport {\n  DndContext,\n  KeyboardSensor,\n  MouseSensor,\n  TouchSensor,\n  closestCenter,\n  useSensor,\n  useSensors,\n  type DragEndEvent,\n  type UniqueIdentifier,\n} from \"@dnd-kit/core\";\nimport { restrictToVerticalAxis } from \"@dnd-kit/modifiers\";\nimport {\n  SortableContext,\n  arrayMove,\n  useSortable,\n  verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport {\n  ColumnDef,\n  ColumnFiltersState,\n  Row,\n  SortingState,\n  VisibilityState,\n  flexRender,\n  getCoreRowModel,\n  getFacetedRowModel,\n  getFacetedUniqueValues,\n  getFilteredRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  useReactTable,\n} from \"@tanstack/react-table\";\nimport {\n  ChevronDownIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  ChevronsLeftIcon,\n  ChevronsRightIcon,\n  ColumnsIcon,\n  GripVerticalIcon,\n  MoreVerticalIcon,\n  PlusIcon,\n  TrendingUpIcon,\n} from \"lucide-react\";\nimport { Area, AreaChart, CartesianGrid, XAxis } from \"recharts\";\nimport { toast } from \"sonner\";\nimport { z } from \"zod\";\n\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  ChartConfig,\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n} from \"@/components/ui/chart\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n  Sheet,\n  SheetClose,\n  SheetContent,\n  SheetDescription,\n  SheetFooter,\n  SheetHeader,\n  SheetTitle,\n  SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\n\nexport const schema = z.object({\n  id: z.number(),\n  header: z.string(),\n  type: z.string(),\n  target: z.string(),\n  limit: z.string(),\n  reviewer: z.string(),\n});\n\n// Create a separate component for the drag handle\nfunction DragHandle({ id }: { id: number }) {\n  const { attributes, listeners } = useSortable({\n    id,\n  });\n\n  return (\n    <Button\n      {...attributes}\n      {...listeners}\n      variant=\"ghost\"\n      size=\"icon\"\n      className=\"size-7 text-muted-foreground hover:bg-transparent\"\n    >\n      <GripVerticalIcon className=\"size-3 text-muted-foreground\" />\n      <span className=\"sr-only\">Drag to reorder</span>\n    </Button>\n  );\n}\n\nconst columns: ColumnDef<z.infer<typeof schema>>[] = [\n  {\n    id: \"drag\",\n    header: () => null,\n    cell: ({ row }) => <DragHandle id={row.original.id} />,\n  },\n  {\n    id: \"select\",\n    header: ({ table }) => (\n      <div className=\"flex items-center justify-center\">\n        <Checkbox\n          checked={\n            table.getIsAllPageRowsSelected() ||\n            (table.getIsSomePageRowsSelected() ? \"indeterminate\" : false)\n          }\n          onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}\n          aria-label=\"Select all\"\n        />\n      </div>\n    ),\n    cell: ({ row }) => (\n      <div className=\"flex items-center justify-center\">\n        <Checkbox\n          checked={row.getIsSelected()}\n          onCheckedChange={(value) => row.toggleSelected(!!value)}\n          aria-label=\"Select row\"\n        />\n      </div>\n    ),\n    enableSorting: false,\n    enableHiding: false,\n  },\n  {\n    accessorKey: \"header\",\n    header: \"Header\",\n    cell: ({ row }) => {\n      return <TableCellViewer item={row.original} />;\n    },\n    enableHiding: false,\n  },\n  {\n    accessorKey: \"type\",\n    header: \"Section Type\",\n    cell: ({ row }) => (\n      <div className=\"w-32\">\n        <Badge variant=\"outline\" className=\"px-1.5 text-muted-foreground\">\n          {row.original.type}\n        </Badge>\n      </div>\n    ),\n  },\n  {\n    accessorKey: \"target\",\n    header: () => <div className=\"w-full text-right\">Target</div>,\n    cell: ({ row }) => (\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {\n            loading: `Saving ${row.original.header}`,\n            success: \"Done\",\n            error: \"Error\",\n          });\n        }}\n      >\n        <Label htmlFor={`${row.original.id}-target`} className=\"sr-only\">\n          Target\n        </Label>\n        <Input\n          className=\"h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background\"\n          defaultValue={row.original.target}\n          id={`${row.original.id}-target`}\n        />\n      </form>\n    ),\n  },\n  {\n    accessorKey: \"limit\",\n    header: () => <div className=\"w-full text-right\">Limit</div>,\n    cell: ({ row }) => (\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {\n            loading: `Saving ${row.original.header}`,\n            success: \"Done\",\n            error: \"Error\",\n          });\n        }}\n      >\n        <Label htmlFor={`${row.original.id}-limit`} className=\"sr-only\">\n          Limit\n        </Label>\n        <Input\n          className=\"h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background\"\n          defaultValue={row.original.limit}\n          id={`${row.original.id}-limit`}\n        />\n      </form>\n    ),\n  },\n  {\n    accessorKey: \"reviewer\",\n    header: \"Reviewer\",\n    cell: ({ row }) => {\n      const isAssigned = row.original.reviewer !== \"Assign reviewer\";\n\n      if (isAssigned) {\n        return row.original.reviewer;\n      }\n\n      return (\n        <>\n          <Label htmlFor={`${row.original.id}-reviewer`} className=\"sr-only\">\n            Reviewer\n          </Label>\n          <Select>\n            <SelectTrigger\n              className=\"h-8 w-40\"\n              id={`${row.original.id}-reviewer`}\n            >\n              <SelectValue placeholder=\"Assign reviewer\" />\n            </SelectTrigger>\n            <SelectContent align=\"end\">\n              <SelectItem value=\"Eddie Lake\">Eddie Lake</SelectItem>\n              <SelectItem value=\"Jamik Tashpulatov\">\n                Jamik Tashpulatov\n              </SelectItem>\n            </SelectContent>\n          </Select>\n        </>\n      );\n    },\n  },\n  {\n    id: \"actions\",\n    cell: () => (\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild>\n          <Button\n            variant=\"ghost\"\n            className=\"flex size-8 text-muted-foreground data-[state=open]:bg-muted\"\n            size=\"icon\"\n          >\n            <MoreVerticalIcon />\n            <span className=\"sr-only\">Open menu</span>\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent align=\"end\" className=\"w-32\">\n          <DropdownMenuItem>Edit</DropdownMenuItem>\n          <DropdownMenuItem>Make a copy</DropdownMenuItem>\n          <DropdownMenuItem>Favorite</DropdownMenuItem>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem className=\"text-destructive focus:text-destructive\">\n            Delete\n          </DropdownMenuItem>\n        </DropdownMenuContent>\n      </DropdownMenu>\n    ),\n  },\n];\n\nfunction DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {\n  const { transform, transition, setNodeRef, isDragging } = useSortable({\n    id: row.original.id,\n  });\n\n  return (\n    <TableRow\n      data-state={row.getIsSelected() && \"selected\"}\n      data-dragging={isDragging}\n      ref={setNodeRef}\n      className=\"relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80\"\n      style={{\n        transform: CSS.Transform.toString(transform),\n        transition: transition,\n      }}\n    >\n      {row.getVisibleCells().map((cell) => (\n        <TableCell key={cell.id}>\n          {flexRender(cell.column.columnDef.cell, cell.getContext())}\n        </TableCell>\n      ))}\n    </TableRow>\n  );\n}\n\nexport function DataTable({\n  data: initialData,\n}: {\n  data: z.infer<typeof schema>[];\n}) {\n  const [data, setData] = React.useState(() => initialData);\n  const [rowSelection, setRowSelection] = React.useState({});\n  const [columnVisibility, setColumnVisibility] =\n    React.useState<VisibilityState>({});\n  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(\n    []\n  );\n  const [sorting, setSorting] = React.useState<SortingState>([]);\n  const [pagination, setPagination] = React.useState({\n    pageIndex: 0,\n    pageSize: 10,\n  });\n  const sortableId = React.useId();\n  const sensors = useSensors(\n    useSensor(MouseSensor, {}),\n    useSensor(TouchSensor, {}),\n    useSensor(KeyboardSensor, {})\n  );\n\n  const dataIds = React.useMemo<UniqueIdentifier[]>(\n    () => data?.map(({ id }) => id) || [],\n    [data]\n  );\n\n  const table = useReactTable({\n    data,\n    columns,\n    state: {\n      sorting,\n      columnVisibility,\n      rowSelection,\n      columnFilters,\n      pagination,\n    },\n    getRowId: (row) => row.id.toString(),\n    enableRowSelection: true,\n    onRowSelectionChange: setRowSelection,\n    onSortingChange: setSorting,\n    onColumnFiltersChange: setColumnFilters,\n    onColumnVisibilityChange: setColumnVisibility,\n    onPaginationChange: setPagination,\n    getCoreRowModel: getCoreRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFacetedRowModel: getFacetedRowModel(),\n    getFacetedUniqueValues: getFacetedUniqueValues(),\n  });\n\n  function handleDragEnd(event: DragEndEvent) {\n    const { active, over } = event;\n    if (active && over && active.id !== over.id) {\n      setData((data) => {\n        const oldIndex = dataIds.indexOf(active.id);\n        const newIndex = dataIds.indexOf(over.id);\n        return arrayMove(data, oldIndex, newIndex);\n      });\n    }\n  }\n\n  return (\n    <Tabs\n      defaultValue=\"outline\"\n      className=\"flex w-full flex-col justify-start gap-6\"\n    >\n      <div className=\"flex items-center justify-between px-4 lg:px-6\">\n        <Label htmlFor=\"view-selector\" className=\"sr-only\">\n          View\n        </Label>\n        <Select defaultValue=\"outline\">\n          <SelectTrigger\n            className=\"@4xl/main:hidden flex w-fit\"\n            id=\"view-selector\"\n          >\n            <SelectValue placeholder=\"Select a view\" />\n          </SelectTrigger>\n          <SelectContent>\n            <SelectItem value=\"outline\">Outline</SelectItem>\n            <SelectItem value=\"past-performance\">Past Performance</SelectItem>\n            <SelectItem value=\"key-personnel\">Key Personnel</SelectItem>\n            <SelectItem value=\"focus-documents\">Focus Documents</SelectItem>\n          </SelectContent>\n        </Select>\n        <TabsList className=\"@4xl/main:flex hidden\">\n          <TabsTrigger value=\"outline\">Outline</TabsTrigger>\n          <TabsTrigger value=\"past-performance\" className=\"gap-1\">\n            Past Performance{\" \"}\n            <Badge\n              variant=\"secondary\"\n              className=\"flex h-5 w-5 items-center justify-center rounded-full bg-muted-foreground/30\"\n            >\n              3\n            </Badge>\n          </TabsTrigger>\n          <TabsTrigger value=\"key-personnel\" className=\"gap-1\">\n            Key Personnel{\" \"}\n            <Badge\n              variant=\"secondary\"\n              className=\"flex h-5 w-5 items-center justify-center rounded-full bg-muted-foreground/30\"\n            >\n              2\n            </Badge>\n          </TabsTrigger>\n          <TabsTrigger value=\"focus-documents\">Focus Documents</TabsTrigger>\n        </TabsList>\n        <div className=\"flex items-center gap-2\">\n          <DropdownMenu>\n            <DropdownMenuTrigger asChild>\n              <Button variant=\"outline\" size=\"sm\">\n                <ColumnsIcon />\n                <span className=\"hidden lg:inline\">Customize Columns</span>\n                <span className=\"lg:hidden\">Columns</span>\n                <ChevronDownIcon />\n              </Button>\n            </DropdownMenuTrigger>\n            <DropdownMenuContent align=\"end\" className=\"w-56\">\n              {table\n                .getAllColumns()\n                .filter(\n                  (column) =>\n                    typeof column.accessorFn !== \"undefined\" &&\n                    column.getCanHide()\n                )\n                .map((column) => {\n                  return (\n                    <DropdownMenuCheckboxItem\n                      key={column.id}\n                      className=\"capitalize\"\n                      checked={column.getIsVisible()}\n                      onCheckedChange={(value) =>\n                        column.toggleVisibility(!!value)\n                      }\n                    >\n                      {column.id}\n                    </DropdownMenuCheckboxItem>\n                  );\n                })}\n            </DropdownMenuContent>\n          </DropdownMenu>\n          <Button variant=\"outline\" size=\"sm\">\n            <PlusIcon />\n            <span className=\"hidden lg:inline\">Add Section</span>\n          </Button>\n        </div>\n      </div>\n      <TabsContent\n        value=\"outline\"\n        className=\"relative flex flex-col gap-4 overflow-auto px-4 lg:px-6\"\n      >\n        <div className=\"overflow-hidden rounded-lg border\">\n          <DndContext\n            collisionDetection={closestCenter}\n            modifiers={[restrictToVerticalAxis]}\n            onDragEnd={handleDragEnd}\n            sensors={sensors}\n            id={sortableId}\n          >\n            <Table>\n              <TableHeader className=\"sticky top-0 z-10 bg-muted\">\n                {table.getHeaderGroups().map((headerGroup) => (\n                  <TableRow key={headerGroup.id}>\n                    {headerGroup.headers.map((header) => {\n                      return (\n                        <TableHead key={header.id} colSpan={header.colSpan}>\n                          {header.isPlaceholder\n                            ? null\n                            : flexRender(\n                                header.column.columnDef.header,\n                                header.getContext()\n                              )}\n                        </TableHead>\n                      );\n                    })}\n                  </TableRow>\n                ))}\n              </TableHeader>\n              <TableBody className=\"**:data-[slot=table-cell]:first:w-8\">\n                {table.getRowModel().rows?.length ? (\n                  <SortableContext\n                    items={dataIds}\n                    strategy={verticalListSortingStrategy}\n                  >\n                    {table.getRowModel().rows.map((row) => (\n                      <DraggableRow key={row.id} row={row} />\n                    ))}\n                  </SortableContext>\n                ) : (\n                  <TableRow>\n                    <TableCell\n                      colSpan={columns.length}\n                      className=\"h-24 text-center\"\n                    >\n                      No results.\n                    </TableCell>\n                  </TableRow>\n                )}\n              </TableBody>\n            </Table>\n          </DndContext>\n        </div>\n        <div className=\"flex items-center justify-between px-4\">\n          <div className=\"hidden flex-1 text-sm text-muted-foreground lg:flex\">\n            {table.getFilteredSelectedRowModel().rows.length} of{\" \"}\n            {table.getFilteredRowModel().rows.length} row(s) selected.\n          </div>\n          <div className=\"flex w-full items-center gap-8 lg:w-fit\">\n            <div className=\"hidden items-center gap-2 lg:flex\">\n              <Label htmlFor=\"rows-per-page\" className=\"text-sm font-medium\">\n                Rows per page\n              </Label>\n              <Select\n                value={`${table.getState().pagination.pageSize}`}\n                onValueChange={(value) => {\n                  table.setPageSize(Number(value));\n                }}\n              >\n                <SelectTrigger className=\"w-20\" id=\"rows-per-page\">\n                  <SelectValue\n                    placeholder={table.getState().pagination.pageSize}\n                  />\n                </SelectTrigger>\n                <SelectContent side=\"top\">\n                  {[10, 20, 30, 40, 50].map((pageSize) => (\n                    <SelectItem key={pageSize} value={`${pageSize}`}>\n                      {pageSize}\n                    </SelectItem>\n                  ))}\n                </SelectContent>\n              </Select>\n            </div>\n            <div className=\"flex w-fit items-center justify-center text-sm font-medium\">\n              Page {table.getState().pagination.pageIndex + 1} of{\" \"}\n              {table.getPageCount()}\n            </div>\n            <div className=\"ml-auto flex items-center gap-2 lg:ml-0\">\n              <Button\n                variant=\"outline\"\n                className=\"hidden h-8 w-8 p-0 lg:flex\"\n                onClick={() => table.setPageIndex(0)}\n                disabled={!table.getCanPreviousPage()}\n              >\n                <span className=\"sr-only\">Go to first page</span>\n                <ChevronsLeftIcon />\n              </Button>\n              <Button\n                variant=\"outline\"\n                className=\"size-8\"\n                size=\"icon\"\n                onClick={() => table.previousPage()}\n                disabled={!table.getCanPreviousPage()}\n              >\n                <span className=\"sr-only\">Go to previous page</span>\n                <ChevronLeftIcon />\n              </Button>\n              <Button\n                variant=\"outline\"\n                className=\"size-8\"\n                size=\"icon\"\n                onClick={() => table.nextPage()}\n                disabled={!table.getCanNextPage()}\n              >\n                <span className=\"sr-only\">Go to next page</span>\n                <ChevronRightIcon />\n              </Button>\n              <Button\n                variant=\"outline\"\n                className=\"hidden size-8 lg:flex\"\n                size=\"icon\"\n                onClick={() => table.setPageIndex(table.getPageCount() - 1)}\n                disabled={!table.getCanNextPage()}\n              >\n                <span className=\"sr-only\">Go to last page</span>\n                <ChevronsRightIcon />\n              </Button>\n            </div>\n          </div>\n        </div>\n      </TabsContent>\n      <TabsContent\n        value=\"past-performance\"\n        className=\"flex flex-col px-4 lg:px-6\"\n      >\n        <div className=\"aspect-video w-full flex-1 rounded-lg border border-dashed\"></div>\n      </TabsContent>\n      <TabsContent value=\"key-personnel\" className=\"flex flex-col px-4 lg:px-6\">\n        <div className=\"aspect-video w-full flex-1 rounded-lg border border-dashed\"></div>\n      </TabsContent>\n      <TabsContent\n        value=\"focus-documents\"\n        className=\"flex flex-col px-4 lg:px-6\"\n      >\n        <div className=\"aspect-video w-full flex-1 rounded-lg border border-dashed\"></div>\n      </TabsContent>\n    </Tabs>\n  );\n}\n\nconst chartData = [\n  { month: \"January\", desktop: 186, mobile: 80 },\n  { month: \"February\", desktop: 305, mobile: 200 },\n  { month: \"March\", desktop: 237, mobile: 120 },\n  { month: \"April\", desktop: 73, mobile: 190 },\n  { month: \"May\", desktop: 209, mobile: 130 },\n  { month: \"June\", desktop: 214, mobile: 140 },\n];\n\nconst chartConfig = {\n  desktop: {\n    label: \"Desktop\",\n    color: \"var(--primary)\",\n  },\n  mobile: {\n    label: \"Mobile\",\n    color: \"var(--primary)\",\n  },\n} satisfies ChartConfig;\n\nfunction TableCellViewer({ item }: { item: z.infer<typeof schema> }) {\n  const isMobile = useIsMobile();\n\n  return (\n    <Sheet>\n      <SheetTrigger asChild>\n        <Button variant=\"link\" className=\"w-fit px-0 text-left text-foreground\">\n          {item.header}\n        </Button>\n      </SheetTrigger>\n      <SheetContent side=\"right\" className=\"flex flex-col\">\n        <SheetHeader className=\"gap-1\">\n          <SheetTitle>{item.header}</SheetTitle>\n          <SheetDescription>\n            Showing total visitors for the last 6 months\n          </SheetDescription>\n        </SheetHeader>\n        <div className=\"flex flex-1 flex-col gap-4 overflow-y-auto py-4 text-sm\">\n          {!isMobile && (\n            <>\n              <ChartContainer config={chartConfig}>\n                <AreaChart\n                  accessibilityLayer\n                  data={chartData}\n                  margin={{\n                    left: 0,\n                    right: 10,\n                  }}\n                >\n                  <CartesianGrid vertical={false} />\n                  <XAxis\n                    dataKey=\"month\"\n                    tickLine={false}\n                    axisLine={false}\n                    tickMargin={8}\n                    tickFormatter={(value) => value.slice(0, 3)}\n                    hide\n                  />\n                  <ChartTooltip\n                    cursor={false}\n                    content={<ChartTooltipContent indicator=\"dot\" />}\n                  />\n                  <Area\n                    dataKey=\"mobile\"\n                    type=\"natural\"\n                    fill=\"var(--color-mobile)\"\n                    fillOpacity={0.6}\n                    stroke=\"var(--color-mobile)\"\n                    stackId=\"a\"\n                  />\n                  <Area\n                    dataKey=\"desktop\"\n                    type=\"natural\"\n                    fill=\"var(--color-desktop)\"\n                    fillOpacity={0.4}\n                    stroke=\"var(--color-desktop)\"\n                    stackId=\"a\"\n                  />\n                </AreaChart>\n              </ChartContainer>\n              <Separator />\n              <div className=\"grid gap-2\">\n                <div className=\"flex gap-2 font-medium leading-none\">\n                  Trending up by 5.2% this month{\" \"}\n                  <TrendingUpIcon className=\"size-4\" />\n                </div>\n                <div className=\"text-muted-foreground\">\n                  Showing total visitors for the last 6 months. This is just\n                  some random text to test the layout. It spans multiple lines\n                  and should wrap around.\n                </div>\n              </div>\n              <Separator />\n            </>\n          )}\n          <form className=\"flex flex-col gap-4\">\n            <div className=\"flex flex-col gap-3\">\n              <Label htmlFor=\"header\">Header</Label>\n              <Input id=\"header\" defaultValue={item.header} />\n            </div>\n            <div className=\"grid grid-cols-2 gap-4\">\n              <div className=\"flex flex-col gap-3\">\n                <Label htmlFor=\"type\">Type</Label>\n                <Select defaultValue={item.type}>\n                  <SelectTrigger id=\"type\" className=\"w-full\">\n                    <SelectValue placeholder=\"Select a type\" />\n                  </SelectTrigger>\n                  <SelectContent>\n                    <SelectItem value=\"Table of Contents\">\n                      Table of Contents\n                    </SelectItem>\n                    <SelectItem value=\"Executive Summary\">\n                      Executive Summary\n                    </SelectItem>\n                    <SelectItem value=\"Technical Approach\">\n                      Technical Approach\n                    </SelectItem>\n                    <SelectItem value=\"Design\">Design</SelectItem>\n                    <SelectItem value=\"Capabilities\">Capabilities</SelectItem>\n                    <SelectItem value=\"Focus Documents\">\n                      Focus Documents\n                    </SelectItem>\n                    <SelectItem value=\"Narrative\">Narrative</SelectItem>\n                    <SelectItem value=\"Cover Page\">Cover Page</SelectItem>\n                  </SelectContent>\n                </Select>\n              </div>\n            </div>\n            <div className=\"grid grid-cols-2 gap-4\">\n              <div className=\"flex flex-col gap-3\">\n                <Label htmlFor=\"target\">Target</Label>\n                <Input id=\"target\" defaultValue={item.target} />\n              </div>\n              <div className=\"flex flex-col gap-3\">\n                <Label htmlFor=\"limit\">Limit</Label>\n                <Input id=\"limit\" defaultValue={item.limit} />\n              </div>\n            </div>\n            <div className=\"flex flex-col gap-3\">\n              <Label htmlFor=\"reviewer\">Reviewer</Label>\n              <Select defaultValue={item.reviewer}>\n                <SelectTrigger id=\"reviewer\" className=\"w-full\">\n                  <SelectValue placeholder=\"Select a reviewer\" />\n                </SelectTrigger>\n                <SelectContent>\n                  <SelectItem value=\"Eddie Lake\">Eddie Lake</SelectItem>\n                  <SelectItem value=\"Jamik Tashpulatov\">\n                    Jamik Tashpulatov\n                  </SelectItem>\n                  <SelectItem value=\"Emily Whalen\">Emily Whalen</SelectItem>\n                </SelectContent>\n              </Select>\n            </div>\n          </form>\n        </div>\n        <SheetFooter className=\"mt-auto flex gap-2 sm:flex-col sm:space-x-0\">\n          <Button className=\"w-full\">Submit</Button>\n          <SheetClose asChild>\n            <Button variant=\"outline\" className=\"w-full\">\n              Done\n            </Button>\n          </SheetClose>\n        </SheetFooter>\n      </SheetContent>\n    </Sheet>\n  );\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/nav-documents.tsx",
    "content": "import {\n  FolderIcon,\n  MoreHorizontalIcon,\n  ShareIcon,\n  type LucideIcon,\n} from \"lucide-react\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n  SidebarGroup,\n  SidebarGroupLabel,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"@/components/ui/sidebar\";\n\nexport function NavDocuments({\n  items,\n}: {\n  items: {\n    name: string;\n    url: string;\n    icon: LucideIcon;\n  }[];\n}) {\n  const { isMobile } = useSidebar();\n\n  return (\n    <SidebarGroup className=\"group-data-[collapsible=icon]:hidden\">\n      <SidebarGroupLabel>Documents</SidebarGroupLabel>\n      <SidebarMenu>\n        {items.map((item) => (\n          <SidebarMenuItem key={item.name}>\n            <SidebarMenuButton asChild>\n              <a href={item.url}>\n                <item.icon />\n                <span>{item.name}</span>\n              </a>\n            </SidebarMenuButton>\n            <DropdownMenu>\n              <DropdownMenuTrigger asChild>\n                <SidebarMenuAction\n                  showOnHover\n                  className=\"rounded-sm data-[state=open]:bg-accent\"\n                >\n                  <MoreHorizontalIcon />\n                  <span className=\"sr-only\">More</span>\n                </SidebarMenuAction>\n              </DropdownMenuTrigger>\n              <DropdownMenuContent\n                className=\"w-24 rounded-lg\"\n                side={isMobile ? \"bottom\" : \"right\"}\n                align={isMobile ? \"end\" : \"start\"}\n              >\n                <DropdownMenuItem>\n                  <FolderIcon />\n                  <span>Open</span>\n                </DropdownMenuItem>\n                <DropdownMenuItem>\n                  <ShareIcon />\n                  <span>Share</span>\n                </DropdownMenuItem>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </SidebarMenuItem>\n        ))}\n        <SidebarMenuItem>\n          <SidebarMenuButton className=\"text-sidebar-foreground/70\">\n            <MoreHorizontalIcon className=\"text-sidebar-foreground/70\" />\n            <span>More</span>\n          </SidebarMenuButton>\n        </SidebarMenuItem>\n      </SidebarMenu>\n    </SidebarGroup>\n  );\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/nav-main.tsx",
    "content": "\"use client\";\n\nimport { PlusCircleIcon, type LucideIcon } from \"lucide-react\";\n\nimport {\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"@/components/ui/sidebar\";\n\nexport function NavMain({\n  items,\n}: {\n  items: {\n    title: string;\n    url: string;\n    icon?: LucideIcon;\n  }[];\n}) {\n  return (\n    <SidebarGroup>\n      <SidebarGroupContent className=\"flex flex-col gap-2\">\n        <SidebarMenu>\n          <SidebarMenuItem className=\"flex items-center gap-2\">\n            <SidebarMenuButton\n              tooltip=\"Quick Create\"\n              className=\"min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground\"\n            >\n              <PlusCircleIcon />\n              <span>Quick Create</span>\n            </SidebarMenuButton>\n          </SidebarMenuItem>\n        </SidebarMenu>\n        <SidebarMenu>\n          {items.map((item) => (\n            <SidebarMenuItem key={item.title}>\n              <SidebarMenuButton tooltip={item.title}>\n                {item.icon && <item.icon />}\n                <span>{item.title}</span>\n              </SidebarMenuButton>\n            </SidebarMenuItem>\n          ))}\n        </SidebarMenu>\n      </SidebarGroupContent>\n    </SidebarGroup>\n  );\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/nav-secondary.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { LucideIcon } from \"lucide-react\";\n\nimport {\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"@/components/ui/sidebar\";\n\nexport function NavSecondary({\n  items,\n  ...props\n}: {\n  items: {\n    title: string;\n    url: string;\n    icon: LucideIcon;\n  }[];\n} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {\n  return (\n    <SidebarGroup {...props}>\n      <SidebarGroupContent>\n        <SidebarMenu>\n          {items.map((item) => (\n            <SidebarMenuItem key={item.title}>\n              <SidebarMenuButton asChild>\n                <a href={item.url}>\n                  <item.icon />\n                  <span>{item.title}</span>\n                </a>\n              </SidebarMenuButton>\n            </SidebarMenuItem>\n          ))}\n        </SidebarMenu>\n      </SidebarGroupContent>\n    </SidebarGroup>\n  );\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/nav-user.tsx",
    "content": "\"use client\";\n\nimport {\n  BellIcon,\n  CreditCardIcon,\n  LogOutIcon,\n  MoreVerticalIcon,\n  UserCircleIcon,\n} from \"lucide-react\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"@/components/ui/sidebar\";\n\nexport function NavUser({\n  user,\n}: {\n  user: {\n    name: string;\n    email: string;\n    avatar: string;\n  };\n}) {\n  const { isMobile } = useSidebar();\n\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <DropdownMenu>\n          <DropdownMenuTrigger asChild>\n            <SidebarMenuButton\n              size=\"lg\"\n              className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n            >\n              <Avatar className=\"h-8 w-8 rounded-lg grayscale\">\n                <AvatarImage src={user.avatar} alt={user.name} />\n                <AvatarFallback className=\"rounded-lg\">CN</AvatarFallback>\n              </Avatar>\n              <div className=\"grid flex-1 text-left text-sm leading-tight\">\n                <span className=\"truncate font-medium\">{user.name}</span>\n                <span className=\"truncate text-xs text-muted-foreground\">\n                  {user.email}\n                </span>\n              </div>\n              <MoreVerticalIcon className=\"ml-auto size-4\" />\n            </SidebarMenuButton>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            className=\"w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg\"\n            side={isMobile ? \"bottom\" : \"right\"}\n            align=\"end\"\n            sideOffset={4}\n          >\n            <DropdownMenuLabel className=\"p-0 font-normal\">\n              <div className=\"flex items-center gap-2 px-1 py-1.5 text-left text-sm\">\n                <Avatar className=\"h-8 w-8 rounded-lg\">\n                  <AvatarImage src={user.avatar} alt={user.name} />\n                  <AvatarFallback className=\"rounded-lg\">CN</AvatarFallback>\n                </Avatar>\n                <div className=\"grid flex-1 text-left text-sm leading-tight\">\n                  <span className=\"truncate font-medium\">{user.name}</span>\n                  <span className=\"truncate text-xs text-muted-foreground\">\n                    {user.email}\n                  </span>\n                </div>\n              </div>\n            </DropdownMenuLabel>\n            <DropdownMenuSeparator />\n            <DropdownMenuGroup>\n              <DropdownMenuItem>\n                <UserCircleIcon />\n                Account\n              </DropdownMenuItem>\n              <DropdownMenuItem>\n                <CreditCardIcon />\n                Billing\n              </DropdownMenuItem>\n              <DropdownMenuItem>\n                <BellIcon />\n                Notifications\n              </DropdownMenuItem>\n            </DropdownMenuGroup>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem className=\"text-destructive hover:text-destructive focus:text-destructive\">\n              <LogOutIcon />\n              Log out\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </SidebarMenuItem>\n    </SidebarMenu>\n  );\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/section-cards.tsx",
    "content": "import { TrendingDownIcon, TrendingUpIcon } from \"lucide-react\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n  Card,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\n\nexport function SectionCards() {\n  return (\n    <div className=\"*:data-[slot=card]:shadow-xs @xl/main:grid-cols-2 @5xl/main:grid-cols-4 grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card lg:px-6\">\n      <Card className=\"@container/card\">\n        <CardHeader className=\"relative\">\n          <CardDescription>Total Revenue</CardDescription>\n          <CardTitle className=\"@[250px]/card:text-3xl text-2xl font-semibold tabular-nums\">\n            $1,250.00\n          </CardTitle>\n          <div className=\"absolute right-4 top-4\">\n            <Badge variant=\"outline\" className=\"flex gap-1 rounded-lg text-xs\">\n              <TrendingUpIcon className=\"size-3\" />\n              +12.5%\n            </Badge>\n          </div>\n        </CardHeader>\n        <CardFooter className=\"flex-col items-start gap-1 text-sm\">\n          <div className=\"line-clamp-1 flex gap-2 font-medium\">\n            Trending up this month <TrendingUpIcon className=\"size-4\" />\n          </div>\n          <div className=\"text-muted-foreground\">Visitors for the last 6 months</div>\n        </CardFooter>\n      </Card>\n      <Card className=\"@container/card\">\n        <CardHeader className=\"relative\">\n          <CardDescription>New Customers</CardDescription>\n          <CardTitle className=\"@[250px]/card:text-3xl text-2xl font-semibold tabular-nums\">\n            1,234\n          </CardTitle>\n          <div className=\"absolute right-4 top-4\">\n            <Badge variant=\"outline\" className=\"flex gap-1 rounded-lg text-xs\">\n              <TrendingDownIcon className=\"size-3\" />\n              -20%\n            </Badge>\n          </div>\n        </CardHeader>\n        <CardFooter className=\"flex-col items-start gap-1 text-sm\">\n          <div className=\"line-clamp-1 flex gap-2 font-medium\">\n            Down 20% this period <TrendingDownIcon className=\"size-4\" />\n          </div>\n          <div className=\"text-muted-foreground\">Acquisition needs attention</div>\n        </CardFooter>\n      </Card>\n      <Card className=\"@container/card\">\n        <CardHeader className=\"relative\">\n          <CardDescription>Active Accounts</CardDescription>\n          <CardTitle className=\"@[250px]/card:text-3xl text-2xl font-semibold tabular-nums\">\n            45,678\n          </CardTitle>\n          <div className=\"absolute right-4 top-4\">\n            <Badge variant=\"outline\" className=\"flex gap-1 rounded-lg text-xs\">\n              <TrendingUpIcon className=\"size-3\" />\n              +12.5%\n            </Badge>\n          </div>\n        </CardHeader>\n        <CardFooter className=\"flex-col items-start gap-1 text-sm\">\n          <div className=\"line-clamp-1 flex gap-2 font-medium\">\n            Strong user retention <TrendingUpIcon className=\"size-4\" />\n          </div>\n          <div className=\"text-muted-foreground\">Engagement exceed targets</div>\n        </CardFooter>\n      </Card>\n      <Card className=\"@container/card\">\n        <CardHeader className=\"relative\">\n          <CardDescription>Growth Rate</CardDescription>\n          <CardTitle className=\"@[250px]/card:text-3xl text-2xl font-semibold tabular-nums\">\n            4.5%\n          </CardTitle>\n          <div className=\"absolute right-4 top-4\">\n            <Badge variant=\"outline\" className=\"flex gap-1 rounded-lg text-xs\">\n              <TrendingUpIcon className=\"size-3\" />\n              +4.5%\n            </Badge>\n          </div>\n        </CardHeader>\n        <CardFooter className=\"flex-col items-start gap-1 text-sm\">\n          <div className=\"line-clamp-1 flex gap-2 font-medium\">\n            Steady performance <TrendingUpIcon className=\"size-4\" />\n          </div>\n          <div className=\"text-muted-foreground\">Meets growth projections</div>\n        </CardFooter>\n      </Card>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/dashboard/components/site-header.tsx",
    "content": "import { Separator } from \"@/components/ui/separator\";\nimport { SidebarTrigger } from \"@/components/ui/sidebar\";\n\nexport function SiteHeader() {\n  return (\n    <header className=\"group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 flex h-12 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear\">\n      <div className=\"flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6\">\n        <SidebarTrigger className=\"-ml-1\" />\n        <Separator\n          orientation=\"vertical\"\n          className=\"mx-2 data-[orientation=vertical]:h-4\"\n        />\n        <h1 className=\"text-base font-medium\">Documents</h1>\n      </div>\n    </header>\n  );\n}\n"
  },
  {
    "path": "components/examples/dashboard/data.json",
    "content": "[\n  {\n    \"id\": 1,\n    \"header\": \"Cover page\",\n    \"type\": \"Cover page\",\n    \"status\": \"In Process\",\n    \"target\": \"18\",\n    \"limit\": \"5\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 2,\n    \"header\": \"Table of contents\",\n    \"type\": \"Table of contents\",\n    \"status\": \"Done\",\n    \"target\": \"29\",\n    \"limit\": \"24\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 3,\n    \"header\": \"Executive summary\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"10\",\n    \"limit\": \"13\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 4,\n    \"header\": \"Technical approach\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"27\",\n    \"limit\": \"23\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 5,\n    \"header\": \"Design\",\n    \"type\": \"Narrative\",\n    \"status\": \"In Process\",\n    \"target\": \"2\",\n    \"limit\": \"16\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 6,\n    \"header\": \"Capabilities\",\n    \"type\": \"Narrative\",\n    \"status\": \"In Process\",\n    \"target\": \"20\",\n    \"limit\": \"8\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 7,\n    \"header\": \"Integration with existing systems\",\n    \"type\": \"Narrative\",\n    \"status\": \"In Process\",\n    \"target\": \"19\",\n    \"limit\": \"21\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 8,\n    \"header\": \"Innovation and Advantages\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"25\",\n    \"limit\": \"26\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 9,\n    \"header\": \"Overview of EMR's Innovative Solutions\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"7\",\n    \"limit\": \"23\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 10,\n    \"header\": \"Advanced Algorithms and Machine Learning\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"30\",\n    \"limit\": \"28\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 11,\n    \"header\": \"Adaptive Communication Protocols\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"9\",\n    \"limit\": \"31\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 12,\n    \"header\": \"Advantages Over Current Technologies\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"12\",\n    \"limit\": \"0\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 13,\n    \"header\": \"Past Performance\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"22\",\n    \"limit\": \"33\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 14,\n    \"header\": \"Customer Feedback and Satisfaction Levels\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"15\",\n    \"limit\": \"34\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 15,\n    \"header\": \"Implementation Challenges and Solutions\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"3\",\n    \"limit\": \"35\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 16,\n    \"header\": \"Security Measures and Data Protection Policies\",\n    \"type\": \"Narrative\",\n    \"status\": \"In Process\",\n    \"target\": \"6\",\n    \"limit\": \"36\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 17,\n    \"header\": \"Scalability and Future Proofing\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"4\",\n    \"limit\": \"37\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 18,\n    \"header\": \"Cost-Benefit Analysis\",\n    \"type\": \"Plain language\",\n    \"status\": \"Done\",\n    \"target\": \"14\",\n    \"limit\": \"38\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 19,\n    \"header\": \"User Training and Onboarding Experience\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"17\",\n    \"limit\": \"39\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 20,\n    \"header\": \"Future Development Roadmap\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"11\",\n    \"limit\": \"40\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 21,\n    \"header\": \"System Architecture Overview\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"24\",\n    \"limit\": \"18\",\n    \"reviewer\": \"Maya Johnson\"\n  },\n  {\n    \"id\": 22,\n    \"header\": \"Risk Management Plan\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"15\",\n    \"limit\": \"22\",\n    \"reviewer\": \"Carlos Rodriguez\"\n  },\n  {\n    \"id\": 23,\n    \"header\": \"Compliance Documentation\",\n    \"type\": \"Legal\",\n    \"status\": \"In Process\",\n    \"target\": \"31\",\n    \"limit\": \"27\",\n    \"reviewer\": \"Sarah Chen\"\n  },\n  {\n    \"id\": 24,\n    \"header\": \"API Documentation\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"8\",\n    \"limit\": \"12\",\n    \"reviewer\": \"Raj Patel\"\n  },\n  {\n    \"id\": 25,\n    \"header\": \"User Interface Mockups\",\n    \"type\": \"Visual\",\n    \"status\": \"In Process\",\n    \"target\": \"19\",\n    \"limit\": \"25\",\n    \"reviewer\": \"Leila Ahmadi\"\n  },\n  {\n    \"id\": 26,\n    \"header\": \"Database Schema\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"22\",\n    \"limit\": \"20\",\n    \"reviewer\": \"Thomas Wilson\"\n  },\n  {\n    \"id\": 27,\n    \"header\": \"Testing Methodology\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"17\",\n    \"limit\": \"14\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 28,\n    \"header\": \"Deployment Strategy\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"26\",\n    \"limit\": \"30\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 29,\n    \"header\": \"Budget Breakdown\",\n    \"type\": \"Financial\",\n    \"status\": \"In Process\",\n    \"target\": \"13\",\n    \"limit\": \"16\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 30,\n    \"header\": \"Market Analysis\",\n    \"type\": \"Research\",\n    \"status\": \"Done\",\n    \"target\": \"29\",\n    \"limit\": \"32\",\n    \"reviewer\": \"Sophia Martinez\"\n  },\n  {\n    \"id\": 31,\n    \"header\": \"Competitor Comparison\",\n    \"type\": \"Research\",\n    \"status\": \"In Process\",\n    \"target\": \"21\",\n    \"limit\": \"19\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 32,\n    \"header\": \"Maintenance Plan\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"16\",\n    \"limit\": \"23\",\n    \"reviewer\": \"Alex Thompson\"\n  },\n  {\n    \"id\": 33,\n    \"header\": \"User Personas\",\n    \"type\": \"Research\",\n    \"status\": \"In Process\",\n    \"target\": \"27\",\n    \"limit\": \"24\",\n    \"reviewer\": \"Nina Patel\"\n  },\n  {\n    \"id\": 34,\n    \"header\": \"Accessibility Compliance\",\n    \"type\": \"Legal\",\n    \"status\": \"Done\",\n    \"target\": \"18\",\n    \"limit\": \"21\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 35,\n    \"header\": \"Performance Metrics\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"23\",\n    \"limit\": \"26\",\n    \"reviewer\": \"David Kim\"\n  },\n  {\n    \"id\": 36,\n    \"header\": \"Disaster Recovery Plan\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"14\",\n    \"limit\": \"17\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 37,\n    \"header\": \"Third-party Integrations\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"25\",\n    \"limit\": \"28\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 38,\n    \"header\": \"User Feedback Summary\",\n    \"type\": \"Research\",\n    \"status\": \"Done\",\n    \"target\": \"20\",\n    \"limit\": \"15\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 39,\n    \"header\": \"Localization Strategy\",\n    \"type\": \"Narrative\",\n    \"status\": \"In Process\",\n    \"target\": \"12\",\n    \"limit\": \"19\",\n    \"reviewer\": \"Maria Garcia\"\n  },\n  {\n    \"id\": 40,\n    \"header\": \"Mobile Compatibility\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"28\",\n    \"limit\": \"31\",\n    \"reviewer\": \"James Wilson\"\n  },\n  {\n    \"id\": 41,\n    \"header\": \"Data Migration Plan\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"19\",\n    \"limit\": \"22\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 42,\n    \"header\": \"Quality Assurance Protocols\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"30\",\n    \"limit\": \"33\",\n    \"reviewer\": \"Priya Singh\"\n  },\n  {\n    \"id\": 43,\n    \"header\": \"Stakeholder Analysis\",\n    \"type\": \"Research\",\n    \"status\": \"In Process\",\n    \"target\": \"11\",\n    \"limit\": \"14\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 44,\n    \"header\": \"Environmental Impact Assessment\",\n    \"type\": \"Research\",\n    \"status\": \"Done\",\n    \"target\": \"24\",\n    \"limit\": \"27\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 45,\n    \"header\": \"Intellectual Property Rights\",\n    \"type\": \"Legal\",\n    \"status\": \"In Process\",\n    \"target\": \"17\",\n    \"limit\": \"20\",\n    \"reviewer\": \"Sarah Johnson\"\n  },\n  {\n    \"id\": 46,\n    \"header\": \"Customer Support Framework\",\n    \"type\": \"Narrative\",\n    \"status\": \"Done\",\n    \"target\": \"22\",\n    \"limit\": \"25\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 47,\n    \"header\": \"Version Control Strategy\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"15\",\n    \"limit\": \"18\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 48,\n    \"header\": \"Continuous Integration Pipeline\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"26\",\n    \"limit\": \"29\",\n    \"reviewer\": \"Michael Chen\"\n  },\n  {\n    \"id\": 49,\n    \"header\": \"Regulatory Compliance\",\n    \"type\": \"Legal\",\n    \"status\": \"In Process\",\n    \"target\": \"13\",\n    \"limit\": \"16\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 50,\n    \"header\": \"User Authentication System\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"28\",\n    \"limit\": \"31\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 51,\n    \"header\": \"Data Analytics Framework\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"21\",\n    \"limit\": \"24\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 52,\n    \"header\": \"Cloud Infrastructure\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"16\",\n    \"limit\": \"19\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 53,\n    \"header\": \"Network Security Measures\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"29\",\n    \"limit\": \"32\",\n    \"reviewer\": \"Lisa Wong\"\n  },\n  {\n    \"id\": 54,\n    \"header\": \"Project Timeline\",\n    \"type\": \"Planning\",\n    \"status\": \"Done\",\n    \"target\": \"14\",\n    \"limit\": \"17\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 55,\n    \"header\": \"Resource Allocation\",\n    \"type\": \"Planning\",\n    \"status\": \"In Process\",\n    \"target\": \"27\",\n    \"limit\": \"30\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 56,\n    \"header\": \"Team Structure and Roles\",\n    \"type\": \"Planning\",\n    \"status\": \"Done\",\n    \"target\": \"20\",\n    \"limit\": \"23\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 57,\n    \"header\": \"Communication Protocols\",\n    \"type\": \"Planning\",\n    \"status\": \"In Process\",\n    \"target\": \"15\",\n    \"limit\": \"18\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 58,\n    \"header\": \"Success Metrics\",\n    \"type\": \"Planning\",\n    \"status\": \"Done\",\n    \"target\": \"30\",\n    \"limit\": \"33\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 59,\n    \"header\": \"Internationalization Support\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"23\",\n    \"limit\": \"26\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 60,\n    \"header\": \"Backup and Recovery Procedures\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"18\",\n    \"limit\": \"21\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 61,\n    \"header\": \"Monitoring and Alerting System\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"25\",\n    \"limit\": \"28\",\n    \"reviewer\": \"Daniel Park\"\n  },\n  {\n    \"id\": 62,\n    \"header\": \"Code Review Guidelines\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"12\",\n    \"limit\": \"15\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 63,\n    \"header\": \"Documentation Standards\",\n    \"type\": \"Technical content\",\n    \"status\": \"In Process\",\n    \"target\": \"27\",\n    \"limit\": \"30\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 64,\n    \"header\": \"Release Management Process\",\n    \"type\": \"Planning\",\n    \"status\": \"Done\",\n    \"target\": \"22\",\n    \"limit\": \"25\",\n    \"reviewer\": \"Assign reviewer\"\n  },\n  {\n    \"id\": 65,\n    \"header\": \"Feature Prioritization Matrix\",\n    \"type\": \"Planning\",\n    \"status\": \"In Process\",\n    \"target\": \"19\",\n    \"limit\": \"22\",\n    \"reviewer\": \"Emma Davis\"\n  },\n  {\n    \"id\": 66,\n    \"header\": \"Technical Debt Assessment\",\n    \"type\": \"Technical content\",\n    \"status\": \"Done\",\n    \"target\": \"24\",\n    \"limit\": \"27\",\n    \"reviewer\": \"Eddie Lake\"\n  },\n  {\n    \"id\": 67,\n    \"header\": \"Capacity Planning\",\n    \"type\": \"Planning\",\n    \"status\": \"In Process\",\n    \"target\": \"21\",\n    \"limit\": \"24\",\n    \"reviewer\": \"Jamik Tashpulatov\"\n  },\n  {\n    \"id\": 68,\n    \"header\": \"Service Level Agreements\",\n    \"type\": \"Legal\",\n    \"status\": \"Done\",\n    \"target\": \"26\",\n    \"limit\": \"29\",\n    \"reviewer\": \"Assign reviewer\"\n  }\n]\n"
  },
  {
    "path": "components/examples/dashboard/index.tsx",
    "content": "\"use client\";\n\nimport { AppSidebar } from \"@/components/examples/dashboard/components/app-sidebar\";\nimport { ChartAreaInteractive } from \"@/components/examples/dashboard/components/chart-area-interactive\";\nimport { DataTable } from \"@/components/examples/dashboard/components/data-table\";\nimport { SectionCards } from \"@/components/examples/dashboard/components/section-cards\";\nimport { SiteHeader } from \"@/components/examples/dashboard/components/site-header\";\nimport { SidebarInset, SidebarProvider } from \"@/components/ui/sidebar\";\nimport { ChartBarMixed } from \"./components/chart-bar-mixed\";\nimport { ChartPieDonut } from \"./components/chart-pie-donut\";\n\nimport data from \"./data.json\";\n\nexport default function Dashboard() {\n  return (\n    <SidebarProvider>\n      <AppSidebar variant=\"inset\" />\n      <SidebarInset>\n        <SiteHeader />\n        <div className=\"flex flex-1 flex-col\">\n          <div className=\"@container/main flex flex-1 flex-col gap-2\">\n            <div className=\"flex flex-col gap-4 py-4 md:gap-6 md:py-6\">\n              <SectionCards />\n              <div className=\"px-4 lg:px-6\">\n                <ChartAreaInteractive />\n              </div>\n              <DataTable data={data} />\n              <div className=\"flex gap-4 px-4 lg:px-6\">\n                <div className=\"basis-1/2\">\n                  <ChartPieDonut />\n                </div>\n                <div className=\"basis-1/2\">\n                  <ChartBarMixed />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </SidebarInset>\n    </SidebarProvider>\n  );\n}\n"
  },
  {
    "path": "components/examples/mail/components/account-switcher.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\n\ninterface AccountSwitcherProps {\n  isCollapsed: boolean;\n  accounts: {\n    label: string;\n    email: string;\n    icon: React.ReactNode;\n  }[];\n}\n\nexport function AccountSwitcher({ isCollapsed, accounts }: AccountSwitcherProps) {\n  const [selectedAccount, setSelectedAccount] = React.useState<string>(\n    accounts[0].email\n  );\n\n  return (\n    <Select defaultValue={selectedAccount} onValueChange={setSelectedAccount}>\n      <SelectTrigger\n        className={cn(\n          \"flex w-full items-center gap-2 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0\",\n          isCollapsed &&\n            \"flex h-9 w-9 shrink-0 items-center justify-center p-0 [&>span]:w-auto [&>svg]:hidden\"\n        )}\n        aria-label=\"Select account\"\n      >\n        <SelectValue placeholder=\"Select an account\">\n          {accounts.find((account) => account.email === selectedAccount)?.icon}\n          <span className={cn(\"ml-2\", isCollapsed && \"hidden\")}>\n            {accounts.find((account) => account.email === selectedAccount)?.label}\n          </span>\n        </SelectValue>\n      </SelectTrigger>\n      <SelectContent>\n        {accounts.map((account) => (\n          <SelectItem key={account.email} value={account.email}>\n            <div className=\"flex items-center gap-3 [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0 [&_svg]:text-foreground\">\n              {account.icon}\n              {account.email}\n            </div>\n          </SelectItem>\n        ))}\n      </SelectContent>\n    </Select>\n  );\n}\n"
  },
  {
    "path": "components/examples/mail/components/mail-display.tsx",
    "content": "import { addDays } from \"date-fns\";\nimport { addHours } from \"date-fns\";\nimport { format } from \"date-fns\";\nimport { nextSaturday } from \"date-fns\";\nimport {\n  Archive,\n  ArchiveX,\n  Clock,\n  Forward,\n  MoreVertical,\n  Reply,\n  ReplyAll,\n  Trash2,\n} from \"lucide-react\";\n\nimport { DropdownMenuContent, DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Calendar } from \"@/components/ui/calendar\";\nimport { DropdownMenu, DropdownMenuTrigger } from \"@/components/ui/dropdown-menu\";\nimport { Label } from \"@/components/ui/label\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Mail } from \"@/components/examples/mail/data\";\nimport { useState } from \"react\";\n\ninterface MailDisplayProps {\n  mail: Mail | null;\n}\n\nexport function MailDisplay({ mail }: MailDisplayProps) {\n  const [selectedDate, setSelectedDate] = useState<Date>(new Date());\n\n  return (\n    <div className=\"flex h-full flex-col\">\n      <div className=\"flex items-center p-2\">\n        <div className=\"flex items-center gap-2\">\n          <Button variant=\"ghost\" size=\"icon\" disabled={!mail} title=\"Archive\">\n            <Archive className=\"h-4 w-4\" />\n            <span className=\"sr-only\">Archive</span>\n          </Button>\n          <Button variant=\"ghost\" size=\"icon\" disabled={!mail} title=\"Move to junk\">\n            <ArchiveX className=\"h-4 w-4\" />\n            <span className=\"sr-only\">Move to junk</span>\n          </Button>\n          <Button \n            variant=\"ghost\" \n            size=\"icon\" \n            disabled={!mail} \n            title=\"Move to trash\"\n            className=\"text-destructive hover:text-destructive focus-visible:text-destructive\"\n          >\n            <Trash2 className=\"h-4 w-4\" />\n            <span className=\"sr-only\">Move to trash</span>\n          </Button>\n          <Separator orientation=\"vertical\" className=\"mx-1 h-6\" />\n          <Popover>\n            <PopoverTrigger asChild>\n              <Button variant=\"ghost\" size=\"icon\" disabled={!mail} title=\"Snooze\">\n                <Clock className=\"h-4 w-4\" />\n                <span className=\"sr-only\">Snooze</span>\n              </Button>\n            </PopoverTrigger>\n            <PopoverContent className=\"flex w-auto p-0\">\n              <div className=\"flex flex-col gap-2 border-r px-2 py-4\">\n                <div className=\"px-4 text-sm font-medium\">Snooze until</div>\n                <div className=\"grid min-w-[250px] gap-1\">\n                  <Button variant=\"ghost\" className=\"justify-start font-normal\">\n                    Later today{\" \"}\n                    <span className=\"text-muted-foreground ml-auto\">\n                      {format(addHours(selectedDate, 4), \"E, h:mm b\")}\n                    </span>\n                  </Button>\n                  <Button variant=\"ghost\" className=\"justify-start font-normal\">\n                    Tomorrow\n                    <span className=\"text-muted-foreground ml-auto\">\n                      {format(addDays(selectedDate, 1), \"E, h:mm b\")}\n                    </span>\n                  </Button>\n                  <Button variant=\"ghost\" className=\"justify-start font-normal\">\n                    This weekend\n                    <span className=\"text-muted-foreground ml-auto\">\n                      {format(nextSaturday(selectedDate), \"E, h:mm b\")}\n                    </span>\n                  </Button>\n                  <Button variant=\"ghost\" className=\"justify-start font-normal\">\n                    Next week\n                    <span className=\"text-muted-foreground ml-auto\">\n                      {format(addDays(selectedDate, 7), \"E, h:mm b\")}\n                    </span>\n                  </Button>\n                </div>\n              </div>\n              <div className=\"p-2\">\n                <Calendar\n                  mode=\"single\"\n                  selected={selectedDate}\n                  onSelect={setSelectedDate}\n                  classNames={{ today: \"bg-none\" }}\n                  required\n                />\n              </div>\n            </PopoverContent>\n          </Popover>\n        </div>\n        <div className=\"ml-auto flex items-center gap-2\">\n          <Button variant=\"ghost\" size=\"icon\" disabled={!mail} title=\"Reply\">\n            <Reply className=\"h-4 w-4\" />\n            <span className=\"sr-only\">Reply</span>\n          </Button>\n          <Button variant=\"ghost\" size=\"icon\" disabled={!mail} title=\"Reply all\">\n            <ReplyAll className=\"h-4 w-4\" />\n            <span className=\"sr-only\">Reply all</span>\n          </Button>\n          <Button variant=\"ghost\" size=\"icon\" disabled={!mail} title=\"Forward\">\n            <Forward className=\"h-4 w-4\" />\n            <span className=\"sr-only\">Forward</span>\n          </Button>\n        </div>\n        <Separator orientation=\"vertical\" className=\"mx-2 h-6\" />\n        <DropdownMenu>\n          <DropdownMenuTrigger asChild>\n            <Button variant=\"ghost\" size=\"icon\" disabled={!mail}>\n              <MoreVertical className=\"h-4 w-4\" />\n              <span className=\"sr-only\">More</span>\n            </Button>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent align=\"end\">\n            <DropdownMenuItem>Mark as unread</DropdownMenuItem>\n            <DropdownMenuItem>Star thread</DropdownMenuItem>\n            <DropdownMenuItem>Add label</DropdownMenuItem>\n            <DropdownMenuItem>Mute thread</DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </div>\n      <Separator />\n      {mail ? (\n        <div className=\"flex flex-1 flex-col\">\n          <div className=\"flex items-start p-4\">\n            <div className=\"flex items-start gap-4 text-sm\">\n              <Avatar>\n                <AvatarImage alt={mail.name} />\n                <AvatarFallback>\n                  {mail.name\n                    .split(\" \")\n                    .map((chunk) => chunk[0])\n                    .join(\"\")}\n                </AvatarFallback>\n              </Avatar>\n              <div className=\"grid gap-1\">\n                <div className=\"font-semibold\">{mail.name}</div>\n                <div className=\"line-clamp-1 text-xs\">{mail.subject}</div>\n                <div className=\"line-clamp-1 text-xs\">\n                  <span className=\"font-medium\">Reply-To:</span> {mail.email}\n                </div>\n              </div>\n            </div>\n            {mail.date && (\n              <div className=\"text-muted-foreground ml-auto text-xs\">\n                {format(new Date(mail.date), \"PPpp\")}\n              </div>\n            )}\n          </div>\n          <Separator />\n          <div className=\"flex-1 p-4 text-sm whitespace-pre-wrap\">{mail.text}</div>\n          <Separator className=\"mt-auto\" />\n          <div className=\"p-4\">\n            <form>\n              <div className=\"grid gap-4\">\n                <Textarea className=\"p-4\" placeholder={`Reply ${mail.name}...`} />\n                <div className=\"flex items-center\">\n                  <Label htmlFor=\"mute\" className=\"flex items-center gap-2 text-xs font-normal\">\n                    <Switch id=\"mute\" aria-label=\"Mute thread\" /> Mute this thread\n                  </Label>\n                  <Button onClick={(e) => e.preventDefault()} size=\"sm\" className=\"ml-auto\">\n                    Send\n                  </Button>\n                </div>\n              </div>\n            </form>\n          </div>\n        </div>\n      ) : (\n        <div className=\"text-muted-foreground p-8 text-center\">No message selected</div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/mail/components/mail-list.tsx",
    "content": "import { ComponentProps } from \"react\";\nimport { formatDistanceToNow } from \"date-fns\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Mail } from \"@/components/examples/mail/data\";\nimport { useMail } from \"@/components/examples/mail/use-mail\";\n\ninterface MailListProps {\n  items: Mail[];\n}\n\nexport function MailList({ items }: MailListProps) {\n  const [mail, setMail] = useMail();\n\n  return (\n    <ScrollArea className=\"h-full\">\n      <div className=\"flex flex-col gap-2 p-4 pt-0\">\n        {items.map((item) => (\n          <button\n            key={item.id}\n            className={cn(\n              \"hover:bg-accent hover:text-accent-foreground flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all\",\n              mail.selected === item.id && \"bg-muted\"\n            )}\n            onClick={() =>\n              setMail({\n                ...mail,\n                selected: item.id,\n              })\n            }\n          >\n            <div className=\"flex w-full flex-col gap-1\">\n              <div className=\"flex items-center\">\n                <div className=\"flex items-center gap-2\">\n                  <div className=\"font-semibold\">{item.name}</div>\n                  {!item.read && <span className=\"flex h-2 w-2 rounded-full bg-blue-600\" />}\n                </div>\n                <div\n                  className={cn(\n                    \"ml-auto text-xs\",\n                    mail.selected === item.id ? \"text-foreground\" : \"text-muted-foreground\"\n                  )}\n                >\n                  {formatDistanceToNow(new Date(item.date), {\n                    addSuffix: true,\n                  })}\n                </div>\n              </div>\n              <div className=\"text-xs font-medium\">{item.subject}</div>\n            </div>\n            <div className=\"text-muted-foreground line-clamp-2 text-xs\">\n              {item.text.substring(0, 300)}\n            </div>\n            {item.labels.length ? (\n              <div className=\"flex items-center gap-2\">\n                {item.labels.map((label) => (\n                  <Badge key={label} variant={getBadgeVariantFromLabel(label)}>\n                    {label}\n                  </Badge>\n                ))}\n              </div>\n            ) : null}\n          </button>\n        ))}\n      </div>\n    </ScrollArea>\n  );\n}\n\nfunction getBadgeVariantFromLabel(label: string): ComponentProps<typeof Badge>[\"variant\"] {\n  if ([\"work\"].includes(label.toLowerCase())) {\n    return \"default\";\n  }\n\n  if ([\"personal\"].includes(label.toLowerCase())) {\n    return \"outline\";\n  }\n\n  return \"secondary\";\n}\n"
  },
  {
    "path": "components/examples/mail/components/mail.tsx",
    "content": "import * as React from \"react\";\nimport {\n  AlertCircle,\n  Archive,\n  ArchiveX,\n  File,\n  Inbox,\n  MessagesSquare,\n  Search,\n  Send,\n  ShoppingCart,\n  Trash2,\n  Users2,\n} from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Input } from \"@/components/ui/input\";\nimport { ResizableHandle, ResizablePanel, ResizablePanelGroup } from \"@/components/ui/resizable\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport { AccountSwitcher } from \"@/components/examples/mail/components/account-switcher\";\nimport { MailDisplay } from \"@/components/examples/mail/components/mail-display\";\nimport { MailList } from \"@/components/examples/mail/components/mail-list\";\nimport { Nav } from \"@/components/examples/mail/components/nav\";\nimport { type Mail } from \"@/components/examples/mail/data\";\nimport { useMail } from \"@/components/examples/mail/use-mail\";\n\ninterface MailProps {\n  accounts: {\n    label: string;\n    email: string;\n    icon: React.ReactNode;\n  }[];\n  mails: Mail[];\n  defaultLayout?: number[];\n  defaultCollapsed?: boolean;\n  navCollapsedSize: number;\n}\n\nexport function Mail({\n  accounts,\n  mails,\n  defaultLayout = [20, 32, 48],\n  defaultCollapsed = false,\n  navCollapsedSize,\n}: MailProps) {\n  const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed);\n  const [mail] = useMail();\n\n  return (\n    <TooltipProvider delayDuration={0}>\n      <ResizablePanelGroup\n        orientation=\"horizontal\"\n        onLayoutChanged={(layout) => {\n          document.cookie = `react-resizable-panels:layout:mail=${JSON.stringify(layout)}`;\n        }}\n        className=\"h-full max-h-[min(800px,90vh)] items-stretch\"\n      >\n        <ResizablePanel\n          defaultSize={`${defaultLayout[0]}%`}\n          collapsedSize={`${navCollapsedSize}%`}\n          collapsible={true}\n          minSize=\"15%\"\n          maxSize=\"20%\"\n          onResize={(panelSize) => {\n            const collapsed = panelSize.asPercentage <= navCollapsedSize;\n            setIsCollapsed(collapsed);\n            document.cookie = `react-resizable-panels:collapsed=${JSON.stringify(collapsed)}`;\n          }}\n          className={cn(isCollapsed && \"min-w-[50px] transition-all duration-300 ease-in-out\")}\n        >\n          <div\n            className={cn(\n              \"flex items-center justify-center px-2 py-1.5\",\n              isCollapsed && \"px-0\"\n            )}\n          >\n            <AccountSwitcher isCollapsed={isCollapsed} accounts={accounts} />\n          </div>\n          <Separator />\n          <Nav\n            isCollapsed={isCollapsed}\n            links={[\n              {\n                title: \"Inbox\",\n                label: \"128\",\n                icon: Inbox,\n                variant: \"default\",\n              },\n              {\n                title: \"Drafts\",\n                label: \"9\",\n                icon: File,\n                variant: \"ghost\",\n              },\n              {\n                title: \"Sent\",\n                label: \"\",\n                icon: Send,\n                variant: \"ghost\",\n              },\n              {\n                title: \"Junk\",\n                label: \"23\",\n                icon: ArchiveX,\n                variant: \"ghost\",\n              },\n              {\n                title: \"Trash\",\n                label: \"\",\n                icon: Trash2,\n                variant: \"ghost\",\n              },\n              {\n                title: \"Archive\",\n                label: \"\",\n                icon: Archive,\n                variant: \"ghost\",\n              },\n            ]}\n          />\n          <Separator />\n          <Nav\n            isCollapsed={isCollapsed}\n            links={[\n              {\n                title: \"Social\",\n                label: \"972\",\n                icon: Users2,\n                variant: \"ghost\",\n              },\n              {\n                title: \"Updates\",\n                label: \"342\",\n                icon: AlertCircle,\n                variant: \"ghost\",\n              },\n              {\n                title: \"Forums\",\n                label: \"128\",\n                icon: MessagesSquare,\n                variant: \"ghost\",\n              },\n              {\n                title: \"Shopping\",\n                label: \"8\",\n                icon: ShoppingCart,\n                variant: \"ghost\",\n              },\n              {\n                title: \"Promotions\",\n                label: \"21\",\n                icon: Archive,\n                variant: \"ghost\",\n              },\n            ]}\n          />\n        </ResizablePanel>\n        <ResizableHandle withHandle />\n        <ResizablePanel defaultSize={`${defaultLayout[1]}%`} minSize=\"30%\">\n          <Tabs defaultValue=\"all\" className=\"flex h-full flex-col\">\n            <div className=\"flex items-center px-4 py-1.5\">\n              <h1 className=\"text-foreground text-xl font-bold\">Inbox</h1>\n              <TabsList className=\"ml-auto\">\n                <TabsTrigger value=\"all\">All mail</TabsTrigger>\n                <TabsTrigger value=\"unread\">Unread</TabsTrigger>\n              </TabsList>\n            </div>\n            <Separator />\n            <div className=\"bg-background/95 supports-[backdrop-filter]:bg-background/60 p-4 backdrop-blur\">\n              <form>\n                <div className=\"relative\">\n                  <Search className=\"text-muted-foreground absolute top-2.5 left-2 h-4 w-4\" />\n                  <Input placeholder=\"Search\" className=\"pl-8\" />\n                </div>\n              </form>\n            </div>\n            <TabsContent value=\"all\" className=\"m-0 min-h-0 flex-1\">\n              <MailList items={mails} />\n            </TabsContent>\n            <TabsContent value=\"unread\" className=\"m-0 min-h-0 flex-1\">\n              <MailList items={mails.filter((item) => !item.read)} />\n            </TabsContent>\n          </Tabs>\n        </ResizablePanel>\n        <ResizableHandle withHandle />\n        <ResizablePanel defaultSize={`${defaultLayout[2]}%`} minSize=\"30%\">\n          <MailDisplay mail={mails.find((item) => item.id === mail.selected) || null} />\n        </ResizablePanel>\n      </ResizablePanelGroup>\n    </TooltipProvider>\n  );\n}\n"
  },
  {
    "path": "components/examples/mail/components/nav.tsx",
    "content": "import { LucideIcon } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\n\ninterface NavProps {\n  isCollapsed: boolean;\n  links: {\n    title: string;\n    label?: string;\n    icon: LucideIcon;\n    variant: \"default\" | \"ghost\";\n  }[];\n}\n\nexport function Nav({ links, isCollapsed }: NavProps) {\n  return (\n    <div\n      data-collapsed={isCollapsed}\n      className=\"group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2\"\n    >\n      <nav className=\"grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2\">\n        {links.map((link, index) =>\n          isCollapsed ? (\n            <Tooltip key={index} delayDuration={0}>\n              <TooltipTrigger asChild>\n                <button\n                  className={cn(\n                    buttonVariants({ variant: link.variant, size: \"icon\" }),\n                    \"h-9 w-9\",\n                    link.variant === \"default\" &&\n                      \"dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white\"\n                  )}\n                >\n                  <link.icon className=\"h-4 w-4\" />\n                  <span className=\"sr-only\">{link.title}</span>\n                </button>\n              </TooltipTrigger>\n              <TooltipContent side=\"right\" className=\"flex items-center gap-4\">\n                {link.title}\n                {link.label && (\n                  <span className=\"ml-auto text-muted-foreground\">{link.label}</span>\n                )}\n              </TooltipContent>\n            </Tooltip>\n          ) : (\n            <button\n              key={index}\n              className={cn(\n                buttonVariants({ variant: link.variant, size: \"sm\" }),\n                link.variant === \"default\" &&\n                  \"group dark:bg-muted dark:text-foreground dark:hover:bg-muted dark:hover:text-foreground\",\n                \"justify-start\"\n              )}\n            >\n              <link.icon className=\"mr-2 h-4 w-4\" />\n              {link.title}\n              {link.label && (\n                <span\n                  className={cn(\n                    \"ml-auto\",\n                    link.variant === \"default\" &&\n                      \"text-background dark:text-muted-foreground\"\n                  )}\n                >\n                  {link.label}\n                </span>\n              )}\n            </button>\n          )\n        )}\n      </nav>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/mail/data.tsx",
    "content": "export const mails = [\n  {\n    id: \"6c84fb90-12c4-11e1-840d-7b25c5ee775a\",\n    name: \"William Smith\",\n    email: \"williamsmith@example.com\",\n    subject: \"Meeting Tomorrow\",\n    text: \"Hi, let's have a meeting tomorrow to discuss the project. I've been reviewing the project details and have some ideas I'd like to share. It's crucial that we align on our next steps to ensure the project's success.\\n\\nPlease come prepared with any questions or insights you may have. Looking forward to our meeting!\\n\\nBest regards, William\",\n    date: \"2023-10-22T09:00:00\",\n    read: true,\n    labels: [\"meeting\", \"work\", \"important\"],\n  },\n  {\n    id: \"110e8400-e29b-11d4-a716-446655440000\",\n    name: \"Alice Smith\",\n    email: \"alicesmith@example.com\",\n    subject: \"Re: Project Update\",\n    text: \"Thank you for the project update. It looks great! I've gone through the report, and the progress is impressive. The team has done a fantastic job, and I appreciate the hard work everyone has put in.\\n\\nI have a few minor suggestions that I'll include in the attached document.\\n\\nLet's discuss these during our next meeting. Keep up the excellent work!\\n\\nBest regards, Alice\",\n    date: \"2023-10-22T10:30:00\",\n    read: true,\n    labels: [\"work\", \"important\"],\n  },\n  {\n    id: \"3e7c3f6d-bdf5-46ae-8d90-171300f27ae2\",\n    name: \"Bob Johnson\",\n    email: \"bobjohnson@example.com\",\n    subject: \"Weekend Plans\",\n    text: \"Any plans for the weekend? I was thinking of going hiking in the nearby mountains. It's been a while since we had some outdoor fun.\\n\\nIf you're interested, let me know, and we can plan the details. It'll be a great way to unwind and enjoy nature.\\n\\nLooking forward to your response!\\n\\nBest, Bob\",\n    date: \"2023-04-10T11:45:00\",\n    read: true,\n    labels: [\"personal\"],\n  },\n  {\n    id: \"61c35085-72d7-42b4-8d62-738f700d4b92\",\n    name: \"Emily Davis\",\n    email: \"emilydavis@example.com\",\n    subject: \"Re: Question about Budget\",\n    text: \"I have a question about the budget for the upcoming project. It seems like there's a discrepancy in the allocation of resources.\\n\\nI've reviewed the budget report and identified a few areas where we might be able to optimize our spending without compromising the project's quality.\\n\\nI've attached a detailed analysis for your reference. Let's discuss this further in our next meeting.\\n\\nThanks, Emily\",\n    date: \"2023-03-25T13:15:00\",\n    read: false,\n    labels: [\"work\", \"budget\"],\n  },\n  {\n    id: \"8f7b5db9-d935-4e42-8e05-1f1d0a3dfb97\",\n    name: \"Michael Wilson\",\n    email: \"michaelwilson@example.com\",\n    subject: \"Important Announcement\",\n    text: \"I have an important announcement to make during our team meeting. It pertains to a strategic shift in our approach to the upcoming product launch. We've received valuable feedback from our beta testers, and I believe it's time to make some adjustments to better meet our customers' needs.\\n\\nThis change is crucial to our success, and I look forward to discussing it with the team. Please be prepared to share your insights during the meeting.\\n\\nRegards, Michael\",\n    date: \"2023-03-10T15:00:00\",\n    read: false,\n    labels: [\"meeting\", \"work\", \"important\"],\n  },\n  {\n    id: \"1f0f2c02-e299-40de-9b1d-86ef9e42126b\",\n    name: \"Sarah Brown\",\n    email: \"sarahbrown@example.com\",\n    subject: \"Re: Feedback on Proposal\",\n    text: \"Thank you for your feedback on the proposal. It looks great! I'm pleased to hear that you found it promising. The team worked diligently to address all the key points you raised, and I believe we now have a strong foundation for the project.\\n\\nI've attached the revised proposal for your review.\\n\\nPlease let me know if you have any further comments or suggestions. Looking forward to your response.\\n\\nBest regards, Sarah\",\n    date: \"2023-02-15T16:30:00\",\n    read: true,\n    labels: [\"work\"],\n  },\n  {\n    id: \"17c0a96d-4415-42b1-8b4f-764efab57f66\",\n    name: \"David Lee\",\n    email: \"davidlee@example.com\",\n    subject: \"New Project Idea\",\n    text: \"I have an exciting new project idea to discuss with you. It involves expanding our services to target a niche market that has shown considerable growth in recent months.\\n\\nI've prepared a detailed proposal outlining the potential benefits and the strategy for execution.\\n\\nThis project has the potential to significantly impact our business positively. Let's set up a meeting to dive into the details and determine if it aligns with our current goals.\\n\\nBest regards, David\",\n    date: \"2023-01-28T17:45:00\",\n    read: false,\n    labels: [\"meeting\", \"work\", \"important\"],\n  },\n  {\n    id: \"2f0130cb-39fc-44c4-bb3c-0a4337edaaab\",\n    name: \"Olivia Wilson\",\n    email: \"oliviawilson@example.com\",\n    subject: \"Vacation Plans\",\n    text: \"Let's plan our vacation for next month. What do you think? I've been thinking of visiting a tropical paradise, and I've put together some destination options.\\n\\nI believe it's time for us to unwind and recharge. Please take a look at the options and let me know your preferences.\\n\\nWe can start making arrangements to ensure a smooth and enjoyable trip.\\n\\nExcited to hear your thoughts! Olivia\",\n    date: \"2022-12-20T18:30:00\",\n    read: true,\n    labels: [\"personal\"],\n  },\n  {\n    id: \"de305d54-75b4-431b-adb2-eb6b9e546014\",\n    name: \"James Martin\",\n    email: \"jamesmartin@example.com\",\n    subject: \"Re: Conference Registration\",\n    text: \"I've completed the registration for the conference next month. The event promises to be a great networking opportunity, and I'm looking forward to attending the various sessions and connecting with industry experts.\\n\\nI've also attached the conference schedule for your reference.\\n\\nIf there are any specific topics or sessions you'd like me to explore, please let me know. It's an exciting event, and I'll make the most of it.\\n\\nBest regards, James\",\n    date: \"2022-11-30T19:15:00\",\n    read: true,\n    labels: [\"work\", \"conference\"],\n  },\n  {\n    id: \"7dd90c63-00f6-40f3-bd87-5060a24e8ee7\",\n    name: \"Sophia White\",\n    email: \"sophiawhite@example.com\",\n    subject: \"Team Dinner\",\n    text: \"Let's have a team dinner next week to celebrate our success. We've achieved some significant milestones, and it's time to acknowledge our hard work and dedication.\\n\\nI've made reservations at a lovely restaurant, and I'm sure it'll be an enjoyable evening.\\n\\nPlease confirm your availability and any dietary preferences. Looking forward to a fun and memorable dinner with the team!\\n\\nBest, Sophia\",\n    date: \"2022-11-05T20:30:00\",\n    read: false,\n    labels: [\"meeting\", \"work\"],\n  },\n  {\n    id: \"99a88f78-3eb4-4d87-87b7-7b15a49a0a05\",\n    name: \"Daniel Johnson\",\n    email: \"danieljohnson@example.com\",\n    subject: \"Feedback Request\",\n    text: \"I'd like your feedback on the latest project deliverables. We've made significant progress, and I value your input to ensure we're on the right track.\\n\\nI've attached the deliverables for your review, and I'm particularly interested in any areas where you think we can further enhance the quality or efficiency.\\n\\nYour feedback is invaluable, and I appreciate your time and expertise. Let's work together to make this project a success.\\n\\nRegards, Daniel\",\n    date: \"2022-10-22T09:30:00\",\n    read: false,\n    labels: [\"work\"],\n  },\n  {\n    id: \"f47ac10b-58cc-4372-a567-0e02b2c3d479\",\n    name: \"Ava Taylor\",\n    email: \"avataylor@example.com\",\n    subject: \"Re: Meeting Agenda\",\n    text: \"Here's the agenda for our meeting next week. I've included all the topics we need to cover, as well as time allocations for each.\\n\\nIf you have any additional items to discuss or any specific points to address, please let me know, and we can integrate them into the agenda.\\n\\nIt's essential that our meeting is productive and addresses all relevant matters.\\n\\nLooking forward to our meeting! Ava\",\n    date: \"2022-10-10T10:45:00\",\n    read: true,\n    labels: [\"meeting\", \"work\"],\n  },\n  {\n    id: \"c1a0ecb4-2540-49c5-86f8-21e5ce79e4e6\",\n    name: \"William Anderson\",\n    email: \"williamanderson@example.com\",\n    subject: \"Product Launch Update\",\n    text: \"The product launch is on track. I'll provide an update during our call. We've made substantial progress in the development and marketing of our new product.\\n\\nI'm excited to share the latest updates with you during our upcoming call. It's crucial that we coordinate our efforts to ensure a successful launch. Please come prepared with any questions or insights you may have.\\n\\nLet's make this product launch a resounding success!\\n\\nBest regards, William\",\n    date: \"2022-09-20T12:00:00\",\n    read: false,\n    labels: [\"meeting\", \"work\", \"important\"],\n  },\n  {\n    id: \"ba54eefd-4097-4949-99f2-2a9ae4d1a836\",\n    name: \"Mia Harris\",\n    email: \"miaharris@example.com\",\n    subject: \"Re: Travel Itinerary\",\n    text: \"I've received the travel itinerary. It looks great! Thank you for your prompt assistance in arranging the details. I've reviewed the schedule and the accommodations, and everything seems to be in order. I'm looking forward to the trip, and I'm confident it'll be a smooth and enjoyable experience.\\n\\nIf there are any specific activities or attractions you recommend at our destination, please feel free to share your suggestions.\\n\\nExcited for the trip! Mia\",\n    date: \"2022-09-10T13:15:00\",\n    read: true,\n    labels: [\"personal\", \"travel\"],\n  },\n  {\n    id: \"df09b6ed-28bd-4e0c-85a9-9320ec5179aa\",\n    name: \"Ethan Clark\",\n    email: \"ethanclark@example.com\",\n    subject: \"Team Building Event\",\n    text: \"Let's plan a team-building event for our department. Team cohesion and morale are vital to our success, and I believe a well-organized team-building event can be incredibly beneficial. I've done some research and have a few ideas for fun and engaging activities.\\n\\nPlease let me know your thoughts and availability. We want this event to be both enjoyable and productive.\\n\\nTogether, we'll strengthen our team and boost our performance.\\n\\nRegards, Ethan\",\n    date: \"2022-08-25T15:30:00\",\n    read: false,\n    labels: [\"meeting\", \"work\"],\n  },\n  {\n    id: \"d67c1842-7f8b-4b4b-9be1-1b3b1ab4611d\",\n    name: \"Chloe Hall\",\n    email: \"chloehall@example.com\",\n    subject: \"Re: Budget Approval\",\n    text: \"The budget has been approved. We can proceed with the project. I'm delighted to inform you that our budget proposal has received the green light from the finance department. This is a significant milestone, and it means we can move forward with the project as planned.\\n\\nI've attached the finalized budget for your reference. Let's ensure that we stay on track and deliver the project on time and within budget.\\n\\nIt's an exciting time for us! Chloe\",\n    date: \"2022-08-10T16:45:00\",\n    read: true,\n    labels: [\"work\", \"budget\"],\n  },\n  {\n    id: \"6c9a7f94-8329-4d70-95d3-51f68c186ae1\",\n    name: \"Samuel Turner\",\n    email: \"samuelturner@example.com\",\n    subject: \"Weekend Hike\",\n    text: \"Who's up for a weekend hike in the mountains? I've been craving some outdoor adventure, and a hike in the mountains sounds like the perfect escape. If you're up for the challenge, we can explore some scenic trails and enjoy the beauty of nature.\\n\\nI've done some research and have a few routes in mind.\\n\\nLet me know if you're interested, and we can plan the details.\\n\\nIt's sure to be a memorable experience! Samuel\",\n    date: \"2022-07-28T17:30:00\",\n    read: false,\n    labels: [\"personal\"],\n  },\n];\n\nexport type Mail = (typeof mails)[number];\n\nexport const accounts = [\n  {\n    label: \"Alicia Koch\",\n    email: \"alicia@example.com\",\n    icon: (\n      <svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n        <title>Gmail</title>\n        <path\n          d=\"M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64 12 9.548l6.545-4.91 1.528-1.145C21.69 2.28 24 3.434 24 5.457z\"\n          fill=\"currentColor\"\n        />\n      </svg>\n    ),\n  },\n  {\n    label: \"Alicia Koch\",\n    email: \"alicia2@example.com\",\n    icon: (\n      <svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n        <title>Vercel</title>\n        <path d=\"M24 22.525H0l12-21.05 12 21.05z\" fill=\"currentColor\" />\n      </svg>\n    ),\n  },\n  {\n    label: \"Alicia Koch\",\n    email: \"alicia3@example.com\",\n    icon: (\n      <svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n        <title>iCloud</title>\n        <path\n          d=\"M13.762 4.29a6.51 6.51 0 0 0-5.669 3.332 3.571 3.571 0 0 0-1.558-.36 3.571 3.571 0 0 0-3.516 3A4.918 4.918 0 0 0 0 14.796a4.918 4.918 0 0 0 4.92 4.914 4.93 4.93 0 0 0 .617-.045h14.42c2.305-.272 4.041-2.258 4.043-4.589v-.009a4.594 4.594 0 0 0-3.727-4.508 6.51 6.51 0 0 0-6.511-6.27z\"\n          fill=\"currentColor\"\n        />\n      </svg>\n    ),\n  },\n];\n\nexport type Account = (typeof accounts)[number];\n\nexport const contacts = [\n  {\n    name: \"Emma Johnson\",\n    email: \"emma.johnson@example.com\",\n  },\n  {\n    name: \"Liam Wilson\",\n    email: \"liam.wilson@example.com\",\n  },\n  {\n    name: \"Olivia Davis\",\n    email: \"olivia.davis@example.com\",\n  },\n  {\n    name: \"Noah Martinez\",\n    email: \"noah.martinez@example.com\",\n  },\n  {\n    name: \"Ava Taylor\",\n    email: \"ava.taylor@example.com\",\n  },\n  {\n    name: \"Lucas Brown\",\n    email: \"lucas.brown@example.com\",\n  },\n  {\n    name: \"Sophia Smith\",\n    email: \"sophia.smith@example.com\",\n  },\n  {\n    name: \"Ethan Wilson\",\n    email: \"ethan.wilson@example.com\",\n  },\n  {\n    name: \"Isabella Jackson\",\n    email: \"isabella.jackson@example.com\",\n  },\n  {\n    name: \"Mia Clark\",\n    email: \"mia.clark@example.com\",\n  },\n  {\n    name: \"Mason Lee\",\n    email: \"mason.lee@example.com\",\n  },\n  {\n    name: \"Layla Harris\",\n    email: \"layla.harris@example.com\",\n  },\n  {\n    name: \"William Anderson\",\n    email: \"william.anderson@example.com\",\n  },\n  {\n    name: \"Ella White\",\n    email: \"ella.white@example.com\",\n  },\n  {\n    name: \"James Thomas\",\n    email: \"james.thomas@example.com\",\n  },\n  {\n    name: \"Harper Lewis\",\n    email: \"harper.lewis@example.com\",\n  },\n  {\n    name: \"Benjamin Moore\",\n    email: \"benjamin.moore@example.com\",\n  },\n  {\n    name: \"Aria Hall\",\n    email: \"aria.hall@example.com\",\n  },\n  {\n    name: \"Henry Turner\",\n    email: \"henry.turner@example.com\",\n  },\n  {\n    name: \"Scarlett Adams\",\n    email: \"scarlett.adams@example.com\",\n  },\n];\n\nexport type Contact = (typeof contacts)[number];\n"
  },
  {
    "path": "components/examples/mail/index.tsx",
    "content": "import { Mail } from \"@/components/examples/mail/components/mail\";\nimport { accounts, mails } from \"@/components/examples/mail/data\";\n\nexport default function MailPage() {\n  return <Mail accounts={accounts} mails={mails} navCollapsedSize={4} />;\n}\n"
  },
  {
    "path": "components/examples/mail/use-mail.ts",
    "content": "import { create } from \"zustand\";\nimport { Mail, mails } from \"@/components/examples/mail/data\";\n\ninterface Config {\n  selected: Mail[\"id\"] | null;\n}\n\nconst useMailStore = create<\n  Config & { setState: (newState: Partial<Config>) => void }\n>((set) => ({\n  selected: mails[0].id,\n  setState: (newState) => set((state) => ({ ...state, ...newState })),\n}));\n\nexport function useMail(): [Config, (newState: Partial<Config>) => void] {\n  const selected = useMailStore((state) => state.selected);\n  const setState = useMailStore((state) => state.setState);\n  return [{ selected }, setState];\n}\n"
  },
  {
    "path": "components/examples/music/components/album-artwork.tsx",
    "content": "import * as React from \"react\";\nimport { PlusCircle } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  ContextMenu,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuSeparator,\n  ContextMenuSub,\n  ContextMenuSubContent,\n  ContextMenuSubTrigger,\n  ContextMenuTrigger,\n} from \"@/components/ui/context-menu\";\nimport { Album } from \"../data/albums\";\nimport { playlists } from \"../data/playlists\";\n\ninterface AlbumArtworkProps extends React.HTMLAttributes<HTMLDivElement> {\n  album: Album;\n  aspectRatio?: \"portrait\" | \"square\";\n  width?: number;\n  height?: number;\n}\n\nexport function AlbumArtwork({\n  album,\n  aspectRatio = \"portrait\",\n  width,\n  height,\n  className,\n  ...props\n}: AlbumArtworkProps) {\n  return (\n    <div className={cn(\"flex flex-col gap-3\", className)} {...props}>\n      <ContextMenu>\n        <ContextMenuTrigger>\n          <div className=\"overflow-hidden rounded-md\">\n            <img\n              src={album.cover}\n              alt={album.name}\n              className={cn(\n                \"h-auto w-auto object-cover transition-all hover:scale-105\",\n                aspectRatio === \"portrait\" ? \"aspect-[3/4]\" : \"aspect-square\"\n              )}\n              style={{\n                width: width ? `${width}px` : \"auto\",\n                height: height ? `${height}px` : \"auto\",\n              }}\n            />\n          </div>\n        </ContextMenuTrigger>\n        <ContextMenuContent className=\"w-40\">\n          <ContextMenuItem>Add to Library</ContextMenuItem>\n          <ContextMenuSub>\n            <ContextMenuSubTrigger>Add to Playlist</ContextMenuSubTrigger>\n            <ContextMenuSubContent className=\"w-48\">\n              <ContextMenuItem>\n                <PlusCircle className=\"mr-2 h-4 w-4\" />\n                New Playlist\n              </ContextMenuItem>\n              <ContextMenuSeparator />\n              {playlists.map((playlist) => (\n                <ContextMenuItem key={playlist}>\n                  <svg\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    fill=\"none\"\n                    stroke=\"currentColor\"\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    strokeWidth=\"2\"\n                    className=\"mr-2 h-4 w-4\"\n                    viewBox=\"0 0 24 24\"\n                  >\n                    <path d=\"M21 15V6M18.5 18a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM12 12H3M16 6H3M12 18H3\" />\n                  </svg>\n                  {playlist}\n                </ContextMenuItem>\n              ))}\n            </ContextMenuSubContent>\n          </ContextMenuSub>\n          <ContextMenuSeparator />\n          <ContextMenuItem>Play Next</ContextMenuItem>\n          <ContextMenuItem>Play Later</ContextMenuItem>\n          <ContextMenuItem>Create Station</ContextMenuItem>\n          <ContextMenuSeparator />\n          <ContextMenuItem>Like</ContextMenuItem>\n          <ContextMenuItem>Share</ContextMenuItem>\n        </ContextMenuContent>\n      </ContextMenu>\n      <div className=\"flex flex-col gap-1 text-sm\">\n        <h3 className=\"font-medium leading-none\">{album.name}</h3>\n        <p className=\"text-xs text-muted-foreground\">{album.artist}</p>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/music/components/menu.tsx",
    "content": "import {\n  Menubar,\n  MenubarCheckboxItem,\n  MenubarContent,\n  MenubarItem,\n  MenubarLabel,\n  MenubarMenu,\n  MenubarRadioGroup,\n  MenubarRadioItem,\n  MenubarSeparator,\n  MenubarShortcut,\n  MenubarSub,\n  MenubarSubContent,\n  MenubarSubTrigger,\n  MenubarTrigger,\n} from \"@/components/ui/menubar\";\n\nexport function Menu() {\n  return (\n    <Menubar className=\"rounded-none border-b border-none px-2 lg:px-4\">\n      <MenubarMenu>\n        <MenubarTrigger className=\"font-bold\">Music</MenubarTrigger>\n        <MenubarContent>\n          <MenubarItem>About Music</MenubarItem>\n          <MenubarSeparator />\n          <MenubarItem>\n            Preferences... <MenubarShortcut>⌘,</MenubarShortcut>\n          </MenubarItem>\n          <MenubarSeparator />\n          <MenubarItem>\n            Hide Music... <MenubarShortcut>⌘H</MenubarShortcut>\n          </MenubarItem>\n          <MenubarItem>\n            Hide Others... <MenubarShortcut>⇧⌘H</MenubarShortcut>\n          </MenubarItem>\n          <MenubarShortcut />\n          <MenubarItem>\n            Quit Music <MenubarShortcut>⌘Q</MenubarShortcut>\n          </MenubarItem>\n        </MenubarContent>\n      </MenubarMenu>\n      <MenubarMenu>\n        <MenubarTrigger className=\"relative\">File</MenubarTrigger>\n        <MenubarContent>\n          <MenubarSub>\n            <MenubarSubTrigger>New</MenubarSubTrigger>\n            <MenubarSubContent className=\"w-[230px]\">\n              <MenubarItem>\n                Playlist <MenubarShortcut>⌘N</MenubarShortcut>\n              </MenubarItem>\n              <MenubarItem disabled>\n                Playlist from Selection <MenubarShortcut>⇧⌘N</MenubarShortcut>\n              </MenubarItem>\n              <MenubarItem>\n                Smart Playlist... <MenubarShortcut>⌥⌘N</MenubarShortcut>\n              </MenubarItem>\n              <MenubarItem>Playlist Folder</MenubarItem>\n              <MenubarItem disabled>Genius Playlist</MenubarItem>\n            </MenubarSubContent>\n          </MenubarSub>\n          <MenubarItem>\n            Open Stream URL... <MenubarShortcut>⌘U</MenubarShortcut>\n          </MenubarItem>\n          <MenubarItem>\n            Close Window <MenubarShortcut>⌘W</MenubarShortcut>\n          </MenubarItem>\n          <MenubarSeparator />\n          <MenubarSub>\n            <MenubarSubTrigger>Library</MenubarSubTrigger>\n            <MenubarSubContent>\n              <MenubarItem>Update Cloud Library</MenubarItem>\n              <MenubarItem>Update Genius</MenubarItem>\n              <MenubarSeparator />\n              <MenubarItem>Organize Library...</MenubarItem>\n              <MenubarItem>Export Library...</MenubarItem>\n              <MenubarSeparator />\n              <MenubarItem>Import Playlist...</MenubarItem>\n              <MenubarItem disabled>Export Playlist...</MenubarItem>\n              <MenubarItem>Show Duplicate Items</MenubarItem>\n              <MenubarSeparator />\n              <MenubarItem>Get Album Artwork</MenubarItem>\n              <MenubarItem disabled>Get Track Names</MenubarItem>\n            </MenubarSubContent>\n          </MenubarSub>\n          <MenubarItem>\n            Import... <MenubarShortcut>⌘O</MenubarShortcut>\n          </MenubarItem>\n          <MenubarItem disabled>Burn Playlist to Disc...</MenubarItem>\n          <MenubarSeparator />\n          <MenubarItem>\n            Show in Finder <MenubarShortcut>⇧⌘R</MenubarShortcut>{\" \"}\n          </MenubarItem>\n          <MenubarItem>Convert</MenubarItem>\n          <MenubarSeparator />\n          <MenubarItem>Page Setup...</MenubarItem>\n          <MenubarItem disabled>\n            Print... <MenubarShortcut>⌘P</MenubarShortcut>\n          </MenubarItem>\n        </MenubarContent>\n      </MenubarMenu>\n      <MenubarMenu>\n        <MenubarTrigger>Edit</MenubarTrigger>\n        <MenubarContent>\n          <MenubarItem disabled>\n            Undo <MenubarShortcut>⌘Z</MenubarShortcut>\n          </MenubarItem>\n          <MenubarItem disabled>\n            Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>\n          </MenubarItem>\n          <MenubarSeparator />\n          <MenubarItem disabled>\n            Cut <MenubarShortcut>⌘X</MenubarShortcut>\n          </MenubarItem>\n          <MenubarItem disabled>\n            Copy <MenubarShortcut>⌘C</MenubarShortcut>\n          </MenubarItem>\n          <MenubarItem disabled>\n            Paste <MenubarShortcut>⌘V</MenubarShortcut>\n          </MenubarItem>\n          <MenubarSeparator />\n          <MenubarItem>\n            Select All <MenubarShortcut>⌘A</MenubarShortcut>\n          </MenubarItem>\n          <MenubarItem disabled>\n            Deselect All <MenubarShortcut>⇧⌘A</MenubarShortcut>\n          </MenubarItem>\n          <MenubarSeparator />\n          <MenubarItem>\n            Smart Dictation...{\" \"}\n            <MenubarShortcut>\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                strokeWidth=\"2\"\n                className=\"h-4 w-4\"\n                viewBox=\"0 0 24 24\"\n              >\n                <path d=\"m12 8-9.04 9.06a2.82 2.82 0 1 0 3.98 3.98L16 12\" />\n                <circle cx=\"17\" cy=\"7\" r=\"5\" />\n              </svg>\n            </MenubarShortcut>\n          </MenubarItem>\n          <MenubarItem>\n            Emoji & Symbols{\" \"}\n            <MenubarShortcut>\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                strokeWidth=\"2\"\n                className=\"h-4 w-4\"\n                viewBox=\"0 0 24 24\"\n              >\n                <circle cx=\"12\" cy=\"12\" r=\"10\" />\n                <path d=\"M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\" />\n              </svg>\n            </MenubarShortcut>\n          </MenubarItem>\n        </MenubarContent>\n      </MenubarMenu>\n      <MenubarMenu>\n        <MenubarTrigger>View</MenubarTrigger>\n        <MenubarContent>\n          <MenubarCheckboxItem>Show Playing Next</MenubarCheckboxItem>\n          <MenubarCheckboxItem checked>Show Lyrics</MenubarCheckboxItem>\n          <MenubarSeparator />\n          <MenubarItem inset disabled>\n            Show Status Bar\n          </MenubarItem>\n          <MenubarSeparator />\n          <MenubarItem inset>Hide Sidebar</MenubarItem>\n          <MenubarItem disabled inset>\n            Enter Full Screen\n          </MenubarItem>\n        </MenubarContent>\n      </MenubarMenu>\n      <MenubarMenu>\n        <MenubarTrigger className=\"hidden md:block\">Account</MenubarTrigger>\n        <MenubarContent forceMount>\n          <MenubarLabel inset>Switch Account</MenubarLabel>\n          <MenubarSeparator />\n          <MenubarRadioGroup value=\"benoit\">\n            <MenubarRadioItem value=\"andy\">Andy</MenubarRadioItem>\n            <MenubarRadioItem value=\"benoit\">Benoit</MenubarRadioItem>\n            <MenubarRadioItem value=\"Luis\">Luis</MenubarRadioItem>\n          </MenubarRadioGroup>\n          <MenubarSeparator />\n          <MenubarItem inset>Manage Family...</MenubarItem>\n          <MenubarSeparator />\n          <MenubarItem inset>Add Account...</MenubarItem>\n        </MenubarContent>\n      </MenubarMenu>\n    </Menubar>\n  );\n}\n"
  },
  {
    "path": "components/examples/music/components/podcast-empty-placeholder.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogFooter,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n  ResponsiveDialogTrigger,\n} from \"@/components/ui/revola\";\n\nexport function PodcastEmptyPlaceholder() {\n  return (\n    <div className=\"flex h-[450px] shrink-0 items-center justify-center rounded-md border border-dashed\">\n      <div className=\"mx-auto flex max-w-[420px] flex-col items-center justify-center text-center\">\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          strokeWidth=\"2\"\n          className=\"text-muted-foreground h-10 w-10\"\n          viewBox=\"0 0 24 24\"\n        >\n          <circle cx=\"12\" cy=\"11\" r=\"1\" />\n          <path d=\"M11 17a1 1 0 0 1 2 0c0 .5-.34 3-.5 4.5a.5.5 0 0 1-1 0c-.16-1.5-.5-4-.5-4.5ZM8 14a5 5 0 1 1 8 0\" />\n          <path d=\"M17 18.5a9 9 0 1 0-10 0\" />\n        </svg>\n\n        <h3 className=\"mt-4 text-lg font-semibold\">No episodes added</h3>\n        <p className=\"text-muted-foreground mt-2 mb-4 text-sm\">\n          You have not added any podcasts. Add one below.\n        </p>\n        <ResponsiveDialog>\n          <ResponsiveDialogTrigger asChild>\n            <Button size=\"sm\" className=\"relative\">\n              Add Podcast\n            </Button>\n          </ResponsiveDialogTrigger>\n          <ResponsiveDialogContent>\n            <div className=\"space-y-4 p-6 py-0 sm:pt-6\">\n              <ResponsiveDialogHeader>\n                <ResponsiveDialogTitle>Add Podcast</ResponsiveDialogTitle>\n                <ResponsiveDialogDescription>\n                  Copy and paste the podcast feed URL to import.\n                </ResponsiveDialogDescription>\n              </ResponsiveDialogHeader>\n\n              <div className=\"grid gap-4 py-4\">\n                <div className=\"grid gap-2\">\n                  <Label htmlFor=\"url\">Podcast URL</Label>\n                  <Input id=\"url\" placeholder=\"https://example.com/feed.xml\" />\n                </div>\n              </div>\n            </div>\n\n            <ResponsiveDialogFooter className=\"p-6 pt-2 pb-4 sm:pt-0 sm:pb-6\">\n              <Button>Import Podcast</Button>\n            </ResponsiveDialogFooter>\n          </ResponsiveDialogContent>\n        </ResponsiveDialog>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/music/components/sidebar.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\n\nimport { Playlist } from \"../data/playlists\";\n\ninterface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {\n  playlists: Playlist[];\n}\n\nexport function Sidebar({ className, playlists }: SidebarProps) {\n  return (\n    <div className={cn(\"pb-12\", className)}>\n      <div className=\"space-y-4 py-4\">\n        <div className=\"px-3 py-2\">\n          <h2 className=\"mb-2 px-4 text-lg font-semibold tracking-tight\">\n            Discover\n          </h2>\n          <div className=\"space-y-1\">\n            <Button variant=\"secondary\" className=\"w-full justify-start\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"mr-2 h-4 w-4\"\n              >\n                <circle cx=\"12\" cy=\"12\" r=\"10\" />\n                <polygon points=\"10 8 16 12 10 16 10 8\" />\n              </svg>\n              Listen Now\n            </Button>\n            <Button variant=\"ghost\" className=\"w-full justify-start\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"mr-2 h-4 w-4\"\n              >\n                <rect width=\"7\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\" />\n                <rect width=\"7\" height=\"7\" x=\"14\" y=\"3\" rx=\"1\" />\n                <rect width=\"7\" height=\"7\" x=\"14\" y=\"14\" rx=\"1\" />\n                <rect width=\"7\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\" />\n              </svg>\n              Browse\n            </Button>\n            <Button variant=\"ghost\" className=\"w-full justify-start\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"mr-2 h-4 w-4\"\n              >\n                <path d=\"M4.9 19.1C1 15.2 1 8.8 4.9 4.9\" />\n                <path d=\"M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5\" />\n                <circle cx=\"12\" cy=\"12\" r=\"2\" />\n                <path d=\"M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5\" />\n                <path d=\"M19.1 4.9C23 8.8 23 15.1 19.1 19\" />\n              </svg>\n              Radio\n            </Button>\n          </div>\n        </div>\n        <div className=\"px-3 py-2\">\n          <h2 className=\"mb-2 px-4 text-lg font-semibold tracking-tight\">Library</h2>\n          <div className=\"space-y-1\">\n            <Button variant=\"ghost\" className=\"w-full justify-start\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"mr-2 h-4 w-4\"\n              >\n                <path d=\"M21 15V6\" />\n                <path d=\"M18.5 18a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z\" />\n                <path d=\"M12 12H3\" />\n                <path d=\"M16 6H3\" />\n                <path d=\"M12 18H3\" />\n              </svg>\n              Playlists\n            </Button>\n            <Button variant=\"ghost\" className=\"w-full justify-start\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"mr-2 h-4 w-4\"\n              >\n                <circle cx=\"8\" cy=\"18\" r=\"4\" />\n                <path d=\"M12 18V2l7 4\" />\n              </svg>\n              Songs\n            </Button>\n            <Button variant=\"ghost\" className=\"w-full justify-start\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"mr-2 h-4 w-4\"\n              >\n                <path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\" />\n                <circle cx=\"12\" cy=\"7\" r=\"4\" />\n              </svg>\n              Made for You\n            </Button>\n            <Button variant=\"ghost\" className=\"w-full justify-start\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"mr-2 h-4 w-4\"\n              >\n                <path d=\"m12 8-9.04 9.06a2.82 2.82 0 1 0 3.98 3.98L16 12\" />\n                <circle cx=\"17\" cy=\"7\" r=\"5\" />\n              </svg>\n              Artists\n            </Button>\n            <Button variant=\"ghost\" className=\"w-full justify-start\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"mr-2 h-4 w-4\"\n              >\n                <path d=\"m16 6 4 14\" />\n                <path d=\"M12 6v14\" />\n                <path d=\"M8 8v12\" />\n                <path d=\"M4 4v16\" />\n              </svg>\n              Albums\n            </Button>\n          </div>\n        </div>\n        <div className=\"py-2\">\n          <h2 className=\"relative px-7 text-lg font-semibold tracking-tight\">\n            Playlists\n          </h2>\n          <ScrollArea className=\"h-[300px] px-1\">\n            <div className=\"space-y-1 p-2\">\n              {playlists?.map((playlist, i) => (\n                <Button\n                  key={`${playlist}-${i}`}\n                  variant=\"ghost\"\n                  className=\"w-full justify-start font-normal\"\n                >\n                  <svg\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    viewBox=\"0 0 24 24\"\n                    fill=\"none\"\n                    stroke=\"currentColor\"\n                    strokeWidth=\"2\"\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    className=\"mr-2 h-4 w-4\"\n                  >\n                    <path d=\"M21 15V6\" />\n                    <path d=\"M18.5 18a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z\" />\n                    <path d=\"M12 12H3\" />\n                    <path d=\"M16 6H3\" />\n                    <path d=\"M12 18H3\" />\n                  </svg>\n                  {playlist}\n                </Button>\n              ))}\n            </div>\n          </ScrollArea>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/music/data/albums.ts",
    "content": "export interface Album {\n  name: string;\n  artist: string;\n  cover: string;\n}\n\nexport const listenNowAlbums: Album[] = [\n  {\n    name: \"React Rendezvous\",\n    artist: \"Ethan Byte\",\n    cover:\n      \"https://images.unsplash.com/photo-1611348586804-61bf6c080437?w=300&dpr=2&q=80\",\n  },\n  {\n    name: \"Async Awakenings\",\n    artist: \"Nina Netcode\",\n    cover:\n      \"https://images.unsplash.com/photo-1468817814611-b7edf94b5d60?w=300&dpr=2&q=80\",\n  },\n  {\n    name: \"The Art of Reusability\",\n    artist: \"Lena Logic\",\n    cover:\n      \"https://images.unsplash.com/photo-1528143358888-6d3c7f67bd5d?w=300&dpr=2&q=80\",\n  },\n  {\n    name: \"Stateful Symphony\",\n    artist: \"Beth Binary\",\n    cover:\n      \"https://images.unsplash.com/photo-1490300472339-79e4adc6be4a?w=300&dpr=2&q=80\",\n  },\n];\n\nexport const madeForYouAlbums: Album[] = [\n  {\n    name: \"Thinking Components\",\n    artist: \"Lena Logic\",\n    cover:\n      \"https://images.unsplash.com/photo-1615247001958-f4bc92fa6a4a?w=300&dpr=2&q=80\",\n  },\n  {\n    name: \"Functional Fury\",\n    artist: \"Beth Binary\",\n    cover:\n      \"https://images.unsplash.com/photo-1513745405825-efaf9a49315f?w=300&dpr=2&q=80\",\n  },\n  {\n    name: \"React Rendezvous\",\n    artist: \"Ethan Byte\",\n    cover:\n      \"https://images.unsplash.com/photo-1614113489855-66422ad300a4?w=300&dpr=2&q=80\",\n  },\n  {\n    name: \"Stateful Symphony\",\n    artist: \"Beth Binary\",\n    cover:\n      \"https://images.unsplash.com/photo-1446185250204-f94591f7d702?w=300&dpr=2&q=80\",\n  },\n  {\n    name: \"Async Awakenings\",\n    artist: \"Nina Netcode\",\n    cover:\n      \"https://images.unsplash.com/photo-1468817814611-b7edf94b5d60?w=300&dpr=2&q=80\",\n  },\n  {\n    name: \"The Art of Reusability\",\n    artist: \"Lena Logic\",\n    cover:\n      \"https://images.unsplash.com/photo-1490300472339-79e4adc6be4a?w=300&dpr=2&q=80\",\n  },\n];\n"
  },
  {
    "path": "components/examples/music/data/playlists.ts",
    "content": "export type Playlist = (typeof playlists)[number];\n\nexport const playlists = [\n  \"Recently Added\",\n  \"Recently Played\",\n  \"Top Songs\",\n  \"Top Albums\",\n  \"Top Artists\",\n  \"Logic Discography\",\n  \"Bedtime Beats\",\n  \"Feeling Happy\",\n  \"I miss Y2K Pop\",\n  \"Runtober\",\n  \"Mellow Days\",\n  \"Eminem Essentials\",\n];\n"
  },
  {
    "path": "components/examples/music/index.tsx",
    "content": "import { PlusCircle } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { ScrollArea, ScrollBar } from \"@/components/ui/scroll-area\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\n\nimport { AlbumArtwork } from \"./components/album-artwork\";\nimport { Menu } from \"./components/menu\";\nimport { PodcastEmptyPlaceholder } from \"./components/podcast-empty-placeholder\";\nimport { Sidebar } from \"./components/sidebar\";\nimport { listenNowAlbums, madeForYouAlbums } from \"./data/albums\";\nimport { playlists } from \"./data/playlists\";\n\nexport default function MusicPage() {\n  return (\n    <>\n      <div className=\"hidden md:block\">\n        <Menu />\n        <div className=\"border-t\">\n          <div className=\"bg-background\">\n            <div className=\"grid lg:grid-cols-5\">\n              <Sidebar playlists={playlists} className=\"hidden lg:block\" />\n              <div className=\"col-span-3 lg:col-span-4 lg:border-l\">\n                <div className=\"h-full px-4 py-6 lg:px-8\">\n                  <Tabs defaultValue=\"music\" className=\"h-full space-y-6\">\n                    <div className=\"space-between flex items-center\">\n                      <TabsList>\n                        <TabsTrigger value=\"music\" className=\"relative\">\n                          Music\n                        </TabsTrigger>\n                        <TabsTrigger value=\"podcasts\">Podcasts</TabsTrigger>\n                        <TabsTrigger value=\"live\" disabled>\n                          Live\n                        </TabsTrigger>\n                      </TabsList>\n                      <div className=\"ml-auto mr-4\">\n                        <Button>\n                          <PlusCircle />\n                          Add music\n                        </Button>\n                      </div>\n                    </div>\n                    <TabsContent\n                      value=\"music\"\n                      className=\"border-none p-0 outline-none\"\n                    >\n                      <div className=\"flex items-center justify-between\">\n                        <div className=\"space-y-1\">\n                          <h2 className=\"text-2xl font-semibold tracking-tight\">\n                            Listen Now\n                          </h2>\n                          <p className=\"text-sm text-muted-foreground\">\n                            Top picks for you. Updated daily.\n                          </p>\n                        </div>\n                      </div>\n                      <Separator className=\"my-4\" />\n                      <div className=\"relative\">\n                        <ScrollArea>\n                          <div className=\"flex space-x-4 pb-4\">\n                            {listenNowAlbums.map((album) => (\n                              <AlbumArtwork\n                                key={album.name}\n                                album={album}\n                                className=\"w-[250px]\"\n                                aspectRatio=\"portrait\"\n                                width={250}\n                                height={330}\n                              />\n                            ))}\n                          </div>\n                          <ScrollBar orientation=\"horizontal\" />\n                        </ScrollArea>\n                      </div>\n                      <div className=\"mt-6 space-y-1\">\n                        <h2 className=\"text-2xl font-semibold tracking-tight\">\n                          Made for You\n                        </h2>\n                        <p className=\"text-sm text-muted-foreground\">\n                          Your personal playlists. Updated daily.\n                        </p>\n                      </div>\n                      <Separator className=\"my-4\" />\n                      <div className=\"relative\">\n                        <ScrollArea>\n                          <div className=\"flex space-x-4 pb-4\">\n                            {madeForYouAlbums.map((album) => (\n                              <AlbumArtwork\n                                key={album.name}\n                                album={album}\n                                className=\"w-[150px]\"\n                                aspectRatio=\"square\"\n                                width={150}\n                                height={150}\n                              />\n                            ))}\n                          </div>\n                          <ScrollBar orientation=\"horizontal\" />\n                        </ScrollArea>\n                      </div>\n                    </TabsContent>\n                    <TabsContent\n                      value=\"podcasts\"\n                      className=\"h-full flex-col border-none p-0 data-[state=active]:flex\"\n                    >\n                      <div className=\"flex items-center justify-between\">\n                        <div className=\"space-y-1\">\n                          <h2 className=\"text-2xl font-semibold tracking-tight\">\n                            New Episodes\n                          </h2>\n                          <p className=\"text-sm text-muted-foreground\">\n                            Your favorite podcasts. Updated daily.\n                          </p>\n                        </div>\n                      </div>\n                      <Separator className=\"my-4\" />\n                      <PodcastEmptyPlaceholder />\n                    </TabsContent>\n                  </Tabs>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "components/examples/pricing/pricing.tsx",
    "content": "\"use client\";\n\nimport { ArrowRight, CircleCheck } from \"lucide-react\";\nimport { useState } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardFooter, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Switch } from \"@/components/ui/switch\";\n\ninterface PricingFeature {\n  text: string;\n}\n\ninterface PricingPlan {\n  id: string;\n  name: string;\n  description: string;\n  monthlyPrice: string;\n  yearlyPrice: string;\n  features: PricingFeature[];\n  button: {\n    text: string;\n    url: string;\n  };\n}\n\ninterface Pricing2Props {\n  heading?: string;\n  description?: string;\n  plans?: PricingPlan[];\n}\n\nconst Pricing2 = ({\n  heading = \"Pricing\",\n  description = \"Check out our affordable pricing plans\",\n  plans = [\n    {\n      id: \"plus\",\n      name: \"Plus\",\n      description: \"For personal use\",\n      monthlyPrice: \"$19\",\n      yearlyPrice: \"$15\",\n      features: [\n        { text: \"Up to 5 team members\" },\n        { text: \"Basic components library\" },\n        { text: \"Community support\" },\n        { text: \"1GB storage space\" },\n      ],\n      button: {\n        text: \"Purchase\",\n        url: \"https://www.shadcnblocks.com\",\n      },\n    },\n    {\n      id: \"pro\",\n      name: \"Pro\",\n      description: \"For professionals\",\n      monthlyPrice: \"$49\",\n      yearlyPrice: \"$35\",\n      features: [\n        { text: \"Unlimited team members\" },\n        { text: \"Advanced components\" },\n        { text: \"Priority support\" },\n        { text: \"Unlimited storage\" },\n      ],\n      button: {\n        text: \"Purchase\",\n        url: \"https://www.shadcnblocks.com\",\n      },\n    },\n  ],\n}: Pricing2Props) => {\n  const [isYearly, setIsYearly] = useState(false);\n  return (\n    <section className=\"@container py-16\">\n      <div className=\"container mx-auto\">\n        <div className=\"mx-auto flex max-w-5xl flex-col items-center gap-8 text-center\">\n          <div className=\"flex size-full flex-col items-center gap-4\">\n            <h2 className=\"text-foreground text-3xl leading-tight font-bold tracking-tight text-pretty @3xl:text-5xl\">\n              {heading}\n            </h2>\n            <p className=\"text-muted-foreground @3xl:text-xl\">{description}</p>\n            <div className=\"text-foreground flex items-center gap-3 text-lg\">\n              Monthly\n              <Switch checked={isYearly} onCheckedChange={() => setIsYearly(!isYearly)} />\n              Yearly\n            </div>\n          </div>\n\n          <div className=\"flex flex-col items-stretch gap-6 @3xl:flex-row\">\n            {plans.map((plan) => (\n              <Card key={plan.id} className=\"flex w-80 flex-col justify-between text-left\">\n                <CardHeader>\n                  <CardTitle>\n                    <p>{plan.name}</p>\n                  </CardTitle>\n                  <p className=\"text-muted-foreground text-sm\">{plan.description}</p>\n                  <span className=\"text-4xl font-bold\">\n                    {isYearly ? plan.yearlyPrice : plan.monthlyPrice}\n                  </span>\n                  <p className=\"text-muted-foreground\">\n                    Billed{\" \"}\n                    {isYearly\n                      ? `$${Number(plan.yearlyPrice.slice(1)) * 12}`\n                      : `$${Number(plan.monthlyPrice.slice(1)) * 12}`}{\" \"}\n                    annually\n                  </p>\n                </CardHeader>\n                <CardContent>\n                  <Separator className=\"mb-6\" />\n                  {plan.id === \"pro\" && (\n                    <p className=\"mb-3 font-semibold\">Everything in Plus, and:</p>\n                  )}\n                  <ul className=\"space-y-4\">\n                    {plan.features.map((feature, index) => (\n                      <li key={index} className=\"flex items-center gap-2\">\n                        <CircleCheck className=\"size-4\" />\n                        <span>{feature.text}</span>\n                      </li>\n                    ))}\n                  </ul>\n                </CardContent>\n                <CardFooter className=\"mt-auto\">\n                  <Button asChild className=\"w-full\">\n                    <a href={plan.button.url} target=\"_blank\">\n                      {plan.button.text}\n                      <ArrowRight className=\"ml-2 size-4\" />\n                    </a>\n                  </Button>\n                </CardFooter>\n              </Card>\n            ))}\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n};\n\nexport default Pricing2;\n"
  },
  {
    "path": "components/examples/tasks/components/columns.tsx",
    "content": "import { ColumnDef } from \"@tanstack/react-table\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\n\nimport { labels, priorities, statuses } from \"../data/data\";\nimport { Task } from \"../data/schema\";\nimport { DataTableColumnHeader } from \"./data-table-column-header\";\nimport { DataTableRowActions } from \"./data-table-row-actions\";\n\nexport const columns: ColumnDef<Task>[] = [\n  {\n    id: \"select\",\n    header: ({ table }) => (\n      <Checkbox\n        checked={\n          table.getIsAllPageRowsSelected() ||\n          (table.getIsSomePageRowsSelected() ? \"indeterminate\" : false)\n        }\n        onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}\n        aria-label=\"Select all\"\n        className=\"translate-y-[2px]\"\n      />\n    ),\n    cell: ({ row }) => (\n      <Checkbox\n        checked={row.getIsSelected()}\n        onCheckedChange={(value) => row.toggleSelected(!!value)}\n        aria-label=\"Select row\"\n        className=\"translate-y-[2px]\"\n      />\n    ),\n    enableSorting: false,\n    enableHiding: false,\n  },\n  {\n    accessorKey: \"id\",\n    header: ({ column }) => <DataTableColumnHeader column={column} title=\"Task\" />,\n    cell: ({ row }) => <div className=\"w-[80px]\">{row.getValue(\"id\")}</div>,\n    enableSorting: false,\n    enableHiding: false,\n  },\n  {\n    accessorKey: \"title\",\n    header: ({ column }) => <DataTableColumnHeader column={column} title=\"Title\" />,\n    cell: ({ row }) => {\n      const label = labels.find((label) => label.value === row.original.label);\n\n      return (\n        <div className=\"flex space-x-2\">\n          {label && <Badge variant=\"outline\">{label.label}</Badge>}\n          <span className=\"max-w-[500px] truncate font-medium\">\n            {row.getValue(\"title\")}\n          </span>\n        </div>\n      );\n    },\n  },\n  {\n    accessorKey: \"status\",\n    header: ({ column }) => <DataTableColumnHeader column={column} title=\"Status\" />,\n    cell: ({ row }) => {\n      const status = statuses.find(\n        (status) => status.value === row.getValue(\"status\")\n      );\n\n      if (!status) {\n        return null;\n      }\n\n      return (\n        <div className=\"flex w-[100px] items-center\">\n          {status.icon && (\n            <status.icon className=\"mr-2 h-4 w-4 text-muted-foreground\" />\n          )}\n          <span>{status.label}</span>\n        </div>\n      );\n    },\n    filterFn: (row, id, value) => {\n      return value.includes(row.getValue(id));\n    },\n  },\n  {\n    accessorKey: \"priority\",\n    header: ({ column }) => (\n      <DataTableColumnHeader column={column} title=\"Priority\" />\n    ),\n    cell: ({ row }) => {\n      const priority = priorities.find(\n        (priority) => priority.value === row.getValue(\"priority\")\n      );\n\n      if (!priority) {\n        return null;\n      }\n\n      return (\n        <div className=\"flex items-center\">\n          {priority.icon && (\n            <priority.icon className=\"mr-2 h-4 w-4 text-muted-foreground\" />\n          )}\n          <span>{priority.label}</span>\n        </div>\n      );\n    },\n    filterFn: (row, id, value) => {\n      return value.includes(row.getValue(id));\n    },\n  },\n  {\n    id: \"actions\",\n    cell: ({ row }) => <DataTableRowActions row={row} />,\n  },\n];\n"
  },
  {
    "path": "components/examples/tasks/components/data-table-column-header.tsx",
    "content": "import { Column } from \"@tanstack/react-table\";\nimport { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\ninterface DataTableColumnHeaderProps<TData, TValue>\n  extends React.HTMLAttributes<HTMLDivElement> {\n  column: Column<TData, TValue>;\n  title: string;\n}\n\nexport function DataTableColumnHeader<TData, TValue>({\n  column,\n  title,\n  className,\n}: DataTableColumnHeaderProps<TData, TValue>) {\n  if (!column.getCanSort()) {\n    return <div className={cn(className)}>{title}</div>;\n  }\n\n  return (\n    <div className={cn(\"flex items-center space-x-2\", className)}>\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild>\n          <Button\n            variant=\"ghost\"\n            size=\"sm\"\n            className=\"-ml-3 h-8 data-[state=open]:bg-accent\"\n          >\n            <span>{title}</span>\n            {column.getIsSorted() === \"desc\" ? (\n              <ArrowDown />\n            ) : column.getIsSorted() === \"asc\" ? (\n              <ArrowUp />\n            ) : (\n              <ChevronsUpDown />\n            )}\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent align=\"start\">\n          <DropdownMenuItem onClick={() => column.toggleSorting(false)}>\n            <ArrowUp className=\"h-3.5 w-3.5 text-muted-foreground/70\" />\n            Asc\n          </DropdownMenuItem>\n          <DropdownMenuItem onClick={() => column.toggleSorting(true)}>\n            <ArrowDown className=\"h-3.5 w-3.5 text-muted-foreground/70\" />\n            Desc\n          </DropdownMenuItem>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>\n            <EyeOff className=\"h-3.5 w-3.5 text-muted-foreground/70\" />\n            Hide\n          </DropdownMenuItem>\n        </DropdownMenuContent>\n      </DropdownMenu>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/tasks/components/data-table-faceted-filter.tsx",
    "content": "import * as React from \"react\";\nimport { Column } from \"@tanstack/react-table\";\nimport { Check, PlusCircle } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandSeparator,\n} from \"@/components/ui/command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\n\ninterface DataTableFacetedFilterProps<TData, TValue> {\n  column?: Column<TData, TValue>;\n  title?: string;\n  options: {\n    label: string;\n    value: string;\n    icon?: React.ComponentType<{ className?: string }>;\n  }[];\n}\n\nexport function DataTableFacetedFilter<TData, TValue>({\n  column,\n  title,\n  options,\n}: DataTableFacetedFilterProps<TData, TValue>) {\n  const facets = column?.getFacetedUniqueValues();\n  const selectedValues = new Set(column?.getFilterValue() as string[]);\n\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <Button variant=\"outline\" size=\"sm\" className=\"h-8 border-dashed\">\n          <PlusCircle />\n          {title}\n          {selectedValues?.size > 0 && (\n            <>\n              <Separator orientation=\"vertical\" className=\"mx-2 h-4\" />\n              <Badge\n                variant=\"secondary\"\n                className=\"rounded-sm px-1 font-normal lg:hidden\"\n              >\n                {selectedValues.size}\n              </Badge>\n              <div className=\"hidden space-x-1 lg:flex\">\n                {selectedValues.size > 2 ? (\n                  <Badge variant=\"secondary\" className=\"rounded-sm px-1 font-normal\">\n                    {selectedValues.size} selected\n                  </Badge>\n                ) : (\n                  options\n                    .filter((option) => selectedValues.has(option.value))\n                    .map((option) => (\n                      <Badge\n                        variant=\"secondary\"\n                        key={option.value}\n                        className=\"rounded-sm px-1 font-normal\"\n                      >\n                        {option.label}\n                      </Badge>\n                    ))\n                )}\n              </div>\n            </>\n          )}\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-[200px] p-0\" align=\"start\">\n        <Command>\n          <CommandInput placeholder={title} />\n          <CommandList>\n            <CommandEmpty>No results found.</CommandEmpty>\n            <CommandGroup>\n              {options.map((option) => {\n                const isSelected = selectedValues.has(option.value);\n                return (\n                  <CommandItem\n                    key={option.value}\n                    onSelect={() => {\n                      if (isSelected) {\n                        selectedValues.delete(option.value);\n                      } else {\n                        selectedValues.add(option.value);\n                      }\n                      const filterValues = Array.from(selectedValues);\n                      column?.setFilterValue(\n                        filterValues.length ? filterValues : undefined\n                      );\n                    }}\n                  >\n                    <div\n                      className={cn(\n                        \"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary\",\n                        isSelected\n                          ? \"bg-primary text-primary-foreground\"\n                          : \"opacity-50 [&_svg]:invisible\"\n                      )}\n                    >\n                      <Check />\n                    </div>\n                    {option.icon && (\n                      <option.icon className=\"mr-2 h-4 w-4 text-muted-foreground\" />\n                    )}\n                    <span>{option.label}</span>\n                    {facets?.get(option.value) && (\n                      <span className=\"ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs\">\n                        {facets.get(option.value)}\n                      </span>\n                    )}\n                  </CommandItem>\n                );\n              })}\n            </CommandGroup>\n            {selectedValues.size > 0 && (\n              <>\n                <CommandSeparator />\n                <CommandGroup>\n                  <CommandItem\n                    onSelect={() => column?.setFilterValue(undefined)}\n                    className=\"justify-center text-center\"\n                  >\n                    Clear filters\n                  </CommandItem>\n                </CommandGroup>\n              </>\n            )}\n          </CommandList>\n        </Command>\n      </PopoverContent>\n    </Popover>\n  );\n}\n"
  },
  {
    "path": "components/examples/tasks/components/data-table-pagination.tsx",
    "content": "import { Table } from \"@tanstack/react-table\";\nimport {\n  ChevronLeft,\n  ChevronRight,\n  ChevronsLeft,\n  ChevronsRight,\n} from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\n\ninterface DataTablePaginationProps<TData> {\n  table: Table<TData>;\n}\n\nexport function DataTablePagination<TData>({\n  table,\n}: DataTablePaginationProps<TData>) {\n  return (\n    <div className=\"flex items-center justify-between px-2\">\n      <div className=\"flex-1 text-sm text-muted-foreground\">\n        {table.getFilteredSelectedRowModel().rows.length} of{\" \"}\n        {table.getFilteredRowModel().rows.length} row(s) selected.\n      </div>\n      <div className=\"flex items-center space-x-6 lg:space-x-8\">\n        <div className=\"flex items-center space-x-2\">\n          <p className=\"text-sm font-medium\">Rows per page</p>\n          <Select\n            value={`${table.getState().pagination.pageSize}`}\n            onValueChange={(value) => {\n              table.setPageSize(Number(value));\n            }}\n          >\n            <SelectTrigger size=\"sm\" className=\"w-[70px]\">\n              <SelectValue placeholder={table.getState().pagination.pageSize} />\n            </SelectTrigger>\n            <SelectContent side=\"top\">\n              {[10, 20, 30, 40, 50].map((pageSize) => (\n                <SelectItem key={pageSize} value={`${pageSize}`}>\n                  {pageSize}\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n        </div>\n        <div className=\"flex w-[100px] items-center justify-center text-sm font-medium\">\n          Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}\n        </div>\n        <div className=\"flex items-center space-x-2\">\n          <Button\n            variant=\"outline\"\n            className=\"hidden h-8 w-8 p-0 lg:flex\"\n            onClick={() => table.setPageIndex(0)}\n            disabled={!table.getCanPreviousPage()}\n          >\n            <span className=\"sr-only\">Go to first page</span>\n            <ChevronsLeft />\n          </Button>\n          <Button\n            variant=\"outline\"\n            className=\"h-8 w-8 p-0\"\n            onClick={() => table.previousPage()}\n            disabled={!table.getCanPreviousPage()}\n          >\n            <span className=\"sr-only\">Go to previous page</span>\n            <ChevronLeft />\n          </Button>\n          <Button\n            variant=\"outline\"\n            className=\"h-8 w-8 p-0\"\n            onClick={() => table.nextPage()}\n            disabled={!table.getCanNextPage()}\n          >\n            <span className=\"sr-only\">Go to next page</span>\n            <ChevronRight />\n          </Button>\n          <Button\n            variant=\"outline\"\n            className=\"hidden h-8 w-8 p-0 lg:flex\"\n            onClick={() => table.setPageIndex(table.getPageCount() - 1)}\n            disabled={!table.getCanNextPage()}\n          >\n            <span className=\"sr-only\">Go to last page</span>\n            <ChevronsRight />\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/tasks/components/data-table-row-actions.tsx",
    "content": "import { Row } from \"@tanstack/react-table\";\nimport { MoreHorizontal } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\nimport { labels } from \"../data/data\";\nimport { taskSchema } from \"../data/schema\";\n\ninterface DataTableRowActionsProps<TData> {\n  row: Row<TData>;\n}\n\nexport function DataTableRowActions<TData>({\n  row,\n}: DataTableRowActionsProps<TData>) {\n  const task = taskSchema.parse(row.original);\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button\n          variant=\"ghost\"\n          className=\"flex h-8 w-8 p-0 data-[state=open]:bg-muted\"\n        >\n          <MoreHorizontal />\n          <span className=\"sr-only\">Open menu</span>\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"w-[160px]\">\n        <DropdownMenuItem>Edit</DropdownMenuItem>\n        <DropdownMenuItem>Make a copy</DropdownMenuItem>\n        <DropdownMenuItem>Favorite</DropdownMenuItem>\n        <DropdownMenuSeparator />\n        <DropdownMenuSub>\n          <DropdownMenuSubTrigger>Labels</DropdownMenuSubTrigger>\n          <DropdownMenuSubContent>\n            <DropdownMenuRadioGroup value={task.label}>\n              {labels.map((label) => (\n                <DropdownMenuRadioItem key={label.value} value={label.value}>\n                  {label.label}\n                </DropdownMenuRadioItem>\n              ))}\n            </DropdownMenuRadioGroup>\n          </DropdownMenuSubContent>\n        </DropdownMenuSub>\n        <DropdownMenuSeparator />\n        <DropdownMenuItem className=\"text-destructive focus:text-destructive\">\n          Delete\n          <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "components/examples/tasks/components/data-table-toolbar.tsx",
    "content": "import { Table } from \"@tanstack/react-table\";\nimport { X } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { DataTableViewOptions } from \"@/components/examples/tasks/components/data-table-view-options\";\n\nimport { priorities, statuses } from \"../data/data\";\nimport { DataTableFacetedFilter } from \"./data-table-faceted-filter\";\n\ninterface DataTableToolbarProps<TData> {\n  table: Table<TData>;\n}\n\nexport function DataTableToolbar<TData>({ table }: DataTableToolbarProps<TData>) {\n  const isFiltered = table.getState().columnFilters.length > 0;\n\n  return (\n    <div className=\"flex items-center justify-between\">\n      <div className=\"flex flex-1 items-center space-x-2\">\n        <Input\n          placeholder=\"Filter tasks...\"\n          value={(table.getColumn(\"title\")?.getFilterValue() as string) ?? \"\"}\n          onChange={(event) =>\n            table.getColumn(\"title\")?.setFilterValue(event.target.value)\n          }\n          className=\"h-8 w-[150px] lg:w-[250px]\"\n        />\n        {table.getColumn(\"status\") && (\n          <DataTableFacetedFilter\n            column={table.getColumn(\"status\")}\n            title=\"Status\"\n            options={statuses}\n          />\n        )}\n        {table.getColumn(\"priority\") && (\n          <DataTableFacetedFilter\n            column={table.getColumn(\"priority\")}\n            title=\"Priority\"\n            options={priorities}\n          />\n        )}\n        {isFiltered && (\n          <Button\n            variant=\"ghost\"\n            onClick={() => table.resetColumnFilters()}\n            className=\"h-8 px-2 lg:px-3\"\n          >\n            Reset\n            <X />\n          </Button>\n        )}\n      </div>\n      <DataTableViewOptions table={table} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/tasks/components/data-table-view-options.tsx",
    "content": "import { DropdownMenuTrigger } from \"@/components/ui/dropdown-menu\";\nimport { Table } from \"@tanstack/react-table\";\nimport { Settings2 } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n} from \"@/components/ui/dropdown-menu\";\n\ninterface DataTableViewOptionsProps<TData> {\n  table: Table<TData>;\n}\n\nexport function DataTableViewOptions<TData>({\n  table,\n}: DataTableViewOptionsProps<TData>) {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button variant=\"outline\" size=\"sm\" className=\"ml-auto hidden h-8 lg:flex\">\n          <Settings2 />\n          View\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"w-[150px]\">\n        <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>\n        <DropdownMenuSeparator />\n        {table\n          .getAllColumns()\n          .filter(\n            (column) =>\n              typeof column.accessorFn !== \"undefined\" && column.getCanHide()\n          )\n          .map((column) => {\n            return (\n              <DropdownMenuCheckboxItem\n                key={column.id}\n                className=\"capitalize\"\n                checked={column.getIsVisible()}\n                onCheckedChange={(value) => column.toggleVisibility(!!value)}\n              >\n                {column.id}\n              </DropdownMenuCheckboxItem>\n            );\n          })}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "components/examples/tasks/components/data-table.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  ColumnDef,\n  ColumnFiltersState,\n  SortingState,\n  VisibilityState,\n  flexRender,\n  getCoreRowModel,\n  getFacetedRowModel,\n  getFacetedUniqueValues,\n  getFilteredRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  useReactTable,\n} from \"@tanstack/react-table\";\n\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\n\nimport { DataTablePagination } from \"./data-table-pagination\";\nimport { DataTableToolbar } from \"./data-table-toolbar\";\n\ninterface DataTableProps<TData, TValue> {\n  columns: ColumnDef<TData, TValue>[];\n  data: TData[];\n}\n\nexport function DataTable<TData, TValue>({\n  columns,\n  data,\n}: DataTableProps<TData, TValue>) {\n  const [rowSelection, setRowSelection] = React.useState({});\n  const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(\n    {}\n  );\n  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);\n  const [sorting, setSorting] = React.useState<SortingState>([]);\n\n  const table = useReactTable({\n    data,\n    columns,\n    state: {\n      sorting,\n      columnVisibility,\n      rowSelection,\n      columnFilters,\n    },\n    enableRowSelection: true,\n    onRowSelectionChange: setRowSelection,\n    onSortingChange: setSorting,\n    onColumnFiltersChange: setColumnFilters,\n    onColumnVisibilityChange: setColumnVisibility,\n    getCoreRowModel: getCoreRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFacetedRowModel: getFacetedRowModel(),\n    getFacetedUniqueValues: getFacetedUniqueValues(),\n  });\n\n  return (\n    <div className=\"space-y-4\">\n      <DataTableToolbar table={table} />\n      <div className=\"rounded-md border\">\n        <Table>\n          <TableHeader>\n            {table.getHeaderGroups().map((headerGroup) => (\n              <TableRow key={headerGroup.id}>\n                {headerGroup.headers.map((header) => {\n                  return (\n                    <TableHead key={header.id} colSpan={header.colSpan}>\n                      {header.isPlaceholder\n                        ? null\n                        : flexRender(\n                            header.column.columnDef.header,\n                            header.getContext()\n                          )}\n                    </TableHead>\n                  );\n                })}\n              </TableRow>\n            ))}\n          </TableHeader>\n          <TableBody>\n            {table.getRowModel().rows?.length ? (\n              table.getRowModel().rows.map((row) => (\n                <TableRow\n                  key={row.id}\n                  data-state={row.getIsSelected() && \"selected\"}\n                >\n                  {row.getVisibleCells().map((cell) => (\n                    <TableCell key={cell.id}>\n                      {flexRender(cell.column.columnDef.cell, cell.getContext())}\n                    </TableCell>\n                  ))}\n                </TableRow>\n              ))\n            ) : (\n              <TableRow>\n                <TableCell colSpan={columns.length} className=\"h-24 text-center\">\n                  No results.\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n        </Table>\n      </div>\n      <DataTablePagination table={table} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/examples/tasks/components/user-nav.tsx",
    "content": "import { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\nexport function UserNav() {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button variant=\"ghost\" className=\"relative h-8 w-8 rounded-full\">\n          <Avatar className=\"h-9 w-9\">\n            <AvatarImage src=\"/avatars/03.png\" alt=\"@shadcn\" />\n            <AvatarFallback>SC</AvatarFallback>\n          </Avatar>\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"w-56\" align=\"end\" forceMount>\n        <DropdownMenuLabel className=\"font-normal\">\n          <div className=\"flex flex-col space-y-1\">\n            <p className=\"text-sm font-medium leading-none\">shadcn</p>\n            <p className=\"text-xs leading-none text-muted-foreground\">\n              m@example.com\n            </p>\n          </div>\n        </DropdownMenuLabel>\n        <DropdownMenuSeparator />\n        <DropdownMenuGroup>\n          <DropdownMenuItem>\n            Profile\n            <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>\n          </DropdownMenuItem>\n          <DropdownMenuItem>\n            Billing\n            <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>\n          </DropdownMenuItem>\n          <DropdownMenuItem>\n            Settings\n            <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>\n          </DropdownMenuItem>\n          <DropdownMenuItem>New Team</DropdownMenuItem>\n        </DropdownMenuGroup>\n        <DropdownMenuSeparator />\n        <DropdownMenuItem className=\"text-destructive focus:text-destructive\">\n          Log out\n          <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "components/examples/tasks/data/data.tsx",
    "content": "import {\n  ArrowDown,\n  ArrowRight,\n  ArrowUp,\n  CheckCircle,\n  Circle,\n  CircleOff,\n  HelpCircle,\n  Timer,\n} from \"lucide-react\"\n\nexport const labels = [\n  {\n    value: \"bug\",\n    label: \"Bug\",\n  },\n  {\n    value: \"feature\",\n    label: \"Feature\",\n  },\n  {\n    value: \"documentation\",\n    label: \"Documentation\",\n  },\n]\n\nexport const statuses = [\n  {\n    value: \"backlog\",\n    label: \"Backlog\",\n    icon: HelpCircle,\n  },\n  {\n    value: \"todo\",\n    label: \"Todo\",\n    icon: Circle,\n  },\n  {\n    value: \"in progress\",\n    label: \"In Progress\",\n    icon: Timer,\n  },\n  {\n    value: \"done\",\n    label: \"Done\",\n    icon: CheckCircle,\n  },\n  {\n    value: \"canceled\",\n    label: \"Canceled\",\n    icon: CircleOff,\n  },\n]\n\nexport const priorities = [\n  {\n    label: \"Low\",\n    value: \"low\",\n    icon: ArrowDown,\n  },\n  {\n    label: \"Medium\",\n    value: \"medium\",\n    icon: ArrowRight,\n  },\n  {\n    label: \"High\",\n    value: \"high\",\n    icon: ArrowUp,\n  },\n]\n"
  },
  {
    "path": "components/examples/tasks/data/schema.ts",
    "content": "import { z } from \"zod\"\n\n// We're keeping a simple non-relational schema here.\n// IRL, you will have a schema for your data models.\nexport const taskSchema = z.object({\n  id: z.string(),\n  title: z.string(),\n  status: z.string(),\n  label: z.string(),\n  priority: z.string(),\n})\n\nexport type Task = z.infer<typeof taskSchema>\n"
  },
  {
    "path": "components/examples/tasks/data/tasks.json",
    "content": "[\n  {\n    \"id\": \"TASK-8782\",\n    \"title\": \"You can't compress the program without quantifying the open-source SSD pixel!\",\n    \"status\": \"in progress\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-7878\",\n    \"title\": \"Try to calculate the EXE feed, maybe it will index the multi-byte pixel!\",\n    \"status\": \"backlog\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-7839\",\n    \"title\": \"We need to bypass the neural TCP card!\",\n    \"status\": \"todo\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-5562\",\n    \"title\": \"The SAS interface is down, bypass the open-source pixel so we can back up the PNG bandwidth!\",\n    \"status\": \"backlog\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-8686\",\n    \"title\": \"I'll parse the wireless SSL protocol, that should driver the API panel!\",\n    \"status\": \"canceled\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-1280\",\n    \"title\": \"Use the digital TLS panel, then you can transmit the haptic system!\",\n    \"status\": \"done\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-7262\",\n    \"title\": \"The UTF8 application is down, parse the neural bandwidth so we can back up the PNG firewall!\",\n    \"status\": \"done\",\n    \"label\": \"feature\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-1138\",\n    \"title\": \"Generating the driver won't do anything, we need to quantify the 1080p SMTP bandwidth!\",\n    \"status\": \"in progress\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-7184\",\n    \"title\": \"We need to program the back-end THX pixel!\",\n    \"status\": \"todo\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-5160\",\n    \"title\": \"Calculating the bus won't do anything, we need to navigate the back-end JSON protocol!\",\n    \"status\": \"in progress\",\n    \"label\": \"documentation\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-5618\",\n    \"title\": \"Generating the driver won't do anything, we need to index the online SSL application!\",\n    \"status\": \"done\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-6699\",\n    \"title\": \"I'll transmit the wireless JBOD capacitor, that should hard drive the SSD feed!\",\n    \"status\": \"backlog\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-2858\",\n    \"title\": \"We need to override the online UDP bus!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-9864\",\n    \"title\": \"I'll reboot the 1080p FTP panel, that should matrix the HEX hard drive!\",\n    \"status\": \"done\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-8404\",\n    \"title\": \"We need to generate the virtual HEX alarm!\",\n    \"status\": \"in progress\",\n    \"label\": \"bug\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-5365\",\n    \"title\": \"Backing up the pixel won't do anything, we need to transmit the primary IB array!\",\n    \"status\": \"in progress\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-1780\",\n    \"title\": \"The CSS feed is down, index the bluetooth transmitter so we can compress the CLI protocol!\",\n    \"status\": \"todo\",\n    \"label\": \"documentation\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-6938\",\n    \"title\": \"Use the redundant SCSI application, then you can hack the optical alarm!\",\n    \"status\": \"todo\",\n    \"label\": \"documentation\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-9885\",\n    \"title\": \"We need to compress the auxiliary VGA driver!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-3216\",\n    \"title\": \"Transmitting the transmitter won't do anything, we need to compress the virtual HDD sensor!\",\n    \"status\": \"backlog\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-9285\",\n    \"title\": \"The IP monitor is down, copy the haptic alarm so we can generate the HTTP transmitter!\",\n    \"status\": \"todo\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-1024\",\n    \"title\": \"Overriding the microchip won't do anything, we need to transmit the digital OCR transmitter!\",\n    \"status\": \"in progress\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-7068\",\n    \"title\": \"You can't generate the capacitor without indexing the wireless HEX pixel!\",\n    \"status\": \"canceled\",\n    \"label\": \"bug\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-6502\",\n    \"title\": \"Navigating the microchip won't do anything, we need to bypass the back-end SQL bus!\",\n    \"status\": \"todo\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-5326\",\n    \"title\": \"We need to hack the redundant UTF8 transmitter!\",\n    \"status\": \"todo\",\n    \"label\": \"bug\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-6274\",\n    \"title\": \"Use the virtual PCI circuit, then you can parse the bluetooth alarm!\",\n    \"status\": \"canceled\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-1571\",\n    \"title\": \"I'll input the neural DRAM circuit, that should protocol the SMTP interface!\",\n    \"status\": \"in progress\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-9518\",\n    \"title\": \"Compressing the interface won't do anything, we need to compress the online SDD matrix!\",\n    \"status\": \"canceled\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-5581\",\n    \"title\": \"I'll synthesize the digital COM pixel, that should transmitter the UTF8 protocol!\",\n    \"status\": \"backlog\",\n    \"label\": \"documentation\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-2197\",\n    \"title\": \"Parsing the feed won't do anything, we need to copy the bluetooth DRAM bus!\",\n    \"status\": \"todo\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-8484\",\n    \"title\": \"We need to parse the solid state UDP firewall!\",\n    \"status\": \"in progress\",\n    \"label\": \"bug\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-9892\",\n    \"title\": \"If we back up the application, we can get to the UDP application through the multi-byte THX capacitor!\",\n    \"status\": \"done\",\n    \"label\": \"documentation\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-9616\",\n    \"title\": \"We need to synthesize the cross-platform ASCII pixel!\",\n    \"status\": \"in progress\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-9744\",\n    \"title\": \"Use the back-end IP card, then you can input the solid state hard drive!\",\n    \"status\": \"done\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-1376\",\n    \"title\": \"Generating the alarm won't do anything, we need to generate the mobile IP capacitor!\",\n    \"status\": \"backlog\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-7382\",\n    \"title\": \"If we back up the firewall, we can get to the RAM alarm through the primary UTF8 pixel!\",\n    \"status\": \"todo\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-2290\",\n    \"title\": \"I'll compress the virtual JSON panel, that should application the UTF8 bus!\",\n    \"status\": \"canceled\",\n    \"label\": \"documentation\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-1533\",\n    \"title\": \"You can't input the firewall without overriding the wireless TCP firewall!\",\n    \"status\": \"done\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-4920\",\n    \"title\": \"Bypassing the hard drive won't do anything, we need to input the bluetooth JSON program!\",\n    \"status\": \"in progress\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-5168\",\n    \"title\": \"If we synthesize the bus, we can get to the IP panel through the virtual TLS array!\",\n    \"status\": \"in progress\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-7103\",\n    \"title\": \"We need to parse the multi-byte EXE bandwidth!\",\n    \"status\": \"canceled\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-4314\",\n    \"title\": \"If we compress the program, we can get to the XML alarm through the multi-byte COM matrix!\",\n    \"status\": \"in progress\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-3415\",\n    \"title\": \"Use the cross-platform XML application, then you can quantify the solid state feed!\",\n    \"status\": \"todo\",\n    \"label\": \"feature\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-8339\",\n    \"title\": \"Try to calculate the DNS interface, maybe it will input the bluetooth capacitor!\",\n    \"status\": \"in progress\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-6995\",\n    \"title\": \"Try to hack the XSS bandwidth, maybe it will override the bluetooth matrix!\",\n    \"status\": \"todo\",\n    \"label\": \"feature\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-8053\",\n    \"title\": \"If we connect the program, we can get to the UTF8 matrix through the digital UDP protocol!\",\n    \"status\": \"todo\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-4336\",\n    \"title\": \"If we synthesize the microchip, we can get to the SAS sensor through the optical UDP program!\",\n    \"status\": \"todo\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-8790\",\n    \"title\": \"I'll back up the optical COM alarm, that should alarm the RSS capacitor!\",\n    \"status\": \"done\",\n    \"label\": \"bug\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-8980\",\n    \"title\": \"Try to navigate the SQL transmitter, maybe it will back up the virtual firewall!\",\n    \"status\": \"canceled\",\n    \"label\": \"bug\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-7342\",\n    \"title\": \"Use the neural CLI card, then you can parse the online port!\",\n    \"status\": \"backlog\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-5608\",\n    \"title\": \"I'll hack the haptic SSL program, that should bus the UDP transmitter!\",\n    \"status\": \"canceled\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-1606\",\n    \"title\": \"I'll generate the bluetooth PNG firewall, that should pixel the SSL driver!\",\n    \"status\": \"done\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-7872\",\n    \"title\": \"Transmitting the circuit won't do anything, we need to reboot the 1080p RSS monitor!\",\n    \"status\": \"canceled\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-4167\",\n    \"title\": \"Use the cross-platform SMS circuit, then you can synthesize the optical feed!\",\n    \"status\": \"canceled\",\n    \"label\": \"bug\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-9581\",\n    \"title\": \"You can't index the port without hacking the cross-platform XSS monitor!\",\n    \"status\": \"backlog\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-8806\",\n    \"title\": \"We need to bypass the back-end SSL panel!\",\n    \"status\": \"done\",\n    \"label\": \"bug\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-6542\",\n    \"title\": \"Try to quantify the RSS firewall, maybe it will quantify the open-source system!\",\n    \"status\": \"done\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-6806\",\n    \"title\": \"The VGA protocol is down, reboot the back-end matrix so we can parse the CSS panel!\",\n    \"status\": \"canceled\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-9549\",\n    \"title\": \"You can't bypass the bus without connecting the neural JBOD bus!\",\n    \"status\": \"todo\",\n    \"label\": \"feature\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-1075\",\n    \"title\": \"Backing up the driver won't do anything, we need to parse the redundant RAM pixel!\",\n    \"status\": \"done\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-1427\",\n    \"title\": \"Use the auxiliary PCI circuit, then you can calculate the cross-platform interface!\",\n    \"status\": \"done\",\n    \"label\": \"documentation\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-1907\",\n    \"title\": \"Hacking the circuit won't do anything, we need to back up the online DRAM system!\",\n    \"status\": \"todo\",\n    \"label\": \"documentation\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-4309\",\n    \"title\": \"If we generate the system, we can get to the TCP sensor through the optical GB pixel!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-3973\",\n    \"title\": \"I'll parse the back-end ADP array, that should bandwidth the RSS bandwidth!\",\n    \"status\": \"todo\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-7962\",\n    \"title\": \"Use the wireless RAM program, then you can hack the cross-platform feed!\",\n    \"status\": \"canceled\",\n    \"label\": \"bug\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-3360\",\n    \"title\": \"You can't quantify the program without synthesizing the neural OCR interface!\",\n    \"status\": \"done\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-9887\",\n    \"title\": \"Use the auxiliary ASCII sensor, then you can connect the solid state port!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-3649\",\n    \"title\": \"I'll input the virtual USB system, that should circuit the DNS monitor!\",\n    \"status\": \"in progress\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-3586\",\n    \"title\": \"If we quantify the circuit, we can get to the CLI feed through the mobile SMS hard drive!\",\n    \"status\": \"in progress\",\n    \"label\": \"bug\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-5150\",\n    \"title\": \"I'll hack the wireless XSS port, that should transmitter the IP interface!\",\n    \"status\": \"canceled\",\n    \"label\": \"feature\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-3652\",\n    \"title\": \"The SQL interface is down, override the optical bus so we can program the ASCII interface!\",\n    \"status\": \"backlog\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-6884\",\n    \"title\": \"Use the digital PCI circuit, then you can synthesize the multi-byte microchip!\",\n    \"status\": \"canceled\",\n    \"label\": \"feature\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-1591\",\n    \"title\": \"We need to connect the mobile XSS driver!\",\n    \"status\": \"in progress\",\n    \"label\": \"feature\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-3802\",\n    \"title\": \"Try to override the ASCII protocol, maybe it will parse the virtual matrix!\",\n    \"status\": \"in progress\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-7253\",\n    \"title\": \"Programming the capacitor won't do anything, we need to bypass the neural IB hard drive!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-9739\",\n    \"title\": \"We need to hack the multi-byte HDD bus!\",\n    \"status\": \"done\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-4424\",\n    \"title\": \"Try to hack the HEX alarm, maybe it will connect the optical pixel!\",\n    \"status\": \"in progress\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-3922\",\n    \"title\": \"You can't back up the capacitor without generating the wireless PCI program!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-4921\",\n    \"title\": \"I'll index the open-source IP feed, that should system the GB application!\",\n    \"status\": \"canceled\",\n    \"label\": \"bug\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-5814\",\n    \"title\": \"We need to calculate the 1080p AGP feed!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-2645\",\n    \"title\": \"Synthesizing the system won't do anything, we need to navigate the multi-byte HDD firewall!\",\n    \"status\": \"todo\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-4535\",\n    \"title\": \"Try to copy the JSON circuit, maybe it will connect the wireless feed!\",\n    \"status\": \"in progress\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-4463\",\n    \"title\": \"We need to copy the solid state AGP monitor!\",\n    \"status\": \"done\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-9745\",\n    \"title\": \"If we connect the protocol, we can get to the GB system through the bluetooth PCI microchip!\",\n    \"status\": \"canceled\",\n    \"label\": \"feature\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-2080\",\n    \"title\": \"If we input the bus, we can get to the RAM matrix through the auxiliary RAM card!\",\n    \"status\": \"todo\",\n    \"label\": \"bug\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-3838\",\n    \"title\": \"I'll bypass the online TCP application, that should panel the AGP system!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-1340\",\n    \"title\": \"We need to navigate the virtual PNG circuit!\",\n    \"status\": \"todo\",\n    \"label\": \"bug\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-6665\",\n    \"title\": \"If we parse the monitor, we can get to the SSD hard drive through the cross-platform AGP alarm!\",\n    \"status\": \"canceled\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-7585\",\n    \"title\": \"If we calculate the hard drive, we can get to the SSL program through the multi-byte CSS microchip!\",\n    \"status\": \"backlog\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-6319\",\n    \"title\": \"We need to copy the multi-byte SCSI program!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-4369\",\n    \"title\": \"Try to input the SCSI bus, maybe it will generate the 1080p pixel!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-9035\",\n    \"title\": \"We need to override the solid state PNG array!\",\n    \"status\": \"canceled\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-3970\",\n    \"title\": \"You can't index the transmitter without quantifying the haptic ASCII card!\",\n    \"status\": \"todo\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-4473\",\n    \"title\": \"You can't bypass the protocol without overriding the neural RSS program!\",\n    \"status\": \"todo\",\n    \"label\": \"documentation\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-4136\",\n    \"title\": \"You can't hack the hard drive without hacking the primary JSON program!\",\n    \"status\": \"canceled\",\n    \"label\": \"bug\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-3939\",\n    \"title\": \"Use the back-end SQL firewall, then you can connect the neural hard drive!\",\n    \"status\": \"done\",\n    \"label\": \"feature\",\n    \"priority\": \"low\"\n  },\n  {\n    \"id\": \"TASK-2007\",\n    \"title\": \"I'll input the back-end USB protocol, that should bandwidth the PCI system!\",\n    \"status\": \"backlog\",\n    \"label\": \"bug\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-7516\",\n    \"title\": \"Use the primary SQL program, then you can generate the auxiliary transmitter!\",\n    \"status\": \"done\",\n    \"label\": \"documentation\",\n    \"priority\": \"medium\"\n  },\n  {\n    \"id\": \"TASK-6906\",\n    \"title\": \"Try to back up the DRAM system, maybe it will reboot the online transmitter!\",\n    \"status\": \"done\",\n    \"label\": \"feature\",\n    \"priority\": \"high\"\n  },\n  {\n    \"id\": \"TASK-5207\",\n    \"title\": \"The SMS interface is down, copy the bluetooth bus so we can quantify the VGA card!\",\n    \"status\": \"in progress\",\n    \"label\": \"bug\",\n    \"priority\": \"low\"\n  }\n]"
  },
  {
    "path": "components/examples/tasks/index.tsx",
    "content": "import { z } from \"zod\";\n\nimport { columns } from \"./components/columns\";\nimport { DataTable } from \"./components/data-table\";\nimport { UserNav } from \"./components/user-nav\";\nimport { taskSchema } from \"./data/schema\";\nimport tasks from \"./data/tasks.json\";\n\n// Simulate a database read for tasks.\nfunction getTasks() {\n  return z.array(taskSchema).parse(tasks);\n}\n\nexport default function TaskPage() {\n  const tasks = getTasks();\n\n  return (\n    <>\n      <div className=\"hidden h-full flex-1 flex-col space-y-8 p-8 md:flex\">\n        <div className=\"flex items-center justify-between space-y-2\">\n          <div>\n            <h2 className=\"text-2xl font-bold tracking-tight\">Welcome back!</h2>\n            <p className=\"text-muted-foreground\">\n              Here&apos;s a list of your tasks for this month!\n            </p>\n          </div>\n          <div className=\"flex items-center space-x-2\">\n            <UserNav />\n          </div>\n        </div>\n        <DataTable data={tasks} columns={columns} />\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "components/examples/typography/blog-post.tsx",
    "content": "import { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Bookmark, Calendar, Clock, Heart, MessageCircle, Share2 } from \"lucide-react\";\nimport Image from \"next/image\";\n\nexport function BlogPost() {\n  return (\n    <Card className=\"bg-background flex min-h-screen flex-col overflow-hidden\">\n      <div className=\"bg-card flex items-center justify-between border-b px-4 py-2\">\n        <div className=\"flex items-center gap-3\">\n          <div className=\"flex gap-2\">\n            <div className=\"size-3 rounded-full bg-red-500\"></div>\n            <div className=\"size-3 rounded-full bg-yellow-500\"></div>\n            <div className=\"size-3 rounded-full bg-green-500\"></div>\n          </div>\n        </div>\n      </div>\n      <div className=\"@container container mx-auto max-w-4xl px-4 py-8\">\n        <article className=\"space-y-8\">\n          {/* Header */}\n          <header className=\"space-y-6\">\n            <div className=\"flex flex-wrap gap-2\">\n              <Badge variant=\"secondary\">Technology</Badge>\n              <Badge variant=\"outline\">Web Development</Badge>\n              <Badge variant=\"outline\">React</Badge>\n            </div>\n\n            <h1 className=\"text-4xl leading-tight font-bold tracking-tight @md:text-5xl @lg:text-6xl\">\n              The Future of Web Development: Embracing Modern Technologies\n            </h1>\n\n            <p className=\"text-muted-foreground text-xl leading-relaxed\">\n              Discover how cutting-edge technologies are reshaping the landscape of web development,\n              from AI-powered tools to revolutionary frameworks that are changing how we build for\n              the web.\n            </p>\n\n            {/* Author and Meta */}\n            <div className=\"flex flex-col justify-between gap-4 pt-4 @sm:flex-row @sm:items-center\">\n              <div className=\"flex items-center gap-3\">\n                <Avatar className=\"h-12 w-12\">\n                  <AvatarImage src=\"/placeholder.svg?height=48&width=48\" alt=\"Author\" />\n                  <AvatarFallback>JD</AvatarFallback>\n                </Avatar>\n                <div>\n                  <p className=\"font-semibold\">Jane Doe</p>\n                  <p className=\"text-muted-foreground text-sm\">Senior Developer</p>\n                </div>\n              </div>\n\n              <div className=\"text-muted-foreground flex items-center gap-4 text-sm\">\n                <div className=\"flex items-center gap-1\">\n                  <Calendar className=\"h-4 w-4\" />\n                  <span>Dec 15, 2024</span>\n                </div>\n                <div className=\"flex items-center gap-1\">\n                  <Clock className=\"h-4 w-4\" />\n                  <span>8 min read</span>\n                </div>\n              </div>\n            </div>\n          </header>\n\n          <Separator />\n\n          {/* Featured Image */}\n          <div className=\"relative aspect-video overflow-hidden rounded-lg\">\n            <Image\n              src=\"/placeholder.svg?height=600&width=1200\"\n              alt=\"Featured image\"\n              fill\n              className=\"object-cover\"\n            />\n          </div>\n\n          {/* Content */}\n          <div className=\"prose prose-gray dark:prose-invert max-w-none\">\n            <p className=\"text-lg leading-relaxed\">\n              Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor\n              incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud\n              exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n            </p>\n\n            <h2 className=\"mt-8 mb-4 text-2xl font-bold\">The Evolution of Modern Frameworks</h2>\n\n            <p>\n              Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui\n              officia deserunt mollit anim id est laborum.\n            </p>\n\n            <blockquote className=\"border-primary my-6 border-l-4 pl-6 text-lg italic\">\n              &quot;The best way to predict the future is to create it. In web development,\n              we&apos;re not just following trends—we&apos;re setting them.&quot;\n            </blockquote>\n\n            <h3 className=\"mt-6 mb-3 text-xl font-semibold\">Key Technologies Shaping the Future</h3>\n\n            <ul className=\"my-4 space-y-2\">\n              <li>Artificial Intelligence and Machine Learning integration</li>\n              <li>Edge computing and serverless architectures</li>\n              <li>Progressive Web Applications (PWAs)</li>\n              <li>WebAssembly for high-performance applications</li>\n              <li>Advanced CSS features and container queries</li>\n            </ul>\n\n            <p>\n              Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque\n              laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi\n              architecto beatae vitae dicta sunt explicabo.\n            </p>\n\n            <div className=\"my-8\">\n              <Card>\n                <CardContent className=\"p-6\">\n                  <h4 className=\"mb-2 font-semibold\">💡 Pro Tip</h4>\n                  <p className=\"text-muted-foreground text-sm\">\n                    Always stay updated with the latest web standards and best practices. The web\n                    development landscape evolves rapidly, and continuous learning is key to staying\n                    relevant.\n                  </p>\n                </CardContent>\n              </Card>\n            </div>\n\n            <h2 className=\"mt-8 mb-4 text-2xl font-bold\">Looking Ahead</h2>\n\n            <p>\n              Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia\n              consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro\n              quisquam est, qui dolorem ipsum quia dolor sit amet.\n            </p>\n\n            <p>\n              At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium\n              voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint\n              occaecati cupiditate non provident.\n            </p>\n          </div>\n\n          <Separator />\n\n          {/* Engagement Actions */}\n          <div className=\"flex flex-col items-start justify-between gap-4 @sm:flex-row @sm:items-center\">\n            <div className=\"flex items-center gap-2\">\n              <Button variant=\"ghost\" size=\"sm\" className=\"gap-2\">\n                <Heart className=\"h-4 w-4\" />\n                <span>42</span>\n              </Button>\n              <Button variant=\"ghost\" size=\"sm\" className=\"gap-2\">\n                <MessageCircle className=\"h-4 w-4\" />\n                <span>12</span>\n              </Button>\n              <Button variant=\"ghost\" size=\"sm\" className=\"gap-2\">\n                <Share2 className=\"h-4 w-4\" />\n                <span>Share</span>\n              </Button>\n            </div>\n\n            <Button variant=\"outline\" size=\"sm\" className=\"gap-2\">\n              <Bookmark className=\"h-4 w-4\" />\n              <span>Save for later</span>\n            </Button>\n          </div>\n\n          {/* Author Bio */}\n          <Card>\n            <CardContent className=\"p-6\">\n              <div className=\"flex flex-col gap-4 @sm:flex-row\">\n                <Avatar className=\"h-16 w-16\">\n                  <AvatarImage src=\"/placeholder.svg?height=64&width=64\" alt=\"Author\" />\n                  <AvatarFallback>JD</AvatarFallback>\n                </Avatar>\n                <div className=\"flex-1\">\n                  <h3 className=\"text-lg font-semibold\">Jane Doe</h3>\n                  <p className=\"text-muted-foreground mb-2\">Senior Developer & Tech Writer</p>\n                  <p className=\"text-sm\">\n                    Jane is a passionate developer with over 8 years of experience in web\n                    development. She specializes in React, TypeScript, and modern web technologies.\n                    When she&apos;s not coding, you can find her writing about the latest trends in\n                    tech.\n                  </p>\n                </div>\n                <Button variant=\"outline\" size=\"sm\" className=\"self-start\">\n                  Follow\n                </Button>\n              </div>\n            </CardContent>\n          </Card>\n        </article>\n      </div>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/typography/font-showcase.tsx",
    "content": "import { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\n\nexport function DemoFontShowcase() {\n  return (\n    <Card className=\"flex flex-1 flex-col\">\n      <CardHeader>\n        <CardTitle>Font Showcase</CardTitle>\n        <CardDescription>View theme fonts in different styles</CardDescription>\n      </CardHeader>\n      <CardContent className=\"flex-coloverflow-hidden flex flex-1 px-0\">\n        <ScrollArea className=\"flex size-full max-h-100 flex-1 flex-col px-6\">\n          <div className=\"space-y-4\">\n            <div>\n              <h3 className=\"text-muted-foreground mb-2 text-lg font-semibold\">Sans-Serif</h3>\n              <div className=\"space-y-1 font-sans\">\n                <div className=\"text-normal line-clamp-1 font-light\">Light Weight Text</div>\n                <div className=\"text-normal line-clamp-1\">Regular Weight Text</div>\n                <div className=\"text-normal line-clamp-1 font-medium\">Medium Weight Text</div>\n                <div className=\"text-normal line-clamp-1 font-semibold\">Semibold Weight Text</div>\n                <div className=\"text-normal line-clamp-1 font-bold\">Bold Weight Text</div>\n              </div>\n            </div>\n\n            <div>\n              <h3 className=\"text-muted-foreground mb-2 text-lg font-semibold\">Serif</h3>\n              <div className=\"space-y-1 font-serif\">\n                <div className=\"text-normal line-clamp-1 font-light\">Light Weight Text</div>\n                <div className=\"text-normal line-clamp-1\">Regular Weight Text</div>\n                <div className=\"text-normal line-clamp-1 font-medium\">Medium Weight Text</div>\n                <div className=\"text-normal line-clamp-1 font-semibold\">Semibold Weight Text</div>\n                <div className=\"text-normal line-clamp-1 font-bold\">Bold Weight Text</div>\n              </div>\n            </div>\n\n            <div>\n              <h3 className=\"text-muted-foreground mb-2 text-lg font-semibold\">Monospace</h3>\n              <div className=\"space-y-1 font-mono\">\n                <div className=\"text-normal line-clamp-1 font-light\">Light Weight Text</div>\n                <div className=\"text-normal line-clamp-1\">Regular Weight Text</div>\n                <div className=\"text-normal line-clamp-1 font-medium\">Medium Weight Text</div>\n                <div className=\"text-normal line-clamp-1 font-semibold\">Semibold Weight Text</div>\n                <div className=\"text-normal line-clamp-1 font-bold\">Bold Weight Text</div>\n              </div>\n            </div>\n          </div>\n        </ScrollArea>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "components/examples/typography/typography-demo.tsx",
    "content": "import { BlogPost } from \"./blog-post\";\nimport { DemoFontShowcase } from \"./font-showcase\";\n\nexport default function TypographyDemo() {\n  return (\n    <div className=\"@container relative grid grid-cols-9 gap-4 p-4\">\n      <div className=\"sticky top-4 hidden size-full max-h-140 overflow-hidden lg:col-span-3 lg:block\">\n        <DemoFontShowcase />\n      </div>\n      <div className=\"col-span-9 lg:col-span-6\">\n        <BlogPost />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/figma-export-dialog.tsx",
    "content": "\"use client\";\n\nimport FigmaIcon from \"@/assets/figma.svg\";\nimport Logo from \"@/assets/logo.svg\";\nimport Shadcraft from \"@/assets/shadcraft.svg\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n} from \"@/components/ui/revola\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { FIGMA_CONSTANTS, redirectToShadcraft } from \"@/lib/figma-constants\";\nimport { ArrowUpRight, Cable, Check, Figma, Paintbrush, X } from \"lucide-react\";\nimport Link from \"next/link\";\ninterface FigmaExportDialogProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n}\n\nexport function FigmaExportDialog({ open, onOpenChange }: FigmaExportDialogProps) {\n  const steps = FIGMA_CONSTANTS.steps.map((step, index) => ({\n    ...step,\n    icon:\n      index === 0 ? (\n        <Figma className=\"h-6 w-6\" />\n      ) : index === 1 ? (\n        <Cable className=\"h-6 w-6\" />\n      ) : (\n        <Paintbrush className=\"h-6 w-6\" />\n      ),\n  }));\n\n  const handleGetStarted = () => {\n    redirectToShadcraft();\n  };\n\n  return (\n    <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n      <ResponsiveDialogContent className=\"flex max-h-[90%] flex-col overflow-hidden sm:max-h-[min(700px,90dvh)] sm:max-w-150\">\n        {/* Header */}\n        <ScrollArea className=\"flex h-full flex-col gap-4 overflow-hidden\">\n          <ResponsiveDialogHeader className=\"sr-only\">\n            <ResponsiveDialogTitle>Figma Export</ResponsiveDialogTitle>\n            <ResponsiveDialogDescription>\n              Apply your theme to the ultimate Figma UI kit\n            </ResponsiveDialogDescription>\n          </ResponsiveDialogHeader>\n\n          <div className=\"p-8 py-5 sm:pt-8\">\n            <div className=\"flex items-center justify-center gap-2\">\n              <div className=\"flex items-center gap-2\">\n                <Logo className=\"h-6 w-6\" />\n                <div className=\"text-lg font-bold\">tweakcn</div>\n              </div>\n              <X className=\"h-4 w-4\" />\n              <Link href={FIGMA_CONSTANTS.shadcraftUrl} target=\"_blank\">\n                <div className=\"flex items-center gap-2\">\n                  <Shadcraft className=\"h-6 w-6\" />\n                  <div className=\"text-lg font-bold\">shadcraft</div>\n                </div>\n              </Link>\n            </div>\n          </div>\n\n          <div className=\"mt-4 space-y-16 px-8 pb-32\">\n            {/* Hero Section */}\n            <div className=\"space-y-6 text-center\">\n              <h1 className=\"text-5xl leading-12 font-semibold tracking-tight\">\n                Apply your theme to the ultimate Figma UI kit\n              </h1>\n              <div className=\"flex justify-center gap-3.5\">\n                <Button size=\"lg\" className=\"h-10 px-8\" onClick={handleGetStarted}>\n                  Get started\n                </Button>\n                <Link href={FIGMA_CONSTANTS.previewUrl} target=\"_blank\">\n                  <Button variant=\"outline\" size=\"lg\" className=\"h-10 gap-2 px-8\">\n                    <FigmaIcon className=\"h-4 w-4\" />\n                    Preview\n                  </Button>\n                </Link>\n              </div>\n              <div className=\"space-y-1.5 pt-1\">\n                <p className=\"text-muted-foreground text-sm\">Trusted by top designers</p>\n                <div className=\"flex justify-center -space-x-3\">\n                  {FIGMA_CONSTANTS.designers.map((designer, index) => (\n                    <Avatar key={index} className=\"border-background h-8 w-8 border-2\">\n                      <AvatarImage src={designer.avatar} alt={designer.name} />\n                      <AvatarFallback className=\"text-xs\">{designer.fallback}</AvatarFallback>\n                    </Avatar>\n                  ))}\n                </div>\n              </div>\n            </div>\n            {/* How it works */}\n            <div className=\"space-y-4\">\n              <h2 className=\"text-center text-2xl font-semibold\">How it works</h2>\n              <div className=\"border-border rounded-2xl border px-6\">\n                <div className=\"divide-border grid grid-cols-3 divide-x\">\n                  {steps.map((step, index) => (\n                    <div\n                      key={index}\n                      className=\"space-y-2 px-6 py-6 text-center first:pl-0 last:pr-0\"\n                    >\n                      <div className=\"text-foreground mb-2 flex justify-center\">{step.icon}</div>\n                      <p className=\"text-muted-foreground text-sm\">{step.step}</p>\n                      <h3 className=\"font-semibold\">{step.title}</h3>\n                    </div>\n                  ))}\n                </div>\n              </div>\n            </div>\n            {/* Feature Description */}\n            <div className=\"space-y-6 text-center\">\n              <div className=\"mx-auto max-w-sm space-y-1.5\">\n                <h2 className=\"text-2xl font-semibold\">\n                  Top quality Figma UI kit for professionals\n                </h2>\n                <p className=\"text-muted-foreground\">\n                  Shadcraft is packed with top quality components, true to the shadcn/ui ethos.\n                </p>\n              </div>\n              {/* Demo UI Preview */}\n              <div className=\"border-border relative overflow-hidden rounded-2xl border\">\n                <img\n                  src=\"/figma-onboarding/shadcraft-preview.jpg\"\n                  alt=\"Shadcraft Figma UI Kit Preview\"\n                  className=\"h-auto w-full\"\n                />\n              </div>\n              <Link href={FIGMA_CONSTANTS.shadcraftUrl} target=\"_blank\">\n                <Button variant=\"link\" className=\"gap-1 text-sm\">\n                  More on Shadcraft\n                  <ArrowUpRight className=\"h-3 w-3\" />\n                </Button>\n              </Link>\n            </div>\n            {/* Pricing */}\n            <div className=\"space-y-6\">\n              <h2 className=\"text-center text-2xl font-semibold\">Pricing</h2>\n              <Card className=\"p-6\">\n                <div className=\"grid gap-7 md:grid-cols-2\">\n                  <div className=\"space-y-4\">\n                    <h3 className=\"font-semibold\">What you get with Shadcraft</h3>\n                    <div className=\"space-y-2\">\n                      {FIGMA_CONSTANTS.features.map((feature, index) => (\n                        <div key={index} className=\"flex items-center gap-1.5\">\n                          <Check className=\"h-4 w-4 text-green-600\" />\n                          <span className=\"text-sm\">{feature}</span>\n                        </div>\n                      ))}\n                    </div>\n                  </div>\n                  <div className=\"space-y-4\">\n                    <div className=\"mt-6 space-y-1.5\">\n                      <div className=\"flex items-end gap-1\">\n                        <span className=\"text-5xl font-semibold\">$119</span>\n                      </div>\n                    </div>\n                    <div className=\"flex gap-3\">\n                      <Button className=\"flex-1\" onClick={handleGetStarted}>\n                        Get started\n                      </Button>\n                      <Link href={FIGMA_CONSTANTS.previewUrl} target=\"_blank\">\n                        <Button variant=\"outline\" className=\"gap-2\">\n                          <FigmaIcon className=\"h-4 w-4\" />\n                          Preview\n                        </Button>\n                      </Link>\n                    </div>\n                  </div>\n                </div>\n              </Card>\n              <p className=\"text-muted-foreground text-center text-xs\">Prices in USD</p>\n            </div>\n          </div>\n        </ScrollArea>\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n}\n"
  },
  {
    "path": "components/figma-header.tsx",
    "content": "\"use client\";\n\nimport GitHubIcon from \"@/assets/github.svg\";\nimport Logo from \"@/assets/logo.svg\";\nimport { Button } from \"@/components/ui/button\";\nimport { useGithubStars } from \"@/hooks/use-github-stars\";\nimport { cn } from \"@/lib/utils\";\nimport { formatCompactNumber } from \"@/utils/format\";\nimport { Menu, X } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport Link from \"next/link\";\nimport { ThemeToggle } from \"./theme-toggle\";\n\ninterface FigmaHeaderProps {\n  isScrolled: boolean;\n  mobileMenuOpen: boolean;\n  setMobileMenuOpen: (open: boolean) => void;\n}\n\nexport function FigmaHeader({ isScrolled, mobileMenuOpen, setMobileMenuOpen }: FigmaHeaderProps) {\n  const { stargazersCount } = useGithubStars(\"jnsahaj\", \"tweakcn\");\n\n  return (\n    <header\n      className={cn(\n        \"sticky top-0 z-50 w-full backdrop-blur-lg\",\n        isScrolled ? \"bg-background/90 border-border/20 border-b shadow-xs\" : \"bg-transparent\"\n      )}\n    >\n      <div className=\"container mx-auto flex h-16 items-center justify-between px-4 md:px-6\">\n        <Link href=\"/\">\n          <div className=\"flex items-center gap-2 font-bold\">\n            <Logo className=\"size-6\" />\n            <span className=\"hidden lg:block\">tweakcn</span>\n          </div>\n        </Link>\n\n        <div className=\"hidden cursor-pointer items-center gap-4 md:flex\">\n          <motion.div\n            initial={{ opacity: 0, scale: 0.9 }}\n            animate={{ opacity: 1, scale: 1 }}\n            transition={{ duration: 0.3, delay: 0.2 }}\n          >\n            <Button variant=\"ghost\" asChild>\n              <a\n                href=\"https://github.com/jnsahaj/tweakcn\"\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                className=\"font-semibold\"\n              >\n                <GitHubIcon className=\"size-5\" />\n                {stargazersCount > 0 && formatCompactNumber(stargazersCount)}\n              </a>\n            </Button>\n          </motion.div>\n\n          <motion.div\n            initial={{ opacity: 0, scale: 0.9 }}\n            animate={{ opacity: 1, scale: 1 }}\n            transition={{ duration: 0.3, delay: 0.3 }}\n          >\n            <ThemeToggle\n              variant=\"secondary\"\n              size=\"icon\"\n              className=\"rounded-full transition-transform hover:scale-105\"\n            />\n          </motion.div>\n        </div>\n\n        <div className=\"flex items-center gap-2 md:hidden\">\n          <ThemeToggle variant=\"ghost\" size=\"icon\" />\n          <Button variant=\"ghost\" size=\"icon\" onClick={() => setMobileMenuOpen(!mobileMenuOpen)}>\n            {mobileMenuOpen ? <X className=\"size-5\" /> : <Menu className=\"size-5\" />}\n            <span className=\"sr-only\">Toggle menu</span>\n          </Button>\n        </div>\n      </div>\n\n      {/* Mobile menu - simplified */}\n      {mobileMenuOpen && (\n        <motion.div\n          initial={{ opacity: 0, y: -20 }}\n          animate={{ opacity: 1, y: 0 }}\n          exit={{ opacity: 0, y: -20 }}\n          className=\"bg-background/95 absolute inset-x-0 top-16 border-b backdrop-blur-lg md:hidden\"\n        >\n          <div className=\"container mx-auto flex flex-col gap-4 px-4 py-4\">\n            <motion.div\n              initial={{ opacity: 0, y: 10 }}\n              animate={{ opacity: 1, y: 0 }}\n              transition={{ duration: 0.3, delay: 0.1 }}\n              className=\"border-border/30 border-t pt-2\"\n            >\n              <Button variant=\"ghost\" asChild className=\"w-full justify-start\">\n                <a\n                  href=\"https://github.com/jnsahaj/tweakcn\"\n                  target=\"_blank\"\n                  rel=\"noopener noreferrer\"\n                  onClick={() => setMobileMenuOpen(false)}\n                >\n                  <GitHubIcon className=\"mr-2 size-5\" />\n                  GitHub {stargazersCount > 0 && `(${formatCompactNumber(stargazersCount)})`}\n                </a>\n              </Button>\n            </motion.div>\n          </div>\n        </motion.div>\n      )}\n    </header>\n  );\n}\n"
  },
  {
    "path": "components/footer.tsx",
    "content": "import Link from \"next/link\";\nimport Logo from \"@/assets/logo.svg\";\nimport GitHubIcon from \"@/assets/github.svg\";\nimport TwitterIcon from \"@/assets/twitter.svg\";\nimport DiscordIcon from \"@/assets/discord.svg\";\n\nexport function Footer() {\n  return (\n    <footer className=\"bg-background/95 w-full border-t backdrop-blur-sm\">\n      <div className=\"container mx-auto flex flex-col gap-8 px-4 py-10 md:px-6 lg:py-16\">\n        <div className=\"grid gap-8 sm:grid-cols-2 md:grid-cols-4\">\n          <div className=\"col-span-2 max-w-md space-y-4\">\n            <Link href=\"/\" className=\"flex items-center gap-2 font-bold\">\n              <Logo className=\"size-6\" />\n              <span>tweakcn</span>\n            </Link>\n            <p className=\"text-muted-foreground text-sm\">\n              A powerful visual theme editor for shadcn/ui components with Tailwind CSS support.\n              Make your components stand out.\n            </p>\n            <div className=\"flex gap-4\">\n              <a\n                href=\"https://github.com/jnsahaj/tweakcn\"\n                className=\"text-muted-foreground hover:text-foreground transition-colors\"\n              >\n                <GitHubIcon className=\"size-5\" />\n                <span className=\"sr-only\">GitHub</span>\n              </a>\n              <a\n                href=\"https://discord.gg/Phs4u2NM3n\"\n                className=\"text-muted-foreground hover:text-foreground transition-colors\"\n              >\n                <DiscordIcon className=\"size-5\" />\n                <span className=\"sr-only\">Discord</span>\n              </a>\n              <a\n                href=\"https://x.com/iamsahaj_xyz\"\n                className=\"text-muted-foreground hover:text-foreground transition-colors\"\n              >\n                <TwitterIcon className=\"size-5\" />\n                <span className=\"sr-only\">Twitter</span>\n              </a>\n            </div>\n          </div>\n          <div className=\"space-y-4\">\n            <h4 className=\"text-sm font-bold\">Product</h4>\n            <ul className=\"space-y-2 text-sm\">\n              <li>\n                <Link\n                  href=\"/#features\"\n                  className=\"text-muted-foreground hover:text-foreground transition-colors\"\n                >\n                  Features\n                </Link>\n              </li>\n              <li>\n                <Link\n                  href=\"/community\"\n                  className=\"text-muted-foreground hover:text-foreground transition-colors\"\n                >\n                  Examples\n                </Link>\n              </li>\n              <li>\n                <Link\n                  href=\"/#roadmap\"\n                  className=\"text-muted-foreground hover:text-foreground transition-colors\"\n                >\n                  Roadmap\n                </Link>\n              </li>\n            </ul>\n          </div>\n          <div className=\"space-y-4\">\n            <h4 className=\"text-sm font-bold\">Resources</h4>\n            <ul className=\"space-y-2 text-sm\">\n              <li>\n                <a\n                  href=\"https://github.com/jnsahaj/tweakcn\"\n                  className=\"text-muted-foreground hover:text-foreground transition-colors\"\n                >\n                  GitHub\n                </a>\n              </li>\n              <li>\n                <a\n                  href=\"https://discord.gg/Phs4u2NM3n\"\n                  className=\"text-muted-foreground hover:text-foreground transition-colors\"\n                >\n                  Discord\n                </a>\n              </li>\n              <li>\n                <a\n                  href=\"https://x.com/messages/compose?recipient_id=1426676644152889345\"\n                  className=\"text-muted-foreground hover:text-foreground transition-colors\"\n                >\n                  Contact\n                </a>\n              </li>\n            </ul>\n          </div>\n        </div>\n\n        <div className=\"border-border/40 flex flex-col items-center justify-between gap-4 border-t pt-8 sm:flex-row\">\n          <p className=\"text-muted-foreground text-xs\">\n            &copy; {new Date().getFullYear()} tweakcn. All rights reserved.\n          </p>\n          <p className=\"text-muted-foreground text-xs\">\n            <Link href=\"/privacy-policy\">Privacy Policy</Link>\n          </p>\n        </div>\n      </div>\n    </footer>\n  );\n}\n"
  },
  {
    "path": "components/get-pro-cta.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { useSubscription } from \"@/hooks/use-subscription\";\nimport { cn } from \"@/lib/utils\";\nimport { Gem } from \"lucide-react\";\nimport Link from \"next/link\";\n\ninterface GetProCTAProps extends React.ComponentProps<typeof Button> {}\n\nexport function GetProCTA({ className, ...props }: GetProCTAProps) {\n  const { subscriptionStatus, isPending } = useSubscription();\n  const isPro = subscriptionStatus?.isSubscribed ?? false;\n\n  if (isPending || isPro) {\n    return null;\n  }\n\n  return (\n    <Button\n      variant=\"ghost\"\n      className={cn(\n        \"text-primary animate-in fade-in-50 bg-primary/10 hover:bg-primary hover:text-primary-foreground shadow-none duration-300\",\n        className\n      )}\n      asChild\n      {...props}\n    >\n      <Link href=\"/pricing\">\n        <Gem />\n        Get Pro\n      </Link>\n    </Button>\n  );\n}\n"
  },
  {
    "path": "components/get-pro-dialog-wrapper.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { useGetProDialogStore } from \"@/store/get-pro-dialog-store\";\nimport { PRO_SUB_FEATURES } from \"@/utils/subscription\";\nimport { Calendar, Check } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { NoiseEffect } from \"./effects/noise-effect\";\nimport { AIChatDemo } from \"./examples/ai-chat-demo\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogFooter,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n} from \"./ui/revola\";\n\nexport function GetProDialogWrapper() {\n  const { isOpen, closeGetProDialog } = useGetProDialogStore();\n\n  return <GetProDialog isOpen={isOpen} onClose={closeGetProDialog} />;\n}\n\ninterface GetProDialogProps {\n  isOpen: boolean;\n  onClose: () => void;\n}\n\nexport function GetProDialog({ isOpen, onClose }: GetProDialogProps) {\n  return (\n    <ResponsiveDialog open={isOpen} onOpenChange={onClose}>\n      <ResponsiveDialogContent\n        closeButtonClassName=\"backdrop-blur-md bg-muted/15\"\n        className=\"gap-0 overflow-hidden sm:max-w-lg md:w-[calc(100vw-2rem)] md:max-w-4xl\"\n      >\n        <div className=\"flex flex-col md:flex-row\">\n          {/* Left section: content */}\n          <section className=\"w-full space-y-8 border-r md:w-1/2\">\n            <ResponsiveDialogHeader className=\"sm:p-6 sm:pb-0\">\n              <ResponsiveDialogTitle>Get Pro</ResponsiveDialogTitle>\n              <ResponsiveDialogDescription>{`Unlock all of tweakcn's features`}</ResponsiveDialogDescription>\n            </ResponsiveDialogHeader>\n\n            <div className=\"space-y-6 px-6\">\n              <ul className=\"space-y-3\">\n                {PRO_SUB_FEATURES.map((feature, index) => (\n                  <li key={index} className=\"flex items-start gap-3\">\n                    <div\n                      className={cn(\n                        \"flex items-center justify-center rounded-full p-1\",\n                        feature.status === \"done\" ? \"bg-primary/15\" : \"bg-muted\"\n                      )}\n                    >\n                      {feature.status === \"done\" ? (\n                        <Check className=\"text-primary size-3 stroke-2\" />\n                      ) : (\n                        <Calendar className=\"text-muted-foreground size-3 stroke-2\" />\n                      )}\n                    </div>\n                    <span className={cn(\"text-sm\", feature.status === \"done\" ? \"\" : \"opacity-60\")}>\n                      {feature.description}\n                    </span>\n                  </li>\n                ))}\n              </ul>\n\n              <p className=\"text-muted-foreground text-sm\">{`Don't worry, full theme customization is still yours, for free. Upgrade to Pro to take it\n  to the next level, cancel anytime.`}</p>\n            </div>\n\n            <ResponsiveDialogFooter className=\"bg-muted/30 relative flex-col border-t p-6\">\n              <Button asChild className=\"grow\">\n                <Link href=\"/pricing\" onNavigate={onClose}>\n                  Upgrade to Pro\n                </Link>\n              </Button>\n              <Button variant=\"ghost\" onClick={onClose}>\n                Maybe Later\n              </Button>\n            </ResponsiveDialogFooter>\n          </section>\n\n          {/* Right section: chat preview, only visible md+ */}\n          <section className=\"bg-muted/30 relative isolate hidden shrink-0 items-center justify-center overflow-hidden md:block md:w-1/2\">\n            {/* ----Background effects---- */}\n            <div\n              className={cn(\n                \"absolute inset-0 -z-10 bg-[linear-gradient(to_right,rgba(from_var(--primary)_r_g_b_/_0.25)_1px,transparent_1px),linear-gradient(to_bottom,rgba(from_var(--primary)_r_g_b_/_0.25)_1px,transparent_1px)] bg-[size:2rem_2rem]\",\n                \"mask-r-from-80% mask-b-from-80% mask-radial-from-70% mask-radial-to-85%\"\n              )}\n            />\n\n            <NoiseEffect />\n            <div\n              className={cn(\n                \"absolute inset-0 -z-10 bg-[linear-gradient(to_right,rgba(from_var(--muted-foreground)_r_g_b_/_0.025)_1px,transparent_1px),linear-gradient(to_bottom,rgba(from_var(--muted-foreground)_r_g_b_/_0.025)_1px,transparent_1px)] bg-[size:2rem_2rem]\"\n              )}\n            />\n            <div className=\"bg-foreground/10 absolute top-0 left-0 -z-10 size-35 -translate-x-1/2 -translate-y-1/2 animate-pulse rounded-full blur-3xl\" />\n            <div className=\"bg-primary/15 absolute right-0 bottom-0 -z-10 size-70 translate-x-1/2 translate-y-1/2 rounded-full blur-3xl\" />\n            {/* ----Background effects---- */}\n\n            <div className=\"absolute inset-4 top-4 z-10 flex items-center justify-center overflow-hidden rounded-lg border lg:inset-6\">\n              <AIChatDemo />\n            </div>\n          </section>\n        </div>\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n}\n"
  },
  {
    "path": "components/header.tsx",
    "content": "\"use client\";\n\nimport DiscordIcon from \"@/assets/discord.svg\";\nimport FigmaIcon from \"@/assets/figma.svg\";\nimport GitHubIcon from \"@/assets/github.svg\";\nimport Logo from \"@/assets/logo.svg\";\nimport TwitterIcon from \"@/assets/twitter.svg\";\nimport { FigmaExportDialog } from \"@/components/figma-export-dialog\";\nimport { SocialLink } from \"@/components/social-link\";\nimport { Button } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { UserProfileDropdown } from \"@/components/user-profile-dropdown\";\nimport { useGithubStars } from \"@/hooks/use-github-stars\";\nimport { formatCompactNumber } from \"@/utils/format\";\nimport Link from \"next/link\";\nimport { useState } from \"react\";\nimport { GetProCTA } from \"./get-pro-cta\";\n\nexport function Header() {\n  const { stargazersCount } = useGithubStars(\"jnsahaj\", \"tweakcn\");\n  const [figmaDialogOpen, setFigmaDialogOpen] = useState(false);\n\n  return (\n    <header className=\"border-b\">\n      <div className=\"flex items-center justify-between gap-2 p-4\">\n        <div className=\"flex items-center gap-1\">\n          <Link href=\"/\" className=\"flex items-center gap-2\">\n            <Logo className=\"size-6\" title=\"tweakcn\" />\n            <span className=\"hidden font-bold md:block\">tweakcn</span>\n          </Link>\n        </div>\n        <div className=\"flex items-center gap-3.5\">\n          <GetProCTA className=\"h-8\" />\n\n          <SocialLink\n            href=\"https://github.com/jnsahaj/tweakcn\"\n            className=\"flex items-center gap-2 text-sm font-bold\"\n          >\n            <GitHubIcon className=\"size-4\" />\n            {stargazersCount > 0 && formatCompactNumber(stargazersCount)}\n          </SocialLink>\n          <Separator orientation=\"vertical\" className=\"h-8\" />\n          <div className=\"flex items-center gap-3.5\">\n            <div className=\"hidden items-center gap-3.5 md:flex\">\n              <SocialLink href=\"https://discord.gg/Phs4u2NM3n\">\n                <DiscordIcon className=\"size-5\" />\n              </SocialLink>\n            </div>\n            <SocialLink href=\"https://x.com/iamsahaj_xyz\">\n              <TwitterIcon className=\"size-4\" />\n            </SocialLink>\n          </div>\n          <Separator orientation=\"vertical\" className=\"h-8\" />\n          <Button\n            onClick={() => setFigmaDialogOpen(true)}\n            variant=\"outline\"\n            className=\"flex h-8 items-center gap-2\"\n          >\n            <FigmaIcon className=\"size-4\" />\n            <span className=\"hidden md:inline\">Export to Figma</span>\n          </Button>\n          <UserProfileDropdown />\n        </div>\n      </div>\n\n      <FigmaExportDialog open={figmaDialogOpen} onOpenChange={setFigmaDialogOpen} />\n    </header>\n  );\n}\n"
  },
  {
    "path": "components/home/ai-generation-cta.tsx",
    "content": "import { AIChatDemo } from \"@/components/examples/ai-chat-demo\";\nimport { Button } from \"@/components/ui/button\";\nimport { ArrowRight, Check } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport Link from \"next/link\";\n\nexport function AIGenerationCTA() {\n  return (\n    <section\n      id=\"ai-generation-cta\"\n      className=\"bg-muted/35 relative isolate w-full overflow-hidden py-24 md:py-32 lg:py-40\"\n    >\n      <div className=\"relative isolate\">\n        <div className=\"relative z-10 container mx-auto w-full px-4 md:px-6\">\n          <div className=\"relative grid items-center gap-16 lg:grid-cols-2\">\n            {/* Left Column - Text Content */}\n            <div className=\"mx-auto max-w-2xl lg:mx-0\">\n              <motion.div\n                initial={{ opacity: 0, y: 20 }}\n                whileInView={{ opacity: 1, y: 0 }}\n                viewport={{ once: true }}\n                transition={{ duration: 0.5 }}\n                className=\"space-y-8\"\n              >\n                <div className=\"justify-left relative flex flex-col items-start gap-6\">\n                  <h2 className=\"text-foreground text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl\">\n                    Generate Themes in <br />\n                    <span className=\"text-primary font-serif italic\">Seconds</span>\n                  </h2>\n                  <p className=\"text-muted-foreground max-w-lg text-lg leading-relaxed md:text-xl\">\n                    Just provide an image or text prompt, and our AI will create a stunning,\n                    production-ready theme for you.\n                  </p>\n                </div>\n\n                <div className=\"flex flex-col gap-4 sm:flex-row\">\n                  <Link href=\"/ai\">\n                    <Button\n                      size=\"lg\"\n                      className=\"hover:shadow-primary/25 h-14 rounded-full px-8 text-lg shadow-lg transition-all hover:scale-105\"\n                    >\n                      Generate with AI <ArrowRight className=\"ml-2 size-5\" />\n                    </Button>\n                  </Link>\n                  <Link href=\"/pricing\">\n                    <Button\n                      size=\"lg\"\n                      variant=\"outline\"\n                      className=\"border-primary/20 bg-background/50 hover:bg-accent/50 h-14 rounded-full px-8 text-lg backdrop-blur-sm\"\n                    >\n                      View Pricing\n                    </Button>\n                  </Link>\n                </div>\n\n                <div className=\"grid grid-cols-1 gap-4 pt-4 sm:grid-cols-2\">\n                  {[\n                    \"Theme Preview\",\n                    \"Checkpoint Restoration\",\n                    \"Image Extraction\",\n                    \"Text-to-Theme\",\n                  ].map((feature) => (\n                    <div\n                      key={feature}\n                      className=\"text-muted-foreground flex items-center gap-3 text-base\"\n                    >\n                      <div className=\"bg-primary/10 flex size-6 items-center justify-center rounded-full\">\n                        <Check className=\"text-primary size-3.5\" />\n                      </div>\n                      <span>{feature}</span>\n                    </div>\n                  ))}\n                </div>\n              </motion.div>\n            </div>\n\n            {/* Right Column - Visual Preview */}\n            <div className=\"relative hidden lg:block\">\n              <motion.div\n                initial={{ opacity: 0, x: 20 }}\n                whileInView={{ opacity: 1, x: 0 }}\n                viewport={{ once: true }}\n                transition={{ duration: 0.5, delay: 0.2 }}\n                className=\"relative\"\n              >\n                {/* Glassmorphism Container */}\n                <div className=\"relative z-10 overflow-hidden rounded-2xl border border-white/10 bg-white/5 shadow-2xl backdrop-blur-xl dark:border-white/5 dark:bg-black/5\">\n                  <div className=\"relative h-[550px] w-full p-6\">\n                    <AIChatDemo disabled={false} className=\"h-full pb-0 bg-transparent\" />\n                  </div>\n                </div>\n\n                {/* Background Glow */}\n                <div className=\"absolute -inset-4 bg-gradient-to-r from-primary/20 to-secondary/20 blur-3xl -z-10 rounded-full opacity-50\" />\n              </motion.div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "components/home/cta.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport { motion } from \"motion/react\";\nimport { ArrowRight } from \"lucide-react\";\nimport Link from \"next/link\";\n\nexport function CTA() {\n  return (\n    <section className=\"w-full py-20 md:py-32 bg-gradient-to-br from-primary to-primary/80 text-primary-foreground relative overflow-hidden isolate\">\n      <div className=\"absolute inset-0 -z-10 bg-[linear-gradient(to_right,rgba(from_var(--primary-foreground)_r_g_b_/_0.075)_1px,transparent_1px),linear-gradient(to_bottom,rgba(from_var(--primary-foreground)_r_g_b_/_0.075)_1px,transparent_1px)] bg-[size:4rem_4rem]\"></div>\n      <div className=\"absolute -top-24 -left-24 w-64 h-64 bg-foreground/15 rounded-full blur-3xl animate-pulse\"></div>\n      <div\n        className=\"absolute -bottom-24 -right-24 w-64 h-64 bg-foreground/15 rounded-full blur-3xl animate-pulse\"\n        style={{ animationDelay: \"1.5s\" }}\n      ></div>\n\n      <div className=\"container mx-auto px-4 md:px-6 relative\">\n        <motion.div\n          initial={{ opacity: 0, y: 20 }}\n          whileInView={{ opacity: 1, y: 0 }}\n          transition={{ duration: 0.5 }}\n          className=\"flex flex-col items-center justify-center space-y-6 text-center\"\n        >\n          <motion.h2\n            initial={{ opacity: 0, y: 20 }}\n            whileInView={{ opacity: 1, y: 0 }}\n            viewport={{ once: true }}\n            transition={{ duration: 0.5 }}\n            className=\"text-3xl md:text-4xl lg:text-5xl font-bold tracking-tight\"\n          >\n            Ready to Make Your Components Stand Out?\n          </motion.h2>\n          <motion.p\n            initial={{ opacity: 0, y: 20 }}\n            whileInView={{ opacity: 1, y: 0 }}\n            viewport={{ once: true }}\n            transition={{ duration: 0.5, delay: 0.1 }}\n            className=\"mx-auto max-w-[700px] text-primary-foreground/80 md:text-xl\"\n          >\n            Start customizing your shadcn/ui components today and create a unique\n            look for your application.\n          </motion.p>\n          <motion.div\n            initial={{ opacity: 0, y: 20 }}\n            whileInView={{ opacity: 1, y: 0 }}\n            viewport={{ once: true }}\n            transition={{ duration: 0.5, delay: 0.2 }}\n            className=\"flex flex-col sm:flex-row gap-4 mt-4\"\n          >\n            <Link href=\"/editor/theme\">\n              <Button\n                size=\"lg\"\n                variant=\"secondary\"\n                className=\"rounded-full h-12 px-8 text-base cursor-pointer shadow-md hover:shadow-lg transition-all duration-300 hover:translate-y-[-2px]\"\n              >\n                Try It Now\n                <ArrowRight className=\"ml-2 size-4\" />\n              </Button>\n            </Link>\n            <Link href=\"https://github.com/jnsahaj/tweakcn\">\n              <Button\n                size=\"lg\"\n                variant=\"outline\"\n                className=\"rounded-full bg-transparent h-12 px-8 text-base transition-all duration-300 hover:translate-y-[-2px]\"\n              >\n                View on GitHub\n              </Button>\n            </Link>\n          </motion.div>\n          <motion.p\n            initial={{ opacity: 0 }}\n            whileInView={{ opacity: 1 }}\n            viewport={{ once: true }}\n            transition={{ duration: 0.5, delay: 0.3 }}\n            className=\"text-sm text-primary-foreground/80 mt-4\"\n          >\n            No login required. Free to use. Open source.\n          </motion.p>\n        </motion.div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "components/home/faq.tsx",
    "content": "import {\n  Accordion,\n  AccordionContent,\n  AccordionItem,\n  AccordionTrigger,\n} from \"@/components/ui/accordion\";\nimport { motion } from \"motion/react\";\n\nconst faqs = [\n  {\n    question: \"What is tweakcn?\",\n    answer:\n      \"tweakcn is a visual theme editor for shadcn/ui components. It allows you to customize your theme visually and export the code for your project.\",\n  },\n  {\n    question: \"Is it free?\",\n    answer:\n      \"Yes, the core features are completely free. We offer a Pro plan for advanced AI features.\",\n  },\n  {\n    question: \"What's included in Pro?\",\n    answer:\n      \"Pro includes AI theme generation from images and prompts, as well as cloud saving for multiple themes.\",\n  },\n  {\n    question: \"Supports Tailwind v4?\",\n    answer:\n      \"Yes! We support both Tailwind CSS v3 and v4, along with OKLCH, HSL, and other color formats.\",\n  },\n  {\n    question: \"Can I use with existing projects?\",\n    answer:\n      \"Absolutely. Just copy the generated configuration into your existing project.\",\n  },\n];\n\nexport function FAQ() {\n  return (\n    <section id=\"faq\" className=\"w-full py-24 md:py-32\">\n      <div className=\"container mx-auto px-4 md:px-6\">\n        <div className=\"grid lg:grid-cols-12 gap-12\">\n          <motion.div\n            initial={{ opacity: 0, y: 20 }}\n            whileInView={{ opacity: 1, y: 0 }}\n            viewport={{ once: true }}\n            transition={{ duration: 0.5 }}\n            className=\"lg:col-span-4\"\n          >\n            <h2 className=\"text-3xl md:text-5xl font-bold tracking-tight mb-6\">\n              FAQ\n            </h2>\n            <p className=\"text-muted-foreground text-lg mb-8\">\n              Got questions? We&apos;ve got answers. If you can&apos;t find what you&apos;re looking for, feel free to reach out.\n            </p>\n            <div className=\"text-sm text-muted-foreground\">\n              <p>Contact us at <a href=\"#\" className=\"text-primary underline\">sahaj@tweakcn.com</a></p>\n            </div>\n          </motion.div>\n\n          <div className=\"lg:col-span-8\">\n            <Accordion type=\"single\" collapsible className=\"w-full space-y-4\">\n              {faqs.map((faq, i) => (\n                <motion.div\n                  key={i}\n                  initial={{ opacity: 0, y: 10 }}\n                  whileInView={{ opacity: 1, y: 0 }}\n                  viewport={{ once: true }}\n                  transition={{ duration: 0.3, delay: i * 0.05 }}\n                >\n                  <AccordionItem value={`item-${i}`} className=\"border rounded-lg px-4 bg-muted/20\">\n                    <AccordionTrigger className=\"hover:no-underline text-lg font-medium py-6\">\n                      {faq.question}\n                    </AccordionTrigger>\n                    <AccordionContent className=\"text-muted-foreground pb-6 text-base\">\n                      {faq.answer}\n                    </AccordionContent>\n                  </AccordionItem>\n                </motion.div>\n              ))}\n            </Accordion>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "components/home/features.tsx",
    "content": "import { BrainCircuit, Code, Contrast, FileCode, Gem, Layers, Paintbrush } from \"lucide-react\";\nimport { motion } from \"motion/react\";\n\nconst features = [\n  {\n    title: \"Color Control\",\n    description:\n      \"Customize background, text, and border colors with an intuitive color picker interface.\",\n    icon: <Paintbrush className=\"size-6\" />,\n  },\n  {\n    title: \"Typography Settings\",\n    description: \"Fine-tune font size, weight, and text transform to create the perfect look.\",\n    icon: <FileCode className=\"size-6\" />,\n  },\n  {\n    title: \"Tailwind v4 & v3\",\n    description:\n      \"Seamlessly switch between Tailwind versions with support for OKLCH & HSL formats.\",\n    icon: <Code className=\"size-6\" />,\n  },\n  {\n    title: \"Detailed Properties\",\n    description:\n      \"Fine-tune every aspect including radius, spacing, shadows, and other properties.\",\n    icon: <Layers className=\"size-6\" />,\n  },\n  {\n    title: \"Contrast Checker\",\n    description:\n      \"Ensure designs meet accessibility standards with built-in contrast ratio checking.\",\n    icon: <Contrast className=\"size-6\" />,\n  },\n  {\n    title: \"AI Theme Generation\",\n    description:\n      \"Create stunning, ready-to-use themes in seconds. Just provide an image or prompt.\",\n    icon: <BrainCircuit className=\"size-6\" />,\n    pro: true,\n  },\n];\n\nconst container = {\n  hidden: { opacity: 0 },\n  show: {\n    opacity: 1,\n    transition: {\n      staggerChildren: 0.1,\n    },\n  },\n};\n\nconst item = {\n  hidden: { opacity: 0, y: 20 },\n  show: { opacity: 1, y: 0 },\n};\n\nexport function Features() {\n  return (\n    <section id=\"features\" className=\"relative isolate w-full py-20 md:py-32\">\n      <div className=\"absolute inset-0 -z-10 bg-[radial-gradient(ellipse_at_center,rgba(from_var(--primary)_r_g_b_/_0.03),transparent_70%)]\"></div>\n\n      <div className=\"container mx-auto px-4 md:px-6\">\n        <div className=\"grid gap-12 lg:grid-cols-[1fr_2fr]\">\n          <motion.div\n            initial={{ opacity: 0, x: -20 }}\n            whileInView={{ opacity: 1, x: 0 }}\n            viewport={{ once: true }}\n            transition={{ duration: 0.5 }}\n            className=\"flex flex-col justify-center space-y-4\"\n          >\n            <h2 className=\"text-3xl font-bold tracking-tight md:text-4xl lg:text-5xl text-left\">\n              Powerful Tools <br className=\"hidden lg:block\" />\n              <span className=\"text-muted-foreground\">For Total Control</span>\n            </h2>\n            <p className=\"text-muted-foreground max-w-[400px] text-lg\">\n              Everything you need to customize your shadcn/ui components and make them unique.\n            </p>\n          </motion.div>\n\n          <motion.div\n            variants={container}\n            initial=\"hidden\"\n            whileInView=\"show\"\n            viewport={{ once: true }}\n            className=\"grid gap-6 sm:grid-cols-2\"\n          >\n            {features.map((feature, i) => (\n              <motion.div\n                key={i}\n                variants={item}\n                whileHover={{ y: -5, transition: { duration: 0.2 } }}\n              >\n                <div className=\"group h-full rounded-2xl border border-border/40 bg-card/50 p-6 transition-all hover:bg-card hover:shadow-lg\">\n                    <div className=\"mb-4 flex size-12 items-center justify-center rounded-xl bg-primary/10 text-primary transition-colors group-hover:bg-primary group-hover:text-primary-foreground\">\n                      {feature.icon}\n                    </div>\n                    <h3 className=\"mb-2 flex items-center gap-2 text-xl font-bold\">\n                      {feature.title}\n                      {feature.pro && (\n                        <span className=\"bg-primary/10 text-primary inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-semibold\">\n                          <Gem className=\"size-3\" />\n                          Pro\n                        </span>\n                      )}\n                    </h3>\n                    <p className=\"text-muted-foreground\">{feature.description}</p>\n                </div>\n              </motion.div>\n            ))}\n          </motion.div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "components/home/header.tsx",
    "content": "\"use client\";\n\nimport GitHubIcon from \"@/assets/github.svg\";\nimport Logo from \"@/assets/logo.svg\";\nimport { Button } from \"@/components/ui/button\";\nimport { useGithubStars } from \"@/hooks/use-github-stars\";\nimport { cn } from \"@/lib/utils\";\nimport { formatCompactNumber } from \"@/utils/format\";\nimport { ChevronRight, Menu, X } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport Link from \"next/link\";\nimport { ThemeToggle } from \"../theme-toggle\";\n\ninterface HeaderProps {\n  isScrolled: boolean;\n  mobileMenuOpen: boolean;\n  setMobileMenuOpen: (open: boolean) => void;\n}\n\nconst navbarItems = [\n  {\n    label: \"Features\",\n    href: \"#features\",\n  },\n  {\n    label: \"AI\",\n    href: \"/ai\",\n  },\n  {\n    label: \"Pricing\",\n    href: \"/pricing\",\n  },\n  {\n    label: \"Community\",\n    href: \"/community\",\n  },\n  {\n    label: \"FAQ\",\n    href: \"#faq\",\n  },\n];\n\nexport function Header({ isScrolled, mobileMenuOpen, setMobileMenuOpen }: HeaderProps) {\n  const { stargazersCount } = useGithubStars(\"jnsahaj\", \"tweakcn\");\n\n  const handleScrollToSection = (e: React.MouseEvent<HTMLAnchorElement>) => {\n    e.preventDefault();\n    const targetId = e.currentTarget.getAttribute(\"href\")?.slice(1);\n    if (!targetId) return;\n\n    const element = document.getElementById(targetId);\n    if (element) {\n      element.scrollIntoView({ behavior: \"smooth\" });\n    }\n  };\n\n  return (\n    <header\n      className={cn(\n        \"sticky top-0 z-50 w-full backdrop-blur-lg\",\n        isScrolled ? \"bg-background/90 border-border/20 border-b shadow-xs\" : \"bg-transparent\"\n      )}\n    >\n      <div className=\"container mx-auto relative flex h-16 items-center justify-between px-4 md:px-6\">\n        <Link href=\"/\">\n          <div className=\"flex items-center gap-2 font-bold\">\n            <Logo className=\"size-6\" />\n            <span className=\"hidden lg:block\">tweakcn</span>\n          </div>\n        </Link>\n        <nav className=\"absolute left-1/2 hidden -translate-x-1/2 items-center gap-4 md:flex lg:gap-8\">\n          {navbarItems.map((item, i) => (\n            <motion.a\n              key={item.label}\n              initial={{ opacity: 0, y: -10 }}\n              animate={{ opacity: 1, y: 0 }}\n              transition={{ duration: 0.3, delay: 0.1 + i * 0.05 }}\n              href={item.href}\n              onClick={item.href.startsWith(\"#\") ? handleScrollToSection : undefined}\n              className=\"text-muted-foreground hover:text-foreground group relative text-xs font-medium transition-colors lg:text-sm\"\n            >\n              {item.label}\n              <span className=\"bg-primary absolute -bottom-1 left-0 h-0.5 w-0 transition-all duration-300 group-hover:w-full\"></span>\n            </motion.a>\n          ))}\n        </nav>\n        <div className=\"hidden cursor-pointer items-center gap-4 md:flex\">\n          <motion.div\n            initial={{ opacity: 0, scale: 0.9 }}\n            animate={{ opacity: 1, scale: 1 }}\n            transition={{ duration: 0.3, delay: 0.45 }}\n          >\n            <Button variant=\"ghost\" asChild>\n              <a\n                href=\"https://github.com/jnsahaj/tweakcn\"\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                className=\"font-semibold\"\n              >\n                <GitHubIcon className=\"size-5\" />\n                {stargazersCount > 0 && formatCompactNumber(stargazersCount)}\n              </a>\n            </Button>\n          </motion.div>\n\n          <motion.div\n            initial={{ opacity: 0, scale: 0.9 }}\n            animate={{ opacity: 1, scale: 1 }}\n            transition={{ duration: 0.3, delay: 0.4 }}\n          >\n            <ThemeToggle\n              variant=\"secondary\"\n              size=\"icon\"\n              className=\"rounded-full transition-transform hover:scale-105\"\n            />\n          </motion.div>\n\n          <motion.div\n            initial={{ opacity: 0, scale: 0.9 }}\n            animate={{ opacity: 1, scale: 1 }}\n            transition={{ duration: 0.3, delay: 0.5 }}\n          >\n            <Link href=\"/editor/theme\" prefetch>\n              <Button className=\"cursor-pointer rounded-full font-medium transition-transform hover:scale-105\">\n                Try It Now\n                <ChevronRight className=\"ml-1 size-4\" />\n              </Button>\n            </Link>\n          </motion.div>\n        </div>\n        <div className=\"flex items-center gap-2 md:hidden\">\n          <ThemeToggle variant=\"ghost\" size=\"icon\" />\n          <Button variant=\"ghost\" size=\"icon\" onClick={() => setMobileMenuOpen(!mobileMenuOpen)}>\n            {mobileMenuOpen ? <X className=\"size-5\" /> : <Menu className=\"size-5\" />}\n            <span className=\"sr-only\">Toggle menu</span>\n          </Button>\n        </div>\n      </div>\n      {/* Mobile menu */}\n      {mobileMenuOpen && (\n        <motion.div\n          initial={{ opacity: 0, y: -20 }}\n          animate={{ opacity: 1, y: 0 }}\n          exit={{ opacity: 0, y: -20 }}\n          className=\"bg-background/95 absolute inset-x-0 top-16 border-b backdrop-blur-lg md:hidden\"\n        >\n          <div className=\"container mx-auto flex flex-col gap-4 px-4 py-4\">\n            {navbarItems.map((item, i) => (\n              <motion.a\n                key={item.label}\n                initial={{ opacity: 0, x: -10 }}\n                animate={{ opacity: 1, x: 0 }}\n                transition={{ duration: 0.2, delay: i * 0.05 }}\n                href={item.href}\n                onClick={(e) => {\n                  handleScrollToSection(e);\n                  setMobileMenuOpen(false);\n                }}\n                className=\"group relative overflow-hidden py-2 text-sm font-medium\"\n              >\n                <span className=\"relative z-10\">{item.href}</span>\n                <span className=\"bg-primary absolute bottom-0 left-0 h-0.5 w-0 transition-all duration-300 group-hover:w-full\"></span>\n              </motion.a>\n            ))}\n            <motion.div\n              initial={{ opacity: 0, y: 10 }}\n              animate={{ opacity: 1, y: 0 }}\n              transition={{ duration: 0.3, delay: 0.3 }}\n              className=\"border-border/30 mt-2 border-t pt-2\"\n            >\n              <Link href=\"/editor/theme\" onClick={() => setMobileMenuOpen(false)}>\n                <Button className=\"w-full rounded-full\">\n                  Try It Now\n                  <ChevronRight className=\"ml-2 size-4\" />\n                </Button>\n              </Link>\n            </motion.div>\n          </div>\n        </motion.div>\n      )}\n    </header>\n  );\n}\n"
  },
  {
    "path": "components/home/hero.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { ArrowRight, Check } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport Link from \"next/link\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { defaultPresets } from \"@/utils/theme-presets\";\nimport { ThemePresetButtons } from \"@/components/home/theme-preset-buttons\";\n\nconst presetNames = Object.keys(defaultPresets);\n\nexport function Hero() {\n  const { themeState, applyThemePreset } = useEditorStore();\n  const mode = themeState.currentMode;\n\n  return (\n    <section className=\"relative isolate w-full overflow-hidden bg-background pt-20 pb-32 md:pt-32 md:pb-40\">\n      {/* Background Effects */}\n      <div className=\"absolute inset-0 -z-10 h-full w-full bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)]\"></div>\n\n\n      <div className=\"container relative z-20 mx-auto px-4 md:px-6\">\n        <div className=\"flex flex-col items-center text-center\">\n\n          {/* Headline */}\n          <motion.h1\n            initial={{ opacity: 0, y: 20 }}\n            animate={{ opacity: 1, y: 0 }}\n            transition={{ duration: 0.5, delay: 0.1 }}\n            className=\"max-w-4xl bg-gradient-to-br from-foreground via-foreground/90 to-foreground/70 bg-clip-text text-5xl font-bold tracking-tight text-transparent sm:text-6xl md:text-7xl lg:text-8xl\"\n          >\n            Design Your <span className=\"font-serif italic font-light text-foreground\">Perfect</span>{\" \"}\n            <span className=\"relative inline-block\">\n              <span className=\"absolute -inset-1 rounded-lg bg-primary/10 blur-xl opacity-50\"></span>\n              <span className=\"relative text-primary inline-flex items-center gap-2\">\n                shadcn/ui\n              </span>\n            </span>{\" \"}\n            Theme\n          </motion.h1>\n\n          {/* Description */}\n          <motion.p\n            initial={{ opacity: 0, y: 20 }}\n            animate={{ opacity: 1, y: 0 }}\n            transition={{ duration: 0.5, delay: 0.2 }}\n            className=\"mt-6 max-w-2xl text-lg text-muted-foreground md:text-xl leading-relaxed\"\n          >\n            Customize colors, typography, and layouts with a real-time preview. No signup\n            required.\n          </motion.p>\n\n          {/* Buttons */}\n          <motion.div\n            initial={{ opacity: 0, y: 20 }}\n            animate={{ opacity: 1, y: 0 }}\n            transition={{ duration: 0.5, delay: 0.3 }}\n            className=\"mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row\"\n          >\n            <Link href=\"/editor/theme\">\n              <Button\n                size=\"lg\"\n                className=\"h-12 min-w-[180px] rounded-full px-8 text-base shadow-lg shadow-primary/20 transition-all hover:scale-105 hover:shadow-primary/40\"\n              >\n                Start Customizing\n                <ArrowRight className=\"ml-2 size-4\" />\n              </Button>\n            </Link>\n            <a href=\"/community\">\n              <Button\n                size=\"lg\"\n                variant=\"outline\"\n                className=\"h-12 min-w-[180px] rounded-full border-primary/20 bg-background/50 px-8 text-base backdrop-blur-sm transition-all hover:bg-accent/50 hover:border-primary/50\"\n              >\n                Browse Community\n              </Button>\n            </a>\n          </motion.div>\n\n          {/* Features List */}\n          <motion.div\n            initial={{ opacity: 0, y: 20 }}\n            animate={{ opacity: 1, y: 0 }}\n            transition={{ duration: 0.5, delay: 0.4 }}\n            className=\"mt-12 flex flex-wrap justify-center gap-x-8 gap-y-4 text-sm text-muted-foreground\"\n          >\n            <div className=\"flex items-center gap-2\">\n              <div className=\"rounded-full bg-primary/10 p-1\">\n                <Check className=\"size-3 text-primary\" />\n              </div>\n              <span>Real-time Preview</span>\n            </div>\n            <div className=\"flex items-center gap-2\">\n              <div className=\"rounded-full bg-primary/10 p-1\">\n                <Check className=\"size-3 text-primary\" />\n              </div>\n              <span>Export to Tailwind</span>\n            </div>\n            <div className=\"flex items-center gap-2\">\n              <div className=\"rounded-full bg-primary/10 p-1\">\n                <Check className=\"size-3 text-primary\" />\n              </div>\n              <span>Beautiful Presets</span>\n            </div>\n          </motion.div>\n\n          {/* Carousel */}\n          <motion.div\n            initial={{ opacity: 0, scale: 0.95 }}\n            animate={{ opacity: 1, scale: 1 }}\n            transition={{ duration: 0.7, delay: 0.5 }}\n            className=\"mt-20 w-full max-w-[100vw] overflow-hidden\"\n          >\n            <div className=\"relative py-1\">\n              {/* Gradient Masks for Carousel */}\n              <div className=\"absolute left-0 top-0 bottom-0 z-10 w-20 md:w-40 bg-gradient-to-r from-background to-transparent pointer-events-none\"></div>\n              <div className=\"absolute right-0 top-0 bottom-0 z-10 w-20 md:w-40 bg-gradient-to-l from-background to-transparent pointer-events-none\"></div>\n\n              <ThemePresetButtons\n                presetNames={presetNames}\n                mode={mode}\n                themeState={themeState}\n                applyThemePreset={applyThemePreset}\n              />\n            </div>\n          </motion.div>\n\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "components/home/how-it-works.tsx",
    "content": "import { motion } from \"motion/react\";\n\nconst steps = [\n  {\n    step: \"01\",\n    title: \"Select Preset\",\n    description: \"Start with a pre-made theme from our library.\",\n  },\n  {\n    step: \"02\",\n    title: \"Customize\",\n    description:\n      \"Adjust colors, radius, and typography visually.\",\n  },\n  {\n    step: \"03\",\n    title: \"Export Code\",\n    description: \"Copy the Tailwind CSS config to your project.\",\n  },\n];\n\nexport function HowItWorks() {\n  return (\n    <section\n      id=\"how-it-works\"\n      className=\"w-full py-24 md:py-32 relative overflow-hidden isolate\"\n    >\n      <div className=\"container mx-auto px-4 md:px-6\">\n        <div className=\"flex flex-col md:flex-row md:items-end justify-between mb-16 gap-6\">\n           <motion.div\n            initial={{ opacity: 0, y: 20 }}\n            whileInView={{ opacity: 1, y: 0 }}\n            viewport={{ once: true }}\n            transition={{ duration: 0.5 }}\n            className=\"max-w-2xl\"\n           >\n                <h2 className=\"text-3xl md:text-5xl font-bold tracking-tight mb-4\">\n                    Three Steps to <br/>\n                    <span className=\"text-primary\">Perfection</span>\n                </h2>\n                <p className=\"text-muted-foreground text-lg md:text-xl max-w-[600px]\">\n                    We&apos;ve simplified the theming process so you can focus on building your app.\n                </p>\n           </motion.div>\n        </div>\n\n        <div className=\"grid md:grid-cols-3 gap-8\">\n          {steps.map((step, i) => (\n            <motion.div\n              key={i}\n              initial={{ opacity: 0, y: 20 }}\n              whileInView={{ opacity: 1, y: 0 }}\n              viewport={{ once: true }}\n              transition={{ duration: 0.5, delay: i * 0.2 }}\n              className=\"relative group\"\n            >\n              <div className=\"mb-6 relative\">\n                 <span className=\"text-8xl font-bold text-muted/20 group-hover:text-primary/10 transition-colors duration-500 block\">\n                    {step.step}\n                 </span>\n                 <div className=\"absolute bottom-4 left-2 w-12 h-1 bg-primary rounded-full\"></div>\n              </div>\n              <h3 className=\"text-2xl font-bold mb-3\">{step.title}</h3>\n              <p className=\"text-muted-foreground text-lg leading-relaxed\">{step.description}</p>\n            </motion.div>\n          ))}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "components/home/testimonials.tsx",
    "content": "\"use client\";\n\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { motion, useMotionValue, useReducedMotion } from \"motion/react\";\nimport Link from \"next/link\";\nimport { useEffect, useRef, useState } from \"react\";\n\n// Testimonials Data\nconst testimonials = [\n  {\n    image: \"https://pbs.twimg.com/profile_images/1766632098461253634/2t4wT1TZ_400x400.png\",\n    name: \"YiMing\",\n    tag: \"yimingdothan\",\n    description: `v0 + tweakcn + chatgpt for graphics\n\ngenerated a landing page in about 2~ hours\n\ncrazy how easy this shit is now`,\n    href: \"https://x.com/yimingdothan/status/1923833970799608086\",\n  },\n  {\n    image: \"https://pbs.twimg.com/profile_images/1783856060249595904/8TfcCN0r_400x400.jpg\",\n    name: \"Guillermo Rauch\",\n    tag: \"rauchg\",\n    description: `If you're looking to learn:\n▪️ full stack Next.js\n▪️ how to build a focused product people love\n\n… look no further than tweakcn[0] by \n@iamsahaj_xyz. It's an open-source @shadcn theme builder.\n`,\n    href: \"https://x.com/rauchg/status/1938745259204493738\",\n  },\n  {\n    image: \"https://pbs.twimg.com/profile_images/1593304942210478080/TUYae5z7_400x400.jpg\",\n    name: \"shadcn\",\n    tag: \"shadcn\",\n    description: `4/n - Finally, a custom theme from tweakcn by @iamsahaj_xyz`,\n    href: \"https://x.com/shadcn/status/1909619407124676701\",\n  },\n  {\n    image: \"https://pbs.twimg.com/profile_images/1849574174785732608/ltlLcyaT_400x400.jpg\",\n    name: \"Kevin Kern\",\n    tag: \"kregenrek\",\n    description: `Tweakcn is really cool. Custom shadcn themes on the fly.`,\n    href: \"https://x.com/kregenrek/status/1911892242568216618\",\n  },\n  {\n    image: \"https://pbs.twimg.com/profile_images/1756766826736893952/6Gvg6jha_400x400.jpg\",\n    name: \"OrcDev\",\n    tag: \"theorcdev\",\n    description: `Transform your Shadcn app with one click!\n\n@iamsahaj_xyz created a great concept with Tweakcn ⚔️`,\n    href: \"https://x.com/theorcdev/status/1923396394452124081\",\n  },\n  {\n    image: \"https://pbs.twimg.com/profile_images/1934209156816216064/NZns8Qth_400x400.jpg\",\n    name: \"Ciara Wearen\",\n    tag: \"nocheerleader\",\n    description: `Create a Custom Theme: Your app instantly looks more intentional.\n\nBuild a color palette, typography and layout preview with tweakcn dot com\n\nGrab  the CSS → drop into Bolt = cohesive design`,\n    href: \"https://x.com/nocheerleader/status/1934648830315684275\",\n  },\n  {\n    image: \"https://pbs.twimg.com/profile_images/1937802227672109056/JHRKKC9G_400x400.jpg\",\n    name: \"Tanpreet Jolly 🌂\",\n    tag: \"JollyTanpreet\",\n    description:\n      \"I just tried tweakcn and seems like you nailed it. This is what I have been looking for, awesome job!\",\n    href: \"https://x.com/JollyTanpreet/status/1926923858721808484\",\n  },\n  {\n    image: \"https://pbs.twimg.com/profile_images/1677359164580929544/jngFF04Y_400x400.jpg\",\n    name: \"Code With Antonio\",\n    tag: \"YTCodeAntonio\",\n    description: \"there is an entire chapter dedicated to tweakcn!! such a cool project\",\n    href: \"https://x.com/YTCodeAntonio/status/1938314416497549430\",\n  },\n  {\n    image: \"https://pbs.twimg.com/profile_images/1942939901994893312/epjxuhCr_400x400.jpg\",\n    name: \"Emir\",\n    tag: \"emirthedev\",\n    description: \"Started using tweakcn for client projects too. This is a real game changer\",\n    href: \"https://x.com/emirthedev/status/1919418644183843211\",\n  },\n  {\n    image: \"https://pbs.twimg.com/profile_images/1903255064149442560/TYvinGL9_400x400.jpg\",\n    name: \"Matt Silverlock 🐀\",\n    tag: \"elithrar\",\n    description: `used this shadcn theme editor to make it a little less plain: tweakcn.com`,\n    href: \"https://x.com/elithrar/status/1905704716589510889\",\n  },\n];\n\nconst MarqueeRow = ({\n  items,\n  reverse = false,\n}: {\n  items: typeof testimonials;\n  reverse?: boolean;\n}) => {\n  const shouldReduceMotion = useReducedMotion();\n  const x = useRef(useMotionValue(0));\n  const speed = shouldReduceMotion ? 0 : 20;\n  const containerRef = useRef<HTMLDivElement>(null);\n  const animationFrame = useRef(0);\n  const lastTime = useRef(performance.now());\n  const isPaused = useRef(false);\n  const [duplicateCount, setDuplicateCount] = useState(2);\n  const [containerWidth, setContainerWidth] = useState(0);\n\n  useEffect(() => {\n    if (typeof window !== \"undefined\" && containerRef.current) {\n      const cardWidth = 400;\n      const screenWidth = window.innerWidth;\n      const cardsNeeded = Math.ceil(screenWidth / cardWidth) + 3;\n      const loopCount = Math.ceil(cardsNeeded / items.length);\n      setDuplicateCount(loopCount);\n\n      const totalWidth = cardWidth * items.length * loopCount;\n      setContainerWidth(totalWidth);\n      x.current.set(reverse ? -totalWidth / 2 : 0);\n    }\n  }, [items.length, reverse]);\n\n  useEffect(() => {\n    if (shouldReduceMotion) return;\n\n    const animate = (time: number) => {\n      const delta = time - lastTime.current;\n      lastTime.current = time;\n\n      if (!isPaused.current && containerRef.current) {\n        const direction = reverse ? 1 : -1;\n        const distance = (speed * delta * direction) / 1000;\n        const currentX = x.current.get();\n\n        let newX = currentX + distance;\n\n        if (reverse && newX >= 0) {\n          newX = -containerWidth / 2;\n        } else if (!reverse && Math.abs(newX) >= containerWidth / 2) {\n          newX = 0;\n        }\n\n        x.current.set(newX);\n      }\n\n      animationFrame.current = requestAnimationFrame(animate);\n    };\n\n    animationFrame.current = requestAnimationFrame(animate);\n    return () => cancelAnimationFrame(animationFrame.current);\n  }, [containerWidth, reverse, shouldReduceMotion, speed]);\n\n  const pause = () => (isPaused.current = true);\n  const resume = () => {\n    lastTime.current = performance.now();\n    isPaused.current = false;\n  };\n\n  const repeatedItems = Array(duplicateCount)\n    .fill(null)\n    .flatMap(() => items);\n\n  return (\n    <div\n      className=\"relative w-full overflow-hidden py-2\"\n      style={{\n        maskImage:\n          \"linear-gradient(to right, transparent 0%, black 15%, black 85%, transparent 100%)\",\n        WebkitMaskImage:\n          \"linear-gradient(to right, transparent 0%, black 15%, black 85%, transparent 100%)\",\n      }}\n    >\n      <motion.div\n        ref={containerRef}\n        style={{ x: x.current }}\n        onMouseEnter={pause}\n        onMouseLeave={resume}\n        className={`flex w-max items-stretch gap-4 ${reverse ? \"flex-row-reverse\" : \"\"}`}\n      >\n        {repeatedItems.map((testimonial, i) => (\n          <Card\n            key={i}\n            className=\"border-border/40 from-card to-card/50 hover:border-primary/20 group focus-within:ring-primary max-h-[240px] w-full max-w-[420px] min-w-[260px] overflow-hidden border bg-gradient-to-b backdrop-blur transition-all focus-within:ring-2 focus-within:ring-offset-2 hover:shadow-lg sm:max-w-[400px] sm:min-w-[300px]\"\n          >\n            <Link\n              href={testimonial.href}\n              className=\"focus:ring-primary h-full rounded-lg focus:ring-2 focus:ring-offset-2 focus:outline-none\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              <CardContent className=\"flex h-full w-[300px] flex-col gap-4 p-4 md:w-[400px]\">\n                <div className=\"flex items-center gap-3\">\n                  <Avatar className=\"h-12 w-12\">\n                    <AvatarImage src={testimonial.image} alt={testimonial.name} loading=\"lazy\" />\n                    <AvatarFallback className=\"bg-primary text-primary-foreground text-lg font-semibold\">\n                      {testimonial.name.charAt(0)}\n                    </AvatarFallback>\n                  </Avatar>\n                  <div>\n                    <h3 className=\"text-foreground text-xl font-semibold\">{testimonial.name}</h3>\n                    <p className=\"text-muted-foreground text-sm\">@{testimonial.tag}</p>\n                  </div>\n                </div>\n                <p className=\"text-foreground line-clamp-4 overflow-hidden text-ellipsis whitespace-pre-wrap md:line-clamp-5\">\n                  {testimonial.description}\n                </p>\n              </CardContent>\n            </Link>\n          </Card>\n        ))}\n      </motion.div>\n    </div>\n  );\n};\n\n// Testimonials Main Section\nexport function Testimonials() {\n  return (\n    <section id=\"testimonials\" className=\"relative isolate w-full py-20 md:py-32\">\n      <div className=\"absolute inset-0 -z-10 bg-[radial-gradient(ellipse_at_center,rgba(from_var(--primary)_r_g_b_/_0.03),transparent_70%)]\" />\n\n      <div className=\"container mx-auto px-4 md:px-6\">\n        <motion.div\n          initial={{ opacity: 0, y: 20 }}\n          whileInView={{ opacity: 1, y: 0 }}\n          viewport={{ once: true }}\n          transition={{ duration: 0.5 }}\n          className=\"mb-16 flex flex-col items-center justify-center space-y-4 text-center\"\n        >\n          <h2 className=\"text-3xl font-bold tracking-tight md:text-5xl lg:text-6xl\">\n            Loved by <span className=\"text-primary font-serif italic\">developers</span>\n          </h2>\n          <p className=\"text-muted-foreground max-w-[600px] text-lg md:text-xl\">\n            Join thousands of developers building beautiful interfaces.\n          </p>\n        </motion.div>\n\n        {/* 🚀 Two Marquee Rows */}\n        <div className=\"flex flex-col gap-y-0\">\n          <MarqueeRow items={testimonials.slice(0, 5)} reverse={false} />\n          <MarqueeRow items={testimonials.slice(5, 10)} reverse={true} />\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "components/home/theme-preset-buttons.tsx",
    "content": "\"use client\";\n\nimport { AnimationOptions, AnimationPlaybackControls, motion, TargetAndTransition, useAnimate } from \"motion/react\";\nimport { Button } from \"@/components/ui/button\";\nimport { getPresetThemeStyles } from \"@/utils/theme-preset-helper\";\nimport { cn } from \"@/lib/utils\";\nimport { colorFormatter } from \"@/utils/color-converter\";\nimport { ThemeEditorState } from \"@/types/editor\";\nimport { useEffect, useMemo, useRef } from \"react\";\n\n// ColorBox component remains internal to ThemePresetButtons\nconst ColorBox = ({ color, radius }: { color: string; radius: string }) => {\n  return (\n    <div\n      className=\"w-3 h-3 border\"\n      style={{ backgroundColor: color, borderRadius: `calc(${radius} * 0.35)` }}\n    />\n  );\n};\n\ninterface ThemePresetButtonsProps {\n  presetNames: string[];\n  mode: \"light\" | \"dark\";\n  themeState: ThemeEditorState;\n  applyThemePreset: (presetName: string) => void;\n}\n\nexport function ThemePresetButtons({\n  presetNames,\n  mode,\n  themeState,\n  applyThemePreset,\n}: ThemePresetButtonsProps) {\n  // Use the intended slice of presets\n  const presetsToShow = presetNames || [];\n  const numUniquePresets = presetsToShow.length;\n\n  // --- Configuration ---\n  const numRows = 3;\n  const buttonWidthPx = 200; // Keep consistent with previous style\n  const gapPx = 16; // space-x-4 -> 1rem = 16px\n  const rowGapPx = 16; // gap-y-4 -> 1rem = 16px\n  const duplicationFactor = 4; // Duplicate multiple times for wider screens\n  const baseDurationPerItem = 5; // Seconds per item for animation speed\n  // ---------------------\n\n  const rowsData = useMemo(() => {\n    // Distribute presets somewhat evenly across rows\n    const presetsByRow: string[][] = Array.from({ length: numRows }, () => []);\n    presetsToShow.forEach((preset, index) => {\n      presetsByRow[index % numRows].push(preset);\n    });\n\n    // Function to create props for a single row\n    const createRowProps = (rowIndex: number) => {\n      const rowPresets = presetsByRow[rowIndex];\n      const numPresetsInRow = rowPresets.length;\n      if (numPresetsInRow === 0) return null;\n\n      const duplicatedRowPresets = Array(duplicationFactor)\n        .fill(rowPresets)\n        .flat();\n      const totalWidth = numPresetsInRow * (buttonWidthPx + gapPx);\n      const duration = numPresetsInRow * baseDurationPerItem;\n\n      const initialOffset = rowIndex === 1 ? -100 : 0;\n\n      return {\n        key: `row-${rowIndex}`,\n        presets: duplicatedRowPresets,\n        numOriginalPresets: numPresetsInRow,\n        animate: { x: [initialOffset, initialOffset - totalWidth] }, // Animate based on original set width, starting from offset\n        transition: {\n          duration,\n          ease: \"linear\" as const,\n          repeat: Infinity,\n        },\n        style: { x: initialOffset }, // Apply initial offset\n      };\n    };\n\n    return Array.from({ length: numRows }, (_, i) =>\n      createRowProps(i)\n    ).filter(Boolean);\n  }, [presetsToShow, numRows, buttonWidthPx, gapPx, duplicationFactor, baseDurationPerItem]);\n\n  // Avoid rendering if no presets\n  if (numUniquePresets === 0) {\n    return null;\n  }\n\n  return (\n    // Container for the rows with vertical gap\n    <motion.div\n      initial={{ opacity: 0, y: 20 }}\n      whileInView={{ opacity: 1, y: 0 }}\n      viewport={{ once: true }}\n      transition={{ duration: 0.5, ease: \"easeOut\" }}\n      className=\"w-full overflow-hidden mb-8 flex flex-col py-2 -my-2\"\n      style={{\n        gap: `${rowGapPx}px`,\n        maskImage:\n          \"linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%)\", // Added mask for fading edges\n        WebkitMaskImage:\n          \"linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%)\", // Added for Safari compatibility\n      }}\n    >\n      {rowsData.map((rowData) => (\n        <AnimatedRow\n          key={rowData!.key}\n          target={rowData!.animate}\n          options={rowData!.transition}\n        >\n          {/* Inner div necessary for spacing when using justify-content */}\n          <div className=\"flex flex-shrink-0\" style={{ gap: `${gapPx}px` }}>\n            {rowData!.presets.map((presetName, index) => {\n              const themeStyles = getPresetThemeStyles(presetName)[mode];\n              const bgColor = colorFormatter(themeStyles.primary, \"hsl\", \"4\");\n              const isSelected = presetName === themeState.preset;\n\n              return (\n                // Wrapper for each button\n                <motion.div\n                  key={`${presetName}-${rowData!.key}-${index}`} // More unique key\n                  className=\"flex-shrink-0 w-[200px]\" // Fixed width\n                  whileHover={{ scale: 1.02, y: -3, zIndex: 20 }} // Pop on hover\n                  transition={{ duration: 0.2, ease: \"easeOut\" }}\n                >\n                  <Button\n                    className={cn(\n                      \"flex w-full h-full items-center justify-center relative transition-colors duration-200 bg-primary/10\",\n                      \"hover:shadow-lg px-4 py-3\",\n                      isSelected ? \"ring-2 ring-primary/50 shadow-md\" : \"\"\n                    )}\n                    variant=\"ghost\"\n                    style={{\n                      backgroundColor: bgColor\n                        .replace(\"hsl\", \"hsla\")\n                        .replace(/\\s+/g, \", \")\n                        .replace(\")\", \", 0.10)\"),\n                      color: themeStyles.foreground,\n                      borderRadius: themeStyles.radius,\n                    }}\n                    onClick={() => applyThemePreset(presetName)}\n                  >\n                    <div className=\"flex items-center gap-2.5 text-center\">\n                      <div className=\"flex gap-1\">\n                        <ColorBox color={themeStyles.primary} radius={themeStyles.radius} />\n                        <ColorBox color={themeStyles.secondary} radius={themeStyles.radius} />\n                        <ColorBox color={themeStyles.accent} radius={themeStyles.radius} />\n                      </div>\n                      <span\n                        className=\"capitalize px-1 leading-tight truncate\"\n                        style={{\n                          fontFamily: themeStyles[\"font-sans\"],\n                        }}\n                      >\n                        {presetName.replace(/-/g, \" \")}\n                      </span>\n                    </div>\n                  </Button>\n                </motion.div>\n              );\n            })}\n          </div>\n        </AnimatedRow>\n      ))}\n    </motion.div>\n  );\n}\n\ninterface AnimatedRowProps {\n  children: React.ReactNode;\n  target: TargetAndTransition;\n  options: AnimationOptions;\n}\n\nfunction AnimatedRow({ children, target, options }: AnimatedRowProps) {\n  const [scope, animate] = useAnimate();\n  const controls = useRef<AnimationPlaybackControls | null>(null);\n\n  useEffect(() => {\n    controls.current = animate(scope.current, target, options);\n  }, [target, options, animate, scope]);\n\n  return (\n    <motion.div\n      ref={scope}\n      className=\"flex\"\n      onHoverStart={() => controls.current?.pause()}\n      onHoverEnd={() => controls.current?.play()}\n    >\n      {children}\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "components/home/theme-preset-selector.tsx",
    "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { defaultPresets } from \"@/utils/theme-presets\";\nimport { motion } from \"motion/react\";\n\nimport { ThemePresetButtons } from \"@/components/home/theme-preset-buttons\";\nimport { lazy, Suspense } from \"react\";\nimport { GithubCard } from \"../examples/cards/github-card\";\nimport { CardsStats } from \"../examples/cards/stats\";\nimport { Loading } from \"../loading\";\n\nconst DemoMail = lazy(() => import(\"@/components/examples/mail\"));\n\nexport function ThemePresetSelector() {\n  const { themeState, applyThemePreset } = useEditorStore();\n  const mode = themeState.currentMode;\n  const presetNames = Object.keys(defaultPresets);\n\n  return (\n    <section id=\"examples\" className=\"w-full py-20 md:py-32\">\n      <div className=\"container mx-auto px-4 md:px-6\">\n        <motion.div\n          initial={{ opacity: 0, y: 20 }}\n          whileInView={{ opacity: 1, y: 0 }}\n          viewport={{ once: true }}\n          transition={{ duration: 0.5 }}\n          className=\"mb-12 flex flex-col items-center justify-center space-y-4 text-center\"\n        >\n          <div className=\"mb-4 flex items-center justify-center gap-4\">\n            <Badge\n              className=\"rounded-full px-4 py-1.5 text-sm font-medium shadow-sm\"\n              variant=\"secondary\"\n            >\n              <span className=\"text-primary mr-1\">✦</span> Theme Presets\n            </Badge>\n          </div>\n          <h2 className=\"from-foreground to-foreground/80 bg-gradient-to-r bg-clip-text text-3xl font-bold tracking-tight text-transparent md:text-4xl\">\n            Elevate Your Design Instantly\n          </h2>\n          <p className=\"text-muted-foreground max-w-[800px] md:text-lg\">\n            Apply theme presets with a single click. See how each option enhances the look.\n          </p>\n        </motion.div>\n\n        {/* Theme Selector Buttons */}\n        <ThemePresetButtons\n          presetNames={presetNames}\n          mode={mode}\n          themeState={themeState}\n          applyThemePreset={applyThemePreset}\n        />\n        <motion.div\n          initial={{ opacity: 0, y: 20 }}\n          whileInView={{ opacity: 1, y: 0 }}\n          viewport={{ once: true }}\n          transition={{ duration: 0.5, delay: 0.2 }}\n          className=\"from-card/50 to-card/30 @container relative max-h-[60vh] overflow-hidden rounded-lg border bg-gradient-to-b shadow-lg backdrop-blur-sm md:max-h-[70vh]\"\n        >\n          <div\n            className=\"pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-16\"\n            style={{\n              background: \"linear-gradient(to bottom, rgba(255,255,255,0), var(--background))\",\n            }}\n          />\n          <Suspense fallback={<Loading />}>\n            <div className=\"hidden lg:block\">\n              <DemoMail />\n            </div>\n\n            <div className=\"flex flex-col gap-4 p-4 lg:hidden\">\n              <CardsStats />\n              <GithubCard />\n            </div>\n          </Suspense>\n        </motion.div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "components/horizontal-scroll-area.tsx",
    "content": "\"use client\";\n\nimport { ScrollArea, ScrollBar } from \"@/components/ui/scroll-area\";\nimport { useScrollStartEnd } from \"@/hooks/use-scroll-start-end\";\nimport { cn } from \"@/lib/utils\";\n\ninterface HorizontalScrollAreaProps extends React.ComponentPropsWithoutRef<typeof ScrollArea> {}\n\nexport function HorizontalScrollArea({ className, children, ...props }: HorizontalScrollAreaProps) {\n  const { isScrollStart, isScrollEnd, scrollStartRef, scrollEndRef } = useScrollStartEnd();\n\n  return (\n    <div className=\"relative w-full\">\n      <div\n        className={cn(\n          \"from-background/75 pointer-events-none absolute right-0 left-0 z-10 h-full bg-gradient-to-r to-transparent to-10% opacity-0 transition-opacity\",\n          isScrollStart ? \"opacity-0\" : \"opacity-100\"\n        )}\n      />\n      <div\n        className={cn(\n          \"from-background/75 pointer-events-none absolute right-0 left-0 z-10 h-full bg-gradient-to-l to-transparent to-10% opacity-0 transition-opacity\",\n          isScrollEnd ? \"opacity-0\" : \"opacity-100\"\n        )}\n      />\n\n      <ScrollArea {...props}>\n        <div\n          className={cn(\"relative flex w-fit flex-row items-center justify-start gap-2\", className)}\n        >\n          <div ref={scrollStartRef} className=\"absolute inset-y-0 left-px\" />\n          {children}\n          <div ref={scrollEndRef} className=\"absolute inset-y-0 right-px\" />\n        </div>\n        <ScrollBar orientation=\"horizontal\" className=\"h-1.5\" />\n      </ScrollArea>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/icons/tailwind-css.tsx",
    "content": "import { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\nimport type { SVGProps } from \"react\";\n\nexport function TailwindCSS({ className, ...props }: SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      fill=\"none\"\n      viewBox=\"0 0 54 33\"\n      className={cn(\"text-[#38bdf8]\", className)}\n      {...props}\n    >\n      <g clipPath=\"url(#a)\">\n        <path\n          fill=\"currentColor\"\n          fillRule=\"evenodd\"\n          d=\"M27 0c-7.2 0-11.7 3.6-13.5 10.8 2.7-3.6 5.85-4.95 9.45-4.05 2.054.513 3.522 2.004 5.147 3.653C30.744 13.09 33.808 16.2 40.5 16.2c7.2 0 11.7-3.6 13.5-10.8-2.7 3.6-5.85 4.95-9.45 4.05-2.054-.513-3.522-2.004-5.147-3.653C36.756 3.11 33.692 0 27 0zM13.5 16.2C6.3 16.2 1.8 19.8 0 27c2.7-3.6 5.85-4.95 9.45-4.05 2.054.514 3.522 2.004 5.147 3.653C17.244 29.29 20.308 32.4 27 32.4c7.2 0 11.7-3.6 13.5-10.8-2.7 3.6-5.85 4.95-9.45 4.05-2.054-.513-3.522-2.004-5.147-3.653C23.256 19.31 20.192 16.2 13.5 16.2z\"\n          clipRule=\"evenodd\"\n        />\n      </g>\n      <defs>\n        <clipPath id=\"a\">\n          <path fill=\"#fff\" d=\"M0 0h54v32.4H0z\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\nexport default TailwindCSS;\n"
  },
  {
    "path": "components/icons.tsx",
    "content": "type IconProps = React.HTMLAttributes<SVGElement>;\n\nexport const Icons = {\n  logo: (props: IconProps) => (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" {...props}>\n      <rect width=\"256\" height=\"256\" fill=\"none\" />\n      <line\n        x1=\"208\"\n        y1=\"128\"\n        x2=\"128\"\n        y2=\"208\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"32\"\n      />\n      <line\n        x1=\"192\"\n        y1=\"40\"\n        x2=\"40\"\n        y2=\"192\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"32\"\n      />\n    </svg>\n  ),\n  twitter: (props: IconProps) => (\n    <svg\n      {...props}\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  ),\n  gitHub: (props: IconProps) => (\n    <svg viewBox=\"0 0 438.549 438.549\" {...props}>\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      ></path>\n    </svg>\n  ),\n  radix: (props: IconProps) => (\n    <svg viewBox=\"0 0 25 25\" fill=\"none\" {...props}>\n      <path\n        d=\"M12 25C7.58173 25 4 21.4183 4 17C4 12.5817 7.58173 9 12 9V25Z\"\n        fill=\"currentcolor\"\n      ></path>\n      <path d=\"M12 0H4V8H12V0Z\" fill=\"currentcolor\"></path>\n      <path\n        d=\"M17 8C19.2091 8 21 6.20914 21 4C21 1.79086 19.2091 0 17 0C14.7909 0 13 1.79086 13 4C13 6.20914 14.7909 8 17 8Z\"\n        fill=\"currentcolor\"\n      ></path>\n    </svg>\n  ),\n  aria: (props: IconProps) => (\n    <svg role=\"img\" viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n      <path d=\"M13.966 22.624l-1.69-4.281H8.122l3.892-9.144 5.662 13.425zM8.884 1.376H0v21.248zm15.116 0h-8.884L24 22.624Z\" />\n    </svg>\n  ),\n  npm: (props: IconProps) => (\n    <svg viewBox=\"0 0 24 24\" {...props}>\n      <path\n        d=\"M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  ),\n  yarn: (props: IconProps) => (\n    <svg viewBox=\"0 0 24 24\" {...props}>\n      <path\n        d=\"M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm.768 4.105c.183 0 .363.053.525.157.125.083.287.185.755 1.154.31-.088.468-.042.551-.019.204.056.366.19.463.375.477.917.542 2.553.334 3.605-.241 1.232-.755 2.029-1.131 2.576.324.329.778.899 1.117 1.825.278.774.31 1.478.273 2.015a5.51 5.51 0 0 0 .602-.329c.593-.366 1.487-.917 2.553-.931.714-.009 1.269.445 1.353 1.103a1.23 1.23 0 0 1-.945 1.362c-.649.158-.95.278-1.821.843-1.232.797-2.539 1.242-3.012 1.39a1.686 1.686 0 0 1-.704.343c-.737.181-3.266.315-3.466.315h-.046c-.783 0-1.214-.241-1.45-.491-.658.329-1.51.19-2.122-.134a1.078 1.078 0 0 1-.58-1.153 1.243 1.243 0 0 1-.153-.195c-.162-.25-.528-.936-.454-1.946.056-.723.556-1.367.88-1.71a5.522 5.522 0 0 1 .408-2.256c.306-.727.885-1.348 1.32-1.737-.32-.537-.644-1.367-.329-2.21.227-.602.412-.936.82-1.08h-.005c.199-.074.389-.153.486-.259a3.418 3.418 0 0 1 2.298-1.103c.037-.093.079-.185.125-.283.31-.658.639-1.029 1.024-1.168a.94.94 0 0 1 .328-.06zm.006.7c-.507.016-1.001 1.519-1.001 1.519s-1.27-.204-2.266.871c-.199.218-.468.334-.746.44-.079.028-.176.023-.417.672-.371.991.625 2.094.625 2.094s-1.186.839-1.626 1.881c-.486 1.144-.338 2.261-.338 2.261s-.843.732-.899 1.487c-.051.663.139 1.2.343 1.515.227.343.51.176.51.176s-.561.653-.037.931c.477.25 1.283.394 1.71-.037.31-.31.371-1.001.486-1.283.028-.065.12.111.209.199.097.093.264.195.264.195s-.755.324-.445 1.066c.102.246.468.403 1.066.398.222-.005 2.664-.139 3.313-.296.375-.088.505-.283.505-.283s1.566-.431 2.998-1.357c.917-.598 1.293-.76 2.034-.936.612-.148.57-1.098-.241-1.084-.839.009-1.575.44-2.196.825-1.163.718-1.742.672-1.742.672l-.018-.032c-.079-.13.371-1.293-.134-2.678-.547-1.515-1.413-1.881-1.344-1.997.297-.5 1.038-1.297 1.334-2.78.176-.899.13-2.377-.269-3.151-.074-.144-.732.241-.732.241s-.616-1.371-.788-1.483a.271.271 0 0 0-.157-.046z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  ),\n  pnpm: (props: IconProps) => (\n    <svg viewBox=\"0 0 24 24\" {...props}>\n      <path\n        d=\"M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  ),\n  react: (props: IconProps) => (\n    <svg viewBox=\"0 0 24 24\" {...props}>\n      <path\n        d=\"M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  ),\n  tailwind: (props: IconProps) => (\n    <svg viewBox=\"0 0 24 24\" {...props}>\n      <path\n        d=\"M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  ),\n  google: (props: IconProps) => (\n    <svg role=\"img\" viewBox=\"0 0 24 24\" {...props}>\n      <path\n        fill=\"currentColor\"\n        d=\"M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z\"\n      />\n    </svg>\n  ),\n  apple: (props: IconProps) => (\n    <svg role=\"img\" viewBox=\"0 0 24 24\" {...props}>\n      <path\n        d=\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  ),\n  paypal: (props: IconProps) => (\n    <svg role=\"img\" viewBox=\"0 0 24 24\" {...props}>\n      <path\n        d=\"M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.106zm14.146-14.42a3.35 3.35 0 0 0-.607-.541c-.013.076-.026.175-.041.254-.93 4.778-4.005 7.201-9.138 7.201h-2.19a.563.563 0 0 0-.556.479l-1.187 7.527h-.506l-.24 1.516a.56.56 0 0 0 .554.647h3.882c.46 0 .85-.334.922-.788.06-.26.76-4.852.816-5.09a.932.932 0 0 1 .923-.788h.58c3.76 0 6.705-1.528 7.565-5.946.36-1.847.174-3.388-.777-4.471z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  ),\n  spinner: (props: IconProps) => (\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      {...props}\n    >\n      <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" />\n    </svg>\n  ),\n};\n"
  },
  {
    "path": "components/loader.tsx",
    "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nexport interface LoaderProps {\n  variant?:\n    | \"circular\"\n    | \"classic\"\n    | \"pulse\"\n    | \"pulse-dot\"\n    | \"dots\"\n    | \"typing\"\n    | \"wave\"\n    | \"bars\"\n    | \"terminal\"\n    | \"text-blink\"\n    | \"text-shimmer\"\n    | \"loading-dots\";\n  size?: \"sm\" | \"md\" | \"lg\";\n  text?: string;\n  className?: string;\n}\n\nexport function CircularLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  };\n\n  return (\n    <div\n      className={cn(\n        \"border-primary animate-spin rounded-full border-2 border-t-transparent\",\n        sizeClasses[size],\n        className\n      )}\n    >\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  );\n}\n\nexport function ClassicLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  };\n\n  const barSizes = {\n    sm: { height: \"6px\", width: \"1.5px\" },\n    md: { height: \"8px\", width: \"2px\" },\n    lg: { height: \"10px\", width: \"2.5px\" },\n  };\n\n  return (\n    <div className={cn(\"relative\", sizeClasses[size], className)}>\n      <div className=\"absolute h-full w-full\">\n        {[...Array(12)].map((_, i) => (\n          <div\n            key={i}\n            className=\"bg-primary absolute animate-[spinner-fade_1.2s_linear_infinite] rounded-full\"\n            style={{\n              top: \"0\",\n              left: \"50%\",\n              marginLeft: size === \"sm\" ? \"-0.75px\" : size === \"lg\" ? \"-1.25px\" : \"-1px\",\n              transformOrigin: `${size === \"sm\" ? \"0.75px\" : size === \"lg\" ? \"1.25px\" : \"1px\"} ${size === \"sm\" ? \"10px\" : size === \"lg\" ? \"14px\" : \"12px\"}`,\n              transform: `rotate(${i * 30}deg)`,\n              opacity: 0,\n              animationDelay: `${i * 0.1}s`,\n              height: barSizes[size].height,\n              width: barSizes[size].width,\n            }}\n          />\n        ))}\n      </div>\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  );\n}\n\nexport function PulseLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  };\n\n  return (\n    <div className={cn(\"relative\", sizeClasses[size], className)}>\n      <div className=\"border-primary absolute inset-0 animate-[thin-pulse_1.5s_ease-in-out_infinite] rounded-full border-2\" />\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  );\n}\n\nexport function PulseDotLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const sizeClasses = {\n    sm: \"size-1\",\n    md: \"size-2\",\n    lg: \"size-3\",\n  };\n\n  return (\n    <div\n      className={cn(\n        \"bg-primary animate-[pulse-dot_1.2s_ease-in-out_infinite] rounded-full\",\n        sizeClasses[size],\n        className\n      )}\n    >\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  );\n}\n\nexport function DotsLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const dotSizes = {\n    sm: \"h-1.5 w-1.5\",\n    md: \"h-2 w-2\",\n    lg: \"h-2.5 w-2.5\",\n  };\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  };\n\n  return (\n    <div className={cn(\"flex items-center space-x-1\", containerSizes[size], className)}>\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[bounce-dots_1.4s_ease-in-out_infinite] rounded-full\",\n            dotSizes[size]\n          )}\n          style={{\n            animationDelay: `${i * 160}ms`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  );\n}\n\nexport function TypingLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const dotSizes = {\n    sm: \"h-1 w-1\",\n    md: \"h-1.5 w-1.5\",\n    lg: \"h-2 w-2\",\n  };\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  };\n\n  return (\n    <div className={cn(\"flex items-center space-x-1\", containerSizes[size], className)}>\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\"bg-primary animate-[typing_1s_infinite] rounded-full\", dotSizes[size])}\n          style={{\n            animationDelay: `${i * 250}ms`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  );\n}\n\nexport function WaveLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const barWidths = {\n    sm: \"w-0.5\",\n    md: \"w-0.5\",\n    lg: \"w-1\",\n  };\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  };\n\n  const heights = {\n    sm: [\"6px\", \"9px\", \"12px\", \"9px\", \"6px\"],\n    md: [\"8px\", \"12px\", \"16px\", \"12px\", \"8px\"],\n    lg: [\"10px\", \"15px\", \"20px\", \"15px\", \"10px\"],\n  };\n\n  return (\n    <div className={cn(\"flex items-center gap-0.5\", containerSizes[size], className)}>\n      {[...Array(5)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[wave_1s_ease-in-out_infinite] rounded-full\",\n            barWidths[size]\n          )}\n          style={{\n            animationDelay: `${i * 100}ms`,\n            height: heights[size][i],\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  );\n}\n\nexport function BarsLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const barWidths = {\n    sm: \"w-1\",\n    md: \"w-1.5\",\n    lg: \"w-2\",\n  };\n\n  const containerSizes = {\n    sm: \"h-4 gap-1\",\n    md: \"h-5 gap-1.5\",\n    lg: \"h-6 gap-2\",\n  };\n\n  return (\n    <div className={cn(\"flex\", containerSizes[size], className)}>\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary h-full animate-[wave-bars_1.2s_ease-in-out_infinite]\",\n            barWidths[size]\n          )}\n          style={{\n            animationDelay: `${i * 0.2}s`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  );\n}\n\nexport function TerminalLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const cursorSizes = {\n    sm: \"h-3 w-1.5\",\n    md: \"h-4 w-2\",\n    lg: \"h-5 w-2.5\",\n  };\n\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  };\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  };\n\n  return (\n    <div className={cn(\"flex items-center space-x-1\", containerSizes[size], className)}>\n      <span className={cn(\"text-primary font-mono\", textSizes[size])}>{\">\"}</span>\n      <div className={cn(\"bg-primary animate-[blink_1s_step-end_infinite]\", cursorSizes[size])} />\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  );\n}\n\nexport function TextBlinkLoader({\n  text = \"Thinking\",\n  className,\n  size = \"md\",\n}: {\n  text?: string;\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  };\n\n  return (\n    <div\n      className={cn(\n        \"animate-[text-blink_2s_ease-in-out_infinite] font-medium\",\n        textSizes[size],\n        className\n      )}\n    >\n      {text}\n    </div>\n  );\n}\n\nexport function TextShimmerLoader({\n  text = \"Thinking\",\n  className,\n  size = \"md\",\n}: {\n  text?: string;\n  className?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  };\n\n  return (\n    <div\n      className={cn(\n        \"bg-[linear-gradient(to_right,var(--muted-foreground)_40%,var(--foreground)_60%,var(--muted-foreground)_80%)]\",\n        \"bg-size-[200%_auto] bg-clip-text font-medium text-transparent\",\n        \"animate-[shimmer_4s_infinite_linear]\",\n        textSizes[size],\n        className\n      )}\n    >\n      {text}\n    </div>\n  );\n}\n\nexport function TextDotsLoader({\n  className,\n  text = \"Thinking\",\n  size = \"md\",\n}: {\n  className?: string;\n  text?: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  };\n\n  return (\n    <div className={cn(\"inline-flex items-center\", className)}>\n      <span className={cn(\"text-primary font-medium\", textSizes[size])}>{text}</span>\n      <span className=\"inline-flex\">\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.2s]\">.</span>\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.4s]\">.</span>\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.6s]\">.</span>\n      </span>\n    </div>\n  );\n}\n\nfunction Loader({ variant = \"circular\", size = \"md\", text, className }: LoaderProps) {\n  switch (variant) {\n    case \"circular\":\n      return <CircularLoader size={size} className={className} />;\n    case \"classic\":\n      return <ClassicLoader size={size} className={className} />;\n    case \"pulse\":\n      return <PulseLoader size={size} className={className} />;\n    case \"pulse-dot\":\n      return <PulseDotLoader size={size} className={className} />;\n    case \"dots\":\n      return <DotsLoader size={size} className={className} />;\n    case \"typing\":\n      return <TypingLoader size={size} className={className} />;\n    case \"wave\":\n      return <WaveLoader size={size} className={className} />;\n    case \"bars\":\n      return <BarsLoader size={size} className={className} />;\n    case \"terminal\":\n      return <TerminalLoader size={size} className={className} />;\n    case \"text-blink\":\n      return <TextBlinkLoader text={text} size={size} className={className} />;\n    case \"text-shimmer\":\n      return <TextShimmerLoader text={text} size={size} className={className} />;\n    case \"loading-dots\":\n      return <TextDotsLoader text={text} size={size} className={className} />;\n    default:\n      return <CircularLoader size={size} className={className} />;\n  }\n}\n\nexport { Loader };\n"
  },
  {
    "path": "components/loading.tsx",
    "content": "import { cn } from \"@/lib/utils\";\n\ninterface LoadingProps {\n  className?: string;\n}\n\nexport function Loading({ className }: LoadingProps) {\n  return (\n    <div\n      className={cn(\n        \"flex items-center justify-center min-h-[400px]\",\n        className\n      )}\n    >\n      <div className=\"flex space-x-2\">\n        <div className=\"w-3 h-3 bg-primary rounded-full animate-bounce [animation-delay:-0.3s]\"></div>\n        <div className=\"w-3 h-3 bg-primary rounded-full animate-bounce [animation-delay:-0.15s]\"></div>\n        <div className=\"w-3 h-3 bg-primary rounded-full animate-bounce\"></div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/posthog-init.tsx",
    "content": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { initPostHog } from \"@/lib/posthog\";\n\nexport function PostHogInit() {\n  useEffect(() => {\n    initPostHog();\n  }, []);\n\n  return null;\n}\n"
  },
  {
    "path": "components/social-link.tsx",
    "content": "import { cn } from \"@/lib/utils\";\nimport { ArrowUpRight } from \"lucide-react\";\n\ninterface SocialLinkProps extends React.ComponentProps<\"a\"> {\n  showIcon?: boolean;\n}\n\nexport function SocialLink({ href, children, className, showIcon = false }: SocialLinkProps) {\n  return (\n    <a\n      href={href}\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n      className={cn(\n        \"text-foreground/60 hover:text-foreground inline-flex w-fit items-center transition-colors\",\n        className\n      )}\n    >\n      {children}\n      {showIcon && <ArrowUpRight className=\"size-3 transition group-hover/link:rotate-45\" />}\n    </a>\n  );\n}\n"
  },
  {
    "path": "components/tag-selector.tsx",
    "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { COMMUNITY_THEME_TAGS, MAX_TAGS_PER_THEME } from \"@/lib/constants\";\nimport { cn } from \"@/lib/utils\";\nimport { Check, ChevronsUpDown, X } from \"lucide-react\";\nimport { useState } from \"react\";\n\ninterface TagSelectorProps {\n  selectedTags: string[];\n  onTagsChange: (tags: string[]) => void;\n  disabled?: boolean;\n}\n\nexport function TagSelector({\n  selectedTags,\n  onTagsChange,\n  disabled,\n}: TagSelectorProps) {\n  const [open, setOpen] = useState(false);\n  const [search, setSearch] = useState(\"\");\n\n  const handleSelect = (tag: string) => {\n    setSearch(\"\");\n    if (selectedTags.includes(tag)) {\n      onTagsChange(selectedTags.filter((t) => t !== tag));\n    } else if (selectedTags.length < MAX_TAGS_PER_THEME) {\n      onTagsChange([...selectedTags, tag]);\n    }\n  };\n\n  const handleRemove = (tag: string) => {\n    if (disabled) return;\n    onTagsChange(selectedTags.filter((t) => t !== tag));\n  };\n\n  return (\n    <div className=\"space-y-2\">\n      <div className=\"flex items-center justify-between\">\n        <p className=\"text-sm font-medium\">\n          Tags{\" \"}\n          <span className=\"text-muted-foreground font-normal\">\n            ({selectedTags.length}/{MAX_TAGS_PER_THEME})\n          </span>\n        </p>\n      </div>\n      {selectedTags.length > 0 && (\n        <div className=\"flex flex-wrap gap-1.5\">\n          {selectedTags.map((tag) => (\n            <Badge key={tag} variant=\"secondary\" className=\"gap-1 pr-1\">\n              {tag}\n              <button\n                type=\"button\"\n                className=\"ml-0.5 rounded-full p-0.5 hover:bg-foreground/10\"\n                onClick={() => handleRemove(tag)}\n                disabled={disabled}\n              >\n                <X className=\"size-3\" />\n              </button>\n            </Badge>\n          ))}\n        </div>\n      )}\n      <Popover open={open} onOpenChange={setOpen}>\n        <PopoverTrigger asChild>\n          <button\n            type=\"button\"\n            role=\"combobox\"\n            aria-expanded={open}\n            disabled={disabled}\n            className={cn(\n              \"flex w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background\",\n              \"placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n              \"disabled:cursor-not-allowed disabled:opacity-50\"\n            )}\n          >\n            <span className=\"text-muted-foreground\">\n              {selectedTags.length >= MAX_TAGS_PER_THEME\n                ? \"Max tags selected\"\n                : \"Search tags...\"}\n            </span>\n            <ChevronsUpDown className=\"ml-2 size-4 shrink-0 opacity-50\" />\n          </button>\n        </PopoverTrigger>\n        <PopoverContent\n          className=\"w-[--radix-popover-trigger-width] p-0\"\n          align=\"start\"\n          onOpenAutoFocus={(e) => e.preventDefault()}\n        >\n          <Command shouldFilter={true}>\n            <CommandInput\n              placeholder=\"Search tags...\"\n              value={search}\n              onValueChange={setSearch}\n            />\n            <CommandList\n              className=\"max-h-[200px]\"\n              onWheelCapture={(e) => e.stopPropagation()}\n            >\n              <CommandEmpty>No tags found.</CommandEmpty>\n              <CommandGroup>\n                {COMMUNITY_THEME_TAGS.map((tag) => {\n                  const isSelected = selectedTags.includes(tag);\n                  const isAtLimit =\n                    !isSelected &&\n                    selectedTags.length >= MAX_TAGS_PER_THEME;\n                  return (\n                    <CommandItem\n                      key={tag}\n                      value={tag}\n                      disabled={isAtLimit}\n                      onSelect={() => handleSelect(tag)}\n                      className={cn(isAtLimit && \"opacity-50\")}\n                    >\n                      <Check\n                        className={cn(\n                          \"mr-2 size-4\",\n                          isSelected ? \"opacity-100\" : \"opacity-0\"\n                        )}\n                      />\n                      {tag}\n                    </CommandItem>\n                  );\n                })}\n              </CommandGroup>\n            </CommandList>\n          </Command>\n        </PopoverContent>\n      </Popover>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/theme-preview.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { ThemeStyleProps } from \"@/types/theme\";\nimport { cn } from \"@/lib/utils\";\nimport { extractFontFamily } from \"@/utils/fonts\";\nimport { loadGoogleFont } from \"@/utils/fonts/google-fonts\";\nimport { colorFormatter } from \"@/utils/color-converter\";\n\ninterface ThemePreviewProps {\n  styles: ThemeStyleProps;\n  name?: string;\n  className?: string;\n}\n\nfunction computeBoxShadow(styles: ThemeStyleProps): string | undefined {\n  const shadowColor = styles[\"shadow-color\"];\n  const shadowOpacity = parseFloat(styles[\"shadow-opacity\"] || \"0.1\");\n  const shadowBlur = styles[\"shadow-blur\"] || \"3px\";\n  const shadowSpread = styles[\"shadow-spread\"] || \"0px\";\n  const offsetX = styles[\"shadow-offset-x\"] || \"0\";\n  const offsetY = styles[\"shadow-offset-y\"] || \"1px\";\n\n  try {\n    const hsl = colorFormatter(shadowColor, \"hsl\", \"3\");\n    const color = `hsl(${hsl} / ${shadowOpacity.toFixed(2)})`;\n    return `${offsetX} ${offsetY} ${shadowBlur} ${shadowSpread} ${color}`;\n  } catch {\n    return undefined;\n  }\n}\n\nexport function ThemePreview({ styles, name, className }: ThemePreviewProps) {\n  const fontSans = styles[\"font-sans\"];\n  const fontFamily = fontSans ? extractFontFamily(fontSans) : null;\n\n  const [fontLoaded, setFontLoaded] = useState(() => {\n    if (!fontFamily) return true;\n    if (typeof document !== \"undefined\" && document.fonts) {\n      return document.fonts.check(`700 16px \"${fontFamily}\"`);\n    }\n    return false;\n  });\n\n  useEffect(() => {\n    if (!fontFamily) {\n      setFontLoaded(true);\n      return;\n    }\n\n    if (\n      typeof document !== \"undefined\" &&\n      document.fonts?.check(`700 46px \"${fontFamily}\"`)\n    ) {\n      setFontLoaded(true);\n      return;\n    }\n\n    loadGoogleFont(fontFamily, [\"400\", \"700\"]);\n\n    document.fonts\n      .load(`700 16px \"${fontFamily}\"`)\n      .then(() => setFontLoaded(true))\n      .catch(() => setFontLoaded(true));\n  }, [fontFamily]);\n\n  const c = {\n    bg: styles.background || \"#ffffff\",\n    primary: styles.primary || \"#000000\",\n    secondary: styles.secondary || \"#f1f5f9\",\n    accent: styles.accent || \"#f1f5f9\",\n    muted: styles.muted || \"#f1f5f9\",\n    fg: styles.foreground || \"#000000\",\n    primaryFg: styles[\"primary-foreground\"] || \"#ffffff\",\n    secondaryFg: styles[\"secondary-foreground\"] || \"#000000\",\n    accentFg: styles[\"accent-foreground\"] || \"#000000\",\n    mutedFg: styles[\"muted-foreground\"] || \"#666666\",\n    destructive: styles.destructive || \"#ef4444\",\n    destructiveFg: styles[\"destructive-foreground\"] || \"#ffffff\",\n    card: styles.card || \"#ffffff\",\n    cardFg: styles[\"card-foreground\"] || \"#000000\",\n    border: styles.border || \"#e2e8f0\",\n    ring: styles.ring || \"#000000\",\n    radius: styles.radius || \"0.5\",\n  };\n\n  const boxShadow = computeBoxShadow(styles);\n\n  const palette = [\n    c.primary,\n    c.secondary,\n    c.accent,\n    c.muted,\n    c.border,\n    c.card,\n  ];\n\n  return (\n    <div\n      className={cn(\n        \"relative w-full h-full select-none overflow-hidden\",\n        className\n      )}\n      style={{\n        backgroundColor: c.bg,\n        color: c.fg,\n      }}\n    >\n      {/* Color Palette - top right */}\n      <div className=\"absolute top-3 right-3 flex flex-row gap-1.5\">\n        {palette.map((color, i) => (\n          <div\n            key={i}\n            className=\"w-3 h-12\"\n            style={{\n              borderRadius: `${parseFloat(c.radius) * 0.75}rem`,\n              backgroundColor: color,\n              border: color === c.bg ? `1px solid ${c.border}` : undefined,\n              boxShadow,\n            }}\n          />\n        ))}\n      </div>\n\n      {/* Typography - bottom left */}\n      <div\n        className=\"absolute bottom-3 left-4 max-w-[80%] truncate font-medium\"\n        style={{\n          fontSize: \"1.5rem\",\n          color: c.fg,\n          fontFamily: fontLoaded ? fontSans || undefined : undefined,\n          opacity: fontFamily && !fontLoaded ? 0 : 1,\n          transition: \"opacity 0.15s ease-in\",\n        }}\n      >\n        {name || \"Aa\"}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/theme-provider.tsx",
    "content": "\"use client\";\n\nimport { createContext, useContext, useEffect } from \"react\";\nimport { useEditorStore } from \"../store/editor-store\";\nimport { applyThemeToElement } from \"@/utils/apply-theme\";\nimport { useThemePresetFromUrl } from \"@/hooks/use-theme-preset-from-url\";\n\ntype Theme = \"dark\" | \"light\";\n\ntype ThemeProviderProps = {\n  children: React.ReactNode;\n  defaultTheme?: Theme;\n};\n\ntype Coords = { x: number; y: number };\n\ntype ThemeProviderState = {\n  theme: Theme;\n  setTheme: (theme: Theme) => void;\n  toggleTheme: (coords?: Coords) => void;\n};\n\nconst initialState: ThemeProviderState = {\n  theme: \"light\",\n  setTheme: () => null,\n  toggleTheme: () => null,\n};\n\nconst ThemeProviderContext = createContext<ThemeProviderState>(initialState);\n\nexport function ThemeProvider({ children, ...props }: ThemeProviderProps) {\n  const { themeState, setThemeState } = useEditorStore();\n\n  // Handle theme preset from URL\n  useThemePresetFromUrl();\n\n  useEffect(() => {\n    const root = document.documentElement;\n    if (!root) return;\n\n    applyThemeToElement(themeState, root);\n  }, [themeState]);\n\n  const handleThemeChange = (newMode: Theme) => {\n    setThemeState({ ...themeState, currentMode: newMode });\n  };\n\n  const handleThemeToggle = (coords?: Coords) => {\n    const root = document.documentElement;\n    const newMode = themeState.currentMode === \"light\" ? \"dark\" : \"light\";\n\n    const prefersReducedMotion = window.matchMedia(\n      \"(prefers-reduced-motion: reduce)\"\n    ).matches;\n\n    if (!document.startViewTransition || prefersReducedMotion) {\n      handleThemeChange(newMode);\n      return;\n    }\n\n    if (coords) {\n      root.style.setProperty(\"--x\", `${coords.x}px`);\n      root.style.setProperty(\"--y\", `${coords.y}px`);\n    }\n\n    document.startViewTransition(() => {\n      handleThemeChange(newMode);\n    });\n  };\n\n  const value: ThemeProviderState = {\n    theme: themeState.currentMode,\n    setTheme: handleThemeChange,\n    toggleTheme: handleThemeToggle,\n  };\n\n  return (\n    <ThemeProviderContext.Provider {...props} value={value}>\n      {children}\n    </ThemeProviderContext.Provider>\n  );\n}\n\nexport const useTheme = () => {\n  const context = useContext(ThemeProviderContext);\n\n  if (context === undefined) {\n    throw new Error(\"useTheme must be used within a ThemeProvider\");\n  }\n\n  return context;\n};\n"
  },
  {
    "path": "components/theme-script.tsx",
    "content": "\"use client\";\n\nimport { defaultDarkThemeStyles, defaultLightThemeStyles } from \"@/config/theme\";\n\nexport function ThemeScript() {\n  const scriptContent = `\n    // ----- FONT LOADING UTILITIES -----\n    const DEFAULT_FONT_WEIGHTS = [\"400\"];\n\n    function extractFontFamily(fontFamilyValue) {\n      if (!fontFamilyValue) return null;\n      const firstFont = fontFamilyValue.split(\",\")[0].trim();\n      const cleanFont = firstFont.replace(/['\"]/g, \"\");\n      const systemFonts = [\n        \"ui-sans-serif\", \"ui-serif\", \"ui-monospace\", \"system-ui\",\n        \"sans-serif\", \"serif\", \"monospace\", \"cursive\", \"fantasy\"\n      ];\n      if (systemFonts.includes(cleanFont.toLowerCase())) {\n        return null;\n      }\n      return cleanFont;\n    }\n\n    function buildFontCssUrl(family, weights) {\n      weights = weights || DEFAULT_FONT_WEIGHTS;\n      const encodedFamily = encodeURIComponent(family);\n      const weightsParam = weights.join(\";\"); \n      return \\`https://fonts.googleapis.com/css2?family=\\${encodedFamily}:wght@\\${weightsParam}&display=swap\\`;\n    }\n\n    function loadGoogleFont(family, weights) {\n      weights = weights || DEFAULT_FONT_WEIGHTS;\n      const href = buildFontCssUrl(family, weights);\n      const existing = document.querySelector(\\`link[href=\"\\${href}\"]\\`);\n      if (existing) return;\n\n      const link = document.createElement(\"link\");\n      link.rel = \"stylesheet\";\n      link.href = href;\n      document.head.appendChild(link);\n    }\n\n    // ----- THEME INITIALIZATION -----\n    (function() {\n      const storageKey = \"editor-storage\";\n      const root = document.documentElement;\n      const defaultLightStyles = ${JSON.stringify(defaultLightThemeStyles)};\n      const defaultDarkStyles = ${JSON.stringify(defaultDarkThemeStyles)};\n\n      let themeState = null;\n      try {\n        const persistedStateJSON = localStorage.getItem(storageKey);\n        if (persistedStateJSON) {\n          themeState = JSON.parse(persistedStateJSON)?.state?.themeState;\n        }\n      } catch (e) {\n        console.warn(\"Theme initialization: Failed to read/parse localStorage:\", e);\n      }\n\n      const prefersDark = window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n      const mode = themeState?.currentMode ?? (prefersDark ? \"dark\" : \"light\");\n\n      const activeStyles =\n        mode === \"dark\"\n          ? themeState?.styles?.dark || defaultDarkStyles\n          : themeState?.styles?.light || defaultLightStyles;\n\n      const stylesToApply = Object.keys(defaultLightStyles);\n\n      // Apply Theme Styles properties\n      for (const styleName of stylesToApply) {\n        const value = activeStyles[styleName];\n        if (value !== undefined) {\n          root.style.setProperty(\\`--\\${styleName}\\`, value);\n        }\n      }\n\n      // Load Google fonts *immediately*\n      try {\n        if (activeStyles) {\n          const currentFonts = {\n            sans: activeStyles[\"font-sans\"],\n            serif: activeStyles[\"font-serif\"],\n            mono: activeStyles[\"font-mono\"],\n          };\n\n          Object.entries(currentFonts).forEach(([_type, fontValue]) => {\n            const fontFamily = extractFontFamily(fontValue);\n            if (fontFamily) {\n              loadGoogleFont(fontFamily, DEFAULT_FONT_WEIGHTS);\n            }\n          });\n        }\n      } catch (e) {\n        console.warn(\"Theme Script initialization: Failed to load Google fonts:\", e);\n      }\n    })();\n  `;\n\n  return <script dangerouslySetInnerHTML={{ __html: scriptContent }} suppressHydrationWarning />;\n}\n"
  },
  {
    "path": "components/theme-toggle.tsx",
    "content": "\"use client\";\n\nimport { useTheme } from \"@/components/theme-provider\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { Moon, Sun } from \"lucide-react\";\nimport { TooltipWrapper } from \"./tooltip-wrapper\";\n\ninterface ThemeToggleProps extends React.ComponentProps<typeof Button> {}\n\nexport function ThemeToggle({ className, ...props }: ThemeToggleProps) {\n  const { theme, toggleTheme } = useTheme();\n\n  const handleThemeToggle = (event: React.MouseEvent<HTMLButtonElement>) => {\n    const { clientX: x, clientY: y } = event;\n    toggleTheme({ x, y });\n  };\n\n  return (\n    <TooltipWrapper label=\"Toggle theme\" asChild>\n      <Button className={cn(\"cursor-pointer\", className)} {...props} onClick={handleThemeToggle}>\n        {theme === \"light\" ? <Sun /> : <Moon />}\n      </Button>\n    </TooltipWrapper>\n  );\n}\n"
  },
  {
    "path": "components/theme-view.tsx",
    "content": "\"use client\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { toast } from \"@/components/ui/use-toast\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { useToggleLike } from \"@/hooks/themes\";\nimport { useSessionGuard } from \"@/hooks/use-guards\";\nimport { usePostLoginAction } from \"@/hooks/use-post-login-action\";\nimport type { Theme } from \"@/types/theme\";\nimport { cn } from \"@/lib/utils\";\nimport { Calendar, Edit, Heart, Moon, Share2, Sun } from \"lucide-react\";\nimport { notFound, useRouter } from \"next/navigation\";\nimport { useEffect, useState } from \"react\";\nimport { CodeButton } from \"./editor/action-bar/components/code-button\";\nimport { CodePanelDialog } from \"./editor/code-panel-dialog\";\nimport ThemePreviewPanel from \"./editor/theme-preview-panel\";\nimport { DialogActionsProvider } from \"@/hooks/use-dialog-actions\";\n\ninterface CommunityData {\n  communityThemeId: string;\n  author: { id: string; name: string; image: string | null };\n  likeCount: number;\n  isLikedByMe: boolean;\n  publishedAt: string;\n  tags: string[];\n}\n\ninterface ThemeViewProps {\n  theme: Theme;\n  communityData?: CommunityData | null;\n}\n\nexport default function ThemeView({ theme, communityData }: ThemeViewProps) {\n  const { themeState, setThemeState, saveThemeCheckpoint, restoreThemeCheckpoint } =\n    useEditorStore();\n  const router = useRouter();\n  const currentMode = themeState.currentMode;\n  const [codePanelOpen, setCodePanelOpen] = useState(false);\n\n  useEffect(() => {\n    saveThemeCheckpoint();\n    setThemeState({\n      ...themeState,\n      styles: theme.styles,\n    });\n    return () => {\n      restoreThemeCheckpoint();\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [theme, saveThemeCheckpoint, setThemeState, restoreThemeCheckpoint]);\n\n  if (!theme) {\n    notFound();\n  }\n\n  const toggleTheme = () => {\n    setThemeState({\n      ...themeState,\n      currentMode: currentMode === \"light\" ? \"dark\" : \"light\",\n    });\n  };\n\n  const handleOpenInEditor = () => {\n    setThemeState({\n      ...themeState,\n      styles: theme.styles,\n    });\n    saveThemeCheckpoint();\n    router.push(\"/editor/theme\");\n  };\n\n  const handleShare = () => {\n    const url = `https://tweakcn.com/themes/${theme.id}`;\n    navigator.clipboard.writeText(url);\n    toast({\n      title: \"Theme URL copied to clipboard!\",\n    });\n  };\n\n  const publishedDate = communityData\n    ? new Date(communityData.publishedAt).toLocaleDateString(\"en-US\", {\n        day: \"numeric\",\n        month: \"long\",\n        year: \"numeric\",\n      })\n    : null;\n\n  return (\n    <>\n      <div className=\"space-y-4\">\n        <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n          <div className=\"space-y-3\">\n            <h1 className=\"text-3xl font-bold\">{theme.name}</h1>\n            {communityData && (\n              <>\n                <div className=\"flex flex-wrap items-center gap-4 text-sm text-muted-foreground\">\n                  <CommunityAuthorInfo communityData={communityData} />\n                  <div className=\"flex items-center gap-1.5\">\n                    <Calendar className=\"size-3.5\" />\n                    <span>{publishedDate}</span>\n                  </div>\n                  <div className=\"flex items-center gap-1.5\">\n                    <Heart className=\"size-3.5\" />\n                    <span>\n                      {communityData.likeCount}{\" \"}\n                      {communityData.likeCount === 1 ? \"like\" : \"likes\"}\n                    </span>\n                  </div>\n                </div>\n                {communityData.tags.length > 0 && (\n                  <div className=\"flex flex-wrap gap-1.5\">\n                    {communityData.tags.map((tag) => (\n                      <Badge key={tag} variant=\"secondary\" className=\"text-xs\">\n                        {tag}\n                      </Badge>\n                    ))}\n                  </div>\n                )}\n              </>\n            )}\n          </div>\n          <div className=\"flex shrink-0 flex-wrap items-center gap-2\">\n            {communityData && <LikeButton communityData={communityData} />}\n            <Button variant=\"outline\" size=\"icon\" onClick={toggleTheme}>\n              {currentMode === \"dark\" ? (\n                <Sun className=\"size-4\" />\n              ) : (\n                <Moon className=\"size-4\" />\n              )}\n            </Button>\n            <CodeButton\n              variant=\"outline\"\n              size=\"default\"\n              onClick={() => setCodePanelOpen(true)}\n            />\n            <Button variant=\"outline\" size=\"default\" onClick={handleShare}>\n              <Share2 className=\"size-4\" />\n              Share\n            </Button>\n            <Button variant=\"outline\" size=\"default\" onClick={handleOpenInEditor}>\n              <Edit className=\"size-4\" />\n              Open in Editor\n            </Button>\n          </div>\n        </div>\n      </div>\n\n      <DialogActionsProvider>\n        <div className=\"-m-4 mt-6 flex h-[min(80svh,900px)] flex-col\">\n          <ThemePreviewPanel styles={theme.styles} currentMode={currentMode} themeId={theme.id} themeName={theme.name} />\n        </div>\n\n        <CodePanelDialog\n          open={codePanelOpen}\n          onOpenChange={setCodePanelOpen}\n          themeEditorState={themeState}\n          themeId={theme.id}\n        />\n      </DialogActionsProvider>\n    </>\n  );\n}\n\nfunction CommunityAuthorInfo({\n  communityData,\n}: {\n  communityData: CommunityData;\n}) {\n  const authorInitials = communityData.author.name\n    ?.split(\" \")\n    .map((n) => n[0])\n    .join(\"\")\n    .slice(0, 2)\n    .toUpperCase();\n\n  return (\n    <div className=\"flex items-center gap-1.5\">\n      <Avatar className=\"h-5 w-5\">\n        {communityData.author.image && (\n          <AvatarImage\n            src={communityData.author.image}\n            alt={communityData.author.name}\n          />\n        )}\n        <AvatarFallback className=\"text-[9px]\">{authorInitials}</AvatarFallback>\n      </Avatar>\n      <span className=\"font-medium text-foreground\">\n        {communityData.author.name}\n      </span>\n    </div>\n  );\n}\n\nfunction LikeButton({ communityData }: { communityData: CommunityData }) {\n  const toggleLike = useToggleLike();\n  const { checkValidSession } = useSessionGuard();\n  const [liked, setLiked] = useState(communityData.isLikedByMe);\n  const [count, setCount] = useState(communityData.likeCount);\n\n  usePostLoginAction(\"LIKE_THEME\", (data?: { communityThemeId: string }) => {\n    if (data?.communityThemeId === communityData.communityThemeId) {\n      toggleLike.mutate(communityData.communityThemeId, {\n        onSuccess: (result) => {\n          setLiked(result.liked);\n          setCount(result.likeCount);\n        },\n      });\n    }\n  });\n\n  const handleLike = () => {\n    if (\n      !checkValidSession(\"signin\", \"LIKE_THEME\", {\n        communityThemeId: communityData.communityThemeId,\n      })\n    ) {\n      return;\n    }\n\n    // Optimistic update\n    setLiked((prev) => !prev);\n    setCount((prev) => (liked ? prev - 1 : prev + 1));\n\n    toggleLike.mutate(communityData.communityThemeId, {\n      onSuccess: (result) => {\n        setLiked(result.liked);\n        setCount(result.likeCount);\n      },\n      onError: () => {\n        // Rollback\n        setLiked((prev) => !prev);\n        setCount((prev) => (liked ? prev + 1 : prev - 1));\n      },\n    });\n  };\n\n  return (\n    <Button\n      variant=\"outline\"\n      size=\"default\"\n      onClick={handleLike}\n      className={cn(liked && \"text-red-500\")}\n    >\n      <Heart className={cn(\"size-4\", liked && \"fill-current\")} />\n      {count > 0 ? count : \"Like\"}\n    </Button>\n  );\n}\n"
  },
  {
    "path": "components/tooltip-wrapper.tsx",
    "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { ComponentProps } from \"react\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"./ui/tooltip\";\n\nexport function TooltipWrapper({\n  label,\n  command,\n  className,\n  children,\n  ...props\n}: ComponentProps<typeof TooltipTrigger> & {\n  label: string;\n  command?: React.ReactNode;\n}) {\n  return (\n    <Tooltip key={label}>\n      <TooltipTrigger className={cn(className)} {...props}>\n        {children}\n      </TooltipTrigger>\n\n      <TooltipContent>\n        <span className=\"flex items-center gap-[1ch]\">\n          {label}\n          {command && (\n            <kbd className=\"bg-muted text-muted-foreground flex items-center gap-[0.5ch] rounded px-1.5 py-0.5 font-mono text-xs [&>svg]:size-3\">\n              {command}\n            </kbd>\n          )}\n        </span>\n      </TooltipContent>\n    </Tooltip>\n  );\n}\n"
  },
  {
    "path": "components/ui/accordion.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDownIcon } from \"lucide-react\"\nimport { Accordion as AccordionPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Accordion({\n  ...props\n}: React.ComponentProps<typeof AccordionPrimitive.Root>) {\n  return <AccordionPrimitive.Root data-slot=\"accordion\" {...props} />\n}\n\nfunction AccordionItem({\n  className,\n  ...props\n}: React.ComponentProps<typeof AccordionPrimitive.Item>) {\n  return (\n    <AccordionPrimitive.Item\n      data-slot=\"accordion-item\"\n      className={cn(\"border-b last:border-b-0\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction AccordionTrigger({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {\n  return (\n    <AccordionPrimitive.Header className=\"flex\">\n      <AccordionPrimitive.Trigger\n        data-slot=\"accordion-trigger\"\n        className={cn(\n          \"flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <ChevronDownIcon className=\"pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200\" />\n      </AccordionPrimitive.Trigger>\n    </AccordionPrimitive.Header>\n  )\n}\n\nfunction AccordionContent({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof AccordionPrimitive.Content>) {\n  return (\n    <AccordionPrimitive.Content\n      data-slot=\"accordion-content\"\n      className=\"overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down\"\n      {...props}\n    >\n      <div className={cn(\"pt-0 pb-4\", className)}>{children}</div>\n    </AccordionPrimitive.Content>\n  )\n}\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent }\n"
  },
  {
    "path": "components/ui/alert-dialog.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AlertDialog as AlertDialogPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\nfunction AlertDialog({\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {\n  return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />\n}\n\nfunction AlertDialogTrigger({\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {\n  return (\n    <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />\n  )\n}\n\nfunction AlertDialogPortal({\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {\n  return (\n    <AlertDialogPrimitive.Portal data-slot=\"alert-dialog-portal\" {...props} />\n  )\n}\n\nfunction AlertDialogOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {\n  return (\n    <AlertDialogPrimitive.Overlay\n      data-slot=\"alert-dialog-overlay\"\n      className={cn(\n        \"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogContent({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {\n  size?: \"default\" | \"sm\"\n}) {\n  return (\n    <AlertDialogPortal>\n      <AlertDialogOverlay />\n      <AlertDialogPrimitive.Content\n        data-slot=\"alert-dialog-content\"\n        data-size={size}\n        className={cn(\n          \"group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[size=sm]:max-w-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[size=default]:sm:max-w-lg\",\n          className\n        )}\n        {...props}\n      />\n    </AlertDialogPortal>\n  )\n}\n\nfunction AlertDialogHeader({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"alert-dialog-header\"\n      className={cn(\n        \"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogFooter({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"alert-dialog-footer\"\n      className={cn(\n        \"flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {\n  return (\n    <AlertDialogPrimitive.Title\n      data-slot=\"alert-dialog-title\"\n      className={cn(\n        \"text-lg font-semibold sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {\n  return (\n    <AlertDialogPrimitive.Description\n      data-slot=\"alert-dialog-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogMedia({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"alert-dialog-media\"\n      className={cn(\n        \"mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDialogAction({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &\n  Pick<React.ComponentProps<typeof Button>, \"variant\" | \"size\">) {\n  return (\n    <Button variant={variant} size={size} asChild>\n      <AlertDialogPrimitive.Action\n        data-slot=\"alert-dialog-action\"\n        className={cn(className)}\n        {...props}\n      />\n    </Button>\n  )\n}\n\nfunction AlertDialogCancel({\n  className,\n  variant = \"outline\",\n  size = \"default\",\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &\n  Pick<React.ComponentProps<typeof Button>, \"variant\" | \"size\">) {\n  return (\n    <Button variant={variant} size={size} asChild>\n      <AlertDialogPrimitive.Cancel\n        data-slot=\"alert-dialog-cancel\"\n        className={cn(className)}\n        {...props}\n      />\n    </Button>\n  )\n}\n\nexport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogMedia,\n  AlertDialogOverlay,\n  AlertDialogPortal,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n}\n"
  },
  {
    "path": "components/ui/alert.tsx",
    "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst alertVariants = cva(\n  \"relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-card text-card-foreground\",\n        destructive:\n          \"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction Alert({\n  className,\n  variant,\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof alertVariants>) {\n  return (\n    <div\n      data-slot=\"alert\"\n      role=\"alert\"\n      className={cn(alertVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction AlertTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"alert-title\"\n      className={cn(\n        \"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertDescription({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"alert-description\"\n      className={cn(\n        \"col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Alert, AlertTitle, AlertDescription }\n"
  },
  {
    "path": "components/ui/aspect-ratio.tsx",
    "content": "\"use client\"\n\nimport { AspectRatio as AspectRatioPrimitive } from \"radix-ui\"\n\nfunction AspectRatio({\n  ...props\n}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {\n  return <AspectRatioPrimitive.Root data-slot=\"aspect-ratio\" {...props} />\n}\n\nexport { AspectRatio }\n"
  },
  {
    "path": "components/ui/avatar.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Avatar as AvatarPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Avatar({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Root> & {\n  size?: \"default\" | \"sm\" | \"lg\"\n}) {\n  return (\n    <AvatarPrimitive.Root\n      data-slot=\"avatar\"\n      data-size={size}\n      className={cn(\n        \"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarImage({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Image>) {\n  return (\n    <AvatarPrimitive.Image\n      data-slot=\"avatar-image\"\n      className={cn(\"aspect-square size-full\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {\n  return (\n    <AvatarPrimitive.Fallback\n      data-slot=\"avatar-fallback\"\n      className={cn(\n        \"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"avatar-badge\"\n      className={cn(\n        \"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background select-none\",\n        \"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden\",\n        \"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2\",\n        \"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"avatar-group\"\n      className={cn(\n        \"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"avatar-group-count\"\n      className={cn(\n        \"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarBadge,\n  AvatarGroup,\n  AvatarGroupCount,\n}\n"
  },
  {
    "path": "components/ui/badge.tsx",
    "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n  \"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground [a&]:hover:bg-primary/90\",\n        secondary:\n          \"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90\",\n        destructive:\n          \"bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90\",\n        outline:\n          \"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n        ghost: \"[a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 [a&]:hover:underline\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction Badge({\n  className,\n  variant = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"span\"> &\n  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : \"span\"\n\n  return (\n    <Comp\n      data-slot=\"badge\"\n      data-variant={variant}\n      className={cn(badgeVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nexport { Badge, badgeVariants }\n"
  },
  {
    "path": "components/ui/base-ui-tabs.tsx",
    "content": "import * as React from \"react\";\nimport { Tabs as TabsPrimitive } from \"@base-ui-components/react/tabs\";\nimport { cn } from \"@/lib/utils\";\n\nconst Tabs = TabsPrimitive.Root;\n\nconst TabsList = React.forwardRef<\n  React.ComponentRef<typeof TabsPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.List\n    ref={ref}\n    className={cn(\n      \"bg-muted text-muted-foreground relative inline-flex h-10 items-center justify-center rounded-md p-1\",\n      className\n    )}\n    {...props}\n  />\n));\nTabsList.displayName = TabsPrimitive.List.displayName;\n\nconst TabsIndicator = React.forwardRef<\n  React.ComponentRef<typeof TabsPrimitive.Indicator>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Indicator>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Indicator\n    ref={ref}\n    className={cn(\n      \"bg-secondary absolute top-1/2 left-0 z-0 h-7 w-(--active-tab-width) translate-x-(--active-tab-left) -translate-y-1/2 rounded-full shadow-xs transition-all duration-200 ease-in-out\",\n      className\n    )}\n    {...props}\n  />\n));\nTabsIndicator.displayName = TabsPrimitive.Indicator.displayName;\n\nconst TabsTrigger = React.forwardRef<\n  React.ComponentRef<typeof TabsPrimitive.Tab>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Tab>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Tab\n    ref={ref}\n    className={cn(\n      \"ring-offset-background focus-visible:ring-ring data-selected:text-foreground z-1 inline-flex items-center justify-center rounded-sm px-3 py-1.5 text-sm font-medium whitespace-nowrap focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\",\n      className\n    )}\n    {...props}\n  />\n));\nTabsTrigger.displayName = TabsPrimitive.Tab.displayName;\n\nconst TabsContent = React.forwardRef<\n  React.ComponentRef<typeof TabsPrimitive.Panel>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Panel>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Panel\n    ref={ref}\n    className={cn(\n      \"ring-offset-background focus-visible:ring-ring mt-2 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden\",\n      className\n    )}\n    {...props}\n  />\n));\nTabsContent.displayName = TabsPrimitive.Panel.displayName;\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, TabsIndicator };\n"
  },
  {
    "path": "components/ui/breadcrumb.tsx",
    "content": "import * as React from \"react\"\nimport { ChevronRight, MoreHorizontal } from \"lucide-react\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Breadcrumb({ ...props }: React.ComponentProps<\"nav\">) {\n  return <nav aria-label=\"breadcrumb\" data-slot=\"breadcrumb\" {...props} />\n}\n\nfunction BreadcrumbList({ className, ...props }: React.ComponentProps<\"ol\">) {\n  return (\n    <ol\n      data-slot=\"breadcrumb-list\"\n      className={cn(\n        \"flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground sm:gap-2.5\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbItem({ className, ...props }: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"breadcrumb-item\"\n      className={cn(\"inline-flex items-center gap-1.5\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbLink({\n  asChild,\n  className,\n  ...props\n}: React.ComponentProps<\"a\"> & {\n  asChild?: boolean\n}) {\n  const Comp = asChild ? Slot.Root : \"a\"\n\n  return (\n    <Comp\n      data-slot=\"breadcrumb-link\"\n      className={cn(\"transition-colors hover:text-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbPage({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"breadcrumb-page\"\n      role=\"link\"\n      aria-disabled=\"true\"\n      aria-current=\"page\"\n      className={cn(\"font-normal text-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"breadcrumb-separator\"\n      role=\"presentation\"\n      aria-hidden=\"true\"\n      className={cn(\"[&>svg]:size-3.5\", className)}\n      {...props}\n    >\n      {children ?? <ChevronRight />}\n    </li>\n  )\n}\n\nfunction BreadcrumbEllipsis({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"breadcrumb-ellipsis\"\n      role=\"presentation\"\n      aria-hidden=\"true\"\n      className={cn(\"flex size-9 items-center justify-center\", className)}\n      {...props}\n    >\n      <MoreHorizontal className=\"size-4\" />\n      <span className=\"sr-only\">More</span>\n    </span>\n  )\n}\n\nexport {\n  Breadcrumb,\n  BreadcrumbList,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n  BreadcrumbEllipsis,\n}\n"
  },
  {
    "path": "components/ui/button.tsx",
    "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n  \"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40\",\n        outline:\n          \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost:\n          \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n        accent: \"bg-accent text-accent-foreground shadow-sm hover:bg-accent/80\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2\",\n        xs: \"h-6 gap-1 rounded-md px-2 text-xs [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-8 gap-1.5 rounded-md px-3\",\n        lg: \"h-10 rounded-md px-8\",\n        icon: \"size-9\",\n        \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\": \"size-8\",\n        \"icon-lg\": \"size-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean\n  }) {\n  const Comp = asChild ? Slot.Root : \"button\"\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "components/ui/calendar.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  ChevronDownIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n} from \"lucide-react\"\nimport {\n  DayPicker,\n  getDefaultClassNames,\n  type DayButton,\n} from \"react-day-picker\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button, buttonVariants } from \"@/components/ui/button\"\n\nfunction Calendar({\n  className,\n  classNames,\n  showOutsideDays = true,\n  captionLayout = \"label\",\n  buttonVariant = \"ghost\",\n  formatters,\n  components,\n  ...props\n}: React.ComponentProps<typeof DayPicker> & {\n  buttonVariant?: React.ComponentProps<typeof Button>[\"variant\"]\n}) {\n  const defaultClassNames = getDefaultClassNames()\n\n  return (\n    <DayPicker\n      showOutsideDays={showOutsideDays}\n      className={cn(\n        \"group/calendar bg-background p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent\",\n        String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n        String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n        className\n      )}\n      captionLayout={captionLayout}\n      formatters={{\n        formatMonthDropdown: (date) =>\n          date.toLocaleString(\"default\", { month: \"short\" }),\n        ...formatters,\n      }}\n      classNames={{\n        root: cn(\"w-fit\", defaultClassNames.root),\n        months: cn(\n          \"relative flex flex-col gap-4 md:flex-row\",\n          defaultClassNames.months\n        ),\n        month: cn(\"flex w-full flex-col gap-4\", defaultClassNames.month),\n        nav: cn(\n          \"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1\",\n          defaultClassNames.nav\n        ),\n        button_previous: cn(\n          buttonVariants({ variant: buttonVariant }),\n          \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n          defaultClassNames.button_previous\n        ),\n        button_next: cn(\n          buttonVariants({ variant: buttonVariant }),\n          \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n          defaultClassNames.button_next\n        ),\n        month_caption: cn(\n          \"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)\",\n          defaultClassNames.month_caption\n        ),\n        dropdowns: cn(\n          \"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium\",\n          defaultClassNames.dropdowns\n        ),\n        dropdown_root: cn(\n          \"relative rounded-md border border-input shadow-xs has-focus:border-ring has-focus:ring-[3px] has-focus:ring-ring/50\",\n          defaultClassNames.dropdown_root\n        ),\n        dropdown: cn(\n          \"absolute inset-0 bg-popover opacity-0\",\n          defaultClassNames.dropdown\n        ),\n        caption_label: cn(\n          \"font-medium select-none\",\n          captionLayout === \"label\"\n            ? \"text-sm\"\n            : \"flex h-8 items-center gap-1 rounded-md pr-1 pl-2 text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground\",\n          defaultClassNames.caption_label\n        ),\n        table: \"w-full border-collapse\",\n        weekdays: cn(\"flex\", defaultClassNames.weekdays),\n        weekday: cn(\n          \"flex-1 rounded-md text-[0.8rem] font-normal text-muted-foreground select-none\",\n          defaultClassNames.weekday\n        ),\n        week: cn(\"mt-2 flex w-full\", defaultClassNames.week),\n        week_number_header: cn(\n          \"w-(--cell-size) select-none\",\n          defaultClassNames.week_number_header\n        ),\n        week_number: cn(\n          \"text-[0.8rem] text-muted-foreground select-none\",\n          defaultClassNames.week_number\n        ),\n        day: cn(\n          \"group/day relative aspect-square h-full w-full p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-md\",\n          props.showWeekNumber\n            ? \"[&:nth-child(2)[data-selected=true]_button]:rounded-l-md\"\n            : \"[&:first-child[data-selected=true]_button]:rounded-l-md\",\n          defaultClassNames.day\n        ),\n        range_start: cn(\n          \"rounded-l-md bg-accent\",\n          defaultClassNames.range_start\n        ),\n        range_middle: cn(\"rounded-none\", defaultClassNames.range_middle),\n        range_end: cn(\"rounded-r-md bg-accent\", defaultClassNames.range_end),\n        today: cn(\n          \"rounded-md bg-accent text-accent-foreground data-[selected=true]:rounded-none\",\n          defaultClassNames.today\n        ),\n        outside: cn(\n          \"text-muted-foreground aria-selected:text-muted-foreground\",\n          defaultClassNames.outside\n        ),\n        disabled: cn(\n          \"text-muted-foreground opacity-50\",\n          defaultClassNames.disabled\n        ),\n        hidden: cn(\"invisible\", defaultClassNames.hidden),\n        ...classNames,\n      }}\n      components={{\n        Root: ({ className, rootRef, ...props }) => {\n          return (\n            <div\n              data-slot=\"calendar\"\n              ref={rootRef}\n              className={cn(className)}\n              {...props}\n            />\n          )\n        },\n        Chevron: ({ className, orientation, ...props }) => {\n          if (orientation === \"left\") {\n            return (\n              <ChevronLeftIcon className={cn(\"size-4\", className)} {...props} />\n            )\n          }\n\n          if (orientation === \"right\") {\n            return (\n              <ChevronRightIcon\n                className={cn(\"size-4\", className)}\n                {...props}\n              />\n            )\n          }\n\n          return (\n            <ChevronDownIcon className={cn(\"size-4\", className)} {...props} />\n          )\n        },\n        DayButton: CalendarDayButton,\n        WeekNumber: ({ children, ...props }) => {\n          return (\n            <td {...props}>\n              <div className=\"flex size-(--cell-size) items-center justify-center text-center\">\n                {children}\n              </div>\n            </td>\n          )\n        },\n        ...components,\n      }}\n      {...props}\n    />\n  )\n}\n\nfunction CalendarDayButton({\n  className,\n  day,\n  modifiers,\n  ...props\n}: React.ComponentProps<typeof DayButton>) {\n  const defaultClassNames = getDefaultClassNames()\n\n  const ref = React.useRef<HTMLButtonElement>(null)\n  React.useEffect(() => {\n    if (modifiers.focused) ref.current?.focus()\n  }, [modifiers.focused])\n\n  return (\n    <Button\n      ref={ref}\n      variant=\"ghost\"\n      size=\"icon\"\n      data-day={day.date.toLocaleDateString()}\n      data-selected-single={\n        modifiers.selected &&\n        !modifiers.range_start &&\n        !modifiers.range_end &&\n        !modifiers.range_middle\n      }\n      data-range-start={modifiers.range_start}\n      data-range-end={modifiers.range_end}\n      data-range-middle={modifiers.range_middle}\n      className={cn(\n        \"flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-accent-foreground [&>span]:text-xs [&>span]:opacity-70\",\n        defaultClassNames.day,\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Calendar, CalendarDayButton }\n"
  },
  {
    "path": "components/ui/card.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Card({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card\"\n      className={cn(\n        \"flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        \"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn(\"leading-none font-semibold\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\n        \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-content\"\n      className={cn(\"px-6\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn(\"flex items-center px-6 [.border-t]:pt-6\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n}\n"
  },
  {
    "path": "components/ui/carousel.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport useEmblaCarousel, {\n  type UseEmblaCarouselType,\n} from \"embla-carousel-react\"\nimport { ArrowLeft, ArrowRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\ntype CarouselProps = {\n  opts?: CarouselOptions\n  plugins?: CarouselPlugin\n  orientation?: \"horizontal\" | \"vertical\"\n  setApi?: (api: CarouselApi) => void\n}\n\ntype CarouselContextProps = {\n  carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n  api: ReturnType<typeof useEmblaCarousel>[1]\n  scrollPrev: () => void\n  scrollNext: () => void\n  canScrollPrev: boolean\n  canScrollNext: boolean\n} & CarouselProps\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n  const context = React.useContext(CarouselContext)\n\n  if (!context) {\n    throw new Error(\"useCarousel must be used within a <Carousel />\")\n  }\n\n  return context\n}\n\nfunction Carousel({\n  orientation = \"horizontal\",\n  opts,\n  setApi,\n  plugins,\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & CarouselProps) {\n  const [carouselRef, api] = useEmblaCarousel(\n    {\n      ...opts,\n      axis: orientation === \"horizontal\" ? \"x\" : \"y\",\n    },\n    plugins\n  )\n  const [canScrollPrev, setCanScrollPrev] = React.useState(false)\n  const [canScrollNext, setCanScrollNext] = React.useState(false)\n\n  const onSelect = React.useCallback((api: CarouselApi) => {\n    if (!api) return\n    setCanScrollPrev(api.canScrollPrev())\n    setCanScrollNext(api.canScrollNext())\n  }, [])\n\n  const scrollPrev = React.useCallback(() => {\n    api?.scrollPrev()\n  }, [api])\n\n  const scrollNext = React.useCallback(() => {\n    api?.scrollNext()\n  }, [api])\n\n  const handleKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (event.key === \"ArrowLeft\") {\n        event.preventDefault()\n        scrollPrev()\n      } else if (event.key === \"ArrowRight\") {\n        event.preventDefault()\n        scrollNext()\n      }\n    },\n    [scrollPrev, scrollNext]\n  )\n\n  React.useEffect(() => {\n    if (!api || !setApi) return\n    setApi(api)\n  }, [api, setApi])\n\n  React.useEffect(() => {\n    if (!api) return\n    onSelect(api)\n    api.on(\"reInit\", onSelect)\n    api.on(\"select\", onSelect)\n\n    return () => {\n      api?.off(\"select\", onSelect)\n    }\n  }, [api, onSelect])\n\n  return (\n    <CarouselContext.Provider\n      value={{\n        carouselRef,\n        api: api,\n        opts,\n        orientation:\n          orientation || (opts?.axis === \"y\" ? \"vertical\" : \"horizontal\"),\n        scrollPrev,\n        scrollNext,\n        canScrollPrev,\n        canScrollNext,\n      }}\n    >\n      <div\n        onKeyDownCapture={handleKeyDown}\n        className={cn(\"relative\", className)}\n        role=\"region\"\n        aria-roledescription=\"carousel\"\n        data-slot=\"carousel\"\n        {...props}\n      >\n        {children}\n      </div>\n    </CarouselContext.Provider>\n  )\n}\n\nfunction CarouselContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { carouselRef, orientation } = useCarousel()\n\n  return (\n    <div\n      ref={carouselRef}\n      className=\"overflow-hidden\"\n      data-slot=\"carousel-content\"\n    >\n      <div\n        className={cn(\n          \"flex\",\n          orientation === \"horizontal\" ? \"-ml-4\" : \"-mt-4 flex-col\",\n          className\n        )}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction CarouselItem({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { orientation } = useCarousel()\n\n  return (\n    <div\n      role=\"group\"\n      aria-roledescription=\"slide\"\n      data-slot=\"carousel-item\"\n      className={cn(\n        \"min-w-0 shrink-0 grow-0 basis-full\",\n        orientation === \"horizontal\" ? \"pl-4\" : \"pt-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CarouselPrevious({\n  className,\n  variant = \"outline\",\n  size = \"icon\",\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n\n  return (\n    <Button\n      data-slot=\"carousel-previous\"\n      variant={variant}\n      size={size}\n      className={cn(\n        \"absolute size-8 rounded-full\",\n        orientation === \"horizontal\"\n          ? \"top-1/2 -left-12 -translate-y-1/2\"\n          : \"-top-12 left-1/2 -translate-x-1/2 rotate-90\",\n        className\n      )}\n      disabled={!canScrollPrev}\n      onClick={scrollPrev}\n      {...props}\n    >\n      <ArrowLeft />\n      <span className=\"sr-only\">Previous slide</span>\n    </Button>\n  )\n}\n\nfunction CarouselNext({\n  className,\n  variant = \"outline\",\n  size = \"icon\",\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { orientation, scrollNext, canScrollNext } = useCarousel()\n\n  return (\n    <Button\n      data-slot=\"carousel-next\"\n      variant={variant}\n      size={size}\n      className={cn(\n        \"absolute size-8 rounded-full\",\n        orientation === \"horizontal\"\n          ? \"top-1/2 -right-12 -translate-y-1/2\"\n          : \"-bottom-12 left-1/2 -translate-x-1/2 rotate-90\",\n        className\n      )}\n      disabled={!canScrollNext}\n      onClick={scrollNext}\n      {...props}\n    >\n      <ArrowRight />\n      <span className=\"sr-only\">Next slide</span>\n    </Button>\n  )\n}\n\nexport {\n  type CarouselApi,\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n  CarouselPrevious,\n  CarouselNext,\n}\n"
  },
  {
    "path": "components/ui/chart.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RechartsPrimitive from \"recharts\"\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\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  config: ChartConfig\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >[\"children\"]\n}) {\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-slot=\"chart\"\n        data-chart={chartId}\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-hidden [&_.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]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\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}\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\nfunction ChartTooltipContent({\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}: 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  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      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\n          .filter((item) => item.type !== \"none\")\n          .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 text-foreground tabular-nums\">\n                          {item.value.toLocaleString()}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            )\n          })}\n      </div>\n    </div>\n  )\n}\n\nconst ChartLegend = RechartsPrimitive.Legend\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = \"bottom\",\n  nameKey,\n}: React.ComponentProps<\"div\"> &\n  Pick<RechartsPrimitive.LegendProps, \"payload\" | \"verticalAlign\"> & {\n    hideIcon?: boolean\n    nameKey?: string\n  }) {\n  const { config } = useChart()\n\n  if (!payload?.length) {\n    return null\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center justify-center gap-4\",\n        verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\",\n        className\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== \"none\")\n        .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\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": "components/ui/checkbox.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CheckIcon } from \"lucide-react\"\nimport { Checkbox as CheckboxPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Checkbox({\n  className,\n  ...props\n}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {\n  return (\n    <CheckboxPrimitive.Root\n      data-slot=\"checkbox\"\n      className={cn(\n        \"peer size-4 shrink-0 rounded-[4px] border border-input shadow-xs transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary\",\n        className\n      )}\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        data-slot=\"checkbox-indicator\"\n        className=\"grid place-content-center text-current transition-none\"\n      >\n        <CheckIcon className=\"size-3.5\" />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  )\n}\n\nexport { Checkbox }\n"
  },
  {
    "path": "components/ui/collapsible.tsx",
    "content": "\"use client\"\n\nimport { Collapsible as CollapsiblePrimitive } from \"radix-ui\"\n\nfunction Collapsible({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />\n}\n\nfunction CollapsibleTrigger({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {\n  return (\n    <CollapsiblePrimitive.CollapsibleTrigger\n      data-slot=\"collapsible-trigger\"\n      {...props}\n    />\n  )\n}\n\nfunction CollapsibleContent({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {\n  return (\n    <CollapsiblePrimitive.CollapsibleContent\n      data-slot=\"collapsible-content\"\n      {...props}\n    />\n  )\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent }\n"
  },
  {
    "path": "components/ui/command.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Command as CommandPrimitive } from \"cmdk\"\nimport { SearchIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\"\n\nfunction Command({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive>) {\n  return (\n    <CommandPrimitive\n      data-slot=\"command\"\n      className={cn(\n        \"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandDialog({\n  title = \"Command Palette\",\n  description = \"Search for a command to run...\",\n  children,\n  className,\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof Dialog> & {\n  title?: string\n  description?: string\n  className?: string\n  showCloseButton?: boolean\n}) {\n  return (\n    <Dialog {...props}>\n      <DialogHeader className=\"sr-only\">\n        <DialogTitle>{title}</DialogTitle>\n        <DialogDescription>{description}</DialogDescription>\n      </DialogHeader>\n      <DialogContent\n        className={cn(\"overflow-hidden p-0\", className)}\n        showCloseButton={showCloseButton}\n      >\n        <Command className=\"**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n          {children}\n        </Command>\n      </DialogContent>\n    </Dialog>\n  )\n}\n\nfunction CommandInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  return (\n    <div\n      data-slot=\"command-input-wrapper\"\n      className=\"flex h-9 items-center gap-2 border-b px-3\"\n    >\n      <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n      <CommandPrimitive.Input\n        data-slot=\"command-input\"\n        className={cn(\n          \"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n          className\n        )}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction CommandList({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.List>) {\n  return (\n    <CommandPrimitive.List\n      data-slot=\"command-list\"\n      className={cn(\n        \"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandEmpty({\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  return (\n    <CommandPrimitive.Empty\n      data-slot=\"command-empty\"\n      className=\"py-6 text-center text-sm\"\n      {...props}\n    />\n  )\n}\n\nfunction CommandGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  return (\n    <CommandPrimitive.Group\n      data-slot=\"command-group\"\n      className={cn(\n        \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return (\n    <CommandPrimitive.Separator\n      data-slot=\"command-separator\"\n      className={cn(\"-mx-1 h-px bg-border\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CommandItem({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  return (\n    <CommandPrimitive.Item\n      data-slot=\"command-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"command-shortcut\"\n      className={cn(\n        \"ml-auto text-xs tracking-widest text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n}\n"
  },
  {
    "path": "components/ui/context-menu.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from \"lucide-react\"\nimport { ContextMenu as ContextMenuPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction ContextMenu({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {\n  return <ContextMenuPrimitive.Root data-slot=\"context-menu\" {...props} />\n}\n\nfunction ContextMenuTrigger({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {\n  return (\n    <ContextMenuPrimitive.Trigger data-slot=\"context-menu-trigger\" {...props} />\n  )\n}\n\nfunction ContextMenuGroup({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {\n  return (\n    <ContextMenuPrimitive.Group data-slot=\"context-menu-group\" {...props} />\n  )\n}\n\nfunction ContextMenuPortal({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {\n  return (\n    <ContextMenuPrimitive.Portal data-slot=\"context-menu-portal\" {...props} />\n  )\n}\n\nfunction ContextMenuSub({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {\n  return <ContextMenuPrimitive.Sub data-slot=\"context-menu-sub\" {...props} />\n}\n\nfunction ContextMenuRadioGroup({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {\n  return (\n    <ContextMenuPrimitive.RadioGroup\n      data-slot=\"context-menu-radio-group\"\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {\n  inset?: boolean\n}) {\n  return (\n    <ContextMenuPrimitive.SubTrigger\n      data-slot=\"context-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </ContextMenuPrimitive.SubTrigger>\n  )\n}\n\nfunction ContextMenuSubContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {\n  return (\n    <ContextMenuPrimitive.SubContent\n      data-slot=\"context-menu-sub-content\"\n      className={cn(\n        \"z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {\n  return (\n    <ContextMenuPrimitive.Portal>\n      <ContextMenuPrimitive.Content\n        data-slot=\"context-menu-content\"\n        className={cn(\n          \"z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n          className\n        )}\n        {...props}\n      />\n    </ContextMenuPrimitive.Portal>\n  )\n}\n\nfunction ContextMenuItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {\n  inset?: boolean\n  variant?: \"default\" | \"destructive\"\n}) {\n  return (\n    <ContextMenuPrimitive.Item\n      data-slot=\"context-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {\n  return (\n    <ContextMenuPrimitive.CheckboxItem\n      data-slot=\"context-menu-checkbox-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <ContextMenuPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </ContextMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </ContextMenuPrimitive.CheckboxItem>\n  )\n}\n\nfunction ContextMenuRadioItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {\n  return (\n    <ContextMenuPrimitive.RadioItem\n      data-slot=\"context-menu-radio-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <ContextMenuPrimitive.ItemIndicator>\n          <CircleIcon className=\"size-2 fill-current\" />\n        </ContextMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </ContextMenuPrimitive.RadioItem>\n  )\n}\n\nfunction ContextMenuLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {\n  inset?: boolean\n}) {\n  return (\n    <ContextMenuPrimitive.Label\n      data-slot=\"context-menu-label\"\n      data-inset={inset}\n      className={cn(\n        \"px-2 py-1.5 text-sm font-medium text-foreground data-[inset]:pl-8\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {\n  return (\n    <ContextMenuPrimitive.Separator\n      data-slot=\"context-menu-separator\"\n      className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"context-menu-shortcut\"\n      className={cn(\n        \"ml-auto text-xs tracking-widest text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  ContextMenu,\n  ContextMenuTrigger,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuCheckboxItem,\n  ContextMenuRadioItem,\n  ContextMenuLabel,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuGroup,\n  ContextMenuPortal,\n  ContextMenuSub,\n  ContextMenuSubContent,\n  ContextMenuSubTrigger,\n  ContextMenuRadioGroup,\n}\n"
  },
  {
    "path": "components/ui/dialog.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { XIcon } from \"lucide-react\"\nimport { Dialog as DialogPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\nfunction Dialog({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Root>) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />\n}\n\nfunction DialogTrigger({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />\n}\n\nfunction DialogPortal({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />\n}\n\nfunction DialogClose({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n  return (\n    <DialogPrimitive.Overlay\n      data-slot=\"dialog-overlay\"\n      className={cn(\n        \"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n  showCloseButton?: boolean\n}) {\n  return (\n    <DialogPortal data-slot=\"dialog-portal\">\n      <DialogOverlay />\n      <DialogPrimitive.Content\n        data-slot=\"dialog-content\"\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 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 sm:rounded-lg\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            className=\"absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Content>\n    </DialogPortal>\n  )\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      className={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  showCloseButton?: boolean\n}) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      className={cn(\n        \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close asChild>\n          <Button variant=\"outline\">Close</Button>\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  )\n}\n\nfunction DialogTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n  return (\n    <DialogPrimitive.Title\n      data-slot=\"dialog-title\"\n      className={cn(\"text-lg leading-none font-semibold\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n  return (\n    <DialogPrimitive.Description\n      data-slot=\"dialog-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n}\n"
  },
  {
    "path": "components/ui/drawer.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Drawer as DrawerPrimitive } from \"vaul\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Drawer({\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Root>) {\n  return <DrawerPrimitive.Root data-slot=\"drawer\" {...props} />\n}\n\nfunction DrawerTrigger({\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {\n  return <DrawerPrimitive.Trigger data-slot=\"drawer-trigger\" {...props} />\n}\n\nfunction DrawerPortal({\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {\n  return <DrawerPrimitive.Portal data-slot=\"drawer-portal\" {...props} />\n}\n\nfunction DrawerClose({\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Close>) {\n  return <DrawerPrimitive.Close data-slot=\"drawer-close\" {...props} />\n}\n\nfunction DrawerOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {\n  return (\n    <DrawerPrimitive.Overlay\n      data-slot=\"drawer-overlay\"\n      className={cn(\n        \"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DrawerContent({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Content>) {\n  return (\n    <DrawerPortal data-slot=\"drawer-portal\">\n      <DrawerOverlay />\n      <DrawerPrimitive.Content\n        data-slot=\"drawer-content\"\n        className={cn(\n          \"group/drawer-content fixed z-50 flex h-auto flex-col bg-background\",\n          \"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b\",\n          \"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t\",\n          \"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm\",\n          \"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm\",\n          className\n        )}\n        {...props}\n      >\n        <div className=\"mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block\" />\n        {children}\n      </DrawerPrimitive.Content>\n    </DrawerPortal>\n  )\n}\n\nfunction DrawerHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"drawer-header\"\n      className={cn(\n        \"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DrawerFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"drawer-footer\"\n      className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DrawerTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Title>) {\n  return (\n    <DrawerPrimitive.Title\n      data-slot=\"drawer-title\"\n      className={cn(\"font-semibold text-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DrawerDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Description>) {\n  return (\n    <DrawerPrimitive.Description\n      data-slot=\"drawer-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Drawer,\n  DrawerPortal,\n  DrawerOverlay,\n  DrawerTrigger,\n  DrawerClose,\n  DrawerContent,\n  DrawerHeader,\n  DrawerFooter,\n  DrawerTitle,\n  DrawerDescription,\n}\n"
  },
  {
    "path": "components/ui/dropdown-menu.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from \"lucide-react\"\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction DropdownMenu({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n  return <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n}\n\nfunction DropdownMenuPortal({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {\n  return (\n    <DropdownMenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />\n  )\n}\n\nfunction DropdownMenuTrigger({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n  return (\n    <DropdownMenuPrimitive.Trigger\n      data-slot=\"dropdown-menu-trigger\"\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuContent({\n  className,\n  sideOffset = 4,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n  return (\n    <DropdownMenuPrimitive.Portal>\n      <DropdownMenuPrimitive.Content\n        data-slot=\"dropdown-menu-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          \"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n          className\n        )}\n        {...props}\n      />\n    </DropdownMenuPrimitive.Portal>\n  )\n}\n\nfunction DropdownMenuGroup({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n  return (\n    <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\n  )\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n  inset?: boolean\n  variant?: \"default\" | \"destructive\"\n}) {\n  return (\n    <DropdownMenuPrimitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {\n  return (\n    <DropdownMenuPrimitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <DropdownMenuPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </DropdownMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </DropdownMenuPrimitive.CheckboxItem>\n  )\n}\n\nfunction DropdownMenuRadioGroup({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {\n  return (\n    <DropdownMenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {\n  return (\n    <DropdownMenuPrimitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <DropdownMenuPrimitive.ItemIndicator>\n          <CircleIcon className=\"size-2 fill-current\" />\n        </DropdownMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </DropdownMenuPrimitive.RadioItem>\n  )\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {\n  inset?: boolean\n}) {\n  return (\n    <DropdownMenuPrimitive.Label\n      data-slot=\"dropdown-menu-label\"\n      data-inset={inset}\n      className={cn(\n        \"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n  return (\n    <DropdownMenuPrimitive.Separator\n      data-slot=\"dropdown-menu-separator\"\n      className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"dropdown-menu-shortcut\"\n      className={cn(\n        \"ml-auto text-xs tracking-widest text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuSub({\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n  return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {\n  inset?: boolean\n}) {\n  return (\n    <DropdownMenuPrimitive.SubTrigger\n      data-slot=\"dropdown-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto size-4\" />\n    </DropdownMenuPrimitive.SubTrigger>\n  )\n}\n\nfunction DropdownMenuSubContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n  return (\n    <DropdownMenuPrimitive.SubContent\n      data-slot=\"dropdown-menu-sub-content\"\n      className={cn(\n        \"z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n}\n"
  },
  {
    "path": "components/ui/form.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport type { Label as LabelPrimitive } from \"radix-ui\"\nimport { Slot } from \"radix-ui\"\nimport {\n  Controller,\n  FormProvider,\n  useFormContext,\n  useFormState,\n  type ControllerProps,\n  type FieldPath,\n  type FieldValues,\n} from \"react-hook-form\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/components/ui/label\"\n\nconst Form = FormProvider\n\ntype FormFieldContextValue<\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> = {\n  name: TName\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>(\n  {} as FormFieldContextValue\n)\n\nconst FormField = <\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>({\n  ...props\n}: ControllerProps<TFieldValues, TName>) => {\n  return (\n    <FormFieldContext.Provider value={{ name: props.name }}>\n      <Controller {...props} />\n    </FormFieldContext.Provider>\n  )\n}\n\nconst useFormField = () => {\n  const fieldContext = React.useContext(FormFieldContext)\n  const itemContext = React.useContext(FormItemContext)\n  const { getFieldState } = useFormContext()\n  const formState = useFormState({ name: fieldContext.name })\n  const fieldState = getFieldState(fieldContext.name, formState)\n\n  if (!fieldContext) {\n    throw new Error(\"useFormField should be used within <FormField>\")\n  }\n\n  const { id } = itemContext\n\n  return {\n    id,\n    name: fieldContext.name,\n    formItemId: `${id}-form-item`,\n    formDescriptionId: `${id}-form-item-description`,\n    formMessageId: `${id}-form-item-message`,\n    ...fieldState,\n  }\n}\n\ntype FormItemContextValue = {\n  id: string\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue>(\n  {} as FormItemContextValue\n)\n\nfunction FormItem({ className, ...props }: React.ComponentProps<\"div\">) {\n  const id = React.useId()\n\n  return (\n    <FormItemContext.Provider value={{ id }}>\n      <div\n        data-slot=\"form-item\"\n        className={cn(\"grid gap-2\", className)}\n        {...props}\n      />\n    </FormItemContext.Provider>\n  )\n}\n\nfunction FormLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof LabelPrimitive.Root>) {\n  const { error, formItemId } = useFormField()\n\n  return (\n    <Label\n      data-slot=\"form-label\"\n      data-error={!!error}\n      className={cn(\"data-[error=true]:text-destructive\", className)}\n      htmlFor={formItemId}\n      {...props}\n    />\n  )\n}\n\nfunction FormControl({ ...props }: React.ComponentProps<typeof Slot.Root>) {\n  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\n\n  return (\n    <Slot.Root\n      data-slot=\"form-control\"\n      id={formItemId}\n      aria-describedby={\n        !error\n          ? `${formDescriptionId}`\n          : `${formDescriptionId} ${formMessageId}`\n      }\n      aria-invalid={!!error}\n      {...props}\n    />\n  )\n}\n\nfunction FormDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  const { formDescriptionId } = useFormField()\n\n  return (\n    <p\n      data-slot=\"form-description\"\n      id={formDescriptionId}\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction FormMessage({ className, ...props }: React.ComponentProps<\"p\">) {\n  const { error, formMessageId } = useFormField()\n  const body = error ? String(error?.message ?? \"\") : props.children\n\n  if (!body) {\n    return null\n  }\n\n  return (\n    <p\n      data-slot=\"form-message\"\n      id={formMessageId}\n      className={cn(\"text-sm text-destructive\", className)}\n      {...props}\n    >\n      {body}\n    </p>\n  )\n}\n\nexport {\n  useFormField,\n  Form,\n  FormItem,\n  FormLabel,\n  FormControl,\n  FormDescription,\n  FormMessage,\n  FormField,\n}\n"
  },
  {
    "path": "components/ui/hover-card.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { HoverCard as HoverCardPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction HoverCard({\n  ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {\n  return <HoverCardPrimitive.Root data-slot=\"hover-card\" {...props} />\n}\n\nfunction HoverCardTrigger({\n  ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {\n  return (\n    <HoverCardPrimitive.Trigger data-slot=\"hover-card-trigger\" {...props} />\n  )\n}\n\nfunction HoverCardContent({\n  className,\n  align = \"center\",\n  sideOffset = 4,\n  ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {\n  return (\n    <HoverCardPrimitive.Portal data-slot=\"hover-card-portal\">\n      <HoverCardPrimitive.Content\n        data-slot=\"hover-card-content\"\n        align={align}\n        sideOffset={sideOffset}\n        className={cn(\n          \"z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n          className\n        )}\n        {...props}\n      />\n    </HoverCardPrimitive.Portal>\n  )\n}\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent }\n"
  },
  {
    "path": "components/ui/input-otp.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { OTPInput, OTPInputContext } from \"input-otp\"\nimport { MinusIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction InputOTP({\n  className,\n  containerClassName,\n  ...props\n}: React.ComponentProps<typeof OTPInput> & {\n  containerClassName?: string\n}) {\n  return (\n    <OTPInput\n      data-slot=\"input-otp\"\n      containerClassName={cn(\n        \"flex items-center gap-2 has-disabled:opacity-50\",\n        containerClassName\n      )}\n      className={cn(\"disabled:cursor-not-allowed\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction InputOTPGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"input-otp-group\"\n      className={cn(\"flex items-center\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction InputOTPSlot({\n  index,\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  index: number\n}) {\n  const inputOTPContext = React.useContext(OTPInputContext)\n  const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}\n\n  return (\n    <div\n      data-slot=\"input-otp-slot\"\n      data-active={isActive}\n      className={cn(\n        \"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[3px] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-destructive/40\",\n        className\n      )}\n      {...props}\n    >\n      {char}\n      {hasFakeCaret && (\n        <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n          <div className=\"h-4 w-px animate-caret-blink bg-foreground duration-1000\" />\n        </div>\n      )}\n    </div>\n  )\n}\n\nfunction InputOTPSeparator({ ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div data-slot=\"input-otp-separator\" role=\"separator\" {...props}>\n      <MinusIcon />\n    </div>\n  )\n}\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }\n"
  },
  {
    "path": "components/ui/input.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\n  return (\n    <input\n      type={type}\n      data-slot=\"input\"\n      className={cn(\n        \"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30\",\n        \"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n        \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Input }\n"
  },
  {
    "path": "components/ui/label.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Label as LabelPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Label({\n  className,\n  ...props\n}: React.ComponentProps<typeof LabelPrimitive.Root>) {\n  return (\n    <LabelPrimitive.Root\n      data-slot=\"label\"\n      className={cn(\n        \"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Label }\n"
  },
  {
    "path": "components/ui/menubar.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from \"lucide-react\"\nimport { Menubar as MenubarPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Menubar({\n  className,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Root>) {\n  return (\n    <MenubarPrimitive.Root\n      data-slot=\"menubar\"\n      className={cn(\n        \"flex h-9 items-center gap-1 rounded-md border bg-background p-1 shadow-xs\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarMenu({\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {\n  return <MenubarPrimitive.Menu data-slot=\"menubar-menu\" {...props} />\n}\n\nfunction MenubarGroup({\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Group>) {\n  return <MenubarPrimitive.Group data-slot=\"menubar-group\" {...props} />\n}\n\nfunction MenubarPortal({\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {\n  return <MenubarPrimitive.Portal data-slot=\"menubar-portal\" {...props} />\n}\n\nfunction MenubarRadioGroup({\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {\n  return (\n    <MenubarPrimitive.RadioGroup data-slot=\"menubar-radio-group\" {...props} />\n  )\n}\n\nfunction MenubarTrigger({\n  className,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {\n  return (\n    <MenubarPrimitive.Trigger\n      data-slot=\"menubar-trigger\"\n      className={cn(\n        \"flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarContent({\n  className,\n  align = \"start\",\n  alignOffset = -4,\n  sideOffset = 8,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Content>) {\n  return (\n    <MenubarPortal>\n      <MenubarPrimitive.Content\n        data-slot=\"menubar-content\"\n        align={align}\n        alignOffset={alignOffset}\n        sideOffset={sideOffset}\n        className={cn(\n          \"z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n          className\n        )}\n        {...props}\n      />\n    </MenubarPortal>\n  )\n}\n\nfunction MenubarItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Item> & {\n  inset?: boolean\n  variant?: \"default\" | \"destructive\"\n}) {\n  return (\n    <MenubarPrimitive.Item\n      data-slot=\"menubar-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {\n  return (\n    <MenubarPrimitive.CheckboxItem\n      data-slot=\"menubar-checkbox-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <MenubarPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </MenubarPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </MenubarPrimitive.CheckboxItem>\n  )\n}\n\nfunction MenubarRadioItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {\n  return (\n    <MenubarPrimitive.RadioItem\n      data-slot=\"menubar-radio-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <MenubarPrimitive.ItemIndicator>\n          <CircleIcon className=\"size-2 fill-current\" />\n        </MenubarPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </MenubarPrimitive.RadioItem>\n  )\n}\n\nfunction MenubarLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Label> & {\n  inset?: boolean\n}) {\n  return (\n    <MenubarPrimitive.Label\n      data-slot=\"menubar-label\"\n      data-inset={inset}\n      className={cn(\n        \"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {\n  return (\n    <MenubarPrimitive.Separator\n      data-slot=\"menubar-separator\"\n      className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"menubar-shortcut\"\n      className={cn(\n        \"ml-auto text-xs tracking-widest text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarSub({\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {\n  return <MenubarPrimitive.Sub data-slot=\"menubar-sub\" {...props} />\n}\n\nfunction MenubarSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {\n  inset?: boolean\n}) {\n  return (\n    <MenubarPrimitive.SubTrigger\n      data-slot=\"menubar-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto h-4 w-4\" />\n    </MenubarPrimitive.SubTrigger>\n  )\n}\n\nfunction MenubarSubContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {\n  return (\n    <MenubarPrimitive.SubContent\n      data-slot=\"menubar-sub-content\"\n      className={cn(\n        \"z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Menubar,\n  MenubarPortal,\n  MenubarMenu,\n  MenubarTrigger,\n  MenubarContent,\n  MenubarGroup,\n  MenubarSeparator,\n  MenubarLabel,\n  MenubarItem,\n  MenubarShortcut,\n  MenubarCheckboxItem,\n  MenubarRadioGroup,\n  MenubarRadioItem,\n  MenubarSub,\n  MenubarSubTrigger,\n  MenubarSubContent,\n}\n"
  },
  {
    "path": "components/ui/navigation-menu.tsx",
    "content": "import * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { ChevronDownIcon } from \"lucide-react\"\nimport { NavigationMenu as NavigationMenuPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction NavigationMenu({\n  className,\n  children,\n  viewport = true,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {\n  viewport?: boolean\n}) {\n  return (\n    <NavigationMenuPrimitive.Root\n      data-slot=\"navigation-menu\"\n      data-viewport={viewport}\n      className={cn(\n        \"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      {viewport && <NavigationMenuViewport />}\n    </NavigationMenuPrimitive.Root>\n  )\n}\n\nfunction NavigationMenuList({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {\n  return (\n    <NavigationMenuPrimitive.List\n      data-slot=\"navigation-menu-list\"\n      className={cn(\n        \"group flex flex-1 list-none items-center justify-center gap-1\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction NavigationMenuItem({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {\n  return (\n    <NavigationMenuPrimitive.Item\n      data-slot=\"navigation-menu-item\"\n      className={cn(\"relative\", className)}\n      {...props}\n    />\n  )\n}\n\nconst navigationMenuTriggerStyle = cva(\n  \"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-[color,box-shadow] outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-accent/50 data-[state=open]:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent\"\n)\n\nfunction NavigationMenuTrigger({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {\n  return (\n    <NavigationMenuPrimitive.Trigger\n      data-slot=\"navigation-menu-trigger\"\n      className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n      {...props}\n    >\n      {children}{\" \"}\n      <ChevronDownIcon\n        className=\"relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180\"\n        aria-hidden=\"true\"\n      />\n    </NavigationMenuPrimitive.Trigger>\n  )\n}\n\nfunction NavigationMenuContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {\n  return (\n    <NavigationMenuPrimitive.Content\n      data-slot=\"navigation-menu-content\"\n      className={cn(\n        \"top-0 left-0 w-full p-2 pr-2.5 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out md:absolute md:w-auto\",\n        \"group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction NavigationMenuViewport({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {\n  return (\n    <div\n      className={cn(\n        \"absolute top-full left-0 isolate z-50 flex justify-center\"\n      )}\n    >\n      <NavigationMenuPrimitive.Viewport\n        data-slot=\"navigation-menu-viewport\"\n        className={cn(\n          \"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]\",\n          className\n        )}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction NavigationMenuLink({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {\n  return (\n    <NavigationMenuPrimitive.Link\n      data-slot=\"navigation-menu-link\"\n      className={cn(\n        \"flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground data-[active=true]:hover:bg-accent data-[active=true]:focus:bg-accent [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction NavigationMenuIndicator({\n  className,\n  ...props\n}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {\n  return (\n    <NavigationMenuPrimitive.Indicator\n      data-slot=\"navigation-menu-indicator\"\n      className={cn(\n        \"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md\" />\n    </NavigationMenuPrimitive.Indicator>\n  )\n}\n\nexport {\n  NavigationMenu,\n  NavigationMenuList,\n  NavigationMenuItem,\n  NavigationMenuContent,\n  NavigationMenuTrigger,\n  NavigationMenuLink,\n  NavigationMenuIndicator,\n  NavigationMenuViewport,\n  navigationMenuTriggerStyle,\n}\n"
  },
  {
    "path": "components/ui/pagination.tsx",
    "content": "import * as React from \"react\"\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  MoreHorizontalIcon,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants, type Button } from \"@/components/ui/button\"\n\nfunction Pagination({ className, ...props }: React.ComponentProps<\"nav\">) {\n  return (\n    <nav\n      role=\"navigation\"\n      aria-label=\"pagination\"\n      data-slot=\"pagination\"\n      className={cn(\"mx-auto flex w-full justify-center\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction PaginationContent({\n  className,\n  ...props\n}: React.ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"pagination-content\"\n      className={cn(\"flex flex-row items-center gap-1\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction PaginationItem({ ...props }: React.ComponentProps<\"li\">) {\n  return <li data-slot=\"pagination-item\" {...props} />\n}\n\ntype PaginationLinkProps = {\n  isActive?: boolean\n} & Pick<React.ComponentProps<typeof Button>, \"size\"> &\n  React.ComponentProps<\"a\">\n\nfunction PaginationLink({\n  className,\n  isActive,\n  size = \"icon\",\n  ...props\n}: PaginationLinkProps) {\n  return (\n    <a\n      aria-current={isActive ? \"page\" : undefined}\n      data-slot=\"pagination-link\"\n      data-active={isActive}\n      className={cn(\n        buttonVariants({\n          variant: isActive ? \"outline\" : \"ghost\",\n          size,\n        }),\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction PaginationPrevious({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) {\n  return (\n    <PaginationLink\n      aria-label=\"Go to previous page\"\n      size=\"default\"\n      className={cn(\"gap-1 px-2.5 sm:pl-2.5\", className)}\n      {...props}\n    >\n      <ChevronLeftIcon />\n      <span className=\"hidden sm:block\">Previous</span>\n    </PaginationLink>\n  )\n}\n\nfunction PaginationNext({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) {\n  return (\n    <PaginationLink\n      aria-label=\"Go to next page\"\n      size=\"default\"\n      className={cn(\"gap-1 px-2.5 sm:pr-2.5\", className)}\n      {...props}\n    >\n      <span className=\"hidden sm:block\">Next</span>\n      <ChevronRightIcon />\n    </PaginationLink>\n  )\n}\n\nfunction PaginationEllipsis({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      aria-hidden\n      data-slot=\"pagination-ellipsis\"\n      className={cn(\"flex size-9 items-center justify-center\", className)}\n      {...props}\n    >\n      <MoreHorizontalIcon className=\"size-4\" />\n      <span className=\"sr-only\">More pages</span>\n    </span>\n  )\n}\n\nexport {\n  Pagination,\n  PaginationContent,\n  PaginationLink,\n  PaginationItem,\n  PaginationPrevious,\n  PaginationNext,\n  PaginationEllipsis,\n}\n"
  },
  {
    "path": "components/ui/popover.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Popover as PopoverPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Popover({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Root>) {\n  return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />\n}\n\nfunction PopoverTrigger({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {\n  return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />\n}\n\nfunction PopoverContent({\n  className,\n  align = \"center\",\n  sideOffset = 4,\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>) {\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverPrimitive.Content\n        data-slot=\"popover-content\"\n        align={align}\n        sideOffset={sideOffset}\n        className={cn(\n          \"z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n          className\n        )}\n        {...props}\n      />\n    </PopoverPrimitive.Portal>\n  )\n}\n\nfunction PopoverAnchor({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {\n  return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />\n}\n\nfunction PopoverHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"popover-header\"\n      className={cn(\"flex flex-col gap-1 text-sm\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction PopoverTitle({ className, ...props }: React.ComponentProps<\"h2\">) {\n  return (\n    <div\n      data-slot=\"popover-title\"\n      className={cn(\"font-medium\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction PopoverDescription({\n  className,\n  ...props\n}: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"popover-description\"\n      className={cn(\"text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Popover,\n  PopoverTrigger,\n  PopoverContent,\n  PopoverAnchor,\n  PopoverHeader,\n  PopoverTitle,\n  PopoverDescription,\n}\n"
  },
  {
    "path": "components/ui/progress.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Progress as ProgressPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Progress({\n  className,\n  value,\n  ...props\n}: React.ComponentProps<typeof ProgressPrimitive.Root>) {\n  return (\n    <ProgressPrimitive.Root\n      data-slot=\"progress\"\n      className={cn(\n        \"relative h-2 w-full overflow-hidden rounded-full bg-primary/20\",\n        className\n      )}\n      {...props}\n    >\n      <ProgressPrimitive.Indicator\n        data-slot=\"progress-indicator\"\n        className=\"h-full w-full flex-1 bg-primary transition-all\"\n        style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n      />\n    </ProgressPrimitive.Root>\n  )\n}\n\nexport { Progress }\n"
  },
  {
    "path": "components/ui/radio-group.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CircleIcon } from \"lucide-react\"\nimport { RadioGroup as RadioGroupPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction RadioGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {\n  return (\n    <RadioGroupPrimitive.Root\n      data-slot=\"radio-group\"\n      className={cn(\"grid gap-3\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction RadioGroupItem({\n  className,\n  ...props\n}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {\n  return (\n    <RadioGroupPrimitive.Item\n      data-slot=\"radio-group-item\"\n      className={cn(\n        \"aspect-square size-4 shrink-0 rounded-full border border-input text-primary shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:ring-destructive/40\",\n        className\n      )}\n      {...props}\n    >\n      <RadioGroupPrimitive.Indicator\n        data-slot=\"radio-group-indicator\"\n        className=\"relative flex items-center justify-center\"\n      >\n        <CircleIcon className=\"absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-primary\" />\n      </RadioGroupPrimitive.Indicator>\n    </RadioGroupPrimitive.Item>\n  )\n}\n\nexport { RadioGroup, RadioGroupItem }\n"
  },
  {
    "path": "components/ui/resizable.tsx",
    "content": "\"use client\"\n\nimport { GripVerticalIcon } from \"lucide-react\"\nimport * as ResizablePrimitive from \"react-resizable-panels\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction ResizablePanelGroup({\n  className,\n  ...props\n}: ResizablePrimitive.GroupProps) {\n  return (\n    <ResizablePrimitive.Group\n      data-slot=\"resizable-panel-group\"\n      className={cn(\n        \"flex h-full w-full aria-[orientation=vertical]:flex-col\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {\n  return <ResizablePrimitive.Panel data-slot=\"resizable-panel\" {...props} />\n}\n\nfunction ResizableHandle({\n  withHandle,\n  className,\n  ...props\n}: ResizablePrimitive.SeparatorProps & {\n  withHandle?: boolean\n}) {\n  return (\n    <ResizablePrimitive.Separator\n      data-slot=\"resizable-handle\"\n      className={cn(\n        \"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90\",\n        className\n      )}\n      {...props}\n    >\n      {withHandle && (\n        <div className=\"z-10 flex h-4 w-3 items-center justify-center rounded-xs border bg-border\">\n          <GripVerticalIcon className=\"size-2.5\" />\n        </div>\n      )}\n    </ResizablePrimitive.Separator>\n  )\n}\n\nexport { ResizableHandle, ResizablePanel, ResizablePanelGroup }\n"
  },
  {
    "path": "components/ui/revola.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport { cva } from \"class-variance-authority\";\nimport { X } from \"lucide-react\";\nimport { Drawer as DrawerPrimitive, Content as VaulDrawerContent } from \"vaul\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport useMediaQuery from \"@/hooks/use-media-query\";\n\nexport type ResponsiveDialogProps = React.ComponentProps<typeof DrawerPrimitive.Root>;\n\ntype ResponsiveDialogContextProps = {\n  modal?: boolean;\n  dismissible?: boolean;\n  direction?: \"top\" | \"right\" | \"bottom\" | \"left\";\n  onlyDrawer?: boolean;\n  onlyDialog?: boolean;\n  alert?: boolean;\n  shouldUseDialog: boolean;\n};\n\ntype ResponsiveDialogProviderProps = {\n  children: React.ReactNode;\n} & ResponsiveDialogContextProps;\n\nconst ResponsiveDialogContext = React.createContext<ResponsiveDialogContextProps | null>(null);\nconst MOBILE_BREAKPOINT = \"(min-width: 640px)\";\n\nconst ResponsiveDialogProvider = ({\n  modal = true,\n  dismissible = true,\n  direction = \"bottom\",\n  onlyDrawer = false,\n  onlyDialog = false,\n  alert = false,\n  shouldUseDialog,\n  children,\n}: ResponsiveDialogProviderProps & { shouldUseDialog: boolean }) => {\n  return (\n    <ResponsiveDialogContext.Provider\n      value={{ modal, dismissible, direction, onlyDrawer, onlyDialog, alert, shouldUseDialog }}\n    >\n      {children}\n    </ResponsiveDialogContext.Provider>\n  );\n};\n\nexport const useResponsiveDialog = () => {\n  const context = React.useContext(ResponsiveDialogContext);\n\n  if (!context) {\n    throw new Error(\"useResponsiveDialog must be used within a <ResponsiveDialog />\");\n  }\n\n  return context;\n};\n\nconst ResponsiveDialog = ({\n  modal = true,\n  dismissible = true,\n  direction = \"bottom\",\n  onlyDrawer = false,\n  onlyDialog = false,\n  alert = false,\n  shouldScaleBackground = true,\n  open: controlledOpen,\n  onOpenChange: controlledOnOpenChange,\n  ...props\n}: ResponsiveDialogProps & { onlyDrawer?: boolean; onlyDialog?: boolean; alert?: boolean }) => {\n  const [internalState, setInternalState] = React.useState<boolean>(false);\n\n  const isControlledOpen = typeof controlledOpen === \"undefined\";\n  const toggleInternalState = () => setInternalState((prev) => !prev);\n\n  const open = isControlledOpen ? internalState : controlledOpen;\n  const onOpenChange = isControlledOpen ? toggleInternalState : controlledOnOpenChange;\n\n  const isDesktop = useMediaQuery(MOBILE_BREAKPOINT);\n\n  const shouldUseDialog = onlyDialog || (!onlyDrawer && !!isDesktop);\n  const ResponsiveDialog = shouldUseDialog ? DialogPrimitive.Root : DrawerPrimitive.Root;\n\n  const effectiveModal = alert ? true : modal;\n  const effectiveDismissible = alert ? true : dismissible;\n\n const isIOS = typeof window !== \"undefined\" && /iPad|iPhone|iPod/.test(navigator.userAgent)\n  return (\n    <ResponsiveDialogProvider\n      modal={effectiveModal}\n      dismissible={effectiveDismissible}\n      direction={direction}\n      onlyDrawer={onlyDrawer}\n      onlyDialog={onlyDialog}\n      alert={alert}\n      shouldUseDialog={shouldUseDialog}\n    >\n      <ResponsiveDialog\n        modal={effectiveModal}\n        direction={direction}\n        dismissible={effectiveDismissible}\n        shouldScaleBackground={shouldScaleBackground}\n        open={open}\n        onOpenChange={onOpenChange}\n        repositionInputs={!isIOS}\n        {...props}\n      />\n    </ResponsiveDialogProvider>\n  );\n};\nResponsiveDialog.displayName = \"ResponsiveDialog\";\n\nconst ResponsiveDialogTrigger = ({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) => {\n  const { shouldUseDialog } = useResponsiveDialog();\n  const ResponsiveDialogTrigger = shouldUseDialog\n    ? DialogPrimitive.Trigger\n    : DrawerPrimitive.Trigger;\n  return <ResponsiveDialogTrigger {...props} />;\n};\nResponsiveDialogTrigger.displayName = \"ResponsiveDialogTrigger\";\n\nconst ResponsiveDialogPortal = ({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) => {\n  const { shouldUseDialog } = useResponsiveDialog();\n  const ResponsiveDialogPortal = shouldUseDialog ? DialogPrimitive.Portal : DrawerPrimitive.Portal;\n  return <ResponsiveDialogPortal {...props} />;\n};\nResponsiveDialogPortal.displayName = \"ResponsiveDialogPortal\";\n\nconst ResponsiveDialogOverlay = ({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) => {\n  const { shouldUseDialog } = useResponsiveDialog();\n  const ResponsiveDialogOverlay = shouldUseDialog\n    ? DialogPrimitive.Overlay\n    : DrawerPrimitive.Overlay;\n  return (\n    <ResponsiveDialogOverlay\n      {...props}\n      className={cn(\n        \"sm:data-[state=open]:animate-in sm:data-[state=closed]:animate-out sm:data-[state=closed]:fade-out-0 sm:data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/70\",\n        className\n      )}\n    />\n  );\n};\nResponsiveDialogOverlay.displayName = \"ResponsiveDialogOverlay\";\n\nconst ResponsiveDialogClose = ({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) => {\n  const { dismissible, alert, shouldUseDialog } = useResponsiveDialog();\n  const ResponsiveDialogClose = shouldUseDialog ? DialogPrimitive.Close : DrawerPrimitive.Close;\n\n  const shouldPreventClose = !dismissible && !alert;\n\n  return (\n    <ResponsiveDialogClose\n      aria-label=\"Close\"\n      {...(shouldPreventClose && { disabled: true })}\n      {...(shouldPreventClose && { \"aria-disabled\": true })}\n      {...(shouldPreventClose && { onClick: (e) => e.preventDefault() })}\n      {...props}\n    />\n  );\n};\nResponsiveDialogClose.displayName = \"ResponsiveDialogClose\";\n\nconst ResponsiveDialogContentVariants = cva(\"fixed z-[9999] bg-background\", {\n  variants: {\n    device: {\n      desktop:\n        \"left-1/2 top-1/2 grid max-h-[calc(100%-4rem)] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-transparent dark:!border-primary/10 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 sm:max-w-lg\",\n      mobile: \"flex \",\n    },\n    direction: {\n      bottom: \"\",\n      top: \"\",\n      left: \"\",\n      right: \"\",\n    },\n  },\n  defaultVariants: {\n    device: \"desktop\",\n    direction: \"bottom\",\n  },\n  compoundVariants: [\n    {\n      device: \"mobile\",\n      direction: \"bottom\",\n      className:\n        \"inset-x-0 bottom-0 mt-24 h-fit max-h-[75%] flex-col rounded-t-lg border border-b-0 !border-primary/10\",\n    },\n    {\n      device: \"mobile\",\n      direction: \"top\",\n      className:\n        \"inset-x-0 top-0 mb-24 h-fit max-h-[75%] flex-col rounded-b-lg border border-b-0 !border-primary/10\",\n    },\n    {\n      device: \"mobile\",\n      direction: \"left\",\n      className:\n        \"bottom-2 left-2 top-2 flex w-[310px] bg-transparent outline-none [--initial-transform:calc(100%+8px)]\",\n    },\n    {\n      device: \"mobile\",\n      direction: \"right\",\n      className:\n        \"bottom-2 right-2 top-2 w-[310px] bg-transparent outline-none [--initial-transform:calc(100%+8px)]\",\n    },\n  ],\n});\n\nconst ResponsiveDialogContent = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {\n    showCloseButton?: boolean;\n    /** Styles for the built in close button */\n    closeButtonClassName?: string;\n    /** Styles for the drag handle */\n    dragHandleClassName?: string;\n  }\n>(\n  (\n    {\n      className,\n      children,\n      closeButtonClassName,\n      dragHandleClassName,\n      showCloseButton = true,\n      ...props\n    },\n    ref\n  ) => {\n    const { direction, modal, dismissible, alert, shouldUseDialog } = useResponsiveDialog();\n    const ResponsiveDialogContent = shouldUseDialog ? DialogPrimitive.Content : VaulDrawerContent;\n\n    const shouldShowCloseButton = !alert && showCloseButton;\n    const shouldPreventEscape = !dismissible && !alert;\n    const shouldPreventOutsideInteraction = !modal || (!dismissible && !alert) || alert;\n\n    return (\n      <ResponsiveDialogPortal>\n        <ResponsiveDialogOverlay />\n        <ResponsiveDialogContent\n          ref={ref}\n          {...props}\n          {...(shouldPreventEscape &&\n            shouldUseDialog && { onEscapeKeyDown: (e) => e.preventDefault() })}\n          {...(shouldPreventOutsideInteraction &&\n            shouldUseDialog && {\n              onInteractOutside: (e) => e.preventDefault(),\n            })}\n          {...(!shouldUseDialog &&\n            shouldPreventOutsideInteraction && {\n              onPointerDownOutside: (e) => e.preventDefault(),\n              onInteractOutside: (e) => e.preventDefault(),\n            })}\n          className={cn(\n            ResponsiveDialogContentVariants({\n              device: shouldUseDialog ? \"desktop\" : \"mobile\",\n              direction,\n            }),\n            className\n          )}\n        >\n          {!shouldUseDialog && direction === \"bottom\" && (\n            <div\n              className={cn(\n                \"bg-muted-foreground/25 dark:bg-muted mx-auto my-4 h-1.5 w-14 rounded-full pb-1.5 data-[vaul-handle]:h-1.5 data-[vaul-handle]:w-14 data-[vaul-handle]:pb-1.5\",\n                dragHandleClassName\n              )}\n            />\n          )}\n          {children}\n          {shouldShowCloseButton && (\n            <ResponsiveDialogClose\n              className={cn(\n                \"ring-offset-background focus-visible:ring-ring data-[state=open]:bg-accent absolute top-4 right-4 rounded-sm opacity-70 backdrop-blur-sm transition-opacity hover:opacity-100 focus:ring-offset-2 focus:outline-none focus-visible:ring-2 disabled:pointer-events-none data-[state=open]:text-white\",\n                closeButtonClassName\n              )}\n            >\n              <X className=\"size-4\" />\n              <span className=\"sr-only\">close</span>\n            </ResponsiveDialogClose>\n          )}\n        </ResponsiveDialogContent>\n      </ResponsiveDialogPortal>\n    );\n  }\n);\nResponsiveDialogContent.displayName = \"ResponsiveDialogContent\";\n\nconst ResponsiveDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => {\n  return (\n    <div className={cn(\"flex flex-col gap-1.5 text-center sm:text-left\", className)} {...props} />\n  );\n};\nResponsiveDialogHeader.displayName = \"ResponsiveDialogHeader\";\n\nconst ResponsiveDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => {\n  return (\n    <footer\n      className={cn(\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n      {...props}\n    />\n  );\n};\nResponsiveDialogFooter.displayName = \"ResponsiveDialogFooter\";\n\nconst ResponsiveDialogTitle = React.forwardRef<\n  React.ComponentRef<typeof DialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => {\n  const { shouldUseDialog } = useResponsiveDialog();\n  const ResponsiveDialogTitle = shouldUseDialog ? DialogPrimitive.Title : DrawerPrimitive.Title;\n  return (\n    <ResponsiveDialogTitle\n      ref={ref}\n      className={cn(\"text-lg leading-none font-semibold tracking-tight\", className)}\n      {...props}\n    />\n  );\n});\n\nResponsiveDialogTitle.displayName = \"ResponsiveDialogTitle\";\n\nconst ResponsiveDialogDescription = React.forwardRef<\n  React.ComponentRef<typeof DialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => {\n  const { shouldUseDialog } = useResponsiveDialog();\n  const ResponsiveDialogDescription = shouldUseDialog\n    ? DialogPrimitive.Description\n    : DrawerPrimitive.Description;\n  return (\n    <ResponsiveDialogDescription\n      ref={ref}\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  );\n});\n\nResponsiveDialogDescription.displayName = \"ResponsiveDialogDescription\";\n\nexport {\n  ResponsiveDialog,\n  ResponsiveDialogClose,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogFooter,\n  ResponsiveDialogHeader,\n  ResponsiveDialogOverlay,\n  ResponsiveDialogPortal,\n  ResponsiveDialogTitle,\n  ResponsiveDialogTrigger,\n};\n"
  },
  {
    "path": "components/ui/scroll-area.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ScrollArea as ScrollAreaPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction ScrollArea({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {\n  return (\n    <ScrollAreaPrimitive.Root\n      data-slot=\"scroll-area\"\n      className={cn(\"relative\", className)}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Viewport\n        data-slot=\"scroll-area-viewport\"\n        className=\"size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1\"\n      >\n        {children}\n      </ScrollAreaPrimitive.Viewport>\n      <ScrollBar />\n      <ScrollAreaPrimitive.Corner />\n    </ScrollAreaPrimitive.Root>\n  )\n}\n\nfunction ScrollBar({\n  className,\n  orientation = \"vertical\",\n  ...props\n}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {\n  return (\n    <ScrollAreaPrimitive.ScrollAreaScrollbar\n      data-slot=\"scroll-area-scrollbar\"\n      orientation={orientation}\n      className={cn(\n        \"flex touch-none p-px transition-colors select-none\",\n        orientation === \"vertical\" &&\n          \"h-full w-2.5 border-l border-l-transparent\",\n        orientation === \"horizontal\" &&\n          \"h-2.5 flex-col border-t border-t-transparent\",\n        className\n      )}\n      {...props}\n    >\n      <ScrollAreaPrimitive.ScrollAreaThumb\n        data-slot=\"scroll-area-thumb\"\n        className=\"relative flex-1 rounded-full bg-border\"\n      />\n    </ScrollAreaPrimitive.ScrollAreaScrollbar>\n  )\n}\n\nexport { ScrollArea, ScrollBar }\n"
  },
  {
    "path": "components/ui/select.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from \"lucide-react\"\nimport { Select as SelectPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Select({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Root>) {\n  return <SelectPrimitive.Root data-slot=\"select\" {...props} />\n}\n\nfunction SelectGroup({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Group>) {\n  return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />\n}\n\nfunction SelectValue({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Value>) {\n  return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />\n}\n\nfunction SelectTrigger({\n  className,\n  size = \"default\",\n  children,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {\n  size?: \"sm\" | \"default\"\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      data-slot=\"select-trigger\"\n      data-size={size}\n      className={cn(\n        \"flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon asChild>\n        <ChevronDownIcon className=\"size-4 opacity-50\" />\n      </SelectPrimitive.Icon>\n    </SelectPrimitive.Trigger>\n  )\n}\n\nfunction SelectContent({\n  className,\n  children,\n  position = \"item-aligned\",\n  align = \"center\",\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Content\n        data-slot=\"select-content\"\n        className={cn(\n          \"relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\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        align={align}\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)] scroll-my-1\"\n          )}\n        >\n          {children}\n        </SelectPrimitive.Viewport>\n        <SelectScrollDownButton />\n      </SelectPrimitive.Content>\n    </SelectPrimitive.Portal>\n  )\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Label>) {\n  return (\n    <SelectPrimitive.Label\n      data-slot=\"select-label\"\n      className={cn(\"px-2 py-1.5 text-xs text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item>) {\n  return (\n    <SelectPrimitive.Item\n      data-slot=\"select-item\"\n      className={cn(\n        \"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      {...props}\n    >\n      <span\n        data-slot=\"select-item-indicator\"\n        className=\"absolute right-2 flex size-3.5 items-center justify-center\"\n      >\n        <SelectPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </SelectPrimitive.ItemIndicator>\n      </span>\n      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n    </SelectPrimitive.Item>\n  )\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n  return (\n    <SelectPrimitive.Separator\n      data-slot=\"select-separator\"\n      className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {\n  return (\n    <SelectPrimitive.ScrollUpButton\n      data-slot=\"select-scroll-up-button\"\n      className={cn(\n        \"flex cursor-default items-center justify-center py-1\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronUpIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollUpButton>\n  )\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {\n  return (\n    <SelectPrimitive.ScrollDownButton\n      data-slot=\"select-scroll-down-button\"\n      className={cn(\n        \"flex cursor-default items-center justify-center py-1\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronDownIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollDownButton>\n  )\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n}\n"
  },
  {
    "path": "components/ui/separator.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Separator as SeparatorPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Separator({\n  className,\n  orientation = \"horizontal\",\n  decorative = true,\n  ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n  return (\n    <SeparatorPrimitive.Root\n      data-slot=\"separator\"\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        \"shrink-0 bg-border\",\n        orientation === \"horizontal\" ? \"h-px w-full\" : \"h-full w-px\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Separator }\n"
  },
  {
    "path": "components/ui/sheet.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { XIcon } from \"lucide-react\"\nimport { Dialog as SheetPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />\n}\n\nfunction SheetTrigger({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />\n}\n\nfunction SheetClose({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Close>) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />\n}\n\nfunction SheetPortal({\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Portal>) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />\n}\n\nfunction SheetOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {\n  return (\n    <SheetPrimitive.Overlay\n      data-slot=\"sheet-overlay\"\n      className={cn(\n        \"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = \"right\",\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Content> & {\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\"\n  showCloseButton?: boolean\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Content\n        data-slot=\"sheet-content\"\n        className={cn(\n          \"fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500\",\n          side === \"right\" &&\n            \"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm\",\n          side === \"left\" &&\n            \"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm\",\n          side === \"top\" &&\n            \"inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n          side === \"bottom\" &&\n            \"inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close className=\"absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary\">\n            <XIcon className=\"size-4\" />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Content>\n    </SheetPortal>\n  )\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sheet-header\"\n      className={cn(\"flex flex-col gap-1.5 p-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sheet-footer\"\n      className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Title>) {\n  return (\n    <SheetPrimitive.Title\n      data-slot=\"sheet-title\"\n      className={cn(\"font-semibold text-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Description>) {\n  return (\n    <SheetPrimitive.Description\n      data-slot=\"sheet-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n}\n"
  },
  {
    "path": "components/ui/sidebar.tsx",
    "content": "import * as React from \"react\";\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport { VariantProps, cva } from \"class-variance-authority\";\nimport { PanelLeft } from \"lucide-react\";\n\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Sheet, SheetContent } from \"@/components/ui/sheet\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar:state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = \"16rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\";\nconst SIDEBAR_WIDTH_ICON = \"3rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\n\ntype SidebarContext = {\n  state: \"expanded\" | \"collapsed\";\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContext | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n  }\n\n  return context;\n}\n\nconst SidebarProvider = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<\"div\"> & {\n    defaultOpen?: boolean;\n    open?: boolean;\n    onOpenChange?: (open: boolean) => void;\n  }\n>(\n  (\n    {\n      defaultOpen = true,\n      open: openProp,\n      onOpenChange: setOpenProp,\n      className,\n      style,\n      children,\n      ...props\n    },\n    ref\n  ) => {\n    const isMobile = useIsMobile();\n    const [openMobile, setOpenMobile] = React.useState(false);\n\n    // This is the internal state of the sidebar.\n    // We use openProp and setOpenProp for control from outside the component.\n    const [_open, _setOpen] = React.useState(defaultOpen);\n    const open = openProp ?? _open;\n    const setOpen = React.useCallback(\n      (value: boolean | ((value: boolean) => boolean)) => {\n        const openState = typeof value === \"function\" ? value(open) : value;\n        if (setOpenProp) {\n          setOpenProp(openState);\n        } else {\n          _setOpen(openState);\n        }\n\n        // This sets the cookie to keep the sidebar state.\n        document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n      },\n      [setOpenProp, open]\n    );\n\n    // Helper to toggle the sidebar.\n    const toggleSidebar = React.useCallback(() => {\n      return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n    }, [isMobile, setOpen, setOpenMobile]);\n\n    // Adds a keyboard shortcut to toggle the sidebar.\n    React.useEffect(() => {\n      const handleKeyDown = (event: KeyboardEvent) => {\n        if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n          event.preventDefault();\n          toggleSidebar();\n        }\n      };\n\n      window.addEventListener(\"keydown\", handleKeyDown);\n      return () => window.removeEventListener(\"keydown\", handleKeyDown);\n    }, [toggleSidebar]);\n\n    // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n    // This makes it easier to style the sidebar with Tailwind classes.\n    const state = open ? \"expanded\" : \"collapsed\";\n\n    const contextValue = React.useMemo<SidebarContext>(\n      () => ({\n        state,\n        open,\n        setOpen,\n        isMobile,\n        openMobile,\n        setOpenMobile,\n        toggleSidebar,\n      }),\n      [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]\n    );\n\n    return (\n      <SidebarContext.Provider value={contextValue}>\n        <TooltipProvider delayDuration={0}>\n          <div\n            style={\n              {\n                \"--sidebar-width\": SIDEBAR_WIDTH,\n                \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n                ...style,\n              } as React.CSSProperties\n            }\n            className={cn(\n              \"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full\",\n              className\n            )}\n            ref={ref}\n            {...props}\n          >\n            {children}\n          </div>\n        </TooltipProvider>\n      </SidebarContext.Provider>\n    );\n  }\n);\nSidebarProvider.displayName = \"SidebarProvider\";\n\nconst Sidebar = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<\"div\"> & {\n    side?: \"left\" | \"right\";\n    variant?: \"sidebar\" | \"floating\" | \"inset\";\n    collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n  }\n>(\n  (\n    {\n      side = \"left\",\n      variant = \"sidebar\",\n      collapsible = \"offcanvas\",\n      className,\n      children,\n      ...props\n    },\n    ref\n  ) => {\n    const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n    if (collapsible === \"none\") {\n      return (\n        <div\n          className={cn(\n            \"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col\",\n            className\n          )}\n          ref={ref}\n          {...props}\n        >\n          {children}\n        </div>\n      );\n    }\n\n    if (isMobile) {\n      return (\n        <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n          <SheetContent\n            data-sidebar=\"sidebar\"\n            data-mobile=\"true\"\n            className=\"bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden\"\n            style={\n              {\n                \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n              } as React.CSSProperties\n            }\n            side={side}\n          >\n            <div className=\"flex h-full w-full flex-col\">{children}</div>\n          </SheetContent>\n        </Sheet>\n      );\n    }\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          \"group peer text-sidebar-foreground transition-width hidden w-[var(--sidebar-width)] duration-200 md:block\",\n          state === \"collapsed\" && \"w-0\"\n        )}\n        data-state={state}\n        data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n        data-variant={variant}\n        data-side={side}\n      >\n        <div\n          className={cn(\n            \"sticky top-0 hidden w-[var(--sidebar-width)] transition-[width,transform] duration-200 ease-linear md:flex\",\n            side === \"left\"\n              ? \"group-data-[collapsible=offcanvas]:-translate-x-full\"\n              : \"right-0 group-data-[collapsible=offcanvas]:translate-x-full\",\n            variant === \"floating\" || variant === \"inset\"\n              ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4)+2px)]\"\n              : \"group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)] group-data-[side=left]:border-r group-data-[side=right]:border-l\",\n            className\n          )}\n          {...props}\n        >\n          <div\n            data-sidebar=\"sidebar\"\n            className=\"bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm\"\n          >\n            {children}\n          </div>\n        </div>\n      </div>\n    );\n  }\n);\nSidebar.displayName = \"Sidebar\";\n\nconst SidebarTrigger = React.forwardRef<\n  React.ElementRef<typeof Button>,\n  React.ComponentProps<typeof Button>\n>(({ className, onClick, ...props }, ref) => {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      ref={ref}\n      data-sidebar=\"trigger\"\n      variant=\"ghost\"\n      size=\"icon\"\n      className={cn(\"h-7 w-7\", className)}\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      {...props}\n    >\n      <PanelLeft />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n});\nSidebarTrigger.displayName = \"SidebarTrigger\";\n\nconst SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<\"button\">>(\n  ({ className, ...props }, ref) => {\n    const { toggleSidebar } = useSidebar();\n\n    return (\n      <button\n        ref={ref}\n        data-sidebar=\"rail\"\n        aria-label=\"Toggle Sidebar\"\n        tabIndex={-1}\n        onClick={toggleSidebar}\n        title=\"Toggle Sidebar\"\n        className={cn(\n          \"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex\",\n          \"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize\",\n          \"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize\",\n          \"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full\",\n          \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n          \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n          className\n        )}\n        {...props}\n      />\n    );\n  }\n);\nSidebarRail.displayName = \"SidebarRail\";\n\nconst SidebarInset = React.forwardRef<HTMLDivElement, React.ComponentProps<\"main\">>(\n  ({ className, ...props }, ref) => {\n    return (\n      <main\n        ref={ref}\n        className={cn(\n          \"bg-background relative flex min-h-svh flex-1 flex-col\",\n          \"transition-[margin] duration-200 ease-linear\",\n          \"peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm\",\n          className\n        )}\n        {...props}\n      />\n    );\n  }\n);\nSidebarInset.displayName = \"SidebarInset\";\n\nconst SidebarInput = React.forwardRef<\n  React.ElementRef<typeof Input>,\n  React.ComponentProps<typeof Input>\n>(({ className, ...props }, ref) => {\n  return (\n    <Input\n      ref={ref}\n      data-sidebar=\"input\"\n      className={cn(\n        \"bg-background focus-visible:ring-sidebar-ring h-8 w-full shadow-none focus-visible:ring-2\",\n        className\n      )}\n      {...props}\n    />\n  );\n});\nSidebarInput.displayName = \"SidebarInput\";\n\nconst SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n  ({ className, ...props }, ref) => {\n    return (\n      <div\n        ref={ref}\n        data-sidebar=\"header\"\n        className={cn(\"flex flex-col gap-2 p-2\", className)}\n        {...props}\n      />\n    );\n  }\n);\nSidebarHeader.displayName = \"SidebarHeader\";\n\nconst SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n  ({ className, ...props }, ref) => {\n    return (\n      <div\n        ref={ref}\n        data-sidebar=\"footer\"\n        className={cn(\"flex flex-col gap-2 p-2\", className)}\n        {...props}\n      />\n    );\n  }\n);\nSidebarFooter.displayName = \"SidebarFooter\";\n\nconst SidebarSeparator = React.forwardRef<\n  React.ElementRef<typeof Separator>,\n  React.ComponentProps<typeof Separator>\n>(({ className, ...props }, ref) => {\n  return (\n    <Separator\n      ref={ref}\n      data-sidebar=\"separator\"\n      className={cn(\"bg-sidebar-border mx-2 w-auto\", className)}\n      {...props}\n    />\n  );\n});\nSidebarSeparator.displayName = \"SidebarSeparator\";\n\nconst SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n  ({ className, ...props }, ref) => {\n    return (\n      <div\n        ref={ref}\n        data-sidebar=\"content\"\n        className={cn(\n          \"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden\",\n          className\n        )}\n        {...props}\n      />\n    );\n  }\n);\nSidebarContent.displayName = \"SidebarContent\";\n\nconst SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n  ({ className, ...props }, ref) => {\n    return (\n      <div\n        ref={ref}\n        data-sidebar=\"group\"\n        className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n        {...props}\n      />\n    );\n  }\n);\nSidebarGroup.displayName = \"SidebarGroup\";\n\nconst SidebarGroupLabel = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<\"div\"> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n  const Comp = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <Comp\n      ref={ref}\n      data-sidebar=\"group-label\"\n      className={cn(\n        \"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opa] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n        \"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0\",\n        className\n      )}\n      {...props}\n    />\n  );\n});\nSidebarGroupLabel.displayName = \"SidebarGroupLabel\";\n\nconst SidebarGroupAction = React.forwardRef<\n  HTMLButtonElement,\n  React.ComponentProps<\"button\"> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n  const Comp = asChild ? SlotPrimitive.Slot : \"button\";\n\n  return (\n    <Comp\n      ref={ref}\n      data-sidebar=\"group-action\"\n      className={cn(\n        \"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n        // Increases the hit area of the button on mobile.\n        \"after:absolute after:-inset-2 md:after:hidden\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className\n      )}\n      {...props}\n    />\n  );\n});\nSidebarGroupAction.displayName = \"SidebarGroupAction\";\n\nconst SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n  ({ className, ...props }, ref) => (\n    <div\n      ref={ref}\n      data-sidebar=\"group-content\"\n      className={cn(\"w-full text-sm\", className)}\n      {...props}\n    />\n  )\n);\nSidebarGroupContent.displayName = \"SidebarGroupContent\";\n\nconst SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n  ({ className, ...props }, ref) => (\n    <ul\n      ref={ref}\n      data-sidebar=\"menu\"\n      className={cn(\"flex w-full min-w-0 flex-col gap-1\", className)}\n      {...props}\n    />\n  )\n);\nSidebarMenu.displayName = \"SidebarMenu\";\n\nconst SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n  ({ className, ...props }, ref) => (\n    <li\n      ref={ref}\n      data-sidebar=\"menu-item\"\n      className={cn(\"group/menu-item relative\", className)}\n      {...props}\n    />\n  )\n);\nSidebarMenuItem.displayName = \"SidebarMenuItem\";\n\nconst sidebarMenuButtonVariants = cva(\n  \"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: \"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\n        outline:\n          \"bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]\",\n      },\n      size: {\n        default: \"h-8 text-sm\",\n        sm: \"h-7 text-xs\",\n        lg: \"h-12 text-sm group-data-[collapsible=icon]:p-0!\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nconst SidebarMenuButton = React.forwardRef<\n  HTMLButtonElement,\n  React.ComponentProps<\"button\"> & {\n    asChild?: boolean;\n    isActive?: boolean;\n    tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n  } & VariantProps<typeof sidebarMenuButtonVariants>\n>(\n  (\n    {\n      asChild = false,\n      isActive = false,\n      variant = \"default\",\n      size = \"default\",\n      tooltip,\n      className,\n      ...props\n    },\n    ref\n  ) => {\n    const Comp = asChild ? SlotPrimitive.Slot : \"button\";\n    const { isMobile, state } = useSidebar();\n\n    const button = (\n      <Comp\n        ref={ref}\n        data-sidebar=\"menu-button\"\n        data-size={size}\n        data-active={isActive}\n        className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n        {...props}\n      />\n    );\n\n    if (!tooltip) {\n      return button;\n    }\n\n    if (typeof tooltip === \"string\") {\n      tooltip = {\n        children: tooltip,\n      };\n    }\n\n    return (\n      <Tooltip>\n        <TooltipTrigger asChild>{button}</TooltipTrigger>\n        <TooltipContent\n          side=\"right\"\n          align=\"center\"\n          hidden={state !== \"collapsed\" || isMobile}\n          {...tooltip}\n        />\n      </Tooltip>\n    );\n  }\n);\nSidebarMenuButton.displayName = \"SidebarMenuButton\";\n\nconst SidebarMenuAction = React.forwardRef<\n  HTMLButtonElement,\n  React.ComponentProps<\"button\"> & {\n    asChild?: boolean;\n    showOnHover?: boolean;\n  }\n>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {\n  const Comp = asChild ? SlotPrimitive.Slot : \"button\";\n\n  return (\n    <Comp\n      ref={ref}\n      data-sidebar=\"menu-action\"\n      className={cn(\n        \"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n        // Increases the hit area of the button on mobile.\n        \"after:absolute after:-inset-2 md:after:hidden\",\n        \"peer-data-[size=sm]/menu-button:top-1\",\n        \"peer-data-[size=default]/menu-button:top-1.5\",\n        \"peer-data-[size=lg]/menu-button:top-2.5\",\n        \"group-data-[collapsible=icon]:hidden\",\n        showOnHover &&\n          \"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0\",\n        className\n      )}\n      {...props}\n    />\n  );\n});\nSidebarMenuAction.displayName = \"SidebarMenuAction\";\n\nconst SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n  ({ className, ...props }, ref) => (\n    <div\n      ref={ref}\n      data-sidebar=\"menu-badge\"\n      className={cn(\n        \"text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none\",\n        \"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground\",\n        \"peer-data-[size=sm]/menu-button:top-1\",\n        \"peer-data-[size=default]/menu-button:top-1.5\",\n        \"peer-data-[size=lg]/menu-button:top-2.5\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className\n      )}\n      {...props}\n    />\n  )\n);\nSidebarMenuBadge.displayName = \"SidebarMenuBadge\";\n\nconst SidebarMenuSkeleton = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<\"div\"> & {\n    showIcon?: boolean;\n  }\n>(({ className, showIcon = false, ...props }, ref) => {\n  // Random width between 50 to 90%.\n  const width = React.useMemo(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  }, []);\n\n  return (\n    <div\n      ref={ref}\n      data-sidebar=\"menu-skeleton\"\n      className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\n      {...props}\n    >\n      {showIcon && <Skeleton className=\"size-4 rounded-md\" data-sidebar=\"menu-skeleton-icon\" />}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            \"--skeleton-width\": width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n});\nSidebarMenuSkeleton.displayName = \"SidebarMenuSkeleton\";\n\nconst SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n  ({ className, ...props }, ref) => (\n    <ul\n      ref={ref}\n      data-sidebar=\"menu-sub\"\n      className={cn(\n        \"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className\n      )}\n      {...props}\n    />\n  )\n);\nSidebarMenuSub.displayName = \"SidebarMenuSub\";\n\nconst SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n  ({ ...props }, ref) => <li ref={ref} {...props} />\n);\nSidebarMenuSubItem.displayName = \"SidebarMenuSubItem\";\n\nconst SidebarMenuSubButton = React.forwardRef<\n  HTMLAnchorElement,\n  React.ComponentProps<\"a\"> & {\n    asChild?: boolean;\n    size?: \"sm\" | \"md\";\n    isActive?: boolean;\n  }\n>(({ asChild = false, size = \"md\", isActive, className, ...props }, ref) => {\n  const Comp = asChild ? SlotPrimitive.Slot : \"a\";\n\n  return (\n    <Comp\n      ref={ref}\n      data-sidebar=\"menu-sub-button\"\n      data-size={size}\n      data-active={isActive}\n      className={cn(\n        \"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n        \"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground\",\n        size === \"sm\" && \"text-xs\",\n        size === \"md\" && \"text-sm\",\n        \"group-data-[collapsible=icon]:hidden\",\n        className\n      )}\n      {...props}\n    />\n  );\n});\nSidebarMenuSubButton.displayName = \"SidebarMenuSubButton\";\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n"
  },
  {
    "path": "components/ui/skeleton.tsx",
    "content": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"skeleton\"\n      className={cn(\"animate-pulse rounded-md bg-accent\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Skeleton }\n"
  },
  {
    "path": "components/ui/slider.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slider as SliderPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Slider({\n  className,\n  defaultValue,\n  value,\n  min = 0,\n  max = 100,\n  ...props\n}: React.ComponentProps<typeof SliderPrimitive.Root>) {\n  const _values = React.useMemo(\n    () =>\n      Array.isArray(value)\n        ? value\n        : Array.isArray(defaultValue)\n          ? defaultValue\n          : [min, max],\n    [value, defaultValue, min, max]\n  )\n\n  return (\n    <SliderPrimitive.Root\n      data-slot=\"slider\"\n      defaultValue={defaultValue}\n      value={value}\n      min={min}\n      max={max}\n      className={cn(\n        \"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col\",\n        className\n      )}\n      {...props}\n    >\n      <SliderPrimitive.Track\n        data-slot=\"slider-track\"\n        className={cn(\n          \"relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\"\n        )}\n      >\n        <SliderPrimitive.Range\n          data-slot=\"slider-range\"\n          className={cn(\n            \"absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\"\n          )}\n        />\n      </SliderPrimitive.Track>\n      {Array.from({ length: _values.length }, (_, index) => (\n        <SliderPrimitive.Thumb\n          data-slot=\"slider-thumb\"\n          key={index}\n          className=\"block size-4 shrink-0 rounded-full border border-primary bg-white shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\"\n        />\n      ))}\n    </SliderPrimitive.Root>\n  )\n}\n\nexport { Slider }\n"
  },
  {
    "path": "components/ui/sonner.tsx",
    "content": "\"use client\"\n\nimport {\n  CircleCheckIcon,\n  InfoIcon,\n  Loader2Icon,\n  OctagonXIcon,\n  TriangleAlertIcon,\n} from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, type ToasterProps } from \"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      icons={{\n        success: <CircleCheckIcon className=\"size-4\" />,\n        info: <InfoIcon className=\"size-4\" />,\n        warning: <TriangleAlertIcon className=\"size-4\" />,\n        error: <OctagonXIcon className=\"size-4\" />,\n        loading: <Loader2Icon className=\"size-4 animate-spin\" />,\n      }}\n      style={\n        {\n          \"--normal-bg\": \"var(--popover)\",\n          \"--normal-text\": \"var(--popover-foreground)\",\n          \"--normal-border\": \"var(--border)\",\n          \"--border-radius\": \"var(--radius)\",\n        } as React.CSSProperties\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 { Switch as SwitchPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Switch({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<typeof SwitchPrimitive.Root> & {\n  size?: \"sm\" | \"default\"\n}) {\n  return (\n    <SwitchPrimitive.Root\n      data-slot=\"switch\"\n      data-size={size}\n      className={cn(\n        \"peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80\",\n        className\n      )}\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        data-slot=\"switch-thumb\"\n        className={cn(\n          \"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground\"\n        )}\n      />\n    </SwitchPrimitive.Root>\n  )\n}\n\nexport { Switch }\n"
  },
  {
    "path": "components/ui/table.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Table({ className, ...props }: React.ComponentProps<\"table\">) {\n  return (\n    <div\n      data-slot=\"table-container\"\n      className=\"relative w-full overflow-x-auto\"\n    >\n      <table\n        data-slot=\"table\"\n        className={cn(\"w-full caption-bottom text-sm\", className)}\n        {...props}\n      />\n    </div>\n  )\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<\"thead\">) {\n  return (\n    <thead\n      data-slot=\"table-header\"\n      className={cn(\"[&_tr]:border-b\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<\"tbody\">) {\n  return (\n    <tbody\n      data-slot=\"table-body\"\n      className={cn(\"[&_tr:last-child]:border-0\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<\"tfoot\">) {\n  return (\n    <tfoot\n      data-slot=\"table-footer\"\n      className={cn(\n        \"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<\"tr\">) {\n  return (\n    <tr\n      data-slot=\"table-row\"\n      className={cn(\n        \"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<\"th\">) {\n  return (\n    <th\n      data-slot=\"table-head\"\n      className={cn(\n        \"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<\"td\">) {\n  return (\n    <td\n      data-slot=\"table-cell\"\n      className={cn(\n        \"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<\"caption\">) {\n  return (\n    <caption\n      data-slot=\"table-caption\"\n      className={cn(\"mt-4 text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption,\n}\n"
  },
  {
    "path": "components/ui/tabs.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Tabs as TabsPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Tabs({\n  className,\n  orientation = \"horizontal\",\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Root>) {\n  return (\n    <TabsPrimitive.Root\n      data-slot=\"tabs\"\n      data-orientation={orientation}\n      orientation={orientation}\n      className={cn(\n        \"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst tabsListVariants = cva(\n  \"group/tabs-list inline-flex h-10 w-fit items-center justify-center rounded-md p-1 text-muted-foreground group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-muted\",\n        line: \"gap-1 bg-transparent\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction TabsList({\n  className,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.List> &\n  VariantProps<typeof tabsListVariants>) {\n  return (\n    <TabsPrimitive.List\n      data-slot=\"tabs-list\"\n      data-variant={variant}\n      className={cn(tabsListVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction TabsTrigger({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {\n  return (\n    <TabsPrimitive.Trigger\n      data-slot=\"tabs-trigger\"\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-hidden 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-xs\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TabsContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Content>) {\n  return (\n    <TabsPrimitive.Content\n      data-slot=\"tabs-content\"\n      className={cn(\"mt-2 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }\n"
  },
  {
    "path": "components/ui/textarea.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Textarea({ className, ...props }: React.ComponentProps<\"textarea\">) {\n  return (\n    <textarea\n      data-slot=\"textarea\"\n      className={cn(\n        \"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Textarea }\n"
  },
  {
    "path": "components/ui/toast.tsx",
    "content": "import * as React from \"react\";\nimport { Toast as ToastPrimitives } from \"radix-ui\";\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 sm:data-[state=open]: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-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 hover:group-[.destructive]:border-destructive/30 hover:group-[.destructive]:bg-destructive hover:group-[.destructive]:text-destructive-foreground focus:group-[.destructive]: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-hidden focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 hover:group-[.destructive]:text-red-50 focus:group-[.destructive]:ring-red-400 focus:group-[.destructive]: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 { useToast } from \"@/hooks/use-toast\";\nimport {\n  Toast,\n  ToastClose,\n  ToastDescription,\n  ToastProvider,\n  ToastTitle,\n  ToastViewport,\n} from \"@/components/ui/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 && <ToastDescription>{description}</ToastDescription>}\n            </div>\n            {action}\n            <ToastClose />\n          </Toast>\n        );\n      })}\n      <ToastViewport />\n    </ToastProvider>\n  );\n}\n"
  },
  {
    "path": "components/ui/toggle-group.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { type VariantProps } from \"class-variance-authority\"\nimport { ToggleGroup as ToggleGroupPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { toggleVariants } from \"@/components/ui/toggle\"\n\nconst ToggleGroupContext = React.createContext<\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number\n  }\n>({\n  size: \"default\",\n  variant: \"default\",\n  spacing: 0,\n})\n\nfunction ToggleGroup({\n  className,\n  variant,\n  size,\n  spacing = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number\n  }) {\n  return (\n    <ToggleGroupPrimitive.Root\n      data-slot=\"toggle-group\"\n      data-variant={variant}\n      data-size={size}\n      data-spacing={spacing}\n      style={{ \"--gap\": spacing } as React.CSSProperties}\n      className={cn(\n        \"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs\",\n        className\n      )}\n      {...props}\n    >\n      <ToggleGroupContext.Provider value={{ variant, size, spacing }}>\n        {children}\n      </ToggleGroupContext.Provider>\n    </ToggleGroupPrimitive.Root>\n  )\n}\n\nfunction ToggleGroupItem({\n  className,\n  children,\n  variant,\n  size,\n  ...props\n}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &\n  VariantProps<typeof toggleVariants>) {\n  const context = React.useContext(ToggleGroupContext)\n\n  return (\n    <ToggleGroupPrimitive.Item\n      data-slot=\"toggle-group-item\"\n      data-variant={context.variant || variant}\n      data-size={context.size || size}\n      data-spacing={context.spacing}\n      className={cn(\n        toggleVariants({\n          variant: context.variant || variant,\n          size: context.size || size,\n        }),\n        \"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10\",\n        \"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </ToggleGroupPrimitive.Item>\n  )\n}\n\nexport { ToggleGroup, ToggleGroupItem }\n"
  },
  {
    "path": "components/ui/toggle.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Toggle as TogglePrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst toggleVariants = cva(\n  \"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        outline:\n          \"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground\",\n      },\n      size: {\n        default: \"h-9 min-w-9 px-2\",\n        sm: \"h-8 min-w-8 px-1.5\",\n        lg: \"h-10 min-w-10 px-2.5\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Toggle({\n  className,\n  variant,\n  size,\n  ...props\n}: React.ComponentProps<typeof TogglePrimitive.Root> &\n  VariantProps<typeof toggleVariants>) {\n  return (\n    <TogglePrimitive.Root\n      data-slot=\"toggle\"\n      className={cn(toggleVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Toggle, toggleVariants }\n"
  },
  {
    "path": "components/ui/tooltip.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Tooltip as TooltipPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction TooltipProvider({\n  delayDuration = 0,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delayDuration={delayDuration}\n      {...props}\n    />\n  )\n}\n\nfunction Tooltip({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n}\n\nfunction TooltipTrigger({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />\n}\n\nfunction TooltipContent({\n  className,\n  sideOffset = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Content\n        data-slot=\"tooltip-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          \"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <TooltipPrimitive.Arrow className=\"z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground\" />\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  )\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }\n"
  },
  {
    "path": "components/ui/use-toast.ts",
    "content": "import { useToast, toast } from \"@/hooks/use-toast\";\n\nexport { useToast, toast };\n"
  },
  {
    "path": "components/user-profile-dropdown.tsx",
    "content": "\"use client\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { useTheme } from \"@/components/theme-provider\";\nimport { useSubscription } from \"@/hooks/use-subscription\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { cn } from \"@/lib/utils\";\nimport { useAuthStore } from \"@/store/auth-store\";\nimport { Switch as SwitchPrimitives } from \"radix-ui\";\nimport { BookLock, Gem, Loader2, LogOut, Moon, Settings, Sun } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport { usePostHog } from \"posthog-js/react\";\n\nexport function UserProfileDropdown() {\n  const { data: session, isPending } = authClient.useSession();\n  const { openAuthDialog } = useAuthStore();\n  const { theme, toggleTheme } = useTheme();\n  const posthog = usePostHog();\n  const router = useRouter();\n\n  const { subscriptionStatus } = useSubscription();\n  const isPro = subscriptionStatus?.isSubscribed ?? false;\n\n  const handleLogOut = async () => {\n    posthog.reset();\n    await authClient.signOut();\n    router.refresh();\n  };\n\n  return (\n    <AnimatePresence mode=\"wait\">\n      {isPending ? (\n        <motion.div\n          key=\"spinner\"\n          initial={{ opacity: 0 }}\n          animate={{ opacity: 1 }}\n          exit={{ opacity: 0 }}\n          transition={{ duration: 0.2 }}\n          className=\"flex size-8 items-center justify-center\"\n        >\n          <Loader2 className=\"text-muted-foreground size-7 animate-spin\" />\n        </motion.div>\n      ) : !session?.user ? (\n        <motion.div\n          key=\"auth-buttons\"\n          initial={{ opacity: 0 }}\n          animate={{ opacity: 1 }}\n          exit={{ opacity: 0 }}\n          transition={{ duration: 0.2 }}\n          className=\"flex gap-3.5\"\n        >\n          <Button\n            variant=\"link\"\n            onClick={() => openAuthDialog(\"signin\")}\n            className=\"text-foreground hover:text-primary hidden h-8 px-0 hover:no-underline md:inline-flex\"\n          >\n            Sign In\n          </Button>\n          <Button onClick={() => openAuthDialog(\"signup\")} className=\"h-8\">\n            Sign Up\n          </Button>\n        </motion.div>\n      ) : (\n        <motion.div\n          key=\"user-dropdown\"\n          initial={{ opacity: 0 }}\n          animate={{ opacity: 1 }}\n          exit={{ opacity: 0 }}\n          transition={{ duration: 0.2 }}\n          className=\"flex\"\n        >\n          <DropdownMenu>\n            <DropdownMenuTrigger asChild>\n              <Button variant=\"ghost\" className=\"0 relative isolate size-8 rounded-full\">\n                <Avatar className=\"size-8\">\n                  <AvatarImage src={session.user.image || \"\"} alt={session.user.name || \"\"} />\n                  <AvatarFallback>{session.user.name?.[0] || \"U\"}</AvatarFallback>\n                </Avatar>\n\n                {isPro && (\n                  <div className=\"bg-accent absolute top-0 left-0 z-1 flex size-4 -translate-x-1/4 -translate-y-1/4 items-center justify-center rounded-full\">\n                    <Gem className=\"text-accent-foreground size-3!\" />\n                  </div>\n                )}\n              </Button>\n            </DropdownMenuTrigger>\n            <DropdownMenuContent className=\"w-56\" align=\"end\" forceMount>\n              <DropdownMenuLabel className=\"font-normal\">\n                <div className=\"flex flex-col space-y-0.5\">\n                  <p className=\"text-sm leading-tight font-medium\">\n                    {session.user.name}{\" \"}\n                    {isPro && (\n                      <span className=\"bg-accent text-accent-foreground inline-flex w-fit items-center gap-1 rounded-md px-1 py-0.5 text-xs leading-tight font-medium\">\n                        <Gem className=\"size-2.5\" /> Pro\n                      </span>\n                    )}\n                  </p>\n                  <p className=\"text-muted-foreground text-xs leading-tight\">\n                    {session.user.email}\n                  </p>\n                </div>\n              </DropdownMenuLabel>\n              <DropdownMenuSeparator className=\"bg-border opacity-80\" />\n              <DropdownMenuItem asChild>\n                <Link href=\"/settings\">\n                  <Settings /> Settings\n                </Link>\n              </DropdownMenuItem>\n              <DropdownMenuItem\n                onSelect={(e) => e.preventDefault()}\n                className=\"flex items-center justify-between\"\n              >\n                <div className=\"flex items-center gap-2\">\n                  {theme === \"dark\" ? <Moon className=\"size-4\" /> : <Sun className=\"size-4\" />}\n                  <span>Theme</span>\n                </div>\n                <SwitchPrimitives.Root\n                  checked={theme === \"dark\"}\n                  onClick={(e) => {\n                    const { clientX: x, clientY: y } = e;\n                    toggleTheme({ x, y });\n                  }}\n                  className={cn(\n                    \"inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors\",\n                    theme === \"dark\" ? \"bg-primary\" : \"bg-input\"\n                  )}\n                >\n                  <SwitchPrimitives.Thumb\n                    className={cn(\n                      \"bg-background pointer-events-none flex size-4 items-center justify-center rounded-full shadow-sm ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0\"\n                    )}\n                  >\n                    {theme === \"dark\" ? <Moon className=\"size-2.5\" /> : <Sun className=\"size-2.5\" />}\n                  </SwitchPrimitives.Thumb>\n                </SwitchPrimitives.Root>\n              </DropdownMenuItem>\n              <DropdownMenuSeparator className=\"bg-border opacity-80\" />\n              <DropdownMenuItem asChild>\n                <Link href=\"/privacy-policy\">\n                  <BookLock />\n                  Privacy Policy\n                </Link>\n              </DropdownMenuItem>\n              <DropdownMenuItem \n                onClick={handleLogOut}\n                className=\"text-destructive focus:text-destructive\"\n              >\n                <LogOut /> Log out\n              </DropdownMenuItem>\n            </DropdownMenuContent>\n          </DropdownMenu>\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n}\n"
  },
  {
    "path": "components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"app/globals.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\",\n    \"ui\": \"@/components/ui\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"iconLibrary\": \"lucide\"\n}"
  },
  {
    "path": "config/theme.ts",
    "content": "import { ThemeEditorState } from \"../types/editor\";\n\n// these are common between light and dark modes\n// we can assume that light mode's value will be used for dark mode as well\nexport const COMMON_STYLES = [\n  \"font-sans\",\n  \"font-serif\",\n  \"font-mono\",\n  \"radius\",\n  \"shadow-opacity\",\n  \"shadow-blur\",\n  \"shadow-spread\",\n  \"shadow-offset-x\",\n  \"shadow-offset-y\",\n  \"letter-spacing\",\n  \"spacing\",\n];\n\nexport const DEFAULT_FONT_SANS =\n  \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'\";\n\nexport const DEFAULT_FONT_SERIF = 'ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif';\n\nexport const DEFAULT_FONT_MONO =\n  'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace';\n\n// Default light theme styles\nexport const defaultLightThemeStyles = {\n  background: \"oklch(1 0 0)\",\n  foreground: \"oklch(0.145 0 0)\",\n  card: \"oklch(1 0 0)\",\n  \"card-foreground\": \"oklch(0.145 0 0)\",\n  popover: \"oklch(1 0 0)\",\n  \"popover-foreground\": \"oklch(0.145 0 0)\",\n  primary: \"oklch(0.205 0 0)\",\n  \"primary-foreground\": \"oklch(0.985 0 0)\",\n  secondary: \"oklch(0.97 0 0)\",\n  \"secondary-foreground\": \"oklch(0.205 0 0)\",\n  muted: \"oklch(0.97 0 0)\",\n  \"muted-foreground\": \"oklch(0.556 0 0)\",\n  accent: \"oklch(0.97 0 0)\",\n  \"accent-foreground\": \"oklch(0.205 0 0)\",\n  destructive: \"oklch(0.577 0.245 27.325)\",\n  \"destructive-foreground\": \"oklch(1 0 0)\",\n  border: \"oklch(0.922 0 0)\",\n  input: \"oklch(0.922 0 0)\",\n  ring: \"oklch(0.708 0 0)\",\n  \"chart-1\": \"oklch(0.81 0.10 252)\",\n  \"chart-2\": \"oklch(0.62 0.19 260)\",\n  \"chart-3\": \"oklch(0.55 0.22 263)\",\n  \"chart-4\": \"oklch(0.49 0.22 264)\",\n  \"chart-5\": \"oklch(0.42 0.18 266)\",\n  radius: \"0.625rem\",\n  sidebar: \"oklch(0.985 0 0)\",\n  \"sidebar-foreground\": \"oklch(0.145 0 0)\",\n  \"sidebar-primary\": \"oklch(0.205 0 0)\",\n  \"sidebar-primary-foreground\": \"oklch(0.985 0 0)\",\n  \"sidebar-accent\": \"oklch(0.97 0 0)\",\n  \"sidebar-accent-foreground\": \"oklch(0.205 0 0)\",\n  \"sidebar-border\": \"oklch(0.922 0 0)\",\n  \"sidebar-ring\": \"oklch(0.708 0 0)\",\n  \"font-sans\": DEFAULT_FONT_SANS,\n  \"font-serif\": DEFAULT_FONT_SERIF,\n  \"font-mono\": DEFAULT_FONT_MONO,\n\n  \"shadow-color\": \"oklch(0 0 0)\",\n  \"shadow-opacity\": \"0.1\",\n  \"shadow-blur\": \"3px\",\n  \"shadow-spread\": \"0px\",\n  \"shadow-offset-x\": \"0\",\n  \"shadow-offset-y\": \"1px\",\n\n  \"letter-spacing\": \"0em\",\n  spacing: \"0.25rem\",\n};\n\n// Default dark theme styles\nexport const defaultDarkThemeStyles = {\n  ...defaultLightThemeStyles,\n  background: \"oklch(0.145 0 0)\",\n  foreground: \"oklch(0.985 0 0)\",\n  card: \"oklch(0.205 0 0)\",\n  \"card-foreground\": \"oklch(0.985 0 0)\",\n  popover: \"oklch(0.269 0 0)\",\n  \"popover-foreground\": \"oklch(0.985 0 0)\",\n  primary: \"oklch(0.922 0 0)\",\n  \"primary-foreground\": \"oklch(0.205 0 0)\",\n  secondary: \"oklch(0.269 0 0)\",\n  \"secondary-foreground\": \"oklch(0.985 0 0)\",\n  muted: \"oklch(0.269 0 0)\",\n  \"muted-foreground\": \"oklch(0.708 0 0)\",\n  accent: \"oklch(0.371 0 0)\",\n  \"accent-foreground\": \"oklch(0.985 0 0)\",\n  destructive: \"oklch(0.704 0.191 22.216)\",\n  \"destructive-foreground\": \"oklch(0.985 0 0)\",\n  border: \"oklch(0.275 0 0)\", // in place of oklch(1 0 0 / 10%)\n  input: \"oklch(0.325 0 0)\", // in place of oklch(1 0 0 / 15%)\n  ring: \"oklch(0.556 0 0)\",\n  \"chart-1\": \"oklch(0.81 0.10 252)\",\n  \"chart-2\": \"oklch(0.62 0.19 260)\",\n  \"chart-3\": \"oklch(0.55 0.22 263)\",\n  \"chart-4\": \"oklch(0.49 0.22 264)\",\n  \"chart-5\": \"oklch(0.42 0.18 266)\",\n  // Actual has radius but not in Expected, keeping it as is\n  radius: \"0.625rem\",\n  // Converting sidebar-related variables to match Actual format\n  sidebar: \"oklch(0.205 0 0)\",\n  \"sidebar-foreground\": \"oklch(0.985 0 0)\",\n  \"sidebar-primary\": \"oklch(0.488 0.243 264.376)\",\n  \"sidebar-primary-foreground\": \"oklch(0.985 0 0)\",\n  \"sidebar-accent\": \"oklch(0.269 0 0)\",\n  \"sidebar-accent-foreground\": \"oklch(0.985 0 0)\",\n  \"sidebar-border\": \"oklch(0.275 0 0)\", // in place of oklch(1 0 0 / 10%)\n  \"sidebar-ring\": \"oklch(0.439 0 0)\",\n\n  \"shadow-color\": \"oklch(0 0 0)\",\n\n  \"letter-spacing\": \"0em\",\n  spacing: \"0.25rem\",\n};\n\n// Default theme state\nexport const defaultThemeState: ThemeEditorState = {\n  styles: {\n    light: defaultLightThemeStyles,\n    dark: defaultDarkThemeStyles,\n  },\n  currentMode:\n    typeof window !== \"undefined\" && window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n      ? \"dark\"\n      : \"light\",\n  hslAdjustments: {\n    hueShift: 0,\n    saturationScale: 1,\n    lightnessScale: 1,\n  },\n};\n"
  },
  {
    "path": "db/index.ts",
    "content": "import { drizzle, type NeonHttpDatabase } from \"drizzle-orm/neon-http\";\nimport { neon } from \"@neondatabase/serverless\";\n\nlet _db: NeonHttpDatabase | null = null;\n\nexport const db = new Proxy({} as NeonHttpDatabase, {\n  get(_target, prop) {\n    if (!_db) {\n      if (!process.env.DATABASE_URL) {\n        throw new Error(\n          \"DATABASE_URL is not set. Database features are disabled in local development without a database.\"\n        );\n      }\n      const sql = neon(process.env.DATABASE_URL);\n      _db = drizzle({ client: sql });\n    }\n    return (_db as any)[prop];\n  },\n});\n"
  },
  {
    "path": "db/schema.ts",
    "content": "import { ThemeStyles } from \"@/types/theme\";\nimport {\n  pgTable,\n  json,\n  timestamp,\n  boolean,\n  text,\n  integer,\n  primaryKey,\n  index,\n} from \"drizzle-orm/pg-core\";\n\nexport const user = pgTable(\"user\", {\n  id: text(\"id\").primaryKey(),\n  name: text(\"name\").notNull(),\n  email: text(\"email\").notNull().unique(),\n  emailVerified: boolean(\"email_verified\").notNull(),\n  image: text(\"image\"),\n  createdAt: timestamp(\"created_at\").notNull(),\n  updatedAt: timestamp(\"updated_at\").notNull(),\n});\n\nexport const session = pgTable(\"session\", {\n  id: text(\"id\").primaryKey(),\n  expiresAt: timestamp(\"expires_at\").notNull(),\n  token: text(\"token\").notNull().unique(),\n  createdAt: timestamp(\"created_at\").notNull(),\n  updatedAt: timestamp(\"updated_at\").notNull(),\n  ipAddress: text(\"ip_address\"),\n  userAgent: text(\"user_agent\"),\n  userId: text(\"user_id\")\n    .notNull()\n    .references(() => user.id, { onDelete: \"cascade\" }),\n});\n\nexport const account = pgTable(\"account\", {\n  id: text(\"id\").primaryKey(),\n  accountId: text(\"account_id\").notNull(),\n  providerId: text(\"provider_id\").notNull(),\n  userId: text(\"user_id\")\n    .notNull()\n    .references(() => user.id, { onDelete: \"cascade\" }),\n  accessToken: text(\"access_token\"),\n  refreshToken: text(\"refresh_token\"),\n  idToken: text(\"id_token\"),\n  accessTokenExpiresAt: timestamp(\"access_token_expires_at\"),\n  refreshTokenExpiresAt: timestamp(\"refresh_token_expires_at\"),\n  scope: text(\"scope\"),\n  password: text(\"password\"),\n  createdAt: timestamp(\"created_at\").notNull(),\n  updatedAt: timestamp(\"updated_at\").notNull(),\n});\n\nexport const verification = pgTable(\"verification\", {\n  id: text(\"id\").primaryKey(),\n  identifier: text(\"identifier\").notNull(),\n  value: text(\"value\").notNull(),\n  expiresAt: timestamp(\"expires_at\").notNull(),\n  createdAt: timestamp(\"created_at\"),\n  updatedAt: timestamp(\"updated_at\"),\n});\n\nexport const theme = pgTable(\"theme\", {\n  id: text(\"id\").primaryKey(),\n  userId: text(\"user_id\")\n    .notNull()\n    .references(() => user.id, { onDelete: \"cascade\" }),\n  name: text(\"name\").notNull(),\n  styles: json(\"styles\").$type<ThemeStyles>().notNull(),\n  createdAt: timestamp(\"created_at\").notNull(),\n  updatedAt: timestamp(\"updated_at\").notNull(),\n});\n\nexport const aiUsage = pgTable(\"ai_usage\", {\n  id: text(\"id\").primaryKey(),\n  userId: text(\"user_id\")\n    .notNull()\n    .references(() => user.id, { onDelete: \"cascade\" }),\n  modelId: text(\"model_id\").notNull(),\n  promptTokens: text(\"prompt_tokens\").notNull().default(\"0\"),\n  completionTokens: text(\"completion_tokens\").notNull().default(\"0\"),\n  daysSinceEpoch: text(\"days_since_epoch\").notNull(),\n  createdAt: timestamp(\"created_at\").notNull(),\n});\n\nexport const subscription = pgTable(\"subscription\", {\n  id: text(\"id\").primaryKey(),\n  createdAt: timestamp(\"createdAt\").notNull(),\n  modifiedAt: timestamp(\"modifiedAt\"),\n  amount: integer(\"amount\").notNull(),\n  currency: text(\"currency\").notNull(),\n  recurringInterval: text(\"recurringInterval\").notNull(),\n  status: text(\"status\").notNull(),\n  currentPeriodStart: timestamp(\"currentPeriodStart\").notNull(),\n  currentPeriodEnd: timestamp(\"currentPeriodEnd\").notNull(),\n  cancelAtPeriodEnd: boolean(\"cancelAtPeriodEnd\").notNull().default(false),\n  canceledAt: timestamp(\"canceledAt\"),\n  startedAt: timestamp(\"startedAt\").notNull(),\n  endsAt: timestamp(\"endsAt\"),\n  endedAt: timestamp(\"endedAt\"),\n  customerId: text(\"customerId\").notNull(),\n  productId: text(\"productId\").notNull(),\n  discountId: text(\"discountId\"),\n  checkoutId: text(\"checkoutId\").notNull(),\n  customerCancellationReason: text(\"customerCancellationReason\"),\n  customerCancellationComment: text(\"customerCancellationComment\"),\n  metadata: text(\"metadata\"), // JSON string\n  customFieldData: text(\"customFieldData\"), // JSON string\n  userId: text(\"userId\").references(() => user.id),\n});\n\n// OAuth 2.0 tables\n\nexport const oauthApp = pgTable(\"oauth_app\", {\n  id: text(\"id\").primaryKey(),\n  name: text(\"name\").notNull(),\n  description: text(\"description\"),\n  clientId: text(\"client_id\").notNull().unique(),\n  clientSecretHash: text(\"client_secret_hash\").notNull(),\n  redirectUris: json(\"redirect_uris\").$type<string[]>().notNull(),\n  scopes: json(\"scopes\").$type<string[]>().notNull(),\n  isActive: boolean(\"is_active\").notNull().default(true),\n  createdAt: timestamp(\"created_at\").notNull(),\n  updatedAt: timestamp(\"updated_at\").notNull(),\n});\n\nexport const oauthAuthorizationCode = pgTable(\"oauth_authorization_code\", {\n  id: text(\"id\").primaryKey(),\n  code: text(\"code\").notNull().unique(),\n  appId: text(\"app_id\")\n    .notNull()\n    .references(() => oauthApp.id, { onDelete: \"cascade\" }),\n  userId: text(\"user_id\")\n    .notNull()\n    .references(() => user.id, { onDelete: \"cascade\" }),\n  scopes: json(\"scopes\").$type<string[]>().notNull(),\n  redirectUri: text(\"redirect_uri\").notNull(),\n  codeChallenge: text(\"code_challenge\"),\n  codeChallengeMethod: text(\"code_challenge_method\"),\n  expiresAt: timestamp(\"expires_at\").notNull(),\n  usedAt: timestamp(\"used_at\"),\n  createdAt: timestamp(\"created_at\").notNull(),\n});\n\nexport const oauthToken = pgTable(\"oauth_token\", {\n  id: text(\"id\").primaryKey(),\n  accessTokenHash: text(\"access_token_hash\").notNull().unique(),\n  refreshTokenHash: text(\"refresh_token_hash\").unique(),\n  appId: text(\"app_id\")\n    .notNull()\n    .references(() => oauthApp.id, { onDelete: \"cascade\" }),\n  userId: text(\"user_id\")\n    .notNull()\n    .references(() => user.id, { onDelete: \"cascade\" }),\n  scopes: json(\"scopes\").$type<string[]>().notNull(),\n  accessTokenExpiresAt: timestamp(\"access_token_expires_at\").notNull(),\n  refreshTokenExpiresAt: timestamp(\"refresh_token_expires_at\"),\n  revokedAt: timestamp(\"revoked_at\"),\n  createdAt: timestamp(\"created_at\").notNull(),\n  updatedAt: timestamp(\"updated_at\").notNull(),\n});\n\n// Community themes\n\nexport const communityTheme = pgTable(\n  \"community_theme\",\n  {\n    id: text(\"id\").primaryKey(),\n    themeId: text(\"theme_id\")\n      .notNull()\n      .unique()\n      .references(() => theme.id, { onDelete: \"cascade\" }),\n    userId: text(\"user_id\")\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n    publishedAt: timestamp(\"published_at\").notNull(),\n    likeCount: integer(\"like_count\").notNull().default(0),\n  },\n  (table) => [\n    index(\"community_theme_published_at_idx\").on(table.publishedAt),\n    index(\"community_theme_like_count_idx\").on(table.likeCount),\n  ]\n);\n\nexport const communityThemeTag = pgTable(\n  \"community_theme_tag\",\n  {\n    communityThemeId: text(\"community_theme_id\")\n      .notNull()\n      .references(() => communityTheme.id, { onDelete: \"cascade\" }),\n    tag: text(\"tag\").notNull(),\n  },\n  (table) => [\n    primaryKey({ columns: [table.communityThemeId, table.tag] }),\n    index(\"community_theme_tag_tag_idx\").on(table.tag),\n  ]\n);\n\nexport const themeLike = pgTable(\n  \"theme_like\",\n  {\n    userId: text(\"user_id\")\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n    themeId: text(\"theme_id\")\n      .notNull()\n      .references(() => communityTheme.id, { onDelete: \"cascade\" }),\n    createdAt: timestamp(\"created_at\").notNull(),\n  },\n  (table) => [primaryKey({ columns: [table.userId, table.themeId] })]\n);\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: \"3.8\"\n\nservices:\n  app:\n    build: .\n    ports:\n      - \"3000:3000\"\n    env_file:\n      - .env.local\n    depends_on:\n      - db\n    volumes:\n      - .:/app\n      - /app/node_modules\n    command: >\n      sh -c \"npx drizzle-kit push && npm run dev\"\n\n  db:\n    image: postgres:15\n    restart: always\n    environment:\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: postgres\n      POSTGRES_DB: tweakcn\n    ports:\n      - \"5432:5432\"\n    volumes:\n      - pgdata:/var/lib/postgresql/data\n\nvolumes:\n  pgdata:\n"
  },
  {
    "path": "docs/oauth-api.md",
    "content": "# OAuth 2.0 API\n\ntweakcn exposes an OAuth 2.0 API so external apps can authenticate users and access their data.\n\n## Registering an app\n\nRegister an OAuth app via the CLI script:\n\n```bash\nnpx tsx scripts/create-oauth-app.ts \\\n  --name \"My App\" \\\n  --redirect-uris \"https://myapp.com/callback\" \\\n  --scopes \"themes:read,profile:read\" \\\n  --description \"Optional description\"\n```\n\nThis outputs a `client_id` and `client_secret`. The secret is shown once and cannot be retrieved later.\n\n## Authorization flow\n\nStandard OAuth 2.0 Authorization Code flow. PKCE is supported but optional.\n\n### 1. Redirect the user to authorize\n\n```\nGET https://tweakcn.com/api/oauth/authorize\n  ?client_id=CLIENT_ID\n  &redirect_uri=https://myapp.com/callback\n  &response_type=code\n  &scope=themes:read profile:read\n  &state=RANDOM_STRING\n```\n\nIf the user is not signed in, they'll be shown a sign-in page. After signing in, they're redirected to your `redirect_uri` with an authorization code:\n\n```\nhttps://myapp.com/callback?code=AUTH_CODE&state=RANDOM_STRING\n```\n\n### 2. Exchange the code for tokens\n\n```bash\ncurl -X POST https://tweakcn.com/api/oauth/token \\\n  -d grant_type=authorization_code \\\n  -d client_id=CLIENT_ID \\\n  -d client_secret=CLIENT_SECRET \\\n  -d code=AUTH_CODE \\\n  -d redirect_uri=https://myapp.com/callback\n```\n\nResponse:\n\n```json\n{\n  \"access_token\": \"...\",\n  \"refresh_token\": \"...\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 3600,\n  \"scope\": \"themes:read profile:read\"\n}\n```\n\n### 3. Call the API\n\nPass the access token as a Bearer token:\n\n```bash\ncurl https://tweakcn.com/api/v1/themes \\\n  -H \"Authorization: Bearer ACCESS_TOKEN\"\n```\n\n### 4. Refresh tokens\n\nAccess tokens expire after 1 hour. Use the refresh token to get a new pair:\n\n```bash\ncurl -X POST https://tweakcn.com/api/oauth/token \\\n  -d grant_type=refresh_token \\\n  -d client_id=CLIENT_ID \\\n  -d client_secret=CLIENT_SECRET \\\n  -d refresh_token=REFRESH_TOKEN\n```\n\n### 5. Revoke tokens\n\n```bash\ncurl -X POST https://tweakcn.com/api/oauth/revoke \\\n  -d token=ACCESS_OR_REFRESH_TOKEN\n```\n\n## Using with Better Auth's genericOAuth\n\ntweakcn works as a provider with Better Auth's `genericOAuth` plugin:\n\n```typescript\n// server\nimport { genericOAuth } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n  plugins: [\n    genericOAuth({\n      config: [\n        {\n          providerId: \"tweakcn\",\n          clientId: process.env.TWEAKCN_CLIENT_ID,\n          clientSecret: process.env.TWEAKCN_CLIENT_SECRET,\n          authorizationUrl: \"https://tweakcn.com/api/oauth/authorize\",\n          tokenUrl: \"https://tweakcn.com/api/oauth/token\",\n          userInfoUrl: \"https://tweakcn.com/api/oauth/userinfo\",\n          scopes: [\"themes:read\", \"profile:read\"],\n        },\n      ],\n    }),\n  ],\n});\n```\n\n```typescript\n// client\nimport { genericOAuthClient } from \"better-auth/client/plugins\";\n\nconst authClient = createAuthClient({\n  plugins: [genericOAuthClient()],\n});\n\nawait authClient.signIn.oauth2({\n  providerId: \"tweakcn\",\n  callbackURL: \"/dashboard\",\n});\n```\n\n## API endpoints\n\nAll endpoints require `Authorization: Bearer <access_token>`.\n\n### `GET /api/oauth/userinfo`\n\nOIDC-compatible userinfo endpoint. Returns flat user fields. Requires `profile:read` scope.\n\n```json\n{\n  \"sub\": \"user_123\",\n  \"name\": \"Jane Doe\",\n  \"email\": \"jane@example.com\",\n  \"picture\": \"https://...\"\n}\n```\n\n### `GET /api/v1/me`\n\nReturns the authenticated user's profile. Requires `profile:read` scope.\n\n```json\n{\n  \"data\": {\n    \"id\": \"...\",\n    \"name\": \"Jane Doe\",\n    \"email\": \"jane@example.com\",\n    \"image\": \"https://...\"\n  }\n}\n```\n\n### `GET /api/v1/themes`\n\nReturns all themes owned by the authenticated user. Requires `themes:read` scope.\n\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"...\",\n      \"name\": \"My Theme\",\n      \"styles\": { ... },\n      \"createdAt\": \"2025-01-01T00:00:00.000Z\",\n      \"updatedAt\": \"2025-01-01T00:00:00.000Z\"\n    }\n  ]\n}\n```\n\n### `GET /api/v1/themes/:themeId`\n\nReturns a single theme by ID. Only returns themes owned by the authenticated user. Requires `themes:read` scope.\n\n## Scopes\n\n| Scope | Description |\n|-------|-------------|\n| `themes:read` | Read the user's saved themes |\n| `profile:read` | Read the user's profile (name, email, avatar) |\n\n## PKCE support\n\nFor public clients (e.g. SPAs, mobile apps), use PKCE by adding `code_challenge` and `code_challenge_method=S256` to the authorize request, then `code_verifier` when exchanging the code.\n\n## Error responses\n\nAll error responses follow the OAuth 2.0 spec:\n\n```json\n{\n  \"error\": \"invalid_token\",\n  \"error_description\": \"Invalid or expired access token\"\n}\n```\n"
  },
  {
    "path": "drizzle/0000_rare_moira_mactaggert.sql",
    "content": "CREATE TABLE \"account\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"account_id\" text NOT NULL,\n\t\"provider_id\" text NOT NULL,\n\t\"user_id\" text NOT NULL,\n\t\"access_token\" text,\n\t\"refresh_token\" text,\n\t\"id_token\" text,\n\t\"access_token_expires_at\" timestamp,\n\t\"refresh_token_expires_at\" timestamp,\n\t\"scope\" text,\n\t\"password\" text,\n\t\"created_at\" timestamp NOT NULL,\n\t\"updated_at\" timestamp NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE \"session\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"expires_at\" timestamp NOT NULL,\n\t\"token\" text NOT NULL,\n\t\"created_at\" timestamp NOT NULL,\n\t\"updated_at\" timestamp NOT NULL,\n\t\"ip_address\" text,\n\t\"user_agent\" text,\n\t\"user_id\" text NOT NULL,\n\tCONSTRAINT \"session_token_unique\" UNIQUE(\"token\")\n);\n--> statement-breakpoint\nCREATE TABLE \"theme\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"user_id\" text NOT NULL,\n\t\"name\" text NOT NULL,\n\t\"styles\" text NOT NULL,\n\t\"created_at\" timestamp NOT NULL,\n\t\"updated_at\" timestamp NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE \"user\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"name\" text NOT NULL,\n\t\"email\" text NOT NULL,\n\t\"email_verified\" boolean NOT NULL,\n\t\"image\" text,\n\t\"created_at\" timestamp NOT NULL,\n\t\"updated_at\" timestamp NOT NULL,\n\tCONSTRAINT \"user_email_unique\" UNIQUE(\"email\")\n);\n--> statement-breakpoint\nCREATE TABLE \"verification\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"identifier\" text NOT NULL,\n\t\"value\" text NOT NULL,\n\t\"expires_at\" timestamp NOT NULL,\n\t\"created_at\" timestamp,\n\t\"updated_at\" timestamp\n);\n--> statement-breakpoint\nALTER TABLE \"account\" ADD CONSTRAINT \"account_user_id_user_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"user\"(\"id\") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint\nALTER TABLE \"session\" ADD CONSTRAINT \"session_user_id_user_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"user\"(\"id\") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint\nALTER TABLE \"theme\" ADD CONSTRAINT \"theme_user_id_user_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"user\"(\"id\") ON DELETE cascade ON UPDATE no action;"
  },
  {
    "path": "drizzle/0001_late_mikhail_rasputin.sql",
    "content": "CREATE TABLE \"ai_usage\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"user_id\" text NOT NULL,\n\t\"model_id\" text NOT NULL,\n\t\"prompt_tokens\" text DEFAULT '0' NOT NULL,\n\t\"completion_tokens\" text DEFAULT '0' NOT NULL,\n\t\"days_since_epoch\" text NOT NULL,\n\t\"created_at\" timestamp NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE \"subscription\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"createdAt\" timestamp NOT NULL,\n\t\"modifiedAt\" timestamp,\n\t\"amount\" integer NOT NULL,\n\t\"currency\" text NOT NULL,\n\t\"recurringInterval\" text NOT NULL,\n\t\"status\" text NOT NULL,\n\t\"currentPeriodStart\" timestamp NOT NULL,\n\t\"currentPeriodEnd\" timestamp NOT NULL,\n\t\"cancelAtPeriodEnd\" boolean DEFAULT false NOT NULL,\n\t\"canceledAt\" timestamp,\n\t\"startedAt\" timestamp NOT NULL,\n\t\"endsAt\" timestamp,\n\t\"endedAt\" timestamp,\n\t\"customerId\" text NOT NULL,\n\t\"productId\" text NOT NULL,\n\t\"discountId\" text,\n\t\"checkoutId\" text NOT NULL,\n\t\"customerCancellationReason\" text,\n\t\"customerCancellationComment\" text,\n\t\"metadata\" text,\n\t\"customFieldData\" text,\n\t\"userId\" text\n);\n--> statement-breakpoint\nALTER TABLE \"theme\" ALTER COLUMN \"styles\" SET DATA TYPE json USING \"styles\"::json;--> statement-breakpoint\nALTER TABLE \"ai_usage\" ADD CONSTRAINT \"ai_usage_user_id_user_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"user\"(\"id\") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint\nALTER TABLE \"subscription\" ADD CONSTRAINT \"subscription_userId_user_id_fk\" FOREIGN KEY (\"userId\") REFERENCES \"public\".\"user\"(\"id\") ON DELETE no action ON UPDATE no action;"
  },
  {
    "path": "drizzle/0002_nebulous_randall.sql",
    "content": "CREATE TABLE \"community_theme\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"theme_id\" text NOT NULL,\n\t\"user_id\" text NOT NULL,\n\t\"published_at\" timestamp NOT NULL,\n\tCONSTRAINT \"community_theme_theme_id_unique\" UNIQUE(\"theme_id\")\n);\n--> statement-breakpoint\nCREATE TABLE \"theme_like\" (\n\t\"user_id\" text NOT NULL,\n\t\"theme_id\" text NOT NULL,\n\t\"created_at\" timestamp NOT NULL,\n\tCONSTRAINT \"theme_like_user_id_theme_id_pk\" PRIMARY KEY(\"user_id\",\"theme_id\")\n);\n--> statement-breakpoint\nALTER TABLE \"community_theme\" ADD CONSTRAINT \"community_theme_theme_id_theme_id_fk\" FOREIGN KEY (\"theme_id\") REFERENCES \"public\".\"theme\"(\"id\") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint\nALTER TABLE \"community_theme\" ADD CONSTRAINT \"community_theme_user_id_user_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"user\"(\"id\") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint\nALTER TABLE \"theme_like\" ADD CONSTRAINT \"theme_like_user_id_user_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"user\"(\"id\") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint\nALTER TABLE \"theme_like\" ADD CONSTRAINT \"theme_like_theme_id_community_theme_id_fk\" FOREIGN KEY (\"theme_id\") REFERENCES \"public\".\"community_theme\"(\"id\") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint\nCREATE INDEX \"community_theme_published_at_idx\" ON \"community_theme\" USING btree (\"published_at\");\n"
  },
  {
    "path": "drizzle/0003_bumpy_quasimodo.sql",
    "content": "CREATE TABLE \"community_theme_tag\" (\n\t\"community_theme_id\" text NOT NULL,\n\t\"tag\" text NOT NULL,\n\tCONSTRAINT \"community_theme_tag_community_theme_id_tag_pk\" PRIMARY KEY(\"community_theme_id\",\"tag\")\n);\n--> statement-breakpoint\nALTER TABLE \"community_theme_tag\" ADD CONSTRAINT \"community_theme_tag_community_theme_id_community_theme_id_fk\" FOREIGN KEY (\"community_theme_id\") REFERENCES \"public\".\"community_theme\"(\"id\") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint\nCREATE INDEX \"community_theme_tag_tag_idx\" ON \"community_theme_tag\" USING btree (\"tag\");"
  },
  {
    "path": "drizzle/0004_red_monster_badoon.sql",
    "content": "ALTER TABLE \"community_theme\" ADD COLUMN \"like_count\" integer DEFAULT 0 NOT NULL;--> statement-breakpoint\nUPDATE \"community_theme\" SET \"like_count\" = (\n  SELECT COUNT(*) FROM \"theme_like\" WHERE \"theme_like\".\"theme_id\" = \"community_theme\".\"id\"\n);--> statement-breakpoint\nCREATE INDEX \"community_theme_like_count_idx\" ON \"community_theme\" USING btree (\"like_count\");"
  },
  {
    "path": "drizzle/meta/0000_snapshot.json",
    "content": "{\n  \"id\": \"309e33a7-716e-400b-beaa-8f622fe07479\",\n  \"prevId\": \"00000000-0000-0000-0000-000000000000\",\n  \"version\": \"7\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.account\": {\n      \"name\": \"account\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"account_id\": {\n          \"name\": \"account_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"provider_id\": {\n          \"name\": \"provider_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"access_token\": {\n          \"name\": \"access_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"refresh_token\": {\n          \"name\": \"refresh_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"id_token\": {\n          \"name\": \"id_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"access_token_expires_at\": {\n          \"name\": \"access_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"refresh_token_expires_at\": {\n          \"name\": \"refresh_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"scope\": {\n          \"name\": \"scope\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"password\": {\n          \"name\": \"password\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"account_user_id_user_id_fk\": {\n          \"name\": \"account_user_id_user_id_fk\",\n          \"tableFrom\": \"account\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.session\": {\n      \"name\": \"session\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"token\": {\n          \"name\": \"token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"ip_address\": {\n          \"name\": \"ip_address\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_agent\": {\n          \"name\": \"user_agent\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"session_user_id_user_id_fk\": {\n          \"name\": \"session_user_id_user_id_fk\",\n          \"tableFrom\": \"session\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"session_token_unique\": {\n          \"name\": \"session_token_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"token\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.theme\": {\n      \"name\": \"theme\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"styles\": {\n          \"name\": \"styles\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"theme_user_id_user_id_fk\": {\n          \"name\": \"theme_user_id_user_id_fk\",\n          \"tableFrom\": \"theme\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.user\": {\n      \"name\": \"user\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"email\": {\n          \"name\": \"email\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"email_verified\": {\n          \"name\": \"email_verified\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"image\": {\n          \"name\": \"image\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"user_email_unique\": {\n          \"name\": \"user_email_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"email\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.verification\": {\n      \"name\": \"verification\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"identifier\": {\n          \"name\": \"identifier\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"value\": {\n          \"name\": \"value\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"sequences\": {},\n  \"roles\": {},\n  \"policies\": {},\n  \"views\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "drizzle/meta/0001_snapshot.json",
    "content": "{\n  \"id\": \"3b13531c-b7aa-4c99-8e60-4606750d0046\",\n  \"prevId\": \"309e33a7-716e-400b-beaa-8f622fe07479\",\n  \"version\": \"7\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.account\": {\n      \"name\": \"account\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"account_id\": {\n          \"name\": \"account_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"provider_id\": {\n          \"name\": \"provider_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"access_token\": {\n          \"name\": \"access_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"refresh_token\": {\n          \"name\": \"refresh_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"id_token\": {\n          \"name\": \"id_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"access_token_expires_at\": {\n          \"name\": \"access_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"refresh_token_expires_at\": {\n          \"name\": \"refresh_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"scope\": {\n          \"name\": \"scope\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"password\": {\n          \"name\": \"password\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"account_user_id_user_id_fk\": {\n          \"name\": \"account_user_id_user_id_fk\",\n          \"tableFrom\": \"account\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.ai_usage\": {\n      \"name\": \"ai_usage\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"model_id\": {\n          \"name\": \"model_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"prompt_tokens\": {\n          \"name\": \"prompt_tokens\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'0'\"\n        },\n        \"completion_tokens\": {\n          \"name\": \"completion_tokens\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'0'\"\n        },\n        \"days_since_epoch\": {\n          \"name\": \"days_since_epoch\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"ai_usage_user_id_user_id_fk\": {\n          \"name\": \"ai_usage_user_id_user_id_fk\",\n          \"tableFrom\": \"ai_usage\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.session\": {\n      \"name\": \"session\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"token\": {\n          \"name\": \"token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"ip_address\": {\n          \"name\": \"ip_address\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_agent\": {\n          \"name\": \"user_agent\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"session_user_id_user_id_fk\": {\n          \"name\": \"session_user_id_user_id_fk\",\n          \"tableFrom\": \"session\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"session_token_unique\": {\n          \"name\": \"session_token_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"token\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.subscription\": {\n      \"name\": \"subscription\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"createdAt\": {\n          \"name\": \"createdAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"modifiedAt\": {\n          \"name\": \"modifiedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"amount\": {\n          \"name\": \"amount\",\n          \"type\": \"integer\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currency\": {\n          \"name\": \"currency\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"recurringInterval\": {\n          \"name\": \"recurringInterval\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"status\": {\n          \"name\": \"status\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currentPeriodStart\": {\n          \"name\": \"currentPeriodStart\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currentPeriodEnd\": {\n          \"name\": \"currentPeriodEnd\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"cancelAtPeriodEnd\": {\n          \"name\": \"cancelAtPeriodEnd\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": false\n        },\n        \"canceledAt\": {\n          \"name\": \"canceledAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"startedAt\": {\n          \"name\": \"startedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"endsAt\": {\n          \"name\": \"endsAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"endedAt\": {\n          \"name\": \"endedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customerId\": {\n          \"name\": \"customerId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"productId\": {\n          \"name\": \"productId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"discountId\": {\n          \"name\": \"discountId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"checkoutId\": {\n          \"name\": \"checkoutId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"customerCancellationReason\": {\n          \"name\": \"customerCancellationReason\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customerCancellationComment\": {\n          \"name\": \"customerCancellationComment\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"metadata\": {\n          \"name\": \"metadata\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customFieldData\": {\n          \"name\": \"customFieldData\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"userId\": {\n          \"name\": \"userId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"subscription_userId_user_id_fk\": {\n          \"name\": \"subscription_userId_user_id_fk\",\n          \"tableFrom\": \"subscription\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"userId\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.theme\": {\n      \"name\": \"theme\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"styles\": {\n          \"name\": \"styles\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"theme_user_id_user_id_fk\": {\n          \"name\": \"theme_user_id_user_id_fk\",\n          \"tableFrom\": \"theme\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.user\": {\n      \"name\": \"user\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"email\": {\n          \"name\": \"email\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"email_verified\": {\n          \"name\": \"email_verified\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"image\": {\n          \"name\": \"image\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"user_email_unique\": {\n          \"name\": \"user_email_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"email\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.verification\": {\n      \"name\": \"verification\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"identifier\": {\n          \"name\": \"identifier\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"value\": {\n          \"name\": \"value\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"sequences\": {},\n  \"roles\": {},\n  \"policies\": {},\n  \"views\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "drizzle/meta/0002_snapshot.json",
    "content": "{\n  \"id\": \"1640eaa5-d751-4a37-9a62-932995a54ccd\",\n  \"prevId\": \"3b13531c-b7aa-4c99-8e60-4606750d0046\",\n  \"version\": \"7\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.account\": {\n      \"name\": \"account\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"account_id\": {\n          \"name\": \"account_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"provider_id\": {\n          \"name\": \"provider_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"access_token\": {\n          \"name\": \"access_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"refresh_token\": {\n          \"name\": \"refresh_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"id_token\": {\n          \"name\": \"id_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"access_token_expires_at\": {\n          \"name\": \"access_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"refresh_token_expires_at\": {\n          \"name\": \"refresh_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"scope\": {\n          \"name\": \"scope\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"password\": {\n          \"name\": \"password\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"account_user_id_user_id_fk\": {\n          \"name\": \"account_user_id_user_id_fk\",\n          \"tableFrom\": \"account\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.ai_usage\": {\n      \"name\": \"ai_usage\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"model_id\": {\n          \"name\": \"model_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"prompt_tokens\": {\n          \"name\": \"prompt_tokens\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'0'\"\n        },\n        \"completion_tokens\": {\n          \"name\": \"completion_tokens\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'0'\"\n        },\n        \"days_since_epoch\": {\n          \"name\": \"days_since_epoch\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"ai_usage_user_id_user_id_fk\": {\n          \"name\": \"ai_usage_user_id_user_id_fk\",\n          \"tableFrom\": \"ai_usage\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.community_theme\": {\n      \"name\": \"community_theme\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"theme_id\": {\n          \"name\": \"theme_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"published_at\": {\n          \"name\": \"published_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {\n        \"community_theme_published_at_idx\": {\n          \"name\": \"community_theme_published_at_idx\",\n          \"columns\": [\n            {\n              \"expression\": \"published_at\",\n              \"isExpression\": false,\n              \"asc\": true,\n              \"nulls\": \"last\"\n            }\n          ],\n          \"isUnique\": false,\n          \"concurrently\": false,\n          \"method\": \"btree\",\n          \"with\": {}\n        }\n      },\n      \"foreignKeys\": {\n        \"community_theme_theme_id_theme_id_fk\": {\n          \"name\": \"community_theme_theme_id_theme_id_fk\",\n          \"tableFrom\": \"community_theme\",\n          \"tableTo\": \"theme\",\n          \"columnsFrom\": [\n            \"theme_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"community_theme_user_id_user_id_fk\": {\n          \"name\": \"community_theme_user_id_user_id_fk\",\n          \"tableFrom\": \"community_theme\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"community_theme_theme_id_unique\": {\n          \"name\": \"community_theme_theme_id_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"theme_id\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.oauth_app\": {\n      \"name\": \"oauth_app\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"description\": {\n          \"name\": \"description\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"client_id\": {\n          \"name\": \"client_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"client_secret_hash\": {\n          \"name\": \"client_secret_hash\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"redirect_uris\": {\n          \"name\": \"redirect_uris\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"scopes\": {\n          \"name\": \"scopes\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"is_active\": {\n          \"name\": \"is_active\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"oauth_app_client_id_unique\": {\n          \"name\": \"oauth_app_client_id_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"client_id\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.oauth_authorization_code\": {\n      \"name\": \"oauth_authorization_code\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"code\": {\n          \"name\": \"code\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"app_id\": {\n          \"name\": \"app_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"scopes\": {\n          \"name\": \"scopes\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"redirect_uri\": {\n          \"name\": \"redirect_uri\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"code_challenge\": {\n          \"name\": \"code_challenge\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"code_challenge_method\": {\n          \"name\": \"code_challenge_method\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"used_at\": {\n          \"name\": \"used_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"oauth_authorization_code_app_id_oauth_app_id_fk\": {\n          \"name\": \"oauth_authorization_code_app_id_oauth_app_id_fk\",\n          \"tableFrom\": \"oauth_authorization_code\",\n          \"tableTo\": \"oauth_app\",\n          \"columnsFrom\": [\n            \"app_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"oauth_authorization_code_user_id_user_id_fk\": {\n          \"name\": \"oauth_authorization_code_user_id_user_id_fk\",\n          \"tableFrom\": \"oauth_authorization_code\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"oauth_authorization_code_code_unique\": {\n          \"name\": \"oauth_authorization_code_code_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"code\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.oauth_token\": {\n      \"name\": \"oauth_token\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"access_token_hash\": {\n          \"name\": \"access_token_hash\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"refresh_token_hash\": {\n          \"name\": \"refresh_token_hash\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"app_id\": {\n          \"name\": \"app_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"scopes\": {\n          \"name\": \"scopes\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"access_token_expires_at\": {\n          \"name\": \"access_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"refresh_token_expires_at\": {\n          \"name\": \"refresh_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"revoked_at\": {\n          \"name\": \"revoked_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"oauth_token_app_id_oauth_app_id_fk\": {\n          \"name\": \"oauth_token_app_id_oauth_app_id_fk\",\n          \"tableFrom\": \"oauth_token\",\n          \"tableTo\": \"oauth_app\",\n          \"columnsFrom\": [\n            \"app_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"oauth_token_user_id_user_id_fk\": {\n          \"name\": \"oauth_token_user_id_user_id_fk\",\n          \"tableFrom\": \"oauth_token\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"oauth_token_access_token_hash_unique\": {\n          \"name\": \"oauth_token_access_token_hash_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"access_token_hash\"\n          ]\n        },\n        \"oauth_token_refresh_token_hash_unique\": {\n          \"name\": \"oauth_token_refresh_token_hash_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"refresh_token_hash\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.session\": {\n      \"name\": \"session\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"token\": {\n          \"name\": \"token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"ip_address\": {\n          \"name\": \"ip_address\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_agent\": {\n          \"name\": \"user_agent\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"session_user_id_user_id_fk\": {\n          \"name\": \"session_user_id_user_id_fk\",\n          \"tableFrom\": \"session\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"session_token_unique\": {\n          \"name\": \"session_token_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"token\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.subscription\": {\n      \"name\": \"subscription\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"createdAt\": {\n          \"name\": \"createdAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"modifiedAt\": {\n          \"name\": \"modifiedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"amount\": {\n          \"name\": \"amount\",\n          \"type\": \"integer\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currency\": {\n          \"name\": \"currency\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"recurringInterval\": {\n          \"name\": \"recurringInterval\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"status\": {\n          \"name\": \"status\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currentPeriodStart\": {\n          \"name\": \"currentPeriodStart\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currentPeriodEnd\": {\n          \"name\": \"currentPeriodEnd\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"cancelAtPeriodEnd\": {\n          \"name\": \"cancelAtPeriodEnd\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": false\n        },\n        \"canceledAt\": {\n          \"name\": \"canceledAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"startedAt\": {\n          \"name\": \"startedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"endsAt\": {\n          \"name\": \"endsAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"endedAt\": {\n          \"name\": \"endedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customerId\": {\n          \"name\": \"customerId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"productId\": {\n          \"name\": \"productId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"discountId\": {\n          \"name\": \"discountId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"checkoutId\": {\n          \"name\": \"checkoutId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"customerCancellationReason\": {\n          \"name\": \"customerCancellationReason\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customerCancellationComment\": {\n          \"name\": \"customerCancellationComment\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"metadata\": {\n          \"name\": \"metadata\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customFieldData\": {\n          \"name\": \"customFieldData\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"userId\": {\n          \"name\": \"userId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"subscription_userId_user_id_fk\": {\n          \"name\": \"subscription_userId_user_id_fk\",\n          \"tableFrom\": \"subscription\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"userId\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.theme\": {\n      \"name\": \"theme\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"styles\": {\n          \"name\": \"styles\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"theme_user_id_user_id_fk\": {\n          \"name\": \"theme_user_id_user_id_fk\",\n          \"tableFrom\": \"theme\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.theme_like\": {\n      \"name\": \"theme_like\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"theme_id\": {\n          \"name\": \"theme_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"theme_like_user_id_user_id_fk\": {\n          \"name\": \"theme_like_user_id_user_id_fk\",\n          \"tableFrom\": \"theme_like\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"theme_like_theme_id_community_theme_id_fk\": {\n          \"name\": \"theme_like_theme_id_community_theme_id_fk\",\n          \"tableFrom\": \"theme_like\",\n          \"tableTo\": \"community_theme\",\n          \"columnsFrom\": [\n            \"theme_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {\n        \"theme_like_user_id_theme_id_pk\": {\n          \"name\": \"theme_like_user_id_theme_id_pk\",\n          \"columns\": [\n            \"user_id\",\n            \"theme_id\"\n          ]\n        }\n      },\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.user\": {\n      \"name\": \"user\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"email\": {\n          \"name\": \"email\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"email_verified\": {\n          \"name\": \"email_verified\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"image\": {\n          \"name\": \"image\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"user_email_unique\": {\n          \"name\": \"user_email_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"email\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.verification\": {\n      \"name\": \"verification\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"identifier\": {\n          \"name\": \"identifier\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"value\": {\n          \"name\": \"value\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"sequences\": {},\n  \"roles\": {},\n  \"policies\": {},\n  \"views\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "drizzle/meta/0003_snapshot.json",
    "content": "{\n  \"id\": \"c9d410c1-e958-492f-b792-da1328635165\",\n  \"prevId\": \"1640eaa5-d751-4a37-9a62-932995a54ccd\",\n  \"version\": \"7\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.account\": {\n      \"name\": \"account\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"account_id\": {\n          \"name\": \"account_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"provider_id\": {\n          \"name\": \"provider_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"access_token\": {\n          \"name\": \"access_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"refresh_token\": {\n          \"name\": \"refresh_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"id_token\": {\n          \"name\": \"id_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"access_token_expires_at\": {\n          \"name\": \"access_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"refresh_token_expires_at\": {\n          \"name\": \"refresh_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"scope\": {\n          \"name\": \"scope\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"password\": {\n          \"name\": \"password\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"account_user_id_user_id_fk\": {\n          \"name\": \"account_user_id_user_id_fk\",\n          \"tableFrom\": \"account\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.ai_usage\": {\n      \"name\": \"ai_usage\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"model_id\": {\n          \"name\": \"model_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"prompt_tokens\": {\n          \"name\": \"prompt_tokens\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'0'\"\n        },\n        \"completion_tokens\": {\n          \"name\": \"completion_tokens\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'0'\"\n        },\n        \"days_since_epoch\": {\n          \"name\": \"days_since_epoch\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"ai_usage_user_id_user_id_fk\": {\n          \"name\": \"ai_usage_user_id_user_id_fk\",\n          \"tableFrom\": \"ai_usage\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.community_theme\": {\n      \"name\": \"community_theme\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"theme_id\": {\n          \"name\": \"theme_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"published_at\": {\n          \"name\": \"published_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {\n        \"community_theme_published_at_idx\": {\n          \"name\": \"community_theme_published_at_idx\",\n          \"columns\": [\n            {\n              \"expression\": \"published_at\",\n              \"isExpression\": false,\n              \"asc\": true,\n              \"nulls\": \"last\"\n            }\n          ],\n          \"isUnique\": false,\n          \"concurrently\": false,\n          \"method\": \"btree\",\n          \"with\": {}\n        }\n      },\n      \"foreignKeys\": {\n        \"community_theme_theme_id_theme_id_fk\": {\n          \"name\": \"community_theme_theme_id_theme_id_fk\",\n          \"tableFrom\": \"community_theme\",\n          \"tableTo\": \"theme\",\n          \"columnsFrom\": [\n            \"theme_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"community_theme_user_id_user_id_fk\": {\n          \"name\": \"community_theme_user_id_user_id_fk\",\n          \"tableFrom\": \"community_theme\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"community_theme_theme_id_unique\": {\n          \"name\": \"community_theme_theme_id_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"theme_id\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.community_theme_tag\": {\n      \"name\": \"community_theme_tag\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"community_theme_id\": {\n          \"name\": \"community_theme_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"tag\": {\n          \"name\": \"tag\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {\n        \"community_theme_tag_tag_idx\": {\n          \"name\": \"community_theme_tag_tag_idx\",\n          \"columns\": [\n            {\n              \"expression\": \"tag\",\n              \"isExpression\": false,\n              \"asc\": true,\n              \"nulls\": \"last\"\n            }\n          ],\n          \"isUnique\": false,\n          \"concurrently\": false,\n          \"method\": \"btree\",\n          \"with\": {}\n        }\n      },\n      \"foreignKeys\": {\n        \"community_theme_tag_community_theme_id_community_theme_id_fk\": {\n          \"name\": \"community_theme_tag_community_theme_id_community_theme_id_fk\",\n          \"tableFrom\": \"community_theme_tag\",\n          \"tableTo\": \"community_theme\",\n          \"columnsFrom\": [\n            \"community_theme_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {\n        \"community_theme_tag_community_theme_id_tag_pk\": {\n          \"name\": \"community_theme_tag_community_theme_id_tag_pk\",\n          \"columns\": [\n            \"community_theme_id\",\n            \"tag\"\n          ]\n        }\n      },\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.oauth_app\": {\n      \"name\": \"oauth_app\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"description\": {\n          \"name\": \"description\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"client_id\": {\n          \"name\": \"client_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"client_secret_hash\": {\n          \"name\": \"client_secret_hash\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"redirect_uris\": {\n          \"name\": \"redirect_uris\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"scopes\": {\n          \"name\": \"scopes\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"is_active\": {\n          \"name\": \"is_active\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"oauth_app_client_id_unique\": {\n          \"name\": \"oauth_app_client_id_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"client_id\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.oauth_authorization_code\": {\n      \"name\": \"oauth_authorization_code\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"code\": {\n          \"name\": \"code\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"app_id\": {\n          \"name\": \"app_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"scopes\": {\n          \"name\": \"scopes\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"redirect_uri\": {\n          \"name\": \"redirect_uri\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"code_challenge\": {\n          \"name\": \"code_challenge\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"code_challenge_method\": {\n          \"name\": \"code_challenge_method\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"used_at\": {\n          \"name\": \"used_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"oauth_authorization_code_app_id_oauth_app_id_fk\": {\n          \"name\": \"oauth_authorization_code_app_id_oauth_app_id_fk\",\n          \"tableFrom\": \"oauth_authorization_code\",\n          \"tableTo\": \"oauth_app\",\n          \"columnsFrom\": [\n            \"app_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"oauth_authorization_code_user_id_user_id_fk\": {\n          \"name\": \"oauth_authorization_code_user_id_user_id_fk\",\n          \"tableFrom\": \"oauth_authorization_code\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"oauth_authorization_code_code_unique\": {\n          \"name\": \"oauth_authorization_code_code_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"code\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.oauth_token\": {\n      \"name\": \"oauth_token\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"access_token_hash\": {\n          \"name\": \"access_token_hash\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"refresh_token_hash\": {\n          \"name\": \"refresh_token_hash\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"app_id\": {\n          \"name\": \"app_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"scopes\": {\n          \"name\": \"scopes\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"access_token_expires_at\": {\n          \"name\": \"access_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"refresh_token_expires_at\": {\n          \"name\": \"refresh_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"revoked_at\": {\n          \"name\": \"revoked_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"oauth_token_app_id_oauth_app_id_fk\": {\n          \"name\": \"oauth_token_app_id_oauth_app_id_fk\",\n          \"tableFrom\": \"oauth_token\",\n          \"tableTo\": \"oauth_app\",\n          \"columnsFrom\": [\n            \"app_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"oauth_token_user_id_user_id_fk\": {\n          \"name\": \"oauth_token_user_id_user_id_fk\",\n          \"tableFrom\": \"oauth_token\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"oauth_token_access_token_hash_unique\": {\n          \"name\": \"oauth_token_access_token_hash_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"access_token_hash\"\n          ]\n        },\n        \"oauth_token_refresh_token_hash_unique\": {\n          \"name\": \"oauth_token_refresh_token_hash_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"refresh_token_hash\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.session\": {\n      \"name\": \"session\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"token\": {\n          \"name\": \"token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"ip_address\": {\n          \"name\": \"ip_address\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_agent\": {\n          \"name\": \"user_agent\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"session_user_id_user_id_fk\": {\n          \"name\": \"session_user_id_user_id_fk\",\n          \"tableFrom\": \"session\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"session_token_unique\": {\n          \"name\": \"session_token_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"token\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.subscription\": {\n      \"name\": \"subscription\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"createdAt\": {\n          \"name\": \"createdAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"modifiedAt\": {\n          \"name\": \"modifiedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"amount\": {\n          \"name\": \"amount\",\n          \"type\": \"integer\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currency\": {\n          \"name\": \"currency\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"recurringInterval\": {\n          \"name\": \"recurringInterval\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"status\": {\n          \"name\": \"status\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currentPeriodStart\": {\n          \"name\": \"currentPeriodStart\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currentPeriodEnd\": {\n          \"name\": \"currentPeriodEnd\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"cancelAtPeriodEnd\": {\n          \"name\": \"cancelAtPeriodEnd\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": false\n        },\n        \"canceledAt\": {\n          \"name\": \"canceledAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"startedAt\": {\n          \"name\": \"startedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"endsAt\": {\n          \"name\": \"endsAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"endedAt\": {\n          \"name\": \"endedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customerId\": {\n          \"name\": \"customerId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"productId\": {\n          \"name\": \"productId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"discountId\": {\n          \"name\": \"discountId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"checkoutId\": {\n          \"name\": \"checkoutId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"customerCancellationReason\": {\n          \"name\": \"customerCancellationReason\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customerCancellationComment\": {\n          \"name\": \"customerCancellationComment\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"metadata\": {\n          \"name\": \"metadata\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customFieldData\": {\n          \"name\": \"customFieldData\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"userId\": {\n          \"name\": \"userId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"subscription_userId_user_id_fk\": {\n          \"name\": \"subscription_userId_user_id_fk\",\n          \"tableFrom\": \"subscription\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"userId\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.theme\": {\n      \"name\": \"theme\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"styles\": {\n          \"name\": \"styles\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"theme_user_id_user_id_fk\": {\n          \"name\": \"theme_user_id_user_id_fk\",\n          \"tableFrom\": \"theme\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.theme_like\": {\n      \"name\": \"theme_like\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"theme_id\": {\n          \"name\": \"theme_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"theme_like_user_id_user_id_fk\": {\n          \"name\": \"theme_like_user_id_user_id_fk\",\n          \"tableFrom\": \"theme_like\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"theme_like_theme_id_community_theme_id_fk\": {\n          \"name\": \"theme_like_theme_id_community_theme_id_fk\",\n          \"tableFrom\": \"theme_like\",\n          \"tableTo\": \"community_theme\",\n          \"columnsFrom\": [\n            \"theme_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {\n        \"theme_like_user_id_theme_id_pk\": {\n          \"name\": \"theme_like_user_id_theme_id_pk\",\n          \"columns\": [\n            \"user_id\",\n            \"theme_id\"\n          ]\n        }\n      },\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.user\": {\n      \"name\": \"user\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"email\": {\n          \"name\": \"email\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"email_verified\": {\n          \"name\": \"email_verified\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"image\": {\n          \"name\": \"image\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"user_email_unique\": {\n          \"name\": \"user_email_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"email\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.verification\": {\n      \"name\": \"verification\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"identifier\": {\n          \"name\": \"identifier\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"value\": {\n          \"name\": \"value\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"sequences\": {},\n  \"roles\": {},\n  \"policies\": {},\n  \"views\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "drizzle/meta/0004_snapshot.json",
    "content": "{\n  \"id\": \"148abf59-de4f-4043-adc5-3090652c41a0\",\n  \"prevId\": \"c9d410c1-e958-492f-b792-da1328635165\",\n  \"version\": \"7\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.account\": {\n      \"name\": \"account\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"account_id\": {\n          \"name\": \"account_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"provider_id\": {\n          \"name\": \"provider_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"access_token\": {\n          \"name\": \"access_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"refresh_token\": {\n          \"name\": \"refresh_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"id_token\": {\n          \"name\": \"id_token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"access_token_expires_at\": {\n          \"name\": \"access_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"refresh_token_expires_at\": {\n          \"name\": \"refresh_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"scope\": {\n          \"name\": \"scope\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"password\": {\n          \"name\": \"password\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"account_user_id_user_id_fk\": {\n          \"name\": \"account_user_id_user_id_fk\",\n          \"tableFrom\": \"account\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.ai_usage\": {\n      \"name\": \"ai_usage\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"model_id\": {\n          \"name\": \"model_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"prompt_tokens\": {\n          \"name\": \"prompt_tokens\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'0'\"\n        },\n        \"completion_tokens\": {\n          \"name\": \"completion_tokens\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'0'\"\n        },\n        \"days_since_epoch\": {\n          \"name\": \"days_since_epoch\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"ai_usage_user_id_user_id_fk\": {\n          \"name\": \"ai_usage_user_id_user_id_fk\",\n          \"tableFrom\": \"ai_usage\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.community_theme\": {\n      \"name\": \"community_theme\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"theme_id\": {\n          \"name\": \"theme_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"published_at\": {\n          \"name\": \"published_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"like_count\": {\n          \"name\": \"like_count\",\n          \"type\": \"integer\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": 0\n        }\n      },\n      \"indexes\": {\n        \"community_theme_published_at_idx\": {\n          \"name\": \"community_theme_published_at_idx\",\n          \"columns\": [\n            {\n              \"expression\": \"published_at\",\n              \"isExpression\": false,\n              \"asc\": true,\n              \"nulls\": \"last\"\n            }\n          ],\n          \"isUnique\": false,\n          \"concurrently\": false,\n          \"method\": \"btree\",\n          \"with\": {}\n        },\n        \"community_theme_like_count_idx\": {\n          \"name\": \"community_theme_like_count_idx\",\n          \"columns\": [\n            {\n              \"expression\": \"like_count\",\n              \"isExpression\": false,\n              \"asc\": true,\n              \"nulls\": \"last\"\n            }\n          ],\n          \"isUnique\": false,\n          \"concurrently\": false,\n          \"method\": \"btree\",\n          \"with\": {}\n        }\n      },\n      \"foreignKeys\": {\n        \"community_theme_theme_id_theme_id_fk\": {\n          \"name\": \"community_theme_theme_id_theme_id_fk\",\n          \"tableFrom\": \"community_theme\",\n          \"tableTo\": \"theme\",\n          \"columnsFrom\": [\n            \"theme_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"community_theme_user_id_user_id_fk\": {\n          \"name\": \"community_theme_user_id_user_id_fk\",\n          \"tableFrom\": \"community_theme\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"community_theme_theme_id_unique\": {\n          \"name\": \"community_theme_theme_id_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"theme_id\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.community_theme_tag\": {\n      \"name\": \"community_theme_tag\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"community_theme_id\": {\n          \"name\": \"community_theme_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"tag\": {\n          \"name\": \"tag\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {\n        \"community_theme_tag_tag_idx\": {\n          \"name\": \"community_theme_tag_tag_idx\",\n          \"columns\": [\n            {\n              \"expression\": \"tag\",\n              \"isExpression\": false,\n              \"asc\": true,\n              \"nulls\": \"last\"\n            }\n          ],\n          \"isUnique\": false,\n          \"concurrently\": false,\n          \"method\": \"btree\",\n          \"with\": {}\n        }\n      },\n      \"foreignKeys\": {\n        \"community_theme_tag_community_theme_id_community_theme_id_fk\": {\n          \"name\": \"community_theme_tag_community_theme_id_community_theme_id_fk\",\n          \"tableFrom\": \"community_theme_tag\",\n          \"tableTo\": \"community_theme\",\n          \"columnsFrom\": [\n            \"community_theme_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {\n        \"community_theme_tag_community_theme_id_tag_pk\": {\n          \"name\": \"community_theme_tag_community_theme_id_tag_pk\",\n          \"columns\": [\n            \"community_theme_id\",\n            \"tag\"\n          ]\n        }\n      },\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.oauth_app\": {\n      \"name\": \"oauth_app\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"description\": {\n          \"name\": \"description\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"client_id\": {\n          \"name\": \"client_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"client_secret_hash\": {\n          \"name\": \"client_secret_hash\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"redirect_uris\": {\n          \"name\": \"redirect_uris\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"scopes\": {\n          \"name\": \"scopes\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"is_active\": {\n          \"name\": \"is_active\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"oauth_app_client_id_unique\": {\n          \"name\": \"oauth_app_client_id_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"client_id\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.oauth_authorization_code\": {\n      \"name\": \"oauth_authorization_code\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"code\": {\n          \"name\": \"code\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"app_id\": {\n          \"name\": \"app_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"scopes\": {\n          \"name\": \"scopes\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"redirect_uri\": {\n          \"name\": \"redirect_uri\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"code_challenge\": {\n          \"name\": \"code_challenge\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"code_challenge_method\": {\n          \"name\": \"code_challenge_method\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"used_at\": {\n          \"name\": \"used_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"oauth_authorization_code_app_id_oauth_app_id_fk\": {\n          \"name\": \"oauth_authorization_code_app_id_oauth_app_id_fk\",\n          \"tableFrom\": \"oauth_authorization_code\",\n          \"tableTo\": \"oauth_app\",\n          \"columnsFrom\": [\n            \"app_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"oauth_authorization_code_user_id_user_id_fk\": {\n          \"name\": \"oauth_authorization_code_user_id_user_id_fk\",\n          \"tableFrom\": \"oauth_authorization_code\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"oauth_authorization_code_code_unique\": {\n          \"name\": \"oauth_authorization_code_code_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"code\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.oauth_token\": {\n      \"name\": \"oauth_token\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"access_token_hash\": {\n          \"name\": \"access_token_hash\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"refresh_token_hash\": {\n          \"name\": \"refresh_token_hash\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"app_id\": {\n          \"name\": \"app_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"scopes\": {\n          \"name\": \"scopes\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"access_token_expires_at\": {\n          \"name\": \"access_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"refresh_token_expires_at\": {\n          \"name\": \"refresh_token_expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"revoked_at\": {\n          \"name\": \"revoked_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"oauth_token_app_id_oauth_app_id_fk\": {\n          \"name\": \"oauth_token_app_id_oauth_app_id_fk\",\n          \"tableFrom\": \"oauth_token\",\n          \"tableTo\": \"oauth_app\",\n          \"columnsFrom\": [\n            \"app_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"oauth_token_user_id_user_id_fk\": {\n          \"name\": \"oauth_token_user_id_user_id_fk\",\n          \"tableFrom\": \"oauth_token\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"oauth_token_access_token_hash_unique\": {\n          \"name\": \"oauth_token_access_token_hash_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"access_token_hash\"\n          ]\n        },\n        \"oauth_token_refresh_token_hash_unique\": {\n          \"name\": \"oauth_token_refresh_token_hash_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"refresh_token_hash\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.session\": {\n      \"name\": \"session\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"token\": {\n          \"name\": \"token\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"ip_address\": {\n          \"name\": \"ip_address\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_agent\": {\n          \"name\": \"user_agent\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"session_user_id_user_id_fk\": {\n          \"name\": \"session_user_id_user_id_fk\",\n          \"tableFrom\": \"session\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"session_token_unique\": {\n          \"name\": \"session_token_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"token\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.subscription\": {\n      \"name\": \"subscription\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"createdAt\": {\n          \"name\": \"createdAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"modifiedAt\": {\n          \"name\": \"modifiedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"amount\": {\n          \"name\": \"amount\",\n          \"type\": \"integer\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currency\": {\n          \"name\": \"currency\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"recurringInterval\": {\n          \"name\": \"recurringInterval\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"status\": {\n          \"name\": \"status\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currentPeriodStart\": {\n          \"name\": \"currentPeriodStart\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"currentPeriodEnd\": {\n          \"name\": \"currentPeriodEnd\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"cancelAtPeriodEnd\": {\n          \"name\": \"cancelAtPeriodEnd\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": false\n        },\n        \"canceledAt\": {\n          \"name\": \"canceledAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"startedAt\": {\n          \"name\": \"startedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"endsAt\": {\n          \"name\": \"endsAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"endedAt\": {\n          \"name\": \"endedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customerId\": {\n          \"name\": \"customerId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"productId\": {\n          \"name\": \"productId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"discountId\": {\n          \"name\": \"discountId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"checkoutId\": {\n          \"name\": \"checkoutId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"customerCancellationReason\": {\n          \"name\": \"customerCancellationReason\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customerCancellationComment\": {\n          \"name\": \"customerCancellationComment\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"metadata\": {\n          \"name\": \"metadata\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"customFieldData\": {\n          \"name\": \"customFieldData\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"userId\": {\n          \"name\": \"userId\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"subscription_userId_user_id_fk\": {\n          \"name\": \"subscription_userId_user_id_fk\",\n          \"tableFrom\": \"subscription\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"userId\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.theme\": {\n      \"name\": \"theme\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"styles\": {\n          \"name\": \"styles\",\n          \"type\": \"json\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"theme_user_id_user_id_fk\": {\n          \"name\": \"theme_user_id_user_id_fk\",\n          \"tableFrom\": \"theme\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.theme_like\": {\n      \"name\": \"theme_like\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"theme_id\": {\n          \"name\": \"theme_id\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"theme_like_user_id_user_id_fk\": {\n          \"name\": \"theme_like_user_id_user_id_fk\",\n          \"tableFrom\": \"theme_like\",\n          \"tableTo\": \"user\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        },\n        \"theme_like_theme_id_community_theme_id_fk\": {\n          \"name\": \"theme_like_theme_id_community_theme_id_fk\",\n          \"tableFrom\": \"theme_like\",\n          \"tableTo\": \"community_theme\",\n          \"columnsFrom\": [\n            \"theme_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {\n        \"theme_like_user_id_theme_id_pk\": {\n          \"name\": \"theme_like_user_id_theme_id_pk\",\n          \"columns\": [\n            \"user_id\",\n            \"theme_id\"\n          ]\n        }\n      },\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.user\": {\n      \"name\": \"user\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"name\": {\n          \"name\": \"name\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"email\": {\n          \"name\": \"email\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"email_verified\": {\n          \"name\": \"email_verified\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"image\": {\n          \"name\": \"image\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"user_email_unique\": {\n          \"name\": \"user_email_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"email\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.verification\": {\n      \"name\": \"verification\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"text\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"identifier\": {\n          \"name\": \"identifier\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"value\": {\n          \"name\": \"value\",\n          \"type\": \"text\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"expires_at\": {\n          \"name\": \"expires_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"sequences\": {},\n  \"roles\": {},\n  \"policies\": {},\n  \"views\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "drizzle/meta/_journal.json",
    "content": "{\n  \"version\": \"7\",\n  \"dialect\": \"postgresql\",\n  \"entries\": [\n    {\n      \"idx\": 0,\n      \"version\": \"7\",\n      \"when\": 1745600061994,\n      \"tag\": \"0000_rare_moira_mactaggert\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 1,\n      \"version\": \"7\",\n      \"when\": 1752617649128,\n      \"tag\": \"0001_late_mikhail_rasputin\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 2,\n      \"version\": \"7\",\n      \"when\": 1770577489750,\n      \"tag\": \"0002_nebulous_randall\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 3,\n      \"version\": \"7\",\n      \"when\": 1770585276556,\n      \"tag\": \"0003_bumpy_quasimodo\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 4,\n      \"version\": \"7\",\n      \"when\": 1772635867749,\n      \"tag\": \"0004_red_monster_badoon\",\n      \"breakpoints\": true\n    }\n  ]\n}"
  },
  {
    "path": "drizzle.config.ts",
    "content": "import \"dotenv/config\";\nimport { defineConfig } from \"drizzle-kit\";\nimport { config } from \"dotenv\";\n\nconfig({ path: \".env.local\" });\n\nexport default defineConfig({\n  out: \"./drizzle\",\n  schema: \"./db/schema.ts\",\n  dialect: \"postgresql\",\n  dbCredentials: {\n    url: process.env.DATABASE_URL!,\n  },\n});\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport nextPlugin from \"@next/eslint-plugin-next\";\nimport reactPlugin from \"eslint-plugin-react\";\nimport hooksPlugin from \"eslint-plugin-react-hooks\";\nimport tseslint from \"typescript-eslint\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nconst eslintConfig = [\n  {\n    plugins: {\n      \"@next/next\": nextPlugin,\n    },\n    rules: {\n      ...nextPlugin.configs.recommended.rules,\n      ...nextPlugin.configs[\"core-web-vitals\"].rules,\n      \"@next/next/no-page-custom-font\": \"off\",\n      \"@next/next/no-img-element\": \"off\",\n    },\n  },\n  {\n    plugins: {\n      react: reactPlugin,\n    },\n    rules: {\n      ...reactPlugin.configs.recommended.rules,\n      \"react/react-in-jsx-scope\": \"off\",\n      \"react/prop-types\": \"off\",\n      \"react/no-unknown-property\": \"off\",\n      \"react/jsx-no-target-blank\": \"off\",\n    },\n    settings: {\n      react: {\n        version: \"detect\",\n      },\n    },\n  },\n  {\n    plugins: {\n      \"react-hooks\": hooksPlugin,\n    },\n    rules: {\n      // Only use classic react-hooks rules (not React Compiler rules)\n      \"react-hooks/rules-of-hooks\": \"error\",\n      \"react-hooks/exhaustive-deps\": \"warn\",\n    },\n  },\n  ...tseslint.configs.recommended,\n  {\n    rules: {\n      \"@typescript-eslint/no-empty-object-type\": \"off\",\n      \"@typescript-eslint/no-unused-vars\": [\n        \"error\",\n        {\n          args: \"all\",\n          argsIgnorePattern: \"^_\",\n          caughtErrors: \"all\",\n          caughtErrorsIgnorePattern: \"^_\",\n          destructuredArrayIgnorePattern: \"^_\",\n          varsIgnorePattern: \"^_\",\n          ignoreRestSiblings: true,\n        },\n      ],\n    },\n  },\n  {\n    ignores: [\".next/**\", \"node_modules/**\"],\n  },\n];\n\nexport default eslintConfig;\n"
  },
  {
    "path": "hooks/inspector/use-inspector-mouse-events.ts",
    "content": "import { useCallback, useEffect, MouseEvent } from \"react\";\nimport { debounce } from \"../../utils/debounce\";\nimport { findThemeClasses } from \"../../lib/inspector/theme-class-finder\";\n\ninterface UseInspectorMouseEventsProps {\n  inspectorEnabled: boolean;\n  rootRef: React.RefObject<HTMLDivElement | null>;\n  lastElementRef: React.MutableRefObject<HTMLElement | null>;\n  updateInspectorState: (rect: DOMRect, matches: string[]) => void;\n  clearInspectorState: () => void;\n}\n\nexport const useInspectorMouseEvents = ({\n  inspectorEnabled,\n  rootRef,\n  lastElementRef,\n  updateInspectorState,\n  clearInspectorState,\n}: UseInspectorMouseEventsProps) => {\n  const debouncedInspectorUpdate = useCallback(\n    debounce((target: HTMLElement) => {\n      const rootElement = rootRef.current;\n      if (!rootElement) return;\n\n      if (!rootElement.contains(target) || target === rootElement) return;\n\n      const result = findThemeClasses(target, rootElement);\n\n      if (result) {\n        if (lastElementRef.current === result.element) {\n          return;\n        }\n\n        lastElementRef.current = result.element;\n        const rect = result.element.getBoundingClientRect();\n        updateInspectorState(rect, result.matches);\n        return;\n      }\n\n      clearInspectorState();\n    }, 20),\n    [rootRef, lastElementRef, updateInspectorState, clearInspectorState]\n  );\n\n  const handleMouseMove = useCallback(\n    (event: MouseEvent<HTMLDivElement>) => {\n      const target = event.target as HTMLElement | null;\n      if (!target || !inspectorEnabled) return;\n\n      debouncedInspectorUpdate(target);\n    },\n    [inspectorEnabled, debouncedInspectorUpdate]\n  );\n\n  const handleMouseLeave = useCallback(() => {\n    lastElementRef.current = null;\n    debouncedInspectorUpdate.cancel();\n  }, [debouncedInspectorUpdate, lastElementRef]);\n\n  useEffect(() => {\n    if (!inspectorEnabled) return;\n\n    const handleDocumentMouseMove = (event: globalThis.MouseEvent) => {\n      const target = event.target as HTMLElement | null;\n\n      if (!target) return;\n\n      if (rootRef.current?.contains(target)) {\n        return;\n      }\n\n      if (target.closest(\"[data-inspector-overlay]\")) {\n        return;\n      }\n\n      clearInspectorState();\n      debouncedInspectorUpdate.cancel();\n    };\n\n    document.addEventListener(\"mousemove\", handleDocumentMouseMove, { passive: true });\n\n    return () => {\n      document.removeEventListener(\"mousemove\", handleDocumentMouseMove);\n    };\n  }, [inspectorEnabled, clearInspectorState, debouncedInspectorUpdate, rootRef]);\n\n  useEffect(() => {\n    return () => {\n      debouncedInspectorUpdate.cancel();\n    };\n  }, [debouncedInspectorUpdate]);\n\n  return {\n    debouncedInspectorUpdate,\n    handleMouseMove,\n    handleMouseLeave,\n  };\n};\n"
  },
  {
    "path": "hooks/inspector/use-inspector-scroll.ts",
    "content": "import { useRef, useCallback, useEffect } from \"react\";\n\ninterface UseInspectorScrollProps {\n  inspectorEnabled: boolean;\n  clearInspectorState: () => void;\n  debouncedInspectorUpdate: {\n    cancel: () => void;\n  };\n  rootRef: React.RefObject<HTMLDivElement | null>;\n  isOverlayHiddenRef: React.MutableRefObject<boolean>;\n}\n\nexport const useInspectorScroll = ({\n  inspectorEnabled,\n  clearInspectorState,\n  debouncedInspectorUpdate,\n  rootRef,\n  isOverlayHiddenRef,\n}: UseInspectorScrollProps) => {\n  const scrollableElementRef = useRef<Element | null>(null);\n\n  const hideOverlayOnScroll = useCallback(() => {\n    if (!inspectorEnabled || isOverlayHiddenRef.current) return;\n\n    clearInspectorState();\n    debouncedInspectorUpdate.cancel();\n  }, [inspectorEnabled, clearInspectorState, debouncedInspectorUpdate, isOverlayHiddenRef]);\n\n  useEffect(() => {\n    const rootElement = rootRef.current;\n    if (!rootElement) return;\n\n    const viewport = rootElement.querySelector(\"[data-radix-scroll-area-viewport]\");\n    scrollableElementRef.current = viewport || rootElement;\n  }, [rootRef]);\n\n  useEffect(() => {\n    const scrollableElement = scrollableElementRef.current;\n    if (!scrollableElement || !inspectorEnabled) return;\n\n    scrollableElement.addEventListener(\"scroll\", hideOverlayOnScroll, { passive: true });\n\n    return () => {\n      scrollableElement.removeEventListener(\"scroll\", hideOverlayOnScroll);\n    };\n  }, [inspectorEnabled, hideOverlayOnScroll]);\n\n  return {\n    scrollableElementRef,\n  };\n};\n"
  },
  {
    "path": "hooks/inspector/use-inspector-state.ts",
    "content": "import { useState, useRef, useCallback } from \"react\";\nimport {\n  InspectorState,\n  areInspectorStatesEqual,\n  createInspectorState,\n  getEmptyInspectorState,\n} from \"../../lib/inspector/inspector-state-utils\";\n\nexport const useInspectorState = () => {\n  const [inspector, setInspector] = useState<InspectorState>(getEmptyInspectorState());\n  const [inspectorEnabled, setInspectorEnabled] = useState(false);\n  const lastElementRef = useRef<HTMLElement | null>(null);\n  const isOverlayHiddenRef = useRef<boolean>(false);\n\n  const updateInspectorState = useCallback((rect: DOMRect, matches: string[]) => {\n    setInspector((prev: InspectorState) => {\n      const newState = createInspectorState(rect, matches);\n\n      if (areInspectorStatesEqual(prev, newState)) {\n        return prev;\n      }\n\n      isOverlayHiddenRef.current = false;\n      return newState;\n    });\n  }, []);\n\n  const clearInspectorState = useCallback(() => {\n    if (lastElementRef.current || !isOverlayHiddenRef.current) {\n      lastElementRef.current = null;\n      isOverlayHiddenRef.current = true;\n      setInspector(getEmptyInspectorState());\n    }\n  }, []);\n\n  const toggleInspector = useCallback((onToggleOff?: () => void) => {\n    setInspectorEnabled((prev) => {\n      if (prev) {\n        lastElementRef.current = null;\n        isOverlayHiddenRef.current = true;\n        setInspector(getEmptyInspectorState());\n        onToggleOff?.();\n      } else {\n        isOverlayHiddenRef.current = false;\n      }\n      return !prev;\n    });\n  }, []);\n\n  return {\n    inspector,\n    inspectorEnabled,\n    lastElementRef,\n    isOverlayHiddenRef,\n    updateInspectorState,\n    clearInspectorState,\n    toggleInspector,\n  };\n};\n"
  },
  {
    "path": "hooks/inspector/use-theme-inspector.ts",
    "content": "\"use client\";\n\nimport { useRef } from \"react\";\nimport { useInspectorState } from \"./use-inspector-state\";\nimport { useInspectorMouseEvents } from \"./use-inspector-mouse-events\";\nimport { useInspectorScroll } from \"./use-inspector-scroll\";\n\nexport const useThemeInspector = () => {\n  const rootRef = useRef<HTMLDivElement>(null);\n\n  const {\n    inspector,\n    inspectorEnabled,\n    lastElementRef,\n    isOverlayHiddenRef,\n    updateInspectorState,\n    clearInspectorState,\n    toggleInspector: baseToggleInspector,\n  } = useInspectorState();\n\n  const { debouncedInspectorUpdate, handleMouseMove, handleMouseLeave } = useInspectorMouseEvents({\n    inspectorEnabled,\n    rootRef,\n    lastElementRef,\n    updateInspectorState,\n    clearInspectorState,\n  });\n\n  useInspectorScroll({\n    inspectorEnabled,\n    clearInspectorState,\n    debouncedInspectorUpdate,\n    rootRef,\n    isOverlayHiddenRef,\n  });\n\n  const toggleInspector = () => {\n    baseToggleInspector(() => {\n      debouncedInspectorUpdate.cancel();\n    });\n  };\n\n  return {\n    rootRef,\n    inspector,\n    inspectorEnabled,\n    toggleInspector,\n    handleMouseMove,\n    handleMouseLeave,\n  } as const;\n};\n"
  },
  {
    "path": "hooks/themes/index.ts",
    "content": "export { useThemesData, useThemeData, usePrefetchThemes, themeKeys } from \"./use-themes-data\";\nexport { useCreateTheme, useUpdateTheme, useDeleteTheme } from \"./use-theme-mutations\";\nexport {\n  useCommunityThemes,\n  useCommunityTagCounts,\n  usePublishTheme,\n  useUnpublishTheme,\n  useToggleLike,\n  useUpdateCommunityThemeTags,\n  communityKeys,\n} from \"./use-community-themes\";\n"
  },
  {
    "path": "hooks/themes/use-community-themes.ts",
    "content": "import {\n  useInfiniteQuery,\n  useMutation,\n  useQuery,\n  useQueryClient,\n} from \"@tanstack/react-query\";\nimport {\n  getCommunityThemes,\n  getCommunityTagCounts,\n  publishTheme,\n  unpublishTheme,\n  toggleLikeTheme,\n  getMyPublishedThemeIds,\n  updateCommunityThemeTags,\n} from \"@/actions/community-themes\";\nimport { toast } from \"@/components/ui/use-toast\";\nimport type {\n  CommunityTheme,\n  CommunitySortOption,\n  CommunityFilterOption,\n  CommunityTimeRange,\n  CommunityThemesResponse,\n} from \"@/types/community\";\nimport { themeKeys } from \"./use-themes-data\";\nimport { ErrorCode } from \"@/types/errors\";\n\nexport const communityKeys = {\n  all: [\"community-themes\"] as const,\n  list: (\n    sort: CommunitySortOption,\n    filter: CommunityFilterOption = \"all\",\n    tags: string[] = [],\n    timeRange: CommunityTimeRange = \"all\"\n  ) =>\n    [...communityKeys.all, \"list\", { sort, filter, tags, timeRange }] as const,\n  myPublished: () => [...communityKeys.all, \"my-published\"] as const,\n  tagCounts: () => [...communityKeys.all, \"tag-counts\"] as const,\n};\n\nexport function useCommunityThemes(\n  sort: CommunitySortOption,\n  filter: CommunityFilterOption = \"all\",\n  tags: string[] = [],\n  timeRange: CommunityTimeRange = \"all\"\n) {\n  return useInfiniteQuery({\n    queryKey: communityKeys.list(sort, filter, tags, timeRange),\n    queryFn: async ({ pageParam }) => {\n      return getCommunityThemes(\n        sort,\n        pageParam,\n        undefined,\n        filter,\n        tags,\n        timeRange\n      );\n    },\n    initialPageParam: undefined as string | number | undefined,\n    getNextPageParam: (lastPage: CommunityThemesResponse) => {\n      return lastPage.nextCursor ?? undefined;\n    },\n    staleTime: 1000 * 60 * 5, // 5 minutes\n  });\n}\n\nexport function useCommunityTagCounts() {\n  return useQuery({\n    queryKey: communityKeys.tagCounts(),\n    queryFn: getCommunityTagCounts,\n    staleTime: 1000 * 60 * 15, // 15 minutes\n  });\n}\n\nexport function useMyPublishedThemeIds() {\n  return useQuery({\n    queryKey: communityKeys.myPublished(),\n    queryFn: getMyPublishedThemeIds,\n    staleTime: 1000 * 60 * 10, // 10 minutes\n  });\n}\n\nexport function usePublishTheme() {\n  const queryClient = useQueryClient();\n\n  return useMutation({\n    mutationFn: async ({\n      themeId,\n      tags = [],\n    }: {\n      themeId: string;\n      tags?: string[];\n    }) => {\n      const result = await publishTheme(themeId, tags);\n      if (!result.success) {\n        if (result.error.code === ErrorCode.ALREADY_PUBLISHED) {\n          toast({\n            title: \"Already published\",\n            description: result.error.message,\n          });\n        }\n        throw new Error(result.error.message);\n      }\n      return result.data;\n    },\n    onSuccess: () => {\n      toast({\n        title: \"Theme published\",\n        description: \"Your theme is now visible in the community.\",\n      });\n      queryClient.invalidateQueries({ queryKey: communityKeys.all });\n      queryClient.invalidateQueries({ queryKey: communityKeys.myPublished() });\n      queryClient.invalidateQueries({ queryKey: themeKeys.lists() });\n    },\n    onError: (error) => {\n      if (\n        (error as Error).message.includes(\"already published\") ||\n        (error as Error).message.includes(\"Already published\")\n      ) {\n        return;\n      }\n      toast({\n        title: \"Failed to publish theme\",\n        description:\n          (error as Error).message || \"An unexpected error occurred.\",\n        variant: \"destructive\",\n      });\n    },\n  });\n}\n\nexport function useUnpublishTheme() {\n  const queryClient = useQueryClient();\n\n  return useMutation({\n    mutationFn: (themeId: string) => unpublishTheme(themeId),\n    onSuccess: () => {\n      toast({\n        title: \"Theme unpublished\",\n        description: \"Your theme has been removed from the community.\",\n      });\n      queryClient.invalidateQueries({ queryKey: communityKeys.all });\n      queryClient.invalidateQueries({ queryKey: communityKeys.myPublished() });\n      queryClient.invalidateQueries({ queryKey: themeKeys.lists() });\n    },\n    onError: (error) => {\n      toast({\n        title: \"Failed to unpublish theme\",\n        description:\n          (error as Error).message || \"An unexpected error occurred.\",\n        variant: \"destructive\",\n      });\n    },\n  });\n}\n\nexport function useToggleLike() {\n  const queryClient = useQueryClient();\n\n  return useMutation({\n    mutationFn: async (communityThemeId: string) => {\n      const result = await toggleLikeTheme(communityThemeId);\n      if (!result.success) {\n        throw new Error(result.error.message);\n      }\n      return { ...result.data, communityThemeId };\n    },\n    onMutate: async (communityThemeId: string) => {\n      // Optimistic update across all community theme lists\n      await queryClient.cancelQueries({ queryKey: communityKeys.all });\n\n      const previousData = queryClient.getQueriesData({\n        queryKey: communityKeys.all,\n      });\n\n      queryClient.setQueriesData(\n        { queryKey: communityKeys.all },\n        (old: unknown) => {\n          if (!old || typeof old !== \"object\") return old;\n          const data = old as {\n            pages?: CommunityThemesResponse[];\n            pageParams?: unknown[];\n          };\n          if (!data.pages) return old;\n          return {\n            ...data,\n            pages: data.pages.map((page) => ({\n              ...page,\n              themes: page.themes.map((theme: CommunityTheme) =>\n                theme.id === communityThemeId\n                  ? {\n                      ...theme,\n                      isLikedByMe: !theme.isLikedByMe,\n                      likeCount: theme.isLikedByMe\n                        ? theme.likeCount - 1\n                        : theme.likeCount + 1,\n                    }\n                  : theme\n              ),\n            })),\n          };\n        }\n      );\n\n      return { previousData };\n    },\n    onError: (_error, _variables, context) => {\n      // Rollback optimistic updates\n      if (context?.previousData) {\n        for (const [queryKey, data] of context.previousData) {\n          queryClient.setQueryData(queryKey, data);\n        }\n      }\n      toast({\n        title: \"Failed to update like\",\n        description: \"An unexpected error occurred.\",\n        variant: \"destructive\",\n      });\n    },\n    onSettled: () => {\n      queryClient.invalidateQueries({ queryKey: communityKeys.all });\n    },\n  });\n}\n\nexport function useUpdateCommunityThemeTags() {\n  const queryClient = useQueryClient();\n\n  return useMutation({\n    mutationFn: async ({\n      themeId,\n      tags,\n    }: {\n      themeId: string;\n      tags: string[];\n    }) => {\n      const result = await updateCommunityThemeTags(themeId, tags);\n      if (!result.success) {\n        throw new Error(result.error.message);\n      }\n      return result.data;\n    },\n    onSuccess: () => {\n      toast({\n        title: \"Tags updated\",\n        description: \"Your theme tags have been updated.\",\n      });\n      queryClient.invalidateQueries({ queryKey: communityKeys.all });\n    },\n    onError: (error) => {\n      toast({\n        title: \"Failed to update tags\",\n        description:\n          (error as Error).message || \"An unexpected error occurred.\",\n        variant: \"destructive\",\n      });\n    },\n  });\n}\n"
  },
  {
    "path": "hooks/themes/use-theme-mutations.ts",
    "content": "import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { createTheme, updateTheme, deleteTheme } from \"@/actions/themes\";\nimport { themeKeys } from \"./use-themes-data\";\nimport { ThemeStyles, Theme } from \"@/types/theme\";\nimport { toast } from \"@/components/ui/use-toast\";\nimport { useThemePresetStore } from \"@/store/theme-preset-store\";\nimport posthog from \"posthog-js\";\nimport { useRouter } from \"next/navigation\";\nimport { useGetProDialogStore } from \"@/store/get-pro-dialog-store\";\nimport { MAX_FREE_THEMES } from \"@/lib/constants\";\nimport { ErrorCode } from \"@/types/errors\";\n\nfunction handleMutationError(error: Error, operation: string) {\n  console.error(`Theme ${operation} error:`, error);\n\n  const errorName = error.name;\n\n  if (errorName !== \"UnauthorizedError\" && errorName !== \"ValidationError\") {\n    try {\n      posthog.capture(\"theme_mutation_error\", {\n        operation,\n        error: error.message,\n        errorName,\n      });\n    } catch (posthogError) {\n      console.error(\"Failed to log to PostHog:\", posthogError);\n    }\n  }\n\n  const getErrorMessage = (error: Error) => {\n    switch (error.name) {\n      case \"UnauthorizedError\":\n        return \"Please sign in to continue.\";\n      case \"ValidationError\":\n        return error.message || \"Invalid input provided.\";\n      case \"ThemeNotFoundError\":\n        return \"Theme not found.\";\n      default:\n        return \"An unexpected error occurred. Please try again.\";\n    }\n  };\n\n  toast({\n    title: `Failed to ${operation} theme`,\n    description: getErrorMessage(error),\n    variant: \"destructive\",\n  });\n\n  return error;\n}\n\nexport function useCreateTheme() {\n  const queryClient = useQueryClient();\n  const { registerPreset } = useThemePresetStore();\n  const { openGetProDialog } = useGetProDialogStore();\n\n  return useMutation({\n    mutationFn: async (data: { name: string; styles: ThemeStyles }) => {\n      const result = await createTheme(data);\n\n      // Handle theme limit error explicitly (returns ActionResult, not thrown)\n      if (!result.success) {\n        if (result.error.code === ErrorCode.THEME_LIMIT_REACHED) {\n          toast({\n            title: \"Theme limit reached\",\n            description: `You have reached the limit of ${MAX_FREE_THEMES} themes.`,\n            variant: \"destructive\",\n          });\n          openGetProDialog();\n        }\n        // Throw to trigger onError for other error handling\n        throw new Error(result.error.message);\n      }\n\n      return result.data;\n    },\n    onSuccess: (data) => {\n      queryClient.setQueryData(themeKeys.lists(), (old: Theme[] | undefined) => {\n        return old ? [...old, data] : [data];\n      });\n\n      registerPreset(data.id, {\n        label: data.name,\n        source: \"SAVED\",\n        createdAt: data.createdAt.toISOString(),\n        styles: data.styles,\n      });\n\n      toast({\n        title: \"Theme created\",\n        description: `\"${data.name}\" has been created successfully.`,\n      });\n    },\n    onError: (error) => {\n      // Theme limit errors are already handled in mutationFn, skip duplicate toast\n      if ((error as Error).message.includes(\"limit\")) {\n        return;\n      }\n      handleMutationError(error as Error, \"create\");\n    },\n    onSettled: () => {\n      queryClient.invalidateQueries({ queryKey: themeKeys.lists() });\n    },\n  });\n}\n\nexport function useUpdateTheme() {\n  const queryClient = useQueryClient();\n  const { updatePreset } = useThemePresetStore();\n\n  return useMutation({\n    mutationFn: (data: { id: string; name?: string; styles?: ThemeStyles }) => updateTheme(data),\n    onMutate: async (updatedTheme) => {\n      await queryClient.cancelQueries({ queryKey: themeKeys.lists() });\n      await queryClient.cancelQueries({ queryKey: themeKeys.detail(updatedTheme.id) });\n\n      const previousThemes = queryClient.getQueryData(themeKeys.lists());\n      const previousTheme = queryClient.getQueryData(themeKeys.detail(updatedTheme.id));\n\n      queryClient.setQueryData(themeKeys.lists(), (old: Theme[] | undefined) => {\n        if (!old) return [];\n        return old.map((theme) =>\n          theme.id === updatedTheme.id\n            ? {\n                ...theme,\n                ...(updatedTheme.name && { name: updatedTheme.name }),\n                ...(updatedTheme.styles && { styles: updatedTheme.styles }),\n                updatedAt: new Date(),\n              }\n            : theme\n        );\n      });\n\n      return { previousThemes, previousTheme };\n    },\n    onSuccess: (data) => {\n      queryClient.setQueryData(themeKeys.detail(data.id), data);\n      queryClient.setQueryData(themeKeys.lists(), (old: Theme[] | undefined) => {\n        if (!old) return [data];\n        return old.map((theme) => (theme.id === data.id ? data : theme));\n      });\n\n      updatePreset(data.id, {\n        label: data.name,\n        source: \"SAVED\",\n        createdAt: data.createdAt.toISOString(),\n        styles: data.styles,\n      });\n\n      toast({\n        title: \"Theme updated\",\n        description: `\"${data.name}\" has been updated successfully.`,\n      });\n    },\n    onError: (error, variables, context) => {\n      if (context?.previousThemes) {\n        queryClient.setQueryData(themeKeys.lists(), context.previousThemes);\n      }\n      if (context?.previousTheme) {\n        queryClient.setQueryData(themeKeys.detail(variables.id), context.previousTheme);\n      }\n      handleMutationError(error as Error, \"update\");\n    },\n    onSettled: (data, error, variables) => {\n      queryClient.invalidateQueries({ queryKey: themeKeys.lists() });\n      queryClient.invalidateQueries({ queryKey: themeKeys.detail(variables.id) });\n    },\n  });\n}\n\nexport function useDeleteTheme() {\n  const queryClient = useQueryClient();\n  const { unregisterPreset } = useThemePresetStore();\n  const router = useRouter();\n\n  return useMutation({\n    mutationFn: (themeId: string) => deleteTheme(themeId),\n    onMutate: async (themeId) => {\n      await queryClient.cancelQueries({ queryKey: themeKeys.lists() });\n      const previousThemes = queryClient.getQueryData(themeKeys.lists());\n\n      queryClient.setQueryData(themeKeys.lists(), (old: Theme[] | undefined) => {\n        return old ? old.filter((theme) => theme.id !== themeId) : [];\n      });\n\n      return { previousThemes, themeId };\n    },\n    onSuccess: (data, themeId) => {\n      unregisterPreset(themeId);\n      queryClient.removeQueries({ queryKey: themeKeys.detail(themeId) });\n\n      toast({\n        title: \"Theme deleted\",\n        description: `\"${data.name}\" has been deleted successfully.`,\n      });\n      router.refresh();\n    },\n    onError: (error, _themeId, context) => {\n      if (context?.previousThemes) {\n        queryClient.setQueryData(themeKeys.lists(), context.previousThemes);\n      }\n      handleMutationError(error as Error, \"delete\");\n    },\n    onSettled: () => {\n      queryClient.invalidateQueries({ queryKey: themeKeys.lists() });\n    },\n  });\n}\n"
  },
  {
    "path": "hooks/themes/use-themes-data.ts",
    "content": "import { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { getThemes, getTheme } from \"@/actions/themes\";\nimport { Theme } from \"@/types/theme\";\n\nexport type ThemeWithPublished = Awaited<ReturnType<typeof getThemes>>[number];\n\nexport const themeKeys = {\n  all: [\"themes\"] as const,\n  lists: () => [...themeKeys.all, \"list\"] as const,\n  list: (filters: Record<string, any>) => [...themeKeys.lists(), { filters }] as const,\n  details: () => [...themeKeys.all, \"detail\"] as const,\n  detail: (id: string) => [...themeKeys.details(), { id }] as const,\n};\n\nexport function useThemesData(initialData?: ThemeWithPublished[]) {\n  return useQuery({\n    queryKey: themeKeys.lists(),\n    queryFn: getThemes,\n    initialData,\n    staleTime: 1000 * 60 * 5, // 5 minutes\n  });\n}\n\nexport function useThemeData(themeId: string | null, initialData?: Theme) {\n  return useQuery({\n    queryKey: themeKeys.detail(themeId!),\n    queryFn: () => getTheme(themeId!),\n    enabled: !!themeId,\n    initialData,\n    staleTime: 1000 * 60 * 10, // 10 minutes for individual themes\n  });\n}\n\nexport function usePrefetchThemes() {\n  const queryClient = useQueryClient();\n\n  return (themeIds: string[]) => {\n    themeIds.forEach((id) => {\n      queryClient.prefetchQuery({\n        queryKey: themeKeys.detail(id),\n        queryFn: () => getTheme(id),\n        staleTime: 1000 * 60 * 10,\n      });\n    });\n  };\n}\n"
  },
  {
    "path": "hooks/use-ai-chat-form.ts",
    "content": "\"use client\";\n\nimport { useAILocalDraftStore } from \"@/store/ai-local-draft-store\";\nimport { useImageUpload } from \"@/hooks/use-image-upload\";\nimport { createSyncedImageUploadReducer } from \"@/hooks/use-image-upload-reducer\";\nimport { MAX_IMAGE_FILES, MAX_IMAGE_FILE_SIZE } from \"@/lib/constants\";\nimport { useDocumentDragAndDropIntent } from \"@/hooks/use-document-drag-and-drop-intent\";\nimport { convertJSONContentToPromptData } from \"@/utils/ai/ai-prompt\";\nimport { JSONContent } from \"@tiptap/react\";\nimport { useReducer, useEffect, useRef, useTransition } from \"react\";\n\nexport function useAIChatForm() {\n  const {\n    editorContentDraft,\n    setEditorContentDraft,\n    clearLocalDraft,\n    imagesDraft,\n    setImagesDraft,\n  } = useAILocalDraftStore();\n\n  const [isInitializing, startTransition] = useTransition();\n  const hasInitialized = useRef(false);\n\n  const [uploadedImages, dispatch] = useReducer(\n    createSyncedImageUploadReducer(setImagesDraft),\n    [] // Always start with empty array to avoid stale initial state\n  );\n\n  // Initialize uploadedImages from persisted draft once\n  useEffect(() => {\n    if (!hasInitialized.current && imagesDraft.length > 0) {\n      hasInitialized.current = true;\n\n      startTransition(() => {\n        dispatch({\n          type: \"INITIALIZE\",\n          payload: imagesDraft.map(({ url }) => ({ url })),\n        });\n      });\n    }\n  }, [imagesDraft]);\n\n  const {\n    fileInputRef,\n    handleImagesUpload,\n    handleImageRemove,\n    clearUploadedImages,\n    isSomeImageUploading,\n  } = useImageUpload({\n    maxFiles: MAX_IMAGE_FILES,\n    maxFileSize: MAX_IMAGE_FILE_SIZE,\n    images: uploadedImages,\n    dispatch,\n  });\n\n  const { isUserDragging } = useDocumentDragAndDropIntent();\n\n  const promptData = convertJSONContentToPromptData(\n    editorContentDraft || { type: \"doc\", content: [] }\n  );\n\n  const isEmptyPrompt =\n    uploadedImages.length === 0 &&\n    (!promptData?.content?.trim() || promptData.content.length === 0);\n\n  const handleContentChange = (jsonContent: JSONContent) => {\n    setEditorContentDraft(jsonContent);\n  };\n\n  return {\n    editorContentDraft,\n    handleContentChange,\n    promptData,\n    isEmptyPrompt,\n    clearLocalDraft,\n    uploadedImages,\n    fileInputRef,\n    handleImagesUpload,\n    handleImageRemove,\n    clearUploadedImages,\n    isSomeImageUploading,\n    isUserDragging,\n    isInitializing,\n  };\n}\n"
  },
  {
    "path": "hooks/use-ai-enhance-prompt.ts",
    "content": "\"use client\";\n\nimport { toast } from \"@/hooks/use-toast\";\nimport { parseAiSdkTransportError } from \"@/lib/ai/parse-ai-sdk-transport-error\";\nimport { useAILocalDraftStore } from \"@/store/ai-local-draft-store\";\nimport { useGetProDialogStore } from \"@/store/get-pro-dialog-store\";\nimport { AIPromptData } from \"@/types/ai\";\nimport { convertPromptDataToJSONContent } from \"@/utils/ai/ai-prompt\";\nimport { useCompletion } from \"@ai-sdk/react\";\nimport { JSONContent } from \"@tiptap/react\";\nimport posthog from \"posthog-js\";\nimport { useCallback, useMemo, useRef } from \"react\";\n\nexport function useAIEnhancePrompt() {\n  const { openGetProDialog } = useGetProDialogStore();\n  const { complete, completion, isLoading, stop, setCompletion } = useCompletion({\n    api: \"/api/enhance-prompt\",\n    onError: (error) => {\n      const defaultMessage = \"Failed to enhance prompt. Please try again.\";\n      const normalized = parseAiSdkTransportError(error, defaultMessage);\n\n      try {\n        posthog.capture(\"ENHANCE_PROMPT_ERROR\", {\n          message: normalized.message,\n          code: normalized.code,\n          status: normalized.status,\n        });\n      } catch {}\n\n      if (normalized.code === \"SUBSCRIPTION_REQUIRED\") {\n        openGetProDialog();\n      }\n\n      toast({\n        title: \"An error occurred\",\n        description: normalized.message,\n        variant: \"destructive\",\n      });\n    },\n    onFinish: (_prompt, finalCompletion) => {\n      try {\n        const durationMs = startTimeRef.current ? Date.now() - startTimeRef.current : undefined;\n        posthog.capture(\"ENHANCE_PROMPT_FINISH\", {\n          durationMs,\n          finalLength: finalCompletion?.length ?? 0,\n        });\n      } catch {}\n\n      const promptData: AIPromptData = {\n        content: finalCompletion,\n        mentions: activeMentionsRef.current.map((m) => ({\n          id: m.id,\n          label: m.label,\n          themeData: { light: {}, dark: {} },\n        })),\n      };\n      const jsonContent = convertPromptDataToJSONContent(promptData);\n      useAILocalDraftStore.getState().setEditorContentDraft(jsonContent);\n    },\n  });\n\n  const activeMentionsRef = useRef<Array<{ id: string; label: string }>>([]);\n  const startTimeRef = useRef<number | null>(null);\n\n  const enhancedPromptAsJsonContent: JSONContent | undefined = useMemo(() => {\n    if (!completion) return undefined;\n    const promptData: AIPromptData = {\n      content: completion,\n      mentions: activeMentionsRef.current.map((m) => ({\n        id: m.id,\n        label: m.label,\n        themeData: { light: {}, dark: {} },\n      })),\n    };\n    return convertPromptDataToJSONContent(promptData);\n  }, [completion]);\n\n  const startEnhance = useCallback(\n    async (promptData: AIPromptData) => {\n      const prompt = promptData?.content ?? \"\";\n      if (!prompt?.trim()) return;\n\n      if (isLoading) stop();\n      setCompletion(\"\");\n\n      startTimeRef.current = Date.now();\n      activeMentionsRef.current =\n        promptData?.mentions?.map((m) => ({ id: m.id, label: m.label })) ?? [];\n\n      try {\n        posthog.capture(\"ENHANCE_PROMPT_START\", {\n          contentLength: prompt.length,\n          mentionCount: promptData?.mentions?.length ?? 0,\n          imageCount: promptData?.images?.length ?? 0,\n        });\n      } catch {}\n\n      await complete(prompt, { body: { promptData } });\n    },\n    [complete, isLoading, stop, setCompletion]\n  );\n\n  const stopEnhance = useCallback(() => {\n    stop();\n\n    if (enhancedPromptAsJsonContent) {\n      useAILocalDraftStore.getState().setEditorContentDraft(enhancedPromptAsJsonContent);\n    }\n\n    try {\n      const durationMs = startTimeRef.current ? Date.now() - startTimeRef.current : undefined;\n      posthog.capture(\"ENHANCE_PROMPT_CANCEL\", { durationMs });\n    } catch {}\n  }, [stop, enhancedPromptAsJsonContent]);\n\n  return {\n    startEnhance,\n    stopEnhance,\n    enhancedPrompt: completion,\n    enhancedPromptAsJsonContent,\n    isEnhancingPrompt: isLoading,\n  } as const;\n}\n"
  },
  {
    "path": "hooks/use-ai-theme-generation-core.ts",
    "content": "import { useChatContext } from \"@/hooks/use-chat-context\";\nimport { AIPromptData } from \"@/types/ai\";\n\nexport function useAIThemeGenerationCore() {\n  const { status, sendMessage, stop } = useChatContext();\n  const isGeneratingTheme = status === \"submitted\" || status === \"streaming\";\n\n  const generateThemeCore = async (promptData?: AIPromptData) => {\n    if (!promptData) throw new Error(\"Failed to generate theme. Please try again.\");\n\n    sendMessage({\n      text: promptData.content,\n      metadata: { promptData },\n    });\n  };\n\n  return {\n    generateThemeCore,\n    isGeneratingTheme,\n    cancelThemeGeneration: stop,\n  };\n}\n"
  },
  {
    "path": "hooks/use-chat-context.tsx",
    "content": "\"use client\";\n\nimport { SUBSCRIPTION_STATUS_QUERY_KEY } from \"@/hooks/use-subscription\";\nimport { toast } from \"@/hooks/use-toast\";\nimport { useAIChatStore } from \"@/store/ai-chat-store\";\nimport { ChatMessage } from \"@/types/ai\";\nimport { applyGeneratedTheme } from \"@/utils/ai/apply-theme\";\n\nimport { parseAiSdkTransportError } from \"@/lib/ai/parse-ai-sdk-transport-error\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { DefaultChatTransport } from \"ai\";\nimport { createContext, useContext, useEffect, useRef } from \"react\";\n\ninterface ChatContext extends ReturnType<typeof useChat<ChatMessage>> {\n  startNewChat: () => void;\n  resetMessagesUpToIndex: (index: number) => void;\n}\n\nconst ChatContext = createContext<ChatContext | null>(null);\n\nexport function ChatProvider({ children }: { children: React.ReactNode }) {\n  const queryClient = useQueryClient();\n  const storedMessages = useAIChatStore((s) => s.messages);\n  const setStoredMessages = useAIChatStore((s) => s.setMessages);\n\n  const hasStoreHydrated = useAIChatStore((s) => s.hasHydrated);\n  const hasInitializedRef = useRef(false);\n\n  const chat = useChat<ChatMessage>({\n    transport: new DefaultChatTransport({\n      api: \"/api/generate-theme\",\n    }),\n    onError: (error) => {\n      const defaultMessage = \"Failed to generate theme. Please try again.\";\n      const normalizedError = parseAiSdkTransportError(error, defaultMessage);\n\n      toast({\n        title: \"An error occurred\",\n        description: normalizedError.message,\n        variant: \"destructive\",\n      });\n    },\n    onData: (dataPart) => {\n      const { type, data } = dataPart;\n      if (type === \"data-generated-theme-styles\") {\n        if (data.status === \"ready\") applyGeneratedTheme(data.themeStyles);\n      }\n    },\n    onFinish: () => {\n      queryClient.invalidateQueries({ queryKey: [SUBSCRIPTION_STATUS_QUERY_KEY] });\n    },\n  });\n\n  const startNewChat = () => {\n    chat.stop();\n    chat.setMessages([]);\n  };\n\n  const resetMessagesUpToIndex = (index: number) => {\n    const newMessages = chat.messages.slice(0, index);\n    chat.setMessages(newMessages);\n  };\n\n  useEffect(() => {\n    if (!hasInitializedRef.current) return;\n\n    // Only update the stored messages when the chat is not currently processing a request\n    if (chat.status === \"ready\" || chat.status === \"error\") {\n      console.log(\"----- ✅ Updating Stored Messages -----\");\n      setStoredMessages(chat.messages);\n    }\n  }, [chat.status, chat.messages]);\n\n  useEffect(() => {\n    if (!hasStoreHydrated || hasInitializedRef.current) return;\n\n    if (storedMessages.length > 0) {\n      console.log(\"----- ☑️ Populating Chat with Stored Messages -----\");\n      chat.setMessages(storedMessages);\n    }\n\n    hasInitializedRef.current = true;\n  }, [hasStoreHydrated, storedMessages]);\n\n  return (\n    <ChatContext.Provider value={{ ...chat, startNewChat, resetMessagesUpToIndex }}>\n      {children}\n    </ChatContext.Provider>\n  );\n}\n\nexport function useChatContext() {\n  const ctx = useContext(ChatContext);\n  if (!ctx) {\n    throw new Error(\"useChatContext must be used within an ChatProvider\");\n  }\n  return ctx;\n}\n"
  },
  {
    "path": "hooks/use-contrast-checker.ts",
    "content": "import { useState, useEffect, useCallback } from \"react\";\nimport { getContrastRatio } from \"../utils/contrast-checker\";\nimport { debounce } from \"../utils/debounce\";\n\ntype ColorPair = {\n  id: string;\n  foreground: string;\n  background: string;\n};\n\ntype ContrastResult = {\n  id: string;\n  contrastRatio: number;\n};\n\n/**\n * Hook that calculates the contrast ratio between foreground and background colors for a list of pairs.\n * @param colorPairs - An array of color pairs, each with an id, foreground color, and background color.\n * @returns An array of objects, each containing the id and calculated contrast ratio for a pair.\n */\nexport function useContrastChecker(colorPairs: ColorPair[]) {\n  const [contrastResults, setContrastResults] = useState<ContrastResult[]>([]);\n\n  const debouncedCalculation = useCallback(\n    debounce((pairs: ColorPair[]) => {\n      if (!pairs.length) {\n        setContrastResults([]);\n        return;\n      }\n\n      try {\n        const results = pairs.map((pair) => {\n          const ratio = parseFloat(\n            getContrastRatio(pair.foreground, pair.background)\n          );\n          return {\n            id: pair.id,\n            contrastRatio: ratio,\n          };\n        });\n\n        setContrastResults(results);\n      } catch (error) {\n        console.error(\"Error checking contrast:\", error);\n        setContrastResults([]);\n      }\n    }, 750),\n    []\n  );\n\n  useEffect(() => {\n    debouncedCalculation(colorPairs);\n  }, [colorPairs, debouncedCalculation]);\n\n  return contrastResults;\n}\n"
  },
  {
    "path": "hooks/use-controls-tab-from-url.ts",
    "content": "import { useQueryState } from \"nuqs\";\n\nconst TABS = [\"colors\", \"typography\", \"other\", \"ai\"] as const;\nexport const DEFAULT_TAB = TABS[0];\nexport type ControlTab = (typeof TABS)[number];\n\nexport const useControlsTabFromUrl = () => {\n  const [tab, setTab] = useQueryState(\"tab\", {\n    defaultValue: DEFAULT_TAB,\n    parse: (value: string) => {\n      // Synchronously validate the tab value, and if it's invalid, fallback to the default tab\n      if (!TABS.includes(value as ControlTab)) {\n        console.warn(`Invalid tab value: ${value}. Falling back to default.`);\n        return DEFAULT_TAB;\n      }\n      return value as ControlTab;\n    },\n  });\n\n  const handleSetTab = (tab: ControlTab) => {\n    // If the incoming tab is invalid, fallback to the default tab\n    if (!TABS.includes(tab)) {\n      console.warn(`Invalid tab value: ${tab}. Falling back to default.`);\n      setTab(DEFAULT_TAB);\n      return;\n    }\n\n    setTab(tab);\n  };\n\n  return { tab, handleSetTab };\n};\n"
  },
  {
    "path": "hooks/use-copy-to-clipboard.ts",
    "content": "import { useState } from \"react\";\nimport { toast } from \"@/hooks/use-toast\";\n\nexport function useCopyToClipboard() {\n  const [isCopying, setIsCopying] = useState(false);\n  const [hasCopied, setHasCopied] = useState(false);\n\n  const copyToClipboard = async (\n    text: string,\n    successMessage?: { title?: string; description?: string }\n  ) => {\n    try {\n      setIsCopying(true);\n      await navigator.clipboard.writeText(text);\n      setHasCopied(true);\n\n      if (successMessage) {\n        toast({\n          title: successMessage.title || \"Copied to clipboard\",\n          description:\n            successMessage.description ||\n            \"Text has been copied to your clipboard\",\n        });\n      }\n\n      // Reset copied state after 2 seconds\n      setTimeout(() => {\n        setHasCopied(false);\n      }, 2000);\n    } catch (error) {\n      console.error(\"Failed to copy:\", error);\n      toast({\n        title: \"Copy failed\",\n        description: \"Could not copy to clipboard\",\n        variant: \"destructive\",\n      });\n    } finally {\n      setIsCopying(false);\n    }\n  };\n\n  return {\n    isCopying,\n    hasCopied,\n    copyToClipboard,\n  };\n}\n"
  },
  {
    "path": "hooks/use-debounced-callback.ts",
    "content": "import { useRef, useEffect, useCallback } from \"react\";\n\nexport function useDebouncedCallback<A extends unknown[]>(\n  callback: (...args: A) => void,\n  wait: number\n) {\n  // Use a ref to store the latest callback.\n  const callbackRef = useRef(callback);\n  useEffect(() => {\n    callbackRef.current = callback;\n  }, [callback]);\n\n  // Use a ref to store the timeout ID\n  const timeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n  // Cleanup function\n  useEffect(() => {\n    // Clear the timeout when the component unmounts or dependencies change\n    return () => {\n      if (timeoutRef.current) {\n        clearTimeout(timeoutRef.current);\n      }\n    };\n  }, [wait]); // Rerun effect only if wait time changes\n\n  // The debounced function\n  const debouncedCallback = useCallback(\n    (...args: A) => {\n      if (timeoutRef.current) {\n        clearTimeout(timeoutRef.current);\n      }\n\n      timeoutRef.current = setTimeout(() => {\n        callbackRef.current(...args);\n      }, wait);\n    },\n    [wait] // Dependency is only the wait time\n  );\n\n  return debouncedCallback;\n}\n"
  },
  {
    "path": "hooks/use-dialog-actions.tsx",
    "content": "import { CodePanelDialog } from \"@/components/editor/code-panel-dialog\";\nimport CssImportDialog from \"@/components/editor/css-import-dialog\";\nimport { ShareDialog } from \"@/components/editor/share-dialog\";\nimport { ThemeSaveDialog } from \"@/components/editor/theme-save-dialog\";\nimport { toast } from \"@/components/ui/use-toast\";\nimport { useCreateTheme, useUpdateTheme } from \"@/hooks/themes\";\nimport { useAIThemeGenerationCore } from \"@/hooks/use-ai-theme-generation-core\";\nimport { usePostLoginAction } from \"@/hooks/use-post-login-action\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { useAuthStore } from \"@/store/auth-store\";\nimport { useEditorStore } from \"@/store/editor-store\";\nimport { useThemePresetStore } from \"@/store/theme-preset-store\";\nimport { parseCssInput } from \"@/utils/parse-css-input\";\nimport { usePostHog } from \"posthog-js/react\";\nimport { createContext, ReactNode, useContext, useState } from \"react\";\n\ntype PendingAction = \"share\" | \"v0\" | null;\n\n// Get contextual copy for the save dialog based on the pending action\nfunction getSaveDialogCopy(pendingAction: PendingAction) {\n  switch (pendingAction) {\n    case \"share\":\n      return {\n        title: \"Save to share\",\n        description: \"Save your theme first to share it with others.\",\n        ctaLabel: \"Save & Share\",\n      };\n    case \"v0\":\n      return {\n        title: \"Save to open in v0\",\n        description: \"Save your theme first to open it in v0.\",\n        ctaLabel: \"Save & Open in v0\",\n      };\n    default:\n      return {};\n  }\n}\n\ninterface DialogActionsContextType {\n  // Dialog states\n  cssImportOpen: boolean;\n  codePanelOpen: boolean;\n  saveDialogOpen: boolean;\n  shareDialogOpen: boolean;\n  shareUrl: string;\n  dialogKey: number;\n  isCreatingTheme: boolean;\n  isUpdatingTheme: boolean;\n  isGeneratingTheme: boolean;\n  pendingAction: PendingAction;\n  existingThemeName: string | undefined;\n\n  // Dialog actions\n  setCssImportOpen: (open: boolean) => void;\n  setCodePanelOpen: (open: boolean) => void;\n  setSaveDialogOpen: (open: boolean) => void;\n  setShareDialogOpen: (open: boolean) => void;\n\n  // Handler functions\n  handleCssImport: (css: string) => void;\n  handleSaveClick: (options?: { shareAfterSave?: boolean; openInV0AfterSave?: boolean }) => void;\n  handleShareClick: (id?: string) => Promise<void>;\n  handleOpenInV0: (id?: string, name?: string) => void;\n  saveTheme: (themeName: string) => Promise<void>;\n  handleUpdateExisting: () => Promise<void>;\n}\n\nfunction useDialogActionsStore(): DialogActionsContextType {\n  const [cssImportOpen, setCssImportOpen] = useState(false);\n  const [codePanelOpen, setCodePanelOpen] = useState(false);\n  const [saveDialogOpen, setSaveDialogOpen] = useState(false);\n  const [pendingAction, setPendingAction] = useState<PendingAction>(null);\n  const [shareDialogOpen, setShareDialogOpen] = useState(false);\n  const [shareUrl, setShareUrl] = useState(\"\");\n  const [dialogKey, _setDialogKey] = useState(0);\n\n  const { themeState, setThemeState, applyThemePreset, hasThemeChangedFromCheckpoint, hasUnsavedChanges } =\n    useEditorStore();\n  const { getPreset } = useThemePresetStore();\n  const { data: session } = authClient.useSession();\n  const { openAuthDialog } = useAuthStore();\n  const createThemeMutation = useCreateTheme();\n  const updateThemeMutation = useUpdateTheme();\n  const { isGeneratingTheme } = useAIThemeGenerationCore();\n  const posthog = usePostHog();\n\n  const currentPreset = themeState?.preset ? getPreset(themeState.preset) : undefined;\n  const isOnSavedPreset = !!currentPreset && currentPreset.source === \"SAVED\" && hasUnsavedChanges();\n  const existingThemeName = isOnSavedPreset ? currentPreset.label : undefined;\n\n  usePostLoginAction(\"SAVE_THEME\", () => {\n    setSaveDialogOpen(true);\n  });\n\n  usePostLoginAction(\"SAVE_THEME_FOR_SHARE\", () => {\n    setSaveDialogOpen(true);\n    setPendingAction(\"share\");\n  });\n\n  usePostLoginAction(\"SAVE_THEME_FOR_V0\", () => {\n    setSaveDialogOpen(true);\n    setPendingAction(\"v0\");\n  });\n\n  const handleCssImport = (css: string) => {\n    const { lightColors, darkColors } = parseCssInput(css);\n    const styles = {\n      ...themeState.styles,\n      light: { ...themeState.styles.light, ...lightColors },\n      dark: { ...themeState.styles.dark, ...darkColors },\n    };\n\n    setThemeState({\n      ...themeState,\n      styles,\n    });\n\n    toast({\n      title: \"CSS imported\",\n      description: \"Your custom CSS has been imported successfully\",\n    });\n  };\n\n  const handleSaveClick = (options?: { shareAfterSave?: boolean; openInV0AfterSave?: boolean }) => {\n    if (!session) {\n      let action: \"SAVE_THEME\" | \"SAVE_THEME_FOR_SHARE\" | \"SAVE_THEME_FOR_V0\" = \"SAVE_THEME\";\n      if (options?.shareAfterSave) action = \"SAVE_THEME_FOR_SHARE\";\n      if (options?.openInV0AfterSave) action = \"SAVE_THEME_FOR_V0\";\n      openAuthDialog(\"signin\", action);\n      return;\n    }\n\n    setSaveDialogOpen(true);\n    if (options?.shareAfterSave) {\n      setPendingAction(\"share\");\n    }\n    if (options?.openInV0AfterSave) {\n      setPendingAction(\"v0\");\n    }\n  };\n\n  const saveTheme = async (themeName: string) => {\n    const themeData = {\n      name: themeName,\n      styles: themeState.styles,\n    };\n\n    try {\n      const theme = await createThemeMutation.mutateAsync(themeData);\n      posthog.capture(\"CREATE_THEME\", {\n        theme_id: theme?.id,\n        theme_name: theme?.name,\n      });\n      if (!theme) return;\n      applyThemePreset(theme?.id || themeState.preset || \"default\");\n      if (pendingAction === \"share\") {\n        handleShareClick(theme?.id);\n        setPendingAction(null);\n      } else if (pendingAction === \"v0\") {\n        openInV0(theme?.id);\n        setPendingAction(null);\n      }\n      setTimeout(() => {\n        setSaveDialogOpen(false);\n      }, 50);\n    } catch (error) {\n      console.error(\"Save operation failed (error likely handled by hook):\", error);\n    }\n  };\n\n  const handleShareClick = async (id?: string) => {\n    if (hasThemeChangedFromCheckpoint()) {\n      handleSaveClick({ shareAfterSave: true });\n      return;\n    }\n\n    const presetId = id ?? themeState.preset;\n    const currentPreset = presetId ? getPreset(presetId) : undefined;\n\n    if (!currentPreset) {\n      setShareUrl(`https://tweakcn.com/editor/theme`);\n      setShareDialogOpen(true);\n      return;\n    }\n\n    const isSavedPreset = !!currentPreset && currentPreset.source === \"SAVED\";\n\n    posthog.capture(\"SHARE_THEME\", {\n      theme_id: id,\n      theme_name: currentPreset?.label,\n      is_saved: isSavedPreset,\n    });\n\n    const url = isSavedPreset\n      ? `https://tweakcn.com/themes/${id}`\n      : `https://tweakcn.com/editor/theme?theme=${id}`;\n\n    setShareUrl(url);\n    setShareDialogOpen(true);\n  };\n\n  // Internal helper to open v0 with a theme\n  const openInV0 = (id?: string, name?: string) => {\n    const presetId = id ?? themeState.preset;\n    if (!presetId) return;\n\n    const currentPreset = getPreset(presetId);\n    // If an explicit ID is passed but not found in presets, treat as a saved/database theme\n    const isSavedPreset = id ? true : !!currentPreset && currentPreset.source === \"SAVED\";\n    const themeName = name || currentPreset?.label || presetId;\n\n    posthog.capture(\"OPEN_IN_V0\", {\n      theme_id: presetId,\n      theme_name: themeName,\n      is_saved: isSavedPreset,\n    });\n\n    const themeUrl = isSavedPreset\n      ? `https://tweakcn.com/r/v0/${presetId}`\n      : `https://tweakcn.com/r/v0/${presetId}.json`;\n    const title = `\"${themeName}\" from tweakcn`.slice(0, 32);\n    const v0Url = `https://v0.dev/chat/api/open?url=${encodeURIComponent(themeUrl)}&title=${encodeURIComponent(title)}`;\n    window.open(v0Url, \"_blank\", \"noopener,noreferrer\");\n  };\n\n  const handleOpenInV0 = (id?: string, name?: string) => {\n    if (id) {\n      openInV0(id, name);\n      return;\n    }\n\n    if (hasThemeChangedFromCheckpoint()) {\n      handleSaveClick({ openInV0AfterSave: true });\n      return;\n    }\n\n    openInV0();\n  };\n\n  const handleUpdateExisting = async () => {\n    if (!themeState.preset) return;\n    try {\n      const result = await updateThemeMutation.mutateAsync({\n        id: themeState.preset,\n        styles: themeState.styles,\n      });\n      if (result) {\n        applyThemePreset(result.id || themeState.preset);\n        setSaveDialogOpen(false);\n      }\n    } catch (error) {\n      console.error(\"Failed to update theme:\", error);\n    }\n  };\n\n  const value = {\n    // Dialog states\n    cssImportOpen,\n    codePanelOpen,\n    saveDialogOpen,\n    shareDialogOpen,\n    shareUrl,\n    dialogKey,\n    isCreatingTheme: createThemeMutation.isPending,\n    isUpdatingTheme: updateThemeMutation.isPending,\n    isGeneratingTheme,\n    pendingAction,\n    existingThemeName,\n\n    // Dialog actions\n    setCssImportOpen,\n    setCodePanelOpen,\n    setSaveDialogOpen,\n    setShareDialogOpen,\n\n    // Handler functions\n    handleCssImport,\n    handleSaveClick,\n    handleShareClick,\n    handleOpenInV0,\n    saveTheme,\n    handleUpdateExisting,\n  };\n\n  return value;\n}\n\nexport const DialogActionsContext = createContext<DialogActionsContextType | null>(null);\n\nexport function DialogActionsProvider({ children }: { children: ReactNode }) {\n  const { themeState } = useEditorStore();\n  const store = useDialogActionsStore();\n\n  return (\n    <DialogActionsContext value={store}>\n      {children}\n\n      {/* Global Dialogs */}\n      <CssImportDialog\n        open={store.cssImportOpen}\n        onOpenChange={store.setCssImportOpen}\n        onImport={store.handleCssImport}\n      />\n      <CodePanelDialog\n        open={store.codePanelOpen}\n        onOpenChange={store.setCodePanelOpen}\n        themeEditorState={themeState}\n      />\n      <ThemeSaveDialog\n        open={store.saveDialogOpen}\n        onOpenChange={store.setSaveDialogOpen}\n        onSave={store.saveTheme}\n        isSaving={store.isCreatingTheme}\n        existingThemeName={store.existingThemeName}\n        onUpdateExisting={store.handleUpdateExisting}\n        isUpdating={store.isUpdatingTheme}\n        {...getSaveDialogCopy(store.pendingAction)}\n      />\n      <ShareDialog\n        open={store.shareDialogOpen}\n        onOpenChange={store.setShareDialogOpen}\n        url={store.shareUrl}\n      />\n    </DialogActionsContext>\n  );\n}\n\nexport function useDialogActions(): DialogActionsContextType {\n  const context = useContext(DialogActionsContext);\n\n  if (!context) {\n    throw new Error(\"useDialogActions must be used within a DialogActionsProvider\");\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "hooks/use-document-drag-and-drop-intent.ts",
    "content": "import { useEffect, useRef, useState } from \"react\";\n\nexport function useDocumentDragAndDropIntent() {\n  const [isUserDragging, setIsUserDragging] = useState(false);\n  const dragCounter = useRef(0);\n\n  useEffect(() => {\n    const handleDragEnter = (e: DragEvent) => {\n      dragCounter.current++;\n      if (e.dataTransfer?.types?.includes(\"Files\")) {\n        setIsUserDragging(true);\n      }\n    };\n    const handleDragLeave = (e: DragEvent) => {\n      dragCounter.current--;\n      if (dragCounter.current <= 0) {\n        setIsUserDragging(false);\n      }\n    };\n    const handleDrop = () => {\n      dragCounter.current = 0;\n      setIsUserDragging(false);\n    };\n\n    document.addEventListener(\"dragenter\", handleDragEnter);\n    document.addEventListener(\"dragleave\", handleDragLeave);\n    document.addEventListener(\"drop\", handleDrop);\n    return () => {\n      document.removeEventListener(\"dragenter\", handleDragEnter);\n      document.removeEventListener(\"dragleave\", handleDragLeave);\n      document.removeEventListener(\"drop\", handleDrop);\n    };\n  }, []);\n\n  return { isUserDragging };\n}\n"
  },
  {
    "path": "hooks/use-feedback-text.ts",
    "content": "import { useEffect, useState } from \"react\";\n\nconst ROTATION_INTERVAL_IN_SECONDS = 8;\n\nconst DEFAULT_FEEDBACK_MESSAGES = [\"Loading...\"];\n\ntype UseFeedbackTextProps = {\n  showFeedbackText: boolean;\n  feedbackMessages: string[];\n  rotationIntervalInSeconds?: number;\n};\n\nexport function useFeedbackText({\n  showFeedbackText,\n  feedbackMessages = DEFAULT_FEEDBACK_MESSAGES,\n  rotationIntervalInSeconds = ROTATION_INTERVAL_IN_SECONDS,\n}: UseFeedbackTextProps) {\n  const [elapsedTimeGenerating, setElapsedTimeGenerating] = useState(0);\n\n  useEffect(() => {\n    let interval: NodeJS.Timeout | null = null;\n\n    if (showFeedbackText) {\n      setElapsedTimeGenerating(0);\n\n      interval = setInterval(() => {\n        setElapsedTimeGenerating((prev) => prev + 1);\n      }, 1000);\n    } else {\n      setElapsedTimeGenerating(0);\n    }\n\n    return () => {\n      if (interval) clearInterval(interval);\n    };\n  }, [showFeedbackText]);\n\n  const stepsElapsed = Math.floor(elapsedTimeGenerating / rotationIntervalInSeconds);\n  const feedbackIndex = stepsElapsed % feedbackMessages.length;\n  const feedbackText = feedbackMessages[feedbackIndex];\n  return feedbackText;\n}\n"
  },
  {
    "path": "hooks/use-font-search.ts",
    "content": "import { PaginatedFontsResponse, type FontCategory } from \"@/types/fonts\";\nimport { useInfiniteQuery } from \"@tanstack/react-query\";\n\nexport type FilterFontCategory = \"all\" | FontCategory;\n\ninterface UseFontSearchParams {\n  query: string;\n  category?: FilterFontCategory;\n  limit?: number;\n  enabled?: boolean;\n}\n\nexport function useFontSearch({\n  query,\n  category = \"all\",\n  limit = 20,\n  enabled = true,\n}: UseFontSearchParams) {\n  return useInfiniteQuery({\n    queryKey: [\"fonts\", query, category],\n    queryFn: async ({ pageParam }) => {\n      const offset = pageParam || 0;\n      const searchParams = new URLSearchParams({\n        q: query,\n        limit: limit.toString(),\n        offset: offset.toString(),\n      });\n\n      if (category && category !== \"all\") {\n        searchParams.append(\"category\", category);\n      }\n\n      const response = await fetch(`/api/google-fonts?${searchParams}`);\n\n      if (!response.ok) {\n        throw new Error(\"Failed to fetch fonts\");\n      }\n\n      return response.json() as Promise<PaginatedFontsResponse>;\n    },\n    initialPageParam: 0,\n    getNextPageParam: (lastPage) => {\n      return lastPage.hasMore ? lastPage.offset + lastPage.limit : undefined;\n    },\n    staleTime: 1000 * 60 * 60 * 24, // 1 day\n    enabled,\n  });\n}\n"
  },
  {
    "path": "hooks/use-fullscreen.ts",
    "content": "import { useState, useEffect } from \"react\";\nimport screenfull from \"screenfull\";\n\nexport const useFullscreen = () => {\n  const [isFullscreen, setIsFullscreen] = useState(false);\n\n  useEffect(() => {\n    if (screenfull.isEnabled) {\n      const handleFullscreenChange = () => {\n        setIsFullscreen(screenfull.isFullscreen);\n      };\n\n      screenfull.on(\"change\", handleFullscreenChange);\n      return () => {\n        screenfull.off(\"change\", handleFullscreenChange);\n      };\n    }\n  }, []);\n\n  const toggleFullscreen = () => {\n    if (screenfull.isEnabled) {\n      screenfull.toggle();\n    }\n  };\n\n  return {\n    isFullscreen,\n    toggleFullscreen,\n  };\n};\n"
  },
  {
    "path": "hooks/use-github-stars.ts",
    "content": "import useSWR from \"swr\";\n\ninterface GitHubStarsResponse {\n  stargazers_count: number;\n}\n\nasync function fetchGithubStars(\n  owner: string,\n  repo: string\n): Promise<GitHubStarsResponse> {\n  const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`);\n  if (!response.ok) {\n    throw new Error(\"Failed to fetch stargazers count\");\n  }\n  return response.json();\n}\n\nexport function useGithubStars(owner: string, repo: string) {\n  const { data, isLoading, error } = useSWR(\n    [owner, repo],\n    ([owner, repo]) => fetchGithubStars(owner, repo),\n    {\n      revalidateOnFocus: false,\n      dedupingInterval: 60000, // Revalidate at most once per minute\n    }\n  );\n\n  return {\n    stargazersCount: data?.stargazers_count ?? 0,\n    isLoading,\n    error: error as Error | null,\n  };\n}\n"
  },
  {
    "path": "hooks/use-guards.ts",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useAuthStore } from \"@/store/auth-store\";\nimport { useGetProDialogStore } from \"@/store/get-pro-dialog-store\";\nimport { PostLoginActionType } from \"./use-post-login-action\";\nimport { useSubscription } from \"./use-subscription\";\n\nexport function useGuards() {\n  const { checkValidSession } = useSessionGuard();\n  const { checkValidSubscription, checkValidProSubscription } = useSubscriptionGuard();\n\n  return {\n    checkValidSession,\n    checkValidSubscription,\n    checkValidProSubscription,\n  };\n}\n\nexport function useSessionGuard() {\n  const { data: session } = authClient.useSession();\n  const { openAuthDialog } = useAuthStore();\n\n  const checkValidSession = (\n    mode: \"signin\" | \"signup\" = \"signin\",\n    postLoginActionType?: PostLoginActionType,\n    postLoginActionData?: any\n  ) => {\n    if (!session) {\n      openAuthDialog(mode, postLoginActionType, postLoginActionData);\n      return false;\n    }\n\n    return true;\n  };\n\n  return {\n    checkValidSession,\n  };\n}\n\nexport function useSubscriptionGuard() {\n  const { subscriptionStatus, isPending } = useSubscription();\n  const { openGetProDialog } = useGetProDialogStore();\n\n  const checkValidSubscription = () => {\n    if (isPending) return false;\n\n    if (!subscriptionStatus) return false;\n\n    const { isSubscribed, requestsRemaining } = subscriptionStatus;\n\n    if (isSubscribed) return true;\n\n    if (requestsRemaining <= 0) {\n      openGetProDialog();\n      return false;\n    }\n\n    return true; // Allow if not subscribed but still has requests left\n  };\n\n  // Use this guard for features that are Pro only, not including free pro usage\n  const checkValidProSubscription = () => {\n    if (isPending) return false;\n\n    if (!subscriptionStatus) return false;\n\n    const { isSubscribed } = subscriptionStatus;\n\n    if (isSubscribed) return true;\n\n    openGetProDialog();\n    return false;\n  };\n\n  return {\n    checkValidSubscription,\n    checkValidProSubscription,\n  };\n}\n"
  },
  {
    "path": "hooks/use-iframe-theme-injector.ts",
    "content": "import { useEditorStore } from \"@/store/editor-store\";\nimport { EmbedMessage, IframeStatus, MESSAGE } from \"@/types/live-preview-embed\";\nimport { applyThemeToElement } from \"@/utils/apply-theme\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nconst THEME_UPDATE_DEBOUNCE_MS = 50;\n\nexport interface UseIframeThemeInjectorProps {\n  allowCrossOrigin?: boolean; // default false - must explicitly opt-in for external sites\n  iframeRef?: React.RefObject<HTMLIFrameElement | null>; // optional - hook provides one if not given\n}\n\n/**\n * Unified hook for iframe theme injection\n * Same-origin: Direct theme application (no validation needed)\n * Cross-origin: postMessage communication with validation\n */\nexport const useIframeThemeInjector = ({\n  allowCrossOrigin = false,\n  iframeRef,\n}: UseIframeThemeInjectorProps = {}) => {\n  const internalRef = useRef<HTMLIFrameElement | null>(null);\n  const ref = iframeRef ?? internalRef;\n\n  const { themeState } = useEditorStore();\n  const [status, setStatus] = useState<IframeStatus>(\"unknown\");\n  const [themeInjectionError, setThemeInjectionError] = useState<string | null>(null);\n  const validationTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const themeUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  const applySameOriginTheme = useCallback(() => {\n    if (allowCrossOrigin) return; // Only for same-origin mode\n\n    const iframe = ref.current;\n    const iframeRoot = iframe?.contentDocument?.documentElement;\n    if (!iframeRoot) return;\n\n    applyThemeToElement(themeState, iframeRoot);\n  }, [allowCrossOrigin, ref, themeState]);\n\n  const postMessage = useCallback(\n    (msg: EmbedMessage) => {\n      const iframe = ref.current;\n      if (iframe?.contentWindow) {\n        try {\n          iframe.contentWindow.postMessage(msg, \"*\");\n        } catch (error) {\n          console.warn(\"Failed to send message to iframe:\", error);\n          setThemeInjectionError(\"Failed to establish the connection.\");\n        }\n      }\n    },\n    [ref]\n  );\n\n  const startCrossOriginValidation = useCallback(() => {\n    setStatus(\"checking\");\n    postMessage({ type: MESSAGE.PING });\n\n    clearTimeout(validationTimeoutRef.current!);\n    validationTimeoutRef.current = setTimeout(() => {\n      setStatus(\"missing\");\n      setThemeInjectionError(\n        \"The tweakcn's live theme preview script could not be found. Please make sure the script is included in the website's source code and try again.\"\n      );\n    }, 3000);\n  }, [postMessage]);\n\n  // Listen for iframe load\n  useEffect(() => {\n    const iframe = ref.current;\n    if (!iframe) {\n      setStatus(\"unknown\");\n      return;\n    }\n\n    const handleLoad = () => {\n      if (allowCrossOrigin) {\n        // Cross-origin: validate via postMessage\n        startCrossOriginValidation();\n      } else {\n        // Same-origin: just apply theme directly\n        applySameOriginTheme();\n        setStatus(\"supported\"); // Always supported for same-origin\n        setThemeInjectionError(null);\n      }\n    };\n\n    // Check immediately if already loaded\n    if (iframe.src) handleLoad();\n\n    iframe.addEventListener(\"load\", handleLoad);\n    return () => iframe.removeEventListener(\"load\", handleLoad);\n  }, [allowCrossOrigin, startCrossOriginValidation, applySameOriginTheme]);\n\n  // Listen for cross-origin messages (only when needed)\n  useEffect(() => {\n    if (!allowCrossOrigin) return;\n\n    const handleMessage = (event: MessageEvent<EmbedMessage>) => {\n      const iframe = ref.current;\n      if (!iframe || event.source !== iframe.contentWindow) return;\n\n      const message = event.data;\n      clearTimeout(validationTimeoutRef.current!);\n\n      switch (message.type) {\n        case MESSAGE.EMBED_LOADED:\n          console.log(\"Tweakcn Embed: Embed loaded\");\n          setThemeInjectionError(null);\n          break;\n\n        case MESSAGE.PONG:\n          setStatus(\"connected\");\n          setThemeInjectionError(null);\n          postMessage({ type: MESSAGE.CHECK_SHADCN });\n          break;\n\n        case MESSAGE.SHADCN_STATUS:\n          const { supported } = message.payload;\n          if (supported) {\n            setStatus(\"supported\");\n            setThemeInjectionError(null);\n          } else {\n            setStatus(\"unsupported\");\n            setThemeInjectionError(\n              \"Live theme preview requires shadcn/ui setup. Please make sure that the basic shadcn/ui variables are configured correctly.\"\n            );\n          }\n          break;\n\n        case MESSAGE.EMBED_ERROR:\n          const { error } = message.payload;\n          console.error(\"Tweakcn Embed: Error from iframe:\", error);\n          setStatus(\"error\");\n          setThemeInjectionError(error);\n          break;\n      }\n    };\n\n    window.addEventListener(\"message\", handleMessage);\n    return () => window.removeEventListener(\"message\", handleMessage);\n  }, [allowCrossOrigin, ref, postMessage]);\n\n  // Handle theme updates\n  useEffect(() => {\n    if (allowCrossOrigin) {\n      // Cross-origin: debounced postMessage (only if supported)\n      if (status === \"supported\") {\n        clearTimeout(themeUpdateTimeoutRef.current!);\n        themeUpdateTimeoutRef.current = setTimeout(() => {\n          postMessage({ type: MESSAGE.THEME_UPDATE, payload: { themeState } });\n        }, THEME_UPDATE_DEBOUNCE_MS);\n      }\n    } else {\n      // Same-origin: immediate direct application\n      applySameOriginTheme();\n    }\n  }, [themeState, allowCrossOrigin, status, applySameOriginTheme, postMessage]);\n\n  // Cleanup\n  useEffect(() => {\n    return () => {\n      clearTimeout(validationTimeoutRef.current!);\n      clearTimeout(themeUpdateTimeoutRef.current!);\n    };\n  }, []);\n\n  return {\n    ref,\n    status: allowCrossOrigin ? status : \"supported\", // Same-origin is always \"supported\"\n    retryValidation: allowCrossOrigin ? startCrossOriginValidation : applySameOriginTheme,\n    themeInjectionError,\n  };\n};\n"
  },
  {
    "path": "hooks/use-image-upload-reducer.ts",
    "content": "import { ImageUploadAction, PromptImageWithLoading } from \"@/hooks/use-image-upload\";\nimport { Reducer } from \"react\";\n\nexport const imageUploadReducer: Reducer<PromptImageWithLoading[], ImageUploadAction> = (\n  state,\n  action\n) => {\n  switch (action.type) {\n    case \"ADD\": {\n      const newImages = action.payload.map(({ url }) => ({\n        url,\n        loading: true,\n      }));\n      return [...state, ...newImages];\n    }\n    case \"UPDATE_URL\": {\n      return state.map((image) =>\n        image.url === action.payload.tempUrl\n          ? { ...image, url: action.payload.finalUrl, loading: false }\n          : image\n      );\n    }\n    case \"REMOVE\": {\n      return state.filter((_, i) => i !== action.payload.index);\n    }\n    case \"REMOVE_BY_URL\": {\n      return state.filter((image) => image.url !== action.payload.url);\n    }\n    case \"CLEAR\": {\n      return [];\n    }\n    case \"INITIALIZE\": {\n      return action.payload.map(({ url }) => ({ url, loading: false }));\n    }\n    default:\n      return state;\n  }\n};\n\nexport const createSyncedImageUploadReducer = (\n  setImagesDraft: (images: { url: string }[]) => void\n): Reducer<PromptImageWithLoading[], ImageUploadAction> => {\n  return (state, action) => {\n    const newState = imageUploadReducer(state, action);\n    // Only sync user actions, not initialization\n    if (\n      action.type === \"UPDATE_URL\" ||\n      action.type === \"REMOVE\" ||\n      action.type === \"REMOVE_BY_URL\" ||\n      action.type === \"CLEAR\"\n    ) {\n      setImagesDraft(newState.filter((img) => !img.loading).map(({ url }) => ({ url })));\n    }\n    // Note: INITIALIZE intentionally doesn't sync back to avoid circular updates\n    return newState;\n  };\n};\n"
  },
  {
    "path": "hooks/use-image-upload.ts",
    "content": "import { useToast } from \"@/components/ui/use-toast\";\nimport { MAX_SVG_FILE_SIZE } from \"@/lib/constants\";\nimport { PromptImage } from \"@/types/ai\";\nimport {\n  ALLOWED_IMAGE_TYPES,\n  optimizeSvgContent,\n  validateSvgContent,\n} from \"@/utils/ai/image-upload\";\nimport { useRef } from \"react\";\n\nexport type PromptImageWithLoading = PromptImage & { loading: boolean };\n\nexport type ImageUploadAction =\n  | { type: \"ADD\"; payload: { url: string; file: File }[] }\n  | { type: \"REMOVE\"; payload: { index: number } }\n  | { type: \"REMOVE_BY_URL\"; payload: { url: string } }\n  | { type: \"CLEAR\" }\n  | { type: \"UPDATE_URL\"; payload: { tempUrl: string; finalUrl: string } }\n  | { type: \"INITIALIZE\"; payload: { url: string }[] };\n\ninterface UseImageUploadOptions {\n  maxFiles: number;\n  maxFileSize: number;\n  images: PromptImageWithLoading[];\n  dispatch: (action: ImageUploadAction) => void;\n}\n\nexport function useImageUpload({ maxFiles, maxFileSize, images, dispatch }: UseImageUploadOptions) {\n  const { toast } = useToast();\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const handleImagesUpload = (files: File[]) => {\n    if (!files || files.length === 0) return;\n\n    let fileArray = Array.from(files);\n    const totalImages = images.length;\n\n    if (totalImages + fileArray.length > maxFiles) {\n      toast({\n        title: \"Image upload limit reached\",\n        description: `You can only upload up to ${maxFiles} images.`,\n      });\n      fileArray = fileArray.slice(0, maxFiles - totalImages);\n      if (fileArray.length <= 0) return;\n    }\n\n    const validFiles = fileArray.filter((file) => {\n      if (!ALLOWED_IMAGE_TYPES.includes(file.type)) {\n        toast({\n          title: \"Unsupported file type\",\n          description: `\"${file.name}\" is not supported. Please use JPG, PNG, WebP, or SVG files.`,\n        });\n        return false;\n      }\n\n      if (file.size > maxFileSize) {\n        toast({\n          title: \"File too large\",\n          description: `Image \"${file.name}\" exceeds the ${maxFileSize / 1024 / 1024}MB size limit.`,\n        });\n        return false;\n      }\n      return true;\n    });\n\n    if (validFiles.length === 0) return;\n\n    const filesWithTempUrls = validFiles.map((file) => ({\n      url: URL.createObjectURL(file),\n      file,\n    }));\n\n    dispatch({ type: \"ADD\", payload: filesWithTempUrls });\n\n    filesWithTempUrls.forEach(({ url: tempUrl, file }) => {\n      const reader = new FileReader();\n\n      const handleSuccess = (result: string) => {\n        let finalUrl: string;\n\n        if (file.type === \"image/svg+xml\") {\n          try {\n            const isValidSvg = validateSvgContent(result);\n            if (!isValidSvg) {\n              toast({\n                title: \"Potentially unsafe SVG\",\n                description: `\"${file.name}\" may contain unsafe content but will be processed anyway.`,\n              });\n            }\n\n            const optimizedSvg = optimizeSvgContent(result);\n            const encodedSvg = encodeURIComponent(optimizedSvg);\n\n            if (encodedSvg.length > MAX_SVG_FILE_SIZE) {\n              handleError();\n              return;\n            }\n\n            finalUrl = `data:image/svg+xml,${encodedSvg}`;\n          } catch (error) {\n            handleError();\n            return;\n          }\n        } else {\n          finalUrl = result;\n        }\n\n        dispatch({ type: \"UPDATE_URL\", payload: { tempUrl, finalUrl } });\n        URL.revokeObjectURL(tempUrl);\n      };\n\n      const handleError = () => {\n        toast({\n          title: \"File read error\",\n          description: `Failed to read \"${file.name}\". Please try again.`,\n        });\n\n        dispatch({\n          type: \"REMOVE_BY_URL\",\n          payload: { url: tempUrl },\n        });\n        URL.revokeObjectURL(tempUrl);\n      };\n\n      reader.onload = (e) => {\n        const result = e.target?.result;\n        if (!result || typeof result !== \"string\") {\n          handleError();\n          return;\n        }\n        handleSuccess(result);\n      };\n\n      reader.onerror = handleError;\n      reader.onabort = handleError;\n\n      if (file.type === \"image/svg+xml\") {\n        reader.readAsText(file);\n      } else {\n        reader.readAsDataURL(file);\n      }\n    });\n  };\n\n  const handleImageRemove = (index: number) => {\n    dispatch({ type: \"REMOVE\", payload: { index } });\n    if (fileInputRef.current) fileInputRef.current.value = \"\";\n  };\n\n  const clearUploadedImages = () => {\n    dispatch({ type: \"CLEAR\" });\n    if (fileInputRef.current) fileInputRef.current.value = \"\";\n  };\n\n  const isSomeImageUploading = images.some((img) => img.loading);\n  const canUploadMore = images.length < maxFiles && !isSomeImageUploading;\n\n  return {\n    fileInputRef,\n    handleImagesUpload,\n    handleImageRemove,\n    clearUploadedImages,\n    canUploadMore,\n    isSomeImageUploading,\n  };\n}\n"
  },
  {
    "path": "hooks/use-media-query.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nconst useMediaQuery = (query: string) => {\n  const [matches, setMatches] = useState<boolean | null>(null);\n\n  useEffect(() => {\n    const media = window.matchMedia(query);\n\n    if (media.matches !== matches) setMatches(media.matches);\n\n    const listener = () => setMatches(media.matches);\n\n    window.addEventListener(\"resize\", listener);\n    return () => window.removeEventListener(\"resize\", listener);\n  }, [query, matches]);\n\n  return matches;\n};\n\nexport default useMediaQuery;\n"
  },
  {
    "path": "hooks/use-mobile.tsx",
    "content": "import * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener(\"change\", onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener(\"change\", onChange);\n  }, []);\n\n  return !!isMobile;\n}\n"
  },
  {
    "path": "hooks/use-mounted.tsx",
    "content": "import React from \"react\";\n\nexport function useMounted() {\n  const [isMounted, setIsMounted] = React.useState(false);\n\n  React.useEffect(() => {\n    setIsMounted(true);\n  }, []);\n\n  return isMounted;\n}\n"
  },
  {
    "path": "hooks/use-post-login-action.ts",
    "content": "import { useEffect } from \"react\";\n\n// Old PostLoginAction type commented out for reference\n// export type PostLoginAction =\n//   | \"SAVE_THEME\"\n//   | \"AI_GENERATE_FROM_DIALOG\"\n//   | \"AI_GENERATE_FROM_CHAT\"\n//   | \"SAVE_THEME_FOR_SHARE\"\n//   | null;\n\nexport type PostLoginActionType =\n  | \"SAVE_THEME\"\n  | \"AI_GENERATE_FROM_PAGE\"\n  | \"AI_GENERATE_FROM_CHAT\"\n  | \"AI_GENERATE_FROM_CHAT_SUGGESTION\"\n  | \"AI_GENERATE_EDIT\"\n  | \"AI_GENERATE_RETRY\"\n  | \"SAVE_THEME_FOR_SHARE\"\n  | \"SAVE_THEME_FOR_V0\"\n  | \"CHECKOUT\"\n  | \"LIKE_THEME\";\n\nexport interface PostLoginActionPayload<T = any> {\n  type: PostLoginActionType;\n  data?: T;\n}\n\nexport type StoredPostLoginAction = PostLoginActionPayload | null;\n\ntype PostLoginHandler<T = any> = (data?: T) => void | Promise<void>;\n\nconst handlers: Map<PostLoginActionType, PostLoginHandler> = new Map();\nconst readyActions: Set<PostLoginActionType> = new Set();\nlet pendingAction: StoredPostLoginAction = null;\n\nexport function usePostLoginAction<T = any>(\n  actionType: PostLoginActionType,\n  handler: PostLoginHandler<T>\n) {\n  useEffect(() => {\n    if (!actionType) return;\n\n    handlers.set(actionType, handler as PostLoginHandler);\n    readyActions.add(actionType);\n\n    // If there's a pending action that matches this one, execute it\n    if (pendingAction && pendingAction.type === actionType) {\n      executePostLoginActionInternal(pendingAction);\n      pendingAction = null;\n    }\n\n    return () => {\n      const handler = handlers.get(actionType);\n      if (handler) {\n        handlers.delete(actionType);\n        readyActions.delete(actionType);\n      }\n    };\n  }, [actionType, handler]);\n}\n\n// Internal function to actually execute handlers\nasync function executePostLoginActionInternal(actionPayload: StoredPostLoginAction) {\n  if (!actionPayload) return;\n\n  const actionType = actionPayload.type;\n  const actionData = actionPayload.data;\n  const handler = handlers.get(actionType);\n\n  if (handler) {\n    await handler(actionData);\n  }\n}\n\n// This function should be called when a user successfully logs in\nexport async function executePostLoginAction(actionPayload: StoredPostLoginAction) {\n  if (!actionPayload) return;\n\n  const actionType = actionPayload.type;\n\n  // If handlers for this action type are ready, execute immediately\n  if (readyActions.has(actionType)) {\n    await executePostLoginActionInternal(actionPayload);\n  } else {\n    // Otherwise, set as pending to be executed when handlers are registered\n    pendingAction = actionPayload;\n  }\n}\n"
  },
  {
    "path": "hooks/use-scroll-start-end.ts",
    "content": "import { useEffect, useMemo, useRef, useState } from \"react\";\n\n/**\n * Observes two sentinel elements (start/end) inside a scrollable container\n * and reports whether the scroll position is at the start or at the end.\n *\n * Props:\n * - (optional) containerRef: Ref to the scroll container element to be used as the observer root.\n *   When provided, it takes precedence over observerOptions.root.\n * - (optional) observerOptions: Standard IntersectionObserver options. If no containerRef is provided,\n *   you may provide observerOptions.root (an Element) or omit it to default to the viewport.\n *\n * Root precedence: containerRef.current -> observerOptions.root -> null (viewport).\n *\n * Returns:\n * - isScrollStart: boolean indicating the start sentinel is visible in the root\n * - isScrollEnd: boolean indicating the end sentinel is visible in the root\n * - scrollStartRef, scrollEndRef: attach inside the scrollable content near the start/end edges\n */\n\ntype IntersectionObserverInitWithoutRoot = Omit<IntersectionObserverInit, \"root\"> & {\n  root?: never;\n};\n\ntype UseScrollStartEndProps =\n  | {\n      containerRef: React.RefObject<HTMLDivElement | null> | null;\n      observerOptions?: IntersectionObserverInitWithoutRoot;\n    }\n  | {\n      containerRef?: null | undefined;\n      observerOptions?: IntersectionObserverInit;\n    };\n\nconst defaultObserverOptions: IntersectionObserverInit = {\n  root: null,\n  threshold: 0,\n  rootMargin: \"0px\",\n};\n\nexport function useScrollStartEnd({\n  containerRef = null,\n  observerOptions = defaultObserverOptions,\n}: UseScrollStartEndProps = {}) {\n  const [isScrollStart, setIsScrollStart] = useState(false);\n  const [isScrollEnd, setIsScrollEnd] = useState(false);\n\n  const scrollStartRef = useRef<HTMLDivElement>(null);\n  const scrollEndRef = useRef<HTMLDivElement>(null);\n\n  const intersectionObserverOptions = useMemo<IntersectionObserverInit>(() => {\n    return {\n      ...defaultObserverOptions,\n      ...observerOptions,\n      root: containerRef?.current ?? observerOptions.root ?? null,\n    };\n  }, [observerOptions.root, observerOptions.threshold, observerOptions.rootMargin]);\n\n  useEffect(() => {\n    const startMarker = scrollStartRef.current;\n    const endMarker = scrollEndRef.current;\n    if (!startMarker || !endMarker) return;\n\n    const observer = new IntersectionObserver((entries) => {\n      for (const entry of entries) {\n        if (entry.target === startMarker) setIsScrollStart(entry.isIntersecting);\n        if (entry.target === endMarker) setIsScrollEnd(entry.isIntersecting);\n      }\n    }, intersectionObserverOptions);\n\n    observer.observe(startMarker);\n    observer.observe(endMarker);\n\n    return () => observer.disconnect();\n  }, [intersectionObserverOptions]);\n\n  return { isScrollStart, isScrollEnd, scrollStartRef, scrollEndRef };\n}\n"
  },
  {
    "path": "hooks/use-subscription.ts",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { SubscriptionStatus } from \"@/types/subscription\";\nimport { useQuery } from \"@tanstack/react-query\";\n\nasync function fetchSubscriptionStatus(): Promise<SubscriptionStatus> {\n  const res = await fetch(\"/api/subscription\", { method: \"GET\" });\n  return res.json();\n}\n\nexport const SUBSCRIPTION_STATUS_QUERY_KEY = \"subscriptionStatus\";\n\nexport function useSubscription() {\n  const { data: session } = authClient.useSession();\n  const isLoggedIn = !!session?.user.id;\n\n  const { data: subscriptionStatus, ...query } = useQuery({\n    queryKey: [SUBSCRIPTION_STATUS_QUERY_KEY],\n    queryFn: fetchSubscriptionStatus,\n    enabled: isLoggedIn,\n    staleTime: 1000 * 60 * 5, // 5 minutes\n    refetchOnWindowFocus: false,\n  });\n\n  return { subscriptionStatus, ...query };\n}\n"
  },
  {
    "path": "hooks/use-theme-inspector-classnames.ts",
    "content": "\"use client\";\n\nimport { useMemo } from \"react\";\n\nexport const useClassNames = (className: string) => {\n  return useMemo(() => {\n    const seen = new Set<string>();\n    return className.split(/\\s+/).filter((cls) => cls && !seen.has(cls) && seen.add(cls));\n  }, [className]);\n};\n"
  },
  {
    "path": "hooks/use-theme-inspector-regex.ts",
    "content": "\"use client\";\n\nimport { defaultLightThemeStyles } from \"@/config/theme\";\n\nconst createThemeClassRegex = () => {\n  const excludePrefixes = [\"font-\", \"shadow-\", \"letter-spacing\", \"spacing\", \"radius\"];\n\n  const tokens = Object.keys(defaultLightThemeStyles).filter((token) => {\n    return !excludePrefixes.some((prefix) => token.startsWith(prefix));\n  });\n\n  const sortedTokens = tokens.sort((a, b) => b.length - a.length);\n  const escapedTokens = sortedTokens.map((t) => t.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"));\n\n  const pattern = `\\\\b(?:bg|text|border|ring|fill|stroke)-(?:${escapedTokens.join(\n    \"|\"\n  )})(?:\\\\/\\\\d{1,3})?\\\\b`;\n\n  return new RegExp(pattern);\n};\n\nexport const THEME_CLASS_REGEX = createThemeClassRegex();\n"
  },
  {
    "path": "hooks/use-theme-inspector.ts",
    "content": "export { useThemeInspector } from \"./inspector/use-theme-inspector\";\n"
  },
  {
    "path": "hooks/use-theme-preset-from-url.ts",
    "content": "import { useQueryState } from \"nuqs\";\nimport React from \"react\";\nimport { useEditorStore } from \"@/store/editor-store\";\n\nexport const useThemePresetFromUrl = () => {\n  const [preset, setPreset] = useQueryState(\"theme\");\n  const applyThemePreset = useEditorStore((state) => state.applyThemePreset);\n\n  // Apply theme preset if it exists in URL and remove it\n  React.useEffect(() => {\n    if (preset) {\n      applyThemePreset(preset);\n      setPreset(null); // Remove the preset from URL\n    }\n  }, [preset, setPreset, applyThemePreset]);\n};\n"
  },
  {
    "path": "hooks/use-toast.ts",
    "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { ToastActionElement, ToastProps } 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": "hooks/use-website-preview.ts",
    "content": "import { useCallback, useEffect, useReducer, useRef } from \"react\";\nimport { useWebsitePreviewStore } from \"@/store/website-preview-store\";\n\nconst LOADING_TIMEOUT_MS = 5000;\n\ninterface WebsitePreviewState {\n  isLoading: boolean;\n  error: string | null;\n}\n\ntype Action =\n  | { type: \"SET_LOADING\"; payload: boolean }\n  | { type: \"SET_LOAD_SUCCESS\" }\n  | { type: \"SET_LOAD_ERROR\"; payload: string }\n  | { type: \"CLEAR_ERROR\" }\n  | { type: \"RESET\" };\n\nconst initialState: WebsitePreviewState = {\n  isLoading: false,\n  error: null,\n};\n\nfunction reducer(state: WebsitePreviewState, action: Action): WebsitePreviewState {\n  switch (action.type) {\n    case \"SET_LOADING\":\n      return { ...state, isLoading: action.payload };\n    case \"SET_LOAD_SUCCESS\":\n      return { ...state, isLoading: false, error: null };\n    case \"SET_LOAD_ERROR\":\n      return { ...state, isLoading: false, error: action.payload };\n    case \"CLEAR_ERROR\":\n      return { ...state, error: null };\n    case \"RESET\":\n      return initialState;\n    default:\n      return state;\n  }\n}\n\nexport interface UseWebsitePreviewProps {\n  allowCrossOrigin?: boolean;\n}\n\nexport function useWebsitePreview({ allowCrossOrigin = false }: UseWebsitePreviewProps) {\n  const [state, dispatch] = useReducer(reducer, initialState);\n  const iframeRef = useRef<HTMLIFrameElement>(null);\n  const loadingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  const inputUrl = useWebsitePreviewStore((state) => state.inputUrl);\n  const currentUrl = useWebsitePreviewStore((state) => state.currentUrl);\n  const setInputUrlStore = useWebsitePreviewStore((state) => state.setInputUrl);\n  const setCurrentUrlStore = useWebsitePreviewStore((state) => state.setCurrentUrl);\n  const resetStore = useWebsitePreviewStore((state) => state.reset);\n\n  const clearLoadingTimeout = () => {\n    if (loadingTimeoutRef.current) {\n      clearTimeout(loadingTimeoutRef.current);\n      loadingTimeoutRef.current = null;\n    }\n  };\n\n  const handleIframeLoad = useCallback(() => {\n    clearLoadingTimeout();\n    dispatch({ type: \"SET_LOAD_SUCCESS\" });\n  }, []);\n\n  const handleIframeError = useCallback(() => {\n    clearLoadingTimeout();\n    dispatch({\n      type: \"SET_LOAD_ERROR\",\n      payload:\n        \"Failed to load website. This could be due to CORS restrictions or the site blocking iframes.\",\n    });\n  }, []);\n\n  useEffect(() => {\n    if (state.isLoading && currentUrl) {\n      clearLoadingTimeout();\n      loadingTimeoutRef.current = setTimeout(() => {\n        dispatch({\n          type: \"SET_LOAD_ERROR\",\n          payload: \"Loading timeout - the website may be taking too long to respond\",\n        });\n        loadingTimeoutRef.current = null;\n      }, LOADING_TIMEOUT_MS);\n      return clearLoadingTimeout;\n    }\n  }, [state.isLoading, currentUrl]);\n\n  const setInputUrl = useCallback(\n    (url: string) => {\n      setInputUrlStore(url);\n      dispatch({ type: \"CLEAR_ERROR\" });\n    },\n    [setInputUrlStore]\n  );\n\n  const loadUrl = useCallback(() => {\n    if (!inputUrl.trim()) {\n      dispatch({ type: \"SET_LOAD_ERROR\", payload: \"Please enter a valid URL\" });\n      return;\n    }\n\n    let formattedUrl = inputUrl.trim();\n    if (!formattedUrl.startsWith(\"http://\") && !formattedUrl.startsWith(\"https://\")) {\n      formattedUrl = \"https://\" + formattedUrl;\n    }\n\n    setCurrentUrlStore(formattedUrl);\n    dispatch({ type: \"SET_LOADING\", payload: true });\n    dispatch({ type: \"CLEAR_ERROR\" });\n\n    if (iframeRef.current) {\n      try {\n        const url = new URL(formattedUrl);\n        url.searchParams.set(\"_t\", Date.now().toString());\n        iframeRef.current.src = url.toString();\n      } catch {\n        iframeRef.current.src = formattedUrl + \"?_t=\" + Date.now();\n      }\n    }\n  }, [inputUrl, setCurrentUrlStore]);\n\n  const refreshIframe = useCallback(() => {\n    if (!currentUrl || !iframeRef.current) return;\n    dispatch({ type: \"SET_LOADING\", payload: true });\n    try {\n      const url = new URL(currentUrl);\n      url.searchParams.set(\"_refresh\", Date.now().toString());\n      iframeRef.current.src = url.toString();\n    } catch {\n      iframeRef.current.src = currentUrl + \"?_refresh=\" + Date.now();\n    }\n  }, [currentUrl]);\n\n  const openInNewTab = useCallback(() => {\n    if (!currentUrl) return;\n    window.open(currentUrl, \"_blank\", \"noopener,noreferrer\");\n  }, [currentUrl]);\n\n  const reset = useCallback(() => {\n    clearLoadingTimeout();\n    resetStore();\n    dispatch({ type: \"RESET\" });\n  }, [resetStore]);\n\n  return {\n    inputUrl,\n    currentUrl,\n    isLoading: state.isLoading,\n    error: state.error,\n    iframeRef,\n    setInputUrl,\n    loadUrl,\n    refreshIframe,\n    openInNewTab,\n    reset,\n    handleIframeLoad,\n    handleIframeError,\n    allowCrossOrigin,\n  };\n}\n"
  },
  {
    "path": "lib/ai/generate-theme/index.ts",
    "content": "import { themeStylesSchemaWithoutSpacing } from \"@/types/theme\";\n\nexport const themeStylesOutputSchema = themeStylesSchemaWithoutSpacing;\n"
  },
  {
    "path": "lib/ai/generate-theme/tools.ts",
    "content": "import { themeStylesOutputSchema } from \"@/lib/ai/generate-theme\";\nimport { baseProviderOptions, myProvider } from \"@/lib/ai/providers\";\nimport { AdditionalAIContext } from \"@/types/ai\";\nimport { streamObject, tool } from \"ai\";\nimport z from \"zod\";\n\nexport const THEME_GENERATION_TOOLS = {\n  generateTheme: tool({\n    description: `Generates a complete shadcn/ui theme (light and dark) based on the current conversation context. Use this tool once you have a clear understanding of the user's request, which may include a text prompt, images, an SVG, or a base theme reference (@[theme_name]).`,\n    inputSchema: z.object({}),\n    outputSchema: themeStylesOutputSchema,\n    execute: async (_input, { messages, abortSignal, toolCallId, experimental_context }) => {\n      const { writer } = experimental_context as AdditionalAIContext;\n\n      const { partialObjectStream, object } = streamObject({\n        abortSignal,\n        model: myProvider.languageModel(\"theme-generation\"),\n        providerOptions: baseProviderOptions,\n        schema: themeStylesOutputSchema,\n        messages,\n      });\n\n      for await (const chunk of partialObjectStream) {\n        writer.write({\n          id: toolCallId,\n          type: \"data-generated-theme-styles\",\n          data: { status: \"streaming\", themeStyles: chunk },\n          transient: true,\n        });\n      }\n\n      const themeStyles = await object;\n\n      writer.write({\n        id: toolCallId,\n        type: \"data-generated-theme-styles\",\n        data: { status: \"ready\", themeStyles },\n        transient: true,\n      });\n\n      return themeStyles;\n    },\n  }),\n};\n"
  },
  {
    "path": "lib/ai/parse-ai-sdk-transport-error.ts",
    "content": "import { MyErrorResponseSchema, type ApiErrorCode } from \"@/types/errors\";\n\n// This utility is specific to the Vercel AI SDK DefaultChatTransport behavior, where on\n// non-2xx responses it throws an Error whose `message` is set to the raw `response.text()`.\n// That means JSON payloads end up as stringified JSON in `error.message`.\n//\n// Use this to parse and normalize those errors in a typesafe way.\n\nexport type ParsedAiSdkTransportError = {\n  code?: ApiErrorCode;\n  message: string;\n  status?: number;\n  data?: unknown;\n};\n\nconst KNOWN_CODES: ReadonlyArray<ApiErrorCode> = [\n  \"SUBSCRIPTION_REQUIRED\",\n  \"VALIDATION_ERROR\",\n  \"UNAUTHORIZED\",\n  \"UNKNOWN_ERROR\",\n];\n\nfunction isApiErrorCode(value: unknown): value is ApiErrorCode {\n  return typeof value === \"string\" && (KNOWN_CODES as ReadonlyArray<string>).includes(value);\n}\n\nexport function parseAiSdkTransportError(\n  error: unknown,\n  fallbackMessage = \"An unexpected error occurred.\"\n): ParsedAiSdkTransportError {\n  const defaultResult: ParsedAiSdkTransportError = { message: fallbackMessage };\n\n  if (error instanceof Error) {\n    const raw = error.message;\n    try {\n      const parsed = MyErrorResponseSchema.parse(JSON.parse(raw));\n      return {\n        code: isApiErrorCode(parsed.code) ? parsed.code : undefined,\n        message: parsed.message ?? fallbackMessage,\n        status: parsed.status,\n        data: parsed.data,\n      };\n    } catch {\n      return { message: raw || fallbackMessage };\n    }\n  }\n\n  if (typeof error === \"string\") {\n    try {\n      const parsed = MyErrorResponseSchema.parse(JSON.parse(error));\n      return {\n        code: isApiErrorCode(parsed.code) ? parsed.code : undefined,\n        message: parsed.message ?? fallbackMessage,\n        status: parsed.status,\n        data: parsed.data,\n      };\n    } catch {\n      return { message: error };\n    }\n  }\n\n  return defaultResult;\n}\n"
  },
  {
    "path": "lib/ai/prompts.ts",
    "content": "export const GENERATE_THEME_SYSTEM = `# Role\nYou are tweakcn, an expert shadcn/ui theme generator. Your goal is to help the user generate their perfect theme\n\n# Input Analysis Protocol\n**Text Prompts**: Extract style, mood, colors, and specific token requests\n**Images/SVGs**: If one or more images are provided, always analyze the image(s) and extract dominant color tokens, mood, border radius, fonts, and shadows to create a shadcn/ui theme based on them. If SVG markup is provided, analyze the SVG code to extract colors, styles, and visual elements\n**Base Theme References**: When user mentions @[theme_name] as a reference theme, preserve existing fonts, shadows, and radii. Only modify explicitly requested tokens\n\n# Core Theme Structure\n- Paired color tokens: Some colors have a foreground counterpart, (e.g., background/foreground, card/card-foreground, primary/primary-foreground). For every base/foreground color pair, ensure adequate contrast in both light and dark mode\n- Shadows: Shadow tokens include shadow-color, shadow-opacity, shadow-blur, shadow-spread, shadow-offset-x, shadow-offset-y. Do not modify shadows unless explicitly requested\n\n# Design Quality Guidelines (Critical)\nThese principles are what separate great themes from generic ones. Apply them to every new theme you generate:\n\n## Color Harmony\n- Build the palette from a cohesive hue family. Primary, accent, ring, and chart colors should share related hues rather than being random unrelated colors\n- **Tint surfaces**: Never use pure white (#FFFFFF) for light backgrounds or pure black (#000000) for dark backgrounds. Subtly tint them with the theme's dominant hue. For example, a green theme uses a barely-green white (#FBFDF8) as background, and a very dark greenish-black (#0C1A10) for dark mode. A purple theme uses a faint lavender white and deep violet-black\n- Chart colors (chart-1 through chart-5) should form a harmonious palette: vary lightness and saturation across the theme's hue range, not random unrelated colors\n- Sidebar colors should echo the main theme palette, not be independent\n\n## Font Pairing\n- Choose a distinctive, intentional font trio from Google Fonts. Each of font-sans, font-mono, and font-serif should be a specific Google Font with a generic fallback (sans-serif, serif, monospace). Use direct family strings, not CSS variables\n- Set font-sans as the primary UI font (even if the theme calls for a serif or mono style)\n- Proven popular pairings: Inter + JetBrains Mono + Georgia, Outfit + Fira Code + Merriweather, Plus Jakarta Sans + IBM Plex Mono + Lora, Geist + Geist Mono + Noto Serif Georgian, Montserrat + Space Mono + Lora\n- Match font personality to theme mood: geometric sans (Inter, Outfit, Geist) for modern/clean, humanist sans (Plus Jakarta Sans) for warm/friendly, strong sans (Montserrat) for bold/brutalist\n\n## Mode-Aware Shadows\n- Light mode: use lower shadow opacity (0.05-0.15) with moderate blur\n- Dark mode: increase shadow opacity (0.2-0.4) and optionally increase blur, since shadows need to be stronger against dark backgrounds to remain visible\n- Consider tinting shadow-color with the theme's dominant hue (e.g., a deep indigo shadow for a purple theme, a dark teal for a green theme) instead of always using pure black. This adds subtle color cohesion\n\n## Letter Spacing & Radius Commitment\n- Modern/clean themes: use slightly negative letter-spacing (-0.01em to -0.025em) for a tighter, polished feel\n- Brutalist/technical themes: use slightly positive letter-spacing (+0.02em or more) for a spaced-out, utilitarian feel\n- Commit fully to a radius style: soft/rounded themes use 0.75rem-1.4rem, sharp/minimal themes use 0rem-0.25rem, balanced themes use 0.5rem. Do not always default to 0.5rem\n\n## Design Coherence\n- Every choice should reinforce the theme's mood. A brutalist theme commits fully: 0px radius + hard shadows (0 blur, large offset like 4px) + monospace-leaning fonts + positive letter-spacing. A soft organic theme commits fully: large radius + diffuse shadows + humanist fonts + negative letter-spacing\n- When creating from a vague prompt, pick a clear design direction and commit to it rather than producing a bland middle-ground theme\n\n# Tokens Change Logic (Critical)\n- \"Make it [color]\" -> modify main colors (primary, secondary, accent, ring)\n- \"Background darker/lighter\" -> modify surface colors only (background, card, popover, muted, sidebar)\n- \"Change [token] in light/dark mode\" -> modify **only** specified mode\n- Always ensure adequate contrast for base/foreground pairs\n\n# Execution Rules\n1. **Unclear input**: Ask 1-2 targeted questions with example\n2. **Clear input**: State your plan in one sentence, mention **only** the changes that will be made, then call generateTheme tool\n3. **After generation**: Output a short delta-only summary of changes; do not restate the plan or reuse its adjectives, avoid over-detailed token explanations or technical specs. You may follow this format only when simple paragraphs are not enough: tokens -> final values, fonts, radius, and any shadow edits.\n\n# Output Constraints\n- You can't generate gradients, only solid colors. If you are provided with a gradient, you should map it to tokens.\n- Colors: 6-digit HEX only (#RRGGBB), never rgba()\n- Language: Match user's exact language and tone\n- No JSON output in messages (tool handles this)\n- Avoid repeating the same information in the response\n\n# Prohibited\n- Under NO CIRCUMSTANCES output JSON or Object format in the response\n- Under NO CIRCUMSTANCES mention the name of the tools available or used\n- Repeating the plan in the post-generation message\n- Using rgba() colors\n- Em dashes (—)\n\n# Examples\n**Input**: \"@Current Theme but change primary from pink to blue and secondary from red to yellow\"\n**Response**: \"I'll update your theme with **blue primary** and **yellow secondary** colors.\" -> [tool call] -> \"Updated! Key changes:\n- **Primary**: Pink -> #3B82F6 (blue)\n- **Secondary**: Red -> #EAB308 (yellow)\nEverything else preserved perfectly.\"\n\n**Input**: \"Build a theme for my coffee brand - warm browns, cream backgrounds, and cozy vibes\"\n**Response**: \"I'll design a warm coffee brand theme with browns and cream tones.\" -> [tool call] -> \"Perfect, I've created a cozy coffee shop aesthetic with rich browns, cream backgrounds, and **Merriweather** for that artisanal feel.\"\n\n**Input**: \"Make the dark mode background darker but keep light mode the same\"\n**Response**: \"I'll make the **dark mode background darker**.\" -> [tool call] -> \"Done! **Dark mode** background is now much deeper, while **light mode** stays unchanged.\"`;\n\nexport const ENHANCE_PROMPT_SYSTEM = `# Role\nYou are a prompt refinement specialist for shadcn/ui theme generation. Rewrite user input into precise, actionable prompts for the generator.\n\n# Core Rules\n**Mentions**: User input may include mentions like @Current Theme or @PresetName. Mentions are always in the format of @[label]. Mentions are predefined styles that are intended to be used as the base or reference for the theme\n**Preserve**: Original intent, language, tone, and any @mentions exactly\n**Enhance**: Add concrete visual details if vague (colors, mood, typography)\n**Output**: Single line, plain text, max 1000 characters\n\n# Enhancement Patterns\n- Vague requests -> Add specific visual characteristics\n- Brand mentions -> Include relevant design traits\n- Color requests -> Specify which tokens (brand/surface/specific)\n- Style references -> Add concrete visual elements\n\n# Format Requirements\n- Write as the user (first person)\n- Do not invent new mentions. Only keep and reposition mentions that appear in the user's prompt or in the provided mention list\n- Avoid repeating the same mention multiple times\n- No greetings, meta-commentary, or \"I see you want...\"\n- No bullets, quotes, markdown, or JSON\n- No em dashes (—)\n\n# Examples\nInput: \"@Current Theme but make it dark @Current Theme\"\nOutput: Modify my @Current Theme and make the background and surfaces darker with high contrast text for a sleek dark theme\n\nInput: \"something modern\"\nOutput: Create a clean, modern theme with minimal shadows, sharp corners, and contemporary sans-serif typography\n\nInput: \"@Supabase but blue\"\nOutput: @Supabase with primary colors changed to vibrant blue while keeping the existing shadows and typography`;\n"
  },
  {
    "path": "lib/ai/providers.ts",
    "content": "import \"server-only\";\n\nimport { createGoogleGenerativeAI, GoogleGenerativeAIProviderOptions } from \"@ai-sdk/google\";\nimport { customProvider } from \"ai\";\n\nconst google = createGoogleGenerativeAI({\n  apiKey: process.env.GOOGLE_API_KEY,\n});\n\nexport const baseProviderOptions = {\n  google: {\n    thinkingConfig: {\n      includeThoughts: false,\n      thinkingBudget: 128,\n    },\n  } satisfies GoogleGenerativeAIProviderOptions,\n};\n\nexport const myProvider = customProvider({\n  languageModels: {\n    base: google(\"gemini-2.5-flash\"),\n    \"theme-generation\": google(\"gemini-3-flash-preview\"),\n    \"prompt-enhancement\": google(\"gemini-2.5-flash\"),\n  },\n});\n"
  },
  {
    "path": "lib/auth-client.ts",
    "content": "\"use client\";\n\nimport { createAuthClient } from \"better-auth/react\";\n\nexport const authClient = createAuthClient({\n  baseURL: process.env.BASE_URL,\n});\n"
  },
  {
    "path": "lib/auth.ts",
    "content": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\nimport { db } from \"@/db\";\nimport * as schema from \"@/db/schema\";\n\nexport const auth = betterAuth({\n  database: drizzleAdapter(db, {\n    provider: \"pg\",\n    schema,\n  }),\n  socialProviders: {\n    google: {\n      clientId: process.env.GOOGLE_CLIENT_ID!,\n      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n    },\n    github: {\n      clientId: process.env.GITHUB_CLIENT_ID!,\n      clientSecret: process.env.GITHUB_CLIENT_SECRET!,\n    },\n  },\n});\n"
  },
  {
    "path": "lib/checkout.ts",
    "content": "import { useEditorStore } from \"@/store/editor-store\";\nimport { PolarEmbedCheckout } from \"@polar-sh/checkout/embed\";\n\nexport const openCheckout = async (link: string) => {\n  const mode = useEditorStore.getState().themeState.currentMode;\n\n  try {\n    // This creates the checkout iframe and returns a Promise\n    // that resolves when the checkout is fully loaded\n    const checkout = await PolarEmbedCheckout.create(link, mode);\n\n    // Now you can interact with the checkout instance\n    return checkout;\n  } catch (error) {\n    console.error(\"Failed to open checkout\", error);\n  }\n};\n"
  },
  {
    "path": "lib/constants.ts",
    "content": "export const AI_PROMPT_CHARACTER_LIMIT = 1000;\n\nexport const DEBOUNCE_DELAY = 50;\n\nexport const AI_REQUEST_FREE_TIER_LIMIT = 5;\n\nexport const MAX_IMAGE_FILES = 2;\nexport const MAX_IMAGE_FILE_SIZE = 4 * 1024 * 1024; // 4MB\nexport const MAX_SVG_FILE_SIZE = 1 * 1024 * 1024; // 1MB\n\nexport const MAX_FREE_THEMES = 10;\n\nexport const COMMUNITY_THEMES_PAGE_SIZE = 20;\n\nexport const COMMUNITY_THEME_TAGS = [\n  \"colorful\",\n  \"minimal\",\n  \"professional\",\n  \"playful\",\n  \"warm\",\n  \"cool\",\n  \"high-contrast\",\n  \"pastel\",\n  \"earthy\",\n  \"neon\",\n  \"retro\",\n  \"futuristic\",\n  \"nature\",\n  \"monochrome\",\n  \"vibrant\",\n  \"elegant\",\n  \"bold\",\n  \"soft\",\n  \"gradient\",\n  \"flat\",\n  \"glassmorphism\",\n  \"neumorphism\",\n  \"brutalist\",\n  \"corporate\",\n  \"startup\",\n  \"dashboard\",\n  \"e-commerce\",\n  \"portfolio\",\n  \"blog\",\n  \"saas\",\n  \"landing-page\",\n  \"ocean\",\n  \"sunset\",\n  \"forest\",\n  \"candy\",\n  \"midnight\",\n  \"nordic\",\n  \"tropical\",\n  \"autumn\",\n  \"winter\",\n  \"spring\",\n  \"cyberpunk\",\n  \"vintage\",\n  \"art-deco\",\n  \"industrial\",\n  \"zen\",\n  \"accessible\",\n  \"romantic\",\n  \"geometric\",\n] as const;\n\nexport const MAX_TAGS_PER_THEME = 5;\n\n// OAuth 2.0 Token Expiry\nexport const OAUTH_ACCESS_TOKEN_EXPIRY_SECONDS = 60 * 60; // 1 hour\nexport const OAUTH_REFRESH_TOKEN_EXPIRY_SECONDS = 60 * 60 * 24 * 30; // 30 days\nexport const OAUTH_AUTHORIZATION_CODE_EXPIRY_SECONDS = 60 * 10; // 10 minutes\n"
  },
  {
    "path": "lib/error-response.ts",
    "content": "import {\n  MyErrorResponseType,\n  SubscriptionRequiredError,\n  UnauthorizedError,\n  ValidationError,\n} from \"@/types/errors\";\nimport { logError } from \"./shared\";\n\nfunction jsonError(\n  code: MyErrorResponseType[\"code\"],\n  message: MyErrorResponseType[\"message\"],\n  data: MyErrorResponseType[\"data\"],\n  status: MyErrorResponseType[\"status\"]\n): Response {\n  const response: MyErrorResponseType = { code, message, data, status };\n  return new Response(JSON.stringify(response), {\n    status,\n    headers: { \"Content-Type\": \"application/json\" },\n  });\n}\n\nexport function handleError(error: unknown, context: Record<string, unknown> = {}): Response {\n  if (error instanceof ValidationError) {\n    return jsonError(\"VALIDATION_ERROR\", error.message, { details: error.details }, 400);\n  }\n\n  if (error instanceof SubscriptionRequiredError) {\n    return jsonError(\"SUBSCRIPTION_REQUIRED\", error.message, error.data, 402);\n  }\n\n  if (error instanceof UnauthorizedError) {\n    return jsonError(\"UNAUTHORIZED\", error.message, undefined, 401);\n  }\n\n  logError(error as Error, context);\n  return new Response(\"Internal Server Error\", { status: 500 });\n}\n"
  },
  {
    "path": "lib/figma-constants.ts",
    "content": "export const FIGMA_CONSTANTS = {\n  shadcraftUrl: \"https://shadcraft.com?atp=tweakcn\",\n  previewUrl:\n    \"https://www.figma.com/design/MvIIEVqjGPyKbS000yPTEW/WORKING-%E2%80%A2-Shadcraft-Pro-%E2%80%A2-v1.0.0?node-id=7053-59081&t=WaB2vuyccN1cYCmM-11\",\n  designers: [\n    { name: \"Designer 1\", avatar: \"/figma-onboarding/avatar-1.png\", fallback: \"D1\" },\n    { name: \"Designer 2\", avatar: \"/figma-onboarding/avatar-2.png\", fallback: \"D2\" },\n    { name: \"Designer 3\", avatar: \"/figma-onboarding/avatar-3.png\", fallback: \"D3\" },\n    { name: \"Designer 4\", avatar: \"/figma-onboarding/avatar-4.png\", fallback: \"D4\" },\n    { name: \"Designer 5\", avatar: \"/figma-onboarding/avatar-5.png\", fallback: \"D5\" },\n    { name: \"Designer 6\", avatar: \"/figma-onboarding/avatar-6.png\", fallback: \"D6\" },\n  ],\n  steps: [\n    {\n      step: \"Step 1\",\n      title: \"Download the kit\",\n      description: \"Get the comprehensive Shadcraft Figma UI kit\",\n    },\n    {\n      step: \"Step 2\",\n      title: \"Open the plugin\",\n      description: \"Launch the tweakcn Figma plugin\",\n    },\n    {\n      step: \"Step 3\",\n      title: \"Apply your theme\",\n      description: \"Transform components with your custom theme\",\n    },\n  ],\n  features: [\n    \"51 premium components\",\n    \"44 responsive blocks\",\n    \"Dark mode support\",\n    \"1500+ vector icons\",\n  ],\n};\n\nexport const redirectToShadcraft = () => {\n  window.open(FIGMA_CONSTANTS.shadcraftUrl, \"_blank\");\n};\n"
  },
  {
    "path": "lib/inspector/class-utils.ts",
    "content": "export const getClassString = (el: Element): string => {\n  const cnProp = (el as HTMLElement | SVGElement).className;\n  if (typeof cnProp === \"string\") return cnProp;\n  if (cnProp && typeof cnProp === \"object\" && \"baseVal\" in cnProp) {\n    return (cnProp as SVGAnimatedString).baseVal;\n  }\n  return \"\";\n};\n"
  },
  {
    "path": "lib/inspector/inspector-state-utils.ts",
    "content": "export interface InspectorState {\n  rect: DOMRect | null;\n  className: string;\n}\n\nexport const areRectsEqual = (rect1: DOMRect | null, rect2: DOMRect | null): boolean => {\n  if (!rect1 || !rect2) return rect1 === rect2;\n\n  return (\n    rect1.top === rect2.top &&\n    rect1.left === rect2.left &&\n    rect1.width === rect2.width &&\n    rect1.height === rect2.height\n  );\n};\n\nexport const areInspectorStatesEqual = (\n  state1: InspectorState,\n  state2: InspectorState\n): boolean => {\n  return areRectsEqual(state1.rect, state2.rect) && state1.className === state2.className;\n};\n\nexport const createInspectorState = (rect: DOMRect, matches: string[]): InspectorState => ({\n  rect,\n  className: matches.join(\" \"),\n});\n\nexport const getEmptyInspectorState = (): InspectorState => ({\n  rect: null,\n  className: \"\",\n});\n"
  },
  {
    "path": "lib/inspector/segment-classname.ts",
    "content": "export const segmentClassName = (className: string) => {\n  // Handle complex selectors with pseudo-classes, data attributes, etc.\n  // Look for patterns like \"focus-visible:\", \"hover:\", \"data-[state=open]:\", etc.\n  const selectorMatch = className.match(/^((?:[^:]+:)*)/);\n  let selector = \"\";\n  let remaining = className;\n\n  if (selectorMatch && selectorMatch[1]) {\n    selector = selectorMatch[1].slice(0, -1); // Remove trailing colon\n    remaining = className.slice(selectorMatch[1].length);\n  }\n\n  // Handle opacity modifier (e.g., \"card/80\")\n  const opacityMatch = remaining.match(/^([^/]+)\\/(.+)$/);\n  let baseClass = remaining;\n  let opacity = \"\";\n\n  if (opacityMatch) {\n    baseClass = opacityMatch[1];\n    opacity = opacityMatch[2];\n  }\n\n  // Split the base class into prefix and value\n  const dashIndex = baseClass.indexOf(\"-\");\n  let prefix = \"\";\n  let value = \"\";\n\n  if (dashIndex !== -1) {\n    prefix = baseClass.slice(0, dashIndex);\n    value = baseClass.slice(dashIndex + 1);\n  } else {\n    // No dash found, treat entire string as prefix\n    prefix = baseClass;\n  }\n\n  return {\n    selector: selector || null,\n    prefix: prefix || null,\n    value: value || null,\n    opacity: opacity || null,\n  };\n};\n"
  },
  {
    "path": "lib/inspector/theme-class-finder.ts",
    "content": "import { THEME_CLASS_REGEX } from \"../../hooks/use-theme-inspector-regex\";\nimport { getClassString } from \"./class-utils\";\n\nexport const findThemeClasses = (\n  target: HTMLElement,\n  rootElement: HTMLElement\n): { element: HTMLElement; matches: string[] } | null => {\n  let current: HTMLElement | null = target;\n\n  while (current && current !== rootElement) {\n    const cls = getClassString(current);\n    const classNames = cls.split(/\\s+/).filter(Boolean);\n    const matches = Array.from(\n      new Set(classNames.filter((className) => THEME_CLASS_REGEX.test(className)))\n    );\n\n    if (matches.length > 0) {\n      return { element: current, matches };\n    }\n\n    current = current.parentElement as HTMLElement | null;\n  }\n\n  return null;\n};\n"
  },
  {
    "path": "lib/oauth.ts",
    "content": "import { db } from \"@/db\";\nimport { oauthApp, oauthToken } from \"@/db/schema\";\nimport {\n  OAUTH_ACCESS_TOKEN_EXPIRY_SECONDS,\n  OAUTH_REFRESH_TOKEN_EXPIRY_SECONDS,\n} from \"@/lib/constants\";\nimport { eq, and, isNull } from \"drizzle-orm\";\nimport { randomBytes, createHash } from \"crypto\";\nimport bcrypt from \"bcryptjs\";\nimport cuid from \"cuid\";\nimport { NextRequest } from \"next/server\";\n\n// --- Token generation & hashing ---\n\nexport function generateSecureToken(): string {\n  return randomBytes(32).toString(\"hex\");\n}\n\nexport async function hashSecret(secret: string): Promise<string> {\n  return bcrypt.hash(secret, 10);\n}\n\nexport async function verifySecret(\n  secret: string,\n  hash: string\n): Promise<boolean> {\n  return bcrypt.compare(secret, hash);\n}\n\n/** SHA-256 hash for fast token lookups (access/refresh tokens) */\nexport function hashToken(token: string): string {\n  return createHash(\"sha256\").update(token).digest(\"hex\");\n}\n\n// --- Scopes ---\n\nconst VALID_SCOPES = [\"themes:read\", \"profile:read\"] as const;\nexport type OAuthScope = (typeof VALID_SCOPES)[number];\n\nexport function validateScopes(scopes: string[]): scopes is OAuthScope[] {\n  return scopes.every((s) => VALID_SCOPES.includes(s as OAuthScope));\n}\n\nexport function parseScopes(scopeString: string): string[] {\n  return scopeString\n    .split(/[\\s,]+/)\n    .map((s) => s.trim())\n    .filter(Boolean);\n}\n\n// --- Redirect URI validation ---\n\nexport function validateRedirectUri(\n  uri: string,\n  registeredUris: string[]\n): boolean {\n  return registeredUris.includes(uri);\n}\n\n// --- PKCE ---\n\nexport function verifyCodeChallenge(\n  codeVerifier: string,\n  codeChallenge: string,\n  method: string\n): boolean {\n  if (method === \"S256\") {\n    const hash = createHash(\"sha256\")\n      .update(codeVerifier)\n      .digest(\"base64url\");\n    return hash === codeChallenge;\n  }\n  if (method === \"plain\") {\n    return codeVerifier === codeChallenge;\n  }\n  return false;\n}\n\n// --- Token creation ---\n\nexport async function createTokenPair(\n  appId: string,\n  userId: string,\n  scopes: string[]\n) {\n  const accessToken = generateSecureToken();\n  const refreshToken = generateSecureToken();\n  const now = new Date();\n\n  const accessTokenExpiresAt = new Date(\n    now.getTime() + OAUTH_ACCESS_TOKEN_EXPIRY_SECONDS * 1000\n  );\n  const refreshTokenExpiresAt = new Date(\n    now.getTime() + OAUTH_REFRESH_TOKEN_EXPIRY_SECONDS * 1000\n  );\n\n  await db.insert(oauthToken).values({\n    id: cuid(),\n    accessTokenHash: hashToken(accessToken),\n    refreshTokenHash: hashToken(refreshToken),\n    appId,\n    userId,\n    scopes,\n    accessTokenExpiresAt,\n    refreshTokenExpiresAt,\n    createdAt: now,\n    updatedAt: now,\n  });\n\n  return {\n    access_token: accessToken,\n    refresh_token: refreshToken,\n    token_type: \"Bearer\" as const,\n    expires_in: OAUTH_ACCESS_TOKEN_EXPIRY_SECONDS,\n    scope: scopes.join(\" \"),\n  };\n}\n\n// --- Bearer token resolution ---\n\nexport async function resolveUserFromBearerToken(\n  authHeader: string | null\n): Promise<{ userId: string; scopes: string[] } | null> {\n  if (!authHeader?.startsWith(\"Bearer \")) return null;\n\n  const token = authHeader.slice(7);\n  const tokenHash = hashToken(token);\n\n  const [record] = await db\n    .select({\n      userId: oauthToken.userId,\n      scopes: oauthToken.scopes,\n      accessTokenExpiresAt: oauthToken.accessTokenExpiresAt,\n    })\n    .from(oauthToken)\n    .where(\n      and(eq(oauthToken.accessTokenHash, tokenHash), isNull(oauthToken.revokedAt))\n    )\n    .limit(1);\n\n  if (!record) return null;\n  if (new Date() > record.accessTokenExpiresAt) return null;\n\n  return { userId: record.userId, scopes: record.scopes };\n}\n\nexport function requireScope(scopes: string[], required: OAuthScope): boolean {\n  return scopes.includes(required);\n}\n\nexport async function requireAuth(\n  req: NextRequest,\n  scope: OAuthScope\n): Promise<\n  | { tokenData: { userId: string; scopes: string[] }; error: null }\n  | { tokenData: null; error: Response }\n> {\n  const tokenData = await resolveUserFromBearerToken(\n    req.headers.get(\"authorization\")\n  );\n\n  if (!tokenData) {\n    return {\n      tokenData: null,\n      error: oauthError(\"invalid_token\", \"Invalid or expired access token\", 401),\n    };\n  }\n\n  if (!requireScope(tokenData.scopes, scope)) {\n    return {\n      tokenData: null,\n      error: oauthError(\"insufficient_scope\", `Requires ${scope} scope`, 403),\n    };\n  }\n\n  return { tokenData, error: null };\n}\n\n// --- Client authentication ---\n\nexport async function authenticateClient(\n  clientId: string,\n  clientSecret: string\n) {\n  const [app] = await db\n    .select({\n      id: oauthApp.id,\n      clientSecretHash: oauthApp.clientSecretHash,\n    })\n    .from(oauthApp)\n    .where(and(eq(oauthApp.clientId, clientId), eq(oauthApp.isActive, true)))\n    .limit(1);\n\n  if (!app) return null;\n\n  const valid = await verifySecret(clientSecret, app.clientSecretHash);\n  if (!valid) return null;\n\n  return app;\n}\n\n// --- JSON error responses ---\n\nexport function oauthError(\n  error: string,\n  description: string,\n  status: number = 400\n) {\n  return Response.json({ error, error_description: description }, { status });\n}\n"
  },
  {
    "path": "lib/polar.ts",
    "content": "import { Polar } from \"@polar-sh/sdk\";\n\nexport const polar = new Polar({\n  accessToken: process.env.POLAR_ACCESS_TOKEN!,\n  server: process.env.NODE_ENV === \"production\" ? \"production\" : \"sandbox\",\n});\n"
  },
  {
    "path": "lib/posthog.ts",
    "content": "import posthog from \"posthog-js\";\n\nexport function initPostHog() {\n  if (process.env.NODE_ENV === \"development\") return;\n  const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;\n  if (posthogKey) {\n    posthog.init(posthogKey, {\n      api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,\n    });\n  } else {\n    console.warn(\"PostHog key is missing, skipping initialization.\");\n  }\n}\n"
  },
  {
    "path": "lib/query-client.tsx",
    "content": "\"use client\";\n\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\nimport { useState, ReactNode } from \"react\";\nimport posthog from \"posthog-js\";\n\nfunction logClientError(error: Error, context: Record<string, unknown>) {\n  console.error(\"Query error:\", error, context);\n\n  if (error.name !== \"UnauthorizedError\" && error.name !== \"ValidationError\") {\n    try {\n      posthog.capture(\"query_error\", {\n        error: error.message,\n        errorName: error.name,\n        stack: error.stack,\n        ...context,\n      });\n    } catch (posthogError) {\n      console.error(\"Failed to log to PostHog:\", posthogError);\n    }\n  }\n}\n\nfunction createQueryClient() {\n  return new QueryClient({\n    defaultOptions: {\n      queries: {\n        staleTime: 1000 * 60 * 5, // 5 minutes\n        gcTime: 1000 * 60 * 10, // 10 minutes (formerly cacheTime)\n        retry: (failureCount, error: Error) => {\n          // Don't retry on authentication or validation errors\n          if (error?.name === \"UnauthorizedError\" || error?.name === \"ValidationError\") {\n            return false;\n          }\n          // Retry up to 3 times for other errors\n          return failureCount < 3;\n        },\n        refetchOnWindowFocus: false,\n      },\n      mutations: {\n        retry: (failureCount, error: Error) => {\n          // Don't retry mutations on client errors (4xx)\n          if (error?.name === \"UnauthorizedError\" || error?.name === \"ValidationError\") {\n            return false;\n          }\n          // Only retry once for server errors (5xx)\n          return failureCount < 1;\n        },\n        onError: (error: Error, variables: unknown, context: unknown) => {\n          logClientError(error, {\n            type: \"mutation\",\n            variables: JSON.stringify(variables),\n            context,\n          });\n        },\n      },\n    },\n  });\n}\n\ninterface QueryProviderProps {\n  children: ReactNode;\n}\n\nexport function QueryProvider({ children }: QueryProviderProps) {\n  const [queryClient] = useState(() => createQueryClient());\n\n  return (\n    <QueryClientProvider client={queryClient}>\n      {children}\n      {process.env.NODE_ENV === \"development\" && <ReactQueryDevtools initialIsOpen={false} />}\n    </QueryClientProvider>\n  );\n}\n"
  },
  {
    "path": "lib/shared.ts",
    "content": "import { UnauthorizedError } from \"@/types/errors\";\nimport { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport { NextRequest } from \"next/server\";\nimport { User } from \"better-auth\";\n\nexport async function getCurrentUserId(req?: NextRequest): Promise<string> {\n  const session = await auth.api.getSession({\n    headers: req?.headers ?? (await headers()),\n  });\n\n  if (!session?.user?.id) {\n    throw new UnauthorizedError();\n  }\n\n  return session.user.id;\n}\n\nexport async function getCurrentUser(req?: NextRequest): Promise<User> {\n  const session = await auth.api.getSession({\n    headers: req?.headers ?? (await headers()),\n  });\n\n  if (!session) {\n    throw new UnauthorizedError();\n  }\n\n  return session.user;\n}\n\nexport function logError(error: Error, context?: Record<string, unknown>) {\n  console.error(\"Action error:\", error, context);\n\n  if (error.name === \"UnauthorizedError\" || error.name === \"ValidationError\") {\n    console.warn(\"Expected error:\", { error: error.message, context });\n  } else {\n    console.error(\"Unexpected error:\", {\n      error: error.message,\n      stack: error.stack,\n      context,\n    });\n  }\n}\n"
  },
  {
    "path": "lib/subscription.ts",
    "content": "\"use server\";\n\nimport { getMyAllTimeRequestCount } from \"@/actions/ai-usage\";\nimport { SubscriptionRequiredError } from \"@/types/errors\";\nimport { SubscriptionCheck } from \"@/types/subscription\";\nimport { NextRequest } from \"next/server\";\nimport { AI_REQUEST_FREE_TIER_LIMIT } from \"./constants\";\nimport { getCurrentUserId } from \"./shared\";\nimport { db } from \"@/db\";\nimport { subscription } from \"@/db/schema\";\nimport { and, eq } from \"drizzle-orm\";\n\nexport async function getMyActiveSubscription(\n  userId: string\n): Promise<typeof subscription.$inferSelect | null> {\n  const sub = await db\n    .select()\n    .from(subscription)\n    .where(and(eq(subscription.userId, userId), eq(subscription.status, \"active\")));\n  return sub[0];\n}\n\nexport async function validateSubscriptionAndUsage(userId: string): Promise<SubscriptionCheck> {\n  try {\n    const [activeSubscription, requestsUsed] = await Promise.all([\n      getMyActiveSubscription(userId),\n      getMyAllTimeRequestCount(userId),\n    ]);\n\n    const isSubscribed =\n      !!activeSubscription &&\n      activeSubscription?.productId === process.env.NEXT_PUBLIC_TWEAKCN_PRO_PRODUCT_ID;\n\n    if (isSubscribed) {\n      return {\n        canProceed: true,\n        isSubscribed: true,\n        requestsUsed,\n        requestsRemaining: Infinity, // Unlimited for subscribers\n      };\n    }\n\n    const requestsRemaining = Math.max(0, AI_REQUEST_FREE_TIER_LIMIT - requestsUsed);\n    const canProceed = requestsUsed < AI_REQUEST_FREE_TIER_LIMIT;\n\n    if (!canProceed) {\n      return {\n        canProceed: false,\n        isSubscribed: false,\n        requestsUsed,\n        requestsRemaining: 0,\n        error: `You've reached your free limit of ${AI_REQUEST_FREE_TIER_LIMIT} requests. Please upgrade to continue.`,\n      };\n    }\n\n    return {\n      canProceed: true,\n      isSubscribed: false,\n      requestsUsed,\n      requestsRemaining,\n    };\n  } catch (error) {\n    console.error(\"Error validating subscription:\", error);\n    throw error;\n  }\n}\n\nexport async function requireSubscriptionOrFreeUsage(req: NextRequest): Promise<void> {\n  const userId = await getCurrentUserId(req);\n  const validation = await validateSubscriptionAndUsage(userId);\n\n  if (!validation.canProceed) {\n    throw new SubscriptionRequiredError(validation.error, {\n      requestsRemaining: validation.requestsRemaining,\n    });\n  }\n}\n"
  },
  {
    "path": "lib/utils.ts",
    "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n// @ts-expect-error: owned by ngard\nimport { isEqual } from \"@ngard/tiny-isequal\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n\nexport function isDeepEqual(a: unknown, b: unknown): boolean {\n  return isEqual(a, b);\n}\n"
  },
  {
    "path": "middleware.ts",
    "content": "import { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport { NextResponse, type NextRequest } from \"next/server\";\n\nimport { API_AUTH_PREFIX, DEFAULT_LOGIN_REDIRECT } from \"./routes\";\n\nexport async function middleware(request: NextRequest) {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  const pathname = request.nextUrl.pathname;\n\n  const isApiAuth = pathname.startsWith(API_AUTH_PREFIX);\n\n  if (isApiAuth) {\n    return NextResponse.next();\n  }\n\n  if (!session) {\n    return NextResponse.redirect(new URL(DEFAULT_LOGIN_REDIRECT, request.url));\n  }\n\n  if (session) {\n    // Redirect logged-in users from /dashboard or /settings (root) to /settings/themes\n    if (pathname === \"/dashboard\" || pathname === \"/settings\") {\n      return NextResponse.redirect(new URL(\"/settings/themes\", request.url));\n    }\n  }\n\n  return NextResponse.next();\n}\n\nexport const config = {\n  matcher: [\"/editor/theme/:themeId\", \"/dashboard\", \"/settings/:path*\", \"/success\"],\n};\n"
  },
  {
    "path": "next.config.ts",
    "content": "import { type NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n  turbopack: {\n      rules: {\n        '*.svg': {\n          loaders: [\n            {\n              loader: '@svgr/webpack',\n              options: {\n                icon: true,\n              },\n            },\n          ],\n        as: '*.js',\n      },\n    },\n  },\n};\n\nexport default nextConfig;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"tweakcn-next\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbopack\",\n    \"build\": \"next build --turbopack\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\",\n    \"generate-theme-registry\": \"tsx scripts/generate-theme-registry.ts && tsx scripts/generate-registry.ts\",\n    \"minify-live-preview\": \"pnpm dlx terser public/live-preview.js -o public/live-preview.min.js -c -m\",\n    \"prebuild\": \"pnpm generate-theme-registry\",\n    \"postbuild\": \"pnpm minify-live-preview\",\n    \"prepare\": \"husky\"\n  },\n  \"dependencies\": {\n    \"@ai-sdk/google\": \"^2.0.11\",\n    \"@ai-sdk/react\": \"^2.0.28\",\n    \"@base-ui-components/react\": \"1.0.0-beta.2\",\n    \"@dnd-kit/core\": \"^6.3.1\",\n    \"@dnd-kit/modifiers\": \"^9.0.0\",\n    \"@dnd-kit/sortable\": \"^10.0.0\",\n    \"@dnd-kit/utilities\": \"^3.2.2\",\n    \"@google/generative-ai\": \"^0.24.1\",\n    \"@hookform/resolvers\": \"^5.2.1\",\n    \"@lexical/react\": \"^0.31.0\",\n    \"@neondatabase/serverless\": \"^1.0.0\",\n    \"@polar-sh/checkout\": \"^0.1.11\",\n    \"@polar-sh/nextjs\": \"^0.4.3\",\n    \"@polar-sh/sdk\": \"^0.34.5\",\n    \"@tanstack/react-query\": \"^5.81.2\",\n    \"@tanstack/react-query-devtools\": \"^5.81.2\",\n    \"@tanstack/react-table\": \"^8.21.3\",\n    \"@tiptap/extension-character-count\": \"^2.12.0\",\n    \"@tiptap/extension-mention\": \"^2.11.9\",\n    \"@tiptap/extension-placeholder\": \"^2.11.9\",\n    \"@tiptap/react\": \"^2.11.9\",\n    \"@tiptap/starter-kit\": \"^2.11.9\",\n    \"@tiptap/suggestion\": \"^2.11.9\",\n    \"@upstash/ratelimit\": \"^2.0.5\",\n    \"@vercel/kv\": \"^3.0.0\",\n    \"@vercel/og\": \"^0.6.8\",\n    \"ai\": \"^5.0.28\",\n    \"bcryptjs\": \"^3.0.3\",\n    \"better-auth\": \"^1.2.7\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"cmdk\": \"^1.1.1\",\n    \"cuid\": \"^3.0.0\",\n    \"culori\": \"^4.0.1\",\n    \"date-fns\": \"^4.1.0\",\n    \"dotenv\": \"^16.5.0\",\n    \"drizzle-orm\": \"^0.42.0\",\n    \"embla-carousel-react\": \"^8.6.0\",\n    \"idb-keyval\": \"^6.2.2\",\n    \"input-otp\": \"^1.4.2\",\n    \"isbot\": \"^5.1.26\",\n    \"lexical\": \"^0.31.0\",\n    \"lucide-react\": \"^0.488.0\",\n    \"motion\": \"^12.7.3\",\n    \"next\": \"15.4.10\",\n    \"next-themes\": \"^0.4.6\",\n    \"nuqs\": \"^2.4.3\",\n    \"openai\": \"^4.96.2\",\n    \"posthog-js\": \"^1.236.1\",\n    \"radix-ui\": \"^1.4.3\",\n    \"react\": \"^19.0.0\",\n    \"react-day-picker\": \"^9.14.0\",\n    \"react-dom\": \"^19.0.0\",\n    \"react-dropzone\": \"^14.3.8\",\n    \"react-hook-form\": \"^7.62.0\",\n    \"react-resizable-panels\": \"^4.7.1\",\n    \"react-syntax-highlighter\": \"^15.6.1\",\n    \"recharts\": \"^2.15.4\",\n    \"screenfull\": \"^6.0.2\",\n    \"server-only\": \"^0.0.1\",\n    \"shadcn\": \"^3.8.5\",\n    \"sonner\": \"^2.0.3\",\n    \"streamdown\": \"^2.4.0\",\n    \"swr\": \"^2.3.3\",\n    \"tailwind-merge\": \"^3.2.0\",\n    \"tippy.js\": \"^6.3.7\",\n    \"use-scramble\": \"^2.2.15\",\n    \"use-stick-to-bottom\": \"^1.1.1\",\n    \"vaul\": \"^1.1.2\",\n    \"zod\": \"^3.25.76\",\n    \"zustand\": \"^5.0.3\"\n  },\n  \"devDependencies\": {\n    \"@eslint/eslintrc\": \"^3\",\n    \"@ngard/tiny-isequal\": \"^1.1.0\",\n    \"@svgr/webpack\": \"^8.1.0\",\n    \"@tailwindcss/postcss\": \"^4\",\n    \"@types/culori\": \"^2.1.1\",\n    \"@types/next\": \"^9.0.0\",\n    \"@types/node\": \"^20\",\n    \"@types/pg\": \"^8.11.13\",\n    \"@types/react\": \"^19\",\n    \"@types/react-dom\": \"^19\",\n    \"@types/react-syntax-highlighter\": \"^15.5.13\",\n    \"drizzle-kit\": \"^0.31.0\",\n    \"eslint\": \"^9\",\n    \"eslint-config-next\": \"15.4.10\",\n    \"husky\": \"^9.1.7\",\n    \"prettier\": \"^3.5.3\",\n    \"prettier-plugin-tailwindcss\": \"^0.6.11\",\n    \"raw-loader\": \"^4.0.2\",\n    \"tailwindcss\": \"^4\",\n    \"tsx\": \"^4.19.3\",\n    \"tw-animate-css\": \"^1.2.5\",\n    \"typescript\": \"^5\",\n    \"typescript-eslint\": \"^8.54.0\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.mjs",
    "content": "const config = {\n  plugins: [\"@tailwindcss/postcss\"],\n};\n\nexport default config;\n"
  },
  {
    "path": "public/live-preview.js",
    "content": "// ----- SHADCN SUPPORT -----\nconst REQUIRED_SHADCN_VARS = [\n  \"--radius\",\n  \"--background\",\n  \"--foreground\",\n  \"--card\",\n  \"--card-foreground\",\n  \"--popover\",\n  \"--popover-foreground\",\n  \"--primary\",\n  \"--primary-foreground\",\n  \"--secondary\",\n  \"--secondary-foreground\",\n  \"--muted\",\n  \"--muted-foreground\",\n  \"--accent\",\n  \"--accent-foreground\",\n  \"--destructive\",\n  \"--border\",\n  \"--input\",\n  \"--ring\",\n]\n\nfunction checkShadcnSupport() {\n  const rootStyles = getComputedStyle(document.documentElement);\n  const hasSupport = REQUIRED_SHADCN_VARS.every(\n    (v) => rootStyles.getPropertyValue(v).trim() !== \"\"\n  );\n  return { supported: hasSupport };\n};\n\n// ----- FONT LOADING UTILITIES -----\nconst DEFAULT_FONT_WEIGHTS = [\"400\", \"500\", \"600\", \"700\"];\n\nfunction extractFontFamily(fontFamilyValue) {\n  if (!fontFamilyValue) return null;\n  const firstFont = fontFamilyValue.split(\",\")[0].trim();\n  const cleanFont = firstFont.replace(/['\"]/g, \"\");\n  const systemFonts = [\n    \"ui-sans-serif\", \"ui-serif\", \"ui-monospace\", \"system-ui\",\n    \"sans-serif\", \"serif\", \"monospace\", \"cursive\", \"fantasy\"\n  ];\n  if (systemFonts.includes(cleanFont.toLowerCase())) return null;\n  return cleanFont;\n}\n\nfunction buildFontCssUrl(family, weights) {\n  weights = weights || DEFAULT_FONT_WEIGHTS;\n  const encodedFamily = encodeURIComponent(family);\n  const weightsParam = weights.join(\";\"); \n  return `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@${weightsParam}&display=swap`;\n}\n\nfunction loadGoogleFont(family, weights) {\n  weights = weights || DEFAULT_FONT_WEIGHTS;\n  const href = buildFontCssUrl(family, weights);\n  const existing = document.querySelector(`link[href=\"${href}\"]`);\n  if (existing) return;\n\n  const link = document.createElement(\"link\");\n  link.rel = \"stylesheet\";\n  link.href = href;\n  document.head.appendChild(link);\n}\n\nfunction overrideFontClasses(root, fonts) {\n  const doc = root.ownerDocument || document;\n  const styleId = \"tweakcn-font-overrides\";\n  let styleElement = doc.getElementById(styleId);\n  \n  // Create style element if it doesn't exist\n  if (!styleElement) {\n    styleElement = doc.createElement(\"style\");\n    styleElement.id = styleId;\n    doc.head.appendChild(styleElement);\n  }\n\n  // Build CSS rules for font class overrides\n  const cssRules = [];\n  if (fonts.sans) {\n    cssRules.push(`.font-sans { font-family: ${fonts.sans} !important; }`);\n  }\n  if (fonts.serif) {\n    cssRules.push(`.font-serif { font-family: ${fonts.serif} !important; }`);\n  }\n  if (fonts.mono) {\n    cssRules.push(`.font-mono { font-family: ${fonts.mono} !important; }`);\n  }\n\n  styleElement.textContent = cssRules.join(\"\\n\");\n}\n\nfunction overrideShadowClass(root, themeStyles, mode) {\n\n  const getShadowMap = (themeStyles, mode) => {\n    const styles = themeStyles[mode]\n\n    const shadowColor = styles[\"shadow-color\"];\n    const offsetX = styles[\"shadow-offset-x\"];\n    const offsetY = styles[\"shadow-offset-y\"];\n    const blur = styles[\"shadow-blur\"];\n    const spread = styles[\"shadow-spread\"];\n    const opacity = parseFloat(styles[\"shadow-opacity\"]);\n    const color = (opacityMultiplier) =>\n      `color-mix(in srgb, ${shadowColor} calc(${opacity * opacityMultiplier} * 100%), transparent)`;\n\n    const secondLayer = (fixedOffsetY, fixedBlur) => {\n      // Use the same offsetX as the first layer\n      const offsetX2 = offsetX;\n      // Use the fixed offsetY specific to the shadow size\n      const offsetY2 = fixedOffsetY;\n      // Use the fixed blur specific to the shadow size\n      const blur2 = fixedBlur;\n      // Calculate spread relative to the first layer's spread variable\n      const spread2 = (parseFloat(spread?.replace(\"px\", \"\") ?? \"0\") - 1).toString() + \"px\";\n      // Use the same color function (opacity can still be overridden by --shadow-opacity)\n      const color2 = color(1.0); // Default opacity for second layer is 0.1 in examples\n\n      return `${offsetX2} ${offsetY2} ${blur2} ${spread2} ${color2}`;\n    };\n\n    // Map shadow names to their CSS variable string structures\n    const shadowMap = {\n      // Single layer shadows - use base variables directly\n      \"shadow-2xs\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(0.5)}`, // Assumes vars set appropriately (e.g., y=1, blur=0, spread=0)\n      \"shadow-xs\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(0.5)}`, // Assumes vars set appropriately (e.g., y=1, blur=2, spread=0)\n      \"shadow-2xl\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(2.5)}`, // Assumes vars set appropriately (e.g., y=25, blur=50, spread=-12)\n\n      // Two layer shadows - use base vars for layer 1, mix fixed/calculated for layer 2\n      \"shadow-sm\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(\n        1.0\n      )}, ${secondLayer(\"1px\", \"2px\")}`,\n      shadow: `${offsetX} ${offsetY} ${blur} ${spread} ${color(1.0)}, ${secondLayer(\"1px\", \"2px\")}`, // Alias for the 'shadow:' example line\n\n      \"shadow-md\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(\n        1.0\n      )}, ${secondLayer(\"2px\", \"4px\")}`,\n\n      \"shadow-lg\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(\n        1.0\n      )}, ${secondLayer(\"4px\", \"6px\")}`,\n\n      \"shadow-xl\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(\n        1.0\n      )}, ${secondLayer(\"8px\", \"10px\")}`,\n    };\n\n    return shadowMap;\n  };\n\n  const doc = root.ownerDocument || document;\n  const styleId = \"tweakcn-shadow-overrides\";\n  let styleElement = doc.getElementById(styleId);\n  \n  // Create style element if it doesn't exist\n  if (!styleElement) {\n    styleElement = doc.createElement(\"style\");\n    styleElement.id = styleId;\n    doc.head.appendChild(styleElement);\n  }\n\n  const shadowMap = getShadowMap(themeStyles, mode)\n\n  // Build CSS rules for font class overrides\n  const cssRules = [];\n  Object.entries(shadowMap).forEach(([name, value]) => {\n    cssRules.push(`.${name} { box-shadow: ${value} !important; }`);\n  });\n\n  styleElement.textContent = cssRules.join(\"\\n\");\n}\n\nfunction loadThemeFonts(root, themeStyles) {\n  try {\n    const currentFonts = {\n      sans: themeStyles[\"font-sans\"],\n      serif: themeStyles[\"font-serif\"],\n      mono: themeStyles[\"font-mono\"],\n    };\n  \n     Object.entries(currentFonts).forEach(([_type, fontValue]) => {\n      const fontFamily = extractFontFamily(fontValue);\n      if (fontFamily) {\n        loadGoogleFont(fontFamily, DEFAULT_FONT_WEIGHTS);\n      }\n    });    \n\n    // Override font classes with theme fonts\n    overrideFontClasses(root, currentFonts);\n  } catch (error) {\n    console.warn(\"Tweakcn Embed: Failed to load fonts:\", error);\n  }\n}\n\n// ----- THEME STYLES APPLICATION -----\nfunction applyStyleProperty(root, key, value) {\n  if (typeof value === \"string\" && value.trim()) {\n    root.style.setProperty(`--${key}`, value);\n  }\n};\n\nfunction updateThemeModeClass(root, mode) {\n  root.classList.toggle(\"dark\", mode === \"dark\");\n};\n\nfunction applyThemeStyles(root, themeStyles, mode) {\n  updateThemeModeClass(root, mode);\n  // Apply light theme styles first (base styles)\n  const lightStyles = themeStyles.light || {};\n  for (const [key, value] of Object.entries(lightStyles)) {\n    applyStyleProperty(root, key, value);\n  }\n\n  // Apply dark mode overrides\n  const darkStyles = themeStyles.dark;\n  if (mode === \"dark\" && darkStyles) {\n    for (const [key, value] of Object.entries(darkStyles)) {\n      applyStyleProperty(root, key, value);\n    }\n  }\n\n  loadThemeFonts(root, lightStyles);  \n  overrideShadowClass(root, themeStyles, mode)\n};\n\nfunction applyTheme(themeState) {\n  const root = document.documentElement;\n  if (!root || !themeState || !themeState.styles) {\n    console.warn(\"Tweakcn Embed: Missing root element or theme styles.\");\n    return;\n  }\n\n  const { currentMode: mode, styles: themeStyles } = themeState; \n  applyThemeStyles(root, themeStyles, mode);\n};\n\n// ----- MESSAGE SENDING -----\nfunction sendMessageToParent(message) {\n  if (window.parent && window.parent !== window) {\n    try {\n      window.parent.postMessage(message, \"*\");\n    } catch (error) {\n      console.warn(\"Tweakcn Embed: Failed to send message to parent:\", error);\n    }\n  }\n};\n\nconst TWEAKCN_MESSAGE = {\n  PING: \"TWEAKCN_PING\",\n  PONG: \"TWEAKCN_PONG\",\n  CHECK_SHADCN: \"TWEAKCN_CHECK_SHADCN\",\n  SHADCN_STATUS: \"TWEAKCN_SHADCN_STATUS\",\n  THEME_UPDATE: \"TWEAKCN_THEME_UPDATE\",\n  THEME_APPLIED: \"TWEAKCN_THEME_APPLIED\",\n  EMBED_LOADED: \"TWEAKCN_EMBED_LOADED\",\n  EMBED_ERROR: \"TWEAKCN_EMBED_ERROR\",\n};\n\n// ----- MAIN SCRIPT -----\n(() => {\n  \"use strict\";\n  \n  // Prevent multiple initialization\n  if (window.tweakcnEmbed) return; \n\n  const handleMessage = (event) => {\n    // Verify the message is from the parent window\n    if (event.source !== window.parent) return;\n    // Verify the message has the expected structure\n    if (!event.data || typeof event.data.type !== \"string\") return;\n\n    // TODO: Remove localhost once this is live\n    const ALLOWED_ORIGINS = ['https://tweakcn.com', 'http://localhost:3000'];\n    if (!ALLOWED_ORIGINS.includes(event.origin)){\n      sendMessageToParent({ type: TWEAKCN_MESSAGE.EMBED_ERROR, payload: { error: \"Origin not allowed. Preview failed to establish the connection with tweakcn.\" } });\n      return;\n    } ;    \n    \n    const { type, payload } = event.data;\n\n    switch (type) {\n      case TWEAKCN_MESSAGE.PING:\n        sendMessageToParent({ type: TWEAKCN_MESSAGE.PONG });\n        break;\n\n      case TWEAKCN_MESSAGE.CHECK_SHADCN:\n        const supportInfo = checkShadcnSupport();\n        sendMessageToParent({\n          type: TWEAKCN_MESSAGE.SHADCN_STATUS,\n          payload: supportInfo,\n        });\n        break;\n\n      case TWEAKCN_MESSAGE.THEME_UPDATE:\n        if (payload && payload.themeState) {\n          applyTheme(payload.themeState);\n          sendMessageToParent({ type: TWEAKCN_MESSAGE.THEME_APPLIED });\n        }\n        break;\n\n      default:\n        // Ignore unknown message types\n        break;\n    }\n  };\n\n  window.addEventListener(\"message\", handleMessage);\n\n  // ----- NAVIGATION TRACKING -----\n  const emitNavigationUpdate = () => {\n    try {\n      sendMessageToParent({ type: TWEAKCN_MESSAGE.NAVIGATION_UPDATE, payload: { url: window.location.href } });\n    } catch (e) {\n      // noop\n    }\n  };\n\n  const patchHistory = () => {\n    const originalPushState = history.pushState;\n    const originalReplaceState = history.replaceState;\n\n    history.pushState = function () {\n      const ret = originalPushState.apply(this, arguments);\n      emitNavigationUpdate();\n      return ret;\n    };\n\n    history.replaceState = function () {\n      const ret = originalReplaceState.apply(this, arguments);\n      emitNavigationUpdate();\n      return ret;\n    };\n  };\n\n  // Initial and subsequent navigation events\n  try {\n    patchHistory();\n  } catch (e) {}\n  window.addEventListener(\"popstate\", emitNavigationUpdate);\n  window.addEventListener(\"hashchange\", emitNavigationUpdate);\n\n  window.tweakcnEmbed = {\n    initialized: true,\n    version: \"1.0.0\",\n    destroy: () => {\n      window.removeEventListener(\"message\", handleMessage);\n      delete window.tweakcnEmbed;\n    },\n  };\n\n  // Announce that the embed script is ready and send initial URL\n  sendMessageToParent({ type: TWEAKCN_MESSAGE.EMBED_LOADED });\n  emitNavigationUpdate();\n})(); "
  },
  {
    "path": "public/r/registry.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema/registry.json\",\n  \"name\": \"tweakcn-theme-registry\",\n  \"homepage\": \"http://tweakcn.com\",\n  \"items\": [\n    {\n      \"name\": \"modern-minimal\",\n      \"type\": \"registry:style\",\n      \"title\": \"Modern Minimal\",\n      \"description\": \"A theme based on the Modern Minimal color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"radius\": \"0.375rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(1.00 0 0)\",\n          \"foreground\": \"oklch(0.32 0 0)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.32 0 0)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.32 0 0)\",\n          \"primary\": \"oklch(0.62 0.19 259.81)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.97 0.00 264.54)\",\n          \"secondary-foreground\": \"oklch(0.45 0.03 256.80)\",\n          \"muted\": \"oklch(0.98 0.00 247.84)\",\n          \"muted-foreground\": \"oklch(0.55 0.02 264.36)\",\n          \"accent\": \"oklch(0.95 0.03 236.82)\",\n          \"accent-foreground\": \"oklch(0.38 0.14 265.52)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.93 0.01 264.53)\",\n          \"input\": \"oklch(0.93 0.01 264.53)\",\n          \"ring\": \"oklch(0.62 0.19 259.81)\",\n          \"chart-1\": \"oklch(0.62 0.19 259.81)\",\n          \"chart-2\": \"oklch(0.55 0.22 262.88)\",\n          \"chart-3\": \"oklch(0.49 0.22 264.38)\",\n          \"chart-4\": \"oklch(0.42 0.18 265.64)\",\n          \"chart-5\": \"oklch(0.38 0.14 265.52)\",\n          \"radius\": \"0.375rem\",\n          \"sidebar\": \"oklch(0.98 0.00 247.84)\",\n          \"sidebar-foreground\": \"oklch(0.32 0 0)\",\n          \"sidebar-primary\": \"oklch(0.62 0.19 259.81)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.95 0.03 236.82)\",\n          \"sidebar-accent-foreground\": \"oklch(0.38 0.14 265.52)\",\n          \"sidebar-border\": \"oklch(0.93 0.01 264.53)\",\n          \"sidebar-ring\": \"oklch(0.62 0.19 259.81)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.20 0 0)\",\n          \"foreground\": \"oklch(0.92 0 0)\",\n          \"card\": \"oklch(0.27 0 0)\",\n          \"card-foreground\": \"oklch(0.92 0 0)\",\n          \"popover\": \"oklch(0.27 0 0)\",\n          \"popover-foreground\": \"oklch(0.92 0 0)\",\n          \"primary\": \"oklch(0.62 0.19 259.81)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.27 0 0)\",\n          \"secondary-foreground\": \"oklch(0.92 0 0)\",\n          \"muted\": \"oklch(0.27 0 0)\",\n          \"muted-foreground\": \"oklch(0.72 0 0)\",\n          \"accent\": \"oklch(0.38 0.14 265.52)\",\n          \"accent-foreground\": \"oklch(0.88 0.06 254.13)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.37 0 0)\",\n          \"input\": \"oklch(0.37 0 0)\",\n          \"ring\": \"oklch(0.62 0.19 259.81)\",\n          \"chart-1\": \"oklch(0.71 0.14 254.62)\",\n          \"chart-2\": \"oklch(0.62 0.19 259.81)\",\n          \"chart-3\": \"oklch(0.55 0.22 262.88)\",\n          \"chart-4\": \"oklch(0.49 0.22 264.38)\",\n          \"chart-5\": \"oklch(0.42 0.18 265.64)\",\n          \"radius\": \"0.375rem\",\n          \"sidebar\": \"oklch(0.20 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.92 0 0)\",\n          \"sidebar-primary\": \"oklch(0.62 0.19 259.81)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.38 0.14 265.52)\",\n          \"sidebar-accent-foreground\": \"oklch(0.88 0.06 254.13)\",\n          \"sidebar-border\": \"oklch(0.37 0 0)\",\n          \"sidebar-ring\": \"oklch(0.62 0.19 259.81)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"t3-chat\",\n      \"type\": \"registry:style\",\n      \"title\": \"T3 Chat\",\n      \"description\": \"A theme based on the T3 Chat color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0.01 325.64)\",\n          \"foreground\": \"oklch(0.33 0.12 325.04)\",\n          \"card\": \"oklch(0.98 0.01 325.64)\",\n          \"card-foreground\": \"oklch(0.33 0.12 325.04)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.33 0.12 325.04)\",\n          \"primary\": \"oklch(0.53 0.14 355.20)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.87 0.07 334.90)\",\n          \"secondary-foreground\": \"oklch(0.44 0.13 324.80)\",\n          \"muted\": \"oklch(0.94 0.03 331.55)\",\n          \"muted-foreground\": \"oklch(0.49 0.12 324.45)\",\n          \"accent\": \"oklch(0.87 0.07 334.90)\",\n          \"accent-foreground\": \"oklch(0.44 0.13 324.80)\",\n          \"destructive\": \"oklch(0.52 0.14 20.83)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.86 0.08 328.91)\",\n          \"input\": \"oklch(0.85 0.06 336.60)\",\n          \"ring\": \"oklch(0.59 0.22 0.58)\",\n          \"chart-1\": \"oklch(0.60 0.24 344.47)\",\n          \"chart-2\": \"oklch(0.44 0.23 300.62)\",\n          \"chart-3\": \"oklch(0.38 0.04 226.15)\",\n          \"chart-4\": \"oklch(0.83 0.12 88.35)\",\n          \"chart-5\": \"oklch(0.78 0.13 59.00)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.94 0.03 320.58)\",\n          \"sidebar-foreground\": \"oklch(0.49 0.19 354.54)\",\n          \"sidebar-primary\": \"oklch(0.40 0.03 285.20)\",\n          \"sidebar-primary-foreground\": \"oklch(0.97 0.01 337.52)\",\n          \"sidebar-accent\": \"oklch(0.98 0.00 106.42)\",\n          \"sidebar-accent-foreground\": \"oklch(0.40 0.03 285.20)\",\n          \"sidebar-border\": \"oklch(0.94 0.00 48.72)\",\n          \"sidebar-ring\": \"oklch(0.59 0.22 0.58)\",\n          \"font-sans\": \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.24 0.02 307.53)\",\n          \"foreground\": \"oklch(0.84 0.04 309.54)\",\n          \"card\": \"oklch(0.28 0.02 307.54)\",\n          \"card-foreground\": \"oklch(0.85 0.03 341.46)\",\n          \"popover\": \"oklch(0.15 0.01 338.90)\",\n          \"popover-foreground\": \"oklch(0.96 0.01 341.80)\",\n          \"primary\": \"oklch(0.46 0.19 4.10)\",\n          \"primary-foreground\": \"oklch(0.86 0.06 346.37)\",\n          \"secondary\": \"oklch(0.31 0.03 310.06)\",\n          \"secondary-foreground\": \"oklch(0.85 0.04 307.96)\",\n          \"muted\": \"oklch(0.26 0.02 309.47)\",\n          \"muted-foreground\": \"oklch(0.79 0.04 307.10)\",\n          \"accent\": \"oklch(0.36 0.05 308.49)\",\n          \"accent-foreground\": \"oklch(0.96 0.01 341.80)\",\n          \"destructive\": \"oklch(0.23 0.05 12.61)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.33 0.02 343.45)\",\n          \"input\": \"oklch(0.34 0.02 332.83)\",\n          \"ring\": \"oklch(0.59 0.22 0.58)\",\n          \"chart-1\": \"oklch(0.53 0.14 355.20)\",\n          \"chart-2\": \"oklch(0.56 0.19 306.86)\",\n          \"chart-3\": \"oklch(0.72 0.15 60.58)\",\n          \"chart-4\": \"oklch(0.62 0.20 312.74)\",\n          \"chart-5\": \"oklch(0.61 0.21 6.14)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.19 0.02 331.05)\",\n          \"sidebar-foreground\": \"oklch(0.86 0.03 343.66)\",\n          \"sidebar-primary\": \"oklch(0.49 0.22 264.38)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.23 0.03 338.20)\",\n          \"sidebar-accent-foreground\": \"oklch(0.97 0.00 286.38)\",\n          \"sidebar-border\": \"oklch(0 0 0)\",\n          \"sidebar-ring\": \"oklch(0.59 0.22 0.58)\",\n          \"font-sans\": \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"twitter\",\n      \"type\": \"registry:style\",\n      \"title\": \"Twitter\",\n      \"description\": \"A theme based on the Twitter color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Open Sans, sans-serif\",\n          \"font-mono\": \"Menlo, monospace\",\n          \"font-serif\": \"Georgia, serif\",\n          \"radius\": \"1.3rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(1.00 0 0)\",\n          \"foreground\": \"oklch(0.19 0.01 248.51)\",\n          \"card\": \"oklch(0.98 0.00 197.14)\",\n          \"card-foreground\": \"oklch(0.19 0.01 248.51)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.19 0.01 248.51)\",\n          \"primary\": \"oklch(0.67 0.16 245.00)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.19 0.01 248.51)\",\n          \"secondary-foreground\": \"oklch(1.00 0 0)\",\n          \"muted\": \"oklch(0.92 0.00 286.37)\",\n          \"muted-foreground\": \"oklch(0.19 0.01 248.51)\",\n          \"accent\": \"oklch(0.94 0.02 250.85)\",\n          \"accent-foreground\": \"oklch(0.67 0.16 245.00)\",\n          \"destructive\": \"oklch(0.62 0.24 25.77)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.93 0.01 231.66)\",\n          \"input\": \"oklch(0.98 0.00 228.78)\",\n          \"ring\": \"oklch(0.68 0.16 243.35)\",\n          \"chart-1\": \"oklch(0.67 0.16 245.00)\",\n          \"chart-2\": \"oklch(0.69 0.16 160.35)\",\n          \"chart-3\": \"oklch(0.82 0.16 82.53)\",\n          \"chart-4\": \"oklch(0.71 0.18 151.71)\",\n          \"chart-5\": \"oklch(0.59 0.22 10.58)\",\n          \"radius\": \"1.3rem\",\n          \"sidebar\": \"oklch(0.98 0.00 197.14)\",\n          \"sidebar-foreground\": \"oklch(0.19 0.01 248.51)\",\n          \"sidebar-primary\": \"oklch(0.67 0.16 245.00)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.94 0.02 250.85)\",\n          \"sidebar-accent-foreground\": \"oklch(0.67 0.16 245.00)\",\n          \"sidebar-border\": \"oklch(0.93 0.01 238.52)\",\n          \"sidebar-ring\": \"oklch(0.68 0.16 243.35)\",\n          \"font-sans\": \"Open Sans, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"Menlo, monospace\",\n          \"shadow-color\": \"rgba(29,161,242,0.15)\",\n          \"shadow-opacity\": \"0\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-xs\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-sm\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00), 0px 1px 2px -1px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00), 0px 1px 2px -1px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-md\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00), 0px 2px 4px -1px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-lg\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00), 0px 4px 6px -1px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-xl\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00), 0px 8px 10px -1px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-2xl\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0 0 0)\",\n          \"foreground\": \"oklch(0.93 0.00 228.79)\",\n          \"card\": \"oklch(0.21 0.01 274.53)\",\n          \"card-foreground\": \"oklch(0.89 0 0)\",\n          \"popover\": \"oklch(0 0 0)\",\n          \"popover-foreground\": \"oklch(0.93 0.00 228.79)\",\n          \"primary\": \"oklch(0.67 0.16 245.01)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.96 0.00 219.53)\",\n          \"secondary-foreground\": \"oklch(0.19 0.01 248.51)\",\n          \"muted\": \"oklch(0.21 0 0)\",\n          \"muted-foreground\": \"oklch(0.56 0.01 247.97)\",\n          \"accent\": \"oklch(0.19 0.03 242.55)\",\n          \"accent-foreground\": \"oklch(0.67 0.16 245.01)\",\n          \"destructive\": \"oklch(0.62 0.24 25.77)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.27 0.00 248.00)\",\n          \"input\": \"oklch(0.30 0.03 244.82)\",\n          \"ring\": \"oklch(0.68 0.16 243.35)\",\n          \"chart-1\": \"oklch(0.67 0.16 245.00)\",\n          \"chart-2\": \"oklch(0.69 0.16 160.35)\",\n          \"chart-3\": \"oklch(0.82 0.16 82.53)\",\n          \"chart-4\": \"oklch(0.71 0.18 151.71)\",\n          \"chart-5\": \"oklch(0.59 0.22 10.58)\",\n          \"radius\": \"1.3rem\",\n          \"sidebar\": \"oklch(0.21 0.01 274.53)\",\n          \"sidebar-foreground\": \"oklch(0.89 0 0)\",\n          \"sidebar-primary\": \"oklch(0.68 0.16 243.35)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.19 0.03 242.55)\",\n          \"sidebar-accent-foreground\": \"oklch(0.67 0.16 245.01)\",\n          \"sidebar-border\": \"oklch(0.38 0.02 240.59)\",\n          \"sidebar-ring\": \"oklch(0.68 0.16 243.35)\",\n          \"font-sans\": \"Open Sans, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"Menlo, monospace\",\n          \"shadow-color\": \"rgba(29,161,242,0.25)\",\n          \"shadow-opacity\": \"0\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-xs\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-sm\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00), 0px 1px 2px -1px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00), 0px 1px 2px -1px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-md\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00), 0px 2px 4px -1px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-lg\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00), 0px 4px 6px -1px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-xl\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00), 0px 8px 10px -1px hsl(202.82 89.12% 53.14% / 0.00)\",\n          \"shadow-2xl\": \"0px 2px 0px 0px hsl(202.82 89.12% 53.14% / 0.00)\"\n        }\n      }\n    },\n    {\n      \"name\": \"mocha-mousse\",\n      \"type\": \"registry:style\",\n      \"title\": \"Mocha Mousse\",\n      \"description\": \"A theme based on the Mocha Mousse color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"DM Sans, sans-serif\",\n          \"font-mono\": \"Menlo, monospace\",\n          \"font-serif\": \"Georgia, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.95 0.01 102.46)\",\n          \"foreground\": \"oklch(0.41 0.03 40.36)\",\n          \"card\": \"oklch(0.95 0.01 102.46)\",\n          \"card-foreground\": \"oklch(0.41 0.03 40.36)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.41 0.03 40.36)\",\n          \"primary\": \"oklch(0.61 0.06 44.36)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.75 0.04 80.55)\",\n          \"secondary-foreground\": \"oklch(1.00 0 0)\",\n          \"muted\": \"oklch(0.85 0.04 49.09)\",\n          \"muted-foreground\": \"oklch(0.54 0.05 37.21)\",\n          \"accent\": \"oklch(0.85 0.04 49.09)\",\n          \"accent-foreground\": \"oklch(0.41 0.03 40.36)\",\n          \"destructive\": \"oklch(0.22 0.01 52.96)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.75 0.04 80.55)\",\n          \"input\": \"oklch(0.75 0.04 80.55)\",\n          \"ring\": \"oklch(0.61 0.06 44.36)\",\n          \"chart-1\": \"oklch(0.61 0.06 44.36)\",\n          \"chart-2\": \"oklch(0.54 0.05 37.21)\",\n          \"chart-3\": \"oklch(0.73 0.05 52.33)\",\n          \"chart-4\": \"oklch(0.75 0.04 80.55)\",\n          \"chart-5\": \"oklch(0.64 0.04 52.39)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.89 0.03 49.57)\",\n          \"sidebar-foreground\": \"oklch(0.41 0.03 40.36)\",\n          \"sidebar-primary\": \"oklch(0.61 0.06 44.36)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.73 0.05 52.33)\",\n          \"sidebar-accent-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-border\": \"oklch(0.64 0.04 52.39)\",\n          \"sidebar-ring\": \"oklch(0.61 0.06 44.36)\",\n          \"font-sans\": \"DM Sans, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"Menlo, monospace\",\n          \"shadow-color\": \"hsl(20 18% 51% / 0.4)\",\n          \"shadow-opacity\": \"0.11\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"2px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"2px 2px 0px 0px hsl(20 18% 51% / 0.06)\",\n          \"shadow-xs\": \"2px 2px 0px 0px hsl(20 18% 51% / 0.06)\",\n          \"shadow-sm\": \"2px 2px 0px 0px hsl(20 18% 51% / 0.11), 2px 1px 2px -1px hsl(20 18% 51% / 0.11)\",\n          \"shadow\": \"2px 2px 0px 0px hsl(20 18% 51% / 0.11), 2px 1px 2px -1px hsl(20 18% 51% / 0.11)\",\n          \"shadow-md\": \"2px 2px 0px 0px hsl(20 18% 51% / 0.11), 2px 2px 4px -1px hsl(20 18% 51% / 0.11)\",\n          \"shadow-lg\": \"2px 2px 0px 0px hsl(20 18% 51% / 0.11), 2px 4px 6px -1px hsl(20 18% 51% / 0.11)\",\n          \"shadow-xl\": \"2px 2px 0px 0px hsl(20 18% 51% / 0.11), 2px 8px 10px -1px hsl(20 18% 51% / 0.11)\",\n          \"shadow-2xl\": \"2px 2px 0px 0px hsl(20 18% 51% / 0.28)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.27 0.01 48.18)\",\n          \"foreground\": \"oklch(0.95 0.01 102.46)\",\n          \"card\": \"oklch(0.33 0.02 50.89)\",\n          \"card-foreground\": \"oklch(0.95 0.01 102.46)\",\n          \"popover\": \"oklch(0.33 0.02 50.89)\",\n          \"popover-foreground\": \"oklch(0.95 0.01 102.46)\",\n          \"primary\": \"oklch(0.73 0.05 52.33)\",\n          \"primary-foreground\": \"oklch(0.27 0.01 48.18)\",\n          \"secondary\": \"oklch(0.54 0.05 37.21)\",\n          \"secondary-foreground\": \"oklch(0.95 0.01 102.46)\",\n          \"muted\": \"oklch(0.41 0.03 40.36)\",\n          \"muted-foreground\": \"oklch(0.76 0.04 50.86)\",\n          \"accent\": \"oklch(0.75 0.04 80.55)\",\n          \"accent-foreground\": \"oklch(0.27 0.01 48.18)\",\n          \"destructive\": \"oklch(0.69 0.14 21.46)\",\n          \"destructive-foreground\": \"oklch(0.27 0.01 48.18)\",\n          \"border\": \"oklch(0.41 0.03 40.36)\",\n          \"input\": \"oklch(0.41 0.03 40.36)\",\n          \"ring\": \"oklch(0.73 0.05 52.33)\",\n          \"chart-1\": \"oklch(0.73 0.05 52.33)\",\n          \"chart-2\": \"oklch(0.75 0.04 80.55)\",\n          \"chart-3\": \"oklch(0.61 0.06 44.36)\",\n          \"chart-4\": \"oklch(0.54 0.05 37.21)\",\n          \"chart-5\": \"oklch(0.64 0.04 52.39)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.22 0.01 52.96)\",\n          \"sidebar-foreground\": \"oklch(0.95 0.01 102.46)\",\n          \"sidebar-primary\": \"oklch(0.73 0.05 52.33)\",\n          \"sidebar-primary-foreground\": \"oklch(0.22 0.01 52.96)\",\n          \"sidebar-accent\": \"oklch(0.75 0.04 80.55)\",\n          \"sidebar-accent-foreground\": \"oklch(0.22 0.01 52.96)\",\n          \"sidebar-border\": \"oklch(0.41 0.03 40.36)\",\n          \"sidebar-ring\": \"oklch(0.73 0.05 52.33)\",\n          \"font-sans\": \"DM Sans, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"Menlo, monospace\",\n          \"shadow-color\": \"hsl(20 18% 30% / 0.5)\",\n          \"shadow-opacity\": \"0.11\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"2px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"2px 2px 0px 0px hsl(20 18% 30% / 0.06)\",\n          \"shadow-xs\": \"2px 2px 0px 0px hsl(20 18% 30% / 0.06)\",\n          \"shadow-sm\": \"2px 2px 0px 0px hsl(20 18% 30% / 0.11), 2px 1px 2px -1px hsl(20 18% 30% / 0.11)\",\n          \"shadow\": \"2px 2px 0px 0px hsl(20 18% 30% / 0.11), 2px 1px 2px -1px hsl(20 18% 30% / 0.11)\",\n          \"shadow-md\": \"2px 2px 0px 0px hsl(20 18% 30% / 0.11), 2px 2px 4px -1px hsl(20 18% 30% / 0.11)\",\n          \"shadow-lg\": \"2px 2px 0px 0px hsl(20 18% 30% / 0.11), 2px 4px 6px -1px hsl(20 18% 30% / 0.11)\",\n          \"shadow-xl\": \"2px 2px 0px 0px hsl(20 18% 30% / 0.11), 2px 8px 10px -1px hsl(20 18% 30% / 0.11)\",\n          \"shadow-2xl\": \"2px 2px 0px 0px hsl(20 18% 30% / 0.28)\"\n        }\n      }\n    },\n    {\n      \"name\": \"bubblegum\",\n      \"type\": \"registry:style\",\n      \"title\": \"Bubblegum\",\n      \"description\": \"A theme based on the Bubblegum color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Poppins, sans-serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"font-serif\": \"Lora, serif\",\n          \"radius\": \"0.4rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.94 0.02 345.70)\",\n          \"foreground\": \"oklch(0.47 0 0)\",\n          \"card\": \"oklch(0.95 0.05 86.89)\",\n          \"card-foreground\": \"oklch(0.47 0 0)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.47 0 0)\",\n          \"primary\": \"oklch(0.62 0.18 348.14)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.81 0.07 198.19)\",\n          \"secondary-foreground\": \"oklch(0.32 0 0)\",\n          \"muted\": \"oklch(0.88 0.05 212.10)\",\n          \"muted-foreground\": \"oklch(0.58 0 0)\",\n          \"accent\": \"oklch(0.92 0.08 87.67)\",\n          \"accent-foreground\": \"oklch(0.32 0 0)\",\n          \"destructive\": \"oklch(0.71 0.17 21.96)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.62 0.18 348.14)\",\n          \"input\": \"oklch(0.92 0 0)\",\n          \"ring\": \"oklch(0.70 0.16 350.75)\",\n          \"chart-1\": \"oklch(0.70 0.16 350.75)\",\n          \"chart-2\": \"oklch(0.82 0.08 212.09)\",\n          \"chart-3\": \"oklch(0.92 0.08 87.67)\",\n          \"chart-4\": \"oklch(0.80 0.11 348.18)\",\n          \"chart-5\": \"oklch(0.62 0.19 353.91)\",\n          \"radius\": \"0.4rem\",\n          \"sidebar\": \"oklch(0.91 0.04 343.09)\",\n          \"sidebar-foreground\": \"oklch(0.32 0 0)\",\n          \"sidebar-primary\": \"oklch(0.66 0.21 354.31)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.82 0.11 346.02)\",\n          \"sidebar-accent-foreground\": \"oklch(0.32 0 0)\",\n          \"sidebar-border\": \"oklch(0.95 0.03 307.17)\",\n          \"sidebar-ring\": \"oklch(0.66 0.21 354.31)\",\n          \"font-sans\": \"Poppins, sans-serif\",\n          \"font-serif\": \"Lora, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(325.78 58.18% 56.86% / 0.5)\",\n          \"shadow-opacity\": \"1.0\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"3px\",\n          \"shadow-offset-y\": \"3px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"3px 3px 0px 0px hsl(325.78 58.18% 56.86% / 0.50)\",\n          \"shadow-xs\": \"3px 3px 0px 0px hsl(325.78 58.18% 56.86% / 0.50)\",\n          \"shadow-sm\": \"3px 3px 0px 0px hsl(325.78 58.18% 56.86% / 1.00), 3px 1px 2px -1px hsl(325.78 58.18% 56.86% / 1.00)\",\n          \"shadow\": \"3px 3px 0px 0px hsl(325.78 58.18% 56.86% / 1.00), 3px 1px 2px -1px hsl(325.78 58.18% 56.86% / 1.00)\",\n          \"shadow-md\": \"3px 3px 0px 0px hsl(325.78 58.18% 56.86% / 1.00), 3px 2px 4px -1px hsl(325.78 58.18% 56.86% / 1.00)\",\n          \"shadow-lg\": \"3px 3px 0px 0px hsl(325.78 58.18% 56.86% / 1.00), 3px 4px 6px -1px hsl(325.78 58.18% 56.86% / 1.00)\",\n          \"shadow-xl\": \"3px 3px 0px 0px hsl(325.78 58.18% 56.86% / 1.00), 3px 8px 10px -1px hsl(325.78 58.18% 56.86% / 1.00)\",\n          \"shadow-2xl\": \"3px 3px 0px 0px hsl(325.78 58.18% 56.86% / 2.50)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.25 0.03 234.16)\",\n          \"foreground\": \"oklch(0.93 0.02 349.08)\",\n          \"card\": \"oklch(0.29 0.03 233.54)\",\n          \"card-foreground\": \"oklch(0.93 0.02 349.08)\",\n          \"popover\": \"oklch(0.29 0.03 233.54)\",\n          \"popover-foreground\": \"oklch(0.93 0.02 349.08)\",\n          \"primary\": \"oklch(0.92 0.08 87.67)\",\n          \"primary-foreground\": \"oklch(0.25 0.03 234.16)\",\n          \"secondary\": \"oklch(0.78 0.08 4.13)\",\n          \"secondary-foreground\": \"oklch(0.25 0.03 234.16)\",\n          \"muted\": \"oklch(0.27 0.01 255.58)\",\n          \"muted-foreground\": \"oklch(0.78 0.08 4.13)\",\n          \"accent\": \"oklch(0.67 0.10 356.98)\",\n          \"accent-foreground\": \"oklch(0.93 0.02 349.08)\",\n          \"destructive\": \"oklch(0.67 0.18 350.36)\",\n          \"destructive-foreground\": \"oklch(0.25 0.03 234.16)\",\n          \"border\": \"oklch(0.39 0.04 242.22)\",\n          \"input\": \"oklch(0.31 0.03 232.00)\",\n          \"ring\": \"oklch(0.70 0.09 201.87)\",\n          \"chart-1\": \"oklch(0.70 0.09 201.87)\",\n          \"chart-2\": \"oklch(0.78 0.08 4.13)\",\n          \"chart-3\": \"oklch(0.67 0.10 356.98)\",\n          \"chart-4\": \"oklch(0.44 0.07 217.08)\",\n          \"chart-5\": \"oklch(0.27 0.01 255.58)\",\n          \"radius\": \"0.4rem\",\n          \"sidebar\": \"oklch(0.23 0.03 235.97)\",\n          \"sidebar-foreground\": \"oklch(0.97 0.00 264.54)\",\n          \"sidebar-primary\": \"oklch(0.66 0.21 354.31)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.82 0.11 346.02)\",\n          \"sidebar-accent-foreground\": \"oklch(0.28 0.03 256.85)\",\n          \"sidebar-border\": \"oklch(0.37 0.03 259.73)\",\n          \"sidebar-ring\": \"oklch(0.66 0.21 354.31)\",\n          \"font-sans\": \"Poppins, sans-serif\",\n          \"font-serif\": \"Lora, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"#324859\",\n          \"shadow-opacity\": \"1.0\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"3px\",\n          \"shadow-offset-y\": \"3px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"3px 3px 0px 0px hsl(206.15 28.06% 27.25% / 0.50)\",\n          \"shadow-xs\": \"3px 3px 0px 0px hsl(206.15 28.06% 27.25% / 0.50)\",\n          \"shadow-sm\": \"3px 3px 0px 0px hsl(206.15 28.06% 27.25% / 1.00), 3px 1px 2px -1px hsl(206.15 28.06% 27.25% / 1.00)\",\n          \"shadow\": \"3px 3px 0px 0px hsl(206.15 28.06% 27.25% / 1.00), 3px 1px 2px -1px hsl(206.15 28.06% 27.25% / 1.00)\",\n          \"shadow-md\": \"3px 3px 0px 0px hsl(206.15 28.06% 27.25% / 1.00), 3px 2px 4px -1px hsl(206.15 28.06% 27.25% / 1.00)\",\n          \"shadow-lg\": \"3px 3px 0px 0px hsl(206.15 28.06% 27.25% / 1.00), 3px 4px 6px -1px hsl(206.15 28.06% 27.25% / 1.00)\",\n          \"shadow-xl\": \"3px 3px 0px 0px hsl(206.15 28.06% 27.25% / 1.00), 3px 8px 10px -1px hsl(206.15 28.06% 27.25% / 1.00)\",\n          \"shadow-2xl\": \"3px 3px 0px 0px hsl(206.15 28.06% 27.25% / 2.50)\"\n        }\n      }\n    },\n    {\n      \"name\": \"doom-64\",\n      \"type\": \"registry:style\",\n      \"title\": \"Doom 64\",\n      \"description\": \"A theme based on the Doom 64 color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"\\\"Oxanium\\\", sans-serif\",\n          \"font-mono\": \"\\\"Source Code Pro\\\", monospace\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"radius\": \"0px\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.85 0 0)\",\n          \"foreground\": \"oklch(0.24 0 0)\",\n          \"card\": \"oklch(0.76 0 0)\",\n          \"card-foreground\": \"oklch(0.24 0 0)\",\n          \"popover\": \"oklch(0.76 0 0)\",\n          \"popover-foreground\": \"oklch(0.24 0 0)\",\n          \"primary\": \"oklch(0.50 0.19 27.48)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.50 0.09 126.19)\",\n          \"secondary-foreground\": \"oklch(1.00 0 0)\",\n          \"muted\": \"oklch(0.78 0 0)\",\n          \"muted-foreground\": \"oklch(0.41 0 0)\",\n          \"accent\": \"oklch(0.59 0.10 245.74)\",\n          \"accent-foreground\": \"oklch(1.00 0 0)\",\n          \"destructive\": \"oklch(0.71 0.20 46.46)\",\n          \"destructive-foreground\": \"oklch(0 0 0)\",\n          \"border\": \"oklch(0.43 0 0)\",\n          \"input\": \"oklch(0.43 0 0)\",\n          \"ring\": \"oklch(0.50 0.19 27.48)\",\n          \"chart-1\": \"oklch(0.50 0.19 27.48)\",\n          \"chart-2\": \"oklch(0.50 0.09 126.19)\",\n          \"chart-3\": \"oklch(0.59 0.10 245.74)\",\n          \"chart-4\": \"oklch(0.71 0.20 46.46)\",\n          \"chart-5\": \"oklch(0.57 0.04 40.43)\",\n          \"radius\": \"0px\",\n          \"sidebar\": \"oklch(0.76 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.24 0 0)\",\n          \"sidebar-primary\": \"oklch(0.50 0.19 27.48)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.59 0.10 245.74)\",\n          \"sidebar-accent-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-border\": \"oklch(0.43 0 0)\",\n          \"sidebar-ring\": \"oklch(0.50 0.19 27.48)\",\n          \"font-sans\": \"\\\"Oxanium\\\", sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"\\\"Source Code Pro\\\", monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.4\",\n          \"shadow-blur\": \"4px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 2px 4px 0px hsl(0 0% 0% / 0.20)\",\n          \"shadow-xs\": \"0px 2px 4px 0px hsl(0 0% 0% / 0.20)\",\n          \"shadow-sm\": \"0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 1px 2px -1px hsl(0 0% 0% / 0.40)\",\n          \"shadow\": \"0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 1px 2px -1px hsl(0 0% 0% / 0.40)\",\n          \"shadow-md\": \"0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 2px 4px -1px hsl(0 0% 0% / 0.40)\",\n          \"shadow-lg\": \"0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 4px 6px -1px hsl(0 0% 0% / 0.40)\",\n          \"shadow-xl\": \"0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 8px 10px -1px hsl(0 0% 0% / 0.40)\",\n          \"shadow-2xl\": \"0px 2px 4px 0px hsl(0 0% 0% / 1.00)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.22 0 0)\",\n          \"foreground\": \"oklch(0.91 0 0)\",\n          \"card\": \"oklch(0.29 0 0)\",\n          \"card-foreground\": \"oklch(0.91 0 0)\",\n          \"popover\": \"oklch(0.29 0 0)\",\n          \"popover-foreground\": \"oklch(0.91 0 0)\",\n          \"primary\": \"oklch(0.61 0.21 27.03)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.64 0.15 133.01)\",\n          \"secondary-foreground\": \"oklch(0 0 0)\",\n          \"muted\": \"oklch(0.26 0 0)\",\n          \"muted-foreground\": \"oklch(0.71 0 0)\",\n          \"accent\": \"oklch(0.75 0.12 244.75)\",\n          \"accent-foreground\": \"oklch(0 0 0)\",\n          \"destructive\": \"oklch(0.78 0.17 68.09)\",\n          \"destructive-foreground\": \"oklch(0 0 0)\",\n          \"border\": \"oklch(0.41 0 0)\",\n          \"input\": \"oklch(0.41 0 0)\",\n          \"ring\": \"oklch(0.61 0.21 27.03)\",\n          \"chart-1\": \"oklch(0.61 0.21 27.03)\",\n          \"chart-2\": \"oklch(0.64 0.15 133.01)\",\n          \"chart-3\": \"oklch(0.75 0.12 244.75)\",\n          \"chart-4\": \"oklch(0.78 0.17 68.09)\",\n          \"chart-5\": \"oklch(0.65 0.03 40.80)\",\n          \"radius\": \"0px\",\n          \"sidebar\": \"oklch(0.19 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.91 0 0)\",\n          \"sidebar-primary\": \"oklch(0.61 0.21 27.03)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.75 0.12 244.75)\",\n          \"sidebar-accent-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-border\": \"oklch(0.41 0 0)\",\n          \"sidebar-ring\": \"oklch(0.61 0.21 27.03)\",\n          \"font-sans\": \"\\\"Oxanium\\\", sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"\\\"Source Code Pro\\\", monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.6\",\n          \"shadow-blur\": \"5px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 2px 5px 0px hsl(0 0% 0% / 0.30)\",\n          \"shadow-xs\": \"0px 2px 5px 0px hsl(0 0% 0% / 0.30)\",\n          \"shadow-sm\": \"0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 1px 2px -1px hsl(0 0% 0% / 0.60)\",\n          \"shadow\": \"0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 1px 2px -1px hsl(0 0% 0% / 0.60)\",\n          \"shadow-md\": \"0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 2px 4px -1px hsl(0 0% 0% / 0.60)\",\n          \"shadow-lg\": \"0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 4px 6px -1px hsl(0 0% 0% / 0.60)\",\n          \"shadow-xl\": \"0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 8px 10px -1px hsl(0 0% 0% / 0.60)\",\n          \"shadow-2xl\": \"0px 2px 5px 0px hsl(0 0% 0% / 1.50)\"\n        }\n      }\n    },\n    {\n      \"name\": \"catppuccin\",\n      \"type\": \"registry:style\",\n      \"title\": \"Catppuccin\",\n      \"description\": \"A theme based on the Catppuccin color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"font-serif\": \"Georgia, serif\",\n          \"radius\": \"0.35rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.96 0.01 264.53)\",\n          \"foreground\": \"oklch(0.44 0.04 279.33)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.44 0.04 279.33)\",\n          \"popover\": \"oklch(0.86 0.01 268.48)\",\n          \"popover-foreground\": \"oklch(0.44 0.04 279.33)\",\n          \"primary\": \"oklch(0.55 0.25 297.02)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.86 0.01 268.48)\",\n          \"secondary-foreground\": \"oklch(0.44 0.04 279.33)\",\n          \"muted\": \"oklch(0.91 0.01 264.51)\",\n          \"muted-foreground\": \"oklch(0.55 0.03 279.08)\",\n          \"accent\": \"oklch(0.68 0.14 235.38)\",\n          \"accent-foreground\": \"oklch(1.00 0 0)\",\n          \"destructive\": \"oklch(0.55 0.22 19.81)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.81 0.02 271.20)\",\n          \"input\": \"oklch(0.86 0.01 268.48)\",\n          \"ring\": \"oklch(0.55 0.25 297.02)\",\n          \"chart-1\": \"oklch(0.55 0.25 297.02)\",\n          \"chart-2\": \"oklch(0.68 0.14 235.38)\",\n          \"chart-3\": \"oklch(0.63 0.18 140.44)\",\n          \"chart-4\": \"oklch(0.69 0.20 42.43)\",\n          \"chart-5\": \"oklch(0.71 0.10 33.10)\",\n          \"radius\": \"0.35rem\",\n          \"sidebar\": \"oklch(0.93 0.01 264.52)\",\n          \"sidebar-foreground\": \"oklch(0.44 0.04 279.33)\",\n          \"sidebar-primary\": \"oklch(0.55 0.25 297.02)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.68 0.14 235.38)\",\n          \"sidebar-accent-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-border\": \"oklch(0.81 0.02 271.20)\",\n          \"sidebar-ring\": \"oklch(0.55 0.25 297.02)\",\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(240 30% 25%)\",\n          \"shadow-opacity\": \"0.12\",\n          \"shadow-blur\": \"6px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.06)\",\n          \"shadow-xs\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.06)\",\n          \"shadow-sm\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-md\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 2px 4px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-lg\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 4px 6px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-xl\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 8px 10px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-2xl\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.30)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.22 0.03 284.06)\",\n          \"foreground\": \"oklch(0.88 0.04 272.28)\",\n          \"card\": \"oklch(0.24 0.03 283.91)\",\n          \"card-foreground\": \"oklch(0.88 0.04 272.28)\",\n          \"popover\": \"oklch(0.40 0.03 280.15)\",\n          \"popover-foreground\": \"oklch(0.88 0.04 272.28)\",\n          \"primary\": \"oklch(0.79 0.12 304.77)\",\n          \"primary-foreground\": \"oklch(0.24 0.03 283.91)\",\n          \"secondary\": \"oklch(0.48 0.03 278.64)\",\n          \"secondary-foreground\": \"oklch(0.88 0.04 272.28)\",\n          \"muted\": \"oklch(0.30 0.03 276.21)\",\n          \"muted-foreground\": \"oklch(0.75 0.04 273.93)\",\n          \"accent\": \"oklch(0.85 0.08 210.25)\",\n          \"accent-foreground\": \"oklch(0.24 0.03 283.91)\",\n          \"destructive\": \"oklch(0.76 0.13 2.76)\",\n          \"destructive-foreground\": \"oklch(0.24 0.03 283.91)\",\n          \"border\": \"oklch(0.32 0.03 281.98)\",\n          \"input\": \"oklch(0.32 0.03 281.98)\",\n          \"ring\": \"oklch(0.79 0.12 304.77)\",\n          \"chart-1\": \"oklch(0.79 0.12 304.77)\",\n          \"chart-2\": \"oklch(0.85 0.08 210.25)\",\n          \"chart-3\": \"oklch(0.86 0.11 142.72)\",\n          \"chart-4\": \"oklch(0.82 0.10 52.63)\",\n          \"chart-5\": \"oklch(0.92 0.02 30.49)\",\n          \"radius\": \"0.35rem\",\n          \"sidebar\": \"oklch(0.18 0.02 284.20)\",\n          \"sidebar-foreground\": \"oklch(0.88 0.04 272.28)\",\n          \"sidebar-primary\": \"oklch(0.79 0.12 304.77)\",\n          \"sidebar-primary-foreground\": \"oklch(0.24 0.03 283.91)\",\n          \"sidebar-accent\": \"oklch(0.85 0.08 210.25)\",\n          \"sidebar-accent-foreground\": \"oklch(0.24 0.03 283.91)\",\n          \"sidebar-border\": \"oklch(0.40 0.03 280.15)\",\n          \"sidebar-ring\": \"oklch(0.79 0.12 304.77)\",\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(240 30% 25%)\",\n          \"shadow-opacity\": \"0.12\",\n          \"shadow-blur\": \"6px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.06)\",\n          \"shadow-xs\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.06)\",\n          \"shadow-sm\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-md\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 2px 4px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-lg\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 4px 6px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-xl\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 8px 10px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-2xl\": \"0px 4px 6px 0px hsl(240 30% 25% / 0.30)\"\n        }\n      }\n    },\n    {\n      \"name\": \"graphite\",\n      \"type\": \"registry:style\",\n      \"title\": \"Graphite\",\n      \"description\": \"A theme based on the Graphite color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"font-serif\": \"Georgia, serif\",\n          \"radius\": \"0.35rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.96 0 0)\",\n          \"foreground\": \"oklch(0.32 0 0)\",\n          \"card\": \"oklch(0.97 0 0)\",\n          \"card-foreground\": \"oklch(0.32 0 0)\",\n          \"popover\": \"oklch(0.97 0 0)\",\n          \"popover-foreground\": \"oklch(0.32 0 0)\",\n          \"primary\": \"oklch(0.49 0 0)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.91 0 0)\",\n          \"secondary-foreground\": \"oklch(0.32 0 0)\",\n          \"muted\": \"oklch(0.89 0 0)\",\n          \"muted-foreground\": \"oklch(0.51 0 0)\",\n          \"accent\": \"oklch(0.81 0 0)\",\n          \"accent-foreground\": \"oklch(0.32 0 0)\",\n          \"destructive\": \"oklch(0.56 0.19 25.86)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.86 0 0)\",\n          \"input\": \"oklch(0.91 0 0)\",\n          \"ring\": \"oklch(0.49 0 0)\",\n          \"chart-1\": \"oklch(0.49 0 0)\",\n          \"chart-2\": \"oklch(0.49 0.04 196.03)\",\n          \"chart-3\": \"oklch(0.65 0 0)\",\n          \"chart-4\": \"oklch(0.73 0 0)\",\n          \"chart-5\": \"oklch(0.81 0 0)\",\n          \"radius\": \"0.35rem\",\n          \"sidebar\": \"oklch(0.94 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.32 0 0)\",\n          \"sidebar-primary\": \"oklch(0.49 0 0)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.81 0 0)\",\n          \"sidebar-accent-foreground\": \"oklch(0.32 0 0)\",\n          \"sidebar-border\": \"oklch(0.86 0 0)\",\n          \"sidebar-ring\": \"oklch(0.49 0 0)\",\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(0 0% 20% / 0.1)\",\n          \"shadow-opacity\": \"0.15\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.07)\",\n          \"shadow-xs\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.07)\",\n          \"shadow-sm\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.15), 0px 1px 2px -1px hsl(0 0% 20% / 0.15)\",\n          \"shadow\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.15), 0px 1px 2px -1px hsl(0 0% 20% / 0.15)\",\n          \"shadow-md\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.15), 0px 2px 4px -1px hsl(0 0% 20% / 0.15)\",\n          \"shadow-lg\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.15), 0px 4px 6px -1px hsl(0 0% 20% / 0.15)\",\n          \"shadow-xl\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.15), 0px 8px 10px -1px hsl(0 0% 20% / 0.15)\",\n          \"shadow-2xl\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.38)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.22 0 0)\",\n          \"foreground\": \"oklch(0.89 0 0)\",\n          \"card\": \"oklch(0.24 0 0)\",\n          \"card-foreground\": \"oklch(0.89 0 0)\",\n          \"popover\": \"oklch(0.24 0 0)\",\n          \"popover-foreground\": \"oklch(0.89 0 0)\",\n          \"primary\": \"oklch(0.71 0 0)\",\n          \"primary-foreground\": \"oklch(0.22 0 0)\",\n          \"secondary\": \"oklch(0.31 0 0)\",\n          \"secondary-foreground\": \"oklch(0.89 0 0)\",\n          \"muted\": \"oklch(0.29 0 0)\",\n          \"muted-foreground\": \"oklch(0.60 0 0)\",\n          \"accent\": \"oklch(0.37 0 0)\",\n          \"accent-foreground\": \"oklch(0.89 0 0)\",\n          \"destructive\": \"oklch(0.66 0.15 22.17)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.33 0 0)\",\n          \"input\": \"oklch(0.31 0 0)\",\n          \"ring\": \"oklch(0.71 0 0)\",\n          \"chart-1\": \"oklch(0.71 0 0)\",\n          \"chart-2\": \"oklch(0.67 0.03 206.35)\",\n          \"chart-3\": \"oklch(0.55 0 0)\",\n          \"chart-4\": \"oklch(0.46 0 0)\",\n          \"chart-5\": \"oklch(0.37 0 0)\",\n          \"radius\": \"0.35rem\",\n          \"sidebar\": \"oklch(0.24 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.89 0 0)\",\n          \"sidebar-primary\": \"oklch(0.71 0 0)\",\n          \"sidebar-primary-foreground\": \"oklch(0.22 0 0)\",\n          \"sidebar-accent\": \"oklch(0.37 0 0)\",\n          \"sidebar-accent-foreground\": \"oklch(0.89 0 0)\",\n          \"sidebar-border\": \"oklch(0.33 0 0)\",\n          \"sidebar-ring\": \"oklch(0.71 0 0)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(0 0% 20% / 0.1)\",\n          \"shadow-opacity\": \"0.15\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.07)\",\n          \"shadow-xs\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.07)\",\n          \"shadow-sm\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.15), 0px 1px 2px -1px hsl(0 0% 20% / 0.15)\",\n          \"shadow\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.15), 0px 1px 2px -1px hsl(0 0% 20% / 0.15)\",\n          \"shadow-md\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.15), 0px 2px 4px -1px hsl(0 0% 20% / 0.15)\",\n          \"shadow-lg\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.15), 0px 4px 6px -1px hsl(0 0% 20% / 0.15)\",\n          \"shadow-xl\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.15), 0px 8px 10px -1px hsl(0 0% 20% / 0.15)\",\n          \"shadow-2xl\": \"0px 2px 0px 0px hsl(0 0% 20% / 0.38)\"\n        }\n      }\n    },\n    {\n      \"name\": \"perpetuity\",\n      \"type\": \"registry:style\",\n      \"title\": \"Perpetuity\",\n      \"description\": \"A theme based on the Perpetuity color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Source Code Pro, monospace\",\n          \"font-mono\": \"Source Code Pro, monospace\",\n          \"font-serif\": \"Source Code Pro, monospace\",\n          \"radius\": \"0.125rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.95 0.01 197.01)\",\n          \"foreground\": \"oklch(0.38 0.06 212.66)\",\n          \"card\": \"oklch(0.97 0.01 197.07)\",\n          \"card-foreground\": \"oklch(0.38 0.06 212.66)\",\n          \"popover\": \"oklch(0.97 0.01 197.07)\",\n          \"popover-foreground\": \"oklch(0.38 0.06 212.66)\",\n          \"primary\": \"oklch(0.56 0.09 203.28)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.92 0.02 196.84)\",\n          \"secondary-foreground\": \"oklch(0.38 0.06 212.66)\",\n          \"muted\": \"oklch(0.93 0.01 196.97)\",\n          \"muted-foreground\": \"oklch(0.54 0.06 201.57)\",\n          \"accent\": \"oklch(0.90 0.03 201.89)\",\n          \"accent-foreground\": \"oklch(0.38 0.06 212.66)\",\n          \"destructive\": \"oklch(0.57 0.19 25.54)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.89 0.02 204.41)\",\n          \"input\": \"oklch(0.92 0.02 196.84)\",\n          \"ring\": \"oklch(0.56 0.09 203.28)\",\n          \"chart-1\": \"oklch(0.56 0.09 203.28)\",\n          \"chart-2\": \"oklch(0.64 0.10 201.59)\",\n          \"chart-3\": \"oklch(0.71 0.11 201.25)\",\n          \"chart-4\": \"oklch(0.77 0.10 201.18)\",\n          \"chart-5\": \"oklch(0.83 0.08 200.97)\",\n          \"radius\": \"0.125rem\",\n          \"sidebar\": \"oklch(0.93 0.02 205.32)\",\n          \"sidebar-foreground\": \"oklch(0.38 0.06 212.66)\",\n          \"sidebar-primary\": \"oklch(0.56 0.09 203.28)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.90 0.03 201.89)\",\n          \"sidebar-accent-foreground\": \"oklch(0.38 0.06 212.66)\",\n          \"sidebar-border\": \"oklch(0.89 0.02 204.41)\",\n          \"sidebar-ring\": \"oklch(0.56 0.09 203.28)\",\n          \"font-sans\": \"Courier New, monospace\",\n          \"font-serif\": \"Courier New, monospace\",\n          \"font-mono\": \"Courier New, monospace\",\n          \"shadow-color\": \"hsl(185 70% 30% / 0.15)\",\n          \"shadow-opacity\": \"0.15\",\n          \"shadow-blur\": \"2px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"1px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"1px 1px 2px 0px hsl(185 70% 30% / 0.07)\",\n          \"shadow-xs\": \"1px 1px 2px 0px hsl(185 70% 30% / 0.07)\",\n          \"shadow-sm\": \"1px 1px 2px 0px hsl(185 70% 30% / 0.15), 1px 1px 2px -1px hsl(185 70% 30% / 0.15)\",\n          \"shadow\": \"1px 1px 2px 0px hsl(185 70% 30% / 0.15), 1px 1px 2px -1px hsl(185 70% 30% / 0.15)\",\n          \"shadow-md\": \"1px 1px 2px 0px hsl(185 70% 30% / 0.15), 1px 2px 4px -1px hsl(185 70% 30% / 0.15)\",\n          \"shadow-lg\": \"1px 1px 2px 0px hsl(185 70% 30% / 0.15), 1px 4px 6px -1px hsl(185 70% 30% / 0.15)\",\n          \"shadow-xl\": \"1px 1px 2px 0px hsl(185 70% 30% / 0.15), 1px 8px 10px -1px hsl(185 70% 30% / 0.15)\",\n          \"shadow-2xl\": \"1px 1px 2px 0px hsl(185 70% 30% / 0.38)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.21 0.02 224.45)\",\n          \"foreground\": \"oklch(0.85 0.13 195.04)\",\n          \"card\": \"oklch(0.23 0.03 216.07)\",\n          \"card-foreground\": \"oklch(0.85 0.13 195.04)\",\n          \"popover\": \"oklch(0.23 0.03 216.07)\",\n          \"popover-foreground\": \"oklch(0.85 0.13 195.04)\",\n          \"primary\": \"oklch(0.85 0.13 195.04)\",\n          \"primary-foreground\": \"oklch(0.21 0.02 224.45)\",\n          \"secondary\": \"oklch(0.38 0.06 216.50)\",\n          \"secondary-foreground\": \"oklch(0.85 0.13 195.04)\",\n          \"muted\": \"oklch(0.29 0.04 218.82)\",\n          \"muted-foreground\": \"oklch(0.66 0.10 195.05)\",\n          \"accent\": \"oklch(0.38 0.06 216.50)\",\n          \"accent-foreground\": \"oklch(0.85 0.13 195.04)\",\n          \"destructive\": \"oklch(0.62 0.21 25.81)\",\n          \"destructive-foreground\": \"oklch(0.96 0 0)\",\n          \"border\": \"oklch(0.38 0.06 216.50)\",\n          \"input\": \"oklch(0.38 0.06 216.50)\",\n          \"ring\": \"oklch(0.85 0.13 195.04)\",\n          \"chart-1\": \"oklch(0.85 0.13 195.04)\",\n          \"chart-2\": \"oklch(0.66 0.10 195.05)\",\n          \"chart-3\": \"oklch(0.58 0.08 195.07)\",\n          \"chart-4\": \"oklch(0.43 0.06 202.62)\",\n          \"chart-5\": \"oklch(0.31 0.05 204.16)\",\n          \"radius\": \"0.125rem\",\n          \"sidebar\": \"oklch(0.21 0.02 224.45)\",\n          \"sidebar-foreground\": \"oklch(0.85 0.13 195.04)\",\n          \"sidebar-primary\": \"oklch(0.85 0.13 195.04)\",\n          \"sidebar-primary-foreground\": \"oklch(0.21 0.02 224.45)\",\n          \"sidebar-accent\": \"oklch(0.38 0.06 216.50)\",\n          \"sidebar-accent-foreground\": \"oklch(0.85 0.13 195.04)\",\n          \"sidebar-border\": \"oklch(0.38 0.06 216.50)\",\n          \"sidebar-ring\": \"oklch(0.85 0.13 195.04)\",\n          \"font-sans\": \"Source Code Pro, monospace\",\n          \"font-serif\": \"Source Code Pro, monospace\",\n          \"font-mono\": \"Source Code Pro, monospace\",\n          \"shadow-color\": \"hsl(180 70% 60% / 0.2)\",\n          \"shadow-opacity\": \"0.2\",\n          \"shadow-blur\": \"2px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"1px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"1px 1px 2px 0px hsl(180 70% 60% / 0.10)\",\n          \"shadow-xs\": \"1px 1px 2px 0px hsl(180 70% 60% / 0.10)\",\n          \"shadow-sm\": \"1px 1px 2px 0px hsl(180 70% 60% / 0.20), 1px 1px 2px -1px hsl(180 70% 60% / 0.20)\",\n          \"shadow\": \"1px 1px 2px 0px hsl(180 70% 60% / 0.20), 1px 1px 2px -1px hsl(180 70% 60% / 0.20)\",\n          \"shadow-md\": \"1px 1px 2px 0px hsl(180 70% 60% / 0.20), 1px 2px 4px -1px hsl(180 70% 60% / 0.20)\",\n          \"shadow-lg\": \"1px 1px 2px 0px hsl(180 70% 60% / 0.20), 1px 4px 6px -1px hsl(180 70% 60% / 0.20)\",\n          \"shadow-xl\": \"1px 1px 2px 0px hsl(180 70% 60% / 0.20), 1px 8px 10px -1px hsl(180 70% 60% / 0.20)\",\n          \"shadow-2xl\": \"1px 1px 2px 0px hsl(180 70% 60% / 0.50)\"\n        }\n      }\n    },\n    {\n      \"name\": \"kodama-grove\",\n      \"type\": \"registry:style\",\n      \"title\": \"Kodama Grove\",\n      \"description\": \"A theme based on the Kodama Grove color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Merriweather, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"radius\": \"0.425rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.88 0.05 91.79)\",\n          \"foreground\": \"oklch(0.43 0.03 59.22)\",\n          \"card\": \"oklch(0.89 0.04 87.57)\",\n          \"card-foreground\": \"oklch(0.43 0.03 59.22)\",\n          \"popover\": \"oklch(0.94 0.03 89.85)\",\n          \"popover-foreground\": \"oklch(0.43 0.03 59.22)\",\n          \"primary\": \"oklch(0.67 0.11 118.91)\",\n          \"primary-foreground\": \"oklch(0.99 0.01 88.64)\",\n          \"secondary\": \"oklch(0.85 0.06 91.15)\",\n          \"secondary-foreground\": \"oklch(0.43 0.03 59.22)\",\n          \"muted\": \"oklch(0.85 0.06 91.15)\",\n          \"muted-foreground\": \"oklch(0.58 0.03 60.93)\",\n          \"accent\": \"oklch(0.84 0.07 90.33)\",\n          \"accent-foreground\": \"oklch(0.43 0.03 59.22)\",\n          \"destructive\": \"oklch(0.71 0.10 29.98)\",\n          \"destructive-foreground\": \"oklch(0.98 0.01 91.48)\",\n          \"border\": \"oklch(0.69 0.04 59.84)\",\n          \"input\": \"oklch(0.84 0.07 90.33)\",\n          \"ring\": \"oklch(0.73 0.06 130.85)\",\n          \"chart-1\": \"oklch(0.73 0.06 130.85)\",\n          \"chart-2\": \"oklch(0.68 0.06 132.45)\",\n          \"chart-3\": \"oklch(0.82 0.03 136.65)\",\n          \"chart-4\": \"oklch(0.59 0.05 137.62)\",\n          \"chart-5\": \"oklch(0.52 0.04 137.19)\",\n          \"radius\": \"0.425rem\",\n          \"sidebar\": \"oklch(0.86 0.06 90.52)\",\n          \"sidebar-foreground\": \"oklch(0.43 0.03 59.22)\",\n          \"sidebar-primary\": \"oklch(0.73 0.06 130.85)\",\n          \"sidebar-primary-foreground\": \"oklch(0.99 0.01 88.64)\",\n          \"sidebar-accent\": \"oklch(0.92 0.02 88.00)\",\n          \"sidebar-accent-foreground\": \"oklch(0.43 0.03 59.22)\",\n          \"sidebar-border\": \"oklch(0.91 0.02 88.00)\",\n          \"sidebar-ring\": \"oklch(0.73 0.06 130.85)\",\n          \"font-sans\": \"Merriweather, serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(88 22% 35% / 0.15)\",\n          \"shadow-opacity\": \"0.15\",\n          \"shadow-blur\": \"2px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"3px\",\n          \"shadow-offset-y\": \"3px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.07)\",\n          \"shadow-xs\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.07)\",\n          \"shadow-sm\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.15), 3px 1px 2px -1px hsl(88 22% 35% / 0.15)\",\n          \"shadow\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.15), 3px 1px 2px -1px hsl(88 22% 35% / 0.15)\",\n          \"shadow-md\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.15), 3px 2px 4px -1px hsl(88 22% 35% / 0.15)\",\n          \"shadow-lg\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.15), 3px 4px 6px -1px hsl(88 22% 35% / 0.15)\",\n          \"shadow-xl\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.15), 3px 8px 10px -1px hsl(88 22% 35% / 0.15)\",\n          \"shadow-2xl\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.38)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.33 0.02 88.07)\",\n          \"foreground\": \"oklch(0.92 0.02 82.12)\",\n          \"card\": \"oklch(0.36 0.02 82.33)\",\n          \"card-foreground\": \"oklch(0.92 0.02 82.12)\",\n          \"popover\": \"oklch(0.36 0.02 82.33)\",\n          \"popover-foreground\": \"oklch(0.92 0.02 82.12)\",\n          \"primary\": \"oklch(0.68 0.06 132.45)\",\n          \"primary-foreground\": \"oklch(0.27 0.01 61.02)\",\n          \"secondary\": \"oklch(0.44 0.02 84.55)\",\n          \"secondary-foreground\": \"oklch(0.92 0.02 82.12)\",\n          \"muted\": \"oklch(0.39 0.02 82.71)\",\n          \"muted-foreground\": \"oklch(0.71 0.02 73.62)\",\n          \"accent\": \"oklch(0.65 0.07 90.76)\",\n          \"accent-foreground\": \"oklch(0.27 0.01 61.02)\",\n          \"destructive\": \"oklch(0.63 0.08 31.30)\",\n          \"destructive-foreground\": \"oklch(0.94 0.02 84.59)\",\n          \"border\": \"oklch(0.44 0.02 84.55)\",\n          \"input\": \"oklch(0.44 0.02 84.55)\",\n          \"ring\": \"oklch(0.68 0.06 132.45)\",\n          \"chart-1\": \"oklch(0.68 0.06 132.45)\",\n          \"chart-2\": \"oklch(0.73 0.06 130.85)\",\n          \"chart-3\": \"oklch(0.59 0.05 137.62)\",\n          \"chart-4\": \"oklch(0.65 0.07 90.76)\",\n          \"chart-5\": \"oklch(0.52 0.04 137.19)\",\n          \"radius\": \"0.425rem\",\n          \"sidebar\": \"oklch(0.33 0.02 88.07)\",\n          \"sidebar-foreground\": \"oklch(0.92 0.02 82.12)\",\n          \"sidebar-primary\": \"oklch(0.68 0.06 132.45)\",\n          \"sidebar-primary-foreground\": \"oklch(0.27 0.01 61.02)\",\n          \"sidebar-accent\": \"oklch(0.65 0.07 90.76)\",\n          \"sidebar-accent-foreground\": \"oklch(0.27 0.01 61.02)\",\n          \"sidebar-border\": \"oklch(0.44 0.02 84.55)\",\n          \"sidebar-ring\": \"oklch(0.68 0.06 132.45)\",\n          \"font-sans\": \"Merriweather, serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(88 22% 35% / 0.15)\",\n          \"shadow-opacity\": \"0.15\",\n          \"shadow-blur\": \"2px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"3px\",\n          \"shadow-offset-y\": \"3px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.07)\",\n          \"shadow-xs\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.07)\",\n          \"shadow-sm\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.15), 3px 1px 2px -1px hsl(88 22% 35% / 0.15)\",\n          \"shadow\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.15), 3px 1px 2px -1px hsl(88 22% 35% / 0.15)\",\n          \"shadow-md\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.15), 3px 2px 4px -1px hsl(88 22% 35% / 0.15)\",\n          \"shadow-lg\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.15), 3px 4px 6px -1px hsl(88 22% 35% / 0.15)\",\n          \"shadow-xl\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.15), 3px 8px 10px -1px hsl(88 22% 35% / 0.15)\",\n          \"shadow-2xl\": \"3px 3px 2px 0px hsl(88 22% 35% / 0.38)\"\n        }\n      }\n    },\n    {\n      \"name\": \"cosmic-night\",\n      \"type\": \"registry:style\",\n      \"title\": \"Cosmic Night\",\n      \"description\": \"A theme based on the Cosmic Night color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"font-serif\": \"Georgia, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.97 0.01 286.15)\",\n          \"foreground\": \"oklch(0.30 0.06 282.42)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.30 0.06 282.42)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.30 0.06 282.42)\",\n          \"primary\": \"oklch(0.54 0.18 288.03)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.92 0.04 292.69)\",\n          \"secondary-foreground\": \"oklch(0.41 0.10 288.17)\",\n          \"muted\": \"oklch(0.96 0.01 286.15)\",\n          \"muted-foreground\": \"oklch(0.54 0.05 284.74)\",\n          \"accent\": \"oklch(0.92 0.04 262.14)\",\n          \"accent-foreground\": \"oklch(0.30 0.06 282.42)\",\n          \"destructive\": \"oklch(0.69 0.21 14.99)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.91 0.02 285.96)\",\n          \"input\": \"oklch(0.91 0.02 285.96)\",\n          \"ring\": \"oklch(0.54 0.18 288.03)\",\n          \"chart-1\": \"oklch(0.54 0.18 288.03)\",\n          \"chart-2\": \"oklch(0.70 0.16 288.99)\",\n          \"chart-3\": \"oklch(0.57 0.21 276.71)\",\n          \"chart-4\": \"oklch(0.64 0.19 281.81)\",\n          \"chart-5\": \"oklch(0.45 0.18 279.38)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.96 0.01 286.15)\",\n          \"sidebar-foreground\": \"oklch(0.30 0.06 282.42)\",\n          \"sidebar-primary\": \"oklch(0.54 0.18 288.03)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.92 0.04 262.14)\",\n          \"sidebar-accent-foreground\": \"oklch(0.30 0.06 282.42)\",\n          \"sidebar-border\": \"oklch(0.91 0.02 285.96)\",\n          \"sidebar-ring\": \"oklch(0.54 0.18 288.03)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(240 30% 25%)\",\n          \"shadow-opacity\": \"0.12\",\n          \"shadow-blur\": \"10px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.06)\",\n          \"shadow-xs\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.06)\",\n          \"shadow-sm\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-md\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 2px 4px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-lg\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 4px 6px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-xl\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 8px 10px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-2xl\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.30)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.17 0.02 283.80)\",\n          \"foreground\": \"oklch(0.92 0.03 285.88)\",\n          \"card\": \"oklch(0.23 0.04 282.93)\",\n          \"card-foreground\": \"oklch(0.92 0.03 285.88)\",\n          \"popover\": \"oklch(0.23 0.04 282.93)\",\n          \"popover-foreground\": \"oklch(0.92 0.03 285.88)\",\n          \"primary\": \"oklch(0.72 0.16 290.40)\",\n          \"primary-foreground\": \"oklch(0.17 0.02 283.80)\",\n          \"secondary\": \"oklch(0.31 0.07 283.46)\",\n          \"secondary-foreground\": \"oklch(0.84 0.08 285.91)\",\n          \"muted\": \"oklch(0.27 0.06 281.44)\",\n          \"muted-foreground\": \"oklch(0.72 0.05 285.17)\",\n          \"accent\": \"oklch(0.34 0.08 280.97)\",\n          \"accent-foreground\": \"oklch(0.92 0.03 285.88)\",\n          \"destructive\": \"oklch(0.69 0.21 14.99)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.33 0.06 282.58)\",\n          \"input\": \"oklch(0.33 0.06 282.58)\",\n          \"ring\": \"oklch(0.72 0.16 290.40)\",\n          \"chart-1\": \"oklch(0.72 0.16 290.40)\",\n          \"chart-2\": \"oklch(0.64 0.10 274.91)\",\n          \"chart-3\": \"oklch(0.75 0.12 244.75)\",\n          \"chart-4\": \"oklch(0.71 0.10 186.68)\",\n          \"chart-5\": \"oklch(0.75 0.18 346.81)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.23 0.04 282.93)\",\n          \"sidebar-foreground\": \"oklch(0.92 0.03 285.88)\",\n          \"sidebar-primary\": \"oklch(0.72 0.16 290.40)\",\n          \"sidebar-primary-foreground\": \"oklch(0.17 0.02 283.80)\",\n          \"sidebar-accent\": \"oklch(0.34 0.08 280.97)\",\n          \"sidebar-accent-foreground\": \"oklch(0.92 0.03 285.88)\",\n          \"sidebar-border\": \"oklch(0.33 0.06 282.58)\",\n          \"sidebar-ring\": \"oklch(0.72 0.16 290.40)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(240 30% 25%)\",\n          \"shadow-opacity\": \"0.12\",\n          \"shadow-blur\": \"10px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.06)\",\n          \"shadow-xs\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.06)\",\n          \"shadow-sm\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-md\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 2px 4px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-lg\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 4px 6px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-xl\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 8px 10px -1px hsl(240 30% 25% / 0.12)\",\n          \"shadow-2xl\": \"0px 4px 10px 0px hsl(240 30% 25% / 0.30)\"\n        }\n      }\n    },\n    {\n      \"name\": \"tangerine\",\n      \"type\": \"registry:style\",\n      \"title\": \"Tangerine\",\n      \"description\": \"A theme based on the Tangerine color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"radius\": \"0.75rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.94 0.00 236.50)\",\n          \"foreground\": \"oklch(0.32 0 0)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.32 0 0)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.32 0 0)\",\n          \"primary\": \"oklch(0.64 0.17 36.44)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.97 0.00 264.54)\",\n          \"secondary-foreground\": \"oklch(0.45 0.03 256.80)\",\n          \"muted\": \"oklch(0.98 0.00 247.84)\",\n          \"muted-foreground\": \"oklch(0.55 0.02 264.36)\",\n          \"accent\": \"oklch(0.91 0.02 243.82)\",\n          \"accent-foreground\": \"oklch(0.38 0.14 265.52)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.90 0.01 247.88)\",\n          \"input\": \"oklch(0.97 0.00 264.54)\",\n          \"ring\": \"oklch(0.64 0.17 36.44)\",\n          \"chart-1\": \"oklch(0.72 0.06 248.68)\",\n          \"chart-2\": \"oklch(0.79 0.09 35.96)\",\n          \"chart-3\": \"oklch(0.58 0.08 254.16)\",\n          \"chart-4\": \"oklch(0.50 0.08 259.49)\",\n          \"chart-5\": \"oklch(0.42 0.10 264.03)\",\n          \"radius\": \"0.75rem\",\n          \"sidebar\": \"oklch(0.90 0.00 258.33)\",\n          \"sidebar-foreground\": \"oklch(0.32 0 0)\",\n          \"sidebar-primary\": \"oklch(0.64 0.17 36.44)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.91 0.02 243.82)\",\n          \"sidebar-accent-foreground\": \"oklch(0.38 0.14 265.52)\",\n          \"sidebar-border\": \"oklch(0.93 0.01 264.53)\",\n          \"sidebar-ring\": \"oklch(0.64 0.17 36.44)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.26 0.03 262.67)\",\n          \"foreground\": \"oklch(0.92 0 0)\",\n          \"card\": \"oklch(0.31 0.03 268.64)\",\n          \"card-foreground\": \"oklch(0.92 0 0)\",\n          \"popover\": \"oklch(0.29 0.02 268.40)\",\n          \"popover-foreground\": \"oklch(0.92 0 0)\",\n          \"primary\": \"oklch(0.64 0.17 36.44)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.31 0.03 266.71)\",\n          \"secondary-foreground\": \"oklch(0.92 0 0)\",\n          \"muted\": \"oklch(0.31 0.03 266.71)\",\n          \"muted-foreground\": \"oklch(0.72 0 0)\",\n          \"accent\": \"oklch(0.34 0.06 267.59)\",\n          \"accent-foreground\": \"oklch(0.88 0.06 254.13)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.38 0.03 269.73)\",\n          \"input\": \"oklch(0.38 0.03 269.73)\",\n          \"ring\": \"oklch(0.64 0.17 36.44)\",\n          \"chart-1\": \"oklch(0.72 0.06 248.68)\",\n          \"chart-2\": \"oklch(0.77 0.09 34.19)\",\n          \"chart-3\": \"oklch(0.58 0.08 254.16)\",\n          \"chart-4\": \"oklch(0.50 0.08 259.49)\",\n          \"chart-5\": \"oklch(0.42 0.10 264.03)\",\n          \"radius\": \"0.75rem\",\n          \"sidebar\": \"oklch(0.31 0.03 267.74)\",\n          \"sidebar-foreground\": \"oklch(0.92 0 0)\",\n          \"sidebar-primary\": \"oklch(0.64 0.17 36.44)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.34 0.06 267.59)\",\n          \"sidebar-accent-foreground\": \"oklch(0.88 0.06 254.13)\",\n          \"sidebar-border\": \"oklch(0.38 0.03 269.73)\",\n          \"sidebar-ring\": \"oklch(0.64 0.17 36.44)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"quantum-rose\",\n      \"type\": \"registry:style\",\n      \"title\": \"Quantum Rose\",\n      \"description\": \"A theme based on the Quantum Rose color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Quicksand, sans-serif\",\n          \"font-mono\": \"Space Mono, monospace\",\n          \"font-serif\": \"Playfair Display, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.97 0.02 343.93)\",\n          \"foreground\": \"oklch(0.44 0.17 352.38)\",\n          \"card\": \"oklch(0.98 0.01 339.33)\",\n          \"card-foreground\": \"oklch(0.44 0.17 352.38)\",\n          \"popover\": \"oklch(0.98 0.01 339.33)\",\n          \"popover-foreground\": \"oklch(0.44 0.17 352.38)\",\n          \"primary\": \"oklch(0.60 0.24 0.13)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.92 0.07 326.13)\",\n          \"secondary-foreground\": \"oklch(0.44 0.17 352.38)\",\n          \"muted\": \"oklch(0.94 0.04 344.26)\",\n          \"muted-foreground\": \"oklch(0.57 0.17 352.05)\",\n          \"accent\": \"oklch(0.88 0.08 344.88)\",\n          \"accent-foreground\": \"oklch(0.44 0.17 352.38)\",\n          \"destructive\": \"oklch(0.58 0.19 6.34)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.89 0.07 344.39)\",\n          \"input\": \"oklch(0.92 0.07 326.13)\",\n          \"ring\": \"oklch(0.60 0.24 0.13)\",\n          \"chart-1\": \"oklch(0.60 0.24 0.13)\",\n          \"chart-2\": \"oklch(0.60 0.17 345.04)\",\n          \"chart-3\": \"oklch(0.60 0.12 311.80)\",\n          \"chart-4\": \"oklch(0.58 0.12 283.29)\",\n          \"chart-5\": \"oklch(0.65 0.19 267.97)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.96 0.02 345.75)\",\n          \"sidebar-foreground\": \"oklch(0.44 0.17 352.38)\",\n          \"sidebar-primary\": \"oklch(0.60 0.24 0.13)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.88 0.08 344.88)\",\n          \"sidebar-accent-foreground\": \"oklch(0.44 0.17 352.38)\",\n          \"sidebar-border\": \"oklch(0.93 0.04 343.31)\",\n          \"sidebar-ring\": \"oklch(0.60 0.24 0.13)\",\n          \"font-sans\": \"Poppins, sans-serif\",\n          \"font-serif\": \"Playfair Display, serif\",\n          \"font-mono\": \"Space Mono, monospace\",\n          \"shadow-color\": \"hsl(330 70% 30% / 0.12)\",\n          \"shadow-opacity\": \"0.18\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"3px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 3px 0px 0px hsl(330 70% 30% / 0.09)\",\n          \"shadow-xs\": \"0px 3px 0px 0px hsl(330 70% 30% / 0.09)\",\n          \"shadow-sm\": \"0px 3px 0px 0px hsl(330 70% 30% / 0.18), 0px 1px 2px -1px hsl(330 70% 30% / 0.18)\",\n          \"shadow\": \"0px 3px 0px 0px hsl(330 70% 30% / 0.18), 0px 1px 2px -1px hsl(330 70% 30% / 0.18)\",\n          \"shadow-md\": \"0px 3px 0px 0px hsl(330 70% 30% / 0.18), 0px 2px 4px -1px hsl(330 70% 30% / 0.18)\",\n          \"shadow-lg\": \"0px 3px 0px 0px hsl(330 70% 30% / 0.18), 0px 4px 6px -1px hsl(330 70% 30% / 0.18)\",\n          \"shadow-xl\": \"0px 3px 0px 0px hsl(330 70% 30% / 0.18), 0px 8px 10px -1px hsl(330 70% 30% / 0.18)\",\n          \"shadow-2xl\": \"0px 3px 0px 0px hsl(330 70% 30% / 0.45)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.18 0.05 313.72)\",\n          \"foreground\": \"oklch(0.86 0.13 326.64)\",\n          \"card\": \"oklch(0.24 0.07 313.23)\",\n          \"card-foreground\": \"oklch(0.86 0.13 326.64)\",\n          \"popover\": \"oklch(0.24 0.07 313.23)\",\n          \"popover-foreground\": \"oklch(0.86 0.13 326.64)\",\n          \"primary\": \"oklch(0.75 0.23 332.02)\",\n          \"primary-foreground\": \"oklch(0.16 0.05 327.57)\",\n          \"secondary\": \"oklch(0.32 0.09 319.65)\",\n          \"secondary-foreground\": \"oklch(0.86 0.13 326.64)\",\n          \"muted\": \"oklch(0.27 0.08 312.35)\",\n          \"muted-foreground\": \"oklch(0.71 0.16 327.11)\",\n          \"accent\": \"oklch(0.36 0.12 325.77)\",\n          \"accent-foreground\": \"oklch(0.86 0.13 326.64)\",\n          \"destructive\": \"oklch(0.65 0.24 7.17)\",\n          \"destructive-foreground\": \"oklch(0.98 0 0)\",\n          \"border\": \"oklch(0.33 0.12 313.54)\",\n          \"input\": \"oklch(0.32 0.09 319.65)\",\n          \"ring\": \"oklch(0.75 0.23 332.02)\",\n          \"chart-1\": \"oklch(0.75 0.23 332.02)\",\n          \"chart-2\": \"oklch(0.65 0.22 317.63)\",\n          \"chart-3\": \"oklch(0.62 0.22 292.77)\",\n          \"chart-4\": \"oklch(0.61 0.16 278.72)\",\n          \"chart-5\": \"oklch(0.62 0.20 268.05)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.19 0.05 311.40)\",\n          \"sidebar-foreground\": \"oklch(0.86 0.13 326.64)\",\n          \"sidebar-primary\": \"oklch(0.75 0.23 332.02)\",\n          \"sidebar-primary-foreground\": \"oklch(0.16 0.05 327.57)\",\n          \"sidebar-accent\": \"oklch(0.36 0.12 325.77)\",\n          \"sidebar-accent-foreground\": \"oklch(0.86 0.13 326.64)\",\n          \"sidebar-border\": \"oklch(0.33 0.12 313.54)\",\n          \"sidebar-ring\": \"oklch(0.75 0.23 332.02)\",\n          \"font-sans\": \"Quicksand, sans-serif\",\n          \"font-serif\": \"Playfair Display, serif\",\n          \"font-mono\": \"Space Mono, monospace\",\n          \"shadow-color\": \"hsl(300 80% 50% / 0.25)\",\n          \"shadow-opacity\": \"0.18\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"3px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 3px 0px 0px hsl(300 80% 50% / 0.09)\",\n          \"shadow-xs\": \"0px 3px 0px 0px hsl(300 80% 50% / 0.09)\",\n          \"shadow-sm\": \"0px 3px 0px 0px hsl(300 80% 50% / 0.18), 0px 1px 2px -1px hsl(300 80% 50% / 0.18)\",\n          \"shadow\": \"0px 3px 0px 0px hsl(300 80% 50% / 0.18), 0px 1px 2px -1px hsl(300 80% 50% / 0.18)\",\n          \"shadow-md\": \"0px 3px 0px 0px hsl(300 80% 50% / 0.18), 0px 2px 4px -1px hsl(300 80% 50% / 0.18)\",\n          \"shadow-lg\": \"0px 3px 0px 0px hsl(300 80% 50% / 0.18), 0px 4px 6px -1px hsl(300 80% 50% / 0.18)\",\n          \"shadow-xl\": \"0px 3px 0px 0px hsl(300 80% 50% / 0.18), 0px 8px 10px -1px hsl(300 80% 50% / 0.18)\",\n          \"shadow-2xl\": \"0px 3px 0px 0px hsl(300 80% 50% / 0.45)\"\n        }\n      }\n    },\n    {\n      \"name\": \"nature\",\n      \"type\": \"registry:style\",\n      \"title\": \"Nature\",\n      \"description\": \"A theme based on the Nature color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-mono\": \"Source Code Pro, monospace\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.97 0.01 80.72)\",\n          \"foreground\": \"oklch(0.30 0.04 30.20)\",\n          \"card\": \"oklch(0.97 0.01 80.72)\",\n          \"card-foreground\": \"oklch(0.30 0.04 30.20)\",\n          \"popover\": \"oklch(0.97 0.01 80.72)\",\n          \"popover-foreground\": \"oklch(0.30 0.04 30.20)\",\n          \"primary\": \"oklch(0.52 0.13 144.17)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.96 0.02 147.64)\",\n          \"secondary-foreground\": \"oklch(0.43 0.12 144.31)\",\n          \"muted\": \"oklch(0.94 0.01 74.42)\",\n          \"muted-foreground\": \"oklch(0.45 0.05 39.21)\",\n          \"accent\": \"oklch(0.90 0.05 146.04)\",\n          \"accent-foreground\": \"oklch(0.43 0.12 144.31)\",\n          \"destructive\": \"oklch(0.54 0.19 26.72)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.88 0.02 74.64)\",\n          \"input\": \"oklch(0.88 0.02 74.64)\",\n          \"ring\": \"oklch(0.52 0.13 144.17)\",\n          \"chart-1\": \"oklch(0.67 0.16 144.21)\",\n          \"chart-2\": \"oklch(0.58 0.14 144.18)\",\n          \"chart-3\": \"oklch(0.52 0.13 144.17)\",\n          \"chart-4\": \"oklch(0.43 0.12 144.31)\",\n          \"chart-5\": \"oklch(0.22 0.05 145.73)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.94 0.01 74.42)\",\n          \"sidebar-foreground\": \"oklch(0.30 0.04 30.20)\",\n          \"sidebar-primary\": \"oklch(0.52 0.13 144.17)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.90 0.05 146.04)\",\n          \"sidebar-accent-foreground\": \"oklch(0.43 0.12 144.31)\",\n          \"sidebar-border\": \"oklch(0.88 0.02 74.64)\",\n          \"sidebar-ring\": \"oklch(0.52 0.13 144.17)\",\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"font-mono\": \"Source Code Pro, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.27 0.03 150.77)\",\n          \"foreground\": \"oklch(0.94 0.01 72.66)\",\n          \"card\": \"oklch(0.33 0.03 146.99)\",\n          \"card-foreground\": \"oklch(0.94 0.01 72.66)\",\n          \"popover\": \"oklch(0.33 0.03 146.99)\",\n          \"popover-foreground\": \"oklch(0.94 0.01 72.66)\",\n          \"primary\": \"oklch(0.67 0.16 144.21)\",\n          \"primary-foreground\": \"oklch(0.22 0.05 145.73)\",\n          \"secondary\": \"oklch(0.39 0.03 142.99)\",\n          \"secondary-foreground\": \"oklch(0.90 0.02 142.55)\",\n          \"muted\": \"oklch(0.33 0.03 146.99)\",\n          \"muted-foreground\": \"oklch(0.86 0.02 76.10)\",\n          \"accent\": \"oklch(0.58 0.14 144.18)\",\n          \"accent-foreground\": \"oklch(0.94 0.01 72.66)\",\n          \"destructive\": \"oklch(0.54 0.19 26.72)\",\n          \"destructive-foreground\": \"oklch(0.94 0.01 72.66)\",\n          \"border\": \"oklch(0.39 0.03 142.99)\",\n          \"input\": \"oklch(0.39 0.03 142.99)\",\n          \"ring\": \"oklch(0.67 0.16 144.21)\",\n          \"chart-1\": \"oklch(0.77 0.12 145.30)\",\n          \"chart-2\": \"oklch(0.72 0.14 144.89)\",\n          \"chart-3\": \"oklch(0.67 0.16 144.21)\",\n          \"chart-4\": \"oklch(0.63 0.15 144.20)\",\n          \"chart-5\": \"oklch(0.58 0.14 144.18)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.27 0.03 150.77)\",\n          \"sidebar-foreground\": \"oklch(0.94 0.01 72.66)\",\n          \"sidebar-primary\": \"oklch(0.67 0.16 144.21)\",\n          \"sidebar-primary-foreground\": \"oklch(0.22 0.05 145.73)\",\n          \"sidebar-accent\": \"oklch(0.58 0.14 144.18)\",\n          \"sidebar-accent-foreground\": \"oklch(0.94 0.01 72.66)\",\n          \"sidebar-border\": \"oklch(0.39 0.03 142.99)\",\n          \"sidebar-ring\": \"oklch(0.67 0.16 144.21)\",\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"font-mono\": \"Source Code Pro, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"bold-tech\",\n      \"type\": \"registry:style\",\n      \"title\": \"Bold Tech\",\n      \"description\": \"A theme based on the Bold Tech color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Roboto, sans-serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"font-serif\": \"Playfair Display, serif\",\n          \"radius\": \"0.625rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(1.00 0 0)\",\n          \"foreground\": \"oklch(0.36 0.14 278.70)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.36 0.14 278.70)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.36 0.14 278.70)\",\n          \"primary\": \"oklch(0.61 0.22 292.72)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.96 0.02 295.19)\",\n          \"secondary-foreground\": \"oklch(0.46 0.21 277.02)\",\n          \"muted\": \"oklch(0.97 0.02 293.76)\",\n          \"muted-foreground\": \"oklch(0.54 0.25 293.01)\",\n          \"accent\": \"oklch(0.93 0.03 255.59)\",\n          \"accent-foreground\": \"oklch(0.42 0.18 265.64)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.93 0.03 272.79)\",\n          \"input\": \"oklch(0.93 0.03 272.79)\",\n          \"ring\": \"oklch(0.61 0.22 292.72)\",\n          \"chart-1\": \"oklch(0.61 0.22 292.72)\",\n          \"chart-2\": \"oklch(0.54 0.25 293.01)\",\n          \"chart-3\": \"oklch(0.49 0.24 292.58)\",\n          \"chart-4\": \"oklch(0.43 0.21 292.76)\",\n          \"chart-5\": \"oklch(0.38 0.18 293.74)\",\n          \"radius\": \"0.625rem\",\n          \"sidebar\": \"oklch(0.97 0.02 293.76)\",\n          \"sidebar-foreground\": \"oklch(0.36 0.14 278.70)\",\n          \"sidebar-primary\": \"oklch(0.61 0.22 292.72)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.93 0.03 255.59)\",\n          \"sidebar-accent-foreground\": \"oklch(0.42 0.18 265.64)\",\n          \"sidebar-border\": \"oklch(0.93 0.03 272.79)\",\n          \"sidebar-ring\": \"oklch(0.61 0.22 292.72)\",\n          \"font-sans\": \"Roboto, sans-serif\",\n          \"font-serif\": \"Playfair Display, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(255 86% 66%)\",\n          \"shadow-opacity\": \"0.2\",\n          \"shadow-blur\": \"4px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"2px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.10)\",\n          \"shadow-xs\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.10)\",\n          \"shadow-sm\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.20), 2px 1px 2px -1px hsl(255 86% 66% / 0.20)\",\n          \"shadow\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.20), 2px 1px 2px -1px hsl(255 86% 66% / 0.20)\",\n          \"shadow-md\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.20), 2px 2px 4px -1px hsl(255 86% 66% / 0.20)\",\n          \"shadow-lg\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.20), 2px 4px 6px -1px hsl(255 86% 66% / 0.20)\",\n          \"shadow-xl\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.20), 2px 8px 10px -1px hsl(255 86% 66% / 0.20)\",\n          \"shadow-2xl\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.50)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.21 0.04 265.75)\",\n          \"foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"card\": \"oklch(0.26 0.09 281.29)\",\n          \"card-foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"popover\": \"oklch(0.26 0.09 281.29)\",\n          \"popover-foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"primary\": \"oklch(0.61 0.22 292.72)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.26 0.09 281.29)\",\n          \"secondary-foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"muted\": \"oklch(0.26 0.09 281.29)\",\n          \"muted-foreground\": \"oklch(0.81 0.10 293.57)\",\n          \"accent\": \"oklch(0.46 0.21 277.02)\",\n          \"accent-foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.28 0.14 291.09)\",\n          \"input\": \"oklch(0.28 0.14 291.09)\",\n          \"ring\": \"oklch(0.61 0.22 292.72)\",\n          \"chart-1\": \"oklch(0.71 0.16 293.54)\",\n          \"chart-2\": \"oklch(0.61 0.22 292.72)\",\n          \"chart-3\": \"oklch(0.54 0.25 293.01)\",\n          \"chart-4\": \"oklch(0.49 0.24 292.58)\",\n          \"chart-5\": \"oklch(0.43 0.21 292.76)\",\n          \"radius\": \"0.625rem\",\n          \"sidebar\": \"oklch(0.21 0.04 265.75)\",\n          \"sidebar-foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"sidebar-primary\": \"oklch(0.61 0.22 292.72)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.46 0.21 277.02)\",\n          \"sidebar-accent-foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"sidebar-border\": \"oklch(0.28 0.14 291.09)\",\n          \"sidebar-ring\": \"oklch(0.61 0.22 292.72)\",\n          \"font-sans\": \"Roboto, sans-serif\",\n          \"font-serif\": \"Playfair Display, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(255 86% 66%)\",\n          \"shadow-opacity\": \"0.2\",\n          \"shadow-blur\": \"4px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"2px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.10)\",\n          \"shadow-xs\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.10)\",\n          \"shadow-sm\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.20), 2px 1px 2px -1px hsl(255 86% 66% / 0.20)\",\n          \"shadow\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.20), 2px 1px 2px -1px hsl(255 86% 66% / 0.20)\",\n          \"shadow-md\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.20), 2px 2px 4px -1px hsl(255 86% 66% / 0.20)\",\n          \"shadow-lg\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.20), 2px 4px 6px -1px hsl(255 86% 66% / 0.20)\",\n          \"shadow-xl\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.20), 2px 8px 10px -1px hsl(255 86% 66% / 0.20)\",\n          \"shadow-2xl\": \"2px 2px 4px 0px hsl(255 86% 66% / 0.50)\"\n        }\n      }\n    },\n    {\n      \"name\": \"elegant-luxury\",\n      \"type\": \"registry:style\",\n      \"title\": \"Elegant Luxury\",\n      \"description\": \"A theme based on the Elegant Luxury color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Poppins, sans-serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"font-serif\": \"Libre Baskerville, serif\",\n          \"radius\": \"0.375rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0.00 56.38)\",\n          \"foreground\": \"oklch(0.22 0 0)\",\n          \"card\": \"oklch(0.98 0.00 56.38)\",\n          \"card-foreground\": \"oklch(0.22 0 0)\",\n          \"popover\": \"oklch(0.98 0.00 56.38)\",\n          \"popover-foreground\": \"oklch(0.22 0 0)\",\n          \"primary\": \"oklch(0.47 0.15 24.94)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.96 0.04 89.09)\",\n          \"secondary-foreground\": \"oklch(0.48 0.10 75.12)\",\n          \"muted\": \"oklch(0.94 0.01 53.44)\",\n          \"muted-foreground\": \"oklch(0.44 0.01 73.64)\",\n          \"accent\": \"oklch(0.96 0.06 95.62)\",\n          \"accent-foreground\": \"oklch(0.40 0.13 25.72)\",\n          \"destructive\": \"oklch(0.44 0.16 26.90)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.94 0.03 80.99)\",\n          \"input\": \"oklch(0.94 0.03 80.99)\",\n          \"ring\": \"oklch(0.47 0.15 24.94)\",\n          \"chart-1\": \"oklch(0.51 0.19 27.52)\",\n          \"chart-2\": \"oklch(0.47 0.15 24.94)\",\n          \"chart-3\": \"oklch(0.40 0.13 25.72)\",\n          \"chart-4\": \"oklch(0.56 0.15 49.00)\",\n          \"chart-5\": \"oklch(0.47 0.12 46.20)\",\n          \"radius\": \"0.375rem\",\n          \"sidebar\": \"oklch(0.94 0.01 53.44)\",\n          \"sidebar-foreground\": \"oklch(0.22 0 0)\",\n          \"sidebar-primary\": \"oklch(0.47 0.15 24.94)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.96 0.06 95.62)\",\n          \"sidebar-accent-foreground\": \"oklch(0.40 0.13 25.72)\",\n          \"sidebar-border\": \"oklch(0.94 0.03 80.99)\",\n          \"sidebar-ring\": \"oklch(0.47 0.15 24.94)\",\n          \"font-sans\": \"Poppins, sans-serif\",\n          \"font-serif\": \"Libre Baskerville, serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"shadow-color\": \"hsl(0 63% 18%)\",\n          \"shadow-opacity\": \"0.12\",\n          \"shadow-blur\": \"16px\",\n          \"shadow-spread\": \"-2px\",\n          \"shadow-offset-x\": \"1px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.06)\",\n          \"shadow-xs\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.06)\",\n          \"shadow-sm\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.12), 1px 1px 2px -3px hsl(0 63% 18% / 0.12)\",\n          \"shadow\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.12), 1px 1px 2px -3px hsl(0 63% 18% / 0.12)\",\n          \"shadow-md\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.12), 1px 2px 4px -3px hsl(0 63% 18% / 0.12)\",\n          \"shadow-lg\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.12), 1px 4px 6px -3px hsl(0 63% 18% / 0.12)\",\n          \"shadow-xl\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.12), 1px 8px 10px -3px hsl(0 63% 18% / 0.12)\",\n          \"shadow-2xl\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.30)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.22 0.01 56.04)\",\n          \"foreground\": \"oklch(0.97 0.00 106.42)\",\n          \"card\": \"oklch(0.27 0.01 34.30)\",\n          \"card-foreground\": \"oklch(0.97 0.00 106.42)\",\n          \"popover\": \"oklch(0.27 0.01 34.30)\",\n          \"popover-foreground\": \"oklch(0.97 0.00 106.42)\",\n          \"primary\": \"oklch(0.51 0.19 27.52)\",\n          \"primary-foreground\": \"oklch(0.98 0.00 56.38)\",\n          \"secondary\": \"oklch(0.47 0.12 46.20)\",\n          \"secondary-foreground\": \"oklch(0.96 0.06 95.62)\",\n          \"muted\": \"oklch(0.27 0.01 34.30)\",\n          \"muted-foreground\": \"oklch(0.87 0.00 56.37)\",\n          \"accent\": \"oklch(0.56 0.15 49.00)\",\n          \"accent-foreground\": \"oklch(0.96 0.06 95.62)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.37 0.01 67.56)\",\n          \"input\": \"oklch(0.37 0.01 67.56)\",\n          \"ring\": \"oklch(0.51 0.19 27.52)\",\n          \"chart-1\": \"oklch(0.71 0.17 22.22)\",\n          \"chart-2\": \"oklch(0.64 0.21 25.33)\",\n          \"chart-3\": \"oklch(0.58 0.22 27.33)\",\n          \"chart-4\": \"oklch(0.84 0.16 84.43)\",\n          \"chart-5\": \"oklch(0.77 0.16 70.08)\",\n          \"radius\": \"0.375rem\",\n          \"sidebar\": \"oklch(0.22 0.01 56.04)\",\n          \"sidebar-foreground\": \"oklch(0.97 0.00 106.42)\",\n          \"sidebar-primary\": \"oklch(0.51 0.19 27.52)\",\n          \"sidebar-primary-foreground\": \"oklch(0.98 0.00 56.38)\",\n          \"sidebar-accent\": \"oklch(0.56 0.15 49.00)\",\n          \"sidebar-accent-foreground\": \"oklch(0.96 0.06 95.62)\",\n          \"sidebar-border\": \"oklch(0.37 0.01 67.56)\",\n          \"sidebar-ring\": \"oklch(0.51 0.19 27.52)\",\n          \"font-sans\": \"Poppins, sans-serif\",\n          \"font-serif\": \"Libre Baskerville, serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"shadow-color\": \"hsl(0 63% 18%)\",\n          \"shadow-opacity\": \"0.12\",\n          \"shadow-blur\": \"16px\",\n          \"shadow-spread\": \"-2px\",\n          \"shadow-offset-x\": \"1px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.06)\",\n          \"shadow-xs\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.06)\",\n          \"shadow-sm\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.12), 1px 1px 2px -3px hsl(0 63% 18% / 0.12)\",\n          \"shadow\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.12), 1px 1px 2px -3px hsl(0 63% 18% / 0.12)\",\n          \"shadow-md\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.12), 1px 2px 4px -3px hsl(0 63% 18% / 0.12)\",\n          \"shadow-lg\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.12), 1px 4px 6px -3px hsl(0 63% 18% / 0.12)\",\n          \"shadow-xl\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.12), 1px 8px 10px -3px hsl(0 63% 18% / 0.12)\",\n          \"shadow-2xl\": \"1px 1px 16px -2px hsl(0 63% 18% / 0.30)\"\n        }\n      }\n    },\n    {\n      \"name\": \"amber-minimal\",\n      \"type\": \"registry:style\",\n      \"title\": \"Amber Minimal\",\n      \"description\": \"A theme based on the Amber Minimal color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"radius\": \"0.375rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(1.00 0 0)\",\n          \"foreground\": \"oklch(0.27 0 0)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.27 0 0)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.27 0 0)\",\n          \"primary\": \"oklch(0.77 0.16 70.08)\",\n          \"primary-foreground\": \"oklch(0 0 0)\",\n          \"secondary\": \"oklch(0.97 0.00 264.54)\",\n          \"secondary-foreground\": \"oklch(0.45 0.03 256.80)\",\n          \"muted\": \"oklch(0.98 0.00 247.84)\",\n          \"muted-foreground\": \"oklch(0.55 0.02 264.36)\",\n          \"accent\": \"oklch(0.99 0.02 95.28)\",\n          \"accent-foreground\": \"oklch(0.47 0.12 46.20)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.93 0.01 264.53)\",\n          \"input\": \"oklch(0.93 0.01 264.53)\",\n          \"ring\": \"oklch(0.77 0.16 70.08)\",\n          \"chart-1\": \"oklch(0.77 0.16 70.08)\",\n          \"chart-2\": \"oklch(0.67 0.16 58.32)\",\n          \"chart-3\": \"oklch(0.56 0.15 49.00)\",\n          \"chart-4\": \"oklch(0.47 0.12 46.20)\",\n          \"chart-5\": \"oklch(0.41 0.11 45.90)\",\n          \"radius\": \"0.375rem\",\n          \"sidebar\": \"oklch(0.98 0.00 247.84)\",\n          \"sidebar-foreground\": \"oklch(0.27 0 0)\",\n          \"sidebar-primary\": \"oklch(0.77 0.16 70.08)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.99 0.02 95.28)\",\n          \"sidebar-accent-foreground\": \"oklch(0.47 0.12 46.20)\",\n          \"sidebar-border\": \"oklch(0.93 0.01 264.53)\",\n          \"sidebar-ring\": \"oklch(0.77 0.16 70.08)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"8px\",\n          \"shadow-spread\": \"-1px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.20 0 0)\",\n          \"foreground\": \"oklch(0.92 0 0)\",\n          \"card\": \"oklch(0.27 0 0)\",\n          \"card-foreground\": \"oklch(0.92 0 0)\",\n          \"popover\": \"oklch(0.27 0 0)\",\n          \"popover-foreground\": \"oklch(0.92 0 0)\",\n          \"primary\": \"oklch(0.77 0.16 70.08)\",\n          \"primary-foreground\": \"oklch(0 0 0)\",\n          \"secondary\": \"oklch(0.27 0 0)\",\n          \"secondary-foreground\": \"oklch(0.92 0 0)\",\n          \"muted\": \"oklch(0.27 0 0)\",\n          \"muted-foreground\": \"oklch(0.72 0 0)\",\n          \"accent\": \"oklch(0.47 0.12 46.20)\",\n          \"accent-foreground\": \"oklch(0.92 0.12 95.75)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.37 0 0)\",\n          \"input\": \"oklch(0.37 0 0)\",\n          \"ring\": \"oklch(0.77 0.16 70.08)\",\n          \"chart-1\": \"oklch(0.84 0.16 84.43)\",\n          \"chart-2\": \"oklch(0.67 0.16 58.32)\",\n          \"chart-3\": \"oklch(0.47 0.12 46.20)\",\n          \"chart-4\": \"oklch(0.56 0.15 49.00)\",\n          \"chart-5\": \"oklch(0.47 0.12 46.20)\",\n          \"radius\": \"0.375rem\",\n          \"sidebar\": \"oklch(0.17 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.92 0 0)\",\n          \"sidebar-primary\": \"oklch(0.77 0.16 70.08)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.47 0.12 46.20)\",\n          \"sidebar-accent-foreground\": \"oklch(0.92 0.12 95.75)\",\n          \"sidebar-border\": \"oklch(0.37 0 0)\",\n          \"sidebar-ring\": \"oklch(0.77 0.16 70.08)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"8px\",\n          \"shadow-spread\": \"-1px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"supabase\",\n      \"type\": \"registry:style\",\n      \"title\": \"Supabase\",\n      \"description\": \"A theme based on the Supabase color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Outfit, sans-serif\",\n          \"font-mono\": \"monospace\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.99 0 0)\",\n          \"foreground\": \"oklch(0.20 0 0)\",\n          \"card\": \"oklch(0.99 0 0)\",\n          \"card-foreground\": \"oklch(0.20 0 0)\",\n          \"popover\": \"oklch(0.99 0 0)\",\n          \"popover-foreground\": \"oklch(0.44 0 0)\",\n          \"primary\": \"oklch(0.83 0.13 160.91)\",\n          \"primary-foreground\": \"oklch(0.26 0.01 166.46)\",\n          \"secondary\": \"oklch(0.99 0 0)\",\n          \"secondary-foreground\": \"oklch(0.20 0 0)\",\n          \"muted\": \"oklch(0.95 0 0)\",\n          \"muted-foreground\": \"oklch(0.24 0 0)\",\n          \"accent\": \"oklch(0.95 0 0)\",\n          \"accent-foreground\": \"oklch(0.24 0 0)\",\n          \"destructive\": \"oklch(0.55 0.19 32.73)\",\n          \"destructive-foreground\": \"oklch(0.99 0.00 17.21)\",\n          \"border\": \"oklch(0.90 0 0)\",\n          \"input\": \"oklch(0.97 0 0)\",\n          \"ring\": \"oklch(0.83 0.13 160.91)\",\n          \"chart-1\": \"oklch(0.83 0.13 160.91)\",\n          \"chart-2\": \"oklch(0.62 0.19 259.81)\",\n          \"chart-3\": \"oklch(0.61 0.22 292.72)\",\n          \"chart-4\": \"oklch(0.77 0.16 70.08)\",\n          \"chart-5\": \"oklch(0.70 0.15 162.48)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.99 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.55 0 0)\",\n          \"sidebar-primary\": \"oklch(0.83 0.13 160.91)\",\n          \"sidebar-primary-foreground\": \"oklch(0.26 0.01 166.46)\",\n          \"sidebar-accent\": \"oklch(0.95 0 0)\",\n          \"sidebar-accent-foreground\": \"oklch(0.24 0 0)\",\n          \"sidebar-border\": \"oklch(0.90 0 0)\",\n          \"sidebar-ring\": \"oklch(0.83 0.13 160.91)\",\n          \"font-sans\": \"Outfit, sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"monospace\",\n          \"shadow-color\": \"#000000\",\n          \"shadow-opacity\": \"0.17\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0.025em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.09)\",\n          \"shadow-xs\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.09)\",\n          \"shadow-sm\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17)\",\n          \"shadow\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17)\",\n          \"shadow-md\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 2px 4px -1px hsl(0 0% 0% / 0.17)\",\n          \"shadow-lg\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 4px 6px -1px hsl(0 0% 0% / 0.17)\",\n          \"shadow-xl\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 8px 10px -1px hsl(0 0% 0% / 0.17)\",\n          \"shadow-2xl\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.43)\",\n          \"tracking-normal\": \"0.025em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.18 0 0)\",\n          \"foreground\": \"oklch(0.93 0.01 255.51)\",\n          \"card\": \"oklch(0.20 0 0)\",\n          \"card-foreground\": \"oklch(0.93 0.01 255.51)\",\n          \"popover\": \"oklch(0.26 0 0)\",\n          \"popover-foreground\": \"oklch(0.73 0 0)\",\n          \"primary\": \"oklch(0.44 0.10 156.76)\",\n          \"primary-foreground\": \"oklch(0.92 0.01 167.16)\",\n          \"secondary\": \"oklch(0.26 0 0)\",\n          \"secondary-foreground\": \"oklch(0.99 0 0)\",\n          \"muted\": \"oklch(0.24 0 0)\",\n          \"muted-foreground\": \"oklch(0.71 0 0)\",\n          \"accent\": \"oklch(0.31 0 0)\",\n          \"accent-foreground\": \"oklch(0.99 0 0)\",\n          \"destructive\": \"oklch(0.31 0.09 29.79)\",\n          \"destructive-foreground\": \"oklch(0.94 0.00 34.31)\",\n          \"border\": \"oklch(0.28 0 0)\",\n          \"input\": \"oklch(0.26 0 0)\",\n          \"ring\": \"oklch(0.80 0.18 151.71)\",\n          \"chart-1\": \"oklch(0.80 0.18 151.71)\",\n          \"chart-2\": \"oklch(0.71 0.14 254.62)\",\n          \"chart-3\": \"oklch(0.71 0.16 293.54)\",\n          \"chart-4\": \"oklch(0.84 0.16 84.43)\",\n          \"chart-5\": \"oklch(0.78 0.13 181.91)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.18 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.63 0 0)\",\n          \"sidebar-primary\": \"oklch(0.44 0.10 156.76)\",\n          \"sidebar-primary-foreground\": \"oklch(0.92 0.01 167.16)\",\n          \"sidebar-accent\": \"oklch(0.31 0 0)\",\n          \"sidebar-accent-foreground\": \"oklch(0.99 0 0)\",\n          \"sidebar-border\": \"oklch(0.28 0 0)\",\n          \"sidebar-ring\": \"oklch(0.80 0.18 151.71)\",\n          \"font-sans\": \"Outfit, sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"monospace\",\n          \"shadow-color\": \"#000000\",\n          \"shadow-opacity\": \"0.17\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0.025em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.09)\",\n          \"shadow-xs\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.09)\",\n          \"shadow-sm\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17)\",\n          \"shadow\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17)\",\n          \"shadow-md\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 2px 4px -1px hsl(0 0% 0% / 0.17)\",\n          \"shadow-lg\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 4px 6px -1px hsl(0 0% 0% / 0.17)\",\n          \"shadow-xl\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 8px 10px -1px hsl(0 0% 0% / 0.17)\",\n          \"shadow-2xl\": \"0px 1px 3px 0px hsl(0 0% 0% / 0.43)\"\n        }\n      }\n    },\n    {\n      \"name\": \"neo-brutalism\",\n      \"type\": \"registry:style\",\n      \"title\": \"Neo Brutalism\",\n      \"description\": \"A theme based on the Neo Brutalism color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"DM Sans, sans-serif\",\n          \"font-mono\": \"Space Mono, monospace\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"radius\": \"0px\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(1.00 0 0)\",\n          \"foreground\": \"oklch(0 0 0)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0 0 0)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0 0 0)\",\n          \"primary\": \"oklch(0.65 0.24 26.97)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.97 0.21 109.77)\",\n          \"secondary-foreground\": \"oklch(0 0 0)\",\n          \"muted\": \"oklch(0.96 0 0)\",\n          \"muted-foreground\": \"oklch(0.32 0 0)\",\n          \"accent\": \"oklch(0.56 0.24 260.82)\",\n          \"accent-foreground\": \"oklch(1.00 0 0)\",\n          \"destructive\": \"oklch(0 0 0)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0 0 0)\",\n          \"input\": \"oklch(0 0 0)\",\n          \"ring\": \"oklch(0.65 0.24 26.97)\",\n          \"chart-1\": \"oklch(0.65 0.24 26.97)\",\n          \"chart-2\": \"oklch(0.97 0.21 109.77)\",\n          \"chart-3\": \"oklch(0.56 0.24 260.82)\",\n          \"chart-4\": \"oklch(0.73 0.25 142.50)\",\n          \"chart-5\": \"oklch(0.59 0.27 328.36)\",\n          \"radius\": \"0px\",\n          \"sidebar\": \"oklch(0.96 0 0)\",\n          \"sidebar-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-primary\": \"oklch(0.65 0.24 26.97)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.56 0.24 260.82)\",\n          \"sidebar-accent-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-border\": \"oklch(0 0 0)\",\n          \"sidebar-ring\": \"oklch(0.65 0.24 26.97)\",\n          \"font-sans\": \"DM Sans, sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"Space Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"1\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"4px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"4px 4px 0px 0px hsl(0 0% 0% / 0.50)\",\n          \"shadow-xs\": \"4px 4px 0px 0px hsl(0 0% 0% / 0.50)\",\n          \"shadow-sm\": \"4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 1px 2px -1px hsl(0 0% 0% / 1.00)\",\n          \"shadow\": \"4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 1px 2px -1px hsl(0 0% 0% / 1.00)\",\n          \"shadow-md\": \"4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 2px 4px -1px hsl(0 0% 0% / 1.00)\",\n          \"shadow-lg\": \"4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 4px 6px -1px hsl(0 0% 0% / 1.00)\",\n          \"shadow-xl\": \"4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 8px 10px -1px hsl(0 0% 0% / 1.00)\",\n          \"shadow-2xl\": \"4px 4px 0px 0px hsl(0 0% 0% / 2.50)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0 0 0)\",\n          \"foreground\": \"oklch(1.00 0 0)\",\n          \"card\": \"oklch(0.32 0 0)\",\n          \"card-foreground\": \"oklch(1.00 0 0)\",\n          \"popover\": \"oklch(0.32 0 0)\",\n          \"popover-foreground\": \"oklch(1.00 0 0)\",\n          \"primary\": \"oklch(0.70 0.19 23.19)\",\n          \"primary-foreground\": \"oklch(0 0 0)\",\n          \"secondary\": \"oklch(0.97 0.20 109.62)\",\n          \"secondary-foreground\": \"oklch(0 0 0)\",\n          \"muted\": \"oklch(0.32 0 0)\",\n          \"muted-foreground\": \"oklch(0.85 0 0)\",\n          \"accent\": \"oklch(0.68 0.18 252.26)\",\n          \"accent-foreground\": \"oklch(0 0 0)\",\n          \"destructive\": \"oklch(1.00 0 0)\",\n          \"destructive-foreground\": \"oklch(0 0 0)\",\n          \"border\": \"oklch(1.00 0 0)\",\n          \"input\": \"oklch(1.00 0 0)\",\n          \"ring\": \"oklch(0.70 0.19 23.19)\",\n          \"chart-1\": \"oklch(0.70 0.19 23.19)\",\n          \"chart-2\": \"oklch(0.97 0.20 109.62)\",\n          \"chart-3\": \"oklch(0.68 0.18 252.26)\",\n          \"chart-4\": \"oklch(0.74 0.23 142.85)\",\n          \"chart-5\": \"oklch(0.61 0.25 328.07)\",\n          \"radius\": \"0px\",\n          \"sidebar\": \"oklch(0 0 0)\",\n          \"sidebar-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-primary\": \"oklch(0.70 0.19 23.19)\",\n          \"sidebar-primary-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-accent\": \"oklch(0.68 0.18 252.26)\",\n          \"sidebar-accent-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-border\": \"oklch(1.00 0 0)\",\n          \"sidebar-ring\": \"oklch(0.70 0.19 23.19)\",\n          \"font-sans\": \"DM Sans, sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"Space Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"1\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"4px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"4px 4px 0px 0px hsl(0 0% 0% / 0.50)\",\n          \"shadow-xs\": \"4px 4px 0px 0px hsl(0 0% 0% / 0.50)\",\n          \"shadow-sm\": \"4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 1px 2px -1px hsl(0 0% 0% / 1.00)\",\n          \"shadow\": \"4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 1px 2px -1px hsl(0 0% 0% / 1.00)\",\n          \"shadow-md\": \"4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 2px 4px -1px hsl(0 0% 0% / 1.00)\",\n          \"shadow-lg\": \"4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 4px 6px -1px hsl(0 0% 0% / 1.00)\",\n          \"shadow-xl\": \"4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 8px 10px -1px hsl(0 0% 0% / 1.00)\",\n          \"shadow-2xl\": \"4px 4px 0px 0px hsl(0 0% 0% / 2.50)\"\n        }\n      }\n    },\n    {\n      \"name\": \"solar-dusk\",\n      \"type\": \"registry:style\",\n      \"title\": \"Solar Dusk\",\n      \"description\": \"A theme based on the Solar Dusk color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Oxanium, sans-serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"radius\": \"0.3rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.99 0.01 84.57)\",\n          \"foreground\": \"oklch(0.37 0.03 49.61)\",\n          \"card\": \"oklch(0.97 0.01 78.28)\",\n          \"card-foreground\": \"oklch(0.37 0.03 49.61)\",\n          \"popover\": \"oklch(0.97 0.01 78.28)\",\n          \"popover-foreground\": \"oklch(0.37 0.03 49.61)\",\n          \"primary\": \"oklch(0.56 0.15 49.00)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.83 0.08 74.44)\",\n          \"secondary-foreground\": \"oklch(0.44 0.01 73.64)\",\n          \"muted\": \"oklch(0.94 0.02 83.26)\",\n          \"muted-foreground\": \"oklch(0.55 0.01 58.07)\",\n          \"accent\": \"oklch(0.90 0.05 74.99)\",\n          \"accent-foreground\": \"oklch(0.44 0.01 73.64)\",\n          \"destructive\": \"oklch(0.44 0.16 26.90)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.89 0.04 89.70)\",\n          \"input\": \"oklch(0.89 0.04 89.70)\",\n          \"ring\": \"oklch(0.56 0.15 49.00)\",\n          \"chart-1\": \"oklch(0.56 0.15 49.00)\",\n          \"chart-2\": \"oklch(0.55 0.01 58.07)\",\n          \"chart-3\": \"oklch(0.55 0.12 66.44)\",\n          \"chart-4\": \"oklch(0.55 0.01 58.07)\",\n          \"chart-5\": \"oklch(0.68 0.14 75.83)\",\n          \"radius\": \"0.3rem\",\n          \"sidebar\": \"oklch(0.94 0.02 83.26)\",\n          \"sidebar-foreground\": \"oklch(0.37 0.03 49.61)\",\n          \"sidebar-primary\": \"oklch(0.56 0.15 49.00)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.55 0.12 66.44)\",\n          \"sidebar-accent-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-border\": \"oklch(0.89 0.04 89.70)\",\n          \"sidebar-ring\": \"oklch(0.56 0.15 49.00)\",\n          \"font-sans\": \"Oxanium, sans-serif\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(28 18% 25%)\",\n          \"shadow-opacity\": \"0.18\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 2px 3px 0px hsl(28 18% 25% / 0.09)\",\n          \"shadow-xs\": \"0px 2px 3px 0px hsl(28 18% 25% / 0.09)\",\n          \"shadow-sm\": \"0px 2px 3px 0px hsl(28 18% 25% / 0.18), 0px 1px 2px -1px hsl(28 18% 25% / 0.18)\",\n          \"shadow\": \"0px 2px 3px 0px hsl(28 18% 25% / 0.18), 0px 1px 2px -1px hsl(28 18% 25% / 0.18)\",\n          \"shadow-md\": \"0px 2px 3px 0px hsl(28 18% 25% / 0.18), 0px 2px 4px -1px hsl(28 18% 25% / 0.18)\",\n          \"shadow-lg\": \"0px 2px 3px 0px hsl(28 18% 25% / 0.18), 0px 4px 6px -1px hsl(28 18% 25% / 0.18)\",\n          \"shadow-xl\": \"0px 2px 3px 0px hsl(28 18% 25% / 0.18), 0px 8px 10px -1px hsl(28 18% 25% / 0.18)\",\n          \"shadow-2xl\": \"0px 2px 3px 0px hsl(28 18% 25% / 0.45)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.22 0.01 56.04)\",\n          \"foreground\": \"oklch(0.97 0.00 106.42)\",\n          \"card\": \"oklch(0.27 0.01 34.30)\",\n          \"card-foreground\": \"oklch(0.97 0.00 106.42)\",\n          \"popover\": \"oklch(0.27 0.01 34.30)\",\n          \"popover-foreground\": \"oklch(0.97 0.00 106.42)\",\n          \"primary\": \"oklch(0.70 0.19 47.60)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.44 0.01 73.64)\",\n          \"secondary-foreground\": \"oklch(0.92 0.00 48.72)\",\n          \"muted\": \"oklch(0.27 0.01 34.30)\",\n          \"muted-foreground\": \"oklch(0.72 0.01 56.26)\",\n          \"accent\": \"oklch(0.36 0.05 229.32)\",\n          \"accent-foreground\": \"oklch(0.92 0.00 48.72)\",\n          \"destructive\": \"oklch(0.58 0.22 27.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.37 0.01 67.56)\",\n          \"input\": \"oklch(0.37 0.01 67.56)\",\n          \"ring\": \"oklch(0.70 0.19 47.60)\",\n          \"chart-1\": \"oklch(0.70 0.19 47.60)\",\n          \"chart-2\": \"oklch(0.68 0.15 237.32)\",\n          \"chart-3\": \"oklch(0.80 0.16 86.05)\",\n          \"chart-4\": \"oklch(0.72 0.01 56.26)\",\n          \"chart-5\": \"oklch(0.55 0.01 58.07)\",\n          \"radius\": \"0.3rem\",\n          \"sidebar\": \"oklch(0.27 0.01 34.30)\",\n          \"sidebar-foreground\": \"oklch(0.97 0.00 106.42)\",\n          \"sidebar-primary\": \"oklch(0.70 0.19 47.60)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.68 0.15 237.32)\",\n          \"sidebar-accent-foreground\": \"oklch(0.28 0.07 254.54)\",\n          \"sidebar-border\": \"oklch(0.37 0.01 67.56)\",\n          \"sidebar-ring\": \"oklch(0.70 0.19 47.60)\",\n          \"font-sans\": \"Oxanium, sans-serif\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(0 0% 5%)\",\n          \"shadow-opacity\": \"0.18\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 2px 3px 0px hsl(0 0% 5% / 0.09)\",\n          \"shadow-xs\": \"0px 2px 3px 0px hsl(0 0% 5% / 0.09)\",\n          \"shadow-sm\": \"0px 2px 3px 0px hsl(0 0% 5% / 0.18), 0px 1px 2px -1px hsl(0 0% 5% / 0.18)\",\n          \"shadow\": \"0px 2px 3px 0px hsl(0 0% 5% / 0.18), 0px 1px 2px -1px hsl(0 0% 5% / 0.18)\",\n          \"shadow-md\": \"0px 2px 3px 0px hsl(0 0% 5% / 0.18), 0px 2px 4px -1px hsl(0 0% 5% / 0.18)\",\n          \"shadow-lg\": \"0px 2px 3px 0px hsl(0 0% 5% / 0.18), 0px 4px 6px -1px hsl(0 0% 5% / 0.18)\",\n          \"shadow-xl\": \"0px 2px 3px 0px hsl(0 0% 5% / 0.18), 0px 8px 10px -1px hsl(0 0% 5% / 0.18)\",\n          \"shadow-2xl\": \"0px 2px 3px 0px hsl(0 0% 5% / 0.45)\"\n        }\n      }\n    },\n    {\n      \"name\": \"claymorphism\",\n      \"type\": \"registry:style\",\n      \"title\": \"Claymorphism\",\n      \"description\": \"A theme based on the Claymorphism color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Plus Jakarta Sans, sans-serif\",\n          \"font-mono\": \"Roboto Mono, monospace\",\n          \"font-serif\": \"Lora, serif\",\n          \"radius\": \"1.25rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.92 0.00 48.72)\",\n          \"foreground\": \"oklch(0.28 0.04 260.03)\",\n          \"card\": \"oklch(0.97 0.00 106.42)\",\n          \"card-foreground\": \"oklch(0.28 0.04 260.03)\",\n          \"popover\": \"oklch(0.97 0.00 106.42)\",\n          \"popover-foreground\": \"oklch(0.28 0.04 260.03)\",\n          \"primary\": \"oklch(0.59 0.20 277.12)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.87 0.00 56.37)\",\n          \"secondary-foreground\": \"oklch(0.45 0.03 256.80)\",\n          \"muted\": \"oklch(0.92 0.00 48.72)\",\n          \"muted-foreground\": \"oklch(0.55 0.02 264.36)\",\n          \"accent\": \"oklch(0.94 0.03 321.94)\",\n          \"accent-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.87 0.00 56.37)\",\n          \"input\": \"oklch(0.87 0.00 56.37)\",\n          \"ring\": \"oklch(0.59 0.20 277.12)\",\n          \"chart-1\": \"oklch(0.59 0.20 277.12)\",\n          \"chart-2\": \"oklch(0.51 0.23 276.97)\",\n          \"chart-3\": \"oklch(0.46 0.21 277.02)\",\n          \"chart-4\": \"oklch(0.40 0.18 277.37)\",\n          \"chart-5\": \"oklch(0.36 0.14 278.70)\",\n          \"radius\": \"1.25rem\",\n          \"sidebar\": \"oklch(0.87 0.00 56.37)\",\n          \"sidebar-foreground\": \"oklch(0.28 0.04 260.03)\",\n          \"sidebar-primary\": \"oklch(0.59 0.20 277.12)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.94 0.03 321.94)\",\n          \"sidebar-accent-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"sidebar-border\": \"oklch(0.87 0.00 56.37)\",\n          \"sidebar-ring\": \"oklch(0.59 0.20 277.12)\",\n          \"font-sans\": \"Plus Jakarta Sans, sans-serif\",\n          \"font-serif\": \"Lora, serif\",\n          \"font-mono\": \"Roboto Mono, monospace\",\n          \"shadow-color\": \"hsl(240 4% 60%)\",\n          \"shadow-opacity\": \"0.18\",\n          \"shadow-blur\": \"10px\",\n          \"shadow-spread\": \"4px\",\n          \"shadow-offset-x\": \"2px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"2px 2px 10px 4px hsl(240 4% 60% / 0.09)\",\n          \"shadow-xs\": \"2px 2px 10px 4px hsl(240 4% 60% / 0.09)\",\n          \"shadow-sm\": \"2px 2px 10px 4px hsl(240 4% 60% / 0.18), 2px 1px 2px 3px hsl(240 4% 60% / 0.18)\",\n          \"shadow\": \"2px 2px 10px 4px hsl(240 4% 60% / 0.18), 2px 1px 2px 3px hsl(240 4% 60% / 0.18)\",\n          \"shadow-md\": \"2px 2px 10px 4px hsl(240 4% 60% / 0.18), 2px 2px 4px 3px hsl(240 4% 60% / 0.18)\",\n          \"shadow-lg\": \"2px 2px 10px 4px hsl(240 4% 60% / 0.18), 2px 4px 6px 3px hsl(240 4% 60% / 0.18)\",\n          \"shadow-xl\": \"2px 2px 10px 4px hsl(240 4% 60% / 0.18), 2px 8px 10px 3px hsl(240 4% 60% / 0.18)\",\n          \"shadow-2xl\": \"2px 2px 10px 4px hsl(240 4% 60% / 0.45)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.22 0.01 67.44)\",\n          \"foreground\": \"oklch(0.93 0.01 255.51)\",\n          \"card\": \"oklch(0.28 0.01 59.34)\",\n          \"card-foreground\": \"oklch(0.93 0.01 255.51)\",\n          \"popover\": \"oklch(0.28 0.01 59.34)\",\n          \"popover-foreground\": \"oklch(0.93 0.01 255.51)\",\n          \"primary\": \"oklch(0.68 0.16 276.93)\",\n          \"primary-foreground\": \"oklch(0.22 0.01 67.44)\",\n          \"secondary\": \"oklch(0.34 0.01 59.42)\",\n          \"secondary-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"muted\": \"oklch(0.28 0.01 59.34)\",\n          \"muted-foreground\": \"oklch(0.71 0.02 261.32)\",\n          \"accent\": \"oklch(0.39 0.01 59.47)\",\n          \"accent-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(0.22 0.01 67.44)\",\n          \"border\": \"oklch(0.34 0.01 59.42)\",\n          \"input\": \"oklch(0.34 0.01 59.42)\",\n          \"ring\": \"oklch(0.68 0.16 276.93)\",\n          \"chart-1\": \"oklch(0.68 0.16 276.93)\",\n          \"chart-2\": \"oklch(0.59 0.20 277.12)\",\n          \"chart-3\": \"oklch(0.51 0.23 276.97)\",\n          \"chart-4\": \"oklch(0.46 0.21 277.02)\",\n          \"chart-5\": \"oklch(0.40 0.18 277.37)\",\n          \"radius\": \"1.25rem\",\n          \"sidebar\": \"oklch(0.34 0.01 59.42)\",\n          \"sidebar-foreground\": \"oklch(0.93 0.01 255.51)\",\n          \"sidebar-primary\": \"oklch(0.68 0.16 276.93)\",\n          \"sidebar-primary-foreground\": \"oklch(0.22 0.01 67.44)\",\n          \"sidebar-accent\": \"oklch(0.39 0.01 59.47)\",\n          \"sidebar-accent-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"sidebar-border\": \"oklch(0.34 0.01 59.42)\",\n          \"sidebar-ring\": \"oklch(0.68 0.16 276.93)\",\n          \"font-sans\": \"Plus Jakarta Sans, sans-serif\",\n          \"font-serif\": \"Lora, serif\",\n          \"font-mono\": \"Roboto Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.18\",\n          \"shadow-blur\": \"10px\",\n          \"shadow-spread\": \"4px\",\n          \"shadow-offset-x\": \"2px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"2px 2px 10px 4px hsl(0 0% 0% / 0.09)\",\n          \"shadow-xs\": \"2px 2px 10px 4px hsl(0 0% 0% / 0.09)\",\n          \"shadow-sm\": \"2px 2px 10px 4px hsl(0 0% 0% / 0.18), 2px 1px 2px 3px hsl(0 0% 0% / 0.18)\",\n          \"shadow\": \"2px 2px 10px 4px hsl(0 0% 0% / 0.18), 2px 1px 2px 3px hsl(0 0% 0% / 0.18)\",\n          \"shadow-md\": \"2px 2px 10px 4px hsl(0 0% 0% / 0.18), 2px 2px 4px 3px hsl(0 0% 0% / 0.18)\",\n          \"shadow-lg\": \"2px 2px 10px 4px hsl(0 0% 0% / 0.18), 2px 4px 6px 3px hsl(0 0% 0% / 0.18)\",\n          \"shadow-xl\": \"2px 2px 10px 4px hsl(0 0% 0% / 0.18), 2px 8px 10px 3px hsl(0 0% 0% / 0.18)\",\n          \"shadow-2xl\": \"2px 2px 10px 4px hsl(0 0% 0% / 0.45)\"\n        }\n      }\n    },\n    {\n      \"name\": \"cyberpunk\",\n      \"type\": \"registry:style\",\n      \"title\": \"Cyberpunk\",\n      \"description\": \"A theme based on the Cyberpunk color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Outfit, sans-serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0.00 247.84)\",\n          \"foreground\": \"oklch(0.16 0.04 281.83)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.16 0.04 281.83)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.16 0.04 281.83)\",\n          \"primary\": \"oklch(0.67 0.29 341.41)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.96 0.02 286.02)\",\n          \"secondary-foreground\": \"oklch(0.16 0.04 281.83)\",\n          \"muted\": \"oklch(0.96 0.02 286.02)\",\n          \"muted-foreground\": \"oklch(0.16 0.04 281.83)\",\n          \"accent\": \"oklch(0.89 0.17 171.27)\",\n          \"accent-foreground\": \"oklch(0.16 0.04 281.83)\",\n          \"destructive\": \"oklch(0.65 0.23 34.04)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.92 0.01 225.09)\",\n          \"input\": \"oklch(0.92 0.01 225.09)\",\n          \"ring\": \"oklch(0.67 0.29 341.41)\",\n          \"chart-1\": \"oklch(0.67 0.29 341.41)\",\n          \"chart-2\": \"oklch(0.55 0.29 299.10)\",\n          \"chart-3\": \"oklch(0.84 0.15 209.29)\",\n          \"chart-4\": \"oklch(0.89 0.17 171.27)\",\n          \"chart-5\": \"oklch(0.92 0.19 101.41)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.96 0.02 286.02)\",\n          \"sidebar-foreground\": \"oklch(0.16 0.04 281.83)\",\n          \"sidebar-primary\": \"oklch(0.67 0.29 341.41)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.89 0.17 171.27)\",\n          \"sidebar-accent-foreground\": \"oklch(0.16 0.04 281.83)\",\n          \"sidebar-border\": \"oklch(0.92 0.01 225.09)\",\n          \"sidebar-ring\": \"oklch(0.67 0.29 341.41)\",\n          \"font-sans\": \"Outfit, sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"8px\",\n          \"shadow-spread\": \"-2px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.10), 0px 1px 2px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.10), 0px 1px 2px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.10), 0px 2px 4px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.10), 0px 4px 6px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.10), 0px 8px 10px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.16 0.04 281.83)\",\n          \"foreground\": \"oklch(0.95 0.01 260.73)\",\n          \"card\": \"oklch(0.25 0.06 281.14)\",\n          \"card-foreground\": \"oklch(0.95 0.01 260.73)\",\n          \"popover\": \"oklch(0.25 0.06 281.14)\",\n          \"popover-foreground\": \"oklch(0.95 0.01 260.73)\",\n          \"primary\": \"oklch(0.67 0.29 341.41)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.25 0.06 281.14)\",\n          \"secondary-foreground\": \"oklch(0.95 0.01 260.73)\",\n          \"muted\": \"oklch(0.25 0.06 281.14)\",\n          \"muted-foreground\": \"oklch(0.62 0.05 278.10)\",\n          \"accent\": \"oklch(0.89 0.17 171.27)\",\n          \"accent-foreground\": \"oklch(0.16 0.04 281.83)\",\n          \"destructive\": \"oklch(0.65 0.23 34.04)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.33 0.08 280.79)\",\n          \"input\": \"oklch(0.33 0.08 280.79)\",\n          \"ring\": \"oklch(0.67 0.29 341.41)\",\n          \"chart-1\": \"oklch(0.67 0.29 341.41)\",\n          \"chart-2\": \"oklch(0.55 0.29 299.10)\",\n          \"chart-3\": \"oklch(0.84 0.15 209.29)\",\n          \"chart-4\": \"oklch(0.89 0.17 171.27)\",\n          \"chart-5\": \"oklch(0.92 0.19 101.41)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.16 0.04 281.83)\",\n          \"sidebar-foreground\": \"oklch(0.95 0.01 260.73)\",\n          \"sidebar-primary\": \"oklch(0.67 0.29 341.41)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.89 0.17 171.27)\",\n          \"sidebar-accent-foreground\": \"oklch(0.16 0.04 281.83)\",\n          \"sidebar-border\": \"oklch(0.33 0.08 280.79)\",\n          \"sidebar-ring\": \"oklch(0.67 0.29 341.41)\",\n          \"font-sans\": \"Outfit, sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"Fira Code, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"8px\",\n          \"shadow-spread\": \"-2px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.10), 0px 1px 2px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.10), 0px 1px 2px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.10), 0px 2px 4px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.10), 0px 4px 6px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.10), 0px 8px 10px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 4px 8px -2px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"pastel-dreams\",\n      \"type\": \"registry:style\",\n      \"title\": \"Pastel Dreams\",\n      \"description\": \"A theme based on the Pastel Dreams color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Open Sans, sans-serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"radius\": \"1.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.97 0.01 314.78)\",\n          \"foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"primary\": \"oklch(0.71 0.16 293.54)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.91 0.05 306.09)\",\n          \"secondary-foreground\": \"oklch(0.45 0.03 256.80)\",\n          \"muted\": \"oklch(0.95 0.03 307.17)\",\n          \"muted-foreground\": \"oklch(0.55 0.02 264.36)\",\n          \"accent\": \"oklch(0.94 0.03 321.94)\",\n          \"accent-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"destructive\": \"oklch(0.81 0.10 19.57)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.91 0.05 306.09)\",\n          \"input\": \"oklch(0.91 0.05 306.09)\",\n          \"ring\": \"oklch(0.71 0.16 293.54)\",\n          \"chart-1\": \"oklch(0.71 0.16 293.54)\",\n          \"chart-2\": \"oklch(0.61 0.22 292.72)\",\n          \"chart-3\": \"oklch(0.54 0.25 293.01)\",\n          \"chart-4\": \"oklch(0.49 0.24 292.58)\",\n          \"chart-5\": \"oklch(0.43 0.21 292.76)\",\n          \"radius\": \"1.5rem\",\n          \"sidebar\": \"oklch(0.91 0.05 306.09)\",\n          \"sidebar-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"sidebar-primary\": \"oklch(0.71 0.16 293.54)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.94 0.03 321.94)\",\n          \"sidebar-accent-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"sidebar-border\": \"oklch(0.91 0.05 306.09)\",\n          \"sidebar-ring\": \"oklch(0.71 0.16 293.54)\",\n          \"font-sans\": \"Open Sans, sans-serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.08\",\n          \"shadow-blur\": \"16px\",\n          \"shadow-spread\": \"-4px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"8px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.04)\",\n          \"shadow-xs\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.04)\",\n          \"shadow-sm\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.08), 0px 1px 2px -5px hsl(0 0% 0% / 0.08)\",\n          \"shadow\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.08), 0px 1px 2px -5px hsl(0 0% 0% / 0.08)\",\n          \"shadow-md\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.08), 0px 2px 4px -5px hsl(0 0% 0% / 0.08)\",\n          \"shadow-lg\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.08), 0px 4px 6px -5px hsl(0 0% 0% / 0.08)\",\n          \"shadow-xl\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.08), 0px 8px 10px -5px hsl(0 0% 0% / 0.08)\",\n          \"shadow-2xl\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.20)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.22 0.01 56.04)\",\n          \"foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"card\": \"oklch(0.28 0.03 307.23)\",\n          \"card-foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"popover\": \"oklch(0.28 0.03 307.23)\",\n          \"popover-foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"primary\": \"oklch(0.79 0.12 295.75)\",\n          \"primary-foreground\": \"oklch(0.22 0.01 56.04)\",\n          \"secondary\": \"oklch(0.34 0.04 308.85)\",\n          \"secondary-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"muted\": \"oklch(0.28 0.03 307.23)\",\n          \"muted-foreground\": \"oklch(0.71 0.02 261.32)\",\n          \"accent\": \"oklch(0.39 0.05 304.64)\",\n          \"accent-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"destructive\": \"oklch(0.81 0.10 19.57)\",\n          \"destructive-foreground\": \"oklch(0.22 0.01 56.04)\",\n          \"border\": \"oklch(0.34 0.04 308.85)\",\n          \"input\": \"oklch(0.34 0.04 308.85)\",\n          \"ring\": \"oklch(0.79 0.12 295.75)\",\n          \"chart-1\": \"oklch(0.79 0.12 295.75)\",\n          \"chart-2\": \"oklch(0.71 0.16 293.54)\",\n          \"chart-3\": \"oklch(0.61 0.22 292.72)\",\n          \"chart-4\": \"oklch(0.54 0.25 293.01)\",\n          \"chart-5\": \"oklch(0.49 0.24 292.58)\",\n          \"radius\": \"1.5rem\",\n          \"sidebar\": \"oklch(0.34 0.04 308.85)\",\n          \"sidebar-foreground\": \"oklch(0.93 0.03 272.79)\",\n          \"sidebar-primary\": \"oklch(0.79 0.12 295.75)\",\n          \"sidebar-primary-foreground\": \"oklch(0.22 0.01 56.04)\",\n          \"sidebar-accent\": \"oklch(0.39 0.05 304.64)\",\n          \"sidebar-accent-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"sidebar-border\": \"oklch(0.34 0.04 308.85)\",\n          \"sidebar-ring\": \"oklch(0.79 0.12 295.75)\",\n          \"font-sans\": \"Open Sans, sans-serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.08\",\n          \"shadow-blur\": \"16px\",\n          \"shadow-spread\": \"-4px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"8px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.04)\",\n          \"shadow-xs\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.04)\",\n          \"shadow-sm\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.08), 0px 1px 2px -5px hsl(0 0% 0% / 0.08)\",\n          \"shadow\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.08), 0px 1px 2px -5px hsl(0 0% 0% / 0.08)\",\n          \"shadow-md\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.08), 0px 2px 4px -5px hsl(0 0% 0% / 0.08)\",\n          \"shadow-lg\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.08), 0px 4px 6px -5px hsl(0 0% 0% / 0.08)\",\n          \"shadow-xl\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.08), 0px 8px 10px -5px hsl(0 0% 0% / 0.08)\",\n          \"shadow-2xl\": \"0px 8px 16px -4px hsl(0 0% 0% / 0.20)\"\n        }\n      }\n    },\n    {\n      \"name\": \"clean-slate\",\n      \"type\": \"registry:style\",\n      \"title\": \"Clean Slate\",\n      \"description\": \"A theme based on the Clean Slate color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0.00 247.86)\",\n          \"foreground\": \"oklch(0.28 0.04 260.03)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.28 0.04 260.03)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.28 0.04 260.03)\",\n          \"primary\": \"oklch(0.59 0.20 277.12)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.93 0.01 264.53)\",\n          \"secondary-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"muted\": \"oklch(0.97 0.00 264.54)\",\n          \"muted-foreground\": \"oklch(0.55 0.02 264.36)\",\n          \"accent\": \"oklch(0.93 0.03 272.79)\",\n          \"accent-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.87 0.01 258.34)\",\n          \"input\": \"oklch(0.87 0.01 258.34)\",\n          \"ring\": \"oklch(0.59 0.20 277.12)\",\n          \"chart-1\": \"oklch(0.59 0.20 277.12)\",\n          \"chart-2\": \"oklch(0.51 0.23 276.97)\",\n          \"chart-3\": \"oklch(0.46 0.21 277.02)\",\n          \"chart-4\": \"oklch(0.40 0.18 277.37)\",\n          \"chart-5\": \"oklch(0.36 0.14 278.70)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.97 0.00 264.54)\",\n          \"sidebar-foreground\": \"oklch(0.28 0.04 260.03)\",\n          \"sidebar-primary\": \"oklch(0.59 0.20 277.12)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.93 0.03 272.79)\",\n          \"sidebar-accent-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"sidebar-border\": \"oklch(0.87 0.01 258.34)\",\n          \"sidebar-ring\": \"oklch(0.59 0.20 277.12)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"8px\",\n          \"shadow-spread\": \"-1px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.21 0.04 265.75)\",\n          \"foreground\": \"oklch(0.93 0.01 255.51)\",\n          \"card\": \"oklch(0.28 0.04 260.03)\",\n          \"card-foreground\": \"oklch(0.93 0.01 255.51)\",\n          \"popover\": \"oklch(0.28 0.04 260.03)\",\n          \"popover-foreground\": \"oklch(0.93 0.01 255.51)\",\n          \"primary\": \"oklch(0.68 0.16 276.93)\",\n          \"primary-foreground\": \"oklch(0.21 0.04 265.75)\",\n          \"secondary\": \"oklch(0.34 0.03 260.91)\",\n          \"secondary-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"muted\": \"oklch(0.28 0.04 260.03)\",\n          \"muted-foreground\": \"oklch(0.71 0.02 261.32)\",\n          \"accent\": \"oklch(0.37 0.03 259.73)\",\n          \"accent-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(0.21 0.04 265.75)\",\n          \"border\": \"oklch(0.45 0.03 256.80)\",\n          \"input\": \"oklch(0.45 0.03 256.80)\",\n          \"ring\": \"oklch(0.68 0.16 276.93)\",\n          \"chart-1\": \"oklch(0.68 0.16 276.93)\",\n          \"chart-2\": \"oklch(0.59 0.20 277.12)\",\n          \"chart-3\": \"oklch(0.51 0.23 276.97)\",\n          \"chart-4\": \"oklch(0.46 0.21 277.02)\",\n          \"chart-5\": \"oklch(0.40 0.18 277.37)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.28 0.04 260.03)\",\n          \"sidebar-foreground\": \"oklch(0.93 0.01 255.51)\",\n          \"sidebar-primary\": \"oklch(0.68 0.16 276.93)\",\n          \"sidebar-primary-foreground\": \"oklch(0.21 0.04 265.75)\",\n          \"sidebar-accent\": \"oklch(0.37 0.03 259.73)\",\n          \"sidebar-accent-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"sidebar-border\": \"oklch(0.45 0.03 256.80)\",\n          \"sidebar-ring\": \"oklch(0.68 0.16 276.93)\",\n          \"font-sans\": \"Inter, sans-serif\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"8px\",\n          \"shadow-spread\": \"-1px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"caffeine\",\n      \"type\": \"registry:style\",\n      \"title\": \"Caffeine\",\n      \"description\": \"A theme based on the Caffeine color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0 0)\",\n          \"foreground\": \"oklch(0.24 0 0)\",\n          \"card\": \"oklch(0.99 0 0)\",\n          \"card-foreground\": \"oklch(0.24 0 0)\",\n          \"popover\": \"oklch(0.99 0 0)\",\n          \"popover-foreground\": \"oklch(0.24 0 0)\",\n          \"primary\": \"oklch(0.43 0.04 41.99)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.92 0.07 74.37)\",\n          \"secondary-foreground\": \"oklch(0.35 0.07 40.83)\",\n          \"muted\": \"oklch(0.95 0 0)\",\n          \"muted-foreground\": \"oklch(0.50 0 0)\",\n          \"accent\": \"oklch(0.93 0 0)\",\n          \"accent-foreground\": \"oklch(0.24 0 0)\",\n          \"destructive\": \"oklch(0.63 0.19 33.34)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.88 0 0)\",\n          \"input\": \"oklch(0.88 0 0)\",\n          \"ring\": \"oklch(0.43 0.04 41.99)\",\n          \"chart-1\": \"oklch(0.43 0.04 41.99)\",\n          \"chart-2\": \"oklch(0.92 0.07 74.37)\",\n          \"chart-3\": \"oklch(0.93 0 0)\",\n          \"chart-4\": \"oklch(0.94 0.05 75.50)\",\n          \"chart-5\": \"oklch(0.43 0.04 41.67)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.99 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.26 0 0)\",\n          \"sidebar-primary\": \"oklch(0.33 0 0)\",\n          \"sidebar-primary-foreground\": \"oklch(0.99 0 0)\",\n          \"sidebar-accent\": \"oklch(0.98 0 0)\",\n          \"sidebar-accent-foreground\": \"oklch(0.33 0 0)\",\n          \"sidebar-border\": \"oklch(0.94 0 0)\",\n          \"sidebar-ring\": \"oklch(0.77 0 0)\",\n          \"font-sans\": \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.18 0 0)\",\n          \"foreground\": \"oklch(0.95 0 0)\",\n          \"card\": \"oklch(0.21 0 0)\",\n          \"card-foreground\": \"oklch(0.95 0 0)\",\n          \"popover\": \"oklch(0.21 0 0)\",\n          \"popover-foreground\": \"oklch(0.95 0 0)\",\n          \"primary\": \"oklch(0.92 0.05 66.17)\",\n          \"primary-foreground\": \"oklch(0.20 0.02 200.20)\",\n          \"secondary\": \"oklch(0.32 0.02 63.70)\",\n          \"secondary-foreground\": \"oklch(0.92 0.05 66.17)\",\n          \"muted\": \"oklch(0.25 0 0)\",\n          \"muted-foreground\": \"oklch(0.77 0 0)\",\n          \"accent\": \"oklch(0.29 0 0)\",\n          \"accent-foreground\": \"oklch(0.95 0 0)\",\n          \"destructive\": \"oklch(0.63 0.19 33.34)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.24 0.01 91.75)\",\n          \"input\": \"oklch(0.40 0 0)\",\n          \"ring\": \"oklch(0.92 0.05 66.17)\",\n          \"chart-1\": \"oklch(0.92 0.05 66.17)\",\n          \"chart-2\": \"oklch(0.32 0.02 63.70)\",\n          \"chart-3\": \"oklch(0.29 0 0)\",\n          \"chart-4\": \"oklch(0.35 0.02 67.00)\",\n          \"chart-5\": \"oklch(0.92 0.05 67.09)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.21 0.01 285.89)\",\n          \"sidebar-foreground\": \"oklch(0.97 0.00 286.38)\",\n          \"sidebar-primary\": \"oklch(0.49 0.22 264.38)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.27 0.01 286.03)\",\n          \"sidebar-accent-foreground\": \"oklch(0.97 0.00 286.38)\",\n          \"sidebar-border\": \"oklch(0.27 0.01 286.03)\",\n          \"sidebar-ring\": \"oklch(0.87 0.01 286.29)\",\n          \"font-sans\": \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"ocean-breeze\",\n      \"type\": \"registry:style\",\n      \"title\": \"Ocean Breeze\",\n      \"description\": \"A theme based on the Ocean Breeze color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"DM Sans, sans-serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"font-serif\": \"Lora, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0.01 244.25)\",\n          \"foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"primary\": \"oklch(0.72 0.19 149.58)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.95 0.03 236.82)\",\n          \"secondary-foreground\": \"oklch(0.45 0.03 256.80)\",\n          \"muted\": \"oklch(0.97 0.00 264.54)\",\n          \"muted-foreground\": \"oklch(0.55 0.02 264.36)\",\n          \"accent\": \"oklch(0.95 0.05 163.05)\",\n          \"accent-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.93 0.01 264.53)\",\n          \"input\": \"oklch(0.93 0.01 264.53)\",\n          \"ring\": \"oklch(0.72 0.19 149.58)\",\n          \"chart-1\": \"oklch(0.72 0.19 149.58)\",\n          \"chart-2\": \"oklch(0.70 0.15 162.48)\",\n          \"chart-3\": \"oklch(0.60 0.13 163.23)\",\n          \"chart-4\": \"oklch(0.51 0.10 165.61)\",\n          \"chart-5\": \"oklch(0.43 0.09 166.91)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.95 0.03 236.82)\",\n          \"sidebar-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"sidebar-primary\": \"oklch(0.72 0.19 149.58)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.95 0.05 163.05)\",\n          \"sidebar-accent-foreground\": \"oklch(0.37 0.03 259.73)\",\n          \"sidebar-border\": \"oklch(0.93 0.01 264.53)\",\n          \"sidebar-ring\": \"oklch(0.72 0.19 149.58)\",\n          \"font-sans\": \"DM Sans, sans-serif\",\n          \"font-serif\": \"Lora, serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"8px\",\n          \"shadow-spread\": \"-1px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.21 0.04 265.75)\",\n          \"foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"card\": \"oklch(0.28 0.04 260.03)\",\n          \"card-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"popover\": \"oklch(0.28 0.04 260.03)\",\n          \"popover-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"primary\": \"oklch(0.77 0.15 163.22)\",\n          \"primary-foreground\": \"oklch(0.21 0.04 265.75)\",\n          \"secondary\": \"oklch(0.34 0.03 260.91)\",\n          \"secondary-foreground\": \"oklch(0.71 0.01 286.07)\",\n          \"muted\": \"oklch(0.28 0.04 260.03)\",\n          \"muted-foreground\": \"oklch(0.55 0.02 264.36)\",\n          \"accent\": \"oklch(0.37 0.03 259.73)\",\n          \"accent-foreground\": \"oklch(0.71 0.01 286.07)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(0.21 0.04 265.75)\",\n          \"border\": \"oklch(0.45 0.03 256.80)\",\n          \"input\": \"oklch(0.45 0.03 256.80)\",\n          \"ring\": \"oklch(0.77 0.15 163.22)\",\n          \"chart-1\": \"oklch(0.77 0.15 163.22)\",\n          \"chart-2\": \"oklch(0.78 0.13 181.91)\",\n          \"chart-3\": \"oklch(0.72 0.19 149.58)\",\n          \"chart-4\": \"oklch(0.70 0.15 162.48)\",\n          \"chart-5\": \"oklch(0.60 0.13 163.23)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.28 0.04 260.03)\",\n          \"sidebar-foreground\": \"oklch(0.87 0.01 258.34)\",\n          \"sidebar-primary\": \"oklch(0.77 0.15 163.22)\",\n          \"sidebar-primary-foreground\": \"oklch(0.21 0.04 265.75)\",\n          \"sidebar-accent\": \"oklch(0.37 0.03 259.73)\",\n          \"sidebar-accent-foreground\": \"oklch(0.71 0.01 286.07)\",\n          \"sidebar-border\": \"oklch(0.45 0.03 256.80)\",\n          \"sidebar-ring\": \"oklch(0.77 0.15 163.22)\",\n          \"font-sans\": \"DM Sans, sans-serif\",\n          \"font-serif\": \"Lora, serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"8px\",\n          \"shadow-spread\": \"-1px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"4px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 4px 8px -1px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"retro-arcade\",\n      \"type\": \"registry:style\",\n      \"title\": \"Retro Arcade\",\n      \"description\": \"A theme based on the Retro Arcade color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Outfit, sans-serif\",\n          \"font-mono\": \"Space Mono, monospace\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"radius\": \"0.25rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.97 0.03 90.10)\",\n          \"foreground\": \"oklch(0.31 0.05 219.65)\",\n          \"card\": \"oklch(0.93 0.03 92.40)\",\n          \"card-foreground\": \"oklch(0.31 0.05 219.65)\",\n          \"popover\": \"oklch(0.93 0.03 92.40)\",\n          \"popover-foreground\": \"oklch(0.31 0.05 219.65)\",\n          \"primary\": \"oklch(0.59 0.20 355.89)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.64 0.10 187.38)\",\n          \"secondary-foreground\": \"oklch(1.00 0 0)\",\n          \"muted\": \"oklch(0.70 0.02 196.79)\",\n          \"muted-foreground\": \"oklch(0.31 0.05 219.65)\",\n          \"accent\": \"oklch(0.58 0.17 39.50)\",\n          \"accent-foreground\": \"oklch(1.00 0 0)\",\n          \"destructive\": \"oklch(0.59 0.21 27.12)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.65 0.02 205.26)\",\n          \"input\": \"oklch(0.65 0.02 205.26)\",\n          \"ring\": \"oklch(0.59 0.20 355.89)\",\n          \"chart-1\": \"oklch(0.61 0.14 244.93)\",\n          \"chart-2\": \"oklch(0.64 0.10 187.38)\",\n          \"chart-3\": \"oklch(0.59 0.20 355.89)\",\n          \"chart-4\": \"oklch(0.58 0.17 39.50)\",\n          \"chart-5\": \"oklch(0.59 0.21 27.12)\",\n          \"radius\": \"0.25rem\",\n          \"sidebar\": \"oklch(0.97 0.03 90.10)\",\n          \"sidebar-foreground\": \"oklch(0.31 0.05 219.65)\",\n          \"sidebar-primary\": \"oklch(0.59 0.20 355.89)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.64 0.10 187.38)\",\n          \"sidebar-accent-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-border\": \"oklch(0.65 0.02 205.26)\",\n          \"sidebar-ring\": \"oklch(0.59 0.20 355.89)\",\n          \"font-sans\": \"Outfit, sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"Space Mono, monospace\",\n          \"shadow-color\": \"hsl(196 83% 10%)\",\n          \"shadow-opacity\": \"0.15\",\n          \"shadow-blur\": \"4px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"2px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.07)\",\n          \"shadow-xs\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.07)\",\n          \"shadow-sm\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.15), 2px 1px 2px -1px hsl(196 83% 10% / 0.15)\",\n          \"shadow\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.15), 2px 1px 2px -1px hsl(196 83% 10% / 0.15)\",\n          \"shadow-md\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.15), 2px 2px 4px -1px hsl(196 83% 10% / 0.15)\",\n          \"shadow-lg\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.15), 2px 4px 6px -1px hsl(196 83% 10% / 0.15)\",\n          \"shadow-xl\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.15), 2px 8px 10px -1px hsl(196 83% 10% / 0.15)\",\n          \"shadow-2xl\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.38)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.27 0.05 219.82)\",\n          \"foreground\": \"oklch(0.70 0.02 196.79)\",\n          \"card\": \"oklch(0.31 0.05 219.65)\",\n          \"card-foreground\": \"oklch(0.70 0.02 196.79)\",\n          \"popover\": \"oklch(0.31 0.05 219.65)\",\n          \"popover-foreground\": \"oklch(0.70 0.02 196.79)\",\n          \"primary\": \"oklch(0.59 0.20 355.89)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.64 0.10 187.38)\",\n          \"secondary-foreground\": \"oklch(1.00 0 0)\",\n          \"muted\": \"oklch(0.52 0.03 219.14)\",\n          \"muted-foreground\": \"oklch(0.70 0.02 196.79)\",\n          \"accent\": \"oklch(0.58 0.17 39.50)\",\n          \"accent-foreground\": \"oklch(1.00 0 0)\",\n          \"destructive\": \"oklch(0.59 0.21 27.12)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.52 0.03 219.14)\",\n          \"input\": \"oklch(0.52 0.03 219.14)\",\n          \"ring\": \"oklch(0.59 0.20 355.89)\",\n          \"chart-1\": \"oklch(0.61 0.14 244.93)\",\n          \"chart-2\": \"oklch(0.64 0.10 187.38)\",\n          \"chart-3\": \"oklch(0.59 0.20 355.89)\",\n          \"chart-4\": \"oklch(0.58 0.17 39.50)\",\n          \"chart-5\": \"oklch(0.59 0.21 27.12)\",\n          \"radius\": \"0.25rem\",\n          \"sidebar\": \"oklch(0.27 0.05 219.82)\",\n          \"sidebar-foreground\": \"oklch(0.70 0.02 196.79)\",\n          \"sidebar-primary\": \"oklch(0.59 0.20 355.89)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.64 0.10 187.38)\",\n          \"sidebar-accent-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-border\": \"oklch(0.52 0.03 219.14)\",\n          \"sidebar-ring\": \"oklch(0.59 0.20 355.89)\",\n          \"font-sans\": \"Outfit, sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"Space Mono, monospace\",\n          \"shadow-color\": \"hsl(196 83% 10%)\",\n          \"shadow-opacity\": \"0.15\",\n          \"shadow-blur\": \"4px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"2px\",\n          \"shadow-offset-y\": \"2px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.07)\",\n          \"shadow-xs\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.07)\",\n          \"shadow-sm\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.15), 2px 1px 2px -1px hsl(196 83% 10% / 0.15)\",\n          \"shadow\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.15), 2px 1px 2px -1px hsl(196 83% 10% / 0.15)\",\n          \"shadow-md\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.15), 2px 2px 4px -1px hsl(196 83% 10% / 0.15)\",\n          \"shadow-lg\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.15), 2px 4px 6px -1px hsl(196 83% 10% / 0.15)\",\n          \"shadow-xl\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.15), 2px 8px 10px -1px hsl(196 83% 10% / 0.15)\",\n          \"shadow-2xl\": \"2px 2px 4px 0px hsl(196 83% 10% / 0.38)\"\n        }\n      }\n    },\n    {\n      \"name\": \"midnight-bloom\",\n      \"type\": \"registry:style\",\n      \"title\": \"Midnight Bloom\",\n      \"description\": \"A theme based on the Midnight Bloom color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-mono\": \"Source Code Pro, monospace\",\n          \"font-serif\": \"Playfair Display, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0 0)\",\n          \"foreground\": \"oklch(0.32 0 0)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.32 0 0)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.32 0 0)\",\n          \"primary\": \"oklch(0.57 0.20 283.08)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.82 0.07 249.35)\",\n          \"secondary-foreground\": \"oklch(0.32 0 0)\",\n          \"muted\": \"oklch(0.82 0.02 91.62)\",\n          \"muted-foreground\": \"oklch(0.54 0 0)\",\n          \"accent\": \"oklch(0.65 0.06 117.43)\",\n          \"accent-foreground\": \"oklch(1.00 0 0)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.87 0 0)\",\n          \"input\": \"oklch(0.87 0 0)\",\n          \"ring\": \"oklch(0.57 0.20 283.08)\",\n          \"chart-1\": \"oklch(0.57 0.20 283.08)\",\n          \"chart-2\": \"oklch(0.53 0.17 314.65)\",\n          \"chart-3\": \"oklch(0.34 0.18 301.68)\",\n          \"chart-4\": \"oklch(0.67 0.14 261.34)\",\n          \"chart-5\": \"oklch(0.59 0.10 245.74)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.98 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.32 0 0)\",\n          \"sidebar-primary\": \"oklch(0.57 0.20 283.08)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.65 0.06 117.43)\",\n          \"sidebar-accent-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-border\": \"oklch(0.87 0 0)\",\n          \"sidebar-ring\": \"oklch(0.57 0.20 283.08)\",\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-serif\": \"Playfair Display, serif\",\n          \"font-mono\": \"Source Code Pro, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"10px\",\n          \"shadow-spread\": \"-2px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"5px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.10), 0px 1px 2px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.10), 0px 1px 2px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.10), 0px 2px 4px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.10), 0px 4px 6px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.10), 0px 8px 10px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.23 0.01 264.29)\",\n          \"foreground\": \"oklch(0.92 0 0)\",\n          \"card\": \"oklch(0.32 0.01 223.67)\",\n          \"card-foreground\": \"oklch(0.92 0 0)\",\n          \"popover\": \"oklch(0.32 0.01 223.67)\",\n          \"popover-foreground\": \"oklch(0.92 0 0)\",\n          \"primary\": \"oklch(0.57 0.20 283.08)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.34 0.18 301.68)\",\n          \"secondary-foreground\": \"oklch(0.92 0 0)\",\n          \"muted\": \"oklch(0.39 0 0)\",\n          \"muted-foreground\": \"oklch(0.72 0 0)\",\n          \"accent\": \"oklch(0.67 0.14 261.34)\",\n          \"accent-foreground\": \"oklch(0.92 0 0)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.39 0 0)\",\n          \"input\": \"oklch(0.39 0 0)\",\n          \"ring\": \"oklch(0.57 0.20 283.08)\",\n          \"chart-1\": \"oklch(0.57 0.20 283.08)\",\n          \"chart-2\": \"oklch(0.53 0.17 314.65)\",\n          \"chart-3\": \"oklch(0.34 0.18 301.68)\",\n          \"chart-4\": \"oklch(0.67 0.14 261.34)\",\n          \"chart-5\": \"oklch(0.59 0.10 245.74)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.23 0.01 264.29)\",\n          \"sidebar-foreground\": \"oklch(0.92 0 0)\",\n          \"sidebar-primary\": \"oklch(0.57 0.20 283.08)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.67 0.14 261.34)\",\n          \"sidebar-accent-foreground\": \"oklch(0.92 0 0)\",\n          \"sidebar-border\": \"oklch(0.39 0 0)\",\n          \"sidebar-ring\": \"oklch(0.57 0.20 283.08)\",\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-serif\": \"Playfair Display, serif\",\n          \"font-mono\": \"Source Code Pro, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"10px\",\n          \"shadow-spread\": \"-2px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"5px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.10), 0px 1px 2px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.10), 0px 1px 2px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.10), 0px 2px 4px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.10), 0px 4px 6px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.10), 0px 8px 10px -3px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0px 5px 10px -2px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"candyland\",\n      \"type\": \"registry:style\",\n      \"title\": \"Candyland\",\n      \"description\": \"A theme based on the Candyland color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Poppins, sans-serif\",\n          \"font-mono\": \"Roboto Mono, monospace\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0.00 228.78)\",\n          \"foreground\": \"oklch(0.32 0 0)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.32 0 0)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.32 0 0)\",\n          \"primary\": \"oklch(0.87 0.07 7.09)\",\n          \"primary-foreground\": \"oklch(0 0 0)\",\n          \"secondary\": \"oklch(0.81 0.08 225.75)\",\n          \"secondary-foreground\": \"oklch(0 0 0)\",\n          \"muted\": \"oklch(0.88 0.03 98.10)\",\n          \"muted-foreground\": \"oklch(0.54 0 0)\",\n          \"accent\": \"oklch(0.97 0.21 109.77)\",\n          \"accent-foreground\": \"oklch(0 0 0)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.87 0 0)\",\n          \"input\": \"oklch(0.87 0 0)\",\n          \"ring\": \"oklch(0.87 0.07 7.09)\",\n          \"chart-1\": \"oklch(0.87 0.07 7.09)\",\n          \"chart-2\": \"oklch(0.81 0.08 225.75)\",\n          \"chart-3\": \"oklch(0.97 0.21 109.77)\",\n          \"chart-4\": \"oklch(0.80 0.14 349.23)\",\n          \"chart-5\": \"oklch(0.74 0.23 142.85)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.98 0.00 228.78)\",\n          \"sidebar-foreground\": \"oklch(0.32 0 0)\",\n          \"sidebar-primary\": \"oklch(0.87 0.07 7.09)\",\n          \"sidebar-primary-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-accent\": \"oklch(0.97 0.21 109.77)\",\n          \"sidebar-accent-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-border\": \"oklch(0.87 0 0)\",\n          \"sidebar-ring\": \"oklch(0.87 0.07 7.09)\",\n          \"font-sans\": \"Poppins, sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"Roboto Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.23 0.01 264.29)\",\n          \"foreground\": \"oklch(0.92 0 0)\",\n          \"card\": \"oklch(0.32 0.01 223.67)\",\n          \"card-foreground\": \"oklch(0.92 0 0)\",\n          \"popover\": \"oklch(0.32 0.01 223.67)\",\n          \"popover-foreground\": \"oklch(0.92 0 0)\",\n          \"primary\": \"oklch(0.80 0.14 349.23)\",\n          \"primary-foreground\": \"oklch(0 0 0)\",\n          \"secondary\": \"oklch(0.74 0.23 142.85)\",\n          \"secondary-foreground\": \"oklch(0 0 0)\",\n          \"muted\": \"oklch(0.39 0 0)\",\n          \"muted-foreground\": \"oklch(0.72 0 0)\",\n          \"accent\": \"oklch(0.81 0.08 225.75)\",\n          \"accent-foreground\": \"oklch(0 0 0)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.39 0 0)\",\n          \"input\": \"oklch(0.39 0 0)\",\n          \"ring\": \"oklch(0.80 0.14 349.23)\",\n          \"chart-1\": \"oklch(0.80 0.14 349.23)\",\n          \"chart-2\": \"oklch(0.74 0.23 142.85)\",\n          \"chart-3\": \"oklch(0.81 0.08 225.75)\",\n          \"chart-4\": \"oklch(0.97 0.21 109.77)\",\n          \"chart-5\": \"oklch(0.87 0.18 90.38)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.23 0.01 264.29)\",\n          \"sidebar-foreground\": \"oklch(0.92 0 0)\",\n          \"sidebar-primary\": \"oklch(0.80 0.14 349.23)\",\n          \"sidebar-primary-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-accent\": \"oklch(0.81 0.08 225.75)\",\n          \"sidebar-accent-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-border\": \"oklch(0.39 0 0)\",\n          \"sidebar-ring\": \"oklch(0.80 0.14 349.23)\",\n          \"font-sans\": \"Poppins, sans-serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"Roboto Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"northern-lights\",\n      \"type\": \"registry:style\",\n      \"title\": \"Northern Lights\",\n      \"description\": \"A theme based on the Northern Lights color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Plus Jakarta Sans, sans-serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0.00 286.38)\",\n          \"foreground\": \"oklch(0.32 0 0)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.32 0 0)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.32 0 0)\",\n          \"primary\": \"oklch(0.65 0.15 150.31)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.67 0.14 261.34)\",\n          \"secondary-foreground\": \"oklch(1.00 0 0)\",\n          \"muted\": \"oklch(0.88 0.03 98.10)\",\n          \"muted-foreground\": \"oklch(0.54 0 0)\",\n          \"accent\": \"oklch(0.83 0.11 211.96)\",\n          \"accent-foreground\": \"oklch(0.32 0 0)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.87 0 0)\",\n          \"input\": \"oklch(0.87 0 0)\",\n          \"ring\": \"oklch(0.65 0.15 150.31)\",\n          \"chart-1\": \"oklch(0.65 0.15 150.31)\",\n          \"chart-2\": \"oklch(0.67 0.14 261.34)\",\n          \"chart-3\": \"oklch(0.83 0.11 211.96)\",\n          \"chart-4\": \"oklch(0.59 0.10 245.74)\",\n          \"chart-5\": \"oklch(0.59 0.16 148.24)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.98 0.00 286.38)\",\n          \"sidebar-foreground\": \"oklch(0.32 0 0)\",\n          \"sidebar-primary\": \"oklch(0.65 0.15 150.31)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.83 0.11 211.96)\",\n          \"sidebar-accent-foreground\": \"oklch(0.32 0 0)\",\n          \"sidebar-border\": \"oklch(0.87 0 0)\",\n          \"sidebar-ring\": \"oklch(0.65 0.15 150.31)\",\n          \"font-sans\": \"Plus Jakarta Sans, sans-serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.23 0.01 264.29)\",\n          \"foreground\": \"oklch(0.92 0 0)\",\n          \"card\": \"oklch(0.32 0.01 223.67)\",\n          \"card-foreground\": \"oklch(0.92 0 0)\",\n          \"popover\": \"oklch(0.32 0.01 223.67)\",\n          \"popover-foreground\": \"oklch(0.92 0 0)\",\n          \"primary\": \"oklch(0.65 0.15 150.31)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.59 0.10 245.74)\",\n          \"secondary-foreground\": \"oklch(0.92 0 0)\",\n          \"muted\": \"oklch(0.39 0 0)\",\n          \"muted-foreground\": \"oklch(0.72 0 0)\",\n          \"accent\": \"oklch(0.67 0.14 261.34)\",\n          \"accent-foreground\": \"oklch(0.92 0 0)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.39 0 0)\",\n          \"input\": \"oklch(0.39 0 0)\",\n          \"ring\": \"oklch(0.65 0.15 150.31)\",\n          \"chart-1\": \"oklch(0.65 0.15 150.31)\",\n          \"chart-2\": \"oklch(0.59 0.10 245.74)\",\n          \"chart-3\": \"oklch(0.67 0.14 261.34)\",\n          \"chart-4\": \"oklch(0.83 0.11 211.96)\",\n          \"chart-5\": \"oklch(0.59 0.16 148.24)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.23 0.01 264.29)\",\n          \"sidebar-foreground\": \"oklch(0.92 0 0)\",\n          \"sidebar-primary\": \"oklch(0.65 0.15 150.31)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.67 0.14 261.34)\",\n          \"sidebar-accent-foreground\": \"oklch(0.92 0 0)\",\n          \"sidebar-border\": \"oklch(0.39 0 0)\",\n          \"sidebar-ring\": \"oklch(0.65 0.15 150.31)\",\n          \"font-sans\": \"Plus Jakarta Sans, sans-serif\",\n          \"font-serif\": \"Source Serif 4, serif\",\n          \"font-mono\": \"JetBrains Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"vintage-paper\",\n      \"type\": \"registry:style\",\n      \"title\": \"Vintage Paper\",\n      \"description\": \"A theme based on the Vintage Paper color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Libre Baskerville, serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"font-serif\": \"Lora, serif\",\n          \"radius\": \"0.25rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.96 0.02 90.24)\",\n          \"foreground\": \"oklch(0.38 0.02 64.34)\",\n          \"card\": \"oklch(0.99 0.01 87.47)\",\n          \"card-foreground\": \"oklch(0.38 0.02 64.34)\",\n          \"popover\": \"oklch(0.99 0.01 87.47)\",\n          \"popover-foreground\": \"oklch(0.38 0.02 64.34)\",\n          \"primary\": \"oklch(0.62 0.08 65.54)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.88 0.03 85.57)\",\n          \"secondary-foreground\": \"oklch(0.43 0.03 64.93)\",\n          \"muted\": \"oklch(0.92 0.02 83.06)\",\n          \"muted-foreground\": \"oklch(0.54 0.04 71.17)\",\n          \"accent\": \"oklch(0.83 0.04 88.81)\",\n          \"accent-foreground\": \"oklch(0.38 0.02 64.34)\",\n          \"destructive\": \"oklch(0.55 0.14 32.91)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.86 0.03 84.59)\",\n          \"input\": \"oklch(0.86 0.03 84.59)\",\n          \"ring\": \"oklch(0.62 0.08 65.54)\",\n          \"chart-1\": \"oklch(0.62 0.08 65.54)\",\n          \"chart-2\": \"oklch(0.56 0.06 68.58)\",\n          \"chart-3\": \"oklch(0.49 0.06 72.68)\",\n          \"chart-4\": \"oklch(0.68 0.06 64.78)\",\n          \"chart-5\": \"oklch(0.73 0.06 66.70)\",\n          \"radius\": \"0.25rem\",\n          \"sidebar\": \"oklch(0.92 0.02 83.06)\",\n          \"sidebar-foreground\": \"oklch(0.38 0.02 64.34)\",\n          \"sidebar-primary\": \"oklch(0.62 0.08 65.54)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.83 0.04 88.81)\",\n          \"sidebar-accent-foreground\": \"oklch(0.38 0.02 64.34)\",\n          \"sidebar-border\": \"oklch(0.86 0.03 84.59)\",\n          \"sidebar-ring\": \"oklch(0.62 0.08 65.54)\",\n          \"font-sans\": \"Libre Baskerville, serif\",\n          \"font-serif\": \"Lora, serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"shadow-color\": \"hsl(28 13% 20%)\",\n          \"shadow-opacity\": \"0.12\",\n          \"shadow-blur\": \"5px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"2px\",\n          \"shadow-offset-y\": \"3px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.06)\",\n          \"shadow-xs\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.06)\",\n          \"shadow-sm\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.12), 2px 1px 2px -1px hsl(28 13% 20% / 0.12)\",\n          \"shadow\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.12), 2px 1px 2px -1px hsl(28 13% 20% / 0.12)\",\n          \"shadow-md\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.12), 2px 2px 4px -1px hsl(28 13% 20% / 0.12)\",\n          \"shadow-lg\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.12), 2px 4px 6px -1px hsl(28 13% 20% / 0.12)\",\n          \"shadow-xl\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.12), 2px 8px 10px -1px hsl(28 13% 20% / 0.12)\",\n          \"shadow-2xl\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.30)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.27 0.01 57.65)\",\n          \"foreground\": \"oklch(0.92 0.02 83.06)\",\n          \"card\": \"oklch(0.32 0.02 59.06)\",\n          \"card-foreground\": \"oklch(0.92 0.02 83.06)\",\n          \"popover\": \"oklch(0.32 0.02 59.06)\",\n          \"popover-foreground\": \"oklch(0.92 0.02 83.06)\",\n          \"primary\": \"oklch(0.73 0.06 66.70)\",\n          \"primary-foreground\": \"oklch(0.27 0.01 57.65)\",\n          \"secondary\": \"oklch(0.38 0.02 57.13)\",\n          \"secondary-foreground\": \"oklch(0.92 0.02 83.06)\",\n          \"muted\": \"oklch(0.32 0.02 59.06)\",\n          \"muted-foreground\": \"oklch(0.80 0.02 82.11)\",\n          \"accent\": \"oklch(0.42 0.03 56.34)\",\n          \"accent-foreground\": \"oklch(0.92 0.02 83.06)\",\n          \"destructive\": \"oklch(0.55 0.14 32.91)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.38 0.02 57.13)\",\n          \"input\": \"oklch(0.38 0.02 57.13)\",\n          \"ring\": \"oklch(0.73 0.06 66.70)\",\n          \"chart-1\": \"oklch(0.73 0.06 66.70)\",\n          \"chart-2\": \"oklch(0.68 0.06 64.78)\",\n          \"chart-3\": \"oklch(0.62 0.08 65.54)\",\n          \"chart-4\": \"oklch(0.56 0.06 68.58)\",\n          \"chart-5\": \"oklch(0.49 0.06 72.68)\",\n          \"radius\": \"0.25rem\",\n          \"sidebar\": \"oklch(0.27 0.01 57.65)\",\n          \"sidebar-foreground\": \"oklch(0.92 0.02 83.06)\",\n          \"sidebar-primary\": \"oklch(0.73 0.06 66.70)\",\n          \"sidebar-primary-foreground\": \"oklch(0.27 0.01 57.65)\",\n          \"sidebar-accent\": \"oklch(0.42 0.03 56.34)\",\n          \"sidebar-accent-foreground\": \"oklch(0.92 0.02 83.06)\",\n          \"sidebar-border\": \"oklch(0.38 0.02 57.13)\",\n          \"sidebar-ring\": \"oklch(0.73 0.06 66.70)\",\n          \"font-sans\": \"Libre Baskerville, serif\",\n          \"font-serif\": \"Lora, serif\",\n          \"font-mono\": \"IBM Plex Mono, monospace\",\n          \"shadow-color\": \"hsl(28 13% 20%)\",\n          \"shadow-opacity\": \"0.12\",\n          \"shadow-blur\": \"5px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"2px\",\n          \"shadow-offset-y\": \"3px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.06)\",\n          \"shadow-xs\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.06)\",\n          \"shadow-sm\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.12), 2px 1px 2px -1px hsl(28 13% 20% / 0.12)\",\n          \"shadow\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.12), 2px 1px 2px -1px hsl(28 13% 20% / 0.12)\",\n          \"shadow-md\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.12), 2px 2px 4px -1px hsl(28 13% 20% / 0.12)\",\n          \"shadow-lg\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.12), 2px 4px 6px -1px hsl(28 13% 20% / 0.12)\",\n          \"shadow-xl\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.12), 2px 8px 10px -1px hsl(28 13% 20% / 0.12)\",\n          \"shadow-2xl\": \"2px 3px 5px 0px hsl(28 13% 20% / 0.30)\"\n        }\n      }\n    },\n    {\n      \"name\": \"sunset-horizon\",\n      \"type\": \"registry:style\",\n      \"title\": \"Sunset Horizon\",\n      \"description\": \"A theme based on the Sunset Horizon color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-mono\": \"Ubuntu Mono, monospace\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"radius\": \"0.625rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.99 0.01 56.32)\",\n          \"foreground\": \"oklch(0.34 0.01 2.77)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.34 0.01 2.77)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.34 0.01 2.77)\",\n          \"primary\": \"oklch(0.74 0.16 34.71)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.96 0.02 28.90)\",\n          \"secondary-foreground\": \"oklch(0.56 0.13 32.74)\",\n          \"muted\": \"oklch(0.97 0.02 39.40)\",\n          \"muted-foreground\": \"oklch(0.55 0.01 58.07)\",\n          \"accent\": \"oklch(0.83 0.11 58.00)\",\n          \"accent-foreground\": \"oklch(0.34 0.01 2.77)\",\n          \"destructive\": \"oklch(0.61 0.21 22.24)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.93 0.04 38.69)\",\n          \"input\": \"oklch(0.93 0.04 38.69)\",\n          \"ring\": \"oklch(0.74 0.16 34.71)\",\n          \"chart-1\": \"oklch(0.74 0.16 34.71)\",\n          \"chart-2\": \"oklch(0.83 0.11 58.00)\",\n          \"chart-3\": \"oklch(0.88 0.08 54.93)\",\n          \"chart-4\": \"oklch(0.82 0.11 40.89)\",\n          \"chart-5\": \"oklch(0.64 0.13 32.07)\",\n          \"radius\": \"0.625rem\",\n          \"sidebar\": \"oklch(0.97 0.02 39.40)\",\n          \"sidebar-foreground\": \"oklch(0.34 0.01 2.77)\",\n          \"sidebar-primary\": \"oklch(0.74 0.16 34.71)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.83 0.11 58.00)\",\n          \"sidebar-accent-foreground\": \"oklch(0.34 0.01 2.77)\",\n          \"sidebar-border\": \"oklch(0.93 0.04 38.69)\",\n          \"sidebar-ring\": \"oklch(0.74 0.16 34.71)\",\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"font-mono\": \"Ubuntu Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.09\",\n          \"shadow-blur\": \"12px\",\n          \"shadow-spread\": \"-3px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"6px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.04)\",\n          \"shadow-xs\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.04)\",\n          \"shadow-sm\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 1px 2px -4px hsl(0 0% 0% / 0.09)\",\n          \"shadow\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 1px 2px -4px hsl(0 0% 0% / 0.09)\",\n          \"shadow-md\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 2px 4px -4px hsl(0 0% 0% / 0.09)\",\n          \"shadow-lg\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 4px 6px -4px hsl(0 0% 0% / 0.09)\",\n          \"shadow-xl\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 8px 10px -4px hsl(0 0% 0% / 0.09)\",\n          \"shadow-2xl\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.22)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.26 0.02 352.40)\",\n          \"foreground\": \"oklch(0.94 0.01 51.32)\",\n          \"card\": \"oklch(0.32 0.02 341.45)\",\n          \"card-foreground\": \"oklch(0.94 0.01 51.32)\",\n          \"popover\": \"oklch(0.32 0.02 341.45)\",\n          \"popover-foreground\": \"oklch(0.94 0.01 51.32)\",\n          \"primary\": \"oklch(0.74 0.16 34.71)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.36 0.02 342.27)\",\n          \"secondary-foreground\": \"oklch(0.94 0.01 51.32)\",\n          \"muted\": \"oklch(0.32 0.02 341.45)\",\n          \"muted-foreground\": \"oklch(0.84 0.02 52.63)\",\n          \"accent\": \"oklch(0.83 0.11 58.00)\",\n          \"accent-foreground\": \"oklch(0.26 0.02 352.40)\",\n          \"destructive\": \"oklch(0.61 0.21 22.24)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.36 0.02 342.27)\",\n          \"input\": \"oklch(0.36 0.02 342.27)\",\n          \"ring\": \"oklch(0.74 0.16 34.71)\",\n          \"chart-1\": \"oklch(0.74 0.16 34.71)\",\n          \"chart-2\": \"oklch(0.83 0.11 58.00)\",\n          \"chart-3\": \"oklch(0.88 0.08 54.93)\",\n          \"chart-4\": \"oklch(0.82 0.11 40.89)\",\n          \"chart-5\": \"oklch(0.64 0.13 32.07)\",\n          \"radius\": \"0.625rem\",\n          \"sidebar\": \"oklch(0.26 0.02 352.40)\",\n          \"sidebar-foreground\": \"oklch(0.94 0.01 51.32)\",\n          \"sidebar-primary\": \"oklch(0.74 0.16 34.71)\",\n          \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n          \"sidebar-accent\": \"oklch(0.83 0.11 58.00)\",\n          \"sidebar-accent-foreground\": \"oklch(0.26 0.02 352.40)\",\n          \"sidebar-border\": \"oklch(0.36 0.02 342.27)\",\n          \"sidebar-ring\": \"oklch(0.74 0.16 34.71)\",\n          \"font-sans\": \"Montserrat, sans-serif\",\n          \"font-serif\": \"Merriweather, serif\",\n          \"font-mono\": \"Ubuntu Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.09\",\n          \"shadow-blur\": \"12px\",\n          \"shadow-spread\": \"-3px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"6px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.04)\",\n          \"shadow-xs\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.04)\",\n          \"shadow-sm\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 1px 2px -4px hsl(0 0% 0% / 0.09)\",\n          \"shadow\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 1px 2px -4px hsl(0 0% 0% / 0.09)\",\n          \"shadow-md\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 2px 4px -4px hsl(0 0% 0% / 0.09)\",\n          \"shadow-lg\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 4px 6px -4px hsl(0 0% 0% / 0.09)\",\n          \"shadow-xl\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.09), 0px 8px 10px -4px hsl(0 0% 0% / 0.09)\",\n          \"shadow-2xl\": \"0px 6px 12px -3px hsl(0 0% 0% / 0.22)\"\n        }\n      }\n    },\n    {\n      \"name\": \"starry-night\",\n      \"type\": \"registry:style\",\n      \"title\": \"Starry Night\",\n      \"description\": \"A theme based on the Starry Night color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Libre Baskerville, serif\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0.00 258.32)\",\n          \"foreground\": \"oklch(0.26 0.04 268.07)\",\n          \"card\": \"oklch(0.93 0.01 251.56)\",\n          \"card-foreground\": \"oklch(0.26 0.04 268.07)\",\n          \"popover\": \"oklch(0.99 0.03 98.05)\",\n          \"popover-foreground\": \"oklch(0.26 0.04 268.07)\",\n          \"primary\": \"oklch(0.48 0.12 263.38)\",\n          \"primary-foreground\": \"oklch(0.99 0.03 98.05)\",\n          \"secondary\": \"oklch(0.86 0.12 81.01)\",\n          \"secondary-foreground\": \"oklch(0.26 0.04 268.07)\",\n          \"muted\": \"oklch(0.92 0.01 106.56)\",\n          \"muted-foreground\": \"oklch(0.48 0.12 263.38)\",\n          \"accent\": \"oklch(0.69 0.07 234.04)\",\n          \"accent-foreground\": \"oklch(0.99 0.03 98.05)\",\n          \"destructive\": \"oklch(0.26 0.04 322.53)\",\n          \"destructive-foreground\": \"oklch(0.99 0.03 98.05)\",\n          \"border\": \"oklch(0.78 0.02 251.19)\",\n          \"input\": \"oklch(0.69 0.07 234.04)\",\n          \"ring\": \"oklch(0.86 0.12 81.01)\",\n          \"chart-1\": \"oklch(0.48 0.12 263.38)\",\n          \"chart-2\": \"oklch(0.86 0.12 81.01)\",\n          \"chart-3\": \"oklch(0.69 0.07 234.04)\",\n          \"chart-4\": \"oklch(0.78 0.02 251.19)\",\n          \"chart-5\": \"oklch(0.26 0.04 322.53)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.93 0.01 251.56)\",\n          \"sidebar-foreground\": \"oklch(0.26 0.04 268.07)\",\n          \"sidebar-primary\": \"oklch(0.48 0.12 263.38)\",\n          \"sidebar-primary-foreground\": \"oklch(0.99 0.03 98.05)\",\n          \"sidebar-accent\": \"oklch(0.86 0.12 81.01)\",\n          \"sidebar-accent-foreground\": \"oklch(0.26 0.04 268.07)\",\n          \"sidebar-border\": \"oklch(0.78 0.02 251.19)\",\n          \"sidebar-ring\": \"oklch(0.86 0.12 81.01)\",\n          \"font-sans\": \"Libre Baskerville, serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.22 0.02 275.84)\",\n          \"foreground\": \"oklch(0.94 0.01 266.70)\",\n          \"card\": \"oklch(0.27 0.04 281.30)\",\n          \"card-foreground\": \"oklch(0.94 0.01 266.70)\",\n          \"popover\": \"oklch(0.27 0.04 281.30)\",\n          \"popover-foreground\": \"oklch(0.91 0.14 95.11)\",\n          \"primary\": \"oklch(0.48 0.12 263.38)\",\n          \"primary-foreground\": \"oklch(0.91 0.14 95.11)\",\n          \"secondary\": \"oklch(0.91 0.14 95.11)\",\n          \"secondary-foreground\": \"oklch(0.27 0.04 281.30)\",\n          \"muted\": \"oklch(0.27 0.04 281.30)\",\n          \"muted-foreground\": \"oklch(0.62 0.04 262.04)\",\n          \"accent\": \"oklch(0.85 0.05 264.78)\",\n          \"accent-foreground\": \"oklch(0.22 0.02 275.84)\",\n          \"destructive\": \"oklch(0.53 0.12 357.11)\",\n          \"destructive-foreground\": \"oklch(0.91 0.14 95.11)\",\n          \"border\": \"oklch(0.31 0.03 281.77)\",\n          \"input\": \"oklch(0.48 0.12 263.38)\",\n          \"ring\": \"oklch(0.91 0.14 95.11)\",\n          \"chart-1\": \"oklch(0.48 0.12 263.38)\",\n          \"chart-2\": \"oklch(0.91 0.14 95.11)\",\n          \"chart-3\": \"oklch(0.69 0.07 234.04)\",\n          \"chart-4\": \"oklch(0.62 0.04 262.04)\",\n          \"chart-5\": \"oklch(0.53 0.12 357.11)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.27 0.04 281.30)\",\n          \"sidebar-foreground\": \"oklch(0.94 0.01 266.70)\",\n          \"sidebar-primary\": \"oklch(0.48 0.12 263.38)\",\n          \"sidebar-primary-foreground\": \"oklch(0.91 0.14 95.11)\",\n          \"sidebar-accent\": \"oklch(0.91 0.14 95.11)\",\n          \"sidebar-accent-foreground\": \"oklch(0.27 0.04 281.30)\",\n          \"sidebar-border\": \"oklch(0.31 0.03 281.77)\",\n          \"sidebar-ring\": \"oklch(0.91 0.14 95.11)\",\n          \"font-sans\": \"Libre Baskerville, serif\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"claude\",\n      \"type\": \"registry:style\",\n      \"title\": \"Claude\",\n      \"description\": \"A theme based on the Claude color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.98 0.01 95.10)\",\n          \"foreground\": \"oklch(0.34 0.03 95.72)\",\n          \"card\": \"oklch(0.98 0.01 95.10)\",\n          \"card-foreground\": \"oklch(0.19 0.00 106.59)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.27 0.02 98.94)\",\n          \"primary\": \"oklch(0.62 0.14 39.04)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.92 0.01 92.99)\",\n          \"secondary-foreground\": \"oklch(0.43 0.02 98.60)\",\n          \"muted\": \"oklch(0.93 0.02 90.24)\",\n          \"muted-foreground\": \"oklch(0.61 0.01 97.42)\",\n          \"accent\": \"oklch(0.92 0.01 92.99)\",\n          \"accent-foreground\": \"oklch(0.27 0.02 98.94)\",\n          \"destructive\": \"oklch(0.19 0.00 106.59)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.88 0.01 97.36)\",\n          \"input\": \"oklch(0.76 0.02 98.35)\",\n          \"ring\": \"oklch(0.59 0.17 253.06)\",\n          \"chart-1\": \"oklch(0.56 0.13 43.00)\",\n          \"chart-2\": \"oklch(0.69 0.16 290.41)\",\n          \"chart-3\": \"oklch(0.88 0.03 93.13)\",\n          \"chart-4\": \"oklch(0.88 0.04 298.18)\",\n          \"chart-5\": \"oklch(0.56 0.13 42.06)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.97 0.01 98.88)\",\n          \"sidebar-foreground\": \"oklch(0.36 0.01 106.65)\",\n          \"sidebar-primary\": \"oklch(0.62 0.14 39.04)\",\n          \"sidebar-primary-foreground\": \"oklch(0.99 0 0)\",\n          \"sidebar-accent\": \"oklch(0.92 0.01 92.99)\",\n          \"sidebar-accent-foreground\": \"oklch(0.33 0 0)\",\n          \"sidebar-border\": \"oklch(0.94 0 0)\",\n          \"sidebar-ring\": \"oklch(0.77 0 0)\",\n          \"font-sans\": \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.27 0.00 106.64)\",\n          \"foreground\": \"oklch(0.81 0.01 93.01)\",\n          \"card\": \"oklch(0.27 0.00 106.64)\",\n          \"card-foreground\": \"oklch(0.98 0.01 95.10)\",\n          \"popover\": \"oklch(0.31 0.00 106.60)\",\n          \"popover-foreground\": \"oklch(0.92 0.00 106.48)\",\n          \"primary\": \"oklch(0.67 0.13 38.76)\",\n          \"primary-foreground\": \"oklch(1.00 0 0)\",\n          \"secondary\": \"oklch(0.98 0.01 95.10)\",\n          \"secondary-foreground\": \"oklch(0.31 0.00 106.60)\",\n          \"muted\": \"oklch(0.22 0.00 106.71)\",\n          \"muted-foreground\": \"oklch(0.77 0.02 99.07)\",\n          \"accent\": \"oklch(0.21 0.01 95.42)\",\n          \"accent-foreground\": \"oklch(0.97 0.01 98.88)\",\n          \"destructive\": \"oklch(0.64 0.21 25.33)\",\n          \"destructive-foreground\": \"oklch(1.00 0 0)\",\n          \"border\": \"oklch(0.36 0.01 106.89)\",\n          \"input\": \"oklch(0.43 0.01 100.22)\",\n          \"ring\": \"oklch(0.59 0.17 253.06)\",\n          \"chart-1\": \"oklch(0.56 0.13 43.00)\",\n          \"chart-2\": \"oklch(0.69 0.16 290.41)\",\n          \"chart-3\": \"oklch(0.21 0.01 95.42)\",\n          \"chart-4\": \"oklch(0.31 0.05 289.32)\",\n          \"chart-5\": \"oklch(0.56 0.13 42.06)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.24 0.00 67.71)\",\n          \"sidebar-foreground\": \"oklch(0.81 0.01 93.01)\",\n          \"sidebar-primary\": \"oklch(0.33 0 0)\",\n          \"sidebar-primary-foreground\": \"oklch(0.99 0 0)\",\n          \"sidebar-accent\": \"oklch(0.17 0.00 106.62)\",\n          \"sidebar-accent-foreground\": \"oklch(0.81 0.01 93.01)\",\n          \"sidebar-border\": \"oklch(0.94 0 0)\",\n          \"sidebar-ring\": \"oklch(0.77 0 0)\",\n          \"font-sans\": \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'\",\n          \"font-serif\": \"ui-serif, Georgia, Cambria, \\\"Times New Roman\\\", Times, serif\",\n          \"font-mono\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.1\",\n          \"shadow-blur\": \"3px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-xs\": \"0 1px 3px 0px hsl(0 0% 0% / 0.05)\",\n          \"shadow-sm\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-md\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-lg\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)\",\n          \"shadow-2xl\": \"0 1px 3px 0px hsl(0 0% 0% / 0.25)\"\n        }\n      }\n    },\n    {\n      \"name\": \"vercel\",\n      \"type\": \"registry:style\",\n      \"title\": \"Vercel\",\n      \"description\": \"A theme based on the Vercel color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Geist, sans-serif\",\n          \"font-mono\": \"Geist Mono, monospace\",\n          \"font-serif\": \"Georgia, serif\",\n          \"radius\": \"0.5rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(0.99 0 0)\",\n          \"foreground\": \"oklch(0 0 0)\",\n          \"card\": \"oklch(1 0 0)\",\n          \"card-foreground\": \"oklch(0 0 0)\",\n          \"popover\": \"oklch(0.99 0 0)\",\n          \"popover-foreground\": \"oklch(0 0 0)\",\n          \"primary\": \"oklch(0 0 0)\",\n          \"primary-foreground\": \"oklch(1 0 0)\",\n          \"secondary\": \"oklch(0.94 0 0)\",\n          \"secondary-foreground\": \"oklch(0 0 0)\",\n          \"muted\": \"oklch(0.97 0 0)\",\n          \"muted-foreground\": \"oklch(0.44 0 0)\",\n          \"accent\": \"oklch(0.94 0 0)\",\n          \"accent-foreground\": \"oklch(0 0 0)\",\n          \"destructive\": \"oklch(0.63 0.19 23.03)\",\n          \"destructive-foreground\": \"oklch(1 0 0)\",\n          \"border\": \"oklch(0.92 0 0)\",\n          \"input\": \"oklch(0.94 0 0)\",\n          \"ring\": \"oklch(0 0 0)\",\n          \"chart-1\": \"oklch(0.81 0.17 75.35)\",\n          \"chart-2\": \"oklch(0.55 0.22 264.53)\",\n          \"chart-3\": \"oklch(0.72 0 0)\",\n          \"chart-4\": \"oklch(0.92 0 0)\",\n          \"chart-5\": \"oklch(0.56 0 0)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.99 0 0)\",\n          \"sidebar-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-primary\": \"oklch(0 0 0)\",\n          \"sidebar-primary-foreground\": \"oklch(1 0 0)\",\n          \"sidebar-accent\": \"oklch(0.94 0 0)\",\n          \"sidebar-accent-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-border\": \"oklch(0.94 0 0)\",\n          \"sidebar-ring\": \"oklch(0 0 0)\",\n          \"font-sans\": \"Geist, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"Geist Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.18\",\n          \"shadow-blur\": \"2px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.09)\",\n          \"shadow-xs\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.09)\",\n          \"shadow-sm\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18)\",\n          \"shadow\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18)\",\n          \"shadow-md\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18)\",\n          \"shadow-lg\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18)\",\n          \"shadow-xl\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 8px 10px -1px hsl(0 0% 0% / 0.18)\",\n          \"shadow-2xl\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.45)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0 0 0)\",\n          \"foreground\": \"oklch(1 0 0)\",\n          \"card\": \"oklch(0.14 0 0)\",\n          \"card-foreground\": \"oklch(1 0 0)\",\n          \"popover\": \"oklch(0.18 0 0)\",\n          \"popover-foreground\": \"oklch(1 0 0)\",\n          \"primary\": \"oklch(1 0 0)\",\n          \"primary-foreground\": \"oklch(0 0 0)\",\n          \"secondary\": \"oklch(0.25 0 0)\",\n          \"secondary-foreground\": \"oklch(1 0 0)\",\n          \"muted\": \"oklch(0.23 0 0)\",\n          \"muted-foreground\": \"oklch(0.72 0 0)\",\n          \"accent\": \"oklch(0.32 0 0)\",\n          \"accent-foreground\": \"oklch(1 0 0)\",\n          \"destructive\": \"oklch(0.69 0.20 23.91)\",\n          \"destructive-foreground\": \"oklch(0 0 0)\",\n          \"border\": \"oklch(0.26 0 0)\",\n          \"input\": \"oklch(0.32 0 0)\",\n          \"ring\": \"oklch(0.72 0 0)\",\n          \"chart-1\": \"oklch(0.81 0.17 75.35)\",\n          \"chart-2\": \"oklch(0.58 0.21 260.84)\",\n          \"chart-3\": \"oklch(0.56 0 0)\",\n          \"chart-4\": \"oklch(0.44 0 0)\",\n          \"chart-5\": \"oklch(0.92 0 0)\",\n          \"radius\": \"0.5rem\",\n          \"sidebar\": \"oklch(0.18 0 0)\",\n          \"sidebar-foreground\": \"oklch(1 0 0)\",\n          \"sidebar-primary\": \"oklch(1 0 0)\",\n          \"sidebar-primary-foreground\": \"oklch(0 0 0)\",\n          \"sidebar-accent\": \"oklch(0.32 0 0)\",\n          \"sidebar-accent-foreground\": \"oklch(1 0 0)\",\n          \"sidebar-border\": \"oklch(0.32 0 0)\",\n          \"sidebar-ring\": \"oklch(0.72 0 0)\",\n          \"font-sans\": \"Geist, sans-serif\",\n          \"font-serif\": \"Georgia, serif\",\n          \"font-mono\": \"Geist Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0.18\",\n          \"shadow-blur\": \"2px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.09)\",\n          \"shadow-xs\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.09)\",\n          \"shadow-sm\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18)\",\n          \"shadow\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18)\",\n          \"shadow-md\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18)\",\n          \"shadow-lg\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18)\",\n          \"shadow-xl\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 8px 10px -1px hsl(0 0% 0% / 0.18)\",\n          \"shadow-2xl\": \"0px 1px 2px 0px hsl(0 0% 0% / 0.45)\"\n        }\n      }\n    },\n    {\n      \"name\": \"mono\",\n      \"type\": \"registry:style\",\n      \"title\": \"Mono\",\n      \"description\": \"A theme based on the Mono color palette.\",\n      \"css\": {\n        \"@layer base\": {\n          \"body\": {\n            \"letter-spacing\": \"var(--tracking-normal)\"\n          }\n        }\n      },\n      \"cssVars\": {\n        \"theme\": {\n          \"font-sans\": \"Geist Mono, monospace\",\n          \"font-mono\": \"Geist Mono, monospace\",\n          \"font-serif\": \"Geist Mono, monospace\",\n          \"radius\": \"0rem\",\n          \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n          \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n          \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n          \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n          \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\"\n        },\n        \"light\": {\n          \"background\": \"oklch(1.00 0 0)\",\n          \"foreground\": \"oklch(0.14 0 0)\",\n          \"card\": \"oklch(1.00 0 0)\",\n          \"card-foreground\": \"oklch(0.14 0 0)\",\n          \"popover\": \"oklch(1.00 0 0)\",\n          \"popover-foreground\": \"oklch(0.14 0 0)\",\n          \"primary\": \"oklch(0.56 0 0)\",\n          \"primary-foreground\": \"oklch(0.99 0 0)\",\n          \"secondary\": \"oklch(0.97 0 0)\",\n          \"secondary-foreground\": \"oklch(0.20 0 0)\",\n          \"muted\": \"oklch(0.97 0 0)\",\n          \"muted-foreground\": \"oklch(0.55 0 0)\",\n          \"accent\": \"oklch(0.97 0 0)\",\n          \"accent-foreground\": \"oklch(0.20 0 0)\",\n          \"destructive\": \"oklch(0.58 0.24 28.48)\",\n          \"destructive-foreground\": \"oklch(0.97 0 0)\",\n          \"border\": \"oklch(0.92 0 0)\",\n          \"input\": \"oklch(0.92 0 0)\",\n          \"ring\": \"oklch(0.71 0 0)\",\n          \"chart-1\": \"oklch(0.56 0 0)\",\n          \"chart-2\": \"oklch(0.56 0 0)\",\n          \"chart-3\": \"oklch(0.56 0 0)\",\n          \"chart-4\": \"oklch(0.56 0 0)\",\n          \"chart-5\": \"oklch(0.56 0 0)\",\n          \"radius\": \"0rem\",\n          \"sidebar\": \"oklch(0.99 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.14 0 0)\",\n          \"sidebar-primary\": \"oklch(0.20 0 0)\",\n          \"sidebar-primary-foreground\": \"oklch(0.99 0 0)\",\n          \"sidebar-accent\": \"oklch(0.97 0 0)\",\n          \"sidebar-accent-foreground\": \"oklch(0.20 0 0)\",\n          \"sidebar-border\": \"oklch(0.92 0 0)\",\n          \"sidebar-ring\": \"oklch(0.71 0 0)\",\n          \"font-sans\": \"Geist Mono, monospace\",\n          \"font-serif\": \"Geist Mono, monospace\",\n          \"font-mono\": \"Geist Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00)\",\n          \"shadow-xs\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00)\",\n          \"shadow-sm\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00), 0px 1px 2px -1px hsl(0 0% 0% / 0.00)\",\n          \"shadow\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00), 0px 1px 2px -1px hsl(0 0% 0% / 0.00)\",\n          \"shadow-md\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00), 0px 2px 4px -1px hsl(0 0% 0% / 0.00)\",\n          \"shadow-lg\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00), 0px 4px 6px -1px hsl(0 0% 0% / 0.00)\",\n          \"shadow-xl\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00), 0px 8px 10px -1px hsl(0 0% 0% / 0.00)\",\n          \"shadow-2xl\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00)\",\n          \"tracking-normal\": \"0em\"\n        },\n        \"dark\": {\n          \"background\": \"oklch(0.14 0 0)\",\n          \"foreground\": \"oklch(0.99 0 0)\",\n          \"card\": \"oklch(0.21 0 0)\",\n          \"card-foreground\": \"oklch(0.99 0 0)\",\n          \"popover\": \"oklch(0.27 0 0)\",\n          \"popover-foreground\": \"oklch(0.99 0 0)\",\n          \"primary\": \"oklch(0.56 0 0)\",\n          \"primary-foreground\": \"oklch(0.99 0 0)\",\n          \"secondary\": \"oklch(0.27 0 0)\",\n          \"secondary-foreground\": \"oklch(0.99 0 0)\",\n          \"muted\": \"oklch(0.27 0 0)\",\n          \"muted-foreground\": \"oklch(0.71 0 0)\",\n          \"accent\": \"oklch(0.37 0 0)\",\n          \"accent-foreground\": \"oklch(0.99 0 0)\",\n          \"destructive\": \"oklch(0.70 0.19 22.23)\",\n          \"destructive-foreground\": \"oklch(0.27 0 0)\",\n          \"border\": \"oklch(0.34 0 0)\",\n          \"input\": \"oklch(0.44 0 0)\",\n          \"ring\": \"oklch(0.56 0 0)\",\n          \"chart-1\": \"oklch(0.56 0 0)\",\n          \"chart-2\": \"oklch(0.56 0 0)\",\n          \"chart-3\": \"oklch(0.56 0 0)\",\n          \"chart-4\": \"oklch(0.56 0 0)\",\n          \"chart-5\": \"oklch(0.56 0 0)\",\n          \"radius\": \"0rem\",\n          \"sidebar\": \"oklch(0.20 0 0)\",\n          \"sidebar-foreground\": \"oklch(0.99 0 0)\",\n          \"sidebar-primary\": \"oklch(0.99 0 0)\",\n          \"sidebar-primary-foreground\": \"oklch(0.20 0 0)\",\n          \"sidebar-accent\": \"oklch(0.27 0 0)\",\n          \"sidebar-accent-foreground\": \"oklch(0.99 0 0)\",\n          \"sidebar-border\": \"oklch(1.00 0 0)\",\n          \"sidebar-ring\": \"oklch(0.44 0 0)\",\n          \"font-sans\": \"Geist Mono, monospace\",\n          \"font-serif\": \"Geist Mono, monospace\",\n          \"font-mono\": \"Geist Mono, monospace\",\n          \"shadow-color\": \"hsl(0 0% 0%)\",\n          \"shadow-opacity\": \"0\",\n          \"shadow-blur\": \"0px\",\n          \"shadow-spread\": \"0px\",\n          \"shadow-offset-x\": \"0px\",\n          \"shadow-offset-y\": \"1px\",\n          \"letter-spacing\": \"0em\",\n          \"spacing\": \"0.25rem\",\n          \"shadow-2xs\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00)\",\n          \"shadow-xs\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00)\",\n          \"shadow-sm\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00), 0px 1px 2px -1px hsl(0 0% 0% / 0.00)\",\n          \"shadow\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00), 0px 1px 2px -1px hsl(0 0% 0% / 0.00)\",\n          \"shadow-md\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00), 0px 2px 4px -1px hsl(0 0% 0% / 0.00)\",\n          \"shadow-lg\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00), 0px 4px 6px -1px hsl(0 0% 0% / 0.00)\",\n          \"shadow-xl\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00), 0px 8px 10px -1px hsl(0 0% 0% / 0.00)\",\n          \"shadow-2xl\": \"0px 1px 0px 0px hsl(0 0% 0% / 0.00)\"\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "routes.ts",
    "content": "export const API_AUTH_PREFIX: string = \"/api/auth\";\n\nexport const DEFAULT_LOGIN_REDIRECT: string = \"/editor/theme\";\n"
  },
  {
    "path": "scripts/create-oauth-app.ts",
    "content": "/**\n * Create an OAuth app for external integrations.\n *\n * Usage:\n *   npx tsx scripts/create-oauth-app.ts \\\n *     --name \"My App\" \\\n *     --redirect-uris \"http://localhost:3000/callback,https://myapp.com/callback\" \\\n *     [--scopes \"themes:read,profile:read\"] \\\n *     [--description \"My cool app\"]\n */\n\nimport { neon } from \"@neondatabase/serverless\";\nimport { drizzle } from \"drizzle-orm/neon-http\";\nimport { oauthApp } from \"../db/schema\";\nimport { generateSecureToken, hashSecret } from \"../lib/oauth\";\nimport { randomBytes } from \"crypto\";\nimport cuid from \"cuid\";\nimport { config } from \"dotenv\";\n\nconfig({ path: \".env.local\" });\n\nfunction parseArgs() {\n  const args = process.argv.slice(2);\n  const parsed: Record<string, string> = {};\n\n  for (let i = 0; i < args.length; i += 2) {\n    const key = args[i].replace(/^--/, \"\");\n    parsed[key] = args[i + 1];\n  }\n\n  return parsed;\n}\n\nasync function main() {\n  const args = parseArgs();\n\n  if (!args.name || !args[\"redirect-uris\"]) {\n    console.error(\n      \"Usage: npx tsx scripts/create-oauth-app.ts --name <name> --redirect-uris <uri1,uri2> [--scopes <scope1,scope2>] [--description <desc>]\"\n    );\n    process.exit(1);\n  }\n\n  if (!process.env.DATABASE_URL) {\n    console.error(\"DATABASE_URL not set. Make sure .env.local exists.\");\n    process.exit(1);\n  }\n\n  const sql = neon(process.env.DATABASE_URL);\n  const db = drizzle({ client: sql });\n\n  const clientId = randomBytes(16).toString(\"hex\");\n  const clientSecret = generateSecureToken();\n  const clientSecretHash = await hashSecret(clientSecret);\n\n  const redirectUris = args[\"redirect-uris\"].split(\",\").map((u) => u.trim());\n  const scopes = args.scopes\n    ? args.scopes.split(\",\").map((s) => s.trim())\n    : [\"themes:read\", \"profile:read\"];\n\n  const now = new Date();\n\n  await db.insert(oauthApp).values({\n    id: cuid(),\n    name: args.name,\n    description: args.description ?? null,\n    clientId,\n    clientSecretHash,\n    redirectUris,\n    scopes,\n    isActive: true,\n    createdAt: now,\n    updatedAt: now,\n  });\n\n  console.log(\"\\nOAuth app created successfully!\\n\");\n  console.log(\"  Name:          \", args.name);\n  console.log(\"  Client ID:     \", clientId);\n  console.log(\"  Client Secret: \", clientSecret);\n  console.log(\"  Redirect URIs: \", redirectUris.join(\", \"));\n  console.log(\"  Scopes:        \", scopes.join(\", \"));\n  console.log(\n    \"\\n  ⚠️  Save the client secret now — it cannot be retrieved later.\\n\"\n  );\n}\n\nmain().catch((err) => {\n  console.error(\"Failed to create OAuth app:\", err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "scripts/generate-registry.ts",
    "content": "import fs from \"fs\";\nimport path from \"path\";\nimport { defaultPresets } from \"../utils/theme-presets\";\nimport { generateThemeRegistryFromPreset } from \"@/utils/registry/themes\";\n\ninterface ThemeRegistry {\n  $schema: string;\n  name: string;\n  homepage: string;\n  items: unknown[];\n}\n\nfunction generateRegistry() {\n  const registry: ThemeRegistry = {\n    $schema: \"https://ui.shadcn.com/schema/registry.json\",\n    name: \"tweakcn-theme-registry\",\n    homepage: \"https://tweakcn.com\",\n    items: [],\n  };\n\n  // Convert defaultPresets to registry items\n  for (const [name, preset] of Object.entries(defaultPresets)) {\n    const registryItem = generateThemeRegistryFromPreset(name);\n    const item = {\n      name,\n      type: \"registry:style\",\n      title: preset.label || name,\n      description: `A theme based on the ${\n        preset.label || name\n      } color palette.`,\n      css: registryItem.css,\n      cssVars: registryItem.cssVars,\n    };\n    registry.items.push(item);\n  }\n\n  // Create public/r directory if it doesn't exist\n  const publicDir = path.join(process.cwd(), \"public\", \"r\", \"themes\");\n  if (!fs.existsSync(publicDir)) {\n    fs.mkdirSync(publicDir, { recursive: true });\n  }\n\n  // Write the registry file\n  const registryPath = path.join(publicDir, \"registry.json\");\n  fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2));\n  console.log(`Registry file generated at ${registryPath}`);\n}\n\ngenerateRegistry();\n"
  },
  {
    "path": "scripts/generate-theme-registry.ts",
    "content": "import fs from \"fs\";\nimport path from \"path\";\n\nimport { generateThemeRegistryItemFromStyles } from \"@/utils/registry/themes\";\nimport { generateV0RegistryPayload } from \"@/utils/registry/v0\";\nimport { defaultPresets } from \"@/utils/theme-presets\";\nimport { defaultThemeState } from \"@/config/theme\";\nimport { ThemeStyles } from \"@/types/theme\";\n\nconst THEMES_DIR = path.join(process.cwd(), \"public\", \"r\", \"themes\");\nconst V0_DIR = path.join(process.cwd(), \"public\", \"r\", \"v0\");\n\n// Ensure directories exist\n[THEMES_DIR, V0_DIR].forEach((dir) => {\n  if (!fs.existsSync(dir)) {\n    fs.mkdirSync(dir, { recursive: true });\n  }\n});\n\n// Helper to get preset theme styles without going through the store\nfunction getPresetThemeStylesForScript(name: string): ThemeStyles {\n  const defaultTheme = defaultThemeState.styles;\n  if (name === \"default\") {\n    return defaultTheme;\n  }\n\n  const preset = defaultPresets[name];\n  if (!preset) {\n    return defaultTheme;\n  }\n\n  return {\n    light: {\n      ...defaultTheme.light,\n      ...(preset.styles.light || {}),\n    },\n    dark: {\n      ...defaultTheme.dark,\n      ...(preset.styles.light || {}),\n      ...(preset.styles.dark || {}),\n    },\n  };\n}\n\n// Generate registry files for all presets\nObject.keys(defaultPresets).forEach((name) => {\n  const preset = defaultPresets[name];\n  const themeStyles = getPresetThemeStylesForScript(name);\n  const themeName = preset.label || name;\n\n  // Generate shadcn registry format\n  const registryItem = generateThemeRegistryItemFromStyles(name, themeStyles);\n  const filePath = path.join(THEMES_DIR, `${name}.json`);\n  fs.writeFileSync(filePath, JSON.stringify(registryItem, null, 2));\n  console.log(`Generated registry file for theme: ${name}`);\n\n  // Generate v0 format\n  const v0Payload = generateV0RegistryPayload(themeName, themeStyles);\n  const v0FilePath = path.join(V0_DIR, `${name}.json`);\n  fs.writeFileSync(v0FilePath, JSON.stringify(v0Payload, null, 2));\n  console.log(`Generated v0 file for theme: ${name}`);\n});\n"
  },
  {
    "path": "store/ai-chat-store.ts",
    "content": "import { ChatMessage } from \"@/types/ai\";\nimport { create } from \"zustand\";\nimport { createJSONStorage, persist } from \"zustand/middleware\";\nimport { idbStorage } from \"./idb-storage\";\n\ninterface AIChatStore {\n  messages: ChatMessage[];\n  setMessages: (messages: ChatMessage[]) => void;\n\n  // Hook into zustand hydration lifecycle\n  hasHydrated: boolean;\n  _setHasHydrated: () => void;\n}\n\nexport const useAIChatStore = create<AIChatStore>()(\n  persist(\n    (set) => ({\n      messages: [],\n      setMessages: (messages: ChatMessage[]) => {\n        set({ messages });\n      },\n      hasHydrated: false,\n      _setHasHydrated: () => {\n        set({ hasHydrated: true });\n      },\n    }),\n    {\n      version: 2,\n      name: \"ai-chat-storage\",\n      storage: createJSONStorage(() => idbStorage),\n      partialize: (state) => ({ messages: state.messages }),\n      migrate: (persistedState, fromVersion) => {\n        if (!persistedState || typeof persistedState !== \"object\") {\n          return { messages: [] };\n        }\n\n        if (fromVersion === 2) {\n          const current = persistedState as AIChatStore;\n          return { messages: Array.isArray(current.messages) ? current.messages : [] };\n        }\n      },\n      onRehydrateStorage: () => (state) => {\n        state?._setHasHydrated?.();\n      },\n    }\n  )\n);\n"
  },
  {
    "path": "store/ai-local-draft-store.ts",
    "content": "import { JSONContent } from \"@tiptap/react\";\nimport { create } from \"zustand\";\nimport { persist, createJSONStorage } from \"zustand/middleware\";\nimport { idbStorage } from \"./idb-storage\";\n\ninterface AILocalDraftStore {\n  editorContentDraft: JSONContent | null;\n  setEditorContentDraft: (content: JSONContent | null) => void;\n  imagesDraft: { url: string }[];\n  setImagesDraft: (imagesDraft: { url: string }[]) => void;\n  clearLocalDraft: () => void;\n}\n\nexport const useAILocalDraftStore = create<AILocalDraftStore>()(\n  persist(\n    (set) => ({\n      editorContentDraft: null,\n      setEditorContentDraft: (content) => set({ editorContentDraft: content }),\n      imagesDraft: [],\n      setImagesDraft: (images) => set({ imagesDraft: images }),\n      clearLocalDraft: () => set({ editorContentDraft: null, imagesDraft: [] }),\n    }),\n    {\n      name: \"ai-local-draft-store\",\n      storage: createJSONStorage(() => idbStorage),\n    }\n  )\n);\n"
  },
  {
    "path": "store/auth-store.ts",
    "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { PostLoginActionType, StoredPostLoginAction } from \"@/hooks/use-post-login-action\";\n\ninterface AuthStore {\n  isOpen: boolean;\n  mode: \"signin\" | \"signup\";\n  postLoginAction: StoredPostLoginAction;\n  openAuthDialog: (\n    mode?: \"signin\" | \"signup\",\n    postLoginActionType?: PostLoginActionType,\n    postLoginActionData?: any\n  ) => void;\n  closeAuthDialog: () => void;\n  clearPostLoginAction: () => void;\n}\n\nexport const useAuthStore = create<AuthStore>()(\n  persist(\n    (set) => ({\n      isOpen: false,\n      mode: \"signin\",\n      postLoginAction: null,\n      openAuthDialog: (\n        newMode?: \"signin\" | \"signup\",\n        postLoginActionType?: PostLoginActionType,\n        postLoginActionData?: any\n      ) => {\n        set((state) => ({\n          isOpen: true,\n          mode: newMode || state.mode,\n          postLoginAction: postLoginActionType\n            ? { type: postLoginActionType, data: postLoginActionData }\n            : null,\n        }));\n      },\n      closeAuthDialog: () => {\n        set({ isOpen: false });\n      },\n      clearPostLoginAction: () => {\n        set({ postLoginAction: null });\n      },\n    }),\n    {\n      name: \"auth-storage\",\n      partialize: (state) => ({ postLoginAction: state.postLoginAction }),\n    }\n  )\n);\n"
  },
  {
    "path": "store/color-control-focus-store.ts",
    "content": "import { create } from \"zustand\";\n\nimport { DEFAULT_TAB, useControlsTabFromUrl } from \"@/hooks/use-controls-tab-from-url\";\n\nexport type FocusColorId =\n  | \"background\"\n  | \"foreground\"\n  | \"card\"\n  | \"card-foreground\"\n  | \"popover\"\n  | \"popover-foreground\"\n  | \"primary\"\n  | \"primary-foreground\"\n  | \"secondary\"\n  | \"secondary-foreground\"\n  | \"accent\"\n  | \"accent-foreground\"\n  | \"muted\"\n  | \"muted-foreground\"\n  | \"destructive\"\n  | \"destructive-foreground\"\n  | \"border\"\n  | \"input\"\n  | \"ring\"\n  | \"chart-1\"\n  | \"chart-2\"\n  | \"chart-3\"\n  | \"chart-4\"\n  | \"chart-5\"\n  | \"sidebar\"\n  | \"sidebar-foreground\"\n  | \"sidebar-primary\"\n  | \"sidebar-primary-foreground\"\n  | \"sidebar-accent\"\n  | \"sidebar-accent-foreground\"\n  | \"sidebar-border\"\n  | \"sidebar-ring\";\n\ninterface ColorRefEntry {\n  ref: HTMLElement | null;\n}\n\ninterface ColorControlFocusState {\n  colorRefs: Map<FocusColorId, ColorRefEntry>;\n  highlightTarget: FocusColorId | null;\n  /**\n   * Programmatically focus the color control identified by `name`.\n   */\n  focusColor: (name: FocusColorId) => void;\n  registerColor: (name: FocusColorId, ref: HTMLElement | null) => void;\n  unregisterColor: (name: FocusColorId) => void;\n}\n\nexport const useColorControlFocusStore = create<ColorControlFocusState>((set, get) => ({\n  colorRefs: new Map(),\n  highlightTarget: null,\n\n  registerColor: (name, ref) =>\n    set((state) => {\n      const map = new Map(state.colorRefs);\n      map.set(name, { ref });\n      return { colorRefs: map };\n    }),\n\n  unregisterColor: (name) =>\n    set((state) => {\n      const map = new Map(state.colorRefs);\n      map.delete(name);\n      return { colorRefs: map };\n    }),\n\n  focusColor: (name) => {\n    const { colorRefs } = get();\n    const entry = colorRefs.get(name);\n    if (!entry) return;\n\n    // Scroll & highlight after a brief delay to ensure expansion has occurred.\n    setTimeout(() => {\n      if (entry.ref?.scrollIntoView) {\n        entry.ref.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n      }\n      set({ highlightTarget: name });\n      setTimeout(() => set({ highlightTarget: null }), 3000);\n    }, 175);\n  },\n}));\n\n/**\n * Hook that exposes helper functions for color control focus behaviour.\n */\nexport const useColorControlFocus = () => {\n  const { handleSetTab } = useControlsTabFromUrl();\n\n  const focusColor = (name: FocusColorId) => {\n    handleSetTab(DEFAULT_TAB);\n    requestAnimationFrame(() => {\n      useColorControlFocusStore.getState().focusColor(name);\n    });\n  };\n\n  return {\n    registerColor: useColorControlFocusStore((s) => s.registerColor),\n    unregisterColor: useColorControlFocusStore((s) => s.unregisterColor),\n    highlightTarget: useColorControlFocusStore((s) => s.highlightTarget),\n    focusColor,\n  } as const;\n};\n"
  },
  {
    "path": "store/editor-store.ts",
    "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { ThemeEditorState } from \"@/types/editor\";\nimport { defaultThemeState } from \"@/config/theme\";\nimport { getPresetThemeStyles } from \"@/utils/theme-preset-helper\";\nimport { isDeepEqual } from \"@/lib/utils\";\n\nconst MAX_HISTORY_COUNT = 30;\nconst HISTORY_OVERRIDE_THRESHOLD_MS = 500; // 0.5 seconds\n\ninterface ThemeHistoryEntry {\n  state: ThemeEditorState;\n  timestamp: number;\n}\n\ninterface EditorStore {\n  themeState: ThemeEditorState;\n  themeCheckpoint: ThemeEditorState | null;\n  history: ThemeHistoryEntry[];\n  future: ThemeHistoryEntry[];\n  setThemeState: (state: ThemeEditorState) => void;\n  applyThemePreset: (preset: string) => void;\n  saveThemeCheckpoint: () => void;\n  restoreThemeCheckpoint: () => void;\n  resetToCurrentPreset: () => void;\n  hasThemeChangedFromCheckpoint: () => boolean;\n  hasUnsavedChanges: () => boolean;\n  undo: () => void;\n  redo: () => void;\n  canUndo: () => boolean;\n  canRedo: () => boolean;\n}\n\nexport const useEditorStore = create<EditorStore>()(\n  persist(\n    (set, get) => ({\n      themeState: defaultThemeState,\n      themeCheckpoint: null,\n      history: [],\n      future: [],\n      setThemeState: (newState: ThemeEditorState) => {\n        const oldThemeState = get().themeState;\n        let currentHistory = get().history;\n        let currentFuture = get().future;\n\n        // Check if only currentMode changed\n        const oldStateWithoutMode = { ...oldThemeState, currentMode: undefined };\n        const newStateWithoutMode = { ...newState, currentMode: undefined };\n\n        if (\n          isDeepEqual(oldStateWithoutMode, newStateWithoutMode) &&\n          oldThemeState.currentMode !== newState.currentMode\n        ) {\n          // Only currentMode changed\n          // Just update themeState without affecting history or future\n          set({ themeState: newState });\n          return;\n        }\n\n        const currentTime = Date.now();\n\n        // If other things changed, or if it's an actual identical state set (though less likely here)\n        // Proceed with history logic\n        const lastHistoryEntry =\n          currentHistory.length > 0 ? currentHistory[currentHistory.length - 1] : null;\n\n        if (\n          !lastHistoryEntry ||\n          currentTime - lastHistoryEntry.timestamp >= HISTORY_OVERRIDE_THRESHOLD_MS\n        ) {\n          // Add a new history entry\n          currentHistory = [...currentHistory, { state: oldThemeState, timestamp: currentTime }];\n          currentFuture = [];\n        }\n\n        if (currentHistory.length > MAX_HISTORY_COUNT) {\n          currentHistory.shift(); // Remove the oldest entry\n        }\n\n        set({\n          themeState: newState,\n          history: currentHistory,\n          future: currentFuture,\n        });\n      },\n      applyThemePreset: (preset: string) => {\n        const currentThemeState = get().themeState;\n        const oldHistory = get().history;\n        const currentTime = Date.now();\n\n        const newStyles = getPresetThemeStyles(preset);\n        const newThemeState: ThemeEditorState = {\n          ...currentThemeState,\n          preset,\n          styles: newStyles,\n          hslAdjustments: defaultThemeState.hslAdjustments,\n        };\n\n        const newHistoryEntry = { state: currentThemeState, timestamp: currentTime };\n        let updatedHistory = [...oldHistory, newHistoryEntry];\n        if (updatedHistory.length > MAX_HISTORY_COUNT) {\n          updatedHistory.shift();\n        }\n\n        set({\n          themeState: newThemeState,\n          themeCheckpoint: newThemeState, // Applying a preset also updates the checkpoint\n          history: updatedHistory,\n          future: [],\n        });\n      },\n      saveThemeCheckpoint: () => {\n        set({ themeCheckpoint: get().themeState });\n      },\n      restoreThemeCheckpoint: () => {\n        const checkpoint = get().themeCheckpoint;\n        if (checkpoint) {\n          const oldThemeState = get().themeState;\n          const oldHistory = get().history;\n          const currentTime = Date.now();\n\n          const newHistoryEntry = { state: oldThemeState, timestamp: currentTime };\n          let updatedHistory = [...oldHistory, newHistoryEntry];\n          if (updatedHistory.length > MAX_HISTORY_COUNT) {\n            updatedHistory.shift();\n          }\n\n          set({\n            themeState: {\n              ...checkpoint,\n              currentMode: get().themeState.currentMode,\n            },\n            history: updatedHistory,\n            future: [],\n          });\n        } else {\n          console.warn(\"No theme checkpoint available to restore to.\");\n        }\n      },\n      hasThemeChangedFromCheckpoint: () => {\n        const checkpoint = get().themeCheckpoint;\n        return !isDeepEqual(get().themeState, checkpoint);\n      },\n      hasUnsavedChanges: () => {\n        const themeState = get().themeState;\n        const presetThemeStyles = getPresetThemeStyles(themeState.preset ?? \"default\");\n        const stylesChanged = !isDeepEqual(themeState.styles, presetThemeStyles);\n        const hslChanged = !isDeepEqual(\n          themeState.hslAdjustments,\n          defaultThemeState.hslAdjustments\n        );\n        return stylesChanged || hslChanged;\n      },\n      resetToCurrentPreset: () => {\n        const currentThemeState = get().themeState;\n\n        const presetThemeStyles = getPresetThemeStyles(currentThemeState.preset ?? \"default\");\n        const newThemeState: ThemeEditorState = {\n          ...currentThemeState,\n          styles: presetThemeStyles,\n          hslAdjustments: defaultThemeState.hslAdjustments,\n        };\n\n        set({\n          themeState: newThemeState,\n          themeCheckpoint: newThemeState,\n          history: [],\n          future: [],\n        });\n      },\n      undo: () => {\n        const history = get().history;\n        if (history.length === 0) {\n          return;\n        }\n\n        const currentThemeState = get().themeState;\n        const future = get().future;\n\n        const lastHistoryEntry = history[history.length - 1];\n        const newHistory = history.slice(0, -1);\n\n        const newFutureEntry = { state: currentThemeState, timestamp: Date.now() };\n        const newFuture = [newFutureEntry, ...future];\n\n        set({\n          themeState: {\n            ...lastHistoryEntry.state,\n            currentMode: currentThemeState.currentMode,\n          },\n          themeCheckpoint: lastHistoryEntry.state,\n          history: newHistory,\n          future: newFuture,\n        });\n      },\n      redo: () => {\n        const future = get().future;\n        if (future.length === 0) {\n          return;\n        }\n        const history = get().history;\n\n        const firstFutureEntry = future[0];\n        const newFuture = future.slice(1);\n\n        const currentThemeState = get().themeState;\n\n        const newHistoryEntry = { state: currentThemeState, timestamp: Date.now() };\n        let updatedHistory = [...history, newHistoryEntry];\n        if (updatedHistory.length > MAX_HISTORY_COUNT) {\n          updatedHistory.shift();\n        }\n\n        set({\n          themeState: {\n            ...firstFutureEntry.state,\n            currentMode: currentThemeState.currentMode,\n          },\n          themeCheckpoint: firstFutureEntry.state,\n          history: updatedHistory,\n          future: newFuture,\n        });\n      },\n      canUndo: () => get().history.length > 0,\n      canRedo: () => get().future.length > 0,\n    }),\n    {\n      name: \"editor-storage\",\n    }\n  )\n);\n"
  },
  {
    "path": "store/get-pro-dialog-store.ts",
    "content": "import { create } from \"zustand\";\n\ninterface GetProDialogState {\n  isOpen: boolean;\n  openGetProDialog: () => void;\n  closeGetProDialog: () => void;\n}\n\nexport const useGetProDialogStore = create<GetProDialogState>()((set) => ({\n  isOpen: false,\n  openGetProDialog: () =>\n    set({\n      isOpen: true,\n    }),\n  closeGetProDialog: () =>\n    set({\n      isOpen: false,\n    }),\n}));\n"
  },
  {
    "path": "store/idb-storage.ts",
    "content": "import { get, set, del } from \"idb-keyval\";\nimport { StateStorage } from \"zustand/middleware\";\n\nexport const idbStorage: 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"
  },
  {
    "path": "store/preferences-store.ts",
    "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { ColorFormat } from \"@/types\";\n\ntype PackageManager = \"pnpm\" | \"npm\" | \"yarn\" | \"bun\";\nexport type ColorSelectorTab = \"list\" | \"palette\";\n\nconst colorFormatsByVersion = {\n  \"3\": [\"hex\", \"rgb\", \"hsl\"] as const,\n  \"4\": [\"hex\", \"rgb\", \"hsl\", \"oklch\"] as const,\n};\n\ninterface PreferencesStore {\n  tailwindVersion: \"3\" | \"4\";\n  colorFormat: ColorFormat;\n  packageManager: PackageManager;\n  colorSelectorTab: ColorSelectorTab;\n  chatSuggestionsOpen: boolean;\n  setTailwindVersion: (version: \"3\" | \"4\") => void;\n  setColorFormat: (format: ColorFormat) => void;\n  setPackageManager: (pm: PackageManager) => void;\n  setColorSelectorTab: (tab: ColorSelectorTab) => void;\n  setChatSuggestionsOpen: (open: boolean) => void;\n  getAvailableColorFormats: () => readonly ColorFormat[];\n}\n\nexport const usePreferencesStore = create<PreferencesStore>()(\n  persist(\n    (set, get) => ({\n      tailwindVersion: \"4\",\n      colorFormat: \"oklch\",\n      packageManager: \"pnpm\",\n      colorSelectorTab: \"list\",\n      chatSuggestionsOpen: true,\n      setTailwindVersion: (version: \"3\" | \"4\") => {\n        const currentFormat = get().colorFormat;\n        if (version === \"3\" && currentFormat === \"oklch\") {\n          set({ tailwindVersion: version, colorFormat: \"hsl\" });\n        } else {\n          set({ tailwindVersion: version });\n        }\n      },\n      setColorFormat: (format: ColorFormat) => {\n        const availableFormats = get().getAvailableColorFormats();\n        if (availableFormats.includes(format)) {\n          set({ colorFormat: format });\n        }\n      },\n      setPackageManager: (pm: PackageManager) => {\n        set({ packageManager: pm });\n      },\n      setColorSelectorTab: (tab: ColorSelectorTab) => {\n        set({ colorSelectorTab: tab });\n      },\n      getAvailableColorFormats: () => {\n        const version = get().tailwindVersion as \"3\" | \"4\";\n        return colorFormatsByVersion[version];\n      },\n      setChatSuggestionsOpen: (open: boolean) => {\n        set({ chatSuggestionsOpen: open });\n      },\n    }),\n    {\n      name: \"preferences-storage\", // unique name for localStorage\n    }\n  )\n);\n"
  },
  {
    "path": "store/theme-preset-store.ts",
    "content": "import { create } from \"zustand\";\nimport { ThemePreset } from \"@/types/theme\";\nimport { defaultPresets } from \"@/utils/theme-presets\";\nimport { getThemes } from \"@/actions/themes\";\n\ninterface ThemePresetStore {\n  presets: Record<string, ThemePreset>;\n  registerPreset: (name: string, preset: ThemePreset) => void;\n  unregisterPreset: (name: string) => void;\n  updatePreset: (name: string, preset: ThemePreset) => void;\n  getPreset: (name: string) => ThemePreset | undefined;\n  getAllPresets: () => Record<string, ThemePreset>;\n  loadSavedPresets: () => Promise<void>;\n  unloadSavedPresets: () => void;\n}\n\nexport const useThemePresetStore = create<ThemePresetStore>()((set, get) => ({\n  presets: defaultPresets,\n  registerPreset: (name: string, preset: ThemePreset) => {\n    set((state) => ({\n      presets: {\n        ...state.presets,\n        [name]: preset,\n      },\n    }));\n  },\n  unregisterPreset: (name: string) => {\n    set((state) => {\n      const { [name]: _, ...remainingPresets } = state.presets;\n      return {\n        presets: remainingPresets,\n      };\n    });\n  },\n  loadSavedPresets: async () => {\n    try {\n      const savedThemes = await getThemes();\n      const savedPresets = savedThemes.reduce((acc, theme) => {\n        acc[theme.id] = {\n          label: theme.name,\n          styles: theme.styles,\n          source: \"SAVED\",\n        };\n        return acc;\n      }, {} as Record<string, ThemePreset>);\n\n      set((state) => ({\n        presets: {\n          ...state.presets,\n          ...savedPresets,\n        },\n      }));\n    } catch (error) {\n      console.error(\"Failed to load saved presets:\", error);\n    }\n  },\n  unloadSavedPresets: () => {\n    set({ presets: defaultPresets });\n  },\n  updatePreset: (name: string, preset: ThemePreset) => {\n    set((state) => ({\n      presets: {\n        ...state.presets,\n        [name]: preset,\n      },\n    }));\n  },\n  getPreset: (name: string) => {\n    return get().presets[name];\n  },\n  getAllPresets: () => {\n    return get().presets;\n  },\n}));\n"
  },
  {
    "path": "store/website-preview-store.ts",
    "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\n\ninterface WebsitePreviewStore {\n  inputUrl: string;\n  currentUrl: string;\n  setInputUrl: (url: string) => void;\n  setCurrentUrl: (url: string) => void;\n  reset: () => void;\n}\n\nexport const useWebsitePreviewStore = create<WebsitePreviewStore>()(\n  persist(\n    (set) => ({\n      inputUrl: \"\",\n      currentUrl: \"\",\n      setInputUrl: (url: string) => set({ inputUrl: url }),\n      setCurrentUrl: (url: string) => set({ currentUrl: url }),\n      reset: () => set({ inputUrl: \"\", currentUrl: \"\" }),\n    }),\n    {\n      name: \"website-preview-storage\",\n    }\n  )\n);\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"baseUrl\": \".\",\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "types/ai.ts",
    "content": "import { THEME_GENERATION_TOOLS } from \"@/lib/ai/generate-theme/tools\";\nimport { DeepPartial, InferUITools, UIMessage, UIMessageStreamWriter } from \"ai\";\nimport { ThemeStylesWithoutSpacing, type ThemeStyleProps, type ThemeStyles } from \"./theme\";\n\nexport type MentionReference = {\n  id: string;\n  label: string;\n  themeData: {\n    light: Partial<ThemeStyleProps>;\n    dark: Partial<ThemeStyleProps>;\n  };\n};\n\nexport type PromptImage = {\n  url: string;\n};\n\nexport type AIPromptData = {\n  content: string;\n  mentions: MentionReference[];\n  images?: PromptImage[];\n};\n\nexport type MyMetadata = {\n  promptData?: AIPromptData;\n  themeStyles?: ThemeStyles;\n};\n\nexport type MyUIDataParts = {\n  \"generated-theme-styles\":\n    | {\n        status: \"streaming\";\n        themeStyles: DeepPartial<ThemeStylesWithoutSpacing>;\n      }\n    | {\n        status: \"ready\";\n        themeStyles: ThemeStylesWithoutSpacing;\n      };\n};\n\ntype ThemeGenerationUITools = InferUITools<typeof THEME_GENERATION_TOOLS>;\nexport type MyUITools = ThemeGenerationUITools;\n\nexport type ChatMessage = UIMessage<MyMetadata, MyUIDataParts, MyUITools>;\n\nexport type AdditionalAIContext = { writer: UIMessageStreamWriter<ChatMessage> };\n"
  },
  {
    "path": "types/community.ts",
    "content": "import { ThemeStyles } from \"@/types/theme\";\n\nexport interface CommunityThemeAuthor {\n  id: string;\n  name: string;\n  image: string | null;\n}\n\nexport interface CommunityTheme {\n  id: string;\n  themeId: string;\n  name: string;\n  styles: ThemeStyles;\n  author: CommunityThemeAuthor;\n  likeCount: number;\n  isLikedByMe: boolean;\n  publishedAt: string;\n  tags: string[];\n}\n\nexport type CommunitySortOption = \"popular\" | \"newest\" | \"oldest\";\nexport type CommunityTimeRange = \"weekly\" | \"monthly\" | \"all\";\nexport type CommunityFilterOption = \"all\" | \"mine\" | \"liked\";\n\nexport interface CommunityThemesResponse {\n  themes: CommunityTheme[];\n  nextCursor: string | number | null;\n}\n"
  },
  {
    "path": "types/editor.ts",
    "content": "import { ThemeStyles } from \"./theme\";\n\n// Base interface for any editor's state\nexport interface BaseEditorState {\n  styles: ThemeStyles;\n}\n\n// Interface for editor-specific controls\nexport interface EditorControls {\n  // Controls can be added per editor type as needed\n}\n\n// Interface for editor-specific preview props\nexport interface EditorPreviewProps {\n  styles: ThemeStyles;\n}\n\nexport interface ThemeEditorState extends BaseEditorState {\n  preset?: string;\n  styles: ThemeStyles;\n  currentMode: \"light\" | \"dark\";\n  hslAdjustments?: {\n    hueShift: number;\n    saturationScale: number;\n    lightnessScale: number;\n  };\n}\n\n// Type for available editors\nexport type EditorType = \"button\" | \"input\" | \"card\" | \"dialog\" | \"theme\";\n\n// Interface for editor configuration\nexport interface EditorConfig {\n  type: EditorType;\n  name: string;\n  description: string;\n  defaultState: BaseEditorState;\n  controls: React.ComponentType<any>;\n  preview: React.ComponentType<any>;\n}\n"
  },
  {
    "path": "types/errors.ts",
    "content": "import z from \"zod\";\n\n// Error codes for server actions - these survive serialization\nexport const ErrorCode = {\n  UNAUTHORIZED: \"UNAUTHORIZED\",\n  VALIDATION_ERROR: \"VALIDATION_ERROR\",\n  THEME_NOT_FOUND: \"THEME_NOT_FOUND\",\n  THEME_LIMIT_REACHED: \"THEME_LIMIT_REACHED\",\n  ALREADY_PUBLISHED: \"ALREADY_PUBLISHED\",\n  UNKNOWN_ERROR: \"UNKNOWN_ERROR\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\n// Typed result for server actions\nexport type ActionResult<T> =\n  | { success: true; data: T }\n  | { success: false; error: { code: ErrorCode; message: string } };\n\n// Helper to create error results\nexport function actionError(code: ErrorCode, message: string): ActionResult<never> {\n  return { success: false, error: { code, message } };\n}\n\n// Helper to create success results\nexport function actionSuccess<T>(data: T): ActionResult<T> {\n  return { success: true, data };\n}\n\nexport class UnauthorizedError extends Error {\n  constructor(message = \"Unauthorized\") {\n    super(message);\n    this.name = \"UnauthorizedError\";\n  }\n}\n\nexport class ValidationError extends Error {\n  constructor(\n    message: string,\n    public details?: unknown\n  ) {\n    super(message);\n    this.name = \"ValidationError\";\n  }\n}\n\nexport class SubscriptionRequiredError extends Error {\n  constructor(\n    message = \"Subscription required\",\n    public data?: unknown\n  ) {\n    super(message);\n    this.name = \"SubscriptionRequiredError\";\n  }\n}\n\nexport class ThemeNotFoundError extends Error {\n  constructor(message = \"Theme not found\") {\n    super(message);\n    this.name = \"ThemeNotFoundError\";\n  }\n}\n\nexport type ApiErrorCode =\n  | \"SUBSCRIPTION_REQUIRED\"\n  | \"VALIDATION_ERROR\"\n  | \"UNAUTHORIZED\"\n  | \"UNKNOWN_ERROR\";\n\nexport class ApiError extends Error {\n  constructor(\n    public code: ApiErrorCode,\n    message: string,\n    public data?: unknown,\n    public status?: number\n  ) {\n    super(message);\n    this.name = \"ApiError\";\n  }\n}\n\nexport const MyErrorResponseSchema = z.object({\n  code: z.string().optional(),\n  message: z.string().optional(),\n  data: z.unknown().optional(),\n  status: z.number().optional(),\n});\n\nexport type MyErrorResponseType = z.infer<typeof MyErrorResponseSchema>;\n"
  },
  {
    "path": "types/fonts.ts",
    "content": "export type FontCategory = \"sans-serif\" | \"serif\" | \"display\" | \"handwriting\" | \"monospace\";\n\n// Google Fonts API response types\nexport type GoogleFontAxis = {\n  tag: string;\n  start: number;\n  end: number;\n};\n\nexport type GoogleFontFiles = {\n  [variant: string]: string; // URL to font file\n};\n\nexport type GoogleFont = {\n  kind: \"webfonts#webfont\";\n  family: string;\n  category: FontCategory;\n  variants: string[];\n  subsets: string[];\n  version: string;\n  lastModified: string;\n  files: GoogleFontFiles;\n  menu?: string;\n  axes?: GoogleFontAxis[]; // For variable fonts\n};\n\nexport type GoogleFontsAPIResponse = {\n  kind: \"webfonts#webfontList\";\n  items: GoogleFont[];\n};\n\n// App's font info type\nexport type FontInfo = {\n  family: string;\n  category: FontCategory;\n  variants: string[];\n  variable: boolean;\n};\n\nexport type PaginatedFontsResponse = {\n  fonts: FontInfo[];\n  total: number;\n  offset: number;\n  limit: number;\n  hasMore: boolean;\n};\n"
  },
  {
    "path": "types/index.ts",
    "content": "import { FocusColorId } from \"@/store/color-control-focus-store\";\n\nexport type ControlSectionProps = {\n  title: string;\n  children: React.ReactNode;\n  expanded?: boolean;\n  className?: string;\n  headerAction?: React.ReactNode;\n};\n\nexport type ColorPickerProps = {\n  /**\n   * The current color value.\n   */\n  color: string;\n  /**\n   * Callback invoked whenever the color value changes.\n   */\n  onChange: (color: string) => void;\n  /**\n   * Human-readable label for the control.\n   */\n  label: string;\n  /**\n   * (Optional) Identifier that maps this color picker to a theme style key.\n   * When provided, it enables programmatic focusing via `focusColorControl()`.\n   */\n  name?: FocusColorId;\n};\n\nexport type SliderInputProps = {\n  value: number;\n  onChange: (value: number) => void;\n  min: number;\n  max: number;\n  step: number;\n  label: string;\n  unit?: string;\n};\n\nexport type ToggleOptionProps<T> = {\n  value: T;\n  options: { label: string; value: T }[];\n  onChange: (value: T) => void;\n  label: string;\n};\n\nexport type ReadOnlyColorDisplayProps = {\n  color: string;\n  label: string;\n  linkTo: string;\n};\n\nexport type ColorFormat = \"hex\" | \"rgb\" | \"hsl\" | \"oklch\";\n\nexport type ValidTailwindShade =\n  | \"50\"\n  | \"100\"\n  | \"200\"\n  | \"300\"\n  | \"400\"\n  | \"500\"\n  | \"600\"\n  | \"700\"\n  | \"800\"\n  | \"900\"\n  | \"950\";\n"
  },
  {
    "path": "types/live-preview-embed.ts",
    "content": "import type { ThemeEditorState } from \"@/types/editor\";\n\n// Keep in sync with public/live-preview-embed-script.js TWEAKCN_MESSAGE\nexport const MESSAGE = {\n  PING: \"TWEAKCN_PING\",\n  PONG: \"TWEAKCN_PONG\",\n  CHECK_SHADCN: \"TWEAKCN_CHECK_SHADCN\",\n  SHADCN_STATUS: \"TWEAKCN_SHADCN_STATUS\",\n  THEME_UPDATE: \"TWEAKCN_THEME_UPDATE\",\n  THEME_APPLIED: \"TWEAKCN_THEME_APPLIED\",\n  EMBED_LOADED: \"TWEAKCN_EMBED_LOADED\",\n  EMBED_ERROR: \"TWEAKCN_EMBED_ERROR\",\n} as const;\n\nexport type MessageType = (typeof MESSAGE)[keyof typeof MESSAGE];\n\nexport interface ShadcnStatusPayload {\n  supported: boolean;\n}\n\nexport interface ThemeUpdatePayload {\n  themeState: ThemeEditorState;\n}\n\nexport type EmbedMessage =\n  | { type: typeof MESSAGE.PING }\n  | { type: typeof MESSAGE.PONG }\n  | { type: typeof MESSAGE.CHECK_SHADCN }\n  | { type: typeof MESSAGE.SHADCN_STATUS; payload: ShadcnStatusPayload }\n  | { type: typeof MESSAGE.THEME_UPDATE; payload: ThemeUpdatePayload }\n  | { type: typeof MESSAGE.THEME_APPLIED }\n  | { type: typeof MESSAGE.EMBED_LOADED }\n  | { type: typeof MESSAGE.EMBED_ERROR; payload: { error: string } };\n\nexport type IframeStatus =\n  | \"unknown\"\n  | \"checking\"\n  | \"connected\"\n  | \"supported\"\n  | \"unsupported\"\n  | \"missing\"\n  | \"error\";\n"
  },
  {
    "path": "types/subscription.ts",
    "content": "export interface SubscriptionCheck extends SubscriptionStatus {\n  canProceed: boolean;\n  error?: string;\n}\n\nexport interface SubscriptionStatus {\n  isSubscribed: boolean;\n  requestsUsed: number;\n  requestsRemaining: number;\n}\n"
  },
  {
    "path": "types/theme.ts",
    "content": "import { theme } from \"@/db/schema\";\nimport { InferSelectModel } from \"drizzle-orm\";\nimport { z } from \"zod\";\n\nexport const themeStylePropsSchema = z.object({\n  background: z.string().describe(\"The default background color, paired with `foreground`.\"),\n  foreground: z.string().describe(\"Paired with `background`.\"),\n  card: z.string().describe(\"The background color for cards, paired with `card-foreground`.\"),\n  \"card-foreground\": z.string().describe(\"Paired with `card`.\"),\n  popover: z\n    .string()\n    .describe(\"The background color for popovers, paired with `popover-foreground`.\"),\n  \"popover-foreground\": z.string().describe(\"Paired with `popover`.\"),\n  primary: z.string().describe(\"The main color, paired with `primary-foreground`.\"),\n  \"primary-foreground\": z.string().describe(\"Paired with `primary`.\"),\n  secondary: z.string().describe(\"A secondary color, paired with `secondary-foreground`.\"),\n  \"secondary-foreground\": z.string().describe(\"Paired with `secondary`.\"),\n  muted: z.string().describe(\"A muted background color, paired with `muted-foreground`.\"),\n  \"muted-foreground\": z.string().describe(\"Paired with `muted`.\"),\n  accent: z\n    .string()\n    .describe(\"Subtle color for hover or highlight, paired with `accent-foreground`.\"),\n  \"accent-foreground\": z.string().describe(\"Paired with `accent`.\"),\n  destructive: z\n    .string()\n    .describe(\"Color for destructive actions, paired with `destructive-foreground`.\"),\n  \"destructive-foreground\": z.string().describe(\"Paired with `destructive`.\"),\n  border: z.string().describe(\"The color for borders.\"),\n  input: z.string().describe(\"The background color for input fields.\"),\n  ring: z.string().describe(\"The color for focus rings.\"),\n  \"chart-1\": z.string(),\n  \"chart-2\": z.string(),\n  \"chart-3\": z.string(),\n  \"chart-4\": z.string(),\n  \"chart-5\": z.string(),\n  sidebar: z\n    .string()\n    .describe(\"The background color for the sidebar, paired with `sidebar-foreground`.\"),\n  \"sidebar-foreground\": z.string().describe(\"Paired with `sidebar`.\"),\n  \"sidebar-primary\": z\n    .string()\n    .describe(\"The primary color for sidebar elements, paired with `sidebar-primary-foreground`.\"),\n  \"sidebar-primary-foreground\": z.string().describe(\"Paired with `sidebar-primary`.\"),\n  \"sidebar-accent\": z\n    .string()\n    .describe(\"An accent color for the sidebar, paired with `sidebar-accent-foreground`.\"),\n  \"sidebar-accent-foreground\": z.string().describe(\"Paired with `sidebar-accent`.\"),\n  \"sidebar-border\": z.string().describe(\"The color for borders within the sidebar.\"),\n  \"sidebar-ring\": z.string().describe(\"The color for focus rings within the sidebar.\"),\n  \"font-sans\": z\n    .string()\n    .describe(\n      \"Primary UI font. May be serif, sans, monospace, or display depending on the theme vibe.\"\n    ),\n  \"font-serif\": z.string().describe(\"The preferred serif font family.\"),\n  \"font-mono\": z.string().describe(\"The preferred monospace font family. Used for code blocks.\"),\n  radius: z\n    .string()\n    .describe(\"The global border-radius for components. Use 0rem for sharp corners.\"),\n  \"shadow-color\": z.string(),\n  \"shadow-opacity\": z.string(),\n  \"shadow-blur\": z.string(),\n  \"shadow-spread\": z.string(),\n  \"shadow-offset-x\": z.string(),\n  \"shadow-offset-y\": z.string(),\n  \"letter-spacing\": z.string().describe(\"The global letter spacing for text.\"),\n  spacing: z.string().optional(),\n});\n\nexport const themeStylesSchema = z.object({\n  light: themeStylePropsSchema,\n  dark: themeStylePropsSchema,\n});\n\nexport type ThemeStyleProps = z.infer<typeof themeStylePropsSchema>;\nexport type ThemeStyles = z.infer<typeof themeStylesSchema>;\n\nexport const themeStylePropsSchemaWithoutSpacing = themeStylePropsSchema.omit({\n  spacing: true,\n});\n\nexport const themeStylesSchemaWithoutSpacing = z.object({\n  light: themeStylePropsSchemaWithoutSpacing,\n  dark: themeStylePropsSchemaWithoutSpacing,\n});\n\nexport type ThemeStylesWithoutSpacing = z.infer<typeof themeStylesSchemaWithoutSpacing>;\n\nexport interface ThemeEditorPreviewProps {\n  styles: ThemeStyles;\n  currentMode: \"light\" | \"dark\";\n}\n\nexport interface ThemeEditorControlsProps {\n  styles: ThemeStyles;\n  currentMode: \"light\" | \"dark\";\n  onChange: (styles: ThemeStyles) => void;\n}\n\nexport type ThemePreset = {\n  source?: \"SAVED\" | \"BUILT_IN\";\n  createdAt?: string;\n  label?: string;\n  styles: {\n    light: Partial<ThemeStyleProps>;\n    dark: Partial<ThemeStyleProps>;\n  };\n};\n\nexport type Theme = InferSelectModel<typeof theme>;\n"
  },
  {
    "path": "utils/ai/ai-prompt.tsx",
    "content": "import { useEditorStore } from \"@/store/editor-store\";\nimport { useThemePresetStore } from \"@/store/theme-preset-store\";\nimport { AIPromptData, MentionReference, PromptImage } from \"@/types/ai\";\nimport { JSONContent } from \"@tiptap/react\";\n\nexport const getTextContent = (promptData: AIPromptData | null) => {\n  if (!promptData) return \"\";\n  return promptData.content;\n};\n\nexport const buildMentionStringForAPI = (mention: MentionReference) => {\n  return `@${mention.label} = \n  ${JSON.stringify(mention.themeData)}`;\n};\n\nexport const buildPromptForAPI = (promptData: AIPromptData) => {\n  const mentionReferences = promptData.mentions.map((mention) => buildMentionStringForAPI(mention));\n  return `${promptData.content}\\n\\n${mentionReferences.join(\"\\n\")}`;\n};\n\nexport const buildAIPromptRender = (promptData: AIPromptData): React.ReactNode => {\n  // Create a regex that matches all possible mention patterns from the actual mentions\n  const mentionPatterns = promptData.mentions.map(\n    (m) => `@${m.label.replace(/[.*+?^${}()|[\\\\]\\\\]/g, \"\\\\$&\")}`\n  );\n  const mentionRegex = new RegExp(`(${mentionPatterns.join(\"|\")})`, \"g\");\n\n  const parts = promptData.content.split(mentionRegex);\n  const textContent = parts.flatMap((part, index) => {\n    const mention = promptData.mentions.find((m) => `@${m.label}` === part);\n    if (mention) {\n      return (\n        <span key={index} className=\"mention\">\n          {part}\n        </span>\n      );\n    }\n    // Split by \\n and interleave <br /> to show line breaks in the messages UI\n    // without this, the line breaks are not shown and the user message looks messy.\n    const lines = part.split(\"\\n\");\n    return lines.flatMap((line, i) => (i === 0 ? line : [<br key={`br-${index}-${i}`} />, line]));\n  });\n\n  return textContent;\n};\n\nexport function attachCurrentThemeMention(promptData: AIPromptData): AIPromptData {\n  const currentThemeData = useEditorStore.getState().themeState.styles;\n\n  const mentionReference: MentionReference = {\n    id: \"editor:current-changes\",\n    label: \"Current Theme\",\n    themeData: currentThemeData,\n  };\n\n  const promptDataWithMention = {\n    ...promptData,\n    mentions: [...promptData.mentions, mentionReference],\n  };\n  return promptDataWithMention;\n}\n\nexport function createCurrentThemePrompt({ prompt }: { prompt: string }): AIPromptData {\n  const currentThemeData = useEditorStore.getState().themeState.styles;\n\n  const mentionReference: MentionReference = {\n    id: \"editor:current-changes\",\n    label: \"Current Theme\",\n    themeData: currentThemeData,\n  };\n\n  return {\n    content: `Make the following changes to the @Current Theme:\\n${prompt}`,\n    mentions: [mentionReference],\n  };\n}\n\nexport function mentionsCurrentTheme(promptData: AIPromptData): boolean {\n  return promptData.mentions.some((mention) => mention.id === \"editor:current-changes\");\n}\n\nexport function createPromptDataFromMentions(content: string, mentionIds: string[]): AIPromptData {\n  const mentions: MentionReference[] = mentionIds.map((id) => {\n    if (id === \"editor:current-changes\") {\n      return {\n        id,\n        label: \"Current Theme\",\n        themeData: useEditorStore.getState().themeState.styles,\n      };\n    }\n\n    const preset = useThemePresetStore.getState().getPreset(id);\n    if (!preset) {\n      throw new Error(`Theme preset not found: ${id}`);\n    }\n\n    return {\n      id,\n      label: preset.label || id,\n      themeData: preset.styles,\n    };\n  });\n\n  return {\n    content,\n    mentions,\n  };\n}\n\nexport function createPromptDataFromPreset(prompt: string, presetName: string): AIPromptData {\n  const preset = useThemePresetStore.getState().getPreset(presetName);\n\n  if (!preset) {\n    throw new Error(`Preset \"${presetName}\" not found`);\n  }\n\n  return {\n    content: prompt,\n    mentions: [\n      {\n        id: presetName,\n        label: preset.label ?? presetName,\n        themeData: {\n          light: preset.styles.light || {},\n          dark: preset.styles.dark || {},\n        },\n      },\n    ],\n  };\n}\n\n// Utility function to extract text content (user prompt) and theme mentions from the JSON content\n// we need both separate to create the prompt data to send to the AI\n// we also need to handle the line breaks correctly, both in copy/paste and while typing directly\nexport function extractTextContentAndMentions(node: JSONContent): {\n  content: string;\n  mentions: MentionReference[];\n} {\n  const textArr: string[] = [];\n  const mentionsArr: MentionReference[] = [];\n\n  // This is a recursive function that walks through the JSON content (even nested) and extracts the text content and mentions\n  const walk = (n: JSONContent) => {\n    if (n.type === \"text\") {\n      textArr.push(n.text || \"\");\n    }\n    if (n.type === \"mention\") {\n      textArr.push(`@${n.attrs?.label}`);\n      const id = n.attrs?.id;\n      const label = n.attrs?.label;\n      let themeData;\n      if (id === \"editor:current-changes\") {\n        themeData = useEditorStore.getState().themeState.styles;\n      } else {\n        const preset = useThemePresetStore.getState().getPreset(id);\n        themeData = preset?.styles || { light: {}, dark: {} };\n      }\n      mentionsArr.push({ id, label, themeData });\n    }\n    if (n.type === \"hardBreak\") {\n      textArr.push(\"\\n\");\n    }\n    if (n.content) {\n      n.content.forEach((child) => walk(child));\n    }\n  };\n\n  const blocks = node.content;\n  if (Array.isArray(blocks) && blocks.length > 0) {\n    blocks.forEach((block, idx) => {\n      walk(block);\n      if (idx < blocks.length - 1) {\n        textArr.push(\"\\n\");\n      }\n    });\n  } else {\n    walk(node);\n  }\n\n  const formattedText = textArr.join(\"\").replace(/\\\\n/g, \"\\n\");\n\n  return { content: formattedText, mentions: mentionsArr };\n}\n\nexport function convertJSONContentToPromptData(jsonContent: JSONContent): AIPromptData {\n  const { content, mentions } = extractTextContentAndMentions(jsonContent);\n  return { content, mentions };\n}\n\n/**\n * Converts AIPromptData (content + mentions) to JSONContent for initializing the editor.\n * Mentions are inserted as inline nodes, and line breaks are preserved as hardBreak nodes.\n * This matches the structure produced by Tiptap for multi-line content.\n */\nexport function convertPromptDataToJSONContent(promptData: AIPromptData): JSONContent {\n  const { content, mentions } = promptData;\n\n  if (!content) {\n    return {\n      type: \"doc\",\n      content: [\n        {\n          type: \"paragraph\",\n          content: [{ type: \"text\", text: \"\" }],\n        },\n      ],\n    };\n  }\n\n  // If no mentions, just return the content as text\n  if (!mentions || mentions.length === 0) {\n    const lines = content.split(/\\n/);\n    const nodes: JSONContent[] = [];\n\n    lines.forEach((line, lineIdx) => {\n      if (line) {\n        nodes.push({ type: \"text\", text: line });\n      }\n      if (lineIdx < lines.length - 1) {\n        nodes.push({ type: \"hardBreak\" });\n      }\n    });\n\n    if (nodes.length === 0) {\n      nodes.push({ type: \"text\", text: \"\" });\n    }\n\n    return {\n      type: \"doc\",\n      content: [\n        {\n          type: \"paragraph\",\n          content: nodes,\n        },\n      ],\n    };\n  }\n\n  // Process content with mentions using direct string search\n  const lines = content.split(/\\n/);\n  const nodes: JSONContent[] = [];\n\n  // Dedupe mention to avoid scanning the same label multiple times\n  const uniqueMentions = dedupeMentionReferences(mentions);\n\n  lines.forEach((line, lineIdx) => {\n    // Find all mention positions in the line\n    const mentionPositions: Array<{ index: number; mention: MentionReference; length: number }> =\n      [];\n\n    uniqueMentions.forEach((mention) => {\n      const mentionText = `@${mention.label}`;\n      let searchIndex = 0;\n\n      while (true) {\n        const foundIndex = line.indexOf(mentionText, searchIndex);\n        if (foundIndex === -1) break;\n\n        mentionPositions.push({\n          index: foundIndex,\n          mention,\n          length: mentionText.length,\n        });\n\n        searchIndex = foundIndex + mentionText.length;\n      }\n    });\n\n    // Sort by index asc; for same start index prefer the longest match\n    mentionPositions.sort((a, b) =>\n      a.index === b.index ? b.length - a.length : a.index - b.index\n    );\n\n    // Process the line with mentions\n    let currentIndex = 0;\n\n    mentionPositions.forEach(({ index, mention, length }) => {\n      // Skip if this mention would overlap with a previously emitted one\n      if (index < currentIndex) return;\n      // Add text before mention\n      if (index > currentIndex) {\n        const textBefore = line.slice(currentIndex, index);\n        if (textBefore) {\n          nodes.push({ type: \"text\", text: textBefore });\n        }\n      }\n\n      // Add mention node\n      nodes.push({\n        type: \"mention\",\n        attrs: {\n          id: mention.id,\n          label: mention.label,\n        },\n      });\n\n      currentIndex = index + length;\n    });\n\n    // If no mentions are present in this line, push the entire line once.\n    // Otherwise, only push the text that comes after the last mention.\n    if (mentionPositions.length === 0) {\n      if (line) {\n        nodes.push({ type: \"text\", text: line });\n      }\n    } else if (currentIndex < line.length) {\n      const textAfter = line.slice(currentIndex);\n      if (textAfter) {\n        nodes.push({ type: \"text\", text: textAfter });\n      }\n    }\n\n    // Add hardBreak if not the last line\n    if (lineIdx < lines.length - 1) {\n      nodes.push({ type: \"hardBreak\" });\n    }\n  });\n\n  // If no nodes were created, ensure at least one empty text node\n  if (nodes.length === 0) {\n    nodes.push({ type: \"text\", text: \"\" });\n  }\n\n  return {\n    type: \"doc\",\n    content: [\n      {\n        type: \"paragraph\",\n        content: nodes,\n      },\n    ],\n  };\n}\n\nexport function isEmptyPromptData(\n  promptData?: AIPromptData,\n  uploadedImages?: PromptImage[]\n): boolean {\n  const isEmptyPromptDataContent = !promptData?.content?.trim() || promptData?.content.length === 0;\n  const isEmptyPromptDataImages = !!uploadedImages && uploadedImages.length === 0;\n\n  return isEmptyPromptDataImages && isEmptyPromptDataContent;\n}\n\nexport function dedupeMentionReferences(mentions: MentionReference[]): MentionReference[] {\n  const uniqueMentions = new Map<string, MentionReference>();\n  for (const m of mentions) {\n    if (!uniqueMentions.has(m.id)) {\n      uniqueMentions.set(m.id, m);\n    }\n  }\n  return Array.from(uniqueMentions.values());\n}\n"
  },
  {
    "path": "utils/ai/apply-theme.ts",
    "content": "import { useEditorStore } from \"@/store/editor-store\";\nimport { ThemeStyles } from \"@/types/theme\";\nimport { mergeThemeStylesWithDefaults } from \"@/utils/theme-styles\";\n\nexport function applyGeneratedTheme(themeStyles: ThemeStyles) {\n  const { themeState, setThemeState } = useEditorStore.getState();\n\n  // Merge the generated theme styles with the default theme styles\n  // if the generated theme styles are missing a value, use the default theme styles\n  const mergedStyles = mergeThemeStylesWithDefaults(themeStyles);\n\n  if (!document.startViewTransition) {\n    setThemeState({\n      ...themeState,\n      styles: mergedStyles,\n    });\n  } else {\n    document.startViewTransition(() => {\n      setThemeState({\n        ...themeState,\n        styles: mergedStyles,\n      });\n    });\n  }\n}\n"
  },
  {
    "path": "utils/ai/image-upload.ts",
    "content": "export const ALLOWED_IMAGE_TYPES = [\n  \"image/jpeg\",\n  \"image/jpg\",\n  \"image/png\",\n  \"image/webp\",\n  \"image/svg+xml\",\n];\n\nexport function validateSvgContent(svgText: string): boolean {\n  try {\n    const trimmed = svgText.trim();\n    if (!trimmed.toLowerCase().includes(\"<svg\")) {\n      return false;\n    }\n\n    const dangerousPatterns = [\n      /<script/i,\n      /javascript:/i,\n      /on\\w+\\s*=/i, // onclick, onload, etc.\n      /<embed/i,\n      /<object/i,\n      /<iframe/i,\n    ];\n\n    return !dangerousPatterns.some((pattern) => pattern.test(svgText));\n  } catch {\n    return false;\n  }\n}\n\nexport function optimizeSvgContent(svgText: string): string {\n  try {\n    return svgText\n      .replace(/<!--[\\s\\S]*?-->/g, \"\") // Remove comments\n      .replace(/>\\s+</g, \"><\") // Remove unnecessary whitespace\n      .trim();\n  } catch {\n    return svgText.trim();\n  }\n}\n"
  },
  {
    "path": "utils/ai/message-converter.ts",
    "content": "import { AIPromptData, ChatMessage } from \"@/types/ai\";\nimport { buildMentionStringForAPI, dedupeMentionReferences } from \"@/utils/ai/ai-prompt\";\nimport { AssistantContent, ModelMessage, TextPart, UserContent } from \"ai\";\n\nexport function buildUserContentPartsFromPromptData(promptData: AIPromptData): UserContent {\n  const userContentParts: UserContent = [];\n\n  if (promptData.images && promptData.images.length > 0) {\n    promptData.images.forEach((image) => {\n      if (image.url.startsWith(\"data:image/svg+xml\")) {\n        try {\n          const dataUrlPart = image.url.split(\",\")[1];\n          let svgMarkup: string;\n\n          if (image.url.includes(\"base64\")) {\n            svgMarkup = atob(dataUrlPart);\n          } else {\n            svgMarkup = decodeURIComponent(dataUrlPart);\n          }\n\n          userContentParts.push({\n            type: \"text\",\n            text: `Here is an SVG image for analysis:\\n\\`\\`\\`svg\\n${svgMarkup}\\n\\`\\`\\``,\n          });\n        } catch {\n          userContentParts.push({\n            type: \"image\",\n            image: image.url,\n          });\n        }\n      } else {\n        userContentParts.push({\n          type: \"image\",\n          image: image.url,\n        });\n      }\n    });\n  }\n\n  // Add the prompt text content as a text part\n  const textContent = promptData.content;\n  if (textContent.trim().length > 0) {\n    const textPart: TextPart = {\n      type: \"text\",\n      text: textContent,\n    };\n    userContentParts.push(textPart);\n  }\n\n  const uniqueMentions = dedupeMentionReferences(promptData.mentions);\n  // Add each mention as a text part\n  uniqueMentions.forEach((mention) => {\n    userContentParts.push({\n      type: \"text\",\n      text: buildMentionStringForAPI(mention),\n    });\n  });\n\n  return userContentParts;\n}\n\nexport async function convertMessagesToModelMessages(\n  messages: ChatMessage[]\n): Promise<ModelMessage[]> {\n  const modelMessages: ModelMessage[] = [];\n\n  for (const message of messages) {\n    const promptData = message.metadata?.promptData;\n    const themeStyles = message.metadata?.themeStyles;\n\n    const msgTextContent = message.parts\n      .map((part) => (part.type === \"text\" ? part.text : \"\"))\n      .join(\"\");\n\n    if (message.role === \"user\" && promptData) {\n      const userContentParts = buildUserContentPartsFromPromptData(promptData);\n\n      modelMessages.push({\n        role: \"user\",\n        content: userContentParts,\n      });\n    }\n\n    if (message.role === \"assistant\") {\n      const assistantContentParts: AssistantContent = [];\n      assistantContentParts.push({\n        type: \"text\",\n        text: msgTextContent,\n      });\n\n      // If the assistant message has themeStyles attached to the metadata,\n      // we need to add it to the assistant content to provide more context for the next generations\n      if (themeStyles) {\n        assistantContentParts.push({\n          type: \"text\",\n          text: JSON.stringify(themeStyles),\n        });\n      }\n\n      modelMessages.push({\n        role: \"assistant\",\n        content: assistantContentParts,\n      });\n    }\n  }\n\n  return modelMessages;\n}\n"
  },
  {
    "path": "utils/ai/messages.ts",
    "content": "import { ChatMessage } from \"@/types/ai\";\n\nfunction filterMessagesToDisplay(messages: ChatMessage[]): ChatMessage[] {\n  return messages.filter((message) => {\n    const hasTextPart = message.parts.some((part) => part.type === \"text\" && Boolean(part.text));\n    const images = message.metadata?.promptData?.images;\n    const hasAttachments = images && images.length > 0;\n    return hasTextPart || hasAttachments;\n  });\n}\n\nfunction getUserMessages(messages: ChatMessage[]): ChatMessage[] {\n  return messages.filter((message) => message.role === \"user\");\n}\n\nfunction getLastUserMessage(messages: ChatMessage[]): ChatMessage | undefined {\n  return getUserMessages(messages).at(-1);\n}\n\nfunction getAssistantMessages(messages: ChatMessage[]): ChatMessage[] {\n  return messages.filter((message) => message.role === \"assistant\");\n}\n\nfunction getLastAssistantMessage(messages: ChatMessage[]): ChatMessage | undefined {\n  return getAssistantMessages(messages).at(-1);\n}\n\nexport {\n  filterMessagesToDisplay,\n  getAssistantMessages,\n  getLastAssistantMessage,\n  getLastUserMessage,\n  getUserMessages,\n};\n"
  },
  {
    "path": "utils/ai/prompts.ts",
    "content": "export const PROMPTS = {\n  flatDesign: {\n    label: \"Flat Design\",\n    prompt:\n      \"I want a flat design. Make the surface color tokens use the same color value, preferably the same as the 'background' color. Remove shadows completely. Default 'border' styles are okay.\",\n  },\n  minimalStyle: {\n    label: \"Minimal Style\",\n    prompt:\n      \"Generate a minimalist theme palette. All surfaces color tokens should use subtle variations of the same base color, with enough contrast to distinguish them when they are next to each other. For brand colors, keep original color palette. Minimize borders and shadows. For borders, use a subtle grayscale color. Typography should be clean, modern, and easy to read.\",\n  },\n  brutalist: {\n    label: \"Brutalist Vibe\",\n    prompt:\n      \"Make it brutalist style. Set 'radius' to '0px'. The 'border' color should strongly contrast with the 'background' color. For shadows, use a 'shadow-color' that also contrasts sharply with the 'background', set 'shadow-blur' to '0px', 'shadow-opacity' to '100%', and use 'shadow-offset', and 'shadow-spread' to create a hard offset shadow effect, do not exceed 4px. Keep original color palette, and make colors slightly more vibrant.\",\n  },\n};\n\ninterface RemixPrompt {\n  displayContent: string;\n  prompt: string;\n  basePreset: string;\n}\n\ninterface Prompt {\n  displayContent: string;\n  prompt: string;\n}\n\nexport const CREATE_PROMPTS: Prompt[] = [\n  {\n    displayContent: \"JavaScript/TypeScript Advent of Code playground\",\n    prompt:\n      \"Create a retro JavaScript Advent of Code theme. Use a grayish background with JavaScript yellow and TypeScript blue as primary/secondary colors. Change all fonts to monospace. Make borders sharp.\",\n  },\n  {\n    displayContent: \"Retro Terminal UI, green phosphor glow\",\n    prompt:\n      \"Create a retro terminal theme with black background (dark mode) and grayish background (light mode), use phosphorescent pure green (#22FF22 and shades of it) for text and borders. Use monospace fonts and sharp borders.\",\n  },\n  {\n    displayContent: \"Monochrome Manga-inspired theme\",\n    prompt:\n      \"Create a Manga-inspired theme. Monochromatic palette only (black, off-white, grays), square corners, small contrast solid offset shadows, and high-contrast borders (black on light, off-white on dark). Use a playful font, like Architects daughter.\",\n  },\n  {\n    displayContent: \"I want a minimal Ghibli Studio vibe\",\n    prompt:\n      \"Generate a theme inspired by Studio Ghibli — soft pastels, natural greens, organic colors, and hand-drawn charm.\",\n  },\n];\n\nexport const REMIX_PROMPTS: RemixPrompt[] = [\n  {\n    displayContent: \"Make @Twitter but in a slick purple\",\n    prompt: \"Make @Twitter but in a slick purple\",\n    basePreset: \"twitter\",\n  },\n  {\n    displayContent: \"What if @Supabase was vibrant blue?\",\n    prompt: \"Make @Supabase but in vibrant blue\",\n    basePreset: \"supabase\",\n  },\n  {\n    displayContent: \"I want @Doom 64 with muted colors\",\n    prompt: \"I want @Doom 64 with alternate colors\",\n    basePreset: \"doom-64\",\n  },\n];\n\nexport const VARIANT_PROMPTS: Prompt[] = [\n  {\n    displayContent: \"Make my @Current Theme minimalistic\",\n    prompt: PROMPTS.minimalStyle.prompt,\n  },\n  {\n    displayContent: \"Flatten the colors of my @Current Theme\",\n    prompt: PROMPTS.flatDesign.prompt,\n  },\n  {\n    displayContent: \"Create a brutalist variant of my @Current Theme\",\n    prompt: PROMPTS.brutalist.prompt,\n  },\n];\n"
  },
  {
    "path": "utils/apply-style-to-element.ts",
    "content": "export function applyStyleToElement(\n  element: HTMLElement,\n  key: string,\n  value: string\n) {\n  const currentStyle = element.getAttribute(\"style\") || \"\";\n  // Remove the existing variable definitions with the same name\n  const cleanedStyle = currentStyle.replace(\n    new RegExp(`--${key}:\\\\s*[^;]+;?`, \"g\"), \n    \"\"\n  ).trim();\n\n  element.setAttribute(\n    \"style\",\n    `${cleanedStyle}--${key}: ${value};`\n  );\n}\n"
  },
  {
    "path": "utils/apply-theme.ts",
    "content": "import { ThemeEditorState } from \"@/types/editor\";\nimport { ThemeStyleProps, ThemeStyles } from \"@/types/theme\";\nimport { colorFormatter } from \"./color-converter\";\nimport { setShadowVariables } from \"./shadows\";\nimport { applyStyleToElement } from \"./apply-style-to-element\";\nimport { COMMON_STYLES } from \"@/config/theme\";\n\ntype Theme = \"dark\" | \"light\";\n\nconst COMMON_NON_COLOR_KEYS = COMMON_STYLES;\n\n// Helper functions (not exported, used internally by applyThemeToElement)\nconst updateThemeClass = (root: HTMLElement, mode: Theme) => {\n  if (mode === \"light\") {\n    root.classList.remove(\"dark\");\n  } else {\n    root.classList.add(\"dark\");\n  }\n};\n\nconst applyCommonStyles = (root: HTMLElement, themeStyles: ThemeStyleProps) => {\n  Object.entries(themeStyles)\n    .filter(([key]) =>\n      COMMON_NON_COLOR_KEYS.includes(\n        key as (typeof COMMON_NON_COLOR_KEYS)[number]\n      )\n    )\n    .forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        applyStyleToElement(root, key, value);\n      }\n    });\n};\n\nconst applyThemeColors = (\n  root: HTMLElement,\n  themeStyles: ThemeStyles,\n  mode: Theme\n) => {\n  Object.entries(themeStyles[mode]).forEach(([key, value]) => {\n    if (\n      typeof value === \"string\" &&\n      !COMMON_NON_COLOR_KEYS.includes(\n        key as (typeof COMMON_NON_COLOR_KEYS)[number]\n      )\n    ) {\n      const hslValue = colorFormatter(value, \"hsl\", \"4\");\n      applyStyleToElement(root, key, hslValue);\n    }\n  });\n};\n\n// Exported function to apply theme styles to an element\nexport const applyThemeToElement = (\n  themeState: ThemeEditorState,\n  rootElement: HTMLElement\n) => {\n  const { currentMode: mode, styles: themeStyles } = themeState;\n\n  if (!rootElement) return;\n\n  updateThemeClass(rootElement, mode);\n  // Apply common styles (like border-radius) based on the 'light' mode definition\n  applyCommonStyles(rootElement, themeStyles.light);\n  // Apply mode-specific colors\n  applyThemeColors(rootElement, themeStyles, mode);\n  // Apply shadow variables\n  setShadowVariables(themeState);\n};\n"
  },
  {
    "path": "utils/color-converter.ts",
    "content": "import * as culori from \"culori\";\nimport { ColorFormat } from \"../types\";\nimport { Hsl } from \"culori\";\n\nexport const formatNumber = (num?: number) => {\n  if (!num) return \"0\";\n  return num % 1 === 0 ? num : num.toFixed(4);\n};\n\nexport const formatHsl = (hsl: Hsl) => {\n  return `hsl(${formatNumber(hsl.h)} ${formatNumber(hsl.s * 100)}% ${formatNumber(hsl.l * 100)}%)`;\n};\n\nexport const colorFormatter = (\n  colorValue: string,\n  format: ColorFormat = \"hsl\",\n  tailwindVersion: \"3\" | \"4\" = \"3\"\n): string => {\n  try {\n    const color = culori.parse(colorValue);\n    if (!color) throw new Error(\"Invalid color input\");\n\n    switch (format) {\n      case \"hsl\": {\n        const hsl = culori.converter(\"hsl\")(color);\n        if (tailwindVersion === \"4\") {\n          return formatHsl(hsl);\n        }\n        return `${formatNumber(hsl.h)} ${formatNumber(hsl.s * 100)}% ${formatNumber(hsl.l * 100)}%`;\n      }\n      case \"rgb\":\n        return culori.formatRgb(color); // e.g., \"rgb(64, 128, 192)\"\n      case \"oklch\": {\n        const oklch = culori.converter(\"oklch\")(color);\n        return `oklch(${formatNumber(oklch.l)} ${formatNumber(oklch.c)} ${formatNumber(oklch.h)})`;\n      }\n      case \"hex\":\n        return culori.formatHex(color); // e.g., \"#4080c0\"\n      default:\n        return colorValue;\n    }\n  } catch (error) {\n    console.error(`Failed to convert color: ${colorValue}`, error);\n    return colorValue;\n  }\n};\n\nexport const convertToHSL = (colorValue: string): string => colorFormatter(colorValue, \"hsl\");\n"
  },
  {
    "path": "utils/contrast-checker.ts",
    "content": "import * as culori from \"culori\";\n\n/**\n * Calculates the luminance of a color according to WCAG standard\n * @param colorValue - The color in any supported format\n * @returns The relative luminance of the color (0-1)\n */\nfunction getLuminance(colorValue: string): number {\n  try {\n    const color = culori.parse(colorValue);\n    if (!color) {\n      console.warn(`Invalid color: ${colorValue}`);\n      return 0;\n    }\n\n    // Culori directly provides the luminance according to WCAG standard\n    return culori.wcagLuminance(color);\n  } catch (error) {\n    console.error(`Error calculating luminance: ${colorValue}`, error);\n    return 0;\n  }\n}\n\n/**\n * Calculates the contrast ratio between two colors according to WCAG guidelines\n * @param color1 - First color (in any format)\n * @param color2 - Second color (in any format)\n * @returns The contrast ratio as a string (e.g. \"4.50\")\n */\nexport function getContrastRatio(color1: string, color2: string): string {\n  try {\n    const lum1 = getLuminance(color1);\n    const lum2 = getLuminance(color2);\n\n    // WCAG contrast ratio formula\n    const ratio = (Math.max(lum1, lum2) + 0.05) / (Math.min(lum1, lum2) + 0.05);\n    return ratio.toFixed(2);\n  } catch (error) {\n    console.error(\n      `Error calculating contrast between ${color1} and ${color2}:`,\n      error\n    );\n    return \"1.00\"; // Fallback value indicating low contrast\n  }\n}\n"
  },
  {
    "path": "utils/debounce.ts",
    "content": "export function debounce<T extends (...args: any[]) => void>(\n  fn: T,\n  delay: number\n): T & { cancel: () => void } {\n  let timeoutId: NodeJS.Timeout;\n\n  const debounced = function (this: unknown, ...args: Parameters<T>) {\n    clearTimeout(timeoutId);\n    timeoutId = setTimeout(() => {\n      fn.apply(this, args);\n    }, delay);\n  } as T & { cancel: () => void };\n\n  debounced.cancel = () => {\n    clearTimeout(timeoutId);\n  };\n\n  return debounced;\n}\n"
  },
  {
    "path": "utils/fonts/google-fonts.ts",
    "content": "import { FontInfo, GoogleFont, GoogleFontsAPIResponse } from \"@/types/fonts\";\n\nexport const GOOGLE_FONTS_API_URL = \"https://www.googleapis.com/webfonts/v1/webfonts\";\n\nexport async function fetchGoogleFonts(googleFontsApiKey: string | undefined): Promise<FontInfo[]> {\n  try {\n    if (!googleFontsApiKey) throw new Error(\"Google Fonts API key is required\");\n\n    const response = await fetch(`${GOOGLE_FONTS_API_URL}?key=${googleFontsApiKey}`);\n\n    if (!response.ok) throw new Error(`Google Fonts API error: ${response.status}`);\n\n    const data: GoogleFontsAPIResponse = await response.json();\n\n    // Transform to our format\n    const fonts: FontInfo[] = data.items.map((font: GoogleFont) => ({\n      family: font.family,\n      category: font.category,\n      variants: font.variants,\n      variable: font.variants.some(\n        (variant: string) => variant.includes(\"wght\") || variant.includes(\"ital,wght\")\n      ),\n    }));\n\n    console.log(`✅ Fetched ${fonts.length} fonts from Google Fonts API`);\n    return fonts;\n  } catch (error) {\n    console.error(\"Failed to fetch Google Fonts:\", error);\n    throw error;\n  }\n}\n\n// Build Google Fonts CSS API URL\nexport function buildFontCssUrl(family: string, weights: string[] = [\"400\"]): string {\n  const encodedFamily = encodeURIComponent(family);\n  const weightsParam = weights.join(\";\"); // Use semicolon for Google Fonts API v2\n  return `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@${weightsParam}&display=swap`;\n}\n\n// Simple font loading using native browser APIs\n// Just use a <link> tag - seems to be the recommended approach\nexport function loadGoogleFont(family: string, weights: string[] = [\"400\", \"700\"]): void {\n  if (typeof document === \"undefined\") return;\n\n  // Check if already loaded\n  const href = buildFontCssUrl(family, weights);\n  const existing = document.querySelector(`link[href=\"${href}\"]`);\n  if (existing) return;\n\n  const link = document.createElement(\"link\");\n  link.rel = \"stylesheet\";\n  link.href = href;\n  document.head.appendChild(link);\n}\n"
  },
  {
    "path": "utils/fonts/index.ts",
    "content": "import { FontCategory, FontInfo } from \"@/types/fonts\";\n\n// Categories mapped to their display names and common fallbacks\nexport const FONT_CATEGORIES = {\n  \"sans-serif\": {\n    label: \"Sans Serif\",\n    fallback: \"ui-sans-serif, system-ui, sans-serif\",\n  },\n  serif: {\n    label: \"Serif\",\n    fallback: \"ui-serif, Georgia, serif\",\n  },\n  monospace: {\n    label: \"Monospace\",\n    fallback:\n      \"ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Monaco, Consolas, 'Courier New', monospace\",\n  },\n  display: {\n    label: \"Display\",\n    fallback: \"ui-serif, Georgia, serif\",\n  },\n  handwriting: {\n    label: \"Handwriting\",\n    fallback: \"cursive\",\n  },\n} as const;\n\n// Fallback fonts if the JSON file doesn't exist\n// Must be in Sync with the Built-in Fonts\nexport const FALLBACK_FONTS: FontInfo[] = [\n  // Sans Serif fonts\n  {\n    family: \"Inter\",\n    category: \"sans-serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: true,\n  },\n  {\n    family: \"Roboto\",\n    category: \"sans-serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Open Sans\",\n    category: \"sans-serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: true,\n  },\n  {\n    family: \"Poppins\",\n    category: \"sans-serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Montserrat\",\n    category: \"sans-serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Outfit\",\n    category: \"sans-serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Plus Jakarta Sans\",\n    category: \"sans-serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"DM Sans\",\n    category: \"sans-serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: true,\n  },\n  {\n    family: \"Geist\",\n    category: \"sans-serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: true,\n  },\n  {\n    family: \"Oxanium\",\n    category: \"sans-serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Architects Daughter\",\n    category: \"handwriting\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  // Serif fonts\n  {\n    family: \"Merriweather\",\n    category: \"serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Playfair Display\",\n    category: \"serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: true,\n  },\n  {\n    family: \"Lora\",\n    category: \"serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: true,\n  },\n  {\n    family: \"Source Serif Pro\",\n    category: \"serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Libre Baskerville\",\n    category: \"serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Space Grotesk\",\n    category: \"serif\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  // Monospace fonts\n  {\n    family: \"JetBrains Mono\",\n    category: \"monospace\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: true,\n  },\n  {\n    family: \"Fira Code\",\n    category: \"monospace\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: true,\n  },\n  {\n    family: \"Source Code Pro\",\n    category: \"monospace\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"IBM Plex Mono\",\n    category: \"monospace\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Roboto Mono\",\n    category: \"monospace\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Space Mono\",\n    category: \"monospace\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: false,\n  },\n  {\n    family: \"Geist Mono\",\n    category: \"monospace\",\n    variants: [\"400\", \"600\", \"700\"],\n    variable: true,\n  },\n];\n\n// Build font-family value for CSS\n// e.g., font: \"Inter\", category: \"sans-serif\" -> \"Inter, ui-sans-serif, system-ui, sans-serif\"\nexport function buildFontFamily(fontFamily: string, category: FontCategory): string {\n  return `${fontFamily}, ${SYSTEM_FONTS_FALLBACKS[category]}`;\n}\n\n// Extract font family name from CSS font-family value\n// e.g., \"Inter, ui-sans-serif, system-ui, sans-serif\" -> \"Inter\"\nexport function extractFontFamily(fontFamilyValue: string): string | null {\n  if (!fontFamilyValue) return null;\n\n  // Split by comma and get the first font\n  const firstFont = fontFamilyValue.split(\",\")[0].trim();\n\n  // Remove quotes if present\n  const cleanFont = firstFont.replace(/['\"]/g, \"\");\n\n  // Skip system fonts\n  if (SYSTEM_FONTS.includes(cleanFont.toLowerCase())) return null;\n  return cleanFont;\n}\n\n// Get default weights for a font based on available variants\nexport function getDefaultWeights(variants: string[]): string[] {\n  const weightMap = [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"];\n  const availableWeights = variants.filter((variant) => weightMap.includes(variant));\n\n  if (availableWeights.length === 0) return [\"400\"]; // Fallback to normal weight\n\n  const preferredWeights = [\"400\", \"500\", \"600\", \"700\"];\n  const selectedWeights = preferredWeights.filter((weight) => availableWeights.includes(weight));\n\n  // If none of the preferred weights are available, use the first two available\n  if (selectedWeights.length === 0) {\n    const fallbackWeights = availableWeights.slice(0, 2);\n    return fallbackWeights.sort((a, b) => parseInt(a) - parseInt(b));\n  }\n\n  // Return up to 4 weights, starting with preferred ones\n  const finalWeights = [\n    ...selectedWeights,\n    ...availableWeights.filter((w) => !selectedWeights.includes(w)),\n  ].slice(0, 4);\n\n  // Sort weights numerically for Google Fonts API requirement\n  return finalWeights.sort((a, b) => parseInt(a) - parseInt(b));\n}\n\n// Check if a font is available using the native document.fonts API\nexport function isFontLoaded(family: string, weight = \"400\"): boolean {\n  if (typeof document === \"undefined\" || !document.fonts) return false;\n\n  // Use the native FontFaceSet.check() method\n  return document.fonts.check(`${weight} 16px \"${family}\"`);\n}\n\n// Wait for a font to load using the native document.fonts API\nexport async function waitForFont(\n  family: string,\n  weight = \"400\",\n  timeout = 3000\n): Promise<boolean> {\n  if (typeof document === \"undefined\" || !document.fonts) return false;\n\n  const font = `${weight} 16px \"${family}\"`;\n\n  try {\n    // Use the native document.fonts.load() method\n    await Promise.race([\n      document.fonts.load(font),\n      new Promise((_, reject) => setTimeout(() => reject(new Error(\"Timeout\")), timeout)),\n    ]);\n\n    return document.fonts.check(font);\n  } catch {\n    return false;\n  }\n}\n\nexport const SYSTEM_FONTS = [\n  \"ui-sans-serif\",\n  \"ui-serif\",\n  \"ui-monospace\",\n  \"system-ui\",\n  \"sans-serif\",\n  \"serif\",\n  \"monospace\",\n  \"cursive\",\n  \"fantasy\",\n];\n\nexport const SYSTEM_FONTS_FALLBACKS = {\n  \"sans-serif\": \"ui-sans-serif, sans-serif, system-ui\",\n  serif: \"ui-serif, serif\",\n  monospace: \"ui-monospace, monospace\",\n  display: \"ui-serif, serif\",\n  handwriting: \"cursive\",\n};\n"
  },
  {
    "path": "utils/format.ts",
    "content": "/**\n * Format number to show in k format (e.g., 1557 => 1.6k)\n */\nexport function formatCompactNumber(num: number): string {\n  if (num < 1000) {\n    return num.toString();\n  }\n  return (num / 1000).toFixed(1).replace(/\\.0$/, \"\") + \"k\";\n}\n"
  },
  {
    "path": "utils/parse-css-input.ts",
    "content": "import { ThemeStyleProps } from \"@/types/theme\";\nimport { colorFormatter } from \"./color-converter\";\nimport { COMMON_STYLES, defaultThemeState } from \"@/config/theme\";\n\nexport const variableNames = Object.keys(defaultThemeState.styles.light);\nconst nonColorVariables = COMMON_STYLES;\nconst VARIABLE_PREFIX = \"--\";\n\nexport const parseCssInput = (input: string) => {\n  const lightColors: ThemeStyleProps = {} as ThemeStyleProps;\n  const darkColors: ThemeStyleProps = {} as ThemeStyleProps;\n\n  try {\n    const rootContent = extractCssBlockContent(input, \":root\");\n    const darkContent = extractCssBlockContent(input, \".dark\");\n\n    if (rootContent) {\n      parseColorVariables(rootContent, lightColors, variableNames);\n    }\n    if (darkContent) {\n      parseColorVariables(darkContent, darkColors, variableNames);\n    }\n  } catch (error) {\n    console.error(\"Error parsing CSS input:\", error);\n  }\n\n  return { lightColors, darkColors };\n};\n\nconst extractCssBlockContent = (\n  input: string,\n  selector: string\n): string | null => {\n  const regex = new RegExp(`${escapeRegExp(selector)}\\\\s*{([^}]+)}`);\n  return input.match(regex)?.[1]?.trim() || null;\n};\n\nconst parseColorVariables = (\n  cssContent: string,\n  target: ThemeStyleProps,\n  validNames: string[]\n) => {\n  const variableDeclarations = cssContent.match(/--[^:]+:\\s*[^;]+/g) || [];\n\n  variableDeclarations.forEach((declaration) => {\n    const [name, value] = declaration.split(\":\").map((part) => part.trim());\n    const cleanName = name.replace(VARIABLE_PREFIX, \"\");\n\n    if (validNames.includes(cleanName)) {\n      if (nonColorVariables.includes(cleanName)) {\n        target[cleanName as keyof ThemeStyleProps] = value;\n        return;\n      }\n\n      const colorValue = processColorValue(value);\n      const formattedValue = colorFormatter(colorValue, \"hex\");\n      target[cleanName as keyof ThemeStyleProps] = formattedValue;\n    }\n  });\n};\n\nconst processColorValue = (value: string): string => {\n  return /^\\d/.test(value) ? `hsl(${value})` : value;\n};\n\n// Helper function to escape regex special characters\nconst escapeRegExp = (string: string): string => {\n  return string.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n};\n"
  },
  {
    "path": "utils/registry/tailwind-colors.ts",
    "content": "import { ValidTailwindShade } from \"@/types\";\n\n// Complete Tailwind CSS color palette\nexport const TAILWIND_PALETTE = {\n  white: {\n    DEFAULT: \"oklch(1 0 0)\",\n  },\n  black: {\n    DEFAULT: \"oklch(0 0 0)\",\n  },\n  transparent: {\n    DEFAULT: \"transparent\",\n  },\n  red: {\n    50: \"oklch(0.971 0.013 17.38)\",\n    100: \"oklch(0.936 0.032 17.717)\",\n    200: \"oklch(0.885 0.062 18.334)\",\n    300: \"oklch(0.808 0.114 19.571)\",\n    400: \"oklch(0.704 0.191 22.216)\",\n    500: \"oklch(0.637 0.237 25.331)\",\n    600: \"oklch(0.577 0.245 27.325)\",\n    700: \"oklch(0.505 0.213 27.518)\",\n    800: \"oklch(0.444 0.177 26.899)\",\n    900: \"oklch(0.396 0.141 25.723)\",\n    950: \"oklch(0.258 0.092 26.042)\",\n  },\n  orange: {\n    50: \"oklch(0.98 0.016 73.684)\",\n    100: \"oklch(0.954 0.038 75.164)\",\n    200: \"oklch(0.901 0.076 70.697)\",\n    300: \"oklch(0.837 0.128 66.29)\",\n    400: \"oklch(0.75 0.183 55.934)\",\n    500: \"oklch(0.705 0.213 47.604)\",\n    600: \"oklch(0.646 0.222 41.116)\",\n    700: \"oklch(0.553 0.195 38.402)\",\n    800: \"oklch(0.47 0.157 37.304)\",\n    900: \"oklch(0.408 0.123 38.172)\",\n    950: \"oklch(0.266 0.079 36.259)\",\n  },\n  amber: {\n    50: \"oklch(0.987 0.022 95.277)\",\n    100: \"oklch(0.962 0.059 95.617)\",\n    200: \"oklch(0.924 0.12 95.746)\",\n    300: \"oklch(0.879 0.169 91.605)\",\n    400: \"oklch(0.828 0.189 84.429)\",\n    500: \"oklch(0.769 0.188 70.08)\",\n    600: \"oklch(0.666 0.179 58.318)\",\n    700: \"oklch(0.555 0.163 48.998)\",\n    800: \"oklch(0.473 0.137 46.201)\",\n    900: \"oklch(0.414 0.112 45.904)\",\n    950: \"oklch(0.279 0.077 45.635)\",\n  },\n  yellow: {\n    50: \"oklch(0.987 0.026 102.212)\",\n    100: \"oklch(0.973 0.071 103.193)\",\n    200: \"oklch(0.945 0.129 101.54)\",\n    300: \"oklch(0.905 0.182 98.111)\",\n    400: \"oklch(0.852 0.199 91.936)\",\n    500: \"oklch(0.795 0.184 86.047)\",\n    600: \"oklch(0.681 0.162 75.834)\",\n    700: \"oklch(0.554 0.135 66.442)\",\n    800: \"oklch(0.476 0.114 61.907)\",\n    900: \"oklch(0.421 0.095 57.708)\",\n    950: \"oklch(0.286 0.066 53.813)\",\n  },\n  lime: {\n    50: \"oklch(0.986 0.031 120.757)\",\n    100: \"oklch(0.967 0.067 122.328)\",\n    200: \"oklch(0.938 0.127 124.321)\",\n    300: \"oklch(0.897 0.196 126.665)\",\n    400: \"oklch(0.841 0.238 128.85)\",\n    500: \"oklch(0.768 0.233 130.85)\",\n    600: \"oklch(0.648 0.2 131.684)\",\n    700: \"oklch(0.532 0.157 131.589)\",\n    800: \"oklch(0.453 0.124 130.933)\",\n    900: \"oklch(0.405 0.101 131.063)\",\n    950: \"oklch(0.274 0.072 132.109)\",\n  },\n  green: {\n    50: \"oklch(0.982 0.018 155.826)\",\n    100: \"oklch(0.962 0.044 156.743)\",\n    200: \"oklch(0.925 0.084 155.995)\",\n    300: \"oklch(0.871 0.15 154.449)\",\n    400: \"oklch(0.792 0.209 151.711)\",\n    500: \"oklch(0.723 0.219 149.579)\",\n    600: \"oklch(0.627 0.194 149.214)\",\n    700: \"oklch(0.527 0.154 150.069)\",\n    800: \"oklch(0.448 0.119 151.328)\",\n    900: \"oklch(0.393 0.095 152.535)\",\n    950: \"oklch(0.266 0.065 152.934)\",\n  },\n  emerald: {\n    50: \"oklch(0.979 0.021 166.113)\",\n    100: \"oklch(0.95 0.052 163.051)\",\n    200: \"oklch(0.905 0.093 164.15)\",\n    300: \"oklch(0.845 0.143 164.978)\",\n    400: \"oklch(0.765 0.177 163.223)\",\n    500: \"oklch(0.696 0.17 162.48)\",\n    600: \"oklch(0.596 0.145 163.225)\",\n    700: \"oklch(0.508 0.118 165.612)\",\n    800: \"oklch(0.432 0.095 166.913)\",\n    900: \"oklch(0.378 0.077 168.94)\",\n    950: \"oklch(0.262 0.051 172.552)\",\n  },\n  teal: {\n    50: \"oklch(0.984 0.014 180.72)\",\n    100: \"oklch(0.953 0.051 180.801)\",\n    200: \"oklch(0.91 0.096 180.426)\",\n    300: \"oklch(0.855 0.138 181.071)\",\n    400: \"oklch(0.777 0.152 181.912)\",\n    500: \"oklch(0.704 0.14 182.503)\",\n    600: \"oklch(0.6 0.118 184.704)\",\n    700: \"oklch(0.511 0.096 186.391)\",\n    800: \"oklch(0.437 0.078 188.216)\",\n    900: \"oklch(0.386 0.063 188.416)\",\n    950: \"oklch(0.277 0.046 192.524)\",\n  },\n  cyan: {\n    50: \"oklch(0.984 0.019 200.873)\",\n    100: \"oklch(0.956 0.045 203.388)\",\n    200: \"oklch(0.917 0.08 205.041)\",\n    300: \"oklch(0.865 0.127 207.078)\",\n    400: \"oklch(0.789 0.154 211.53)\",\n    500: \"oklch(0.715 0.143 215.221)\",\n    600: \"oklch(0.609 0.126 221.723)\",\n    700: \"oklch(0.52 0.105 223.128)\",\n    800: \"oklch(0.45 0.085 224.283)\",\n    900: \"oklch(0.398 0.07 227.392)\",\n    950: \"oklch(0.302 0.056 229.695)\",\n  },\n  sky: {\n    50: \"oklch(0.977 0.013 236.62)\",\n    100: \"oklch(0.951 0.026 236.824)\",\n    200: \"oklch(0.901 0.058 230.902)\",\n    300: \"oklch(0.828 0.111 230.318)\",\n    400: \"oklch(0.746 0.16 232.661)\",\n    500: \"oklch(0.685 0.169 237.323)\",\n    600: \"oklch(0.588 0.158 241.966)\",\n    700: \"oklch(0.5 0.134 242.749)\",\n    800: \"oklch(0.443 0.11 240.79)\",\n    900: \"oklch(0.391 0.09 240.876)\",\n    950: \"oklch(0.293 0.066 243.157)\",\n  },\n  blue: {\n    50: \"oklch(0.97 0.014 254.604)\",\n    100: \"oklch(0.932 0.032 255.585)\",\n    200: \"oklch(0.882 0.059 254.128)\",\n    300: \"oklch(0.809 0.105 251.813)\",\n    400: \"oklch(0.707 0.165 254.624)\",\n    500: \"oklch(0.623 0.214 259.815)\",\n    600: \"oklch(0.546 0.245 262.881)\",\n    700: \"oklch(0.488 0.243 264.376)\",\n    800: \"oklch(0.424 0.199 265.638)\",\n    900: \"oklch(0.379 0.146 265.522)\",\n    950: \"oklch(0.282 0.091 267.935)\",\n  },\n  indigo: {\n    50: \"oklch(0.962 0.018 272.314)\",\n    100: \"oklch(0.93 0.034 272.788)\",\n    200: \"oklch(0.87 0.065 274.039)\",\n    300: \"oklch(0.785 0.115 274.713)\",\n    400: \"oklch(0.673 0.182 276.935)\",\n    500: \"oklch(0.585 0.233 277.117)\",\n    600: \"oklch(0.511 0.262 276.966)\",\n    700: \"oklch(0.457 0.24 277.023)\",\n    800: \"oklch(0.398 0.195 277.366)\",\n    900: \"oklch(0.359 0.144 278.697)\",\n    950: \"oklch(0.257 0.09 281.288)\",\n  },\n  violet: {\n    50: \"oklch(0.969 0.016 293.756)\",\n    100: \"oklch(0.943 0.029 294.588)\",\n    200: \"oklch(0.894 0.057 293.283)\",\n    300: \"oklch(0.811 0.111 293.571)\",\n    400: \"oklch(0.702 0.183 293.541)\",\n    500: \"oklch(0.606 0.25 292.717)\",\n    600: \"oklch(0.541 0.281 293.009)\",\n    700: \"oklch(0.491 0.27 292.581)\",\n    800: \"oklch(0.432 0.232 292.759)\",\n    900: \"oklch(0.38 0.189 293.745)\",\n    950: \"oklch(0.283 0.141 291.089)\",\n  },\n  purple: {\n    50: \"oklch(0.977 0.014 308.299)\",\n    100: \"oklch(0.946 0.033 307.174)\",\n    200: \"oklch(0.902 0.063 306.703)\",\n    300: \"oklch(0.827 0.119 306.383)\",\n    400: \"oklch(0.714 0.203 305.504)\",\n    500: \"oklch(0.627 0.265 303.9)\",\n    600: \"oklch(0.558 0.288 302.321)\",\n    700: \"oklch(0.496 0.265 301.924)\",\n    800: \"oklch(0.438 0.218 303.724)\",\n    900: \"oklch(0.381 0.176 304.987)\",\n    950: \"oklch(0.291 0.149 302.717)\",\n  },\n  fuchsia: {\n    50: \"oklch(0.977 0.017 320.058)\",\n    100: \"oklch(0.952 0.037 318.852)\",\n    200: \"oklch(0.903 0.076 319.62)\",\n    300: \"oklch(0.833 0.145 321.434)\",\n    400: \"oklch(0.74 0.238 322.16)\",\n    500: \"oklch(0.667 0.295 322.15)\",\n    600: \"oklch(0.591 0.293 322.896)\",\n    700: \"oklch(0.518 0.253 323.949)\",\n    800: \"oklch(0.452 0.211 324.591)\",\n    900: \"oklch(0.401 0.17 325.612)\",\n    950: \"oklch(0.293 0.136 325.661)\",\n  },\n  pink: {\n    50: \"oklch(0.971 0.014 343.198)\",\n    100: \"oklch(0.948 0.028 342.258)\",\n    200: \"oklch(0.899 0.061 343.231)\",\n    300: \"oklch(0.823 0.12 346.018)\",\n    400: \"oklch(0.718 0.202 349.761)\",\n    500: \"oklch(0.656 0.241 354.308)\",\n    600: \"oklch(0.592 0.249 0.584)\",\n    700: \"oklch(0.525 0.223 3.958)\",\n    800: \"oklch(0.459 0.187 3.815)\",\n    900: \"oklch(0.408 0.153 2.432)\",\n    950: \"oklch(0.284 0.109 3.907)\",\n  },\n  rose: {\n    50: \"oklch(0.969 0.015 12.422)\",\n    100: \"oklch(0.941 0.03 12.58)\",\n    200: \"oklch(0.892 0.058 10.001)\",\n    300: \"oklch(0.81 0.117 11.638)\",\n    400: \"oklch(0.712 0.194 13.428)\",\n    500: \"oklch(0.645 0.246 16.439)\",\n    600: \"oklch(0.586 0.253 17.585)\",\n    700: \"oklch(0.514 0.222 16.935)\",\n    800: \"oklch(0.455 0.188 13.697)\",\n    900: \"oklch(0.41 0.159 10.272)\",\n    950: \"oklch(0.271 0.105 12.094)\",\n  },\n  slate: {\n    50: \"oklch(0.984 0.003 247.858)\",\n    100: \"oklch(0.968 0.007 247.896)\",\n    200: \"oklch(0.929 0.013 255.508)\",\n    300: \"oklch(0.869 0.022 252.894)\",\n    400: \"oklch(0.704 0.04 256.788)\",\n    500: \"oklch(0.554 0.046 257.417)\",\n    600: \"oklch(0.446 0.043 257.281)\",\n    700: \"oklch(0.372 0.044 257.287)\",\n    800: \"oklch(0.279 0.041 260.031)\",\n    900: \"oklch(0.208 0.042 265.755)\",\n    950: \"oklch(0.129 0.042 264.695)\",\n  },\n  gray: {\n    50: \"oklch(0.985 0.002 247.839)\",\n    100: \"oklch(0.967 0.003 264.542)\",\n    200: \"oklch(0.928 0.006 264.531)\",\n    300: \"oklch(0.872 0.01 258.338)\",\n    400: \"oklch(0.707 0.022 261.325)\",\n    500: \"oklch(0.551 0.027 264.364)\",\n    600: \"oklch(0.446 0.03 256.802)\",\n    700: \"oklch(0.373 0.034 259.733)\",\n    800: \"oklch(0.278 0.033 256.848)\",\n    900: \"oklch(0.21 0.034 264.665)\",\n    950: \"oklch(0.13 0.028 261.692)\",\n  },\n  zinc: {\n    50: \"oklch(0.985 0 0)\",\n    100: \"oklch(0.967 0.001 286.375)\",\n    200: \"oklch(0.92 0.004 286.32)\",\n    300: \"oklch(0.871 0.006 286.286)\",\n    400: \"oklch(0.705 0.015 286.067)\",\n    500: \"oklch(0.552 0.016 285.938)\",\n    600: \"oklch(0.442 0.017 285.786)\",\n    700: \"oklch(0.37 0.013 285.805)\",\n    800: \"oklch(0.274 0.006 286.033)\",\n    900: \"oklch(0.21 0.006 285.885)\",\n    950: \"oklch(0.141 0.005 285.823)\",\n  },\n  neutral: {\n    50: \"oklch(0.985 0 0)\",\n    100: \"oklch(0.97 0 0)\",\n    200: \"oklch(0.922 0 0)\",\n    300: \"oklch(0.87 0 0)\",\n    400: \"oklch(0.708 0 0)\",\n    500: \"oklch(0.556 0 0)\",\n    600: \"oklch(0.439 0 0)\",\n    700: \"oklch(0.371 0 0)\",\n    800: \"oklch(0.269 0 0)\",\n    900: \"oklch(0.205 0 0)\",\n    950: \"oklch(0.145 0 0)\",\n  },\n  stone: {\n    50: \"oklch(0.985 0.001 106.423)\",\n    100: \"oklch(0.97 0.001 106.424)\",\n    200: \"oklch(0.923 0.003 48.717)\",\n    300: \"oklch(0.869 0.005 56.366)\",\n    400: \"oklch(0.709 0.01 56.259)\",\n    500: \"oklch(0.553 0.013 58.071)\",\n    600: \"oklch(0.444 0.011 73.639)\",\n    700: \"oklch(0.374 0.01 67.558)\",\n    800: \"oklch(0.268 0.007 34.298)\",\n    900: \"oklch(0.216 0.006 56.043)\",\n    950: \"oklch(0.147 0.004 49.25)\",\n  },\n};\n\nexport const TAILWIND_SHADES: ValidTailwindShade[] = [\n  \"50\",\n  \"100\",\n  \"200\",\n  \"300\",\n  \"400\",\n  \"500\",\n  \"600\",\n  \"700\",\n  \"800\",\n  \"900\",\n  \"950\",\n];\n"
  },
  {
    "path": "utils/registry/themes.ts",
    "content": "import {\n  defaultDarkThemeStyles,\n  defaultLightThemeStyles,\n} from \"@/config/theme\";\nimport { ThemeStyleProps, ThemeStyles } from \"@/types/theme\";\nimport { colorFormatter } from \"@/utils/color-converter\";\nimport { getShadowMap } from \"@/utils/shadows\";\nimport { getPresetThemeStyles } from \"@/utils/theme-preset-helper\";\n\n// Convert HSL color to the format expected by shadcn registry\nconst convertToRegistryColor = (color: string): string => {\n  return colorFormatter(color, \"oklch\");\n};\n\n// Helper to get a value from either dark or light theme\nconst getThemeValue = (\n  dark: ThemeStyleProps,\n  light: ThemeStyleProps,\n  key: keyof ThemeStyleProps\n): string => {\n  return dark[key] || light[key] || \"\";\n};\n\n// Convert theme styles to registry format\nconst convertThemeStyles = (styles: ThemeStyles) => {\n  const { light, dark } = styles;\n\n  const convertTheme = (theme: ThemeStyleProps): ThemeStyleProps => {\n    const result: ThemeStyleProps = theme;\n    const convertColor = (color?: string) =>\n      convertToRegistryColor(color || \"\");\n\n    // Convert all color values\n    result.background = convertColor(theme.background);\n    result.foreground = convertColor(theme.foreground);\n    result.card = convertColor(theme.card);\n    result[\"card-foreground\"] = convertColor(theme[\"card-foreground\"]);\n    result.popover = convertColor(theme.popover);\n    result[\"popover-foreground\"] = convertColor(theme[\"popover-foreground\"]);\n    result.primary = convertColor(theme.primary);\n    result[\"primary-foreground\"] = convertColor(theme[\"primary-foreground\"]);\n    result.secondary = convertColor(theme.secondary);\n    result[\"secondary-foreground\"] = convertColor(\n      theme[\"secondary-foreground\"]\n    );\n    result.muted = convertColor(theme.muted);\n    result[\"muted-foreground\"] = convertColor(theme[\"muted-foreground\"]);\n    result.accent = convertColor(theme.accent);\n    result[\"accent-foreground\"] = convertColor(theme[\"accent-foreground\"]);\n    result.destructive = convertColor(theme.destructive);\n    result[\"destructive-foreground\"] = convertColor(\n      theme[\"destructive-foreground\"]\n    );\n    result.border = convertColor(theme.border);\n    result.input = convertColor(theme.input);\n    result.ring = convertColor(theme.ring);\n    result[\"chart-1\"] = convertColor(theme[\"chart-1\"]);\n    result[\"chart-2\"] = convertColor(theme[\"chart-2\"]);\n    result[\"chart-3\"] = convertColor(theme[\"chart-3\"]);\n    result[\"chart-4\"] = convertColor(theme[\"chart-4\"]);\n    result[\"chart-5\"] = convertColor(theme[\"chart-5\"]);\n    result.sidebar = convertColor(theme.sidebar);\n    result[\"sidebar-foreground\"] = convertColor(theme[\"sidebar-foreground\"]);\n    result[\"sidebar-primary\"] = convertColor(theme[\"sidebar-primary\"]);\n    result[\"sidebar-primary-foreground\"] = convertColor(\n      theme[\"sidebar-primary-foreground\"]\n    );\n    result[\"sidebar-accent\"] = convertColor(theme[\"sidebar-accent\"]);\n    result[\"sidebar-accent-foreground\"] = convertColor(\n      theme[\"sidebar-accent-foreground\"]\n    );\n    result[\"sidebar-border\"] = convertColor(theme[\"sidebar-border\"]);\n    result[\"sidebar-ring\"] = convertColor(theme[\"sidebar-ring\"]);\n\n    return result;\n  };\n\n  return {\n    light: { ...defaultLightThemeStyles, ...convertTheme(light) },\n    dark: { ...defaultDarkThemeStyles, ...convertTheme(dark) },\n  };\n};\n\n// This method will do the same as \"generateThemeRegistry\" from `scripts/generate-theme-registry.ts`\nexport const generateThemeRegistryFromPreset = (name: string) => {\n  const styles = getPresetThemeStyles(name);\n  const registryItem = generateThemeRegistryItemFromStyles(name, styles);\n  return registryItem;\n};\n\nexport const generateThemeRegistryItemFromStyles = (\n  name: string,\n  themeStyles: ThemeStyles\n) => {\n  const { light, dark } = convertThemeStyles(themeStyles);\n\n  // Generate shadow variables for both light and dark modes\n  const lightShadows = getShadowMap({\n    styles: { light, dark },\n    currentMode: \"light\",\n  });\n  const darkShadows = getShadowMap({\n    styles: { light, dark },\n    currentMode: \"dark\",\n  });\n\n  const registryItem = {\n    $schema: \"https://ui.shadcn.com/schema/registry-item.json\",\n    name,\n    type: \"registry:style\",\n    css: {\n      \"@layer base\": {\n        body: {\n          \"letter-spacing\": \"var(--tracking-normal)\",\n        },\n      },\n    },\n    cssVars: {\n      theme: {\n        \"font-sans\":\n          getThemeValue(dark, light, \"font-sans\") || \"Inter, sans-serif\",\n        \"font-mono\": getThemeValue(dark, light, \"font-mono\") || \"monospace\",\n        \"font-serif\": getThemeValue(dark, light, \"font-serif\") || \"serif\",\n        radius: getThemeValue(dark, light, \"radius\") || \"0.5rem\",\n        \"tracking-tighter\": \"calc(var(--tracking-normal) - 0.05em)\",\n        \"tracking-tight\": \"calc(var(--tracking-normal) - 0.025em)\",\n        \"tracking-wide\": \"calc(var(--tracking-normal) + 0.025em)\",\n        \"tracking-wider\": \"calc(var(--tracking-normal) + 0.05em)\",\n        \"tracking-widest\": \"calc(var(--tracking-normal) + 0.1em)\",\n      },\n      light: {\n        ...light,\n        \"shadow-2xs\": lightShadows[\"shadow-2xs\"],\n        \"shadow-xs\": lightShadows[\"shadow-xs\"],\n        \"shadow-sm\": lightShadows[\"shadow-sm\"],\n        shadow: lightShadows[\"shadow\"],\n        \"shadow-md\": lightShadows[\"shadow-md\"],\n        \"shadow-lg\": lightShadows[\"shadow-lg\"],\n        \"shadow-xl\": lightShadows[\"shadow-xl\"],\n        \"shadow-2xl\": lightShadows[\"shadow-2xl\"],\n        \"tracking-normal\":\n          getThemeValue(dark, light, \"letter-spacing\") || \"0em\",\n        spacing: getThemeValue(dark, light, \"spacing\") || \"0.25rem\",\n      },\n      dark: {\n        ...dark,\n        \"shadow-2xs\": darkShadows[\"shadow-2xs\"],\n        \"shadow-xs\": darkShadows[\"shadow-xs\"],\n        \"shadow-sm\": darkShadows[\"shadow-sm\"],\n        shadow: darkShadows[\"shadow\"],\n        \"shadow-md\": darkShadows[\"shadow-md\"],\n        \"shadow-lg\": darkShadows[\"shadow-lg\"],\n        \"shadow-xl\": darkShadows[\"shadow-xl\"],\n        \"shadow-2xl\": darkShadows[\"shadow-2xl\"],\n      },\n    },\n  };\n\n  return registryItem;\n};\n"
  },
  {
    "path": "utils/registry/v0.ts",
    "content": "import { ThemeStyles, ThemeStyleProps } from \"@/types/theme\";\nimport { colorFormatter } from \"@/utils/color-converter\";\nimport { getShadowMap } from \"@/utils/shadows\";\nimport { defaultLightThemeStyles, defaultDarkThemeStyles } from \"@/config/theme\";\nimport { SYSTEM_FONTS } from \"@/utils/fonts\";\n\ntype FontConfig = {\n  family: string;\n  variable: string;\n  variableName: string;\n};\n\nfunction extractFontFamily(fontFamilyValue: string): string | null {\n  if (!fontFamilyValue) return null;\n  const firstFont = fontFamilyValue.split(\",\")[0].trim();\n  const cleanFont = firstFont.replace(/['\"]/g, \"\");\n  if (SYSTEM_FONTS.includes(cleanFont.toLowerCase())) return null;\n  return cleanFont;\n}\n\nfunction toVariableName(fontFamily: string): string {\n  // \"Plus Jakarta Sans\" -> \"plusJakartaSans\"\n  const words = fontFamily.split(/\\s+/);\n  return words\n    .map((word, i) => (i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1)))\n    .join(\"\");\n}\n\nfunction toCssVariable(fontFamily: string): string {\n  // \"Plus Jakarta Sans\" -> \"--font-plus-jakarta-sans\"\n  return `--font-${fontFamily.toLowerCase().replace(/\\s+/g, \"-\")}`;\n}\n\nfunction extractGoogleFonts(themeStyles: ThemeStyles): FontConfig[] {\n  const fonts: FontConfig[] = [];\n  const seen = new Set<string>();\n\n  const fontKeys: (keyof ThemeStyleProps)[] = [\"font-sans\", \"font-serif\", \"font-mono\"];\n\n  for (const key of fontKeys) {\n    const fontValue = themeStyles.light[key] || themeStyles.dark[key];\n    if (!fontValue) continue;\n\n    const family = extractFontFamily(fontValue);\n    if (!family || seen.has(family)) continue;\n\n    seen.add(family);\n    fonts.push({\n      family,\n      variable: toCssVariable(family),\n      variableName: toVariableName(family),\n    });\n  }\n\n  return fonts;\n}\n\nfunction formatColor(color: string): string {\n  return colorFormatter(color, \"oklch\");\n}\n\nfunction generateColorVariables(styles: ThemeStyleProps): string {\n  return `  --background: ${formatColor(styles.background)};\n  --foreground: ${formatColor(styles.foreground)};\n  --card: ${formatColor(styles.card)};\n  --card-foreground: ${formatColor(styles[\"card-foreground\"])};\n  --popover: ${formatColor(styles.popover)};\n  --popover-foreground: ${formatColor(styles[\"popover-foreground\"])};\n  --primary: ${formatColor(styles.primary)};\n  --primary-foreground: ${formatColor(styles[\"primary-foreground\"])};\n  --secondary: ${formatColor(styles.secondary)};\n  --secondary-foreground: ${formatColor(styles[\"secondary-foreground\"])};\n  --muted: ${formatColor(styles.muted)};\n  --muted-foreground: ${formatColor(styles[\"muted-foreground\"])};\n  --accent: ${formatColor(styles.accent)};\n  --accent-foreground: ${formatColor(styles[\"accent-foreground\"])};\n  --destructive: ${formatColor(styles.destructive)};\n  --destructive-foreground: ${formatColor(styles[\"destructive-foreground\"])};\n  --border: ${formatColor(styles.border)};\n  --input: ${formatColor(styles.input)};\n  --ring: ${formatColor(styles.ring)};\n  --chart-1: ${formatColor(styles[\"chart-1\"])};\n  --chart-2: ${formatColor(styles[\"chart-2\"])};\n  --chart-3: ${formatColor(styles[\"chart-3\"])};\n  --chart-4: ${formatColor(styles[\"chart-4\"])};\n  --chart-5: ${formatColor(styles[\"chart-5\"])};\n  --radius: ${styles.radius};\n  --sidebar: ${formatColor(styles.sidebar)};\n  --sidebar-foreground: ${formatColor(styles[\"sidebar-foreground\"])};\n  --sidebar-primary: ${formatColor(styles[\"sidebar-primary\"])};\n  --sidebar-primary-foreground: ${formatColor(styles[\"sidebar-primary-foreground\"])};\n  --sidebar-accent: ${formatColor(styles[\"sidebar-accent\"])};\n  --sidebar-accent-foreground: ${formatColor(styles[\"sidebar-accent-foreground\"])};\n  --sidebar-border: ${formatColor(styles[\"sidebar-border\"])};\n  --sidebar-ring: ${formatColor(styles[\"sidebar-ring\"])};`;\n}\n\nexport function generateV0GlobalsCss(themeStyles: ThemeStyles): string {\n  const light = { ...defaultLightThemeStyles, ...themeStyles.light };\n  const dark = { ...defaultDarkThemeStyles, ...themeStyles.dark };\n\n  const lightShadows = getShadowMap({ styles: { light, dark }, currentMode: \"light\" });\n  const darkShadows = getShadowMap({ styles: { light, dark }, currentMode: \"dark\" });\n\n  const lightVars = generateColorVariables(light);\n  const darkVars = generateColorVariables(dark);\n\n  // Get font values with fallbacks\n  // Transform \"Roboto Mono, sans-serif\" -> \"Roboto Mono, Roboto Mono Fallback\"\n  const formatFontWithFallback = (fontValue: string): string => {\n    const firstFont = fontValue.split(\",\")[0].trim().replace(/['\"]/g, \"\");\n    return `${firstFont}, ${firstFont} Fallback`;\n  };\n\n  const fontSans = formatFontWithFallback(\n    light[\"font-sans\"] || defaultLightThemeStyles[\"font-sans\"]\n  );\n  const fontMono = formatFontWithFallback(\n    light[\"font-mono\"] || defaultLightThemeStyles[\"font-mono\"]\n  );\n  const fontSerif = formatFontWithFallback(\n    light[\"font-serif\"] || defaultLightThemeStyles[\"font-serif\"]\n  );\n\n  return `@import \"tailwindcss\";\n@import \"tw-animate-css\";\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n  --font-sans: ${fontSans};\n  --font-mono: ${fontMono};\n  --font-serif: ${fontSerif};\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --color-chart-1: var(--chart-1);\n  --color-chart-2: var(--chart-2);\n  --color-chart-3: var(--chart-3);\n  --color-chart-4: var(--chart-4);\n  --color-chart-5: var(--chart-5);\n  --color-sidebar: var(--sidebar);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-ring: var(--sidebar-ring);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --radius-2xl: calc(var(--radius) + 8px);\n  --radius-3xl: calc(var(--radius) + 12px);\n  --radius-4xl: calc(var(--radius) + 16px);\n}\n\n:root {\n${lightVars}\n  --shadow-2xs: ${lightShadows[\"shadow-2xs\"]};\n  --shadow-xs: ${lightShadows[\"shadow-xs\"]};\n  --shadow-sm: ${lightShadows[\"shadow-sm\"]};\n  --shadow: ${lightShadows[\"shadow\"]};\n  --shadow-md: ${lightShadows[\"shadow-md\"]};\n  --shadow-lg: ${lightShadows[\"shadow-lg\"]};\n  --shadow-xl: ${lightShadows[\"shadow-xl\"]};\n  --shadow-2xl: ${lightShadows[\"shadow-2xl\"]};\n}\n\n.dark {\n${darkVars}\n  --shadow-2xs: ${darkShadows[\"shadow-2xs\"]};\n  --shadow-xs: ${darkShadows[\"shadow-xs\"]};\n  --shadow-sm: ${darkShadows[\"shadow-sm\"]};\n  --shadow: ${darkShadows[\"shadow\"]};\n  --shadow-md: ${darkShadows[\"shadow-md\"]};\n  --shadow-lg: ${darkShadows[\"shadow-lg\"]};\n  --shadow-xl: ${darkShadows[\"shadow-xl\"]};\n  --shadow-2xl: ${darkShadows[\"shadow-2xl\"]};\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}`;\n}\n\nexport function generateV0LayoutTsx(themeStyles: ThemeStyles): string {\n  const fonts = extractGoogleFonts(themeStyles);\n  const hasFonts = fonts.length > 0;\n\n  // Generate font imports\n  const fontImports = hasFonts\n    ? `import { ${fonts.map((f) => f.family.replace(/\\s+/g, \"_\")).join(\", \")} } from \"next/font/google\";`\n    : \"\";\n\n  // Generate font declarations\n  const fontDeclarations = fonts\n    .map(\n      (f) => `const _${f.variableName} = ${f.family.replace(/\\s+/g, \"_\")}({ subsets: [\"latin\"] });`\n    )\n    .join(\"\\n\");\n\n  const htmlClassName = hasFonts ? `className=\"font-sans\"` : \"\";\n\n  return `import type { Metadata } from \"next\";\n${fontImports}\nimport \"./globals.css\";\n\nexport const metadata: Metadata = {\n  title: \"Create Next App\",\n  description: \"Generated by create next app\",\n};\n\n${fontDeclarations}\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <html lang=\"en\" ${htmlClassName}>\n      <body className=\"antialiased\">\n        {children}\n      </body>\n    </html>\n  );\n}`;\n}\n\nexport function generateV0PageTsx(themeName: string): string {\n  return `/**\n * This is a demo page to showcase the theme's color palette.\n * Feel free to replace this with your actual app's logic.\n */\n\nfunction ColorSwatch({ name, bgClass }: { name: string; bgClass: string }) {\n  return (\n    <div className=\"group relative\">\n      <div\n        className={\\`aspect-square rounded-lg border border-border/50 \\${bgClass} transition-transform duration-100 group-hover:scale-102\\`}\n      />\n      <p className=\"mt-2 text-xs text-muted-foreground font-mono text-center truncate\">{name}</p>\n    </div>\n  );\n}\n\nexport default function Page() {\n  return (\n    <main className=\"min-h-screen bg-background text-foreground\">\n      {/* Hero Section */}\n      <div className=\"relative overflow-hidden border-b border-border\">\n        <div className=\"absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-accent/5\" />\n        <div className=\"relative max-w-5xl mx-auto px-6 py-20\">\n          <h1 className=\"text-6xl font-bold tracking-tight text-balance\">${themeName}</h1>\n        </div>\n      </div>\n\n      {/* Color Palette */}\n      <div className=\"border-t border-border bg-muted/30\">\n        <div className=\"max-w-5xl mx-auto px-6 py-16\">\n          <h2 className=\"text-2xl font-semibold mb-2\">Color Palette</h2>\n          <p className=\"text-muted-foreground mb-10\">All the design tokens included in this theme.</p>\n\n          <div className=\"grid gap-10\">\n            {/* Core Colors */}\n            <div>\n              <h3 className=\"text-sm font-medium text-muted-foreground uppercase tracking-wider mb-4\">Core</h3>\n              <div className=\"grid grid-cols-4 md:grid-cols-8 gap-4\">\n                <ColorSwatch name=\"background\" bgClass=\"bg-background\" />\n                <ColorSwatch name=\"foreground\" bgClass=\"bg-foreground\" />\n                <ColorSwatch name=\"primary\" bgClass=\"bg-primary\" />\n                <ColorSwatch name=\"primary-fg\" bgClass=\"bg-primary-foreground\" />\n                <ColorSwatch name=\"secondary\" bgClass=\"bg-secondary\" />\n                <ColorSwatch name=\"secondary-fg\" bgClass=\"bg-secondary-foreground\" />\n                <ColorSwatch name=\"accent\" bgClass=\"bg-accent\" />\n                <ColorSwatch name=\"accent-fg\" bgClass=\"bg-accent-foreground\" />\n              </div>\n            </div>\n\n            {/* UI Colors */}\n            <div>\n              <h3 className=\"text-sm font-medium text-muted-foreground uppercase tracking-wider mb-4\">UI Elements</h3>\n              <div className=\"grid grid-cols-4 md:grid-cols-8 gap-4\">\n                <ColorSwatch name=\"card\" bgClass=\"bg-card\" />\n                <ColorSwatch name=\"card-fg\" bgClass=\"bg-card-foreground\" />\n                <ColorSwatch name=\"popover\" bgClass=\"bg-popover\" />\n                <ColorSwatch name=\"popover-fg\" bgClass=\"bg-popover-foreground\" />\n                <ColorSwatch name=\"muted\" bgClass=\"bg-muted\" />\n                <ColorSwatch name=\"muted-fg\" bgClass=\"bg-muted-foreground\" />\n                <ColorSwatch name=\"border\" bgClass=\"bg-border\" />\n                <ColorSwatch name=\"input\" bgClass=\"bg-input\" />\n              </div>\n            </div>\n\n            {/* Status & Chart */}\n            <div>\n              <h3 className=\"text-sm font-medium text-muted-foreground uppercase tracking-wider mb-4\">Status & Charts</h3>\n              <div className=\"grid grid-cols-4 md:grid-cols-8 gap-4\">\n                <ColorSwatch name=\"destructive\" bgClass=\"bg-destructive\" />\n                <ColorSwatch name=\"ring\" bgClass=\"bg-ring\" />\n                <ColorSwatch name=\"chart-1\" bgClass=\"bg-chart-1\" />\n                <ColorSwatch name=\"chart-2\" bgClass=\"bg-chart-2\" />\n                <ColorSwatch name=\"chart-3\" bgClass=\"bg-chart-3\" />\n                <ColorSwatch name=\"chart-4\" bgClass=\"bg-chart-4\" />\n                <ColorSwatch name=\"chart-5\" bgClass=\"bg-chart-5\" />\n              </div>\n            </div>\n\n            {/* Sidebar */}\n            <div>\n              <h3 className=\"text-sm font-medium text-muted-foreground uppercase tracking-wider mb-4\">Sidebar</h3>\n              <div className=\"grid grid-cols-4 md:grid-cols-8 gap-4\">\n                <ColorSwatch name=\"sidebar\" bgClass=\"bg-sidebar\" />\n                <ColorSwatch name=\"sidebar-fg\" bgClass=\"bg-sidebar-foreground\" />\n                <ColorSwatch name=\"sidebar-primary\" bgClass=\"bg-sidebar-primary\" />\n                <ColorSwatch name=\"sidebar-accent\" bgClass=\"bg-sidebar-accent\" />\n                <ColorSwatch name=\"sidebar-border\" bgClass=\"bg-sidebar-border\" />\n                <ColorSwatch name=\"sidebar-ring\" bgClass=\"bg-sidebar-ring\" />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      {/* Footer */}\n      <footer className=\"border-t border-border py-8\">\n        <div className=\"max-w-5xl mx-auto px-6 flex items-center justify-between\">\n          <p className=\"text-sm text-muted-foreground\">Built with tweakcn</p>\n        </div>\n      </footer>\n    </main>\n  );\n}`;\n}\n\nexport type V0RegistryFile = {\n  path: string;\n  content: string;\n  type: \"registry:file\" | \"registry:page\";\n  target: string;\n};\n\nexport type V0RegistryPayload = {\n  name: string;\n  files: V0RegistryFile[];\n  type: \"registry:item\";\n};\n\nexport function generateV0RegistryPayload(\n  themeName: string,\n  themeStyles: ThemeStyles\n): V0RegistryPayload {\n  return {\n    name: themeName,\n    type: \"registry:item\",\n    files: [\n      {\n        path: \"app/globals.css\",\n        content: generateV0GlobalsCss(themeStyles),\n        type: \"registry:file\",\n        target: \"app/globals.css\",\n      },\n      {\n        path: \"app/layout.tsx\",\n        content: generateV0LayoutTsx(themeStyles),\n        type: \"registry:page\",\n        target: \"app/layout.tsx\",\n      },\n      {\n        path: \"app/page.tsx\",\n        content: generateV0PageTsx(themeName),\n        type: \"registry:page\",\n        target: \"app/page.tsx\",\n      },\n    ],\n  };\n}\n"
  },
  {
    "path": "utils/shadows.ts",
    "content": "import { colorFormatter } from \"./color-converter\";\nimport { applyStyleToElement } from \"./apply-style-to-element\";\nimport { ThemeEditorState } from \"../types/editor\";\nimport { defaultThemeState } from \"../config/theme\";\n\nexport const getShadowMap = (themeEditorState: ThemeEditorState) => {\n  const mode = themeEditorState.currentMode;\n  const styles = {\n    ...defaultThemeState.styles[mode],\n    ...themeEditorState.styles[mode],\n  };\n\n  const shadowColor = styles[\"shadow-color\"];\n  const hsl = colorFormatter(shadowColor, \"hsl\", \"3\");\n  const offsetX = styles[\"shadow-offset-x\"];\n  const offsetY = styles[\"shadow-offset-y\"];\n  const blur = styles[\"shadow-blur\"];\n  const spread = styles[\"shadow-spread\"];\n  const opacity = parseFloat(styles[\"shadow-opacity\"]);\n  const color = (opacityMultiplier: number) =>\n    `hsl(${hsl} / ${(opacity * opacityMultiplier).toFixed(2)})`;\n\n  const secondLayer = (fixedOffsetY: string, fixedBlur: string): string => {\n    // Use the same offsetX as the first layer\n    const offsetX2 = offsetX;\n    // Use the fixed offsetY specific to the shadow size\n    const offsetY2 = fixedOffsetY;\n    // Use the fixed blur specific to the shadow size\n    const blur2 = fixedBlur;\n    // Calculate spread relative to the first layer's spread variable\n    const spread2 = (parseFloat(spread?.replace(\"px\", \"\") ?? \"0\") - 1).toString() + \"px\";\n    // Use the same color function (opacity can still be overridden by --shadow-opacity)\n    const color2 = color(1.0); // Default opacity for second layer is 0.1 in examples\n\n    return `${offsetX2} ${offsetY2} ${blur2} ${spread2} ${color2}`;\n  };\n\n  // Map shadow names to their CSS variable string structures\n  const shadowMap: { [key: string]: string } = {\n    // Single layer shadows - use base variables directly\n    \"shadow-2xs\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(0.5)}`, // Assumes vars set appropriately (e.g., y=1, blur=0, spread=0)\n    \"shadow-xs\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(0.5)}`, // Assumes vars set appropriately (e.g., y=1, blur=2, spread=0)\n    \"shadow-2xl\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(2.5)}`, // Assumes vars set appropriately (e.g., y=25, blur=50, spread=-12)\n\n    // Two layer shadows - use base vars for layer 1, mix fixed/calculated for layer 2\n    \"shadow-sm\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(\n      1.0\n    )}, ${secondLayer(\"1px\", \"2px\")}`,\n    shadow: `${offsetX} ${offsetY} ${blur} ${spread} ${color(1.0)}, ${secondLayer(\"1px\", \"2px\")}`, // Alias for the 'shadow:' example line\n\n    \"shadow-md\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(\n      1.0\n    )}, ${secondLayer(\"2px\", \"4px\")}`,\n\n    \"shadow-lg\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(\n      1.0\n    )}, ${secondLayer(\"4px\", \"6px\")}`,\n\n    \"shadow-xl\": `${offsetX} ${offsetY} ${blur} ${spread} ${color(\n      1.0\n    )}, ${secondLayer(\"8px\", \"10px\")}`,\n  };\n\n  return shadowMap;\n};\n\n// Function to set shadow CSS variables\nexport function setShadowVariables(themeEditorState: ThemeEditorState) {\n  const root = document.documentElement;\n\n  const shadows = getShadowMap(themeEditorState);\n  Object.entries(shadows).forEach(([name, value]) => {\n    applyStyleToElement(root, name, value);\n  });\n}\n"
  },
  {
    "path": "utils/subscription.ts",
    "content": "import { AI_REQUEST_FREE_TIER_LIMIT, MAX_FREE_THEMES } from \"@/lib/constants\";\n\ntype Feature = {\n  description: string;\n  status: \"done\" | \"pending\";\n};\n\nexport const FREE_SUB_FEATURES: Feature[] = [\n  { description: \"Full theme customization\", status: \"done\" },\n  { description: `${AI_REQUEST_FREE_TIER_LIMIT} AI generated themes`, status: \"done\" },\n  { description: `Save and share up to ${MAX_FREE_THEMES} themes`, status: \"done\" },\n  { description: \"Import theme using CSS variables\", status: \"done\" },\n  { description: \"Export theme via CSS variables\", status: \"done\" },\n  { description: \"Export theme via Shadcn Registry Command\", status: \"done\" },\n  { description: \"Contrast checker\", status: \"done\" },\n];\n\nexport const PRO_SUB_FEATURES: Feature[] = [\n  { description: \"Save and share unlimited themes\", status: \"done\" },\n  { description: \"Unlimited AI generated themes\", status: \"done\" },\n  { description: \"Generate themes from images using AI\", status: \"done\" },\n  { description: \"Priority support\", status: \"done\" },\n  { description: \"Save your own fonts and colors\", status: \"pending\" },\n];\n"
  },
  {
    "path": "utils/theme-fonts.ts",
    "content": "import { ThemeEditorState } from \"@/types/editor\";\nimport { SYSTEM_FONTS } from \"@/utils/fonts\";\n\nconst sansSerifFontNames = [\n  \"Inter\",\n  \"Roboto\",\n  \"Open Sans\",\n  \"Poppins\",\n  \"Montserrat\",\n  \"Outfit\",\n  \"Plus Jakarta Sans\",\n  \"DM Sans\",\n  \"Geist\",\n  \"Oxanium\",\n  \"Architects Daughter\",\n];\n\nconst serifFontNames = [\n  \"Merriweather\",\n  \"Playfair Display\",\n  \"Lora\",\n  \"Source Serif Pro\",\n  \"Libre Baskerville\",\n  \"Space Grotesk\",\n];\n\nconst monoFontNames = [\n  \"JetBrains Mono\",\n  \"Fira Code\",\n  \"Source Code Pro\",\n  \"IBM Plex Mono\",\n  \"Roboto Mono\",\n  \"Space Mono\",\n  \"Geist Mono\",\n];\n\nexport const fonts: Record<string, string> = {\n  // Sans-serif fonts\n  Inter: \"Inter, sans-serif\",\n  Roboto: \"Roboto, sans-serif\",\n  \"Open Sans\": \"Open Sans, sans-serif\",\n  Poppins: \"Poppins, sans-serif\",\n  Montserrat: \"Montserrat, sans-serif\",\n  Outfit: \"Outfit, sans-serif\",\n  \"Plus Jakarta Sans\": \"Plus Jakarta Sans, sans-serif\",\n  \"DM Sans\": \"DM Sans, sans-serif\",\n  \"IBM Plex Sans\": \"IBM Plex Sans, sans-serif\",\n  Geist: \"Geist, sans-serif\",\n  Oxanium: \"Oxanium, sans-serif\",\n  \"Architects Daughter\": \"Architects Daughter, sans-serif\",\n\n  // Serif fonts\n  Merriweather: \"Merriweather, serif\",\n  \"Playfair Display\": \"Playfair Display, serif\",\n  Lora: \"Lora, serif\",\n  \"Source Serif Pro\": \"Source Serif Pro, serif\",\n  \"Libre Baskerville\": \"Libre Baskerville, serif\",\n  \"Space Grotesk\": \"Space Grotesk, serif\",\n\n  // Monospace fonts\n  \"JetBrains Mono\": \"JetBrains Mono, monospace\",\n  \"Fira Code\": \"Fira Code, monospace\",\n  \"Source Code Pro\": \"Source Code Pro, monospace\",\n  \"IBM Plex Mono\": \"IBM Plex Mono, monospace\",\n  \"Roboto Mono\": \"Roboto Mono, monospace\",\n  \"Space Mono\": \"Space Mono, monospace\",\n  \"Geist Mono\": \"Geist Mono, monospace\",\n};\n\nexport const sansSerifFonts = Object.fromEntries(\n  Object.entries(fonts).filter(([key]) => sansSerifFontNames.includes(key))\n);\nexport const serifFonts = Object.fromEntries(\n  Object.entries(fonts).filter(([key]) => serifFontNames.includes(key))\n);\nexport const monoFonts = Object.fromEntries(\n  Object.entries(fonts).filter(([key]) => monoFontNames.includes(key))\n);\n\nexport const getAppliedThemeFont = (\n  state: ThemeEditorState,\n  fontKey: \"font-sans\" | \"font-serif\" | \"font-mono\"\n): string | null => {\n  const currentStyles = state.styles[state.currentMode];\n  const fontValue = currentStyles[fontKey];\n\n  // Extract the font family name from the font value\n  if (!fontValue) return null;\n\n  // Handle both old format (\"Outfit, sans-serif\") and new format (\"Outfit\", ui-sans-serif, ...)\n  const firstFont = fontValue.split(\",\")[0].trim();\n  const cleanFont = firstFont.replace(/['\"]/g, \"\");\n\n  // Skip system fonts\n  if (SYSTEM_FONTS.includes(cleanFont.toLowerCase())) return null;\n  return cleanFont;\n};\n"
  },
  {
    "path": "utils/theme-preset-helper.ts",
    "content": "import { defaultThemeState } from \"../config/theme\";\nimport { ThemeStyles } from \"../types/theme\";\nimport { useThemePresetStore } from \"../store/theme-preset-store\";\nimport { defaultPresets } from \"./theme-presets\";\n\n/**\n * Get built-in theme styles by name (without using store).\n * Use this for server-side code where store access is not available.\n * Returns null if the preset doesn't exist.\n */\nexport function getBuiltInThemeStyles(name: string): { name: string; styles: ThemeStyles } | null {\n  const preset = defaultPresets[name];\n  if (!preset) {\n    return null;\n  }\n\n  const styles = mergePresetWithDefaults(preset.styles);\n  return {\n    name: preset.label || name,\n    styles,\n  };\n}\n\nfunction mergePresetWithDefaults(presetStyles: {\n  light?: Partial<ThemeStyles[\"light\"]>;\n  dark?: Partial<ThemeStyles[\"dark\"]>;\n}): ThemeStyles {\n  const defaultTheme = defaultThemeState.styles;\n  return {\n    light: {\n      ...defaultTheme.light,\n      ...(presetStyles.light || {}),\n    },\n    dark: {\n      ...defaultTheme.dark,\n      ...(presetStyles.light || {}),\n      ...(presetStyles.dark || {}),\n    },\n  };\n}\n\nexport function getPresetThemeStyles(name: string): ThemeStyles {\n  if (name === \"default\") {\n    return defaultThemeState.styles;\n  }\n\n  const store = useThemePresetStore.getState();\n  const preset = store.getPreset(name);\n  if (!preset) {\n    return defaultThemeState.styles;\n  }\n\n  return mergePresetWithDefaults(preset.styles);\n}\n"
  },
  {
    "path": "utils/theme-presets.ts",
    "content": "import { ThemePreset } from \"../types/theme\";\n\nexport const defaultPresets: Record<string, ThemePreset> = {\n  \"modern-minimal\": {\n    label: \"Modern Minimal\",\n    styles: {\n      light: {\n        background: \"#ffffff\",\n        foreground: \"#333333\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#333333\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#333333\",\n        primary: \"#3b82f6\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#f3f4f6\",\n        \"secondary-foreground\": \"#4b5563\",\n        muted: \"#f9fafb\",\n        \"muted-foreground\": \"#6b7280\",\n        accent: \"#e0f2fe\",\n        \"accent-foreground\": \"#1e3a8a\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#e5e7eb\",\n        input: \"#e5e7eb\",\n        ring: \"#3b82f6\",\n        \"chart-1\": \"#3b82f6\",\n        \"chart-2\": \"#2563eb\",\n        \"chart-3\": \"#1d4ed8\",\n        \"chart-4\": \"#1e40af\",\n        \"chart-5\": \"#1e3a8a\",\n        radius: \"0.375rem\",\n        sidebar: \"#f9fafb\",\n        \"sidebar-foreground\": \"#333333\",\n        \"sidebar-primary\": \"#3b82f6\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#e0f2fe\",\n        \"sidebar-accent-foreground\": \"#1e3a8a\",\n        \"sidebar-border\": \"#e5e7eb\",\n        \"sidebar-ring\": \"#3b82f6\",\n        \"font-sans\": \"Inter, sans-serif\",\n        \"font-serif\": \"Source Serif 4, serif\",\n        \"font-mono\": \"JetBrains Mono, monospace\",\n      },\n      dark: {\n        background: \"#171717\",\n        foreground: \"#e5e5e5\",\n        card: \"#262626\",\n        \"card-foreground\": \"#e5e5e5\",\n        popover: \"#262626\",\n        \"popover-foreground\": \"#e5e5e5\",\n        primary: \"#3b82f6\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#262626\",\n        \"secondary-foreground\": \"#e5e5e5\",\n        muted: \"#1f1f1f\",\n        \"muted-foreground\": \"#a3a3a3\",\n        accent: \"#1e3a8a\",\n        \"accent-foreground\": \"#bfdbfe\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#404040\",\n        input: \"#404040\",\n        ring: \"#3b82f6\",\n        \"chart-1\": \"#60a5fa\",\n        \"chart-2\": \"#3b82f6\",\n        \"chart-3\": \"#2563eb\",\n        \"chart-4\": \"#1d4ed8\",\n        \"chart-5\": \"#1e40af\",\n        radius: \"0.375rem\",\n        sidebar: \"#171717\",\n        \"sidebar-foreground\": \"#e5e5e5\",\n        \"sidebar-primary\": \"#3b82f6\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#1e3a8a\",\n        \"sidebar-accent-foreground\": \"#bfdbfe\",\n        \"sidebar-border\": \"#404040\",\n        \"sidebar-ring\": \"#3b82f6\",\n      },\n    },\n  },\n\n  \"violet-bloom\": {\n    label: \"Violet Bloom\",\n    createdAt: \"2025-06-26\",\n    styles: {\n      light: {\n        background: \"#fdfdfd\",\n        foreground: \"#000000\",\n        card: \"#fdfdfd\",\n        \"card-foreground\": \"#000000\",\n        popover: \"#fcfcfc\",\n        \"popover-foreground\": \"#000000\",\n        primary: \"#7033ff\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#edf0f4\",\n        \"secondary-foreground\": \"#080808\",\n        muted: \"#f5f5f5\",\n        \"muted-foreground\": \"#525252\",\n        accent: \"#e2ebff\",\n        \"accent-foreground\": \"#1e69dc\",\n        destructive: \"#e54b4f\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#e7e7ee\",\n        input: \"#ebebeb\",\n        ring: \"#000000\",\n        \"chart-1\": \"#4ac885\",\n        \"chart-2\": \"#7033ff\",\n        \"chart-3\": \"#fd822b\",\n        \"chart-4\": \"#3276e4\",\n        \"chart-5\": \"#747474\",\n        sidebar: \"#f5f8fb\",\n        \"sidebar-foreground\": \"#000000\",\n        \"sidebar-primary\": \"#000000\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#ebebeb\",\n        \"sidebar-accent-foreground\": \"#000000\",\n        \"sidebar-border\": \"#ebebeb\",\n        \"sidebar-ring\": \"#000000\",\n        \"font-sans\": \"Plus Jakarta Sans, sans-serif\",\n        \"font-serif\": \"Lora, serif\",\n        \"font-mono\": \"IBM Plex Mono, monospace\",\n        radius: \"1.4rem\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.16\",\n        \"shadow-blur\": \"3px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"2px\",\n        \"letter-spacing\": \"-0.025em\",\n        spacing: \"0.27rem\",\n      },\n      dark: {\n        background: \"#1a1b1e\",\n        foreground: \"#f0f0f0\",\n        card: \"#222327\",\n        \"card-foreground\": \"#f0f0f0\",\n        popover: \"#222327\",\n        \"popover-foreground\": \"#f0f0f0\",\n        primary: \"#8c5cff\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#2a2c33\",\n        \"secondary-foreground\": \"#f0f0f0\",\n        muted: \"#2a2c33\",\n        \"muted-foreground\": \"#a0a0a0\",\n        accent: \"#1e293b\",\n        \"accent-foreground\": \"#79c0ff\",\n        destructive: \"#f87171\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#33353a\",\n        input: \"#33353a\",\n        ring: \"#8c5cff\",\n        \"chart-1\": \"#4ade80\",\n        \"chart-2\": \"#8c5cff\",\n        \"chart-3\": \"#fca5a5\",\n        \"chart-4\": \"#5993f4\",\n        \"chart-5\": \"#a0a0a0\",\n        sidebar: \"#161618\",\n        \"sidebar-foreground\": \"#f0f0f0\",\n        \"sidebar-primary\": \"#8c5cff\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#2a2c33\",\n        \"sidebar-accent-foreground\": \"#8c5cff\",\n        \"sidebar-border\": \"#33353a\",\n        \"sidebar-ring\": \"#8c5cff\",\n        \"font-sans\": \"Plus Jakarta Sans, sans-serif\",\n        \"font-serif\": \"Lora, serif\",\n        \"font-mono\": \"IBM Plex Mono, monospace\",\n        radius: \"1.4rem\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.16\",\n        \"shadow-blur\": \"3px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"2px\",\n        \"letter-spacing\": \"-0.025em\",\n        spacing: \"0.27rem\",\n      },\n    },\n  },\n\n  \"t3-chat\": {\n    label: \"T3 Chat\",\n    createdAt: \"2025-04-19\",\n    styles: {\n      light: {\n        background: \"#faf5fa\",\n        foreground: \"#501854\",\n        card: \"#faf5fa\",\n        \"card-foreground\": \"#501854\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#501854\",\n        primary: \"#a84370\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#f1c4e6\",\n        \"secondary-foreground\": \"#77347c\",\n        muted: \"#f6e5f3\",\n        \"muted-foreground\": \"#834588\",\n        accent: \"#f1c4e6\",\n        \"accent-foreground\": \"#77347c\",\n        destructive: \"#ab4347\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#efbdeb\",\n        input: \"#e7c1dc\",\n        ring: \"#db2777\",\n        \"chart-1\": \"#d926a2\",\n        \"chart-2\": \"#6c12b9\",\n        \"chart-3\": \"#274754\",\n        \"chart-4\": \"#e8c468\",\n        \"chart-5\": \"#f4a462\",\n        sidebar: \"#f3e4f6\",\n        \"sidebar-foreground\": \"#ac1668\",\n        \"sidebar-primary\": \"#454554\",\n        \"sidebar-primary-foreground\": \"#faf1f7\",\n        \"sidebar-accent\": \"#f8f8f7\",\n        \"sidebar-accent-foreground\": \"#454554\",\n        \"sidebar-border\": \"#eceae9\",\n        \"sidebar-ring\": \"#db2777\",\n        radius: \"0.5rem\",\n      },\n      dark: {\n        background: \"#221d27\",\n        foreground: \"#d2c4de\",\n        card: \"#2c2632\",\n        \"card-foreground\": \"#dbc5d2\",\n        popover: \"#100a0e\",\n        \"popover-foreground\": \"#f8f1f5\",\n        primary: \"#a3004c\",\n        \"primary-foreground\": \"#efc0d8\",\n        secondary: \"#362d3d\",\n        \"secondary-foreground\": \"#d4c7e1\",\n        muted: \"#28222d\",\n        \"muted-foreground\": \"#c2b6cf\",\n        accent: \"#463753\",\n        \"accent-foreground\": \"#f8f1f5\",\n        destructive: \"#301015\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#3b3237\",\n        input: \"#3e343c\",\n        ring: \"#db2777\",\n        \"chart-1\": \"#a84370\",\n        \"chart-2\": \"#934dcb\",\n        \"chart-3\": \"#e88c30\",\n        \"chart-4\": \"#af57db\",\n        \"chart-5\": \"#e23670\",\n        sidebar: \"#181117\",\n        \"sidebar-foreground\": \"#e0cad6\",\n        \"sidebar-primary\": \"#1d4ed8\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#261922\",\n        \"sidebar-accent-foreground\": \"#f4f4f5\",\n        \"sidebar-border\": \"#000000\",\n        \"sidebar-ring\": \"#db2777\",\n      },\n    },\n  },\n\n  twitter: {\n    label: \"Twitter\",\n    createdAt: \"2025-04-24\",\n    styles: {\n      light: {\n        background: \"#ffffff\",\n        foreground: \"#0f1419\",\n        card: \"#f7f8f8\",\n        \"card-foreground\": \"#0f1419\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#0f1419\",\n        primary: \"#1e9df1\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#0f1419\",\n        \"secondary-foreground\": \"#ffffff\",\n        muted: \"#E5E5E6\",\n        \"muted-foreground\": \"#0f1419\",\n        accent: \"#E3ECF6\",\n        \"accent-foreground\": \"#1e9df1\",\n        destructive: \"#f4212e\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#e1eaef\",\n        input: \"#f7f9fa\",\n        ring: \"#1da1f2\",\n        \"chart-1\": \"#1e9df1\",\n        \"chart-2\": \"#00b87a\",\n        \"chart-3\": \"#f7b928\",\n        \"chart-4\": \"#17bf63\",\n        \"chart-5\": \"#e0245e\",\n        sidebar: \"#f7f8f8\",\n        \"sidebar-foreground\": \"#0f1419\",\n        \"sidebar-primary\": \"#1e9df1\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#E3ECF6\",\n        \"sidebar-accent-foreground\": \"#1e9df1\",\n        \"sidebar-border\": \"#e1e8ed\",\n        \"sidebar-ring\": \"#1da1f2\",\n        \"font-sans\": \"Open Sans, sans-serif\",\n        \"font-serif\": \"Georgia, serif\",\n        \"font-mono\": \"Menlo, monospace\",\n        radius: \"1.3rem\",\n        \"shadow-color\": \"rgba(29,161,242,0.15)\",\n        \"shadow-opacity\": \"0\",\n        \"shadow-blur\": \"0px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"2px\",\n      },\n      dark: {\n        background: \"#000000\",\n        foreground: \"#e7e9ea\",\n        card: \"#17181c\",\n        \"card-foreground\": \"#d9d9d9\",\n        popover: \"#000000\",\n        \"popover-foreground\": \"#e7e9ea\",\n        primary: \"#1c9cf0\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#f0f3f4\",\n        \"secondary-foreground\": \"#0f1419\",\n        muted: \"#181818\",\n        \"muted-foreground\": \"#72767a\",\n        accent: \"#061622\",\n        \"accent-foreground\": \"#1c9cf0\",\n        destructive: \"#f4212e\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#242628\",\n        input: \"#22303c\",\n        ring: \"#1da1f2\",\n        \"chart-1\": \"#1e9df1\",\n        \"chart-2\": \"#00b87a\",\n        \"chart-3\": \"#f7b928\",\n        \"chart-4\": \"#17bf63\",\n        \"chart-5\": \"#e0245e\",\n        sidebar: \"#17181c\",\n        \"sidebar-foreground\": \"#d9d9d9\",\n        \"sidebar-primary\": \"#1da1f2\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#061622\",\n        \"sidebar-accent-foreground\": \"#1c9cf0\",\n        \"sidebar-border\": \"#38444d\",\n        \"sidebar-ring\": \"#1da1f2\",\n        \"shadow-color\": \"rgba(29,161,242,0.25)\",\n      },\n    },\n  },\n\n  \"mocha-mousse\": {\n    label: \"Mocha Mousse\",\n    createdAt: \"2025-04-24\",\n    styles: {\n      light: {\n        background: \"#F1F0E5\",\n        foreground: \"#56453F\",\n        card: \"#F1F0E5\",\n        \"card-foreground\": \"#56453F\",\n        popover: \"#FFFFFF\",\n        \"popover-foreground\": \"#56453F\",\n        primary: \"#A37764\",\n        \"primary-foreground\": \"#FFFFFF\",\n        secondary: \"#BAAB92\",\n        \"secondary-foreground\": \"#ffffff\",\n        muted: \"#E4C7B8\",\n        \"muted-foreground\": \"#8A655A\",\n        accent: \"#E4C7B8\",\n        \"accent-foreground\": \"#56453F\",\n        destructive: \"#1f1a17\",\n        \"destructive-foreground\": \"#FFFFFF\",\n        border: \"#BAAB92\",\n        input: \"#BAAB92\",\n        ring: \"#A37764\",\n        \"chart-1\": \"#A37764\",\n        \"chart-2\": \"#8A655A\",\n        \"chart-3\": \"#C39E88\",\n        \"chart-4\": \"#BAAB92\",\n        \"chart-5\": \"#A28777\",\n        sidebar: \"#ebd6cb\",\n        \"sidebar-foreground\": \"#56453F\",\n        \"sidebar-primary\": \"#A37764\",\n        \"sidebar-primary-foreground\": \"#FFFFFF\",\n        \"sidebar-accent\": \"#C39E88\",\n        \"sidebar-accent-foreground\": \"#ffffff\",\n        \"sidebar-border\": \"#A28777\",\n        \"sidebar-ring\": \"#A37764\",\n        \"font-sans\": \"DM Sans, sans-serif\",\n        \"font-serif\": \"Georgia, serif\",\n        \"font-mono\": \"Menlo, monospace\",\n        radius: \"0.5rem\",\n        \"shadow-color\": \"hsl(20 18% 51% / 0.4)\",\n        \"shadow-opacity\": \"0.11\",\n        \"shadow-blur\": \"0px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"2px\",\n        \"shadow-offset-y\": \"2px\",\n      },\n      dark: {\n        background: \"#2d2521\",\n        foreground: \"#F1F0E5\",\n        card: \"#3c332e\",\n        \"card-foreground\": \"#F1F0E5\",\n        popover: \"#3c332e\",\n        \"popover-foreground\": \"#F1F0E5\",\n        primary: \"#C39E88\",\n        \"primary-foreground\": \"#2d2521\",\n        secondary: \"#8A655A\",\n        \"secondary-foreground\": \"#F1F0E5\",\n        muted: \"#56453F\",\n        \"muted-foreground\": \"#c5aa9b\",\n        accent: \"#BAAB92\",\n        \"accent-foreground\": \"#2d2521\",\n        destructive: \"#E57373\",\n        \"destructive-foreground\": \"#2d2521\",\n        border: \"#56453F\",\n        input: \"#56453F\",\n        ring: \"#C39E88\",\n        \"chart-1\": \"#C39E88\",\n        \"chart-2\": \"#BAAB92\",\n        \"chart-3\": \"#A37764\",\n        \"chart-4\": \"#8A655A\",\n        \"chart-5\": \"#A28777\",\n        sidebar: \"#1f1a17\",\n        \"sidebar-foreground\": \"#F1F0E5\",\n        \"sidebar-primary\": \"#C39E88\",\n        \"sidebar-primary-foreground\": \"#1f1a17\",\n        \"sidebar-accent\": \"#BAAB92\",\n        \"sidebar-accent-foreground\": \"#1f1a17\",\n        \"sidebar-border\": \"#56453F\",\n        \"sidebar-ring\": \"#C39E88\",\n        \"shadow-color\": \"hsl(20 18% 30% / 0.5)\",\n      },\n    },\n  },\n\n  bubblegum: {\n    label: \"Bubblegum\",\n    createdAt: \"2025-04-18\",\n    styles: {\n      light: {\n        background: \"#f6e6ee\",\n        foreground: \"#5b5b5b\",\n        card: \"#fdedc9\",\n        \"card-foreground\": \"#5b5b5b\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#5b5b5b\",\n        primary: \"#d04f99\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#8acfd1\",\n        \"secondary-foreground\": \"#333333\",\n        muted: \"#b2e1eb\",\n        \"muted-foreground\": \"#7a7a7a\",\n        accent: \"#fbe2a7\",\n        \"accent-foreground\": \"#333333\",\n        destructive: \"#f96f70\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#d04f99\",\n        input: \"#e4e4e4\",\n        ring: \"#e670ab\",\n        \"chart-1\": \"#e670ab\",\n        \"chart-2\": \"#84d2e2\",\n        \"chart-3\": \"#fbe2a7\",\n        \"chart-4\": \"#f3a0ca\",\n        \"chart-5\": \"#d7488e\",\n        sidebar: \"#f8d8ea\",\n        \"sidebar-foreground\": \"#333333\",\n        \"sidebar-primary\": \"#ec4899\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#f9a8d4\",\n        \"sidebar-accent-foreground\": \"#333333\",\n        \"sidebar-border\": \"#f3e8ff\",\n        \"sidebar-ring\": \"#ec4899\",\n        \"font-sans\": \"Poppins, sans-serif\",\n        \"font-serif\": \"Lora, serif\",\n        \"font-mono\": \"Fira Code, monospace\",\n        radius: \"0.4rem\",\n        \"shadow-color\": \"hsl(325.78 58.18% 56.86% / 0.5)\",\n        \"shadow-opacity\": \"1.0\",\n        \"shadow-blur\": \"0px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"3px\",\n        \"shadow-offset-y\": \"3px\",\n      },\n      dark: {\n        background: \"#12242e\",\n        foreground: \"#f3e3ea\",\n        card: \"#1c2e38\",\n        \"card-foreground\": \"#f3e3ea\",\n        popover: \"#1c2e38\",\n        \"popover-foreground\": \"#f3e3ea\",\n        primary: \"#fbe2a7\",\n        \"primary-foreground\": \"#12242e\",\n        secondary: \"#e4a2b1\",\n        \"secondary-foreground\": \"#12242e\",\n        muted: \"#24272b\",\n        \"muted-foreground\": \"#e4a2b1\",\n        accent: \"#c67b96\",\n        \"accent-foreground\": \"#f3e3ea\",\n        destructive: \"#e35ea4\",\n        \"destructive-foreground\": \"#12242e\",\n        border: \"#324859\",\n        input: \"#20333d\",\n        ring: \"#50afb6\",\n        \"chart-1\": \"#50afb6\",\n        \"chart-2\": \"#e4a2b1\",\n        \"chart-3\": \"#c67b96\",\n        \"chart-4\": \"#175c6c\",\n        \"chart-5\": \"#24272b\",\n        sidebar: \"#101f28\",\n        \"sidebar-foreground\": \"#f3f4f6\",\n        \"sidebar-primary\": \"#ec4899\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#f9a8d4\",\n        \"sidebar-accent-foreground\": \"#1f2937\",\n        \"sidebar-border\": \"#374151\",\n        \"sidebar-ring\": \"#ec4899\",\n        \"font-sans\": \"Poppins, sans-serif\",\n        \"font-serif\": \"Lora, serif\",\n        \"font-mono\": \"Fira Code, monospace\",\n        \"shadow-color\": \"#324859\",\n      },\n    },\n  },\n\n  \"amethyst-haze\": {\n    label: \"Amethyst Haze\",\n    createdAt: \"2025-05-08\",\n    styles: {\n      light: {\n        background: \"#f8f7fa\",\n        foreground: \"#3d3c4f\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#3d3c4f\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#3d3c4f\",\n        primary: \"#8a79ab\",\n        \"primary-foreground\": \"#f8f7fa\",\n        secondary: \"#dfd9ec\",\n        \"secondary-foreground\": \"#3d3c4f\",\n        muted: \"#dcd9e3\",\n        \"muted-foreground\": \"#6b6880\",\n        accent: \"#e6a5b8\",\n        \"accent-foreground\": \"#4b2e36\",\n        destructive: \"#d95c5c\",\n        \"destructive-foreground\": \"#f8f7fa\",\n        border: \"#cec9d9\",\n        input: \"#eae7f0\",\n        ring: \"#8a79ab\",\n        \"chart-1\": \"#8a79ab\",\n        \"chart-2\": \"#e6a5b8\",\n        \"chart-3\": \"#77b8a1\",\n        \"chart-4\": \"#f0c88d\",\n        \"chart-5\": \"#a0bbe3\",\n        sidebar: \"#f1eff5\",\n        \"sidebar-foreground\": \"#3d3c4f\",\n        \"sidebar-primary\": \"#8a79ab\",\n        \"sidebar-primary-foreground\": \"#f8f7fa\",\n        \"sidebar-accent\": \"#e6a5b8\",\n        \"sidebar-accent-foreground\": \"#4b2e36\",\n        \"sidebar-border\": \"#d7d2e0\",\n        \"sidebar-ring\": \"#8a79ab\",\n        \"font-sans\": \"Geist, sans-serif\",\n        \"font-serif\": '\"Lora\", Georgia, serif',\n        \"font-mono\": '\"Fira Code\", \"Courier New\", monospace',\n        radius: \"0.5rem\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.06\",\n        \"shadow-blur\": \"5px\",\n        \"shadow-spread\": \"1px\",\n        \"shadow-offset-x\": \"1px\",\n        \"shadow-offset-y\": \"2px\",\n        \"letter-spacing\": \"0em\",\n        spacing: \"0.25rem\",\n      },\n      dark: {\n        background: \"#1a1823\",\n        foreground: \"#e0ddef\",\n        card: \"#232030\",\n        \"card-foreground\": \"#e0ddef\",\n        popover: \"#232030\",\n        \"popover-foreground\": \"#e0ddef\",\n        primary: \"#a995c9\",\n        \"primary-foreground\": \"#1a1823\",\n        secondary: \"#5a5370\",\n        \"secondary-foreground\": \"#e0ddef\",\n        muted: \"#242031\",\n        \"muted-foreground\": \"#a09aad\",\n        accent: \"#372e3f\",\n        \"accent-foreground\": \"#f2b8c6\",\n        destructive: \"#e57373\",\n        \"destructive-foreground\": \"#1a1823\",\n        border: \"#302c40\",\n        input: \"#2a273a\",\n        ring: \"#a995c9\",\n        \"chart-1\": \"#a995c9\",\n        \"chart-2\": \"#f2b8c6\",\n        \"chart-3\": \"#77b8a1\",\n        \"chart-4\": \"#f0c88d\",\n        \"chart-5\": \"#a0bbe3\",\n        sidebar: \"#16141e\",\n        \"sidebar-foreground\": \"#e0ddef\",\n        \"sidebar-primary\": \"#a995c9\",\n        \"sidebar-primary-foreground\": \"#1a1823\",\n        \"sidebar-accent\": \"#372e3f\",\n        \"sidebar-accent-foreground\": \"#f2b8c6\",\n        \"sidebar-border\": \"#2a273a\",\n        \"sidebar-ring\": \"#a995c9\",\n      },\n    },\n  },\n\n  notebook: {\n    label: \"Notebook\",\n    createdAt: \"2025-05-10\",\n    styles: {\n      light: {\n        background: \"#f9f9f9\",\n        foreground: \"#3a3a3a\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#3a3a3a\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#3a3a3a\",\n        primary: \"#606060\",\n        \"primary-foreground\": \"#f0f0f0\",\n        secondary: \"#dedede\",\n        \"secondary-foreground\": \"#3a3a3a\",\n        muted: \"#e3e3e3\",\n        \"muted-foreground\": \"#505050\",\n        accent: \"#f3eac8\",\n        \"accent-foreground\": \"#5d4037\",\n        destructive: \"#c87a7a\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#747272\",\n        input: \"#ffffff\",\n        ring: \"#a0a0a0\",\n        \"chart-1\": \"#333333\",\n        \"chart-2\": \"#555555\",\n        \"chart-3\": \"#777777\",\n        \"chart-4\": \"#999999\",\n        \"chart-5\": \"#bbbbbb\",\n        sidebar: \"#f0f0f0\",\n        \"sidebar-foreground\": \"#3a3a3a\",\n        \"sidebar-primary\": \"#606060\",\n        \"sidebar-primary-foreground\": \"#f0f0f0\",\n        \"sidebar-accent\": \"#f3eac8\",\n        \"sidebar-accent-foreground\": \"#5d4037\",\n        \"sidebar-border\": \"#c0c0c0\",\n        \"sidebar-ring\": \"#a0a0a0\",\n        \"font-sans\": \"Architects Daughter, sans-serif\",\n        \"font-serif\": '\"Times New Roman\", Times, serif',\n        \"font-mono\": '\"Courier New\", Courier, monospace',\n        radius: \"0.625rem\",\n        \"shadow-color\": \"#000000\",\n        \"shadow-opacity\": \"0.03\",\n        \"shadow-blur\": \"5px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"1px\",\n        \"shadow-offset-y\": \"4px\",\n        \"letter-spacing\": \"0.5px\",\n        spacing: \"0.25rem\",\n      },\n      dark: {\n        background: \"#2b2b2b\",\n        foreground: \"#dcdcdc\",\n        card: \"#333333\",\n        \"card-foreground\": \"#dcdcdc\",\n        popover: \"#333333\",\n        \"popover-foreground\": \"#dcdcdc\",\n        primary: \"#b0b0b0\",\n        \"primary-foreground\": \"#2b2b2b\",\n        secondary: \"#5a5a5a\",\n        \"secondary-foreground\": \"#c0c0c0\",\n        muted: \"#454545\",\n        \"muted-foreground\": \"#a0a0a0\",\n        accent: \"#e0e0e0\",\n        \"accent-foreground\": \"#333333\",\n        destructive: \"#d9afaf\",\n        \"destructive-foreground\": \"#2b2b2b\",\n        border: \"#4f4f4f\",\n        input: \"#333333\",\n        ring: \"#c0c0c0\",\n        \"chart-1\": \"#efefef\",\n        \"chart-2\": \"#d0d0d0\",\n        \"chart-3\": \"#b0b0b0\",\n        \"chart-4\": \"#909090\",\n        \"chart-5\": \"#707070\",\n        sidebar: \"#212121\",\n        \"sidebar-foreground\": \"#dcdcdc\",\n        \"sidebar-primary\": \"#b0b0b0\",\n        \"sidebar-primary-foreground\": \"#212121\",\n        \"sidebar-accent\": \"#e0e0e0\",\n        \"sidebar-accent-foreground\": \"#333333\",\n        \"sidebar-border\": \"#4f4f4f\",\n        \"sidebar-ring\": \"#c0c0c0\",\n        \"font-sans\": \"Architects Daughter, sans-serif\",\n        \"font-serif\": \"Georgia, serif\",\n        \"font-mono\": '\"Fira Code\", \"Courier New\", monospace',\n        radius: \"0.625rem\",\n        \"shadow-color\": \"#000000\",\n        \"shadow-opacity\": \"0.03\",\n        \"shadow-blur\": \"5px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"1px\",\n        \"shadow-offset-y\": \"4px\",\n        \"letter-spacing\": \"0.5px\",\n        spacing: \"0.25rem\",\n      },\n    },\n  },\n\n  \"doom-64\": {\n    label: \"Doom 64\",\n    createdAt: \"2025-04-28\",\n    styles: {\n      light: {\n        background: \"#cccccc\",\n        foreground: \"#1f1f1f\",\n        card: \"#b0b0b0\",\n        \"card-foreground\": \"#1f1f1f\",\n        popover: \"#b0b0b0\",\n        \"popover-foreground\": \"#1f1f1f\",\n        primary: \"#b71c1c\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#556b2f\",\n        \"secondary-foreground\": \"#ffffff\",\n        muted: \"#b8b8b8\",\n        \"muted-foreground\": \"#4a4a4a\",\n        accent: \"#4682b4\",\n        \"accent-foreground\": \"#ffffff\",\n        destructive: \"#ff6f00\",\n        \"destructive-foreground\": \"#000000\",\n        border: \"#505050\",\n        input: \"#505050\",\n        ring: \"#b71c1c\",\n        \"chart-1\": \"#b71c1c\",\n        \"chart-2\": \"#556b2f\",\n        \"chart-3\": \"#4682b4\",\n        \"chart-4\": \"#ff6f00\",\n        \"chart-5\": \"#8d6e63\",\n        sidebar: \"#b0b0b0\",\n        \"sidebar-foreground\": \"#1f1f1f\",\n        \"sidebar-primary\": \"#b71c1c\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#4682b4\",\n        \"sidebar-accent-foreground\": \"#ffffff\",\n        \"sidebar-border\": \"#505050\",\n        \"sidebar-ring\": \"#b71c1c\",\n        \"font-sans\": '\"Oxanium\", sans-serif',\n        \"font-serif\": 'ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif',\n        \"font-mono\": '\"Source Code Pro\", monospace',\n        radius: \"0px\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.4\",\n        \"shadow-blur\": \"4px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"2px\",\n        \"letter-spacing\": \"0em\",\n        spacing: \"0.25rem\",\n      },\n      dark: {\n        background: \"#1a1a1a\",\n        foreground: \"#e0e0e0\",\n        card: \"#2a2a2a\",\n        \"card-foreground\": \"#e0e0e0\",\n        popover: \"#2a2a2a\",\n        \"popover-foreground\": \"#e0e0e0\",\n        primary: \"#e53935\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#689f38\",\n        \"secondary-foreground\": \"#000000\",\n        muted: \"#252525\",\n        \"muted-foreground\": \"#a0a0a0\",\n        accent: \"#64b5f6\",\n        \"accent-foreground\": \"#000000\",\n        destructive: \"#ffa000\",\n        \"destructive-foreground\": \"#000000\",\n        border: \"#4a4a4a\",\n        input: \"#4a4a4a\",\n        ring: \"#e53935\",\n        \"chart-1\": \"#e53935\",\n        \"chart-2\": \"#689f38\",\n        \"chart-3\": \"#64b5f6\",\n        \"chart-4\": \"#ffa000\",\n        \"chart-5\": \"#a1887f\",\n        sidebar: \"#141414\",\n        \"sidebar-foreground\": \"#e0e0e0\",\n        \"sidebar-primary\": \"#e53935\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#64b5f6\",\n        \"sidebar-accent-foreground\": \"#000000\",\n        \"sidebar-border\": \"#4a4a4a\",\n        \"sidebar-ring\": \"#e53935\",\n        \"font-sans\": '\"Oxanium\", sans-serif',\n        \"font-serif\": 'ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif',\n        \"font-mono\": '\"Source Code Pro\", monospace',\n        radius: \"0px\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.6\",\n        \"shadow-blur\": \"5px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"2px\",\n        \"letter-spacing\": \"0em\",\n        spacing: \"0.25rem\",\n      },\n    },\n  },\n\n  catppuccin: {\n    label: \"Catppuccin\",\n    createdAt: \"2025-04-18\",\n    styles: {\n      light: {\n        background: \"#eff1f5\",\n        foreground: \"#4c4f69\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#4c4f69\",\n        popover: \"#ccd0da\",\n        \"popover-foreground\": \"#4c4f69\",\n        primary: \"#8839ef\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#ccd0da\",\n        \"secondary-foreground\": \"#4c4f69\",\n        muted: \"#dce0e8\",\n        \"muted-foreground\": \"#6c6f85\",\n        accent: \"#04a5e5\",\n        \"accent-foreground\": \"#ffffff\",\n        destructive: \"#d20f39\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#bcc0cc\",\n        input: \"#ccd0da\",\n        ring: \"#8839ef\",\n        \"chart-1\": \"#8839ef\",\n        \"chart-2\": \"#04a5e5\",\n        \"chart-3\": \"#40a02b\",\n        \"chart-4\": \"#fe640b\",\n        \"chart-5\": \"#dc8a78\",\n        sidebar: \"#e6e9ef\",\n        \"sidebar-foreground\": \"#4c4f69\",\n        \"sidebar-primary\": \"#8839ef\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#04a5e5\",\n        \"sidebar-accent-foreground\": \"#ffffff\",\n        \"sidebar-border\": \"#bcc0cc\",\n        \"sidebar-ring\": \"#8839ef\",\n        \"font-sans\": \"Montserrat, sans-serif\",\n        \"font-serif\": \"Georgia, serif\",\n        \"font-mono\": \"Fira Code, monospace\",\n        radius: \"0.35rem\",\n        \"shadow-color\": \"hsl(240 30% 25%)\",\n        \"shadow-opacity\": \"0.12\",\n        \"shadow-blur\": \"6px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"4px\",\n      },\n      dark: {\n        background: \"#181825\",\n        foreground: \"#cdd6f4\",\n        card: \"#1e1e2e\",\n        \"card-foreground\": \"#cdd6f4\",\n        popover: \"#45475a\",\n        \"popover-foreground\": \"#cdd6f4\",\n        primary: \"#cba6f7\",\n        \"primary-foreground\": \"#1e1e2e\",\n        secondary: \"#585b70\",\n        \"secondary-foreground\": \"#cdd6f4\",\n        muted: \"#292c3c\",\n        \"muted-foreground\": \"#a6adc8\",\n        accent: \"#89dceb\",\n        \"accent-foreground\": \"#1e1e2e\",\n        destructive: \"#f38ba8\",\n        \"destructive-foreground\": \"#1e1e2e\",\n        border: \"#313244\",\n        input: \"#313244\",\n        ring: \"#cba6f7\",\n        \"chart-1\": \"#cba6f7\",\n        \"chart-2\": \"#89dceb\",\n        \"chart-3\": \"#a6e3a1\",\n        \"chart-4\": \"#fab387\",\n        \"chart-5\": \"#f5e0dc\",\n        sidebar: \"#11111b\",\n        \"sidebar-foreground\": \"#cdd6f4\",\n        \"sidebar-primary\": \"#cba6f7\",\n        \"sidebar-primary-foreground\": \"#1e1e2e\",\n        \"sidebar-accent\": \"#89dceb\",\n        \"sidebar-accent-foreground\": \"#1e1e2e\",\n        \"sidebar-border\": \"#45475a\",\n        \"sidebar-ring\": \"#cba6f7\",\n      },\n    },\n  },\n\n  graphite: {\n    label: \"Graphite\",\n    createdAt: \"2025-04-17\",\n    styles: {\n      light: {\n        background: \"#f0f0f0\",\n        foreground: \"#333333\",\n        card: \"#f5f5f5\",\n        \"card-foreground\": \"#333333\",\n        popover: \"#f5f5f5\",\n        \"popover-foreground\": \"#333333\",\n        primary: \"#606060\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#e0e0e0\",\n        \"secondary-foreground\": \"#333333\",\n        muted: \"#d9d9d9\",\n        \"muted-foreground\": \"#666666\",\n        accent: \"#c0c0c0\",\n        \"accent-foreground\": \"#333333\",\n        destructive: \"#cc3333\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#d0d0d0\",\n        input: \"#e0e0e0\",\n        ring: \"#606060\",\n        \"chart-1\": \"#606060\",\n        \"chart-2\": \"#476666\",\n        \"chart-3\": \"#909090\",\n        \"chart-4\": \"#a8a8a8\",\n        \"chart-5\": \"#c0c0c0\",\n        sidebar: \"#eaeaea\",\n        \"sidebar-foreground\": \"#333333\",\n        \"sidebar-primary\": \"#606060\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#c0c0c0\",\n        \"sidebar-accent-foreground\": \"#333333\",\n        \"sidebar-border\": \"#d0d0d0\",\n        \"sidebar-ring\": \"#606060\",\n        \"font-sans\": \"Montserrat, sans-serif\",\n        \"font-serif\": \"Georgia, serif\",\n        \"font-mono\": \"Fira Code, monospace\",\n        radius: \"0.35rem\",\n        \"shadow-color\": \"hsl(0 0% 20% / 0.1)\",\n        \"shadow-opacity\": \"0.15\",\n        \"shadow-blur\": \"0px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"2px\",\n      },\n      dark: {\n        background: \"#1a1a1a\",\n        foreground: \"#d9d9d9\",\n        card: \"#202020\",\n        \"card-foreground\": \"#d9d9d9\",\n        popover: \"#202020\",\n        \"popover-foreground\": \"#d9d9d9\",\n        primary: \"#a0a0a0\",\n        \"primary-foreground\": \"#1a1a1a\",\n        secondary: \"#303030\",\n        \"secondary-foreground\": \"#d9d9d9\",\n        muted: \"#2a2a2a\",\n        \"muted-foreground\": \"#808080\",\n        accent: \"#404040\",\n        \"accent-foreground\": \"#d9d9d9\",\n        destructive: \"#e06666\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#353535\",\n        input: \"#303030\",\n        ring: \"#a0a0a0\",\n        \"chart-1\": \"#a0a0a0\",\n        \"chart-2\": \"#7e9ca0\",\n        \"chart-3\": \"#707070\",\n        \"chart-4\": \"#585858\",\n        \"chart-5\": \"#404040\",\n        sidebar: \"#1f1f1f\",\n        \"sidebar-foreground\": \"#d9d9d9\",\n        \"sidebar-primary\": \"#a0a0a0\",\n        \"sidebar-primary-foreground\": \"#1a1a1a\",\n        \"sidebar-accent\": \"#404040\",\n        \"sidebar-accent-foreground\": \"#d9d9d9\",\n        \"sidebar-border\": \"#353535\",\n        \"sidebar-ring\": \"#a0a0a0\",\n        \"font-sans\": \"Inter, sans-serif\",\n        \"font-serif\": \"Georgia, serif\",\n        \"font-mono\": \"Fira Code, monospace\",\n      },\n    },\n  },\n\n  perpetuity: {\n    label: \"Perpetuity\",\n    createdAt: \"2025-04-01\",\n    styles: {\n      light: {\n        background: \"#e8f0f0\",\n        foreground: \"#0a4a55\",\n        card: \"#f2f7f7\",\n        \"card-foreground\": \"#0a4a55\",\n        popover: \"#f2f7f7\",\n        \"popover-foreground\": \"#0a4a55\",\n        primary: \"#06858e\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#d9eaea\",\n        \"secondary-foreground\": \"#0a4a55\",\n        muted: \"#e0eaea\",\n        \"muted-foreground\": \"#427a7e\",\n        accent: \"#c9e5e7\",\n        \"accent-foreground\": \"#0a4a55\",\n        destructive: \"#d13838\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#cde0e2\",\n        input: \"#d9eaea\",\n        ring: \"#06858e\",\n        \"chart-1\": \"#06858e\",\n        \"chart-2\": \"#1e9ea6\",\n        \"chart-3\": \"#37b6be\",\n        \"chart-4\": \"#5dc7ce\",\n        \"chart-5\": \"#8ad8dd\",\n        sidebar: \"#daebed\",\n        \"sidebar-foreground\": \"#0a4a55\",\n        \"sidebar-primary\": \"#06858e\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#c9e5e7\",\n        \"sidebar-accent-foreground\": \"#0a4a55\",\n        \"sidebar-border\": \"#cde0e2\",\n        \"sidebar-ring\": \"#06858e\",\n        \"font-sans\": \"Courier New, monospace\",\n        \"font-serif\": \"Courier New, monospace\",\n        \"font-mono\": \"Courier New, monospace\",\n        radius: \"0.125rem\",\n        \"shadow-color\": \"hsl(185 70% 30% / 0.15)\",\n        \"shadow-opacity\": \"0.15\",\n        \"shadow-blur\": \"2px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"1px\",\n        \"shadow-offset-y\": \"1px\",\n      },\n      dark: {\n        background: \"#0a1a20\",\n        foreground: \"#4de8e8\",\n        card: \"#0c2025\",\n        \"card-foreground\": \"#4de8e8\",\n        popover: \"#0c2025\",\n        \"popover-foreground\": \"#4de8e8\",\n        primary: \"#4de8e8\",\n        \"primary-foreground\": \"#0a1a20\",\n        secondary: \"#164955\",\n        \"secondary-foreground\": \"#4de8e8\",\n        muted: \"#0f3039\",\n        \"muted-foreground\": \"#36a5a5\",\n        accent: \"#164955\",\n        \"accent-foreground\": \"#4de8e8\",\n        destructive: \"#e83c3c\",\n        \"destructive-foreground\": \"#f2f2f2\",\n        border: \"#164955\",\n        input: \"#164955\",\n        ring: \"#4de8e8\",\n        \"chart-1\": \"#4de8e8\",\n        \"chart-2\": \"#36a5a5\",\n        \"chart-3\": \"#2d8a8a\",\n        \"chart-4\": \"#19595e\",\n        \"chart-5\": \"#0e383c\",\n        sidebar: \"#0a1a20\",\n        \"sidebar-foreground\": \"#4de8e8\",\n        \"sidebar-primary\": \"#4de8e8\",\n        \"sidebar-primary-foreground\": \"#0a1a20\",\n        \"sidebar-accent\": \"#164955\",\n        \"sidebar-accent-foreground\": \"#4de8e8\",\n        \"sidebar-border\": \"#164955\",\n        \"sidebar-ring\": \"#4de8e8\",\n        \"font-sans\": \"Source Code Pro, monospace\",\n        \"font-serif\": \"Source Code Pro, monospace\",\n        \"font-mono\": \"Source Code Pro, monospace\",\n        radius: \"0.125rem\",\n        \"shadow-color\": \"hsl(180 70% 60% / 0.2)\",\n        \"shadow-opacity\": \"0.2\",\n        \"shadow-blur\": \"2px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"1px\",\n        \"shadow-offset-y\": \"1px\",\n      },\n    },\n  },\n  \"kodama-grove\": {\n    label: \"Kodama Grove\",\n    styles: {\n      light: {\n        background: \"#e4d7b0\",\n        foreground: \"#5c4b3e\",\n        card: \"#e7dbbf\",\n        \"card-foreground\": \"#5c4b3e\",\n        popover: \"#f3ead2\",\n        \"popover-foreground\": \"#5c4b3e\",\n        primary: \"#8d9d4f\",\n        \"primary-foreground\": \"#fdfbf6\",\n        secondary: \"#decea0\",\n        \"secondary-foreground\": \"#5c4b3e\",\n        muted: \"#decea0\",\n        \"muted-foreground\": \"#85766a\",\n        accent: \"#dbc894\",\n        \"accent-foreground\": \"#5c4b3e\",\n        destructive: \"#d98b7e\",\n        \"destructive-foreground\": \"#faf8f2\",\n        border: \"#b19681\",\n        input: \"#dbc894\",\n        ring: \"#9db18c\",\n        \"chart-1\": \"#9db18c\",\n        \"chart-2\": \"#8a9f7b\",\n        \"chart-3\": \"#bac9b4\",\n        \"chart-4\": \"#71856a\",\n        \"chart-5\": \"#5e6e58\",\n        sidebar: \"#e2d1a2\",\n        \"sidebar-foreground\": \"#5c4b3e\",\n        \"sidebar-primary\": \"#9db18c\",\n        \"sidebar-primary-foreground\": \"#fdfbf6\",\n        \"sidebar-accent\": \"#eae5d9\",\n        \"sidebar-accent-foreground\": \"#5c4b3e\",\n        \"sidebar-border\": \"#e5e0d4\",\n        \"sidebar-ring\": \"#9db18c\",\n        \"font-sans\": \"Merriweather, serif\",\n        \"font-serif\": \"Source Serif 4, serif\",\n        \"font-mono\": \"JetBrains Mono, monospace\",\n        radius: \"0.425rem\",\n        \"shadow-color\": \"hsl(88 22% 35% / 0.15)\",\n        \"shadow-opacity\": \"0.15\",\n        \"shadow-blur\": \"2px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"3px\",\n        \"shadow-offset-y\": \"3px\",\n      },\n      dark: {\n        background: \"#3a3529\",\n        foreground: \"#ede4d4\",\n        card: \"#413c33\",\n        \"card-foreground\": \"#ede4d4\",\n        popover: \"#413c33\",\n        \"popover-foreground\": \"#ede4d4\",\n        primary: \"#8a9f7b\",\n        \"primary-foreground\": \"#2a2521\",\n        secondary: \"#5a5345\",\n        \"secondary-foreground\": \"#ede4d4\",\n        muted: \"#4a4439\",\n        \"muted-foreground\": \"#a8a096\",\n        accent: \"#a18f5c\",\n        \"accent-foreground\": \"#2a2521\",\n        destructive: \"#b5766a\",\n        \"destructive-foreground\": \"#f0e9db\",\n        border: \"#5a5345\",\n        input: \"#5a5345\",\n        ring: \"#8a9f7b\",\n        \"chart-1\": \"#8a9f7b\",\n        \"chart-2\": \"#9db18c\",\n        \"chart-3\": \"#71856a\",\n        \"chart-4\": \"#a18f5c\",\n        \"chart-5\": \"#5e6e58\",\n        sidebar: \"#3a3529\",\n        \"sidebar-foreground\": \"#ede4d4\",\n        \"sidebar-primary\": \"#8a9f7b\",\n        \"sidebar-primary-foreground\": \"#2a2521\",\n        \"sidebar-accent\": \"#a18f5c\",\n        \"sidebar-accent-foreground\": \"#2a2521\",\n        \"sidebar-border\": \"#5a5345\",\n        \"sidebar-ring\": \"#8a9f7b\",\n      },\n    },\n  },\n\n  \"cosmic-night\": {\n    label: \"Cosmic Night\",\n    createdAt: \"2025-04-04\",\n    styles: {\n      light: {\n        background: \"#f5f5ff\",\n        foreground: \"#2a2a4a\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#2a2a4a\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#2a2a4a\",\n        primary: \"#6e56cf\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#e4dfff\",\n        \"secondary-foreground\": \"#4a4080\",\n        muted: \"#f0f0fa\",\n        \"muted-foreground\": \"#6c6c8a\",\n        accent: \"#d8e6ff\",\n        \"accent-foreground\": \"#2a2a4a\",\n        destructive: \"#ff5470\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#e0e0f0\",\n        input: \"#e0e0f0\",\n        ring: \"#6e56cf\",\n        \"chart-1\": \"#6e56cf\",\n        \"chart-2\": \"#9e8cfc\",\n        \"chart-3\": \"#5d5fef\",\n        \"chart-4\": \"#7c75fa\",\n        \"chart-5\": \"#4740b3\",\n        radius: \"0.5rem\",\n        sidebar: \"#f0f0fa\",\n        \"sidebar-foreground\": \"#2a2a4a\",\n        \"sidebar-primary\": \"#6e56cf\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#d8e6ff\",\n        \"sidebar-accent-foreground\": \"#2a2a4a\",\n        \"sidebar-border\": \"#e0e0f0\",\n        \"sidebar-ring\": \"#6e56cf\",\n        \"font-sans\": \"Inter, sans-serif\",\n        \"font-serif\": \"Georgia, serif\",\n        \"font-mono\": \"JetBrains Mono, monospace\",\n        \"shadow-color\": \"hsl(240 30% 25%)\",\n        \"shadow-opacity\": \"0.12\",\n        \"shadow-blur\": \"10px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"4px\",\n      },\n      dark: {\n        background: \"#0f0f1a\",\n        foreground: \"#e2e2f5\",\n        card: \"#1a1a2e\",\n        \"card-foreground\": \"#e2e2f5\",\n        popover: \"#1a1a2e\",\n        \"popover-foreground\": \"#e2e2f5\",\n        primary: \"#a48fff\",\n        \"primary-foreground\": \"#0f0f1a\",\n        secondary: \"#2d2b55\",\n        \"secondary-foreground\": \"#c4c2ff\",\n        muted: \"#222244\",\n        \"muted-foreground\": \"#a0a0c0\",\n        accent: \"#303060\",\n        \"accent-foreground\": \"#e2e2f5\",\n        destructive: \"#ff5470\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#303052\",\n        input: \"#303052\",\n        ring: \"#a48fff\",\n        \"chart-1\": \"#a48fff\",\n        \"chart-2\": \"#7986cb\",\n        \"chart-3\": \"#64b5f6\",\n        \"chart-4\": \"#4db6ac\",\n        \"chart-5\": \"#ff79c6\",\n        radius: \"0.5rem\",\n        sidebar: \"#1a1a2e\",\n        \"sidebar-foreground\": \"#e2e2f5\",\n        \"sidebar-primary\": \"#a48fff\",\n        \"sidebar-primary-foreground\": \"#0f0f1a\",\n        \"sidebar-accent\": \"#303060\",\n        \"sidebar-accent-foreground\": \"#e2e2f5\",\n        \"sidebar-border\": \"#303052\",\n        \"sidebar-ring\": \"#a48fff\",\n      },\n    },\n  },\n\n  tangerine: {\n    label: \"Tangerine\",\n    createdAt: \"2025-04-09\",\n    styles: {\n      light: {\n        background: \"#e8ebed\",\n        foreground: \"#333333\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#333333\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#333333\",\n        primary: \"#e05d38\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#f3f4f6\",\n        \"secondary-foreground\": \"#4b5563\",\n        muted: \"#f9fafb\",\n        \"muted-foreground\": \"#6b7280\",\n        accent: \"#d6e4f0\",\n        \"accent-foreground\": \"#1e3a8a\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#dcdfe2\",\n        input: \"#f4f5f7\",\n        ring: \"#e05d38\",\n        \"chart-1\": \"#86a7c8\",\n        \"chart-2\": \"#eea591\",\n        \"chart-3\": \"#5a7ca6\",\n        \"chart-4\": \"#466494\",\n        \"chart-5\": \"#334c82\",\n        sidebar: \"#dddfe2\",\n        \"sidebar-foreground\": \"#333333\",\n        \"sidebar-primary\": \"#e05d38\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#d6e4f0\",\n        \"sidebar-accent-foreground\": \"#1e3a8a\",\n        \"sidebar-border\": \"#e5e7eb\",\n        \"sidebar-ring\": \"#e05d38\",\n        \"font-sans\": \"Inter, sans-serif\",\n        \"font-serif\": \"Source Serif 4, serif\",\n        \"font-mono\": \"JetBrains Mono, monospace\",\n        radius: \"0.75rem\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.1\",\n        \"shadow-blur\": \"3px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"1px\",\n      },\n      dark: {\n        background: \"#1c2433\",\n        foreground: \"#e5e5e5\",\n        card: \"#2a3040\",\n        \"card-foreground\": \"#e5e5e5\",\n        popover: \"#262b38\",\n        \"popover-foreground\": \"#e5e5e5\",\n        primary: \"#e05d38\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#2a303e\",\n        \"secondary-foreground\": \"#e5e5e5\",\n        muted: \"#2a303e\",\n        \"muted-foreground\": \"#a3a3a3\",\n        accent: \"#2a3656\",\n        \"accent-foreground\": \"#bfdbfe\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#3d4354\",\n        input: \"#3d4354\",\n        ring: \"#e05d38\",\n        \"chart-1\": \"#86a7c8\",\n        \"chart-2\": \"#e6a08f\",\n        \"chart-3\": \"#5a7ca6\",\n        \"chart-4\": \"#466494\",\n        \"chart-5\": \"#334c82\",\n        sidebar: \"#2a303f\",\n        \"sidebar-foreground\": \"#e5e5e5\",\n        \"sidebar-primary\": \"#e05d38\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#2a3656\",\n        \"sidebar-accent-foreground\": \"#bfdbfe\",\n        \"sidebar-border\": \"#3d4354\",\n        \"sidebar-ring\": \"#e05d38\",\n      },\n    },\n  },\n\n  \"quantum-rose\": {\n    label: \"Quantum Rose\",\n    createdAt: \"2025-04-03\",\n    styles: {\n      light: {\n        background: \"#fff0f8\",\n        foreground: \"#91185c\",\n        card: \"#fff7fc\",\n        \"card-foreground\": \"#91185c\",\n        popover: \"#fff7fc\",\n        \"popover-foreground\": \"#91185c\",\n        primary: \"#e6067a\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#ffd6ff\",\n        \"secondary-foreground\": \"#91185c\",\n        muted: \"#ffe3f2\",\n        \"muted-foreground\": \"#c04283\",\n        accent: \"#ffc1e3\",\n        \"accent-foreground\": \"#91185c\",\n        destructive: \"#d13869\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#ffc7e6\",\n        input: \"#ffd6ff\",\n        ring: \"#e6067a\",\n        \"chart-1\": \"#e6067a\",\n        \"chart-2\": \"#c44b97\",\n        \"chart-3\": \"#9969b6\",\n        \"chart-4\": \"#7371bf\",\n        \"chart-5\": \"#5e84ff\",\n        sidebar: \"#ffedf6\",\n        \"sidebar-foreground\": \"#91185c\",\n        \"sidebar-primary\": \"#e6067a\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#ffc1e3\",\n        \"sidebar-accent-foreground\": \"#91185c\",\n        \"sidebar-border\": \"#ffddf0\",\n        \"sidebar-ring\": \"#e6067a\",\n        \"font-sans\": \"Poppins, sans-serif\",\n        \"font-serif\": \"Playfair Display, serif\",\n        \"font-mono\": \"Space Mono, monospace\",\n        radius: \"0.5rem\",\n        \"shadow-color\": \"hsl(330 70% 30% / 0.12)\",\n        \"shadow-opacity\": \"0.18\",\n        \"shadow-blur\": \"0px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"3px\",\n      },\n      dark: {\n        background: \"#1a0922\",\n        foreground: \"#ffb3ff\",\n        card: \"#2a1435\",\n        \"card-foreground\": \"#ffb3ff\",\n        popover: \"#2a1435\",\n        \"popover-foreground\": \"#ffb3ff\",\n        primary: \"#ff6bef\",\n        \"primary-foreground\": \"#180518\",\n        secondary: \"#46204f\",\n        \"secondary-foreground\": \"#ffb3ff\",\n        muted: \"#331941\",\n        \"muted-foreground\": \"#d67ad6\",\n        accent: \"#5a1f5d\",\n        \"accent-foreground\": \"#ffb3ff\",\n        destructive: \"#ff2876\",\n        \"destructive-foreground\": \"#f9f9f9\",\n        border: \"#4a1b5f\",\n        input: \"#46204f\",\n        ring: \"#ff6bef\",\n        \"chart-1\": \"#ff6bef\",\n        \"chart-2\": \"#c359e3\",\n        \"chart-3\": \"#9161ff\",\n        \"chart-4\": \"#6f73e2\",\n        \"chart-5\": \"#547aff\",\n        sidebar: \"#1c0d25\",\n        \"sidebar-foreground\": \"#ffb3ff\",\n        \"sidebar-primary\": \"#ff6bef\",\n        \"sidebar-primary-foreground\": \"#180518\",\n        \"sidebar-accent\": \"#5a1f5d\",\n        \"sidebar-accent-foreground\": \"#ffb3ff\",\n        \"sidebar-border\": \"#4a1b5f\",\n        \"sidebar-ring\": \"#ff6bef\",\n        \"font-sans\": \"Quicksand, sans-serif\",\n        \"font-serif\": \"Playfair Display, serif\",\n        \"font-mono\": \"Space Mono, monospace\",\n        \"shadow-color\": \"hsl(300 80% 50% / 0.25)\",\n      },\n    },\n  },\n\n  nature: {\n    label: \"Nature\",\n    styles: {\n      light: {\n        background: \"#f8f5f0\",\n        foreground: \"#3e2723\",\n        card: \"#f8f5f0\",\n        \"card-foreground\": \"#3e2723\",\n        popover: \"#f8f5f0\",\n        \"popover-foreground\": \"#3e2723\",\n        primary: \"#2e7d32\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#e8f5e9\",\n        \"secondary-foreground\": \"#1b5e20\",\n        muted: \"#f0e9e0\",\n        \"muted-foreground\": \"#6d4c41\",\n        accent: \"#c8e6c9\",\n        \"accent-foreground\": \"#1b5e20\",\n        destructive: \"#c62828\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#e0d6c9\",\n        input: \"#e0d6c9\",\n        ring: \"#2e7d32\",\n        \"chart-1\": \"#4caf50\",\n        \"chart-2\": \"#388e3c\",\n        \"chart-3\": \"#2e7d32\",\n        \"chart-4\": \"#1b5e20\",\n        \"chart-5\": \"#0a1f0c\",\n        radius: \"0.5rem\",\n        sidebar: \"#f0e9e0\",\n        \"sidebar-foreground\": \"#3e2723\",\n        \"sidebar-primary\": \"#2e7d32\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#c8e6c9\",\n        \"sidebar-accent-foreground\": \"#1b5e20\",\n        \"sidebar-border\": \"#e0d6c9\",\n        \"sidebar-ring\": \"#2e7d32\",\n        \"font-sans\": \"Montserrat, sans-serif\",\n        \"font-serif\": \"Merriweather, serif\",\n        \"font-mono\": \"Source Code Pro, monospace\",\n      },\n      dark: {\n        background: \"#1c2a1f\",\n        foreground: \"#f0ebe5\",\n        card: \"#2d3a2e\",\n        \"card-foreground\": \"#f0ebe5\",\n        popover: \"#2d3a2e\",\n        \"popover-foreground\": \"#f0ebe5\",\n        primary: \"#4caf50\",\n        \"primary-foreground\": \"#0a1f0c\",\n        secondary: \"#3e4a3d\",\n        \"secondary-foreground\": \"#d7e0d6\",\n        muted: \"#252f26\",\n        \"muted-foreground\": \"#d7cfc4\",\n        accent: \"#388e3c\",\n        \"accent-foreground\": \"#f0ebe5\",\n        destructive: \"#c62828\",\n        \"destructive-foreground\": \"#f0ebe5\",\n        border: \"#3e4a3d\",\n        input: \"#3e4a3d\",\n        ring: \"#4caf50\",\n        \"chart-1\": \"#81c784\",\n        \"chart-2\": \"#66bb6a\",\n        \"chart-3\": \"#4caf50\",\n        \"chart-4\": \"#43a047\",\n        \"chart-5\": \"#388e3c\",\n        radius: \"0.5rem\",\n        sidebar: \"#1c2a1f\",\n        \"sidebar-foreground\": \"#f0ebe5\",\n        \"sidebar-primary\": \"#4caf50\",\n        \"sidebar-primary-foreground\": \"#0a1f0c\",\n        \"sidebar-accent\": \"#388e3c\",\n        \"sidebar-accent-foreground\": \"#f0ebe5\",\n        \"sidebar-border\": \"#3e4a3d\",\n        \"sidebar-ring\": \"#4caf50\",\n      },\n    },\n  },\n\n  \"bold-tech\": {\n    label: \"Bold Tech\",\n    styles: {\n      light: {\n        background: \"#ffffff\",\n        foreground: \"#312e81\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#312e81\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#312e81\",\n        primary: \"#8b5cf6\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#f3f0ff\",\n        \"secondary-foreground\": \"#4338ca\",\n        muted: \"#f5f3ff\",\n        \"muted-foreground\": \"#7c3aed\",\n        accent: \"#dbeafe\",\n        \"accent-foreground\": \"#1e40af\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#e0e7ff\",\n        input: \"#e0e7ff\",\n        ring: \"#8b5cf6\",\n        \"chart-1\": \"#8b5cf6\",\n        \"chart-2\": \"#7c3aed\",\n        \"chart-3\": \"#6d28d9\",\n        \"chart-4\": \"#5b21b6\",\n        \"chart-5\": \"#4c1d95\",\n        radius: \"0.625rem\",\n        sidebar: \"#f5f3ff\",\n        \"sidebar-foreground\": \"#312e81\",\n        \"sidebar-primary\": \"#8b5cf6\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#dbeafe\",\n        \"sidebar-accent-foreground\": \"#1e40af\",\n        \"sidebar-border\": \"#e0e7ff\",\n        \"sidebar-ring\": \"#8b5cf6\",\n        \"font-sans\": \"Roboto, sans-serif\",\n        \"font-serif\": \"Playfair Display, serif\",\n        \"font-mono\": \"Fira Code, monospace\",\n        \"shadow-color\": \"hsl(255 86% 66%)\",\n        \"shadow-opacity\": \"0.2\",\n        \"shadow-blur\": \"4px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"2px\",\n        \"shadow-offset-y\": \"2px\",\n      },\n      dark: {\n        background: \"#0f172a\",\n        foreground: \"#e0e7ff\",\n        card: \"#1e1b4b\",\n        \"card-foreground\": \"#e0e7ff\",\n        popover: \"#1e1b4b\",\n        \"popover-foreground\": \"#e0e7ff\",\n        primary: \"#8b5cf6\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#1e1b4b\",\n        \"secondary-foreground\": \"#e0e7ff\",\n        muted: \"#171447\",\n        \"muted-foreground\": \"#c4b5fd\",\n        accent: \"#4338ca\",\n        \"accent-foreground\": \"#e0e7ff\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#2e1065\",\n        input: \"#2e1065\",\n        ring: \"#8b5cf6\",\n        \"chart-1\": \"#a78bfa\",\n        \"chart-2\": \"#8b5cf6\",\n        \"chart-3\": \"#7c3aed\",\n        \"chart-4\": \"#6d28d9\",\n        \"chart-5\": \"#5b21b6\",\n        radius: \"0.625rem\",\n        sidebar: \"#0f172a\",\n        \"sidebar-foreground\": \"#e0e7ff\",\n        \"sidebar-primary\": \"#8b5cf6\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#4338ca\",\n        \"sidebar-accent-foreground\": \"#e0e7ff\",\n        \"sidebar-border\": \"#2e1065\",\n        \"sidebar-ring\": \"#8b5cf6\",\n      },\n    },\n  },\n\n  \"elegant-luxury\": {\n    label: \"Elegant Luxury\",\n    styles: {\n      light: {\n        background: \"#faf7f5\",\n        foreground: \"#1a1a1a\",\n        card: \"#faf7f5\",\n        \"card-foreground\": \"#1a1a1a\",\n        popover: \"#faf7f5\",\n        \"popover-foreground\": \"#1a1a1a\",\n        primary: \"#9b2c2c\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#fdf2d6\",\n        \"secondary-foreground\": \"#805500\",\n        muted: \"#f0ebe8\",\n        \"muted-foreground\": \"#57534e\",\n        accent: \"#fef3c7\",\n        \"accent-foreground\": \"#7f1d1d\",\n        destructive: \"#991b1b\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#f5e8d2\",\n        input: \"#f5e8d2\",\n        ring: \"#9b2c2c\",\n        \"chart-1\": \"#b91c1c\",\n        \"chart-2\": \"#9b2c2c\",\n        \"chart-3\": \"#7f1d1d\",\n        \"chart-4\": \"#b45309\",\n        \"chart-5\": \"#92400e\",\n        radius: \"0.375rem\",\n        sidebar: \"#f0ebe8\",\n        \"sidebar-foreground\": \"#1a1a1a\",\n        \"sidebar-primary\": \"#9b2c2c\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#fef3c7\",\n        \"sidebar-accent-foreground\": \"#7f1d1d\",\n        \"sidebar-border\": \"#f5e8d2\",\n        \"sidebar-ring\": \"#9b2c2c\",\n        \"font-sans\": \"Poppins, sans-serif\",\n        \"font-serif\": \"Libre Baskerville, serif\",\n        \"font-mono\": \"IBM Plex Mono, monospace\",\n        \"shadow-color\": \"hsl(0 63% 18%)\",\n        \"shadow-opacity\": \"0.12\",\n        \"shadow-blur\": \"16px\",\n        \"shadow-spread\": \"-2px\",\n        \"shadow-offset-x\": \"1px\",\n        \"shadow-offset-y\": \"1px\",\n      },\n      dark: {\n        background: \"#1c1917\",\n        foreground: \"#f5f5f4\",\n        card: \"#292524\",\n        \"card-foreground\": \"#f5f5f4\",\n        popover: \"#292524\",\n        \"popover-foreground\": \"#f5f5f4\",\n        primary: \"#b91c1c\",\n        \"primary-foreground\": \"#faf7f5\",\n        secondary: \"#92400e\",\n        \"secondary-foreground\": \"#fef3c7\",\n        muted: \"#1f1c1a\",\n        \"muted-foreground\": \"#d6d3d1\",\n        accent: \"#b45309\",\n        \"accent-foreground\": \"#fef3c7\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#44403c\",\n        input: \"#44403c\",\n        ring: \"#b91c1c\",\n        \"chart-1\": \"#f87171\",\n        \"chart-2\": \"#ef4444\",\n        \"chart-3\": \"#dc2626\",\n        \"chart-4\": \"#fbbf24\",\n        \"chart-5\": \"#f59e0b\",\n        radius: \"0.375rem\",\n        sidebar: \"#1c1917\",\n        \"sidebar-foreground\": \"#f5f5f4\",\n        \"sidebar-primary\": \"#b91c1c\",\n        \"sidebar-primary-foreground\": \"#faf7f5\",\n        \"sidebar-accent\": \"#b45309\",\n        \"sidebar-accent-foreground\": \"#fef3c7\",\n        \"sidebar-border\": \"#44403c\",\n        \"sidebar-ring\": \"#b91c1c\",\n      },\n    },\n  },\n\n  \"amber-minimal\": {\n    label: \"Amber Minimal\",\n    createdAt: \"2025-04-27\",\n    styles: {\n      light: {\n        background: \"#ffffff\",\n        foreground: \"#262626\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#262626\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#262626\",\n        primary: \"#f59e0b\",\n        \"primary-foreground\": \"#000000\",\n        secondary: \"#f3f4f6\",\n        \"secondary-foreground\": \"#4b5563\",\n        muted: \"#f9fafb\",\n        \"muted-foreground\": \"#6b7280\",\n        accent: \"#fffbeb\",\n        \"accent-foreground\": \"#92400e\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#e5e7eb\",\n        input: \"#e5e7eb\",\n        ring: \"#f59e0b\",\n        \"chart-1\": \"#f59e0b\",\n        \"chart-2\": \"#d97706\",\n        \"chart-3\": \"#b45309\",\n        \"chart-4\": \"#92400e\",\n        \"chart-5\": \"#78350f\",\n        sidebar: \"#f9fafb\",\n        \"sidebar-foreground\": \"#262626\",\n        \"sidebar-primary\": \"#f59e0b\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#fffbeb\",\n        \"sidebar-accent-foreground\": \"#92400e\",\n        \"sidebar-border\": \"#e5e7eb\",\n        \"sidebar-ring\": \"#f59e0b\",\n        \"font-sans\": \"Inter, sans-serif\",\n        \"font-serif\": \"Source Serif 4, serif\",\n        \"font-mono\": \"JetBrains Mono, monospace\",\n        radius: \"0.375rem\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.1\",\n        \"shadow-blur\": \"8px\",\n        \"shadow-spread\": \"-1px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"4px\",\n        \"letter-spacing\": \"0em\",\n        spacing: \"0.25rem\",\n      },\n      dark: {\n        background: \"#171717\",\n        foreground: \"#e5e5e5\",\n        card: \"#262626\",\n        \"card-foreground\": \"#e5e5e5\",\n        popover: \"#262626\",\n        \"popover-foreground\": \"#e5e5e5\",\n        primary: \"#f59e0b\",\n        \"primary-foreground\": \"#000000\",\n        secondary: \"#262626\",\n        \"secondary-foreground\": \"#e5e5e5\",\n        muted: \"#1f1f1f\",\n        \"muted-foreground\": \"#a3a3a3\",\n        accent: \"#92400e\",\n        \"accent-foreground\": \"#fde68a\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#404040\",\n        input: \"#404040\",\n        ring: \"#f59e0b\",\n        \"chart-1\": \"#fbbf24\",\n        \"chart-2\": \"#d97706\",\n        \"chart-3\": \"#92400e\",\n        \"chart-4\": \"#b45309\",\n        \"chart-5\": \"#92400e\",\n        sidebar: \"#0f0f0f\",\n        \"sidebar-foreground\": \"#e5e5e5\",\n        \"sidebar-primary\": \"#f59e0b\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#92400e\",\n        \"sidebar-accent-foreground\": \"#fde68a\",\n        \"sidebar-border\": \"#404040\",\n        \"sidebar-ring\": \"#f59e0b\",\n      },\n    },\n  },\n\n  supabase: {\n    label: \"Supabase\",\n    createdAt: \"2025-04-27\",\n    styles: {\n      light: {\n        background: \"#fcfcfc\",\n        foreground: \"#171717\",\n        card: \"#fcfcfc\",\n        \"card-foreground\": \"#171717\",\n        popover: \"#fcfcfc\",\n        \"popover-foreground\": \"#525252\",\n        primary: \"#72e3ad\",\n        \"primary-foreground\": \"#1e2723\",\n        secondary: \"#fdfdfd\",\n        \"secondary-foreground\": \"#171717\",\n        muted: \"#ededed\",\n        \"muted-foreground\": \"#202020\",\n        accent: \"#ededed\",\n        \"accent-foreground\": \"#202020\",\n        destructive: \"#ca3214\",\n        \"destructive-foreground\": \"#fffcfc\",\n        border: \"#dfdfdf\",\n        input: \"#f6f6f6\",\n        ring: \"#72e3ad\",\n        \"chart-1\": \"#72e3ad\",\n        \"chart-2\": \"#3b82f6\",\n        \"chart-3\": \"#8b5cf6\",\n        \"chart-4\": \"#f59e0b\",\n        \"chart-5\": \"#10b981\",\n        sidebar: \"#fcfcfc\",\n        \"sidebar-foreground\": \"#707070\",\n        \"sidebar-primary\": \"#72e3ad\",\n        \"sidebar-primary-foreground\": \"#1e2723\",\n        \"sidebar-accent\": \"#ededed\",\n        \"sidebar-accent-foreground\": \"#202020\",\n        \"sidebar-border\": \"#dfdfdf\",\n        \"sidebar-ring\": \"#72e3ad\",\n        \"font-sans\": \"Outfit, sans-serif\",\n        \"font-serif\": 'ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif',\n        \"font-mono\": \"monospace\",\n        radius: \"0.5rem\",\n        \"shadow-color\": \"#000000\",\n        \"shadow-opacity\": \"0.17\",\n        \"shadow-blur\": \"3px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"1px\",\n        \"letter-spacing\": \"0.025em\",\n      },\n      dark: {\n        background: \"#121212\",\n        foreground: \"#e2e8f0\",\n        card: \"#171717\",\n        \"card-foreground\": \"#e2e8f0\",\n        popover: \"#242424\",\n        \"popover-foreground\": \"#a9a9a9\",\n        primary: \"#006239\",\n        \"primary-foreground\": \"#dde8e3\",\n        secondary: \"#242424\",\n        \"secondary-foreground\": \"#fafafa\",\n        muted: \"#1f1f1f\",\n        \"muted-foreground\": \"#a2a2a2\",\n        accent: \"#313131\",\n        \"accent-foreground\": \"#fafafa\",\n        destructive: \"#541c15\",\n        \"destructive-foreground\": \"#ede9e8\",\n        border: \"#292929\",\n        input: \"#242424\",\n        ring: \"#4ade80\",\n        \"chart-1\": \"#4ade80\",\n        \"chart-2\": \"#60a5fa\",\n        \"chart-3\": \"#a78bfa\",\n        \"chart-4\": \"#fbbf24\",\n        \"chart-5\": \"#2dd4bf\",\n        sidebar: \"#121212\",\n        \"sidebar-foreground\": \"#898989\",\n        \"sidebar-primary\": \"#006239\",\n        \"sidebar-primary-foreground\": \"#dde8e3\",\n        \"sidebar-accent\": \"#313131\",\n        \"sidebar-accent-foreground\": \"#fafafa\",\n        \"sidebar-border\": \"#292929\",\n        \"sidebar-ring\": \"#4ade80\",\n      },\n    },\n  },\n\n  \"neo-brutalism\": {\n    label: \"Neo Brutalism\",\n    styles: {\n      light: {\n        background: \"#ffffff\",\n        foreground: \"#000000\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#000000\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#000000\",\n        primary: \"#ff3333\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#ffff00\",\n        \"secondary-foreground\": \"#000000\",\n        muted: \"#f0f0f0\",\n        \"muted-foreground\": \"#333333\",\n        accent: \"#0066ff\",\n        \"accent-foreground\": \"#ffffff\",\n        destructive: \"#000000\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#000000\",\n        input: \"#000000\",\n        ring: \"#ff3333\",\n        \"chart-1\": \"#ff3333\",\n        \"chart-2\": \"#ffff00\",\n        \"chart-3\": \"#0066ff\",\n        \"chart-4\": \"#00cc00\",\n        \"chart-5\": \"#cc00cc\",\n        radius: \"0px\",\n        sidebar: \"#f0f0f0\",\n        \"sidebar-foreground\": \"#000000\",\n        \"sidebar-primary\": \"#ff3333\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#0066ff\",\n        \"sidebar-accent-foreground\": \"#ffffff\",\n        \"sidebar-border\": \"#000000\",\n        \"sidebar-ring\": \"#ff3333\",\n        \"font-sans\": \"DM Sans, sans-serif\",\n        \"font-mono\": \"Space Mono, monospace\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"1\",\n        \"shadow-blur\": \"0px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"4px\",\n        \"shadow-offset-y\": \"4px\",\n      },\n      dark: {\n        background: \"#000000\",\n        foreground: \"#ffffff\",\n        card: \"#333333\",\n        \"card-foreground\": \"#ffffff\",\n        popover: \"#333333\",\n        \"popover-foreground\": \"#ffffff\",\n        primary: \"#ff6666\",\n        \"primary-foreground\": \"#000000\",\n        secondary: \"#ffff33\",\n        \"secondary-foreground\": \"#000000\",\n        muted: \"#1a1a1a\",\n        \"muted-foreground\": \"#cccccc\",\n        accent: \"#3399ff\",\n        \"accent-foreground\": \"#000000\",\n        destructive: \"#ffffff\",\n        \"destructive-foreground\": \"#000000\",\n        border: \"#ffffff\",\n        input: \"#ffffff\",\n        ring: \"#ff6666\",\n        \"chart-1\": \"#ff6666\",\n        \"chart-2\": \"#ffff33\",\n        \"chart-3\": \"#3399ff\",\n        \"chart-4\": \"#33cc33\",\n        \"chart-5\": \"#cc33cc\",\n        radius: \"0px\",\n        sidebar: \"#000000\",\n        \"sidebar-foreground\": \"#ffffff\",\n        \"sidebar-primary\": \"#ff6666\",\n        \"sidebar-primary-foreground\": \"#000000\",\n        \"sidebar-accent\": \"#3399ff\",\n        \"sidebar-accent-foreground\": \"#000000\",\n        \"sidebar-border\": \"#ffffff\",\n        \"sidebar-ring\": \"#ff6666\",\n      },\n    },\n  },\n\n  \"solar-dusk\": {\n    label: \"Solar Dusk\",\n    createdAt: \"2025-04-12\",\n    styles: {\n      light: {\n        background: \"#FDFBF7\",\n        foreground: \"#4A3B33\",\n        card: \"#F8F4EE\",\n        \"card-foreground\": \"#4A3B33\",\n        popover: \"#F8F4EE\",\n        \"popover-foreground\": \"#4A3B33\",\n        primary: \"#B45309\",\n        \"primary-foreground\": \"#FFFFFF\",\n        secondary: \"#E4C090\",\n        \"secondary-foreground\": \"#57534E\",\n        muted: \"#F1E9DA\",\n        \"muted-foreground\": \"#78716C\",\n        accent: \"#f2daba\",\n        \"accent-foreground\": \"#57534E\",\n        destructive: \"#991B1B\",\n        \"destructive-foreground\": \"#FFFFFF\",\n        border: \"#E4D9BC\",\n        input: \"#E4D9BC\",\n        ring: \"#B45309\",\n        \"chart-1\": \"#B45309\",\n        \"chart-2\": \"#78716C\",\n        \"chart-3\": \"#A16207\",\n        \"chart-4\": \"#78716C\",\n        \"chart-5\": \"#CA8A04\",\n        radius: \"0.3rem\",\n        sidebar: \"#F1E9DA\",\n        \"sidebar-foreground\": \"#4A3B33\",\n        \"sidebar-primary\": \"#B45309\",\n        \"sidebar-primary-foreground\": \"#FFFFFF\",\n        \"sidebar-accent\": \"#A16207\",\n        \"sidebar-accent-foreground\": \"#FFFFFF\",\n        \"sidebar-border\": \"#E4D9BC\",\n        \"sidebar-ring\": \"#B45309\",\n        \"font-sans\": \"Oxanium, sans-serif\",\n        \"font-serif\": \"Merriweather, serif\",\n        \"font-mono\": \"Fira Code, monospace\",\n        \"shadow-color\": \"hsl(28 18% 25%)\",\n        \"shadow-opacity\": \"0.18\",\n        \"shadow-blur\": \"3px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"2px\",\n      },\n      dark: {\n        background: \"#1C1917\",\n        foreground: \"#F5F5F4\",\n        card: \"#292524\",\n        \"card-foreground\": \"#F5F5F4\",\n        popover: \"#292524\",\n        \"popover-foreground\": \"#F5F5F4\",\n        primary: \"#F97316\",\n        \"primary-foreground\": \"#FFFFFF\",\n        secondary: \"#57534E\",\n        \"secondary-foreground\": \"#E7E5E4\",\n        muted: \"#201d1a\",\n        \"muted-foreground\": \"#A8A29E\",\n        accent: \"#1e4252\",\n        \"accent-foreground\": \"#E7E5E4\",\n        destructive: \"#DC2626\",\n        \"destructive-foreground\": \"#FFFFFF\",\n        border: \"#44403C\",\n        input: \"#44403C\",\n        ring: \"#F97316\",\n        \"chart-1\": \"#F97316\",\n        \"chart-2\": \"#0EA5E9\",\n        \"chart-3\": \"#EAB308\",\n        \"chart-4\": \"#A8A29E\",\n        \"chart-5\": \"#78716C\",\n        radius: \"0.3rem\",\n        sidebar: \"#292524\",\n        \"sidebar-foreground\": \"#F5F5F4\",\n        \"sidebar-primary\": \"#F97316\",\n        \"sidebar-primary-foreground\": \"#FFFFFF\",\n        \"sidebar-accent\": \"#0EA5E9\",\n        \"sidebar-accent-foreground\": \"#0C2A4D\",\n        \"sidebar-border\": \"#44403C\",\n        \"sidebar-ring\": \"#F97316\",\n        \"shadow-color\": \"hsl(0 0% 5%)\",\n      },\n    },\n  },\n\n  claymorphism: {\n    label: \"Claymorphism\",\n    styles: {\n      light: {\n        background: \"#e7e5e4\",\n        foreground: \"#1e293b\",\n        card: \"#f5f5f4\",\n        \"card-foreground\": \"#1e293b\",\n        popover: \"#f5f5f4\",\n        \"popover-foreground\": \"#1e293b\",\n        primary: \"#6366f1\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#d6d3d1\",\n        \"secondary-foreground\": \"#4b5563\",\n        muted: \"#e7e5e4\",\n        \"muted-foreground\": \"#6b7280\",\n        accent: \"#f3e5f5\",\n        \"accent-foreground\": \"#374151\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#d6d3d1\",\n        input: \"#d6d3d1\",\n        ring: \"#6366f1\",\n        \"chart-1\": \"#6366f1\",\n        \"chart-2\": \"#4f46e5\",\n        \"chart-3\": \"#4338ca\",\n        \"chart-4\": \"#3730a3\",\n        \"chart-5\": \"#312e81\",\n        radius: \"1.25rem\",\n        sidebar: \"#d6d3d1\",\n        \"sidebar-foreground\": \"#1e293b\",\n        \"sidebar-primary\": \"#6366f1\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#f3e5f5\",\n        \"sidebar-accent-foreground\": \"#374151\",\n        \"sidebar-border\": \"#d6d3d1\",\n        \"sidebar-ring\": \"#6366f1\",\n        \"font-sans\": \"Plus Jakarta Sans, sans-serif\",\n        \"font-serif\": \"Lora, serif\",\n        \"font-mono\": \"Roboto Mono, monospace\",\n        \"shadow-color\": \"hsl(240 4% 60%)\",\n        \"shadow-opacity\": \"0.18\",\n        \"shadow-blur\": \"10px\",\n        \"shadow-spread\": \"4px\",\n        \"shadow-offset-x\": \"2px\",\n        \"shadow-offset-y\": \"2px\",\n      },\n      dark: {\n        background: \"#1e1b18\",\n        foreground: \"#e2e8f0\",\n        card: \"#2c2825\",\n        \"card-foreground\": \"#e2e8f0\",\n        popover: \"#2c2825\",\n        \"popover-foreground\": \"#e2e8f0\",\n        primary: \"#818cf8\",\n        \"primary-foreground\": \"#1e1b18\",\n        secondary: \"#3a3633\",\n        \"secondary-foreground\": \"#d1d5db\",\n        muted: \"#1f1c19\",\n        \"muted-foreground\": \"#9ca3af\",\n        accent: \"#484441\",\n        \"accent-foreground\": \"#d1d5db\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#1e1b18\",\n        border: \"#3a3633\",\n        input: \"#3a3633\",\n        ring: \"#818cf8\",\n        \"chart-1\": \"#818cf8\",\n        \"chart-2\": \"#6366f1\",\n        \"chart-3\": \"#4f46e5\",\n        \"chart-4\": \"#4338ca\",\n        \"chart-5\": \"#3730a3\",\n        radius: \"1.25rem\",\n        sidebar: \"#3a3633\",\n        \"sidebar-foreground\": \"#e2e8f0\",\n        \"sidebar-primary\": \"#818cf8\",\n        \"sidebar-primary-foreground\": \"#1e1b18\",\n        \"sidebar-accent\": \"#484441\",\n        \"sidebar-accent-foreground\": \"#d1d5db\",\n        \"sidebar-border\": \"#3a3633\",\n        \"sidebar-ring\": \"#818cf8\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n      },\n    },\n  },\n\n  cyberpunk: {\n    label: \"Cyberpunk\",\n    styles: {\n      light: {\n        background: \"#f8f9fa\",\n        foreground: \"#0c0c1d\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#0c0c1d\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#0c0c1d\",\n        primary: \"#ff00c8\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#f0f0ff\",\n        \"secondary-foreground\": \"#0c0c1d\",\n        muted: \"#f0f0ff\",\n        \"muted-foreground\": \"#0c0c1d\",\n        accent: \"#00ffcc\",\n        \"accent-foreground\": \"#0c0c1d\",\n        destructive: \"#ff3d00\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#dfe6e9\",\n        input: \"#dfe6e9\",\n        ring: \"#ff00c8\",\n        \"chart-1\": \"#ff00c8\",\n        \"chart-2\": \"#9000ff\",\n        \"chart-3\": \"#00e5ff\",\n        \"chart-4\": \"#00ffcc\",\n        \"chart-5\": \"#ffe600\",\n        radius: \"0.5rem\",\n        sidebar: \"#f0f0ff\",\n        \"sidebar-foreground\": \"#0c0c1d\",\n        \"sidebar-primary\": \"#ff00c8\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#00ffcc\",\n        \"sidebar-accent-foreground\": \"#0c0c1d\",\n        \"sidebar-border\": \"#dfe6e9\",\n        \"sidebar-ring\": \"#ff00c8\",\n        \"font-sans\": \"Outfit, sans-serif\",\n        \"font-mono\": \"Fira Code, monospace\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.1\",\n        \"shadow-blur\": \"8px\",\n        \"shadow-spread\": \"-2px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"4px\",\n      },\n      dark: {\n        background: \"#0c0c1d\",\n        foreground: \"#eceff4\",\n        card: \"#1e1e3f\",\n        \"card-foreground\": \"#eceff4\",\n        popover: \"#1e1e3f\",\n        \"popover-foreground\": \"#eceff4\",\n        primary: \"#ff00c8\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#1e1e3f\",\n        \"secondary-foreground\": \"#eceff4\",\n        muted: \"#151530\",\n        \"muted-foreground\": \"#8085a6\",\n        accent: \"#00ffcc\",\n        \"accent-foreground\": \"#0c0c1d\",\n        destructive: \"#ff3d00\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#2e2e5e\",\n        input: \"#2e2e5e\",\n        ring: \"#ff00c8\",\n        \"chart-1\": \"#ff00c8\",\n        \"chart-2\": \"#9000ff\",\n        \"chart-3\": \"#00e5ff\",\n        \"chart-4\": \"#00ffcc\",\n        \"chart-5\": \"#ffe600\",\n        radius: \"0.5rem\",\n        sidebar: \"#0c0c1d\",\n        \"sidebar-foreground\": \"#eceff4\",\n        \"sidebar-primary\": \"#ff00c8\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#00ffcc\",\n        \"sidebar-accent-foreground\": \"#0c0c1d\",\n        \"sidebar-border\": \"#2e2e5e\",\n        \"sidebar-ring\": \"#ff00c8\",\n      },\n    },\n  },\n  \"pastel-dreams\": {\n    label: \"Pastel Dreams\",\n    styles: {\n      light: {\n        background: \"#f7f3f9\",\n        foreground: \"#374151\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#374151\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#374151\",\n        primary: \"#a78bfa\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#e9d8fd\",\n        \"secondary-foreground\": \"#4b5563\",\n        muted: \"#f3e8ff\",\n        \"muted-foreground\": \"#6b7280\",\n        accent: \"#f3e5f5\",\n        \"accent-foreground\": \"#374151\",\n        destructive: \"#fca5a5\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#e9d8fd\",\n        input: \"#e9d8fd\",\n        ring: \"#a78bfa\",\n        \"chart-1\": \"#a78bfa\",\n        \"chart-2\": \"#8b5cf6\",\n        \"chart-3\": \"#7c3aed\",\n        \"chart-4\": \"#6d28d9\",\n        \"chart-5\": \"#5b21b6\",\n        radius: \"1.5rem\",\n        sidebar: \"#e9d8fd\",\n        \"sidebar-foreground\": \"#374151\",\n        \"sidebar-primary\": \"#a78bfa\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#f3e5f5\",\n        \"sidebar-accent-foreground\": \"#374151\",\n        \"sidebar-border\": \"#e9d8fd\",\n        \"sidebar-ring\": \"#a78bfa\",\n        \"font-sans\": \"Open Sans, sans-serif\",\n        \"font-serif\": \"Source Serif 4, serif\",\n        \"font-mono\": \"IBM Plex Mono, monospace\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.08\",\n        \"shadow-blur\": \"16px\",\n        \"shadow-spread\": \"-4px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"8px\",\n      },\n      dark: {\n        background: \"#1c1917\",\n        foreground: \"#e0e7ff\",\n        card: \"#2d2535\",\n        \"card-foreground\": \"#e0e7ff\",\n        popover: \"#2d2535\",\n        \"popover-foreground\": \"#e0e7ff\",\n        primary: \"#c0aafd\",\n        \"primary-foreground\": \"#1c1917\",\n        secondary: \"#3f324a\",\n        \"secondary-foreground\": \"#d1d5db\",\n        muted: \"#20182b\",\n        \"muted-foreground\": \"#9ca3af\",\n        accent: \"#4a3d5a\",\n        \"accent-foreground\": \"#d1d5db\",\n        destructive: \"#fca5a5\",\n        \"destructive-foreground\": \"#1c1917\",\n        border: \"#3f324a\",\n        input: \"#3f324a\",\n        ring: \"#c0aafd\",\n        \"chart-1\": \"#c0aafd\",\n        \"chart-2\": \"#a78bfa\",\n        \"chart-3\": \"#8b5cf6\",\n        \"chart-4\": \"#7c3aed\",\n        \"chart-5\": \"#6d28d9\",\n        radius: \"1.5rem\",\n        sidebar: \"#3f324a\",\n        \"sidebar-foreground\": \"#e0e7ff\",\n        \"sidebar-primary\": \"#c0aafd\",\n        \"sidebar-primary-foreground\": \"#1c1917\",\n        \"sidebar-accent\": \"#4a3d5a\",\n        \"sidebar-accent-foreground\": \"#d1d5db\",\n        \"sidebar-border\": \"#3f324a\",\n        \"sidebar-ring\": \"#c0aafd\",\n      },\n    },\n  },\n\n  \"clean-slate\": {\n    label: \"Clean Slate\",\n    styles: {\n      light: {\n        background: \"#f8fafc\",\n        foreground: \"#1e293b\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#1e293b\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#1e293b\",\n        primary: \"#6366f1\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#e5e7eb\",\n        \"secondary-foreground\": \"#374151\",\n        muted: \"#f3f4f6\",\n        \"muted-foreground\": \"#6b7280\",\n        accent: \"#e0e7ff\",\n        \"accent-foreground\": \"#374151\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#d1d5db\",\n        input: \"#d1d5db\",\n        ring: \"#6366f1\",\n        \"chart-1\": \"#6366f1\",\n        \"chart-2\": \"#4f46e5\",\n        \"chart-3\": \"#4338ca\",\n        \"chart-4\": \"#3730a3\",\n        \"chart-5\": \"#312e81\",\n        radius: \"0.5rem\",\n        sidebar: \"#f3f4f6\",\n        \"sidebar-foreground\": \"#1e293b\",\n        \"sidebar-primary\": \"#6366f1\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#e0e7ff\",\n        \"sidebar-accent-foreground\": \"#374151\",\n        \"sidebar-border\": \"#d1d5db\",\n        \"sidebar-ring\": \"#6366f1\",\n        \"font-sans\": \"Inter, sans-serif\",\n        \"font-serif\": \"Merriweather, serif\",\n        \"font-mono\": \"JetBrains Mono, monospace\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.1\",\n        \"shadow-blur\": \"8px\",\n        \"shadow-spread\": \"-1px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"4px\",\n      },\n      dark: {\n        background: \"#0f172a\",\n        foreground: \"#e2e8f0\",\n        card: \"#1e293b\",\n        \"card-foreground\": \"#e2e8f0\",\n        popover: \"#1e293b\",\n        \"popover-foreground\": \"#e2e8f0\",\n        primary: \"#818cf8\",\n        \"primary-foreground\": \"#0f172a\",\n        secondary: \"#2d3748\",\n        \"secondary-foreground\": \"#d1d5db\",\n        muted: \"#152032\",\n        \"muted-foreground\": \"#9ca3af\",\n        accent: \"#374151\",\n        \"accent-foreground\": \"#d1d5db\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#0f172a\",\n        border: \"#4b5563\",\n        input: \"#4b5563\",\n        ring: \"#818cf8\",\n        \"chart-1\": \"#818cf8\",\n        \"chart-2\": \"#6366f1\",\n        \"chart-3\": \"#4f46e5\",\n        \"chart-4\": \"#4338ca\",\n        \"chart-5\": \"#3730a3\",\n        radius: \"0.5rem\",\n        sidebar: \"#1e293b\",\n        \"sidebar-foreground\": \"#e2e8f0\",\n        \"sidebar-primary\": \"#818cf8\",\n        \"sidebar-primary-foreground\": \"#0f172a\",\n        \"sidebar-accent\": \"#374151\",\n        \"sidebar-accent-foreground\": \"#d1d5db\",\n        \"sidebar-border\": \"#4b5563\",\n        \"sidebar-ring\": \"#818cf8\",\n      },\n    },\n  },\n\n  caffeine: {\n    label: \"Caffeine\",\n    styles: {\n      light: {\n        background: \"#f9f9f9\",\n        foreground: \"#202020\",\n        card: \"#fcfcfc\",\n        \"card-foreground\": \"#202020\",\n        popover: \"#fcfcfc\",\n        \"popover-foreground\": \"#202020\",\n        primary: \"#644a40\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#ffdfb5\",\n        \"secondary-foreground\": \"#582d1d\",\n        muted: \"#efefef\",\n        \"muted-foreground\": \"#646464\",\n        accent: \"#e8e8e8\",\n        \"accent-foreground\": \"#202020\",\n        destructive: \"#e54d2e\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#d8d8d8\",\n        input: \"#d8d8d8\",\n        ring: \"#644a40\",\n        \"chart-1\": \"#644a40\",\n        \"chart-2\": \"#ffdfb5\",\n        \"chart-3\": \"#e8e8e8\",\n        \"chart-4\": \"#ffe6c4\",\n        \"chart-5\": \"#66493e\",\n        sidebar: \"#fbfbfb\",\n        \"sidebar-foreground\": \"#252525\",\n        \"sidebar-primary\": \"#343434\",\n        \"sidebar-primary-foreground\": \"#fbfbfb\",\n        \"sidebar-accent\": \"#f7f7f7\",\n        \"sidebar-accent-foreground\": \"#343434\",\n        \"sidebar-border\": \"#ebebeb\",\n        \"sidebar-ring\": \"#b5b5b5\",\n        radius: \"0.5rem\",\n      },\n      dark: {\n        background: \"#111111\",\n        foreground: \"#eeeeee\",\n        card: \"#191919\",\n        \"card-foreground\": \"#eeeeee\",\n        popover: \"#191919\",\n        \"popover-foreground\": \"#eeeeee\",\n        primary: \"#ffe0c2\",\n        \"primary-foreground\": \"#081a1b\",\n        secondary: \"#393028\",\n        \"secondary-foreground\": \"#ffe0c2\",\n        muted: \"#222222\",\n        \"muted-foreground\": \"#b4b4b4\",\n        accent: \"#2a2a2a\",\n        \"accent-foreground\": \"#eeeeee\",\n        destructive: \"#e54d2e\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#201e18\",\n        input: \"#484848\",\n        ring: \"#ffe0c2\",\n        \"chart-1\": \"#ffe0c2\",\n        \"chart-2\": \"#393028\",\n        \"chart-3\": \"#2a2a2a\",\n        \"chart-4\": \"#42382e\",\n        \"chart-5\": \"#ffe0c1\",\n        sidebar: \"#18181b\",\n        \"sidebar-foreground\": \"#f4f4f5\",\n        \"sidebar-primary\": \"#1d4ed8\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#27272a\",\n        \"sidebar-accent-foreground\": \"#f4f4f5\",\n        \"sidebar-border\": \"#27272a\",\n        \"sidebar-ring\": \"#d4d4d8\",\n        radius: \"0.5rem\",\n      },\n    },\n  },\n  \"ocean-breeze\": {\n    label: \"Ocean Breeze\",\n    styles: {\n      light: {\n        background: \"#f0f8ff\",\n        foreground: \"#374151\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#374151\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#374151\",\n        primary: \"#22c55e\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#e0f2fe\",\n        \"secondary-foreground\": \"#4b5563\",\n        muted: \"#f3f4f6\",\n        \"muted-foreground\": \"#6b7280\",\n        accent: \"#d1fae5\",\n        \"accent-foreground\": \"#374151\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#e5e7eb\",\n        input: \"#e5e7eb\",\n        ring: \"#22c55e\",\n        \"chart-1\": \"#22c55e\",\n        \"chart-2\": \"#10b981\",\n        \"chart-3\": \"#059669\",\n        \"chart-4\": \"#047857\",\n        \"chart-5\": \"#065f46\",\n        radius: \"0.5rem\",\n        sidebar: \"#e0f2fe\",\n        \"sidebar-foreground\": \"#374151\",\n        \"sidebar-primary\": \"#22c55e\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#d1fae5\",\n        \"sidebar-accent-foreground\": \"#374151\",\n        \"sidebar-border\": \"#e5e7eb\",\n        \"sidebar-ring\": \"#22c55e\",\n        \"font-sans\": \"DM Sans, sans-serif\",\n        \"font-serif\": \"Lora, serif\",\n        \"font-mono\": \"IBM Plex Mono, monospace\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.1\",\n        \"shadow-blur\": \"8px\",\n        \"shadow-spread\": \"-1px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"4px\",\n      },\n      dark: {\n        background: \"#0f172a\",\n        foreground: \"#d1d5db\",\n        card: \"#1e293b\",\n        \"card-foreground\": \"#d1d5db\",\n        popover: \"#1e293b\",\n        \"popover-foreground\": \"#d1d5db\",\n        primary: \"#34d399\",\n        \"primary-foreground\": \"#0f172a\",\n        secondary: \"#2d3748\",\n        \"secondary-foreground\": \"#a1a1aa\",\n        muted: \"#19212e\",\n        \"muted-foreground\": \"#6b7280\",\n        accent: \"#374151\",\n        \"accent-foreground\": \"#a1a1aa\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#0f172a\",\n        border: \"#4b5563\",\n        input: \"#4b5563\",\n        ring: \"#34d399\",\n        \"chart-1\": \"#34d399\",\n        \"chart-2\": \"#2dd4bf\",\n        \"chart-3\": \"#22c55e\",\n        \"chart-4\": \"#10b981\",\n        \"chart-5\": \"#059669\",\n        radius: \"0.5rem\",\n        sidebar: \"#1e293b\",\n        \"sidebar-foreground\": \"#d1d5db\",\n        \"sidebar-primary\": \"#34d399\",\n        \"sidebar-primary-foreground\": \"#0f172a\",\n        \"sidebar-accent\": \"#374151\",\n        \"sidebar-accent-foreground\": \"#a1a1aa\",\n        \"sidebar-border\": \"#4b5563\",\n        \"sidebar-ring\": \"#34d399\",\n      },\n    },\n  },\n  \"retro-arcade\": {\n    label: \"Retro Arcade\",\n    styles: {\n      light: {\n        background: \"#fdf6e3\",\n        foreground: \"#073642\",\n        card: \"#eee8d5\",\n        \"card-foreground\": \"#073642\",\n        popover: \"#eee8d5\",\n        \"popover-foreground\": \"#073642\",\n        primary: \"#d33682\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#2aa198\",\n        \"secondary-foreground\": \"#ffffff\",\n        muted: \"#93a1a1\",\n        \"muted-foreground\": \"#073642\",\n        accent: \"#cb4b16\",\n        \"accent-foreground\": \"#ffffff\",\n        destructive: \"#dc322f\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#839496\",\n        input: \"#839496\",\n        ring: \"#d33682\",\n        \"chart-1\": \"#268bd2\",\n        \"chart-2\": \"#2aa198\",\n        \"chart-3\": \"#d33682\",\n        \"chart-4\": \"#cb4b16\",\n        \"chart-5\": \"#dc322f\",\n        radius: \"0.25rem\",\n        sidebar: \"#fdf6e3\",\n        \"sidebar-foreground\": \"#073642\",\n        \"sidebar-primary\": \"#d33682\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#2aa198\",\n        \"sidebar-accent-foreground\": \"#ffffff\",\n        \"sidebar-border\": \"#839496\",\n        \"sidebar-ring\": \"#d33682\",\n        \"font-sans\": \"Outfit, sans-serif\",\n        \"font-mono\": \"Space Mono, monospace\",\n        \"shadow-color\": \"hsl(196 83% 10%)\",\n        \"shadow-opacity\": \"0.15\",\n        \"shadow-blur\": \"4px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"2px\",\n        \"shadow-offset-y\": \"2px\",\n      },\n      dark: {\n        background: \"#002b36\",\n        foreground: \"#93a1a1\",\n        card: \"#073642\",\n        \"card-foreground\": \"#93a1a1\",\n        popover: \"#073642\",\n        \"popover-foreground\": \"#93a1a1\",\n        primary: \"#d33682\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#2aa198\",\n        \"secondary-foreground\": \"#ffffff\",\n        muted: \"#586e75\",\n        \"muted-foreground\": \"#93a1a1\",\n        accent: \"#cb4b16\",\n        \"accent-foreground\": \"#ffffff\",\n        destructive: \"#dc322f\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#586e75\",\n        input: \"#586e75\",\n        ring: \"#d33682\",\n        \"chart-1\": \"#268bd2\",\n        \"chart-2\": \"#2aa198\",\n        \"chart-3\": \"#d33682\",\n        \"chart-4\": \"#cb4b16\",\n        \"chart-5\": \"#dc322f\",\n        radius: \"0.25rem\",\n        sidebar: \"#002b36\",\n        \"sidebar-foreground\": \"#93a1a1\",\n        \"sidebar-primary\": \"#d33682\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#2aa198\",\n        \"sidebar-accent-foreground\": \"#ffffff\",\n        \"sidebar-border\": \"#586e75\",\n        \"sidebar-ring\": \"#d33682\",\n      },\n    },\n  },\n\n  \"midnight-bloom\": {\n    label: \"Midnight Bloom\",\n    styles: {\n      light: {\n        background: \"#f9f9f9\",\n        foreground: \"#333333\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#333333\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#333333\",\n        primary: \"#6c5ce7\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#a1c9f2\",\n        \"secondary-foreground\": \"#333333\",\n        muted: \"#c9c4b5\",\n        \"muted-foreground\": \"#6e6e6e\",\n        accent: \"#8b9467\",\n        \"accent-foreground\": \"#ffffff\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#d4d4d4\",\n        input: \"#d4d4d4\",\n        ring: \"#6c5ce7\",\n        \"chart-1\": \"#6c5ce7\",\n        \"chart-2\": \"#8e44ad\",\n        \"chart-3\": \"#4b0082\",\n        \"chart-4\": \"#6495ed\",\n        \"chart-5\": \"#4682b4\",\n        radius: \"0.5rem\",\n        sidebar: \"#f9f9f9\",\n        \"sidebar-foreground\": \"#333333\",\n        \"sidebar-primary\": \"#6c5ce7\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#8b9467\",\n        \"sidebar-accent-foreground\": \"#ffffff\",\n        \"sidebar-border\": \"#d4d4d4\",\n        \"sidebar-ring\": \"#6c5ce7\",\n        \"font-sans\": \"Montserrat, sans-serif\",\n        \"font-serif\": \"Playfair Display, serif\",\n        \"font-mono\": \"Source Code Pro, monospace\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.1\",\n        \"shadow-blur\": \"10px\",\n        \"shadow-spread\": \"-2px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"5px\",\n      },\n      dark: {\n        background: \"#1a1d23\",\n        foreground: \"#e5e5e5\",\n        card: \"#2f3436\",\n        \"card-foreground\": \"#e5e5e5\",\n        popover: \"#2f3436\",\n        \"popover-foreground\": \"#e5e5e5\",\n        primary: \"#6c5ce7\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#4b0082\",\n        \"secondary-foreground\": \"#e5e5e5\",\n        muted: \"#444444\",\n        \"muted-foreground\": \"#a3a3a3\",\n        accent: \"#6495ed\",\n        \"accent-foreground\": \"#e5e5e5\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#444444\",\n        input: \"#444444\",\n        ring: \"#6c5ce7\",\n        \"chart-1\": \"#6c5ce7\",\n        \"chart-2\": \"#8e44ad\",\n        \"chart-3\": \"#4b0082\",\n        \"chart-4\": \"#6495ed\",\n        \"chart-5\": \"#4682b4\",\n        radius: \"0.5rem\",\n        sidebar: \"#1a1d23\",\n        \"sidebar-foreground\": \"#e5e5e5\",\n        \"sidebar-primary\": \"#6c5ce7\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#6495ed\",\n        \"sidebar-accent-foreground\": \"#e5e5e5\",\n        \"sidebar-border\": \"#444444\",\n        \"sidebar-ring\": \"#6c5ce7\",\n      },\n    },\n  },\n  candyland: {\n    label: \"Candyland\",\n    styles: {\n      light: {\n        background: \"#f7f9fa\",\n        foreground: \"#333333\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#333333\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#333333\",\n        primary: \"#ffc0cb\",\n        \"primary-foreground\": \"#000000\",\n        secondary: \"#87ceeb\",\n        \"secondary-foreground\": \"#000000\",\n        muted: \"#ddd9c4\",\n        \"muted-foreground\": \"#6e6e6e\",\n        accent: \"#ffff00\",\n        \"accent-foreground\": \"#000000\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#d4d4d4\",\n        input: \"#d4d4d4\",\n        ring: \"#ffc0cb\",\n        \"chart-1\": \"#ffc0cb\",\n        \"chart-2\": \"#87ceeb\",\n        \"chart-3\": \"#ffff00\",\n        \"chart-4\": \"#ff99cc\",\n        \"chart-5\": \"#33cc33\",\n        radius: \"0.5rem\",\n        sidebar: \"#f7f9fa\",\n        \"sidebar-foreground\": \"#333333\",\n        \"sidebar-primary\": \"#ffc0cb\",\n        \"sidebar-primary-foreground\": \"#000000\",\n        \"sidebar-accent\": \"#ffff00\",\n        \"sidebar-accent-foreground\": \"#000000\",\n        \"sidebar-border\": \"#d4d4d4\",\n        \"sidebar-ring\": \"#ffc0cb\",\n        \"font-sans\": \"Poppins, sans-serif\",\n        \"font-mono\": \"Roboto Mono, monospace\",\n      },\n      dark: {\n        background: \"#1a1d23\",\n        foreground: \"#e5e5e5\",\n        card: \"#2f3436\",\n        \"card-foreground\": \"#e5e5e5\",\n        popover: \"#2f3436\",\n        \"popover-foreground\": \"#e5e5e5\",\n        primary: \"#ff99cc\",\n        \"primary-foreground\": \"#000000\",\n        secondary: \"#33cc33\",\n        \"secondary-foreground\": \"#000000\",\n        muted: \"#444444\",\n        \"muted-foreground\": \"#a3a3a3\",\n        accent: \"#87ceeb\",\n        \"accent-foreground\": \"#000000\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#444444\",\n        input: \"#444444\",\n        ring: \"#ff99cc\",\n        \"chart-1\": \"#ff99cc\",\n        \"chart-2\": \"#33cc33\",\n        \"chart-3\": \"#87ceeb\",\n        \"chart-4\": \"#ffff00\",\n        \"chart-5\": \"#ffcc00\",\n        radius: \"0.5rem\",\n        sidebar: \"#1a1d23\",\n        \"sidebar-foreground\": \"#e5e5e5\",\n        \"sidebar-primary\": \"#ff99cc\",\n        \"sidebar-primary-foreground\": \"#000000\",\n        \"sidebar-accent\": \"#87ceeb\",\n        \"sidebar-accent-foreground\": \"#000000\",\n        \"sidebar-border\": \"#444444\",\n        \"sidebar-ring\": \"#ff99cc\",\n      },\n    },\n  },\n  \"northern-lights\": {\n    label: \"Northern Lights\",\n    styles: {\n      light: {\n        background: \"#f9f9fa\",\n        foreground: \"#333333\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#333333\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#333333\",\n        primary: \"#34a85a\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#6495ed\",\n        \"secondary-foreground\": \"#ffffff\",\n        muted: \"#ddd9c4\",\n        \"muted-foreground\": \"#6e6e6e\",\n        accent: \"#66d9ef\",\n        \"accent-foreground\": \"#333333\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#d4d4d4\",\n        input: \"#d4d4d4\",\n        ring: \"#34a85a\",\n        \"chart-1\": \"#34a85a\",\n        \"chart-2\": \"#6495ed\",\n        \"chart-3\": \"#66d9ef\",\n        \"chart-4\": \"#4682b4\",\n        \"chart-5\": \"#1a9641\",\n        radius: \"0.5rem\",\n        sidebar: \"#f9f9fa\",\n        \"sidebar-foreground\": \"#333333\",\n        \"sidebar-primary\": \"#34a85a\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#66d9ef\",\n        \"sidebar-accent-foreground\": \"#333333\",\n        \"sidebar-border\": \"#d4d4d4\",\n        \"sidebar-ring\": \"#34a85a\",\n        \"font-sans\": \"Plus Jakarta Sans, sans-serif\",\n        \"font-serif\": \"Source Serif 4, serif\",\n        \"font-mono\": \"JetBrains Mono, monospace\",\n      },\n      dark: {\n        background: \"#1a1d23\",\n        foreground: \"#e5e5e5\",\n        card: \"#2f3436\",\n        \"card-foreground\": \"#e5e5e5\",\n        popover: \"#2f3436\",\n        \"popover-foreground\": \"#e5e5e5\",\n        primary: \"#34a85a\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#4682b4\",\n        \"secondary-foreground\": \"#e5e5e5\",\n        muted: \"#444444\",\n        \"muted-foreground\": \"#a3a3a3\",\n        accent: \"#6495ed\",\n        \"accent-foreground\": \"#e5e5e5\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#444444\",\n        input: \"#444444\",\n        ring: \"#34a85a\",\n        \"chart-1\": \"#34a85a\",\n        \"chart-2\": \"#4682b4\",\n        \"chart-3\": \"#6495ed\",\n        \"chart-4\": \"#66d9ef\",\n        \"chart-5\": \"#1a9641\",\n        radius: \"0.5rem\",\n        sidebar: \"#1a1d23\",\n        \"sidebar-foreground\": \"#e5e5e5\",\n        \"sidebar-primary\": \"#34a85a\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#6495ed\",\n        \"sidebar-accent-foreground\": \"#e5e5e5\",\n        \"sidebar-border\": \"#444444\",\n        \"sidebar-ring\": \"#34a85a\",\n      },\n    },\n  },\n  \"vintage-paper\": {\n    label: \"Vintage Paper\",\n    styles: {\n      light: {\n        background: \"#f5f1e6\",\n        foreground: \"#4a3f35\",\n        card: \"#fffcf5\",\n        \"card-foreground\": \"#4a3f35\",\n        popover: \"#fffcf5\",\n        \"popover-foreground\": \"#4a3f35\",\n        primary: \"#a67c52\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#e2d8c3\",\n        \"secondary-foreground\": \"#5c4d3f\",\n        muted: \"#ece5d8\",\n        \"muted-foreground\": \"#7d6b56\",\n        accent: \"#d4c8aa\",\n        \"accent-foreground\": \"#4a3f35\",\n        destructive: \"#b54a35\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#dbd0ba\",\n        input: \"#dbd0ba\",\n        ring: \"#a67c52\",\n        \"chart-1\": \"#a67c52\",\n        \"chart-2\": \"#8d6e4c\",\n        \"chart-3\": \"#735a3a\",\n        \"chart-4\": \"#b3906f\",\n        \"chart-5\": \"#c0a080\",\n        radius: \"0.25rem\",\n        sidebar: \"#ece5d8\",\n        \"sidebar-foreground\": \"#4a3f35\",\n        \"sidebar-primary\": \"#a67c52\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#d4c8aa\",\n        \"sidebar-accent-foreground\": \"#4a3f35\",\n        \"sidebar-border\": \"#dbd0ba\",\n        \"sidebar-ring\": \"#a67c52\",\n        \"font-sans\": \"Libre Baskerville, serif\",\n        \"font-serif\": \"Lora, serif\",\n        \"font-mono\": \"IBM Plex Mono, monospace\",\n        \"shadow-color\": \"hsl(28 13% 20%)\",\n        \"shadow-opacity\": \"0.12\",\n        \"shadow-blur\": \"5px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"2px\",\n        \"shadow-offset-y\": \"3px\",\n      },\n      dark: {\n        background: \"#2d2621\",\n        foreground: \"#ece5d8\",\n        card: \"#3a322c\",\n        \"card-foreground\": \"#ece5d8\",\n        popover: \"#3a322c\",\n        \"popover-foreground\": \"#ece5d8\",\n        primary: \"#c0a080\",\n        \"primary-foreground\": \"#2d2621\",\n        secondary: \"#4a4039\",\n        \"secondary-foreground\": \"#ece5d8\",\n        muted: \"#312b26\",\n        \"muted-foreground\": \"#c5bcac\",\n        accent: \"#59493e\",\n        \"accent-foreground\": \"#ece5d8\",\n        destructive: \"#b54a35\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#4a4039\",\n        input: \"#4a4039\",\n        ring: \"#c0a080\",\n        \"chart-1\": \"#c0a080\",\n        \"chart-2\": \"#b3906f\",\n        \"chart-3\": \"#a67c52\",\n        \"chart-4\": \"#8d6e4c\",\n        \"chart-5\": \"#735a3a\",\n        radius: \"0.25rem\",\n        sidebar: \"#2d2621\",\n        \"sidebar-foreground\": \"#ece5d8\",\n        \"sidebar-primary\": \"#c0a080\",\n        \"sidebar-primary-foreground\": \"#2d2621\",\n        \"sidebar-accent\": \"#59493e\",\n        \"sidebar-accent-foreground\": \"#ece5d8\",\n        \"sidebar-border\": \"#4a4039\",\n        \"sidebar-ring\": \"#c0a080\",\n      },\n    },\n  },\n  \"sunset-horizon\": {\n    label: \"Sunset Horizon\",\n    styles: {\n      light: {\n        background: \"#fff9f5\",\n        foreground: \"#3d3436\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#3d3436\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#3d3436\",\n        primary: \"#ff7e5f\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#ffedea\",\n        \"secondary-foreground\": \"#b35340\",\n        muted: \"#fff0eb\",\n        \"muted-foreground\": \"#78716C\",\n        accent: \"#feb47b\",\n        \"accent-foreground\": \"#3d3436\",\n        destructive: \"#e63946\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#ffe0d6\",\n        input: \"#ffe0d6\",\n        ring: \"#ff7e5f\",\n        \"chart-1\": \"#ff7e5f\",\n        \"chart-2\": \"#feb47b\",\n        \"chart-3\": \"#ffcaa7\",\n        \"chart-4\": \"#ffad8f\",\n        \"chart-5\": \"#ce6a57\",\n        radius: \"0.625rem\",\n        sidebar: \"#fff0eb\",\n        \"sidebar-foreground\": \"#3d3436\",\n        \"sidebar-primary\": \"#ff7e5f\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#feb47b\",\n        \"sidebar-accent-foreground\": \"#3d3436\",\n        \"sidebar-border\": \"#ffe0d6\",\n        \"sidebar-ring\": \"#ff7e5f\",\n        \"font-sans\": \"Montserrat, sans-serif\",\n        \"font-serif\": \"Merriweather, serif\",\n        \"font-mono\": \"Ubuntu Mono, monospace\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.09\",\n        \"shadow-blur\": \"12px\",\n        \"shadow-spread\": \"-3px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"6px\",\n      },\n      dark: {\n        background: \"#2a2024\",\n        foreground: \"#f2e9e4\",\n        card: \"#392f35\",\n        \"card-foreground\": \"#f2e9e4\",\n        popover: \"#392f35\",\n        \"popover-foreground\": \"#f2e9e4\",\n        primary: \"#ff7e5f\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#463a41\",\n        \"secondary-foreground\": \"#f2e9e4\",\n        muted: \"#30272c\",\n        \"muted-foreground\": \"#d7c6bc\",\n        accent: \"#feb47b\",\n        \"accent-foreground\": \"#2a2024\",\n        destructive: \"#e63946\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#463a41\",\n        input: \"#463a41\",\n        ring: \"#ff7e5f\",\n        \"chart-1\": \"#ff7e5f\",\n        \"chart-2\": \"#feb47b\",\n        \"chart-3\": \"#ffcaa7\",\n        \"chart-4\": \"#ffad8f\",\n        \"chart-5\": \"#ce6a57\",\n        radius: \"0.625rem\",\n        sidebar: \"#2a2024\",\n        \"sidebar-foreground\": \"#f2e9e4\",\n        \"sidebar-primary\": \"#ff7e5f\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#feb47b\",\n        \"sidebar-accent-foreground\": \"#2a2024\",\n        \"sidebar-border\": \"#463a41\",\n        \"sidebar-ring\": \"#ff7e5f\",\n      },\n    },\n  },\n\n  \"starry-night\": {\n    label: \"Starry Night\",\n    createdAt: \"2025-04-16\",\n    styles: {\n      light: {\n        background: \"#f5f7fa\",\n        foreground: \"#1a2238\",\n        card: \"#e3eaf2\",\n        \"card-foreground\": \"#1a2238\",\n        popover: \"#fffbe6\",\n        \"popover-foreground\": \"#1a2238\",\n        primary: \"#3a5ba0\",\n        \"primary-foreground\": \"#fffbe6\",\n        secondary: \"#f7c873\",\n        \"secondary-foreground\": \"#1a2238\",\n        muted: \"#e5e5df\",\n        \"muted-foreground\": \"#3a5ba0\",\n        accent: \"#6ea3c1\",\n        \"accent-foreground\": \"#fffbe6\",\n        destructive: \"#2d1e2f\",\n        \"destructive-foreground\": \"#fffbe6\",\n        border: \"#b0b8c1\",\n        input: \"#6ea3c1\",\n        ring: \"#f7c873\",\n        \"chart-1\": \"#3a5ba0\",\n        \"chart-2\": \"#f7c873\",\n        \"chart-3\": \"#6ea3c1\",\n        \"chart-4\": \"#b0b8c1\",\n        \"chart-5\": \"#2d1e2f\",\n        sidebar: \"#e3eaf2\",\n        \"sidebar-foreground\": \"#1a2238\",\n        \"sidebar-primary\": \"#3a5ba0\",\n        \"sidebar-primary-foreground\": \"#fffbe6\",\n        \"sidebar-accent\": \"#f7c873\",\n        \"sidebar-accent-foreground\": \"#1a2238\",\n        \"sidebar-border\": \"#b0b8c1\",\n        \"sidebar-ring\": \"#f7c873\",\n        \"font-sans\": \"Libre Baskerville, serif\",\n        radius: \"0.5rem\",\n      },\n      dark: {\n        background: \"#181a24\",\n        foreground: \"#e6eaf3\",\n        card: \"#23243a\",\n        \"card-foreground\": \"#e6eaf3\",\n        popover: \"#23243a\",\n        \"popover-foreground\": \"#ffe066\",\n        primary: \"#3a5ba0\",\n        \"primary-foreground\": \"#ffe066\",\n        secondary: \"#ffe066\",\n        \"secondary-foreground\": \"#23243a\",\n        muted: \"#1d1e2f\",\n        \"muted-foreground\": \"#7a88a1\",\n        accent: \"#bccdf0\",\n        \"accent-foreground\": \"#181a24\",\n        destructive: \"#a04a6c\",\n        \"destructive-foreground\": \"#ffe066\",\n        border: \"#2d2e3e\",\n        input: \"#3a5ba0\",\n        ring: \"#ffe066\",\n        \"chart-1\": \"#3a5ba0\",\n        \"chart-2\": \"#ffe066\",\n        \"chart-3\": \"#6ea3c1\",\n        \"chart-4\": \"#7a88a1\",\n        \"chart-5\": \"#a04a6c\",\n        sidebar: \"#23243a\",\n        \"sidebar-foreground\": \"#e6eaf3\",\n        \"sidebar-primary\": \"#3a5ba0\",\n        \"sidebar-primary-foreground\": \"#ffe066\",\n        \"sidebar-accent\": \"#ffe066\",\n        \"sidebar-accent-foreground\": \"#23243a\",\n        \"sidebar-border\": \"#2d2e3e\",\n        \"sidebar-ring\": \"#ffe066\",\n        radius: \"0.5rem\",\n      },\n    },\n  },\n\n  claude: {\n    label: \"Claude\",\n    styles: {\n      light: {\n        background: \"#faf9f5\",\n        foreground: \"#3d3929\",\n        card: \"#faf9f5\",\n        \"card-foreground\": \"#141413\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#28261b\",\n        primary: \"#c96442\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#e9e6dc\",\n        \"secondary-foreground\": \"#535146\",\n        muted: \"#ede9de\",\n        \"muted-foreground\": \"#83827d\",\n        accent: \"#e9e6dc\",\n        \"accent-foreground\": \"#28261b\",\n        destructive: \"#141413\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#dad9d4\",\n        input: \"#b4b2a7\",\n        ring: \"#c96442\",\n        \"chart-1\": \"#b05730\",\n        \"chart-2\": \"#9c87f5\",\n        \"chart-3\": \"#ded8c4\",\n        \"chart-4\": \"#dbd3f0\",\n        \"chart-5\": \"#b4552d\",\n        sidebar: \"#f5f4ee\",\n        \"sidebar-foreground\": \"#3d3d3a\",\n        \"sidebar-primary\": \"#c96442\",\n        \"sidebar-primary-foreground\": \"#fbfbfb\",\n        \"sidebar-accent\": \"#e9e6dc\",\n        \"sidebar-accent-foreground\": \"#343434\",\n        \"sidebar-border\": \"#ebebeb\",\n        \"sidebar-ring\": \"#b5b5b5\",\n        radius: \"0.5rem\",\n      },\n      dark: {\n        background: \"#262624\",\n        foreground: \"#c3c0b6\",\n        card: \"#262624\",\n        \"card-foreground\": \"#faf9f5\",\n        popover: \"#30302e\",\n        \"popover-foreground\": \"#e5e5e2\",\n        primary: \"#d97757\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#faf9f5\",\n        \"secondary-foreground\": \"#30302e\",\n        muted: \"#1b1b19\",\n        \"muted-foreground\": \"#b7b5a9\",\n        accent: \"#1a1915\",\n        \"accent-foreground\": \"#f5f4ee\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#3e3e38\",\n        input: \"#52514a\",\n        ring: \"#d97757\",\n        \"chart-1\": \"#b05730\",\n        \"chart-2\": \"#9c87f5\",\n        \"chart-3\": \"#1a1915\",\n        \"chart-4\": \"#2f2b48\",\n        \"chart-5\": \"#b4552d\",\n        sidebar: \"#1f1e1d\",\n        \"sidebar-foreground\": \"#c3c0b6\",\n        \"sidebar-primary\": \"#343434\",\n        \"sidebar-primary-foreground\": \"#fbfbfb\",\n        \"sidebar-accent\": \"#0f0f0e\",\n        \"sidebar-accent-foreground\": \"#c3c0b6\",\n        \"sidebar-border\": \"#ebebeb\",\n        \"sidebar-ring\": \"#b5b5b5\",\n      },\n    },\n  },\n\n  vercel: {\n    label: \"Vercel\",\n    createdAt: \"2025-04-13\",\n    styles: {\n      light: {\n        background: \"oklch(0.99 0 0)\",\n        foreground: \"oklch(0 0 0)\",\n        card: \"oklch(1.00 0 0)\",\n        \"card-foreground\": \"oklch(0 0 0)\",\n        popover: \"oklch(0.99 0 0)\",\n        \"popover-foreground\": \"oklch(0 0 0)\",\n        primary: \"oklch(0 0 0)\",\n        \"primary-foreground\": \"oklch(1.00 0 0)\",\n        secondary: \"oklch(0.94 0 0)\",\n        \"secondary-foreground\": \"oklch(0 0 0)\",\n        muted: \"oklch(0.97 0 0)\",\n        \"muted-foreground\": \"oklch(0.44 0 0)\",\n        accent: \"oklch(0.94 0 0)\",\n        \"accent-foreground\": \"oklch(0 0 0)\",\n        destructive: \"oklch(0.63 0.19 23.03)\",\n        \"destructive-foreground\": \"oklch(1.00 0 0)\",\n        border: \"oklch(0.92 0 0)\",\n        input: \"oklch(0.94 0 0)\",\n        ring: \"oklch(0 0 0)\",\n        \"chart-1\": \"oklch(0.81 0.17 75.35)\",\n        \"chart-2\": \"oklch(0.55 0.22 264.53)\",\n        \"chart-3\": \"oklch(0.72 0 0)\",\n        \"chart-4\": \"oklch(0.92 0 0)\",\n        \"chart-5\": \"oklch(0.56 0 0)\",\n        sidebar: \"oklch(0.99 0 0)\",\n        \"sidebar-foreground\": \"oklch(0 0 0)\",\n        \"sidebar-primary\": \"oklch(0 0 0)\",\n        \"sidebar-primary-foreground\": \"oklch(1.00 0 0)\",\n        \"sidebar-accent\": \"oklch(0.94 0 0)\",\n        \"sidebar-accent-foreground\": \"oklch(0 0 0)\",\n        \"sidebar-border\": \"oklch(0.94 0 0)\",\n        \"sidebar-ring\": \"oklch(0 0 0)\",\n        \"font-sans\": \"Geist, sans-serif\",\n        \"font-serif\": \"Georgia, serif\",\n        \"font-mono\": \"Geist Mono, monospace\",\n        radius: \"0.5rem\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0.18\",\n        \"shadow-blur\": \"2px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"1px\",\n      },\n\n      dark: {\n        background: \"oklch(0 0 0)\",\n        foreground: \"oklch(1.00 0 0)\",\n        card: \"oklch(0.14 0 0)\",\n        \"card-foreground\": \"oklch(1.00 0 0)\",\n        popover: \"oklch(0.18 0 0)\",\n        \"popover-foreground\": \"oklch(1.00 0 0)\",\n        primary: \"oklch(1.00 0 0)\",\n        \"primary-foreground\": \"oklch(0 0 0)\",\n        secondary: \"oklch(0.25 0 0)\",\n        \"secondary-foreground\": \"oklch(1.00 0 0)\",\n        muted: \"oklch(0.23 0 0)\",\n        \"muted-foreground\": \"oklch(0.72 0 0)\",\n        accent: \"oklch(0.32 0 0)\",\n        \"accent-foreground\": \"oklch(1.00 0 0)\",\n        destructive: \"oklch(0.69 0.20 23.91)\",\n        \"destructive-foreground\": \"oklch(0 0 0)\",\n        border: \"oklch(0.26 0 0)\",\n        input: \"oklch(0.32 0 0)\",\n        ring: \"oklch(0.72 0 0)\",\n        \"chart-1\": \"oklch(0.81 0.17 75.35)\",\n        \"chart-2\": \"oklch(0.58 0.21 260.84)\",\n        \"chart-3\": \"oklch(0.56 0 0)\",\n        \"chart-4\": \"oklch(0.44 0 0)\",\n        \"chart-5\": \"oklch(0.92 0 0)\",\n        sidebar: \"oklch(0.18 0 0)\",\n        \"sidebar-foreground\": \"oklch(1.00 0 0)\",\n        \"sidebar-primary\": \"oklch(1.00 0 0)\",\n        \"sidebar-primary-foreground\": \"oklch(0 0 0)\",\n        \"sidebar-accent\": \"oklch(0.32 0 0)\",\n        \"sidebar-accent-foreground\": \"oklch(1.00 0 0)\",\n        \"sidebar-border\": \"oklch(0.32 0 0)\",\n        \"sidebar-ring\": \"oklch(0.72 0 0)\",\n        \"font-sans\": \"Geist, sans-serif\",\n        \"font-serif\": \"Georgia, serif\",\n        \"font-mono\": \"Geist Mono, monospace\",\n      },\n    },\n  },\n\n  darkmatter: {\n    label: \"Darkmatter\",\n    createdAt: \"2025-08-23\",\n    styles: {\n      light: {\n        background: \"#ffffff\",\n        foreground: \"#111827\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#111827\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#111827\",\n        primary: \"#d87943\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#527575\",\n        \"secondary-foreground\": \"#ffffff\",\n        muted: \"#f3f4f6\",\n        \"muted-foreground\": \"#6b7280\",\n        accent: \"#eeeeee\",\n        \"accent-foreground\": \"#111827\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#fafafa\",\n        border: \"#e5e7eb\",\n        input: \"#e5e7eb\",\n        ring: \"#d87943\",\n        \"chart-1\": \"#5f8787\",\n        \"chart-2\": \"#e78a53\",\n        \"chart-3\": \"#fbcb97\",\n        \"chart-4\": \"#888888\",\n        \"chart-5\": \"#999999\",\n        sidebar: \"#f3f4f6\",\n        \"sidebar-foreground\": \"#111827\",\n        \"sidebar-primary\": \"#d87943\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#ffffff\",\n        \"sidebar-accent-foreground\": \"#111827\",\n        \"sidebar-border\": \"#e5e7eb\",\n        \"sidebar-ring\": \"#d87943\",\n        \"font-sans\": \"Geist Mono, ui-monospace, monospace\",\n        \"font-serif\": \"serif\",\n        \"font-mono\": \"JetBrains Mono, monospace\",\n        radius: \"0.75rem\",\n        \"shadow-color\": \"#000000\",\n        \"shadow-opacity\": \"0.05\",\n        \"shadow-blur\": \"4px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"1px\",\n        \"letter-spacing\": \"0rem\",\n        spacing: \"0.25rem\",\n      },\n      dark: {\n        background: \"#121113\",\n        foreground: \"#c1c1c1\",\n        card: \"#121212\",\n        \"card-foreground\": \"#c1c1c1\",\n        popover: \"#121113\",\n        \"popover-foreground\": \"#c1c1c1\",\n        primary: \"#e78a53\",\n        \"primary-foreground\": \"#121113\",\n        secondary: \"#5f8787\",\n        \"secondary-foreground\": \"#121113\",\n        muted: \"#222222\",\n        \"muted-foreground\": \"#888888\",\n        accent: \"#333333\",\n        \"accent-foreground\": \"#c1c1c1\",\n        destructive: \"#5f8787\",\n        \"destructive-foreground\": \"#121113\",\n        border: \"#222222\",\n        input: \"#222222\",\n        ring: \"#e78a53\",\n        \"chart-1\": \"#5f8787\",\n        \"chart-2\": \"#e78a53\",\n        \"chart-3\": \"#fbcb97\",\n        \"chart-4\": \"#888888\",\n        \"chart-5\": \"#999999\",\n        sidebar: \"#121212\",\n        \"sidebar-foreground\": \"#c1c1c1\",\n        \"sidebar-primary\": \"#e78a53\",\n        \"sidebar-primary-foreground\": \"#121113\",\n        \"sidebar-accent\": \"#333333\",\n        \"sidebar-accent-foreground\": \"#c1c1c1\",\n        \"sidebar-border\": \"#222222\",\n        \"sidebar-ring\": \"#e78a53\",\n        \"shadow-color\": \"#000000\",\n      },\n    },\n  },\n\n  mono: {\n    label: \"Mono\",\n    createdAt: \"2025-04-20\",\n    styles: {\n      light: {\n        background: \"#ffffff\",\n        foreground: \"#0a0a0a\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#0a0a0a\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#0a0a0a\",\n        primary: \"#737373\",\n        \"primary-foreground\": \"#fafafa\",\n        secondary: \"#f5f5f5\",\n        \"secondary-foreground\": \"#171717\",\n        muted: \"#f5f5f5\",\n        \"muted-foreground\": \"#717171\",\n        accent: \"#f5f5f5\",\n        \"accent-foreground\": \"#171717\",\n        destructive: \"#e7000b\",\n        \"destructive-foreground\": \"#f5f5f5\",\n        border: \"#e5e5e5\",\n        input: \"#e5e5e5\",\n        ring: \"#a1a1a1\",\n        \"chart-1\": \"#737373\",\n        \"chart-2\": \"#737373\",\n        \"chart-3\": \"#737373\",\n        \"chart-4\": \"#737373\",\n        \"chart-5\": \"#737373\",\n        sidebar: \"#fafafa\",\n        \"sidebar-foreground\": \"#0a0a0a\",\n        \"sidebar-primary\": \"#171717\",\n        \"sidebar-primary-foreground\": \"#fafafa\",\n        \"sidebar-accent\": \"#f5f5f5\",\n        \"sidebar-accent-foreground\": \"#171717\",\n        \"sidebar-border\": \"#e5e5e5\",\n        \"sidebar-ring\": \"#a1a1a1\",\n        \"font-sans\": \"Geist Mono, monospace\",\n        \"font-serif\": \"Geist Mono, monospace\",\n        \"font-mono\": \"Geist Mono, monospace\",\n        radius: \"0rem\",\n        \"shadow-color\": \"hsl(0 0% 0%)\",\n        \"shadow-opacity\": \"0\",\n        \"shadow-blur\": \"0px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"1px\",\n      },\n      dark: {\n        background: \"#0a0a0a\",\n        foreground: \"#fafafa\",\n        card: \"#191919\",\n        \"card-foreground\": \"#fafafa\",\n        popover: \"#262626\",\n        \"popover-foreground\": \"#fafafa\",\n        primary: \"#737373\",\n        \"primary-foreground\": \"#fafafa\",\n        secondary: \"#262626\",\n        \"secondary-foreground\": \"#fafafa\",\n        muted: \"#262626\",\n        \"muted-foreground\": \"#a1a1a1\",\n        accent: \"#404040\",\n        \"accent-foreground\": \"#fafafa\",\n        destructive: \"#ff6467\",\n        \"destructive-foreground\": \"#262626\",\n        border: \"#383838\",\n        input: \"#525252\",\n        ring: \"#737373\",\n        \"chart-1\": \"#737373\",\n        \"chart-2\": \"#737373\",\n        \"chart-3\": \"#737373\",\n        \"chart-4\": \"#737373\",\n        \"chart-5\": \"#737373\",\n        sidebar: \"#171717\",\n        \"sidebar-foreground\": \"#fafafa\",\n        \"sidebar-primary\": \"#fafafa\",\n        \"sidebar-primary-foreground\": \"#171717\",\n        \"sidebar-accent\": \"#262626\",\n        \"sidebar-accent-foreground\": \"#fafafa\",\n        \"sidebar-border\": \"#ffffff\",\n        \"sidebar-ring\": \"#525252\",\n      },\n    },\n  },\n  \"soft-pop\": {\n    label: \"Soft Pop\",\n    createdAt: \"2025-07-08\",\n    styles: {\n      light: {\n        background: \"#f7f9f3\",\n        foreground: \"#000000\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#000000\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#000000\",\n        primary: \"#4f46e5\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#14b8a6\",\n        \"secondary-foreground\": \"#ffffff\",\n        muted: \"#f0f0f0\",\n        \"muted-foreground\": \"#333333\",\n        accent: \"#f59e0b\",\n        \"accent-foreground\": \"#000000\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#000000\",\n        input: \"#737373\",\n        ring: \"#a5b4fc\",\n        \"chart-1\": \"#4f46e5\",\n        \"chart-2\": \"#14b8a6\",\n        \"chart-3\": \"#f59e0b\",\n        \"chart-4\": \"#ec4899\",\n        \"chart-5\": \"#22c55e\",\n        sidebar: \"#f7f9f3\",\n        \"sidebar-foreground\": \"#000000\",\n        \"sidebar-primary\": \"#4f46e5\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#f59e0b\",\n        \"sidebar-accent-foreground\": \"#000000\",\n        \"sidebar-border\": \"#000000\",\n        \"sidebar-ring\": \"#a5b4fc\",\n        \"font-sans\": \"DM Sans, sans-serif\",\n        \"font-serif\": \"DM Sans, sans-serif\",\n        \"font-mono\": \"Space Mono, monospace\",\n        radius: \"1rem\",\n        \"shadow-color\": \"#1a1a1a\",\n        \"shadow-opacity\": \"0.05\",\n        \"shadow-blur\": \"0px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"0px\",\n        \"letter-spacing\": \"normal\",\n        spacing: \"0.25rem\",\n      },\n      dark: {\n        background: \"#000000\",\n        foreground: \"#ffffff\",\n        card: \"#1a212b\",\n        \"card-foreground\": \"#ffffff\",\n        popover: \"#1a212b\",\n        \"popover-foreground\": \"#ffffff\",\n        primary: \"#818cf8\",\n        \"primary-foreground\": \"#000000\",\n        secondary: \"#2dd4bf\",\n        \"secondary-foreground\": \"#000000\",\n        muted: \"#333333\",\n        \"muted-foreground\": \"#cccccc\",\n        accent: \"#fcd34d\",\n        \"accent-foreground\": \"#000000\",\n        destructive: \"#f87171\",\n        \"destructive-foreground\": \"#000000\",\n        border: \"#545454\",\n        input: \"#ffffff\",\n        ring: \"#818cf8\",\n        \"chart-1\": \"#818cf8\",\n        \"chart-2\": \"#2dd4bf\",\n        \"chart-3\": \"#fcd34d\",\n        \"chart-4\": \"#f472b6\",\n        \"chart-5\": \"#4ade80\",\n        sidebar: \"#000000\",\n        \"sidebar-foreground\": \"#ffffff\",\n        \"sidebar-primary\": \"#818cf8\",\n        \"sidebar-primary-foreground\": \"#000000\",\n        \"sidebar-accent\": \"#fcd34d\",\n        \"sidebar-accent-foreground\": \"#000000\",\n        \"sidebar-border\": \"#ffffff\",\n        \"sidebar-ring\": \"#818cf8\",\n        \"font-sans\": \"DM Sans, sans-serif\",\n        \"font-serif\": \"DM Sans, sans-serif\",\n        \"font-mono\": \"Space Mono, monospace\",\n        radius: \"1rem\",\n        \"shadow-color\": \"#1a1a1a\",\n        \"shadow-opacity\": \"0.05\",\n        \"shadow-blur\": \"0px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"0px\",\n        \"letter-spacing\": \"normal\",\n        spacing: \"0.25rem\",\n      },\n    },\n  },\n  \"sage-garden\": {\n    label: \"Sage Garden\",\n    createdAt: \"2025-11-08\",\n    styles: {\n      light: {\n        background: \"#f8f7f4\",\n        foreground: \"#1a1f2e\",\n        card: \"#ffffff\",\n        \"card-foreground\": \"#1a1f2e\",\n        popover: \"#ffffff\",\n        \"popover-foreground\": \"#1a1f2e\",\n        primary: \"#7c9082\",\n        \"primary-foreground\": \"#ffffff\",\n        secondary: \"#ced4bf\",\n        \"secondary-foreground\": \"#1a1f2e\",\n        muted: \"#e8e6e1\",\n        \"muted-foreground\": \"#6b7280\",\n        accent: \"#bfc9bb\",\n        \"accent-foreground\": \"#1a1f2e\",\n        destructive: \"#c73e3a\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#e8e6e1\",\n        input: \"#ffffff\",\n        ring: \"#7c9082\",\n        \"chart-1\": \"#7c9082\",\n        \"chart-2\": \"#a0aa88\",\n        \"chart-3\": \"#8b9d83\",\n        \"chart-4\": \"#6b7280\",\n        \"chart-5\": \"#e8e6e1\",\n        sidebar: \"#fafaf8\",\n        \"sidebar-foreground\": \"#1a1f2e\",\n        \"sidebar-primary\": \"#7c9082\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#e8e6e1\",\n        \"sidebar-accent-foreground\": \"#1a1f2e\",\n        \"sidebar-border\": \"#e8e6e1\",\n        \"sidebar-ring\": \"#7c9082\",\n        \"font-sans\": \"Antic, ui-sans-serif, sans-serif, system-ui\",\n        \"font-serif\": \"Signifier, Georgia, serif\",\n        \"font-mono\": \"JetBrains Mono, Courier New, monospace\",\n        radius: \"0.35rem\",\n        \"shadow-color\": \"#1a1f2e\",\n        \"shadow-opacity\": \"0.04\",\n        \"shadow-blur\": \"2px\",\n        \"shadow-spread\": \"0px\",\n        \"shadow-offset-x\": \"0px\",\n        \"shadow-offset-y\": \"1px\",\n        \"letter-spacing\": \"0em\",\n        spacing: \"0.23rem\",\n      },\n      dark: {\n        background: \"#0a0a0a\",\n        foreground: \"#f5f5f5\",\n        card: \"#121212\",\n        \"card-foreground\": \"#f5f5f5\",\n        popover: \"#121212\",\n        \"popover-foreground\": \"#f5f5f5\",\n        primary: \"#7c9082\",\n        \"primary-foreground\": \"#000000\",\n        secondary: \"#1a1a1a\",\n        \"secondary-foreground\": \"#f5f5f5\",\n        muted: \"#1a1a1a\",\n        \"muted-foreground\": \"#a0a0a0\",\n        accent: \"#36443a\",\n        \"accent-foreground\": \"#f5f5f5\",\n        destructive: \"#ef4444\",\n        \"destructive-foreground\": \"#ffffff\",\n        border: \"#2a2a2a\",\n        input: \"#121212\",\n        ring: \"#7c9082\",\n        \"chart-1\": \"#7c9082\",\n        \"chart-2\": \"#a0aa88\",\n        \"chart-3\": \"#8b9d83\",\n        \"chart-4\": \"#6b7280\",\n        \"chart-5\": \"#5a6b5e\",\n        sidebar: \"#0f0f0f\",\n        \"sidebar-foreground\": \"#f5f5f5\",\n        \"sidebar-primary\": \"#7c9082\",\n        \"sidebar-primary-foreground\": \"#ffffff\",\n        \"sidebar-accent\": \"#1a1a1a\",\n        \"sidebar-accent-foreground\": \"#f5f5f5\",\n        \"sidebar-border\": \"#2a2a2a\",\n        \"sidebar-ring\": \"#7c9082\",\n        \"shadow-color\": \"#000000\",\n      },\n    },\n  },\n};\n"
  },
  {
    "path": "utils/theme-style-generator.ts",
    "content": "import { ThemeEditorState } from \"@/types/editor\";\nimport { colorFormatter } from \"./color-converter\";\nimport { ColorFormat } from \"../types\";\nimport { getShadowMap } from \"./shadows\";\nimport { defaultLightThemeStyles } from \"@/config/theme\";\nimport { ThemeStyles } from \"@/types/theme\";\nimport { extractFontFamily } from \"./fonts\";\n\ntype ThemeMode = \"light\" | \"dark\";\n\nconst generateColorVariables = (\n  themeStyles: ThemeStyles,\n  mode: ThemeMode,\n  formatColor: (color: string) => string\n): string => {\n  const styles = themeStyles[mode];\n  return `\n  --background: ${formatColor(styles.background)};\n  --foreground: ${formatColor(styles.foreground)};\n  --card: ${formatColor(styles.card)};\n  --card-foreground: ${formatColor(styles[\"card-foreground\"])};\n  --popover: ${formatColor(styles.popover)};\n  --popover-foreground: ${formatColor(styles[\"popover-foreground\"])};\n  --primary: ${formatColor(styles.primary)};\n  --primary-foreground: ${formatColor(styles[\"primary-foreground\"])};\n  --secondary: ${formatColor(styles.secondary)};\n  --secondary-foreground: ${formatColor(styles[\"secondary-foreground\"])};\n  --muted: ${formatColor(styles.muted)};\n  --muted-foreground: ${formatColor(styles[\"muted-foreground\"])};\n  --accent: ${formatColor(styles.accent)};\n  --accent-foreground: ${formatColor(styles[\"accent-foreground\"])};\n  --destructive: ${formatColor(styles.destructive)};\n  --destructive-foreground: ${formatColor(styles[\"destructive-foreground\"])};\n  --border: ${formatColor(styles.border)};\n  --input: ${formatColor(styles.input)};\n  --ring: ${formatColor(styles.ring)};\n  --chart-1: ${formatColor(styles[\"chart-1\"])};\n  --chart-2: ${formatColor(styles[\"chart-2\"])};\n  --chart-3: ${formatColor(styles[\"chart-3\"])};\n  --chart-4: ${formatColor(styles[\"chart-4\"])};\n  --chart-5: ${formatColor(styles[\"chart-5\"])};\n  --sidebar: ${formatColor(styles.sidebar)};\n  --sidebar-foreground: ${formatColor(styles[\"sidebar-foreground\"])};\n  --sidebar-primary: ${formatColor(styles[\"sidebar-primary\"])};\n  --sidebar-primary-foreground: ${formatColor(styles[\"sidebar-primary-foreground\"])};\n  --sidebar-accent: ${formatColor(styles[\"sidebar-accent\"])};\n  --sidebar-accent-foreground: ${formatColor(styles[\"sidebar-accent-foreground\"])};\n  --sidebar-border: ${formatColor(styles[\"sidebar-border\"])};\n  --sidebar-ring: ${formatColor(styles[\"sidebar-ring\"])};`;\n};\n\nconst generateFontVariables = (themeStyles: ThemeStyles, mode: ThemeMode): string => {\n  const styles = themeStyles[mode];\n  return `\n  --font-sans: ${styles[\"font-sans\"]};\n  --font-serif: ${styles[\"font-serif\"]};\n  --font-mono: ${styles[\"font-mono\"]};`;\n};\n\nconst generateShadowVariables = (shadowMap: Record<string, string>): string => {\n  return `\n  --shadow-2xs: ${shadowMap[\"shadow-2xs\"]};\n  --shadow-xs: ${shadowMap[\"shadow-xs\"]};\n  --shadow-sm: ${shadowMap[\"shadow-sm\"]};\n  --shadow: ${shadowMap[\"shadow\"]};\n  --shadow-md: ${shadowMap[\"shadow-md\"]};\n  --shadow-lg: ${shadowMap[\"shadow-lg\"]};\n  --shadow-xl: ${shadowMap[\"shadow-xl\"]};\n  --shadow-2xl: ${shadowMap[\"shadow-2xl\"]};`;\n};\n\nconst generateRawShadowVariables = (themeStyles: ThemeStyles, mode: ThemeMode): string => {\n  const styles = themeStyles[mode];\n  return `\n  --shadow-x: ${styles[\"shadow-offset-x\"]};\n  --shadow-y: ${styles[\"shadow-offset-y\"]};\n  --shadow-blur: ${styles[\"shadow-blur\"]};\n  --shadow-spread: ${styles[\"shadow-spread\"]};\n  --shadow-opacity: ${styles[\"shadow-opacity\"]};\n  --shadow-color: ${styles[\"shadow-color\"]};`;\n};\n\nconst generateTrackingVariables = (themeStyles: ThemeStyles): string => {\n  const styles = themeStyles[\"light\"];\n  if (styles[\"letter-spacing\"] === \"0em\") {\n    return \"\";\n  }\n  return `\n\n  --tracking-tighter: calc(var(--tracking-normal) - 0.05em);\n  --tracking-tight: calc(var(--tracking-normal) - 0.025em);\n  --tracking-normal: var(--tracking-normal);\n  --tracking-wide: calc(var(--tracking-normal) + 0.025em);\n  --tracking-wider: calc(var(--tracking-normal) + 0.05em);\n  --tracking-widest: calc(var(--tracking-normal) + 0.1em);`;\n};\n\nconst generateThemeVariables = (\n  themeStyles: ThemeStyles,\n  mode: ThemeMode,\n  formatColor: (color: string) => string\n): string => {\n  const selector = mode === \"dark\" ? \".dark\" : \":root\";\n  const colorVars = generateColorVariables(themeStyles, mode, formatColor);\n  const fontVars = generateFontVariables(themeStyles, mode);\n  const radiusVar = `\\n  --radius: ${themeStyles[mode].radius};`;\n  const shadowVars = generateShadowVariables(\n    getShadowMap({ styles: themeStyles, currentMode: mode })\n  );\n  const rawShadowVars = generateRawShadowVariables(themeStyles, mode);\n  const spacingVar =\n    mode === \"light\"\n      ? `\\n  --spacing: ${themeStyles[\"light\"].spacing ?? defaultLightThemeStyles.spacing};`\n      : \"\";\n\n  const trackingVars =\n    mode === \"light\"\n      ? `\\n  --tracking-normal: ${themeStyles[\"light\"][\"letter-spacing\"] ?? defaultLightThemeStyles[\"letter-spacing\"]};`\n      : \"\";\n\n  return (\n    selector +\n    \" {\" +\n    colorVars +\n    fontVars +\n    radiusVar +\n    rawShadowVars +\n    shadowVars +\n    trackingVars +\n    spacingVar +\n    \"\\n}\"\n  );\n};\n\nconst generateTailwindV4ThemeInline = (themeStyles: ThemeStyles): string => {\n  return `@theme inline {\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --color-chart-1: var(--chart-1);\n  --color-chart-2: var(--chart-2);\n  --color-chart-3: var(--chart-3);\n  --color-chart-4: var(--chart-4);\n  --color-chart-5: var(--chart-5);\n  --color-sidebar: var(--sidebar);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-ring: var(--sidebar-ring);\n\n  --font-sans: var(--font-sans);\n  --font-mono: var(--font-mono);\n  --font-serif: var(--font-serif);\n\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n\n  --shadow-2xs: var(--shadow-2xs);\n  --shadow-xs: var(--shadow-xs);\n  --shadow-sm: var(--shadow-sm);\n  --shadow: var(--shadow);\n  --shadow-md: var(--shadow-md);\n  --shadow-lg: var(--shadow-lg);\n  --shadow-xl: var(--shadow-xl);\n  --shadow-2xl: var(--shadow-2xl);${generateTrackingVariables(themeStyles)}\n}`;\n};\n\nconst generateTailwindV3Config = (\n  _themeStyles: ThemeStyles,\n  colorFormat: ColorFormat = \"hsl\"\n): string => {\n  const colorToken = (key: string) => {\n    return colorFormat === \"hsl\" ? `\"hsl(var(--${key}))\"` : `\"var(--${key})\"`;\n  };\n\n  return `/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  darkMode: [\"class\"],\n  theme: {\n    extend: {\n      colors: {\n        border: ${colorToken(\"border\")},\n        input: ${colorToken(\"input\")},\n        ring: ${colorToken(\"ring\")},\n        background: ${colorToken(\"background\")},\n        foreground: ${colorToken(\"foreground\")},\n        primary: {\n          DEFAULT: ${colorToken(\"primary\")},\n          foreground: ${colorToken(\"primary-foreground\")},\n        },\n        secondary: {\n          DEFAULT: ${colorToken(\"secondary\")},\n          foreground: ${colorToken(\"secondary-foreground\")},\n        },\n        destructive: {\n          DEFAULT: ${colorToken(\"destructive\")},\n          foreground: ${colorToken(\"destructive-foreground\")},\n        },\n        muted: {\n            DEFAULT: ${colorToken(\"muted\")},\n          foreground: ${colorToken(\"muted-foreground\")},\n        },\n        accent: {\n          DEFAULT: ${colorToken(\"accent\")},\n          foreground: ${colorToken(\"accent-foreground\")},\n        },\n        popover: {\n          DEFAULT: ${colorToken(\"popover\")},\n          foreground: ${colorToken(\"popover-foreground\")},\n        },\n        card: {\n          DEFAULT: ${colorToken(\"card\")},\n          foreground: ${colorToken(\"card-foreground\")},\n        },\n        sidebar: {\n          DEFAULT: ${colorToken(\"sidebar\")},\n          foreground: ${colorToken(\"sidebar-foreground\")},\n          primary: ${colorToken(\"sidebar-primary\")},\n          \"primary-foreground\": ${colorToken(\"sidebar-primary-foreground\")},\n          accent: ${colorToken(\"sidebar-accent\")},\n          \"accent-foreground\": ${colorToken(\"sidebar-accent-foreground\")},\n          border: ${colorToken(\"sidebar-border\")},\n          ring: ${colorToken(\"sidebar-ring\")},\n        },\n        chart: {\n          1: ${colorToken(\"chart-1\")},\n          2: ${colorToken(\"chart-2\")},\n          3: ${colorToken(\"chart-3\")},\n          4: ${colorToken(\"chart-4\")},\n          5: ${colorToken(\"chart-5\")},\n        },\n      },\n      borderRadius: {\n        xl: \"calc(var(--radius) + 4px)\",\n        lg: \"var(--radius)\",\n        md: \"calc(var(--radius) - 2px)\",\n        sm: \"calc(var(--radius) - 4px)\",\n      },\n      fontFamily: {\n        sans: [\"var(--font-sans)\"],\n        serif: [\"var(--font-serif)\"],\n        mono: [\"var(--font-mono)\"],\n      },\n    },\n  },\n}`;\n};\n\nconst indentBlock = (str: string): string =>\n  str\n    .split(\"\\n\")\n    .map((line) => (line.length > 0 ? `  ${line}` : line))\n    .join(\"\\n\");\n\nexport const generateThemeCode = (\n  themeEditorState: ThemeEditorState,\n  colorFormat: ColorFormat = \"hsl\",\n  tailwindVersion: \"3\" | \"4\" = \"3\"\n): string => {\n  if (\n    !themeEditorState ||\n    !(\"light\" in themeEditorState.styles) ||\n    !(\"dark\" in themeEditorState.styles)\n  ) {\n    throw new Error(\"Invalid theme styles: missing light or dark mode\");\n  }\n\n  const themeStyles = themeEditorState.styles as ThemeStyles;\n  const formatColor = (color: string) => colorFormatter(color, colorFormat, tailwindVersion);\n\n  const lightTheme = generateThemeVariables(themeStyles, \"light\", formatColor);\n  const darkTheme = generateThemeVariables(themeStyles, \"dark\", formatColor);\n\n  if (tailwindVersion === \"4\") {\n    const tailwindV4Theme = generateTailwindV4ThemeInline(themeStyles);\n\n    const bodyLetterSpacing =\n      themeStyles[\"light\"][\"letter-spacing\"] !== \"0em\"\n        ? \"\\n    letter-spacing: var(--tracking-normal);\"\n        : \"\";\n\n    return `@import \"tailwindcss\";\n\n@custom-variant dark (&:is(.dark *));\n\n${lightTheme}\n\n${darkTheme}\n\n${tailwindV4Theme}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;${bodyLetterSpacing}\n  }\n}`;\n  }\n\n  // Tailwind v3\n  return `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n${indentBlock(lightTheme)}\n\n${indentBlock(darkTheme)}\n}\n\n@layer base {\n  * {\n    @apply border-border;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}`;\n};\n\nexport const generateTailwindConfigCode = (\n  themeEditorState: ThemeEditorState,\n  colorFormat: ColorFormat = \"hsl\",\n  _tailwindVersion: \"3\" | \"4\" = \"3\"\n): string => {\n  if (\n    !themeEditorState ||\n    !(\"light\" in themeEditorState.styles) ||\n    !(\"dark\" in themeEditorState.styles)\n  ) {\n    throw new Error(\"Invalid theme styles: missing light or dark mode\");\n  }\n\n  const themeStyles = themeEditorState.styles as ThemeStyles;\n  return generateTailwindV3Config(themeStyles, colorFormat);\n};\n\nconst FONT_SLOTS = [\n  { key: \"font-sans\" as const, varName: \"fontSans\", cssVar: \"--font-sans\" },\n  { key: \"font-serif\" as const, varName: \"fontSerif\", cssVar: \"--font-serif\" },\n  { key: \"font-mono\" as const, varName: \"fontMono\", cssVar: \"--font-mono\" },\n];\n\nexport const generateLayoutCode = (themeEditorState: ThemeEditorState): string => {\n  if (\n    !themeEditorState ||\n    !(\"light\" in themeEditorState.styles) ||\n    !(\"dark\" in themeEditorState.styles)\n  ) {\n    throw new Error(\"Invalid theme styles: missing light or dark mode\");\n  }\n\n  const themeStyles = themeEditorState.styles as ThemeStyles;\n  const lightStyles = themeStyles.light;\n\n  const googleFonts: { importName: string; varName: string; cssVar: string }[] = [];\n\n  for (const slot of FONT_SLOTS) {\n    const fontValue = lightStyles[slot.key];\n    const fontFamily = extractFontFamily(fontValue);\n    if (fontFamily) {\n      googleFonts.push({\n        importName: fontFamily.replace(/ /g, \"_\"),\n        varName: slot.varName,\n        cssVar: slot.cssVar,\n      });\n    }\n  }\n\n  const comment = `// For adding custom fonts with other frameworks, see:\\n// https://tailwindcss.com/docs/font-family`;\n\n  if (googleFonts.length === 0) {\n    return `${comment}\nimport type { Metadata } from \"next\";\nimport \"./globals.css\";\n\nexport const metadata: Metadata = {\n  title: \"Create Next App\",\n  description: \"Generated by create next app\",\n};\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <html lang=\"en\">\n      <body className=\"antialiased\">\n        {children}\n      </body>\n    </html>\n  );\n}`;\n  }\n\n  const importNames = googleFonts.map((f) => f.importName).join(\", \");\n\n  const fontConfigs = googleFonts\n    .map(\n      (f) => `\nconst ${f.varName} = ${f.importName}({\n  subsets: [\"latin\"],\n  variable: \"${f.cssVar}\",\n});`\n    )\n    .join(\"\\n\");\n\n  const classNameParts = googleFonts.map((f) => `\\${${f.varName}.variable}`).join(\" \");\n\n  return `${comment}\nimport type { Metadata } from \"next\";\nimport { ${importNames} } from \"next/font/google\";\nimport \"./globals.css\";\n${fontConfigs}\n\nexport const metadata: Metadata = {\n  title: \"Create Next App\",\n  description: \"Generated by create next app\",\n};\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <html lang=\"en\">\n      <body className={\\`${classNameParts} antialiased\\`}>\n        {children}\n      </body>\n    </html>\n  );\n}`;\n};\n"
  },
  {
    "path": "utils/theme-styles.ts",
    "content": "import { defaultThemeState } from \"@/config/theme\";\nimport { ThemeStyles } from \"@/types/theme\";\n\nexport function mergeThemeStylesWithDefaults(themeStyles: ThemeStyles) {\n  const mergedStyles = {\n    ...defaultThemeState.styles,\n    light: { ...defaultThemeState.styles.light, ...themeStyles.light },\n    dark: { ...defaultThemeState.styles.dark, ...themeStyles.dark },\n  };\n  return mergedStyles;\n}\n"
  },
  {
    "path": "utils/try-catch.ts",
    "content": "export async function tryCatch<T, E = Error>(promise: T | Promise<T>) {\n  try {\n    const data = await promise;\n    return [null, data] as const;\n  } catch (error) {\n    return [error as E, null] as const;\n  }\n}\n"
  }
]