[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\nthanks_dev: # Replace with a single thanks.dev username\ncustom: [\"https://buy.stripe.com/9B6cN6eZq6N136ygPm0Jq02\"]\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "---\nname: \"feat: Add new awesome resource\"\nabout: \"Propose adding a new awesome resource related to shadcn/ui\"\nlabels:\n  - feature\n---\n\n## Submission Options\n\nYou can submit resources in two ways:\n\n1. **Via Website**: Use the [awesome-shadcn-ui.vercel.app](https://awesome-shadcn-ui.vercel.app/) submission form for a guided experience\n2. **Via GitHub PR**: Use this template to create a pull request directly\n\n## Describe the awesome resource you want to add\n\n**What is it?**  \n> Briefly explain what this resource is and why it's awesome...\n-\n\n## **Which section does it belong to?**  \n- [ ] Libs and Components  \n- [ ] Plugins and Extensions  \n- [ ] Colors and Customizations  \n- [ ] Animations  \n- [ ] Tools  \n- [ ] Websites and Portfolios Inspirations  \n- [ ] Platforms  \n- [ ] Ports  \n- [ ] Design System  \n- [ ] Boilerplates / Templates  \n\n**Additional details (optional)**  \n> Include screenshots, demos, or any other useful context...\n-\n\n## **Checklist**\n- [ ] I verified that the resource is listed in alphabetical order within its section.\n- [ ] I checked that the resource is not already listed.\n- [ ] I provided a clear and concise description of the resource.\n- [ ] I included a valid and working link to the resource.\n- [ ] I assigned the correct section to the resource.\n\n**Important Notes:**  \n1. If you are introducing a new section, you must also add it to the `README.md` file and update the table of contents accordingly.  \n2. This repository focuses on open-source and freely accessible projects. Paid or fully commercial resources will not be accepted.  \n\n## Required Table Format\n\n**Format your entry exactly like this:**\n\n```markdown\n| Name | Description | [Link](Your_Link_Here) |\n```\n\n**Table Structure:**\n- **Name**: The resource name (keep it concise)\n- **Description**: Brief description of what the resource does\n- **Link**: Working link to the resource\n- **Date**: Leave empty - our system adds this automatically when merged\n\n**Formatting Rules:**\n- Each entry must be on a single line\n- Use proper markdown table syntax with `|` separators\n- Don't add dates manually - the system handles this\n- Ensure your link is working and accessible\n\nThank you for contributing to the awesome-shadcn/ui repository!"
  },
  {
    "path": ".github/workflows/add-dates.yml",
    "content": "name: Add Dates to New Resources\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - 'README.md'\n\njobs:\n  add-dates:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout main\n        uses: actions/checkout@v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n\n      - name: Add dates to new resources\n        run: node scripts/add-dates.js\n\n      - name: Commit changes (if any)\n        run: |\n          git config --local user.email \"github-actions[bot]@users.noreply.github.com\"\n          git config --local user.name \"github-actions[bot]\"\n          git add README.md\n          git diff --quiet && git diff --staged --quiet || git commit -m \"Add dates to new resources\"\n          git push\n"
  },
  {
    "path": ".github/workflows/format-readme.yml",
    "content": "name: Format README\n\non:\n  pull_request:\n    paths:\n      - \"README.md\"\n  workflow_dispatch:\n\njobs:\n  format-readme:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout PR\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: \"20\"\n\n      - name: Format README\n        run: node scripts/format-readme.js\n\n      - name: Commit changes (if any)\n        run: |\n          git config --local user.email \"github-actions[bot]@users.noreply.github.com\"\n          git config --local user.name \"github-actions[bot]\"\n          git add README.md\n          git diff --quiet && git diff --staged --quiet || git commit -m \"Format README.md\"\n          # Empurra para a branch do PR\n          git push origin HEAD:${{ github.head_ref }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n\n.open-next/\n\n.agents\n.claude\n.mcp.json"
  },
  {
    "path": "DEVELOPMENT.md",
    "content": "# Development Guide\n\nThis guide covers the development setup, architecture, and configuration of the awesome-shadcn-ui website.\n\n## Quick Start\n\n```bash\n# Install dependencies\nnpm install\n\n# Start development server\nnpm run dev\n\n# Build for production\nnpm run build\n```\n\n## Project Structure\n\n```\nsrc/\n├── app/                    # Next.js App Router\n│   ├── api/               # API routes\n│   │   └── github/        # GitHub OAuth & PR submission\n│   ├── globals.css        # Global styles\n│   └── layout.tsx         # Root layout\n├── components/            # React components\n│   ├── ui/               # shadcn/ui components\n│   ├── item-card.tsx     # Resource card component\n│   └── pr-submission-dialog.tsx\n├── hooks/                # Custom React hooks\n│   ├── use-bookmark.ts   # Bookmark management\n│   ├── use-debounce.ts   # Search debouncing\n│   ├── use-github-auth.ts # GitHub OAuth flow\n│   ├── use-pr-submission.ts # PR creation logic\n│   └── use-readme.ts     # README parsing\n├── lib/                  # Utilities & configuration\n│   ├── config.ts         # Centralized config\n│   └── utils.ts          # Helper functions\n└── providers/            # React context providers\n    ├── theme-provider.tsx\n    └── providers.tsx\n```\n\n## Configuration\n\n### Centralized Config (`src/lib/config.ts`)\n\nAll configuration is centralized in one file:\n\n```typescript\nexport const GITHUB_CONFIG = {\n  CLIENT_ID: \"Ov23lizgfZ4yKq0NxcTm\",        // GitHub OAuth App\n  REPO_OWNER: \"birobirobiro\",                // Repository owner\n  REPO_NAME: \"awesome-shadcn-ui\",           // Repository name\n  DEVICE_FLOW_URL: \"https://github.com/login/device/code\",\n  ACCESS_TOKEN_URL: \"https://github.com/login/oauth/access_token\",\n  SCOPES: [\"repo\"],                         // Required permissions\n  FORK_CREATION_DELAY: 5000,               // Delay for fork creation\n};\n\nexport const PR_TEMPLATE = {\n  HEADER: { /* PR template structure */ },\n  CATEGORIES: [ /* Available categories */ ],\n  CHECKLIST_ITEMS: { /* Automated checklist */ }\n};\n\nexport const ERROR_MESSAGES = { /* Error messages */ };\nexport const STATUS_MESSAGES = { /* Status messages */ };\n```\n\n## Key Features\n\n### Resource Display\n- **Source**: Fetches from GitHub README.md\n- **Parsing**: `use-readme.ts` hook parses markdown tables\n- **Caching**: 30-minute cache to reduce API calls\n- **Categories**: Automatically groups by README sections\n\n### Search & Filtering\n- **Real-time search** with debouncing (300ms)\n- **Category filtering** with URL state management\n- **Layout switching** (compact, grid, row)\n- **Bookmark system** with localStorage persistence\n\n### PR Submission System\n- **GitHub OAuth**: Device flow for secure authentication\n- **One-time access**: No credential storage\n- **Automated workflow**:\n  1. Check/create user fork\n  2. Create feature branch\n  3. Update README with new resource\n  4. Create pull request with template\n- **Duplicate prevention**: Checks existing resources\n- **Alphabetical sorting**: Maintains README organization\n\n### GitHub Integration\n\n#### OAuth Flow (`use-github-auth.ts`)\n```typescript\n// 1. Start device flow\nconst { userCode, verificationUri } = await startDeviceFlow();\n\n// 2. User authorizes on GitHub\n// 3. Poll for access token\n// 4. Get user info and create authenticated Octokit\n```\n\n#### PR Creation (`use-pr-submission.ts`)\n```typescript\n// 1. Check/create fork\n// 2. Create branch from latest commit\n// 3. Fetch latest README from upstream\n// 4. Insert new resource alphabetically\n// 5. Commit changes\n// 6. Create PR with template\n```\n\n## UI Components\n\n### shadcn/ui Integration\n- **Components**: Button, Dialog, Input, Select, Badge, etc.\n- **Theming**: Dark/light mode with next-themes\n- **Styling**: Tailwind CSS with custom design system\n- **Accessibility**: Built-in ARIA support\n\n### Custom Components\n- **ItemCard**: Displays resource information\n- **PRSubmissionDialog**: Handles GitHub authentication and form\n- **LayoutSwitcher**: Toggle between view modes\n- **SearchBar**: Real-time search with debouncing\n\n## Data Flow\n\n```\nGitHub README.md → use-readme.ts → Resource[] → UI Components\n                                    ↓\nUser Submission → PR Dialog → GitHub OAuth → PR Creation\n```\n\n## Development Workflow\n\n### Adding New Features\n1. **Hooks**: Add custom logic in `src/hooks/`\n2. **Components**: Create reusable components in `src/components/`\n3. **API**: Add endpoints in `src/app/api/`\n4. **Config**: Update centralized config in `src/lib/config.ts`\n\n### Environment Variables\n```bash\n# No environment variables required\n# All config is in src/lib/config.ts\n```\n\n### Testing\n```bash\n# Run type checking\nnpm run type-check\n\n# Run linting\nnpm run lint\n\n# Build check\nnpm run build\n```\n\n## Dependencies\n\n### Core\n- **Next.js 15.2.4**: React framework with App Router\n- **React 19**: UI library\n- **TypeScript 5.8.3**: Type safety\n\n### UI & Styling\n- **shadcn/ui**: Component library (Radix UI primitives)\n- **Tailwind CSS 4.1.11**: Utility-first CSS\n- **next-themes 0.4.6**: Theme management\n- **Lucide React 0.509.0**: Icons\n- **Framer Motion 11.0.0**: Animations\n\n### GitHub Integration\n- **@octokit/rest 22.0.0**: GitHub API client\n- **Device Flow OAuth**: Secure authentication\n\n### Utilities\n- **clsx 2.1.1**: Conditional classes\n- **tailwind-merge 2.6.0**: Tailwind class merging\n- **sonner 1.7.4**: Toast notifications\n- **class-variance-authority 0.7.1**: Component variants\n- **cmdk 1.0.0**: Command palette\n\n## Contributing\n\n1. **Fork the repository**\n2. **Create feature branch**: `git checkout -b feature/amazing-feature`\n3. **Make changes** following the existing patterns\n4. **Test thoroughly** with different scenarios\n5. **Submit PR** with clear description\n\n## Architecture Decisions\n\n### Why Device Flow OAuth?\n- **Security**: No client secrets in frontend\n- **User-friendly**: No redirects, works everywhere\n- **Temporary**: One-time access, no permanent storage\n\n### Why Centralized Config?\n- **Maintainability**: Single source of truth\n- **Type Safety**: TypeScript constants\n- **Consistency**: Same values across all files\n\n### Why README as Data Source?\n- **User Readme View**: Easily User Viewable\n- **Simplicity**: No database required\n- **Version Control**: Changes tracked in Git\n- **Transparency**: Public data source\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 João Inácio Neto\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://awesome-shadcn-ui.vercel.app\" target=\"_blank\">\n  <img width=\"400\" src=\"public/logo.svg\" alt=\"awesome-shadcn/ui logo\">\n  </a>\n</p>\n\n<h1 align=\"center\">awesome-shadcn/ui</h1>\n\n<p align=\"center\">\n  A curated list of awesome things related to <a href=\"https://ui.shadcn.com/\" target=\"_blank\">shadcn/ui</a>\n</p>\n\n<p align=\"center\">\n  <i>Created by <a href=\"https://birobirobiro.dev/\" target=\"_blank\">birobirobiro.dev</a></i><br>\n  <i>Site by <a href=\"https://bankkroll.xyz/\" target=\"_blank\">bankkroll.xyz</a></i><br>\n  <i>Sponsored by</i><br>\n  <a href=\"https://shadcnstudio.com/?utm_source=awesome-shadcn-ui&utm_medium=banner&utm_campaign=github\" target=\"_blank\">\n    <img src=\"public/sponsors/shadcnstudio.svg\" alt=\"shadcnstudio.com\" width=\"32\">\n  </a>\n  <a href=\"https://www.shadcnblocks.com\" target=\"_blank\">\n    <img src=\"public/sponsors/shadcnblocks.svg\" alt=\"shadcnblocks.com\" width=\"32\">\n  </a>\n  <a href=\"https://shadcnuikit.com/?utm_source=awesome-shadcn-ui&utm_medium=banner&utm_campaign=github\" target=\"_blank\">\n    <img src=\"public/sponsors/shadcnuikit.svg\" alt=\"shadcnuikit.com\" width=\"32\">\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://awesome-shadcn-ui.vercel.app/\" target=\"_blank\">\n    <img src=\"https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg\" alt=\"Awesome\">\n  </a>\n</p>\n\n## Libs and Components\n\n| Name | Description | Link | Date |\n| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ---------- |\n| 21st.dev | Open source npm for shadcn/ui components. Also: Dribble for design engineers. Install UI components via shadcn CLI, or publish your own. | [Link](https://21st.dev/) | 2024-12-06 |\n| 8bitcn.com | A set of retro-designed, accessible components and a code distribution platform. Open Source. Open Code. | [Link](https://www.8bitcn.com/) | 2025-04-12 |\n| aceternity-ui | Copy paste the most trending react components without having to worry about styling and animations. | [Link](https://ui.aceternity.com/) | 2024-12-06 |\n| agents-ui | Agents UI is LiveKit’s open source component library built with React and shadcn for designing voice agent interfaces. Start with production-ready defaults, then customize every detail to match your brand. | [Link](https://livekit.io/ui) | 2026-03-04 |\n| animated-header | Vercel-like animated header. | [Link](https://github.com/mehrdadrafiee/animated-header) | 2025-10-20 |\n| animated-tabs | Vercel-like animated tabs. | [Link](https://github.com/mehrdadrafiee/animated-tabs) | 2025-04-06 |\n| assistant-ui | React Components for AI Chat. | [Link](https://github.com/Yonom/assistant-ui) | 2024-09-23 |\n| audio/ui | A set of accessible and composable Audio UI components. Built on top of shadcn/ui, it's designed for you to copy, paste, and own. | [Link](https://github.com/ouestlabs/audio-ui) | 2025-11-20 |\n| autocomplete-select-shadcn-ui | Autocomplete component built with shadcn/ui and Fancy Multi Select by Maximilian Kaske. | [Link](https://www.armand-salle.fr/post/autocomplete-select-shadcn-ui) | 2024-04-07 |\n| auto-form | A React component that automatically creates a shadcn/ui form based on a zod schema. | [Link](https://github.com/vantezzen/auto-form) | 2024-04-29 |\n| async-select | Async Select component built with shadcn/ui with debounce search. | [Link](https://async.rdsx.dev) | 2024-07-22 |\n| berlix | Animated components library built using Tailwind CSS and Motion | [Link](https://berlix.vercel.app) | 2025-06-10 |\n| big-calendar | A modern, feature-rich calendar application with multiple viewing options built using Next.js, TypeScript, and Tailwind CSS. | [Link](https://github.com/lramos33/big-calendar) | 2025-03-08 |\n| billingsdk | Modern, type-safe billing and subscription management components for React, built with TypeScript and Tailwind CSS. Designed to work seamlessly alongside shadcn/ui by [Dodo Payments](https://dodopayments.com/). [GitHub](https://github.com/dodopayments/billingsdk) | [Link](https://billingsdk.com/) | 2025-09-06 |\n| billui | Open source billing components for React built with Shadcn. Pricing cards, payment methods, invoice history and more. | [Link](https://github.com/commet-labs/billui) | 2025-12-25 |\n| blocks.so | A set of clean, modern building blocks to copy and paste into your apps. Works with all React frameworks. | [Link](https://github.com/ephraimduncan/blocks) | 2025-09-06 |\n| buouui | A UI component library and template suite based on shadcn/ui with stunning landing pages, templates, and rich animations. | [Link](https://buouui.com) | 2025-04-06 |\n| bundui | A collection of reusable animated components built with Tailwind CSS and Framer Motion. | [Link](https://bundui.io) | 2024-09-23 |\n| calendar | React/shadcn full calendar like Google Calendar | [Link](https://github.com/charlietlamb/calendar) | 2024-05-03 |\n| calendar-cn | A beautifully crafted calendar component built with shadcn/ui and Tailwind CSS, inspired by Notion Calendar. Week view, dark mode, and more. | [Link](https://github.com/vmnog/calendarcn) | 2026-03-05 |\n| capture-photo | Browser-based React component for camera functionalities in web applications. | [Link](https://github.com/UretzkyZvi/capture-photo) | 2024-05-06 |\n| carouselcn | Copy-paste carousel components and examples for your react apps | [Link](https://github.com/mnove/carouselcn) | 2026-01-27 |\n| cascader-shadcn | A cascading dropdown menu component for selecting hierarchical data like locations, categories, or organizational structures. | [Link](https://github.com/Ademking/cascader-shadcn) | 2025-12-04 |\n| chanhdai-components | A collection of reusable components. Trusted registry for shadcn/ui. | [Link](https://chanhdai.com/components) | 2026-03-01 |\n| clerk-elements | Composable components for building custom UIs on top of Clerk's APIs. | [Link](https://clerk.com/docs/elements/examples/shadcn-ui) | 2024-06-07 |\n| clerk-shadcn-theme | Synchronize Clerk SignIn/SignUp components with shadcn/ui styles. | [Link](https://github.com/stormynight9/clerk-shadcn-theme) | 2024-06-07 |\n| commerce-ui | Components, blocks and examples to build e-commerce storefronts and apps. | [Link](https://github.com/stackzero-labs/ui) | 2025-02-20 |\n| confirm-dialog | A confirm dialog component built with shadcn/ui. | [Link](https://github.com/Aslam97/react-confirm-dialog) | 2024-07-02 |\n| country-state-dropdown | Component built with Nextjs, Tailwindcss, shadcn/ui & Zustand. | [Link](https://github.com/Jayprecode/country-state-dropdown) | 2024-02-22 |\n| creatorem/ui | Missing awesome shadcn components (like Tour, Stepper, QRCode Motion Dialog -> to imitate native behaviour ...) built on top of radix primitives and motion. | [Link](https://github.com/creatorem/ui) | 2025-12-19 |\n| credenza | Ready-made responsive modal component for shadcn/ui. | [Link](https://github.com/redpangilinan/credenza) | 2024-06-07 |\n| crypto-charts | Crypto charts made for shadcn/ui using PythNetwork. | [Link](https://github.com/jstnw10/crypto-charts) | 2024-07-16 |\n| cult-ui | Curated set of animated shadcn-style React components. | [Link](https://www.cult-ui.com/) | 2024-05-29 |\n| custom-admin-dashboard | A minimal, open-source ecommerce admin dashboard template built with shadcn/ui and Next.js. Includes products, products creation and details page, order, customer, and settings pages. | [Link](https://github.com/S5SAJID/custom-ecom) | 2025-09-06 |\n| data-command | Component for building dynamic command palettes with API-powered data. | [Link](https://shadcn.davidsling.in/components/data-command) | 2025-09-06 |\n| date-range-picker-for-shadcn | Multi-month views, text entry, preset ranges, responsive design, and date range comparisons. | [Link](https://github.com/johnpolacek/date-range-picker-for-shadcn) | 2024-04-29 |\n| date-time-picker-shadcn | Datetime Picker for shadNext Project. | [Link](https://shadcn-datetime-picker.vercel.app) | 2024-07-16 |\n| date-time-range-picker-shadcn | Fully featured date-time range picker with multi-month views, timezone support, preset ranges, and modular components for date and time selection. | [Link](https://date-time-range-picker.vercel.app/) | 2025-03-08 |\n| datetime-picker | Datetime picker with timezone support, min/max dates, and month/year selection. | [Link](https://shadcn-datetime-picker-xi.vercel.app) | 2024-07-16 |\n| dnd-dashboard | Dashboard with drop-to-swap layouts using Next.js, shadcn/ui, and swapy. | [Link](https://github.com/olliethedev/dnd-dashboard) | 2024-10-17 |\n| downshift-shadcn-combobox | Combobox/autocomplete component built with shadcn/ui and Downshift. | [Link](https://github.com/TheOmer77/downshift-shadcn-combobox) | 2024-06-27 |\n| drag-to-resize-sidebar | Extended shadcn/ui sidebar component with persisted state drag-to-resize functionality. | [Link](https://github.com/lumpinif/drag-to-resize-sidebar) | 2024-11-21 |\n| druid/ui | Intercom inspired AI chatbot and UI components built on shadcn/ui. | [Link](https://druidui.com/) | 2024-11-21 |\n| drop-drawer | A dropdown menu on desktop and a drawer on mobile devices. | [Link](https://github.com/jiaweing/DropDrawer) | 2025-05-13 |\n| dy-comps | shacn/ui & Framer Motion React components — flexible, responsive & easy to drop into any project. | [Link](https://dycomps.oimmi.com/) | 2025-03-08 |\n| dynamic-svg-arrow | Connect any two elements with beautiful, animated paths. Control curve shape, routing, layering, arrowheads, gradients, and responsive behavior in real-time. | [Link](https://dsa.hncore.website/) | 2025-10-09 |\n| echo-editor | Modern WYSIWYG rich-text editor based on tiptap and shadcn/ui. | [Link](https://github.com/Seedsa/echo-editor) | 2024-06-07 |\n| edil-ozi | React components with Gsap, framer motion, and tailwind. | [Link](https://edilozi.pro/) | 2024-06-27 |\n| eldora-ui | free and open-source animated components built with React, Typescript, Tailwind CSS, and Framer Motion. | [Link](https://eldoraui.site/) | 2024-12-06 |\n| emblor | Customizable, accessible tag input component with shadcn/ui. | [Link](https://github.com/JaleelB/emblor) | 2024-04-29 |\n| enhanced-button | Enhanced version of the default shadcn-button component. | [Link](https://github.com/jakobhoeg/enhanced-button) | 2024-04-29 |\n| envin | Framework-agnostic, type-safe tool to validate and preview your environment variables - powered by your favorite schema validator. | [Link](https://envin.turbostarter.dev) | 2025-07-07 |\n| eo-n/ui | Enhanced UI components built on shadcn’s robust foundation, integrated with Base UI and Tailwind CSS for a modern and customizable design system. | [Link](https://github.com/aeonzz/eo-n) | 2025-04-04 |\n| event-timeline-roadmap | A pair of customizable, animated event timeline and roadmap components | [Link](https://roadmap.hncore.website/) | 2025-04-01 |\n| extend-ui | Reusable components built on shadcn/ui for web applications. | [Link](https://www.extend-ui.com/) | 2024-11-28 |\n| fancy-area | Textarea with @mention support inspired by GitHub's PR comment section. | [Link](https://craft.mxkaske.dev/post/fancy-area) | 2024-06-27 |\n| fancy-box | GitHub PR label selector-inspired Combobox with radix-ui components. | [Link](https://craft.mxkaske.dev/post/fancy-box) | 2024-07-11 |\n| fancy-multi-select | Multi Select Component inspired by campsite.design and cal.com. | [Link](https://craft.mxkaske.dev/post/fancy-multi-select) | 2024-04-29 |\n| fancy-switch | Fancy switch component built with shadcn/ui. | [Link](https://github.com/Aslam97/react-fancy-switch) | 2024-07-11 |\n| farmui | Styled and animated component library with npm package support. | [Link](https://farmui.com) | 2024-06-08 |\n| file-uploader | File uploader with shadcn/ui and react-dropzone. | [Link](https://github.com/sadmann7/file-uploader) | 2024-06-07 |\n| file-vault | File upload component for React. | [Link](https://github.com/ManishBisht777/file-vault) | 2024-06-07 |\n| floating-dragable-card | Dragable and resizable card using shadcn/ui elements. | [Link](https://github.com/nishansanjuka/react-drag-card) | 2024-11-13 |\n| fusion-ui | Library combining shadcn/ui and MagicUI. | [Link](https://github.com/nyxb-ui/ui) | 2024-07-02 |\n| glasscn-ui | shadcn/ui component library with glassmorphism variants, and many additional components. | [Link](https://github.com/itsjavi/glasscn-ui) | 2025-06-10 |\n| glitchcn-ui | A terminal-styled cyberpunk component library for Next.js with scanline effects, glowing borders, and monospace typography. | [Link](https://glitchcn-ui.vercel.app/) | 2026-02-06 |\n| gluestack-ui | React & React Native Components with Tailwind CSS. | [Link](https://gluestack.io) | 2024-11-08 |\n| goey-toast | Morphing toast notifications for React. Organic blob animations, promise tracking, and full customization out of the box | [Link](https://goey-toast.vercel.app/) | 2026-02-14 |\n| heroicons-animated | An open-source collection of 316 beautifully animated heroicons for your projects. | [Link](https://heroicons-animated.vercel.app/) | 2026-01-23 |\n| hexta-ui | Build stunning websites effortlessly. Modern, responsive, and customizable UI components for Next.js. Copy, adapt, and personalize them. | [Link](https://hextaui.com) | 2025-05-14 |\n| ibelick/background-snippet | Ready to use collection of modern background snippets. | [Link](https://bg.ibelick.com/) | 2024-06-09 |\n| image-crop-field | Image crop field with shadcn/ui. This component is a wrapper around the react-easy-crop component. | [Link](https://github.com/JsCodeDevlopment/upload-crop-image) | 2025-10-01 |\n| image-upload-shadcn | Image upload component. | [Link](https://github.com/kushagrasarathe/image-upload-shadcn) | 2024-10-22 |\n| indie-ui | UI components with variants. | [Link](https://github.com/Ali-Hussein-dev/indie-ui) | 2024-06-07 |\n| inspira-ui | UI components for animated interfaces in Vue/NuxtJS. | [Link](https://inspira-ui.com/) | 2024-10-22 |\n| jolyui | JolyUI is a modern React component library built with TypeScript, Tailwind CSS and Motion. It offers a wide range of customizable and accessible UI components to help you build stunning web applications quickly and efficiently. | [Link](https://github.com/Johuniq/jolyui) | 2026-01-23 |\n| junwen-k/ui-x | Additional beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source. | [Link](https://ui-x.junwen-k.dev/) | 2025-02-03 |\n| kanban-board | A production‑ready Kanban board built on shadcn/ui with React & Tailwind CSS: zero dependencies, drag‑and‑drop, keyboard accessibility and seamless theming. | [Link](https://shadcn-kanban-board.com/) | 2025-07-15 |\n| kibo-ui | Kibo UI is designed to be a more comprehensive library of components that can be used to build more complex applications. | [Link](https://www.kibo-ui.com/overview) | 2025-04-04 |\n| kokonut-ui | Free Modern and Customizable components for Next.js. | [Link](https://kokonutui.com/) | 2024-11-08 |\n| ktui | Open-source collection of customizable UI components for Tailwind CSS and vanilla JavaScript | [Link](https://github.com/keenthemes/ktui) | 2025-06-10 |\n| launch-ui | Landing page components with React, Shadcn/ui and Tailwind. | [Link](https://www.launchuicomponents.com/) | 2024-11-12 |\n| lingua-time | Smart datetime picker with natural language input. | [Link](https://github.com/nainglinnkhant/lingua-time) | 2024-10-22 |\n| linked-chart | Chart component linked with data-table. | [Link](https://github.com/ardasisbot/linked-chart) | 2025-02-03 |\n| lukacho-ui | Next Generation UI Components. | [Link](https://ui.lukacho.com/components) | 2024-07-02 |\n| magicui | React components for landing pages with tailwindcss + framer motion. | [Link](https://magicui.design) | 2024-04-25 |\n| maily.to | Notion-like powerful email editor. | [Link](https://github.com/arikchakma/maily.to) | 2024-06-15 |\n| manfromexistence-ui | Components to build beautiful designs. | [Link](https://github.com/manfromexistence/ui) | 2024-12-26 |\n| manifest-ui | Components for ChatGPT Apps and MCP Apps. | [Link](https://ui.manifest.build) | 2025-12-23 |\n| matsu-theme | Ghibli Studio inspired theme for shadcn/ui made by Matt Wierzbicki | [Link](https://matsu-theme.vercel.app/) | 2025-04-23 |\n| mindmapcn | Beautiful mind maps, works seamlessly with shadcn/ui. | [Link](https://github.com/SSShooter/mindmapcn) | 2026-01-27 |\n| minimal-tiptap | Minimal WYSIWYG editor with shadcn/ui and tiptap. | [Link](https://github.com/Aslam97/shadcn-minimal-tiptap) | 2024-06-22 |\n| mixcnui | Collection of animated components for Nextjs. | [Link](https://github.com/taqui-786/mixcnui) | 2024-07-07 |\n| modal-control-query | A hook to control shadcn modal components using query params | [Link](https://shadcn.davidsling.in/hooks/use-modal-control-query) | 2025-11-02 |\n| moleculeui | A modern React component library focused on intuitive interactions and seamless user experiences. | [Link](https://www.moleculeui.design/) | 2025-09-06 |\n| multi-selection | Managing multi-selection functionality with highlighter. | [Link](https://github.com/sherifawad/multi-selection-with-add-remove) | 2025-01-21 |\n| mvpblocks | Copy-paste beautiful, responsive components without worrying about styling or animations. Build faster, launch sooner. | [Link](https://blocks.mvp-subha.me) | 2025-05-14 |\n| mynaui | TailwindCSS and shadcn/ui UI Kit for Figma and React. | [Link](https://mynaui.com/) | 2024-06-22 |\n| neobrutalism-components | Neobrutalism-styled Tailwind React and shadcn/ui components. | [Link](https://github.com/ekmas/neobrutalism-components) | 2024-04-07 |\n| nextjs-components | Next.js components with TypeScript and shadcn/ui. | [Link](https://components.bridger.to/) | 2024-06-07 |\n| nextjs-dnd | Sortable Drag and Drop with Next.js and dnd-kit. | [Link](https://github.com/sujjeee/nextjs-dnd) | 2024-04-09 |\n| nextjs-link-pagination | Pagination using Nextjs Links and search params. | [Link](https://shadcn-next-link-pagination.vercel.app) | 2024-07-30 |\n| nextjs-multi-image-upload | Compact, responsive file uploader with shadcn/ui, React Hook Form, and cloud support (S3/R2). | [Link](https://github.com/jacksonkasi0/nextjs-multi-image-upload) | 2025-02-23 |\n| next-shadcn-dashboard-starter | Admin Dashboard Starter with Nextjs 14 and Shadcn UI. | [Link](https://github.com/Kiranism/next-shadcn-dashboard-starter) | 2024-06-06 |\n| next-stepper | Dynamic multi-step form with Next.js and zustand. | [Link](https://github.com/ebulku/next-stepper) | 2024-11-25 |\n| novel | Notion-style WYSIWYG editor with AI-powered autocompletion. | [Link](https://github.com/steven-tey/novel) | 2024-06-11 |\n| number-flow | React component for number transitions and formatting. | [Link](https://number-flow.barvian.me/) | 2024-10-17 |\n| origin-ui | Beautiful UI components with Tailwind CSS and Next.js. | [Link](https://originui.com/) | 2024-10-28 |\n| password-input | shadcn/ui custom password input. | [Link](https://gist.github.com/mjbalcueva/b21f39a8787e558d4c536bf68e267398) | 2024-03-28 |\n| payment-gateways | Integration of payment gateways with Next.js 14. | [Link](https://github.com/PremPrakashCodes/payment-gateways) | 2024-08-05 |\n| pdfx | shadcn/ui-style PDF component library for React. Copy-paste components built on @react-pdf/renderer run pdfx add invoice and own the code. | [Link](https://github.com/akii09/pdfx) | 2026-02-28 |\n| phone-input-shadcn-ui | Custom phone number component with shadcn/ui. | [Link](https://www.armand-salle.fr/post/phone-input-shadcn-ui) | 2024-06-07 |\n| pittaya-ui | A fully open-source UI library for React, powered by TypeScript and Tailwind CSS. Fast, composable, and ready for production. | [Link](https://github.com/pittaya-ui/ui-kit) | 2026-01-27 |\n| planner | Adaptable scheduling component for React. | [Link](https://github.com/UretzkyZvi/planner) | 2024-04-29 |\n| plate | AI-powered rich-text editor. | [Link](https://github.com/udecode/plate) | 2024-03-21 |\n| plate-select-editor | Rich multi-select editor. | [Link](https://platejs.org/docs/multi-select) | 2024-11-28 |\n| pqoqubbw | Open-source animated icons collection. | [Link](https://icons.pqoqubbw.dev/) | 2024-11-21 |\n| pricing-page-shadcn | Customizable pricing page with Next.js 14. | [Link](https://github.com/m4nute/pricing-page-shadcn) | 2024-04-07 |\n| progress-button | Extended button component with progress UX. | [Link](https://github.com/tomredman/ProgressButton) | 2024-07-10 |\n| re-ui | Open-source collection of UI components and animated effects built with React, Typescript, Tailwind CSS, and Motion. Pairs beautifully with shadcn/ui | [Link](https://reui.io/) | 2025-09-06 |\n| react-dnd-kit-tailwind-shadcn-ui | Accessible kanban board with dnd-kit. | [Link](https://github.com/Georgegriff/react-dnd-kit-tailwind-shadcn-ui) | 2024-03-27 |\n| react-highlight-popover | Headless component for text selection popovers. | [Link](https://react-highlight-popover.omsimos.com) | 2024-10-03 |\n| react-pdf-flipbook-viewer | PDF flipbook viewer with zoom and fullscreen. | [Link](https://github.com/mohitkumawat310/react-pdf-flipbook-viewer) | 2024-12-26 |\n| react-select | React-select library with shadcn styling. | [Link](https://gist.github.com/ilkou/7bf2dbd42a7faf70053b43034fc4b5a4) | 2024-07-22 |\n| react-wheel-picker | iOS-like wheel picker for React with smooth inertia scrolling and infinite loop support. | [Link](https://react-wheel-picker.chanhdai.com) | 2025-09-11 |\n| recursive-dnd-kanban-board | Recursive drag and drop kanban board. | [Link](https://github.com/mehrdadrafiee/recursive-dnd-kanban-board) | 2024-10-03 |\n| retro-ui | An open source component library, inspred by neo brutalism design system | [Link](https://retroui.dev) | 2025-05-13 |\n| roadmap-ui | Components for interactive roadmaps. | [Link](https://github.com/haydenbleasel/roadmap-ui) | 2024-12-26 |\n| ruixen-ui | Ruixen UI: Lightweight & Customizable React Library | [Link](https://github.com/ruixenui/ruixen.com) | 2025-12-04 |\n| search-address | Interactive address search using OpenStreetMap. | [Link](https://github.com/UretzkyZvi/search-address) | 2024-05-07 |\n| shadboard | An admin dashboard template built with Next.js 15, React 19, Tailwind CSS v4, and Shadcn/UI components, featuring starter and full kits for scalable, user-friendly web apps. | [Link](https://github.com/Qualiora/shadboard) | 2025-04-22 |\n| shadcn-address-autocomplete | Address autocomplete with Google Places API. | [Link](https://github.com/NiazMorshed2007/shadcn-address-autocomplete) | 2024-07-10 |\n| shadcn-admin | Admin Dashboard UI with shadcn/ui and Vite. | [Link](https://github.com/satnaing/shadcn-admin) | 2024-12-27 |\n| shadcn-admin-kit | Powerful open-source shadcn components to build beautiful internal tools, admin panels, and dashboards with React | [Link](https://github.com/marmelab/shadcn-admin-kit) | 2025-07-07 |\n| shadcn-blocks | Official pre-made customizable components. | [Link](https://ui.shadcn.com/blocks) | 2024-03-27 |\n| shadcn-blocks-com | Hundreds of extra blocks built with shadcn/ui. | [Link](https://www.shadcnblocks.com) | 2025-02-23 |\n| shadcn-builder | Create beautiful, responsive forms with the easy-to-use form builder and generate React code using shadcn/ui components. | [Link](https://www.shadcn-builder.com/?utm_source=github&utm_content=awesome-shadcn-ui) | 2025-03-31 |\n| shadcn-cal | Cal.com monthly calendar replica with shadcn/ui. | [Link](https://shadcn-cal-com.vercel.app/?date=2024-04-29) | 2024-05-03 |\n| shadcn-calendar-heatmap | Modern calendar heatmap alternative. | [Link](https://shadcn-calendar-heatmap.vercel.app/) | 2024-07-02 |\n| shadcn-calendar-component | Calendar date picker component. | [Link](https://github.com/sersavan/shadcn-calendar-component) | 2024-07-02 |\n| shadcn-chat | Customizable chat component. | [Link](https://github.com/jakobhoeg/shadcn-chat) | 2024-04-29 |\n| shadcn-carousel-testimonials | Carousel Testimonials component. | [Link](https://github.com/johanguse/shadcn-carousel-testimonials) | 2024-07-16 |\n| shadcn-chatbot-kit | Customizable chatbot components. | [Link](https://shadcn-chatbot-kit.vercel.app/) | 2024-12-27 |\n| shadcn-color-picker | Color picker with react-color. | [Link](https://shadcn-color-picker.vercel.app/) | 2024-09-23 |\n| shadcn-cookies | Sleek and flexible cookie consent component, designed with shadcn/ui | [Link](https://shadcn-cookies.vercel.app/) | 2025-02-06 |\n| shadcn-cookie-consent | Customizable cookie consent component. | [Link](https://github.com/r2hu1/shadcn-cookie-consent) | 2024-10-17 |\n| shadcn-components-blocks | The ultimate blocks and components for Shadcn UI & Tailwind CSS. | [Link](https://shadcncomponents.dev/) | 2025-09-06 |\n| shadcn-country-dropdown | ISO 3166 country selector dropdown. | [Link](https://shadcn-country-dropdown.vercel.app/) | 2024-12-27 |\n| shadcn-data-table-advanced-col-opions | DataTable with column resizing. | [Link](https://github.com/danielagg/shadcn-data-table-advanced-col-opions) | 2024-04-07 |\n| shadcn-date-picker | Advanced date picker with various features. | [Link](https://date-picker.luca-felix.com) | 2024-07-25 |\n| shadcn-drag-and-drop-sort | Drag-and-drop sortable list of pills of different widths using dnd-kit. | [Link](https://github.com/crystaltai/shadcn-drag-and-drop) | 2025-02-03 |\n| shadcn-drag-table | Drag-and-drop table component. | [Link](https://github.com/zenoncao/shadcn-drag-table) | 2024-02-22 |\n| shadcn-dropzone | File upload component using React-Dropzone, built with accessibility in mind. | [Link](https://github.com/janglad/shadcn-dropzone) | 2025-01-13 |\n| shadcn-editor | Lexical editor with shadcn theme. | [Link](https://github.com/htmujahid/shadcn-editor) | 2024-11-08 |\n| shadcn-examples | Dozens of advanced shadcn/ui examples. Easily integrate sample applications and components into your project. | [Link](https://shadcnexamples.com) | 2025-06-04 |\n| shadcn-extends | Collection of shadcn/ui components. | [Link](https://github.com/lucioew28/extends) | 2024-06-07 |\n| shadcn-extension | Open-source component collection. | [Link](https://github.com/BelkacemYerfa/shadcn-extension) | 2024-06-07 |\n| shadcn-font-picker | Font picker using shadcn/ui components and google font API. | [Link](https://shadcn-font-picker.vercel.app) | 2025-04-22 |\n| shadcn-full-calendar | A feature-rich calendar application built with React, TypeScript, and ShadCN UI components. This project provides a customizable and interactive calendar experience with multiple views, event management, and a modern UI. | [Link](https://github.com/yassir-jeraidi/full-calendar) | 2025-07-07 |\n| shadcn-iconpicker | React/shadcn simple icon picker using lucide icons. | [Link](https://icon-picker.alan-courtois.fr/) | 2025-02-20 |\n| shadcn-image-cropper | Image cropper with react-image-crop. | [Link](https://github.com/sujjeee/shadcn-image-cropper) | 2024-11-08 |\n| shadcn-io | Advanced Shadcn/UI components. | [Link](https://shadcn.io) | 2025-09-06 |\n| shadcn-linear-combobox | Linear-style task priority combobox. | [Link](https://github.com/damianricobelli/shadcn-linear-combobox) | 2024-04-25 |\n| shadcn-location-picker | Simple google maps location picker. | [Link](https://github.com/brielov/shadcn-location-picker) | 2025-03-04 |\n| shadcn-map | A map component built for shadcn/ui using Leaflet and React Leaflet. | [Link](https://shadcn-map.vercel.app/) | 2025-09-30 |\n| shadcn-multi-select-component | Multi-select component. | [Link](https://github.com/sersavan/shadcn-multi-select-component) | 2024-06-07 |\n| shadcn-number-scrubber | Draggable numeric input component. | [Link](https://github.com/camwebby/shadcn-react-number-scrubber) | 2025-01-07 |\n| shadcn-packaged | This is an npm package that exports all shadcn/ui components without the need for a CLI, designed for ease of use. | [Link](https://github.com/anuoua/shadcn-packaged) | 2025-02-27 |\n| shadcn-phone-input-2 | Phone input with libphonenumber-js. | [Link](https://github.com/damianricobelli/shadcn-phone-input) | 2024-06-07 |\n| shadcn-phone-input | Phone input with country validation. | [Link](https://github.com/omeralpi/shadcn-phone-input) | 2024-03-27 |\n| shadcn-portable-text-editor | A headless rich text editor built upon Sanity's Portable Text Editor with opinionated starting styles |  | 2025-09-06 |\n| shadcn-pricing-page | Responsive pricing component with toggles. | [Link](https://github.com/aymanch-03/shadcn-pricing-page) | 2024-03-08 |\n| shadcn-space | Open-source shadcn/ui blocks, components, and templates built with React, Tailwind, and Base UI. | [Link](https://github.com/shadcnspace/shadcnspace) | 2026-02-27 |\n| shadcn-spinner | Spinner component. | [Link](https://github.com/allipiopereira/shadcn-spinner) | 2024-12-09 |\n| shadcn-stepper | Complete stepper component. | [Link](https://github.com/damianricobelli/stepperize) | 2024-04-25 |\n| shadcn-studio | Open Source Registry of Shadcn components and blocks. | [Link](https://shadcnstudio.com/) | 2025-04-24 |\n| shadcn-table-maker | Tool for creating dynamic tables. | [Link](https://shadcn-table-maker.vercel.app/) | 2024-12-09 |\n| shadcn-table-v2 | Table with server-side features. | [Link](https://github.com/sadmann7/shadcn-table) | 2024-03-27 |\n| shadcn-tanstack-form | Seamless shadcn TanStack Form component set. Fully featured with validation support and type-safety. | [Link](https://shadcn-tanstack-form.felipestanzani.com/) | 2025-10-08 |\n| shadcn-timeline | Customizable timeline component. | [Link](https://github.com/timDeHof/shadcn-timeline) | 2024-06-22 |\n| shadcn-timeline-2 | Alternative timeline component. | [Link](https://timeline.rilcy.app) | 2024-11-08 |\n| shadcn-tiptap | Custom Tiptap editor extensions. | [Link](https://github.com/NiazMorshed2007/shadcn-tiptap) | 2024-11-08 |\n| shadcn-vaults | Collection of various interactive components & blocks for Internal Tools UI like Dashboard, Monitoring, Admin, CMS, and more. Specifically made for Full-Stack Dev | [Link](https://shadcn-vaults.vercel.app/) | 2025-07-03 |\n| shadcn-tree-view | Hierarchical data component. | [Link](https://github.com/mrlightful/shadcn-tree-view) | 2024-09-23 |\n| shadcn-ui-blocks | Collection of responsive UI blocks. | [Link](https://shadcn-ui-blocks.vercel.app/) | 2024-06-15 |\n| shadcn-ui-expansions | Additional useful components. | [Link](https://github.com/hsuanyi-chou/shadcn-ui-expansions) | 2024-06-07 |\n| shadcn-ui-sidebar | Retractable responsive sidebar. | [Link](https://github.com/salimi-my/shadcn-ui-sidebar) | 2024-05-03 |\n| shadcn-ui-templates | Free & Premium templates collection. | [Link](https://shadcnui-templates.com) | 2024-09-18 |\n| shadcnship | Production-ready shadcn/ui component registry for building modern SaaS applications with Next.js, TypeScript, and Tailwind CSS. | [Link](https://github.com/arnaudvolp/shadcnship) | 2026-01-31 |\n| shaduxe-ui | Component variants for shadcn/ui. | [Link](https://ui.shaduxe.com) | 2024-12-27 |\n| shadcn-event-calendar | A beautiful and flexible event calendar component inspired by Google Calendar and Notion, built with Shadcn UI, TailwindCSS, and Framer Motion. | [Link](https://shadcn-event-calendar.vercel.app) | 2025-07-16 |\n| shsfui | Motion-first React components built with Tailwind CSS + Framer Motion. | [Link](https://www.shsfui.com) | 2025-03-12 |\n| shuip | Provides ready-to-use, business-focused components that help you ship faster. | [Link](https://shuip.plvo.dev/docs) | 2025-10-09 |\n| siddz-ui | A curated collection of modern, reusable React components. Built with performance and accessibility in mind. Copy, paste, and customize. | [Link](https://siddz.com/components) | 2026-02-13 |\n| simple-ai | Components and blocks to easily build AI apps | [Link](https://simple-ai.dev) | 2025-01-21 |\n| simple-image-uploader | Image uploader with dnd, validation and previews | [Link](https://simple-image-uploader-bice.vercel.app/) | 2025-03-25 |\n| simplekit | Wallet and account component for Wagmi. | [Link](https://github.com/vaunblu/SimpleKit) | 2024-09-18 |\n| skiper-ui | Stand out from others with this crazzy ui library built with shad-cn cli | [Link](https://skiper-ui.com/) | 2025-02-03 |\n| solanauth | Solana wallet authentication modal. | [Link](https://solanauth.vercel.app/) | 2024-11-25 |\n| sortable | Sortable component with dnd-kit. | [Link](https://github.com/sadmann7/sortable) | 2024-04-09 |\n| spectrum-ui | Collection using Aceternity UI Magic UI. | [Link](https://github.com/arihantcodes/spectrum-ui) | 2024-11-25 |\n| stateful-button | A shadcn/ui button component that provides clear visual feedback with full accessibility support for loading/progress, success, and error states during asynchronous operations. | [Link](https://github.com/nanyx95/Stateful-Button-React) | 2025-09-06 |\n| stocks | Stock Picker with Next.js charts. | [Link](https://github.com/aryanvichare/stocks) | 2024-07-07 |\n| supabase-shadcn-database-example | supabase + shadcn/ui datatable | [Link](https://github.com/thisisfel1x/supabase-shadcn-database-example) | 2024-12-30 |\n| supercharged-shadcn-components | Type-safe form components collection. | [Link](https://github.com/slickwit/supercharged-shadcn-components) | 2024-11-25 |\n| svelte-image-uploader | Svelte image uploader with dnd, validation and previews. | [Link](https://svelte-image-uploader.vercel.app/) | 2025-06-10 |\n| trable-craft | Drizzle ORM-powered table engine that auto-generates tables from your schema. Built on TanStack Table with server-side filtering, sorting, pagination, search, export, and URL state sync. | [Link](https://github.com/jacksonkasi1/TableCraft) | 2026-02-27 |\n| tanstack-ui-table | Customizable table with @tanstack/table and shadcn/ui | [Link](https://github.com/drefahl/tanstack-ui-table) | 2025-02-27 |\n| the-gridcn | Tron inspired shadcn/ui theme | [Link](https://thegridcn.com/) | 2026-02-06 |\n| time-picker | Simple TimePicker component. | [Link](https://github.com/openstatusHQ/time-picker) | 2024-04-29 |\n| tnks-data-table | Advanced data table component built with shadcn/ui and TanStack Table featuring server-side operations, row selection, filtering, column customization, and export functionality. Fully TypeScript compatible with comprehensive documentation. | [Link](https://github.com/jacksonkasi1/tnks-data-table) | 2025-04-19 |\n| tool-ui | Beautiful, ready-to-use UI components for AI tool calls and MCP UI. | [Link](https://www.tool-ui.com/) | 2025-12-10 |\n| tour | A component for building onboarding tours. Designed to integrate with shadcn/ui. | [Link](https://onboarding-tour.vercel.app) | 2025-12-04 |\n| tremor | Components for charts and dashboards. | [Link](https://github.com/tremorlabs/tremor) | 2024-06-15 |\n| twblocks | Website blocks based on shadcn & Radix. | [Link](https://github.com/tommyjepsen/twblocks) | 2024-11-25 |\n| tweet-to-code | Twitter design recreations as code. | [Link](https://tweet-to-code.vercel.app/) | 2024-12-26 |\n| ui-beats | Animated React Components collection. | [Link](https://uibeats.com) | 2024-09-23 |\n| ui-layouts | UI Layouts isn’t just a library. It’s a complete toolkit with components, effects, design tools, and ready-to-use blocks, everything you need to build modern interfaces, faster. | [Link](https://www.ui-layouts.com/) | 2025-10-15 |\n| ui-nference-sh | a shadcn registry of react ui components for building ai-powered applications, chatbots, and ai agent interfaces. | [Link](https://ui.inference.sh/) | 2026-02-13 |\n| uixmat-onborda | Product tour for Next.js applications. | [Link](https://github.com/uixmat/onborda) | 2024-06-07 |\n| uselayouts | Free premium animated React components and micro-interactions built with Framer Motion and Tailwind CSS. Modern, ready-to-use motion components for high-converting websites. | [Link](https://uselayouts.com) | 2026-01-23 |\n| vaul | Drawer component for React. | [Link](https://vaul.emilkowal.ski/) | 2024-06-07 |\n| vengence-ui | A modern, animated UI component library designed to help developers build beautiful landing pages and interfaces faster. | [Link](https://www.vengenceui.com/) | 2026-02-17 |\n| vyoma-ui | Beautiful Components Made on top of Shadcn/ui with Spatial Wisdom Inside. Truly Beyond UI. | [Link](https://vyomaui.design/) | 2025-07-15 |\n| warcraftcn | A set of components inspired by classic Warcraft III RTS UI aesthetics | [Link](https://www.warcraftcn.com/) | 2026-02-06 |\n| wds registry | A collection of components that you can use to build your own component library. | [Link](https://wds-shadcn-registry.netlify.app) | 2025-09-06 |\n| zoom-charts | Zoomable Charts with shadcn/ui. | [Link](https://github.com/shelwinsunga/zoom-chart-demo) | 2024-07-16 |\n\n## Registries\n\n| Name | Description | Link | Date |\n| ------------------ | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | ---------- |\n| efferd | ready-to-use shadcn blocks that just work — modern, responsive, and built for speed. | [Link](http://efferd.com/) | 2026-03-05 |\n| more-shadcn | A collection of high-quality, copy-paste components for Svelte 5, built on top of shadcn-svelte. | [Link](https://more-shadcn.noair.fun/) | 2026-01-23 |\n| neobrutalism-vue | A vue-based registry of neobrutalism-styled Tailwind components. | [Link](https://github.com/michaelsieminski/neobrutalism-vue) | 2025-12-04 |\n| registry.directory | A curated directory to discover, preview, and copy shadcn/ui registries. | [Link](https://github.com/rbadillap/registry.directory) | 2025-09-20 |\n| tailark | Shadcn blocks for building modern marketing websites | [Link](https://tailark.com) | 2026-02-06 |\n| undraw-cn | Beautiful, customizable React components for unDraw illustrations. | [Link](https://undraw-cn.vaatun.com) | 2025-12-04 |\n\n## Plugins and Extensions\n\n| Name | Description | Link | Date |\n| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | ---------- |\n| chat-with-youtube | A chrome extension is designed to give you the ability to efficiently summarize videos, easily search for specific parts, and enjoy additional useful features. | [Link](https://chat-with-youtube.vercel.app/) | 2024-06-07 |\n| designgui | A Chrome Browser Extension for managing colors in CSS Variables. | [Link](https://www.designgui.io/) | 2024-09-05 |\n| imprompt | A Chrome extension that enhances prompts on AI websites directly, making your AI prompts more effective and productive. | [Link](https://github.com/avalynndev/imprompt) | 2025-09-06 |\n| raycast-shadcn | Raycast extension to Browse shadcn/ui documentation, components, and examples. | [Link](https://www.raycast.com/luisFilipePT/shadcn-ui) | 2024-06-05 |\n| shadcn-hsl-preview | shadcn HSL Preview extension for Visual Studio Code. | [Link](https://marketplace.visualstudio.com/items?itemName=dexxiez.shadcn-color-preview) | 2024-09-05 |\n| shadcn-ui | Add components from shadcn/ui directly from VS Code. | [Link](https://marketplace.visualstudio.com/items?itemName=SuhelMakkad.shadcn-ui) | 2024-03-08 |\n| shadcn/ui Components Manager | A plugin for Jetbrain products. It allows you to manage your shadcn/ui components across Svelte, React, Vue, and Solid frameworks with this plugin. Simplify tasks like adding, removing, and updating components. | [Link](https://plugins.jetbrains.com/plugin/23479-shadcn-ui-components-manager) | 2024-03-28 |\n| vscode-shadcn-svelte | VS Code extension for shadcn/ui components in Svelte projects. | [Link](https://marketplace.visualstudio.com/items?itemName=Selemondev.vscode-shadcn-svelte&ssr=false#overview) | 2024-03-28 |\n| vscode-shadcn-ui-snippets | Easily import and use shadcn-ui components with ease using snippets within VSCode. Just type cn or shadcn in your jsx/tsx file and you will get a list of all the components to choose from. | [Link](https://marketplace.visualstudio.com/items?itemName=VeroXyle.shadcn-ui-snippets) | 2024-06-05 |\n| vscode-shadcn-vue | Extension for integrating shadcn/ui components into Vue.js projects. | [Link](https://marketplace.visualstudio.com/items?itemName=Selemondev.shadcn-vue) | 2024-03-28 |\n\n## Colors and Customizations\n\n| Name | Description | Link | Date |\n| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ---------- |\n| 10000+Themes for shadcn/ui | 10000+ Themes for shadcn/ui. | [Link](https://ui.jln.dev/) | 2024-02-22 |\n| dizzy | Bootstrap a new Next or Vite project with shadcn/ui. Customize font, icons, colors, spacing, radii, and shadows. | [Link](https://dizzy.systems) | 2024-06-05 |\n| ewgenius/ui | Create custom themes for shadcn/ui effortlessly using vibrant palettes from Radix Colors. | [Link](https://ui.ewgenius.me/shadcn-radix-colors) | 2024-10-11 |\n| gradient-picker | Fancy Gradient Picker built with shadcn/ui, Radix UI, and Tailwind CSS. | [Link](https://github.com/Illyism/gradient-picker) | 2024-03-08 |\n| navnote/rangeen | Tool that helps you to create a colour palette for your website. | [Link](https://github.com/navnote/rangeen) | 2024-06-05 |\n| shadesigner.com | A shadcn/ui Palette Generator & Theme Designer with a beautiful interface. | [Link](https://shadesigner.com) | 2024-12-26 |\n| shadcn-ui-customizer | POC - shadcn/ui themes with color pickers. | [Link](https://github.com/Railly/shadcn-ui-customizer) | 2024-03-08 |\n| shadcn theme editor | Shadcn Theme Editor is a user-friendly component designed to simplify the process of managing and customizing theme colors in Shadcn-based projects. | [Link](https://github.com/programming-with-ia/shadcn-theme-editor/) | 2024-08-19 |\n| smui | Nord-inspired terminal theme for shadcn/ui with monospace typography, zero border radius, and frost-blue accents. | [Link](https://smui.statico.io) | 2026-02-18 |\n| tweakcn | powerful theme editor for shadcn/ui components, offering beautifully designed themes and seamless Tailwind CSS V4 integration | [Link](https://tweakcn.com/) | 2025-03-25 |\n| ui-colorgen | An application designed to assist you with color configuration of shadcn/ui. | [Link](https://ui-colorgen.vercel.app/) | 2024-02-22 |\n| zippy starter's shadcn/ui theme generator | Easily create custom themes from a single colour that you can copy and paste into your apps. | [Link](https://zippystarter.com/tools/shadcn-ui-theme-generator) | 2024-03-13 |\n\n## Animations\n\n| Name | Description | Link | Date |\n| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ---------- |\n| animata | Hand-crafted ✍️ interaction animations and effects from around the internet 🛜 to copy and paste into your project. | [Link](https://animata.design) | 2024-08-26 |\n| animate-ui | A fully animated, open-source React component distribution. Browse a list of animated primitives, components and icons you can install and use in your projects. | [Link](https://animate-ui.com/) | 2025-10-15 |\n| magicui.design | Largest collection of open-source react components to build beautiful landing pages. | [Link](https://magicui.design) | 2024-04-25 |\n| motionvariants | Beautiful Framer Motion Animations. | [Link](https://github.com/chrisabdo/motionvariants) | 2024-03-08 |\n| smooth-ui | Highly customizable, production-ready UI blocks for building beautiful websites and apps that look and feel the way you mean it. | [Link](https://smoothui.dev/) | 2025-10-15 |\n| tailwindcss-motion | A new simple syntax animation library. Batteries included. Infinitely configurable. | [Link](https://rombo.co/tailwind/) | 2024-11-13 |\n\n## Tools\n\n| Name | Description | Link | Date |\n| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ---------- |\n| 5devs | A website to get fake Brazilian data for testing purposes. | [Link](https://www.5devs.com.br/) | 2024-05-31 |\n| bento-hub | BentoHub is an application where you can create a bento grid for your GitHub profile readme. | [Link](https://github.com/amittam104/BentoHub) | 2024-09-09 |\n| cheatsheet | A comprehensive, interactive reference for shadcn/ui components with live previews, code examples, and instant copy functionality. | [Link](https://shadcnstore.com/cheatsheet/) | 2025-09-22 |\n| cut-it | Link shortener built using Next.js App Router, Server Actions, Drizzle ORM, Turso, and styled with shadcn/ui. | [Link](https://github.com/mehrabmp/cut-it) | 2024-06-07 |\n| country-data-in-charts | Globe Graph is a web app that visualizes countries' data like GDP, GDP per capita, and population in different years using many charts. | [Link](https://globe-graph.vercel.app/) | 2024-09-09 |\n| dev-quotes | A website that displays quotes from professional programmers. | [Link](https://dev-quotes-six.vercel.app/) | 2025-01-07 |\n| down-dev-detector | This app lists all the services currently down and uses Atlassian Status Page and others (soon). | [Link](https://github.com/birobirobiro/downdevdetector) | 2024-11-04 |\n| cv-forge | Resume builder built with @shadcn/ui, react-hook-form, and react-pdf. | [Link](https://cvforge.app) | 2024-11-04 |\n| focus-brew | A free productivity toolkit that combines essential tools to help you stay focused, organized, and efficient throughout your workday. | [Link](https://focusbrew.vercel.app) | 2025-05-14 |\n| form-builder | UI-based codegen tool to easily create beautiful and type-safe @shadcn/ui forms. | [Link](https://github.com/AlandSleman/FormBuilder) | 2024-06-07 |\n| form-builder-fast | Shadcn Form Builder - Build forms in minutes for free. | [Link](https://ui.indie-starter.dev/form-builder) | 2024-12-26 |\n| graphitup | Create free downloadable Shadcn-themed chart images. Supports PNG, JPEG, WEBP, and even WEBM videos. Upload your own data for more realistic designs. | [Link](https://graphitup.com/tools) | 2025-12-26 |\n| hook-again | A collection of shadcn/ui installable React Hooks. | [Link](https://github.com/ilyichv/hookagain) | 2024-11-04 |\n| Img2m3 | A developer tool that automates the process of creating cohesive, accessible design systems. It bridges the gap between Google's Material Design 3 algorithms and the modern Tailwind CSS v4 ecosystem. | [Link](https://img2m3.vercel.app/studio) | 2026-01-23 |\n| imgsrc | Generate beautiful Open Graph images with zero effort. | [Link](https://imgsrc.io/) | 2024-05-31 |\n| invoify | An invoice generator app built using Next.js, TypeScript, and shadcn/ui. | [Link](https://github.com/aliabb01/invoify) | 2024-03-27 |\n| jobsync | JobSync is a job seekers' assistant to manage job search efficiently. | [Link](https://github.com/Gsync/jobsync) | 2024-07-17 |\n| memfree | Open-source hybrid AI search engine, instantly get accurate answers from the internet, bookmarks, notes, and docs. Built using Next.js and shadcn/ui. | [Link](https://github.com/memfreeme/memfree) | 2024-07-22 |\n| opensearch-ai | SearchGPT/Perplexity clone but personalized for you. | [Link](https://github.com/supermemoryai/opensearch-ai) | 2024-12-26 |\n| pagegen.ai | An AI Page Generator with Claude AI, React, and shadcn/ui. Generate web pages from text, screenshots, and templates with one click. | [Link](https://pagegen.ai) | 2024-12-26 |\n| pastecode | Pastebin alternative built with TypeScript, Next.js, Drizzle, shadcn/ui, and RSC. | [Link](https://github.com/Quorin/PasteCode.app) | 2024-06-07 |\n| pictera | Generate Open Graph images without design skills. | [Link](https://pictera.co) | 2025-10-08 |\n| proxmox-helper-scripts | A catalog of scripts for your Proxmox VE homelab, built with the Next.js App Router and styled with shadcn/ui. | [Link](https://github.com/BramSuurdje/proxmox-helper-scripts) | 2024-07-16 |\n| quack-db | Open-source in-browser DuckDB SQL editor. | [Link](https://github.com/mattf96s/QuackDB) | 2024-10-03 |\n| shadcn-easy-install | Install all shadcn components easily. One-click to install all selected components. | [Link](https://shadcn-easy-install.vercel.app/) | 2025-06-10 |\n| shadcn-form-builder | Create forms with Shadcn, react-hook-form, and Zod within minutes. | [Link](https://shadcn-form-build.vercel.app/) | 2024-10-03 |\n| shadcn-hooks | A comprehensive React Hooks Collection built with Shadcn. | [Link](https://shadcn-hooks.com/) | 2025-10-13 |\n| shadcn-play | A playground for building and previewing shadcn/ui components with a live editor. | [Link](https://github.com/ephraimduncan/shadcn-play) | 2026-02-13 |\n| shadcn-pricing-page-generator | The easiest way to get a React pricing page with shadcn/ui, Radix UI, and/or Tailwind CSS. | [Link](https://shipixen.com/shadcn-pricing-page) | 2024-03-08 |\n| shadcn-theme-editor | Shadcn Theme Editor is a user-friendly component designed to simplify the process of managing and customizing theme colors in Shadcn-based projects. | [Link](https://shadcnthemeeditor.vercel.app) | 2024-08-19 |\n| shadcn-zod-form | CLI tool to generate shadcn/ui forms from Zod schemas. | [Link](https://github.com/ilyichv/shadcn-zod-form) | 2024-10-03 |\n| sharable-form-builder | A sharable form builder for creating forms and sharing your form link, based on shadcn/ui and Next.js. | [Link](https://github.com/ayoubben18/sharable-form-builder) | 2024-10-12 |\n| shoogle | A shadcn search engine | [Link](https://shoogle.dev/) | 2026-02-13 |\n| slidytabs | A tool that adds a sliding indicator animation to shadcn `<Tabs />` without changing how you use or customize the component | [Link](https://slidytabs.dev) | 2026-01-23 |\n| someday | Free to host and open-source Cal.com/Calendly alternative built on Google Apps Script for Gmail users. | [Link](https://github.com/rbbydotdev/someday) | 2024-12-26 |\n| sweep | Sweep is a modern, open-source gradient generator built for designers and developers. Create beautiful linear and radial gradients with real-time preview, noise/blur effects, and export to CSS, Tailwind, SVG, or JPG. Free forever. No sign-up required. | [Link](https://github.com/Johuniq/sweep) | 2025-11-11 |\n| tancn | Build powerful forms and tables with ease using TanStack technologies | [Link](https://tancn.dev/) | 2025-11-02 |\n| tinte | An opinionated VS Code Theme Generator 🎨. | [Link](https://tinte.railly.dev/) | 2024-10-03 |\n| translate-app | Translate App using TypeScript, Tailwind CSS, NextJS, Bun, shadcn/ui, AI SDK/OpenAI, and Zod. | [Link](https://github.com/developaul/translate-app) | 2024-07-08 |\n| typelabs | MonkeyType-inspired typing test app built with React, shadcn, and Zustand at its core. | [Link](https://github.com/imsandeshpandey/typelabs) | 2024-07-08 |\n| ui-builder | A React component editor that provides a no-code, visual way to create UIs, fully compatible with shadcn/ui and custom components. | [Link](https://github.com/olliethedev/ui-builder) | 2024-10-11 |\n| ui-fonts | Test and preview fonts in real-time for all your design needs. Choose the perfect typeface with ease. | [Link](https://www.uifonts.app/) | 2024-10-23 |\n| v0 | Vercel's generative UI system, built on shadcn/ui and TailwindCSS, allows effortless UI generation from text prompts and/or images. | [Link](https://v0.dev/) | 2024-03-27 |\n| vercel-status-tracker | Track the status of all of your projects deployed via Vercel. Built with shadcn/ui and TailwindCSS. | [Link](https://vercel-status-tracker.vercel.app) | 2025-01-02 |\n| wallhaven-desktop | Wallhaven Wallpaper software desktop. Create a Wallhaven API-based client, a true wallpaper software. | [Link](https://github.com/ErKeLost/wallhaven-desktop) | 2024-10-23 |\n\n## Websites and Portfolios\n\n| Name | Description | Link | Date |\n| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | ---------- |\n| andrewsam.xyz | A revamped version of the popular tailwind-nextjs-starter-blog using shadcn/ui, along with a resume section and experience timeline component. | [Link](https://www.andrewsam.xyz/) | 2024-09-18 |\n| anishshobithps.com | Personal portfolio of a software developer, grid-styled design . | [Link](https://anishshobithps.com/) | 2026-02-27 |\n| birobirobiro.dev | A personal developer portfolio. | [Link](https://birobirobiro.dev/) | 2024-07-29 |\n| bucharitesh.in | A minimal portfolio with awesome craft's registry. | [Link](https://bucharitesh.in) | 2025-12-25 |\n| chanhdai.com | A minimal portfolio, component registry, and blog. | [Link](https://chanhdai.com) | 2025-09-11 |\n| devfolios | Find best portfolio inspiration from all over the internet | [Link](https://devfolios-one.vercel.app/) | 2025-10-25 |\n| godly | Astronomically good web design inspiration. Only the best of the best. | [Link](https://godly.website/) | 2024-07-29 |\n| hritu.art | A clean, modern designer portfolio blending minimal aesthetics with functional UI and built-in email support via React Email. | [Link](https://github.com/suraj-xd/design-portfolio) | 2025-06-10 |\n| kinhdev24 | Developer portfolio built with Next.js, shadcn/ui, Aceternity, and Magic UI | [Link](https://kinhdev.id.vn/) | 2025-07-08 |\n| list.swajp.me | It has never been easier to find the right projects or designs by inspiring successful people. | [Link](https://list.swajp.me) | 2024-07-29 |\n| mpakravan | Portfolio website using React, Next.js, Tailwind CSS, ShadCN, and GSAP, modern design system. | [Link](https://mpakravan.com/en) | 2025-10-26 |\n| nathans-ai | An AI Chatbot acting as a portfolio, built with shadcn/ui components. | [Link](https://chat.brodin.dev) | 2024-11-21 |\n| ifte-13 | Portfolio made with Next.js, shadcn/ui (with magic ui ) and Email.js | [Link](https://ifte-13.vercel.app/) | 2025-10-25 |\n| shubhporwal.me | An eye-catching developer portfolio, built on NextJS, GSAP, Tailwind, and React. | [Link](https://www.shubhporwal.me/) | 2024-10-03 |\n| swajp.me | A visually appealing portfolio and resource hub. | [Link](https://swajp.me) | 2024-07-29 |\n| windows-11-clone | A sleek Windows 11 clone built with React, Next.js, Tailwind CSS, ShadCN, and Framer-Motion, featuring smooth animations, draggable windows, and a modern design system. | [Link](https://win11.oimmi.com/) | 2025-02-20 |\n\n## Platforms\n\n| Name | Description | Link | Date |\n| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ---------- |\n| anonypost | Share your thoughts and experiences anonymously by posting on the platform. Crafted using t3-stack. | [Link](https://github.com/avalynndev/anonypost) | 2024-07-17 |\n| bolhadev | The quickest path to learn English is speaking it regularly. Just find someone to chat with. | [Link](https://bolhadev.chat/) | 2024-06-04 |\n| citeme | AI-powered academic citation generator. Searches 11+ databases and formats in 40+ styles (APA, ABNT, MLA, etc.). Web app, Chrome extension, Google Docs add-on, and Word add-in. | [Link](https://citeme.app) | 2026-03-03 |\n| enjoytown | A free anime, manga, movie, and TV-shows streaming platform. Built with Next.js, shadcn/ui. | [Link](https://github.com/avalynndev/enjoytown) | 2024-06-04 |\n| grade-calculator | A grade calculator/dashboard for students, aiming to provide a better overview of academic performance. | [Link](https://grades.nstr.dev/) | 2024-12-27 |\n| infinitunes | A simple music player web app built using Next.js, shadcn/ui, Tailwind CSS, Drizzle ORM, and more. | [Link](https://github.com/rajput-hemant/infinitunes) | 2024-06-04 |\n| kd | Ad-free Kdrama streaming app. Built with Next.js, Drizzle ORM, NeonDB, and shadcn/ui. | [Link](https://github.com/gneiru/kd) | 2024-05-31 |\n| memergez | Quickly generate memes by entering text or an avatar URL, with support for many meme commands. | [Link](https://github.com/avalynndev/memergez) | 2024-08-26 |\n| midday-components | A collection of open-source components based on Midday features. | [Link](https://midday.ai/components) | 2024-11-21 |\n| multiboard | Minimal Kanban platform. Built with Better-Auth, Next.js, ZenStack, Prisma, and shadcn/ui. | [Link](https://github.com/olliethedev/multiboard) | 2025-07-15 |\n| openhive | Open-source, self-hosted Slack alternative with channels, DMs, threads, reactions, file uploads, and video/audio calls. Built with Next.js, Supabase, Zustand, and shadcn/ui. | [Link](https://github.com/arseneHuot/openhive) | 2026-03-14 |\n| plotwist | Easy management and reviews of your movies, series, and animes using Next.js, Tailwind CSS, Supabase, and shadcn/ui. | [Link](https://plotwist.app/en-US) | 2024-05-31 |\n| snapimg | Fast, privacy-focused image compression tool supporting PNG, JPEG, WebP, AVIF. Built with React 19, Vite, Tailwind CSS, and shadcn/ui. | [Link](https://github.com/Moresl/snapimg) | 2025-12-15 |\n| veritas-kanban | Self-hosted Kanban board with AI agent integration, time tracking, and 1,255 tests. Built with React 19, Vite 6, TanStack Query, dnd-kit, and shadcn/ui. | [Link](https://github.com/BradGroux/veritas-kanban) | 2026-01-29 |\n| xeramail | Temporary email address service built with Next.js and shadcn/ui, offering fast inbox access, modern UI, and better control over disposable emails. | [Link](https://xeramail.com) | 2026-01-01 |\n| youropinion.is | Free survey platform which supports importing your exisitng shadcn/ui theme | [Link](https://youropinion.is/news/202505-match-my-style) | 2025-06-16 |\n\n## Ports\n\n| Name | Description | Link | Date |\n| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ---------- |\n| Angular | Angular port of shadcn/ui. | [Link](https://github.com/goetzrobin/spartan) | 2024-03-21 |\n| Basecoat | Vanilla HTML, CSS and JS port of shadcn/ui. | [Link](https://basecoatui.com) | 2025-07-07 |\n| Flutter | Flutter port of shadcn/ui. | [Link](https://github.com/nank1ro/shadcn-ui) | 2024-06-07 |\n| Franken UI | HTML-first, framework-agnostic, beautifully designed components that you can truly copy and paste into your site. Accessible. Customizable. Open Source. | [Link](https://www.franken-ui.dev/) | 2024-06-07 |\n| JollyUI | shadcn/ui compatible react aria components. | [Link](https://github.com/jolbol1/jolly-ui) | 2024-06-07 |\n| Kotlin | Kotlin port of shadcn/ui. | [Link](https://github.com/dead8309/shadcn-kotlin) | 2024-06-07 |\n| mkdocs-shadcn | MkDocs port of shadcn/ui. | [Link](https://github.com/asiffer/mkdocs-shadcn) | 2025-07-07 |\n| .NET (ShadUI) | Avalonia port of shadcn/ui. Based on SukiUI | [Link](https://github.com/accntech/shad-ui/) | 2024-02-22 |\n| Phoenix Liveview | Phoenix Liveview port of shadcn/ui. | [Link](https://github.com/bluzky/salad_ui) | 2024-06-07 |\n| React Native | React Native port of shadcn/ui. | [Link](https://github.com/Mobilecn-UI/nativecn-ui) | 2024-06-07 |\n| React Native (recommended) | React Native port of shadcn/ui (recommended). | [Link](https://github.com/mrzachnugent/react-native-reusables) | 2024-12-27 |\n| Ruby | Ruby port of shadcn/ui. | [Link](https://github.com/aviflombaum/shadcn-rails) | 2024-06-07 |\n| Solid | Solid port of shadcn/ui. | [Link](https://github.com/hngngn/shadcn-solid) | 2024-03-28 |\n| Svelte | Svelte port of shadcn/ui. | [Link](https://github.com/huntabyte/shadcn-svelte) | 2024-02-22 |\n| Swift | Swift port of shadcn/ui. | [Link](https://github.com/Mobilecn-UI/swiftcn-ui) | 2024-06-07 |\n| Sysinfocus simple/ui | Razor component library for Blazor, inspired by shadcn/ui. | [Link](https://sysinfocus.github.io/shadcn-inspired/) | 2024-09-09 |\n| Vue | Vue port of shadcn/ui. | [Link](https://github.com/radix-vue/shadcn-vue) | 2024-02-22 |\n\n## Design System\n\n| Name | Description | Link | Date |\n| ---------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ---------- |\n| shadcn-storybook-registry | Registry of stories for the shadcn components. Quickly get the atomic level components documented in Storybook. | [Link](https://registry.lloydrichards.dev/) | 2025-02-07 |\n| obra-shadcn-ui | This file replicates all 51 shadcn/ui v4 components in a composable way as a reusable Figma library.. | [Link](https://www.figma.com/community/file/1514746685758799870/obra-shadcn-ui) | 2025-06-16 |\n| shadcn-ui-components | Every component recreated in Figma. | [Link](https://www.figma.com/community/file/1342715840824755935/shadcn-ui-components) | 2024-03-21 |\n| shadcn-ui-storybook (JheanAntunes) | All shadcn/ui components registered in the storybook by JheanAntunes. | [Link](https://65711ecf32bae758b457ae34-uryqbzvojc.chromatic.com/) | 2024-12-27 |\n| shadcn-ui-storybook (fellipeutaka) | All shadcn/ui components registered in the storybook by fellipeutaka. | [Link](https://fellipeutaka-ui.vercel.app/?path=/docs/components-accordion--docs) | 2024-12-27 |\n\n## Boilerplates / Templates\n\n| Name | Description | Link | Date |\n| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |\n| agentic-react-nextjs-shadcn | Agent-testable SaaS starter built with Next.js 16, shadcn/ui, and Tailwind CSS. Includes accessibility-first components, semantic HTML for AI agent testing, and production-ready patterns. | [Link](https://github.com/iscale-llc/agentic-react-nextjs-shadcn) | 2026-03-03 |\n| astro-erudite | An opinionated, unstyled static blogging template—built with Astro, Tailwind, and shadcn/ui. | [Link](https://github.com/jktrn/astro-erudite) | 2025-10-25 |\n| atomic-crm | Open-source React CRM built on top of Supabase [Demo site](https://marmelab.com/atomic-crm-demo) | [Link](https://github.com/marmelab/atomic-crm) | 2025-09-16 |\n| autoflow | An open source GraphRAG (Knowledge Graph) built on top of TiDB Vector, LlamaIndex, and DSPy. [Demo site](https://tidb.ai). | [Link](https://github.com/pingcap/autoflow) | 2024-12-06 |\n| browser-extension-starter-plasmo-shadcn-trpc | Browser extension starter kit featuring Plasmo, React, Shadcn, and tRPC. | [Link](https://github.com/poweroutlet2/browser-extension-starter-plasmo-shadcn-trpc) | 2024-10-29 |\n| chadnext | Quick Starter Template includes Next.js 14 App Router, shadcn/ui, LuciaAuth, Prisma, Server Actions, Stripe, Internationalization, and more. | [Link](https://github.com/moinulmoin/chadnext) | 2024-04-25 |\n| cloudflare-saas-stack | An opinionated, batteries-included starter kit for quickly building and deploying SaaS products on Cloudflare. | [Link](https://github.com/Dhravya/cloudflare-saas-stack) | 2024-07-24 |\n| create-tauri-core | A project template for creating a Tauri app with Vite, React, and Tailwind CSS. | [Link](https://github.com/mrlightful/create-tauri-core) | 2024-09-23 |\n| design-system-template | Turborepo + TailwindCSS + Storybook + shadcn/ui. | [Link](https://github.com/arevalolance/design-system-template) | 2024-06-06 |\n| devstarter | Devstarter is a bold, one-page developer portfolio template with a distinctive cyberpunk aesthetic. Built with Next.js, shadcn/ui, and Tailwind CSS, it's designed to help developers showcase their work, skills, and personality in a way that stands out. | [Link](https://github.com/zippystarter/template-devstarter) | 2026-01-27 |\n| easy-ui | 50+ High Quality Open Source Website Templates built using NextJS + shadcn/ui + Tailwind CSS + Framer Motion and more. | [Link](https://github.com/DarkInventor/easy-ui) | 2024-08-06 |\n| ecommerce-kit | Next.js starter kit with the tools you need to quickly launch your e-commerce site. | [Link](https://ecommercekit.dev) | 2025-09-06 |\n| electron-shadcn | Electron app template with shadcn/ui and a bunch of other libs and tools ready to use. | [Link](https://github.com/LuanRoger/electron-shadcn) | 2024-06-17 |\n| forjnot | Modern Project Starter Kit | Launch your next project faster with Forjnot - A professional, customizable and clean starting point featuring modern tech stack and best practices | 2025-11-20 |\n| full-stack-monorepo-starter | Full stack monorepo template built using shadcn/ui + Fastify + graphql + vitejs + Docker and more. | [Link](https://github.com/mnove/monorepo-starter-graphql) | 2025-06-10 |\n| fumadocs-starter | A fully-fledged Fumadocs starter template with built-in plugins, AI features, and everything you need to build your next docs site. | [Link](https://github.com/techwithanirudh/fumadocs-starter) | 2025-11-20 |\n| horizon-ai-nextjs-shadcn-boilerplate | Premium AI NextJS & shadcn/ui Boilerplate + Stripe + Supabase + OAuth. | [Link](https://horizon-ui.com/boilerplate-shadcn) | 2024-05-24 |\n| kirimase | A template and boilerplate for quickly starting your next project with shadcn/ui, Tailwind CSS, and Next.js. | [Link](https://kirimase.dev/) | 2024-06-11 |\n| login-auth | A login authentication web app built with Vite + React, Tailwind CSS, and Shadcn UI. It uses Firebase for Google sign-in, email sign-up, and password reset. | [Link](https://shadcn-login-auth.vercel.app/) | 2026-03-15 |\n| magicui-startup-templates | Magic UI Startup template built using shadcn/ui + TailwindCSS + Framer Motion. | [Link](https://magicui.design/docs/templates/startup) | 2024-04-25 |\n| next-shadcn-admin-dashboard | Modern Admin Dashboard Template built with Shadcn UI and Next.js 15 | [Link](https://github.com/arhamkhnz/next-shadcn-admin-dashboard) | 2025-10-25 |\n| nextMotion | Webdev portfolio template with Nodemailer integrated for easy contact form setup. Uses shadcn/ui + TailwindCSS + Framer Motion. | [Link](https://github.com/yoyocharlie/nextMotion) | 2024-09-23 |\n| next-js-boilerplate | Quickly set up a Next.js project with TypeScript, NextAuth.js, PostgreSQL (Prisma), Sentry, Tailwind CSS v4, shadcn/ui, Zod, Zustand, nuqs, ESLint, Husky, and Prettier. [Demo site](https://next-js-boilerplate-sage-nine.vercel.app/). | [Link](https://github.com/AbhishekSharma55/next-js-boilerplate) | 2025-09-08 |\n| next-shadcn-dashboard-starter | Admin Dashboard Starter with Nextjs 14 and shadcn/ui. | [Link](https://github.com/Kiranism/next-shadcn-dashboard-starter) | 2024-06-06 |\n| next-starter | A Next.js starter template packed with features like TypeScript, TailwindCSS, Next-auth, Eslint, Stripe, testing tools, and more. Jumpstart your project with efficiency and style. | [Link](https://github.com/Skolaczk/next-starter) | 2024-09-23 |\n| nextjs-mdx-blog | Starter template built with Contentlayer, MDX, shadcn/ui, and Tailwind CSS. | [Link](https://github.com/ChangoMan/nextjs-mdx-blog) | 2024-04-25 |\n| next-js-views-template | An open-source collection of reusable view components like Calendar, Table, etc., built with Next.js and ShadCN. Easily copy and paste these pre-built UI elements into your project for fast, responsive, and customizable layouts. | [Link](https://next-js-views-template.vercel.app) | 2024-11-21 |\n| next-wp | Headless Wordpress Starter built with the NextJS App Router and React Server Components. | [Link](https://github.com/9d8dev/next-wp) | 2024-11-21 |\n| onyx | Full stack, batteries-included MVP Template with NextJS 14, Supabase SSR Auth & Postgres DB with CRUD operations, RBAC, Tanstack React Query, Zod Validation, MDX components, Resend, and more. | [Link](https://github.com/rmourey26/onyx) | 2024-08-13 |\n| opendocs | Beautifully designed template that you can use for your projects for free. Accessible. Customizable. Open Source with i18n support. | [Link](https://opendocs.daltonmenezes.com/) | 2024-07-29 |\n| react-starter-kit | An opinionated, full-stack boilerplate for building modern web apps on the edge. Features Bun, React 19, tRPC, Drizzle ORM, and Cloudflare Workers. | [Link](https://github.com/kriasoft/react-starter-kit) | 2025-09-06 |\n| react-vite-starter | React starter powered with Vite + Redux Toolkit + RTKQuery + React Router + shadcn UI and many more. | [Link](https://github.com/tejachundru/react-vite-starter) | 2024-12-02 |\n| shadcn-admin | A admin dashboard template for Next.js, React, Vite and Vue.js, built with Tailwind CSS. | [Link](https://shadcnadmin.com) | 2025-12-12 |\n| shadcn-landing-page | Landing page template using shadcn/ui, React, TypeScript, and Tailwind CSS. | [Link](https://github.com/leoMirandaa/shadcn-landing-page) | 2024-06-06 |\n| shadcn-landing-page (Vue) | Project conversion [shadcn-vue-landing-page](https://github.com/leoMirandaa/shadcn-vue-landing-page) to Next.js - Landing page template using Nestjs, shadcn/ui, TypeScript, and Tailwind CSS. | [Link](https://github.com/nobruf/shadcn-landing-page) | 2024-12-27 |\n| shadcn-nextjs-free-boilerplate | Free & Open-source NextJS Boilerplate + ChatGPT API Dashboard Template. | [Link](https://github.com/horizon-ui/shadcn-nextjs-boilerplate) | 2024-05-24 |\n| shadcn-nextjs-dashboard | Admin Dashboard UI built with Shadcn and NextJS. Free and Open-source. | [Link](https://github.com/NaveenDA/shadcn-nextjs-dashboard) | 2025-06-22 |\n| shadcn-portfolio | A portfolio template, which uses shadcn-ui and Next.JS. | [Link](https://github.com/techwithanirudh/shadcn-portfolio) | 2025-11-20 |\n| shadcn-registry-template | Template repository for building a custom component registry for shadcn/ui. | [Link](https://github.com/vantezzen/shadcn-registry-template) | 2024-09-05 |\n| shadcn-saas-landing | A full-fledged SaaS Landing template built using Next.JS, shadcn/ui, and fumadocs. | [Link](https://github.com/techwithanirudh/shadcn-saas-landing) | 2025-11-20 |\n| shadcn-ui-dashboard | Multipurpose and powerful admin dashboard template compatible with shadcn/ui. | [Link](https://shadcnuidashboard.com) | 2025-09-21 |\n| shadcn-vue-landing-page | Landing page template using Vue, shadcn-vue, TypeScript, and Tailwind CSS. | [Link](https://github.com/leoMirandaa/shadcn-vue-landing-page) | 2024-06-06 |\n| shadcn-next-workflows | Interactive workflow builder using React Flows, Next.js, and Shadcn/ui. Create, connect, and validate custom nodes easily. | [Link](https://github.com/nobruf/shadcn-next-workflows) | 2024-10-29 |\n| supa-next-shad-auth | A fully responsive, fully type-safe, secure server actions, user-friendly customizable UI with best practices. Tech used: NextJS + Supabase + TypeScript + Server Actions + Zod + shadcn/ui. | [Link](https://github.com/Sahil-Sharma-23/supa-next-shad-auth) | 2024-07-02 |\n| sveltekit-shadcn-starter-kit | Production ready open-source generic app template featuring database abstraction (Drizzle & Postgres), server utilities, tests, authentication (better-auth), i18n + RTL/LTR support, mdsvex, predefined pages and content (policies, legal, etc.), App Shell component, base and custom components built with shadcn/ui, Cookies management (compliance), SEO managemnt support, CLI tooling and more. [Demo](https://ssv5.templates.guylahav.com) | [Link](https://github.com/GantonL/templates/tree/main/sveltekit-shadcn-v5) | 2025-09-23 |\n| t3-app-template | Admin template for T3 Stack and shadcn/ui. | [Link](https://github.com/gaofubin/t3-app-template) | 2024-04-25 |\n| tailwind-admin | Open Source Shadcn Dashboard Template Built On React and Tailwind CSS | [Link](https://github.com/Tailwind-Admin/free-tailwind-admin-dashboard-template) | 2026-01-03 |\n| taxonomy | An open-source application built using the new router, server components, and everything new in Next.js. | [Link](https://github.com/shadcn/taxonomy) | 2024-03-21 |\n| template-next | A clean Next.js template with TypeScript, TailwindCSS, Shadcn/ui, and Prettier. | [Link](https://template-next-official.vercel.app/) | 2024-09-24 |\n| turborepo-nextjs-wxt-shadcn-boilerplate | Turborepo boilerplate featuring web and web-extension apps with shadcn/ui for shared ui components and unified Typescript, ESLint, Tailwind CSS, and Prettier configs. | [Link](https://github.com/Aniket-508/turborepo-nextjs-wxt-shadcn-boilerplate) | 2025-04-24 |\n| turborepo-shadcn-ui-tailwindcss | Turborepo starter with shadcn/ui & TailwindCSS pre-configured for shared UI components. | [Link](https://github.com/henriqpohl/turborepo-shadcn-ui-tailwindcss) | 2024-06-06 |\n| turborepo-launchpad | A comprehensive monorepo boilerplate for shadcn projects using Turbo. It features a highly scalable setup ideal for developing complex applications with shared components and utilities. | [Link](https://github.com/JadRizk/turborepo-launchpad) | 2024-06-10 |\n| waitly | A simple and useful waitlist Next.js and Shadcn UI template. | [Link](https://shadcnuikit.com/template/waitly-free-waitlist-template) | 2025-12-19 |\n| wordpress-plugin-boilerplate | WordPress Plugin Boilerplate utilizing modern web technologies and tools such as React, TypeScript, SASS, TailwindCSS, Shadcn UI, Vite, Grunt.js, Storybook, HMR, and more. | [Link](https://github.com/prappo/wordpress-plugin-boilerplate) | 2024-09-24 |\n\n## Star History\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=birobirobiro/awesome-shadcn-ui&type=Date&theme=dark\" />\n  <source media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=birobirobiro/awesome-shadcn-ui&type=Date\" />\n  <img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=birobirobiro/awesome-shadcn-ui&type=Date\" />\n</picture>\n\n## Contributors\n\nThanks goes to all these wonderful people:\n\n<a href=\"https://github.com/birobirobiro/awesome-shadcn-ui/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=birobirobiro/awesome-shadcn-ui\" />\n</a>\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Development & Contributing\n\n### For the Website\n\n- **Development Guide**: See [DEVELOPMENT.md](DEVELOPMENT.md) for setup, architecture, and configuration\n- **Contributing**: Fork the repository, create a feature branch, and submit a PR\n\n### For the Awesome List\n\n- **Via Website**: Use the built-in submission form at [awesome-shadcn-ui.vercel.app](https://awesome-shadcn-ui.vercel.app/)\n- **Via GitHub**: Follow the [PR template](.github/pull_request_template.md) when creating pull requests\n- **Guidelines**: Ensure resources are shadcn/ui related, well-documented, and actively maintained\n"
  },
  {
    "path": "components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"radix-lyra\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"src/app/globals.css\",\n    \"baseColor\": \"zinc\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"iconLibrary\": \"lucide\",\n  \"rtl\": false,\n  \"menuColor\": \"default\",\n  \"menuAccent\": \"subtle\",\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\",\n    \"ui\": \"@/components/ui\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"registries\": {\n    \"@diceui\": \"https://diceui.com/r/{name}.json\"\n  }\n}\n"
  },
  {
    "path": "next.config.mjs",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  reactStrictMode: true,\n  eslint: {\n    ignoreDuringBuilds: true,\n  },\n  typescript: {\n    ignoreBuildErrors: true,\n  },\n  compiler: {\n    removeConsole:\n      process.env.NODE_ENV === \"production\" ? { exclude: [\"error\"] } : false,\n  },\n};\n\nexport default nextConfig;\n"
  },
  {
    "path": "open-next.config.ts",
    "content": "import { defineCloudflareConfig } from \"@opennextjs/cloudflare\";\n\nexport default defineCloudflareConfig();\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"awesome-shadcn-ui\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbo\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"preview\": \"opennextjs-cloudflare build && opennextjs-cloudflare preview\",\n    \"deploy\": \"opennextjs-cloudflare build && opennextjs-cloudflare deploy\",\n    \"cf-typegen\": \"wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts\",\n    \"format\": \"prettier --write ./src\",\n    \"lint\": \"next lint\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {\n    \"@base-ui/react\": \"^1.2.0\",\n    \"@hugeicons/core-free-icons\": \"^3.3.0\",\n    \"@hugeicons/react\": \"^1.1.5\",\n    \"@next/third-parties\": \"^16.1.6\",\n    \"@octokit/rest\": \"^22.0.0\",\n    \"@opennextjs/cloudflare\": \"^1.16.1\",\n    \"@radix-ui/react-avatar\": \"1.1.9\",\n    \"@radix-ui/react-dialog\": \"1.1.13\",\n    \"@radix-ui/react-dropdown-menu\": \"2.1.14\",\n    \"@radix-ui/react-icons\": \"^1.3.0\",\n    \"@radix-ui/react-label\": \"^2.1.7\",\n    \"@radix-ui/react-navigation-menu\": \"^1.2.14\",\n    \"@radix-ui/react-popover\": \"1.1.13\",\n    \"@radix-ui/react-scroll-area\": \"^1.2.10\",\n    \"@radix-ui/react-select\": \"2.2.4\",\n    \"@radix-ui/react-separator\": \"1.1.6\",\n    \"@radix-ui/react-slot\": \"1.2.2\",\n    \"@radix-ui/react-toggle\": \"1.1.8\",\n    \"@radix-ui/react-toggle-group\": \"1.1.9\",\n    \"@radix-ui/react-tooltip\": \"1.2.6\",\n    \"@radix-ui/react-use-controllable-state\": \"^1.2.2\",\n    \"@tailwindcss/postcss\": \"^4.1.11\",\n    \"axios\": \"^1.12.0\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"cmdk\": \"1.0.0\",\n    \"date-fns\": \"^4.1.0\",\n    \"embla-carousel-react\": \"^8.6.0\",\n    \"eslint-config-next\": \"^16.0.10\",\n    \"input-otp\": \"^1.4.2\",\n    \"lucide-react\": \"^0.509.0\",\n    \"motion\": \"^12.23.24\",\n    \"next\": \"16.0.10\",\n    \"next-themes\": \"^0.4.6\",\n    \"radix-ui\": \"latest\",\n    \"react\": \"^19.2.3\",\n    \"react-day-picker\": \"^9.14.0\",\n    \"react-dom\": \"^19.2.3\",\n    \"react-resizable-panels\": \"^4.6.5\",\n    \"recharts\": \"2.15.4\",\n    \"sonner\": \"^1.7.4\",\n    \"vaul\": \"^1.1.2\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"24.0.10\",\n    \"@types/react\": \"^19\",\n    \"prettier\": \"^3\",\n    \"shadcn\": \"^3.8.5\",\n    \"tailwind-merge\": \"^2.6.0\",\n    \"tailwindcss\": \"^4.1.11\",\n    \"tw-animate-css\": \"^1.3.4\",\n    \"typescript\": \"5.8.3\",\n    \"wrangler\": \"^4.61.1\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.mjs",
    "content": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "scripts/add-dates.js",
    "content": "const fs = require('fs');\n\nconst path = 'README.md';\nlet content;\ntry {\n  content = fs.readFileSync(path, 'utf8');\n} catch (error) {\n  console.error(`Error reading README.md: ${error.message}`);\n  process.exit(1);\n}\nconst lines = content.split('\\n');\n\nconst currentDate = new Date().toISOString().split('T')[0]; // e.g., 2025-07-02\nconst updatedLines = [];\nlet changesCount = 0;\n\nconsole.log(`Processing README.md with ${lines.length} lines`);\n\n// Function to validate ISO date format (YYYY-MM-DD)\nfunction isValidDate(dateString) {\n  const regex = /^\\d{4}-\\d{2}-\\d{2}$/;\n  if (!regex.test(dateString)) return false;\n  const date = new Date(dateString);\n  return date instanceof Date && !isNaN(date);\n}\n\nlet inTable = false;\nlet tableHasDateColumn = false;\nlet headerLineCount = 0;\nlet dateColumnIndex = -1;\n\nfor (let i = 0; i < lines.length; i++) {\n  const line = lines[i];\n\n  // Reset table state when hitting a new section\n  if (line.startsWith('## ')) {\n    inTable = false;\n    tableHasDateColumn = false;\n    headerLineCount = 0;\n    dateColumnIndex = -1;\n  }\n\n  if (line.startsWith('|')) {\n    if (!inTable) {\n      // First line of a new table (header)\n      inTable = true;\n      headerLineCount = 1;\n\n      // Check if the table header has a date column and find its index\n      const parts = line.split('|').map(part => part.trim());\n      dateColumnIndex = parts.findIndex(part => part === 'Date');\n      tableHasDateColumn = dateColumnIndex > -1;\n      updatedLines.push(line);\n      continue;\n    } else {\n      headerLineCount++;\n    }\n\n    // Skip header and separator rows\n    if (headerLineCount <= 2) {\n      updatedLines.push(line);\n      continue;\n    }\n\n    // Process data rows\n    if (tableHasDateColumn && dateColumnIndex > 0) {\n      // Split the line into parts, preserving the original structure\n      const parts = line.split('|');\n      \n      // Ensure we have enough parts for the date column\n      while (parts.length <= dateColumnIndex + 1) {\n        parts.push('');\n      }\n      \n      // Get the current date value (trimmed)\n      const currentDateValue = parts[dateColumnIndex].trim();\n      \n      // Only add date if it's empty, whitespace, or invalid\n      if (!currentDateValue || currentDateValue === '' || !isValidDate(currentDateValue)) {\n        parts[dateColumnIndex] = ` ${currentDate} `;\n        changesCount++;\n      }\n      \n      // Reconstruct the line\n      const newLine = parts.join('|');\n      updatedLines.push(newLine);\n    } else {\n      // No date column or invalid structure, keep as is\n      updatedLines.push(line);\n    }\n  } else {\n    // Not a table row\n    updatedLines.push(line);\n  }\n}\n\n// Log summary\nconsole.log(`Completed processing. Made ${changesCount} changes to date columns.`);\n\nif (changesCount > 0) {\n  try {\n    fs.writeFileSync(path, updatedLines.join('\\n'));\n    console.log('Changes written to README.md');\n  } catch (error) {\n    console.error(`Error writing to README.md: ${error.message}`);\n    process.exit(1);\n  }\n} else {\n  console.log('No changes needed, file not modified');\n}\n"
  },
  {
    "path": "scripts/format-readme.js",
    "content": "const fs = require('fs');\nconst path = 'README.md';\n\nlet content;\ntry {\n  content = fs.readFileSync(path, 'utf8');\n} catch (err) {\n  console.error(`Error reading README.md: ${err.message}`);\n  process.exit(1);\n}\n\n// Split the content into lines\nconst lines = content.split('\\n');\nconst updatedLines = [];\n\nlet inTable = false;\n\nfor (let line of lines) {\n  if (line.startsWith('|')) {\n    inTable = true;\n\n    // Remove internal line breaks and extra spaces in cells\n    const parts = line.split('|').map(cell => cell.replace(/\\n/g, ' ').trim());\n\n    // Rebuild the line with pipes, keeping one line per entry\n    const formattedLine = '| ' + parts.filter((_, i) => i !== 0 && i !== parts.length - 1 || parts[i] !== '').join(' | ') + ' |';\n\n    updatedLines.push(formattedLine);\n  } else {\n    if (inTable) {\n      inTable = false;\n    }\n    updatedLines.push(line);\n  }\n}\n\n// Overwrite README.md with the updated lines\ntry {\n  fs.writeFileSync(path, updatedLines.join('\\n'));\n  console.log('README.md formatted successfully!');\n} catch (err) {\n  console.error(`Error writing README.md: ${err.message}`);\n  process.exit(1);\n}"
  },
  {
    "path": "src/app/[...not-found]/page.tsx",
    "content": "import { notFound } from \"next/navigation\";\n\nexport default function CatchAllNotFound() {\n  notFound();\n}\n"
  },
  {
    "path": "src/app/api/github/callback/route.ts",
    "content": "import { NextRequest, NextResponse } from \"next/server\";\n\nexport async function GET(request: NextRequest) {\n  const searchParams = request.nextUrl.searchParams;\n  const code = searchParams.get(\"code\");\n  const state = searchParams.get(\"state\");\n  const error = searchParams.get(\"error\");\n\n  if (error) {\n    console.error(\"GitHub OAuth error:\", error);\n    return NextResponse.redirect(\n      new URL(`/?error=${encodeURIComponent(error)}`, request.url),\n    );\n  }\n\n  if (!code) {\n    console.error(\"No authorization code received\");\n    return NextResponse.redirect(new URL(\"/?error=no_code\", request.url));\n  }\n\n  // For device flow, we don't need to handle the callback here\n  // The device flow handles everything through the device-flow endpoint\n  // This endpoint is just required by GitHub App configuration\n  console.log(\"GitHub App callback received:\", { code, state });\n\n  return NextResponse.redirect(new URL(\"/?success=authorized\", request.url));\n}\n"
  },
  {
    "path": "src/app/api/github/device-flow/route.ts",
    "content": "import { ERROR_MESSAGES, GITHUB_CONFIG } from \"@/lib/config\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\nexport async function POST(request: NextRequest) {\n  try {\n    const body = await request.json();\n\n    if (body.action === \"start\") {\n      // Start the GitHub App device flow\n      const response = await fetch(GITHUB_CONFIG.DEVICE_FLOW_URL, {\n        method: \"POST\",\n        headers: GITHUB_CONFIG.API_HEADERS,\n        body: JSON.stringify({\n          client_id: GITHUB_CONFIG.CLIENT_ID,\n          scope: GITHUB_CONFIG.SCOPES.join(\" \"),\n        }),\n      });\n\n      if (!response.ok) {\n        const errorText = await response.text();\n        console.error(\n          \"GitHub device flow start error:\",\n          response.status,\n          errorText,\n        );\n        console.error(\"Request details:\", {\n          client_id: GITHUB_CONFIG.CLIENT_ID,\n          scope: GITHUB_CONFIG.SCOPES.join(\" \"),\n          url: GITHUB_CONFIG.DEVICE_FLOW_URL,\n        });\n        throw new Error(\n          `${ERROR_MESSAGES.GITHUB_API}: ${response.status} - ${errorText}`,\n        );\n      }\n\n      const data = await response.json();\n      return NextResponse.json(data);\n    } else if (body.action === \"poll\") {\n      // Poll for the GitHub App user access token\n      const response = await fetch(GITHUB_CONFIG.ACCESS_TOKEN_URL, {\n        method: \"POST\",\n        headers: GITHUB_CONFIG.API_HEADERS,\n        body: JSON.stringify({\n          client_id: GITHUB_CONFIG.CLIENT_ID,\n          device_code: body.device_code,\n          grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n        }),\n      });\n\n      if (!response.ok) {\n        const errorText = await response.text();\n        console.error(\n          \"GitHub device flow poll error:\",\n          response.status,\n          errorText,\n        );\n        throw new Error(\n          `${ERROR_MESSAGES.GITHUB_API}: ${response.status} - ${errorText}`,\n        );\n      }\n\n      const data = await response.json();\n      return NextResponse.json(data);\n    }\n\n    return NextResponse.json(\n      { error: ERROR_MESSAGES.INVALID_ACTION },\n      { status: 400 },\n    );\n  } catch (error: any) {\n    console.error(\"GitHub device flow error:\", error);\n    return NextResponse.json(\n      { error: error.message || ERROR_MESSAGES.INTERNAL_SERVER },\n      { status: 500 },\n    );\n  }\n}\n"
  },
  {
    "path": "src/app/api/submit-resource/route.ts",
    "content": "import { NextRequest, NextResponse } from \"next/server\";\nimport { Octokit } from \"@octokit/rest\";\n\nconst GITHUB_CONFIG = {\n  REPO_OWNER: \"birobirobiro\",\n  REPO_NAME: \"awesome-shadcn-ui\",\n  BRANCH: \"main\",\n};\n\ninterface SubmissionData {\n  name: string;\n  description: string;\n  url: string;\n  category: string;\n}\n\nfunction insertResourceIntoReadme(\n  readmeContent: string,\n  submission: SubmissionData,\n): { content: string; error?: string } {\n  const lines = readmeContent.split(\"\\n\");\n  const newEntry = `| ${submission.name} | ${submission.description} | [Link](${submission.url}) |`;\n\n  let insertIndex = -1;\n  let inTargetSection = false;\n  let inTable = false;\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i];\n\n    if (\n      line.startsWith(\"## \") &&\n      line.toLowerCase().includes(submission.category.toLowerCase())\n    ) {\n      inTargetSection = true;\n      continue;\n    }\n\n    if (\n      inTargetSection &&\n      line.startsWith(\"## \") &&\n      !line.toLowerCase().includes(submission.category.toLowerCase())\n    ) {\n      break;\n    }\n\n    if (inTargetSection && line.startsWith(\"| Name\")) {\n      inTable = true;\n      continue;\n    }\n\n    if (inTable && line.match(/^\\|[\\s-]+\\|/)) {\n      continue;\n    }\n\n    if (inTable && line.startsWith(\"|\") && !line.match(/^\\|[\\s-]+\\|/)) {\n      const parts = line.split(\"|\").map((part) => part.trim());\n      if (parts.length >= 2) {\n        const existingName = parts[1];\n\n        const existingUrlMatch = line.match(/\\[Link\\]\\(([^)]+)\\)/);\n        const existingUrl = existingUrlMatch ? existingUrlMatch[1] : null;\n\n        if (existingName.toLowerCase() === submission.name.toLowerCase()) {\n          return {\n            content: \"\",\n            error: `Resource \"${submission.name}\" already exists in this section.`,\n          };\n        }\n\n        if (\n          existingUrl &&\n          existingUrl.toLowerCase() === submission.url.toLowerCase()\n        ) {\n          return {\n            content: \"\",\n            error: `This URL already exists in this section.`,\n          };\n        }\n\n        if (existingName.toLowerCase() > submission.name.toLowerCase()) {\n          insertIndex = i;\n          break;\n        }\n      }\n    }\n\n    if (inTable && (!line.startsWith(\"|\") || line.trim() === \"\")) {\n      if (insertIndex === -1) {\n        insertIndex = i;\n      }\n      break;\n    }\n  }\n\n  if (insertIndex === -1) {\n    return {\n      content: \"\",\n      error: `Could not find insertion point for category \"${submission.category}\".`,\n    };\n  }\n\n  lines.splice(insertIndex, 0, newEntry);\n  return { content: lines.join(\"\\n\") };\n}\n\nfunction generatePRBody(submission: SubmissionData): string {\n  return `---\nname: \"feat: Add new awesome resource\"\nabout: \"Propose adding a new awesome resource related to shadcn/ui\"\nlabels:\n  - feature\n---\n\n## Describe the awesome resource you want to add\n\n**What is it?**  \n${submission.description}\n\n## **Which section does it belong to?**  \n- [x] ${submission.category}\n\n## **Additional details (optional)**\nResource URL: ${submission.url}\n\n## **Checklist**\n- [x] Resource is automatically sorted alphabetically within its section.\n- [x] Duplicate checking is performed automatically.\n- [x] Table formatting is handled automatically.\n- [x] Includes a valid and working link to the resource.\n- [x] Automatically assigned the correct section to the resource.\n`;\n}\n\nexport async function POST(request: NextRequest) {\n  try {\n    const submission: SubmissionData = await request.json();\n\n    if (\n      !submission.name ||\n      !submission.description ||\n      !submission.url ||\n      !submission.category\n    ) {\n      return NextResponse.json(\n        { error: \"All fields are required\" },\n        { status: 400 },\n      );\n    }\n\n    const githubToken = process.env.GITHUB_TOKEN;\n\n    if (!githubToken) {\n      return NextResponse.json(\n        { error: \"Server configuration error\" },\n        { status: 500 },\n      );\n    }\n\n    const octokit = new Octokit({ auth: githubToken });\n\n    const { data: readmeData } = await octokit.repos.getContent({\n      owner: GITHUB_CONFIG.REPO_OWNER,\n      repo: GITHUB_CONFIG.REPO_NAME,\n      path: \"README.md\",\n    });\n\n    if (\n      Array.isArray(readmeData) ||\n      !(\"content\" in readmeData) ||\n      !readmeData.sha\n    ) {\n      return NextResponse.json(\n        { error: \"Could not fetch README\" },\n        { status: 500 },\n      );\n    }\n\n    const currentContent = Buffer.from(readmeData.content, \"base64\").toString();\n\n    const insertResult = insertResourceIntoReadme(currentContent, submission);\n\n    if (insertResult.error) {\n      return NextResponse.json({ error: insertResult.error }, { status: 409 });\n    }\n\n    const updatedContent = insertResult.content;\n    const branchName = `add-${submission.name.toLowerCase().replace(/[^a-z0-9]/g, \"-\")}-${Date.now()}`;\n\n    const { data: ref } = await octokit.git.getRef({\n      owner: GITHUB_CONFIG.REPO_OWNER,\n      repo: GITHUB_CONFIG.REPO_NAME,\n      ref: \"heads/main\",\n    });\n\n    await octokit.git.createRef({\n      owner: GITHUB_CONFIG.REPO_OWNER,\n      repo: GITHUB_CONFIG.REPO_NAME,\n      ref: `refs/heads/${branchName}`,\n      sha: ref.object.sha,\n    });\n\n    await octokit.repos.createOrUpdateFileContents({\n      owner: GITHUB_CONFIG.REPO_OWNER,\n      repo: GITHUB_CONFIG.REPO_NAME,\n      path: \"README.md\",\n      message: `feat: Add ${submission.name}`,\n      content: Buffer.from(updatedContent).toString(\"base64\"),\n      sha: readmeData.sha,\n      branch: branchName,\n    });\n\n    const prBody = generatePRBody(submission);\n\n    const { data: pr } = await octokit.pulls.create({\n      owner: GITHUB_CONFIG.REPO_OWNER,\n      repo: GITHUB_CONFIG.REPO_NAME,\n      title: `feat: Add ${submission.name}`,\n      head: branchName,\n      base: \"main\",\n      body: prBody,\n    });\n\n    return NextResponse.json({\n      success: true,\n      prNumber: pr.number,\n      prUrl: pr.html_url,\n    });\n  } catch (error: any) {\n    console.error(\"Error creating PR:\", error);\n    return NextResponse.json(\n      { error: error.message || \"Failed to create pull request\" },\n      { status: 500 },\n    );\n  }\n}\n"
  },
  {
    "path": "src/app/bookmarks/error.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { AlertTriangle, ArrowLeft, Home, RefreshCw } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { useEffect } from \"react\";\n\nexport default function BookmarksError({\n  error,\n  reset,\n}: {\n  error: Error & { digest?: string };\n  reset: () => void;\n}) {\n  useEffect(() => {\n    console.error(\"Bookmarks error:\", error);\n  }, [error]);\n\n  return (\n    <div className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\">\n      <div className=\"flex flex-col items-center justify-center min-h-[60vh] text-center space-y-6\">\n        <div className=\"flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 rounded-full bg-destructive/10 dark:bg-destructive/20\">\n          <AlertTriangle className=\"h-8 w-8 sm:h-10 sm:w-10 text-destructive\" />\n        </div>\n\n        <div className=\"space-y-2\">\n          <h1 className=\"text-2xl sm:text-3xl font-bold text-foreground\">\n            Failed to load bookmarks\n          </h1>\n          <p className=\"text-sm sm:text-base text-muted-foreground max-w-md\">\n            We couldn't load this bookmarks page. The bookmarks might not exist\n            or there's a temporary issue.\n          </p>\n        </div>\n\n        <div className=\"flex flex-col sm:flex-row gap-3 sm:gap-4\">\n          <Button onClick={reset} className=\"flex items-center gap-2\">\n            <RefreshCw className=\"h-4 w-4\" />\n            Try again\n          </Button>\n          <Button variant=\"outline\" asChild>\n            <Link href=\"/categories\" className=\"flex items-center gap-2\">\n              <ArrowLeft className=\"h-4 w-4\" />\n              Back to categories\n            </Link>\n          </Button>\n          <Button variant=\"outline\" asChild>\n            <Link href=\"/\" className=\"flex items-center gap-2\">\n              <Home className=\"h-4 w-4\" />\n              Homepage\n            </Link>\n          </Button>\n        </div>\n\n        {process.env.NODE_ENV === \"development\" && (\n          <details className=\"mt-6 p-4 bg-muted text-left max-w-2xl w-full\">\n            <summary className=\"cursor-pointer text-sm font-medium\">\n              Error Details (Development)\n            </summary>\n            <pre className=\"mt-2 text-xs text-muted-foreground overflow-auto\">\n              {error.message}\n              {error.stack && `\\n\\n${error.stack}`}\n            </pre>\n          </details>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/app/bookmarks/page.tsx",
    "content": "\"use client\";\n\nimport { ItemGrid } from \"@/components/item-grid\";\nimport { PageHeader } from \"@/components/layout/page-header\";\nimport { PaginationControls } from \"@/components/pagination-controls\";\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 { Input } from \"@/components/ui/input\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { useBookmarks } from \"@/hooks/use-bookmark\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { fetchAndParseReadme, Resource } from \"@/hooks/use-readme\";\nimport { Bookmark, Search } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport Link from \"next/link\";\nimport { useCallback, useEffect, useState } from \"react\";\n\nconst ITEMS_PER_PAGE_OPTIONS = [20, 40, 60, 80];\n\nexport default function BookmarksPage() {\n  const [items, setItems] = useState<Resource[]>([]);\n  const [searchQuery, setSearchQuery] = useState(\"\");\n  const [filteredItems, setFilteredItems] = useState<Resource[]>([]);\n  const [currentPage, setCurrentPage] = useState(1);\n  const [itemsPerPage, setItemsPerPage] = useState(ITEMS_PER_PAGE_OPTIONS[0]);\n  const [isLoading, setIsLoading] = useState(true);\n  const [showClearDialog, setShowClearDialog] = useState(false);\n  const {\n    bookmarkedItems,\n    toggleBookmark,\n    clearBookmarks,\n    isLoading: isBookmarkLoading,\n  } = useBookmarks();\n\n  const debouncedSearchQuery = useDebounce(searchQuery, 300);\n\n  useEffect(() => {\n    async function fetchData() {\n      try {\n        setIsLoading(true);\n        const fetchedResources = await fetchAndParseReadme();\n\n        const bookmarkedResources = fetchedResources.filter((item) =>\n          bookmarkedItems.includes(item.id),\n        );\n\n        setItems(bookmarkedResources);\n        setFilteredItems(bookmarkedResources);\n      } catch (error) {\n        console.error(\"Error fetching bookmarked items:\", error);\n      } finally {\n        setIsLoading(false);\n      }\n    }\n\n    if (!isBookmarkLoading) {\n      fetchData();\n    }\n  }, [bookmarkedItems, isBookmarkLoading]);\n\n  useEffect(() => {\n    if (debouncedSearchQuery) {\n      const filtered = items.filter(\n        (item) =>\n          item.name\n            ?.toLowerCase()\n            .includes(debouncedSearchQuery.toLowerCase()) ||\n          item.description\n            ?.toLowerCase()\n            .includes(debouncedSearchQuery.toLowerCase()),\n      );\n      setFilteredItems(filtered);\n    } else {\n      setFilteredItems(items);\n    }\n    setCurrentPage(1);\n  }, [debouncedSearchQuery, items]);\n  const totalPages = Math.ceil(filteredItems.length / itemsPerPage);\n  const indexOfLastItem = currentPage * itemsPerPage;\n  const indexOfFirstItem = indexOfLastItem - itemsPerPage;\n  const currentItems = filteredItems.slice(indexOfFirstItem, indexOfLastItem);\n\n  const handlePageChange = useCallback((pageNumber: number) => {\n    setCurrentPage(pageNumber);\n  }, []);\n\n  const handleItemsPerPageChange = useCallback((value: string) => {\n    setItemsPerPage(Number(value));\n    setCurrentPage(1);\n  }, []);\n\n  const handleClearBookmarks = useCallback(() => {\n    clearBookmarks();\n    setShowClearDialog(false);\n    setSearchQuery(\"\");\n  }, [clearBookmarks]);\n\n  if (isLoading || isBookmarkLoading) {\n    return (\n      <div className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\">\n        <div className=\"space-y-6 sm:space-y-8\">\n          <div className=\"space-y-3 sm:space-y-4\">\n            <Skeleton className=\"h-8 sm:h-12 w-full max-w-96\" />\n            <Skeleton className=\"h-4 sm:h-6 w-full max-w-64\" />\n            <div className=\"flex items-center gap-2 sm:gap-4\">\n              <Skeleton className=\"h-4 w-20\" />\n              <Skeleton className=\"h-4 w-2\" />\n              <Skeleton className=\"h-4 w-16\" />\n            </div>\n            <Skeleton className=\"h-8 sm:h-10 w-full max-w-md\" />\n          </div>\n\n          <div className=\"grid gap-4 sm:gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n            {[...Array(6)].map((_, i) => (\n              <div key={i} className=\"border p-4 sm:p-6 space-y-3 sm:space-y-4\">\n                <div className=\"space-y-2\">\n                  <Skeleton className=\"h-5 sm:h-6 w-3/4\" />\n                  <Skeleton className=\"h-4 sm:h-5 w-20\" />\n                </div>\n                <div className=\"space-y-2\">\n                  <Skeleton className=\"h-3 sm:h-4 w-full\" />\n                  <Skeleton className=\"h-3 sm:h-4 w-5/6\" />\n                </div>\n                <div className=\"flex justify-between items-center\">\n                  <Skeleton className=\"h-8 w-8 sm:h-9 sm:w-9\" />\n                  <Skeleton className=\"h-8 w-8 sm:h-9 sm:w-9\" />\n                </div>\n              </div>\n            ))}\n          </div>\n\n          <div className=\"flex flex-col sm:flex-row items-center justify-between space-y-3 sm:space-y-0 gap-3 sm:gap-0\">\n            <div className=\"flex items-center space-x-2\">\n              <Skeleton className=\"h-4 w-20 sm:w-24\" />\n              <Skeleton className=\"h-8 sm:h-10 w-[60px] sm:w-[70px]\" />\n            </div>\n            <div className=\"flex items-center space-x-1 sm:space-x-2\">\n              <Skeleton className=\"h-8 w-8 sm:h-10 sm:w-10\" />\n              <Skeleton className=\"h-8 w-8 sm:h-10 sm:w-10\" />\n              <Skeleton className=\"h-4 w-12 sm:w-16\" />\n              <Skeleton className=\"h-8 w-8 sm:h-10 sm:w-10\" />\n              <Skeleton className=\"h-8 w-8 sm:h-10 sm:w-10\" />\n            </div>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <motion.div\n      className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\"\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1 }}\n      transition={{ duration: 0.3 }}\n    >\n      <PageHeader\n        title=\"Bookmarks\"\n        description=\"Your saved shadcn/ui resources\"\n        breadcrumbs={[{ label: \"Bookmarks\", href: \"/bookmarks\" }]}\n        actions={\n          <div className=\"space-y-3 sm:space-y-4 w-full\">\n            <div className=\"flex items-center gap-2 sm:gap-4 text-xs sm:text-sm text-muted-foreground\">\n              <span>\n                {items.length} {items.length === 1 ? \"bookmark\" : \"bookmarks\"}\n              </span>\n            </div>\n            <div className=\"flex flex-col sm:flex-row gap-2 sm:gap-3 w-full\">\n              <div className=\"w-full sm:flex-1 max-w-md\">\n                <Input\n                  type=\"text\"\n                  placeholder=\"Search your bookmarks...\"\n                  value={searchQuery}\n                  onChange={(e) => setSearchQuery(e.target.value)}\n                  className=\"h-10\"\n                />\n              </div>\n              {items.length > 0 && (\n                <div className=\"p-2\">\n                  <Button\n                    variant=\"outline\"\n                    onClick={() => setShowClearDialog(true)}\n                    className=\"w-full sm:w-auto text-sm sm:text-base\"\n                  >\n                    Clear All\n                  </Button>\n                </div>\n              )}\n            </div>\n          </div>\n        }\n      />\n\n      <div className=\"min-h-[400px] mb-6 sm:mb-8\">\n        {items.length === 0 ? (\n          <div className=\"flex flex-col items-center justify-center h-[400px] text-center px-4\">\n            <div className=\"w-16 h-16 rounded-full bg-muted flex items-center justify-center mb-4\">\n              <Bookmark className=\"h-8 w-8 text-muted-foreground\" />\n            </div>\n            <h3 className=\"text-lg font-semibold mb-2\">No bookmarks yet</h3>\n            <p className=\"text-sm text-muted-foreground mb-6 max-w-md\">\n              Start saving your favorite shadcn/ui resources by clicking the\n              bookmark icon on any item.\n            </p>\n            <Button asChild>\n              <Link href=\"/\">Browse resources</Link>\n            </Button>\n          </div>\n        ) : filteredItems.length === 0 && debouncedSearchQuery ? (\n          <div className=\"flex flex-col items-center justify-center h-[400px] text-center px-4\">\n            <div className=\"w-16 h-16 rounded-full bg-muted flex items-center justify-center mb-4\">\n              <Search className=\"h-8 w-8 text-muted-foreground\" />\n            </div>\n            <h3 className=\"text-lg font-semibold mb-2\">No results found</h3>\n            <p className=\"text-sm text-muted-foreground mb-6\">\n              No bookmarks match your search for \"{debouncedSearchQuery}\"\n            </p>\n            <Button variant=\"outline\" onClick={() => setSearchQuery(\"\")}>\n              Clear search\n            </Button>\n          </div>\n        ) : (\n          <ItemGrid\n            items={currentItems}\n            bookmarkedItems={bookmarkedItems}\n            onBookmark={toggleBookmark}\n            isBookmarkLoading={isBookmarkLoading}\n          />\n        )}\n      </div>\n\n      {filteredItems.length > 0 && items.length > 0 && (\n        <div>\n          <PaginationControls\n            currentPage={currentPage}\n            totalPages={totalPages}\n            itemsPerPage={itemsPerPage}\n            handlePageChange={handlePageChange}\n            handleItemsPerPageChange={handleItemsPerPageChange}\n            itemsPerPageOptions={ITEMS_PER_PAGE_OPTIONS}\n          />\n        </div>\n      )}\n\n      {items.length > 0 && (\n        <div className=\"text-xs sm:text-sm text-muted-foreground text-center mt-4 sm:mt-6\">\n          Showing {indexOfFirstItem + 1} -{\" \"}\n          {Math.min(indexOfLastItem, filteredItems.length)} of{\" \"}\n          {filteredItems.length}{\" \"}\n          {filteredItems.length === 1 ? \"bookmark\" : \"bookmarks\"}\n        </div>\n      )}\n\n      <Dialog open={showClearDialog} onOpenChange={setShowClearDialog}>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>Clear All Bookmarks?</DialogTitle>\n            <DialogDescription>\n              This will remove all your bookmarked items. This action cannot be\n              undone.\n            </DialogDescription>\n          </DialogHeader>\n          <DialogFooter>\n            <Button variant=\"outline\" onClick={() => setShowClearDialog(false)}>\n              Cancel\n            </Button>\n            <Button variant=\"destructive\" onClick={handleClearBookmarks}>\n              Clear All\n            </Button>\n          </DialogFooter>\n        </DialogContent>\n      </Dialog>\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/app/categories/[category]/[id]/error.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { AlertTriangle, ArrowLeft, Home, RefreshCw } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { useEffect } from \"react\";\n\nexport default function ItemError({\n  error,\n  reset,\n}: {\n  error: Error & { digest?: string };\n  reset: () => void;\n}) {\n  useEffect(() => {\n    console.error(\"Item error:\", error);\n  }, [error]);\n\n  return (\n    <div className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\">\n      <div className=\"flex flex-col items-center justify-center min-h-[60vh] text-center space-y-6\">\n        <div className=\"flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 rounded-full bg-destructive/10 dark:bg-destructive/20\">\n          <AlertTriangle className=\"h-8 w-8 sm:h-10 sm:w-10 text-destructive\" />\n        </div>\n\n        <div className=\"space-y-2\">\n          <h1 className=\"text-2xl sm:text-3xl font-bold text-foreground\">\n            Failed to load item\n          </h1>\n          <p className=\"text-sm sm:text-base text-muted-foreground max-w-md\">\n            We couldn't load this item page. The item might not exist or there's\n            a temporary issue.\n          </p>\n        </div>\n\n        <div className=\"flex flex-col sm:flex-row gap-3 sm:gap-4\">\n          <Button onClick={reset} className=\"flex items-center gap-2\">\n            <RefreshCw className=\"h-4 w-4\" />\n            Try again\n          </Button>\n          <Button variant=\"outline\" asChild>\n            <Link href=\"/categories\" className=\"flex items-center gap-2\">\n              <ArrowLeft className=\"h-4 w-4\" />\n              Back to categories\n            </Link>\n          </Button>\n          <Button variant=\"outline\" asChild>\n            <Link href=\"/\" className=\"flex items-center gap-2\">\n              <Home className=\"h-4 w-4\" />\n              Homepage\n            </Link>\n          </Button>\n        </div>\n\n        {process.env.NODE_ENV === \"development\" && (\n          <details className=\"mt-6 p-4 bg-muted text-left max-w-2xl w-full\">\n            <summary className=\"cursor-pointer text-sm font-medium\">\n              Error Details (Development)\n            </summary>\n            <pre className=\"mt-2 text-xs text-muted-foreground overflow-auto\">\n              {error.message}\n              {error.stack && `\\n\\n${error.stack}`}\n            </pre>\n          </details>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/app/categories/[category]/[id]/page.tsx",
    "content": "\"use client\";\n\nimport { PageHeader } from \"@/components/layout/page-header\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { fetchAndParseReadme, Resource } from \"@/hooks/use-readme\";\nimport { useWebsitePreview } from \"@/hooks/use-website-preview\";\nimport { categoryNameToSlug, slugToCategoryName } from \"@/lib/slugs\";\nimport { format, isValid, parseISO } from \"date-fns\";\nimport {\n  Calendar,\n  ExternalLink,\n  Github,\n  Globe,\n  Tag,\n  Image,\n} from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport Link from \"next/link\";\nimport { use, useEffect, useState } from \"react\";\n\ninterface ItemPageProps {\n  params: Promise<{\n    category: string;\n    id: string;\n  }>;\n}\n\nconst CATEGORY_DESCRIPTIONS: Record<string, string> = {\n  \"Libs and Components\":\n    \"Essential libraries and reusable components built with shadcn/ui\",\n  Registries: \"Component registries and collections for shadcn/ui\",\n  \"Plugins and Extensions\":\n    \"Tools and extensions that enhance your shadcn/ui workflow\",\n  \"Colors and Customizations\":\n    \"Themes, color palettes, and customization utilities\",\n  Animations: \"Animation libraries and motion components for shadcn/ui\",\n  Tools: \"Development tools, generators, and utilities for shadcn/ui projects\",\n  \"Websites and Portfolios Inspirations\":\n    \"Real-world examples and inspiration for your projects\",\n  Platforms: \"Platforms and services that integrate with shadcn/ui\",\n  Ports: \"Ports of shadcn/ui to other frameworks and technologies\",\n  \"Design System\": \"Complete design systems and component libraries\",\n  \"Boilerplates / Templates\":\n    \"Starter templates and boilerplates for quick project setup\",\n};\n\nexport default function ItemPage({ params }: ItemPageProps) {\n  const resolvedParams = use(params);\n  const [item, setItem] = useState<Resource | null>(null);\n  const [relatedItems, setRelatedItems] = useState<Resource[]>([]);\n  const [faviconUrl, setFaviconUrl] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(true);\n\n  const categorySlug = resolvedParams.category;\n  const categoryName = slugToCategoryName(categorySlug);\n  const itemId = resolvedParams.id;\n\n  useEffect(() => {\n    async function fetchData() {\n      try {\n        setIsLoading(true);\n        const fetchedResources = await fetchAndParseReadme();\n        const foundItem = fetchedResources.find(\n          (resource) => resource.id === itemId,\n        );\n\n        if (foundItem) {\n          setItem(foundItem);\n\n          const related = fetchedResources\n            .filter(\n              (resource) =>\n                resource.category === foundItem.category &&\n                resource.id !== foundItem.id,\n            )\n            .slice(0, 6);\n\n          setRelatedItems(related);\n\n          const domain = new URL(foundItem.url).hostname;\n          setFaviconUrl(`https://icons.duckduckgo.com/ip3/${domain}.ico`);\n        }\n      } catch (error) {\n        console.error(\"Error fetching item data:\", error);\n      } finally {\n        setIsLoading(false);\n      }\n    }\n\n    fetchData();\n  }, [itemId, categoryName]);\n\n  const formatDate = (dateString: string) => {\n    if (dateString === \"Unknown\") return \"Unknown\";\n    const date = parseISO(dateString);\n    return isValid(date) ? format(date, \"MMMM d, yyyy\") : \"Unknown\";\n  };\n\n  const isGitHubUrl = (url: string) => {\n    return url.includes(\"github.com\");\n  };\n\n  if (isLoading) {\n    return (\n      <div className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\">\n        <div className=\"space-y-6 sm:space-y-8\">\n          <div className=\"space-y-3 sm:space-y-4\">\n            <Skeleton className=\"h-8 sm:h-12 w-full max-w-96\" />\n            <Skeleton className=\"h-4 sm:h-6 w-full max-w-64\" />\n            <div className=\"flex items-center gap-2 sm:gap-4 text-xs sm:text-sm\">\n              <div className=\"flex items-center gap-2\">\n                <Skeleton className=\"h-3 w-3 sm:h-4 sm:w-4\" />\n                <Skeleton className=\"h-3 w-16 sm:h-4 sm:w-20\" />\n              </div>\n              <Skeleton className=\"h-3 w-2 sm:h-4\" />\n              <div className=\"flex items-center gap-2\">\n                <Skeleton className=\"h-3 w-3 sm:h-4 sm:w-4\" />\n                <Skeleton className=\"h-3 w-12 sm:h-4 sm:w-16\" />\n              </div>\n            </div>\n          </div>\n\n          <div className=\"grid gap-6 sm:gap-8 grid-cols-1 lg:grid-cols-3\">\n            <div className=\"lg:col-span-2 space-y-4 sm:space-y-6\">\n              <Card>\n                <CardHeader className=\"p-4\">\n                  <div className=\"flex items-center gap-2\">\n                    <Skeleton className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n                    <Skeleton className=\"h-5 w-16 sm:h-6 sm:w-16\" />\n                  </div>\n                </CardHeader>\n                <CardContent className=\"p-4 pt-0\">\n                  <div className=\"h-96 sm:h-[44rem] w-full bg-muted/50 flex items-center justify-center\">\n                    <div className=\"text-center\">\n                      <Globe className=\"h-8 w-8 mx-auto mb-2 text-muted-foreground/50\" />\n                      <Skeleton className=\"h-4 w-24 mx-auto\" />\n                    </div>\n                  </div>\n                </CardContent>\n              </Card>\n            </div>\n\n            <div className=\"space-y-4 sm:space-y-6\">\n              <Card>\n                <CardHeader className=\"p-4\">\n                  <Skeleton className=\"h-5 w-16\" />\n                </CardHeader>\n                <CardContent className=\"p-4 pt-0 space-y-3\">\n                  <Skeleton className=\"h-5 w-20\" />\n                  <div className=\"space-y-2\">\n                    <Skeleton className=\"h-3 w-full sm:h-4\" />\n                    <Skeleton className=\"h-3 w-3/4 sm:h-4\" />\n                  </div>\n                  <div className=\"space-y-2\">\n                    <Skeleton className=\"h-8 w-full\" />\n                    <Skeleton className=\"h-8 w-full\" />\n                  </div>\n                </CardContent>\n              </Card>\n\n              <Card>\n                <CardHeader className=\"p-4\">\n                  <Skeleton className=\"h-5 w-24 sm:h-6\" />\n                </CardHeader>\n                <CardContent className=\"p-4 pt-0 space-y-3\">\n                  {[...Array(6)].map((_, i) => (\n                    <div key={i} className=\"p-3 border space-y-2\">\n                      <Skeleton className=\"h-3 w-3/4 sm:h-4\" />\n                      <Skeleton className=\"h-3 w-full\" />\n                    </div>\n                  ))}\n                </CardContent>\n              </Card>\n            </div>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  if (!item) {\n    return null;\n  }\n\n  // Component to render preview based on state\n  const WebsitePreview = () => {\n    const { previewState, screenshotUrl } = useWebsitePreview({\n      url: item.url,\n      name: item.name,\n    });\n\n    if (previewState === \"loading\") {\n      return (\n        <div className=\"w-full h-full flex items-center justify-center bg-muted\">\n          <div className=\"flex flex-col items-center gap-3\">\n            <div className=\"w-8 h-8 border-4 border-primary/30 border-t-primary rounded-full animate-spin\" />\n            <p className=\"text-sm text-muted-foreground\">Loading preview...</p>\n          </div>\n        </div>\n      );\n    }\n\n    if (previewState === \"iframe\") {\n      return (\n        <>\n          <iframe\n            src={item.url}\n            className=\"w-full h-full border-0\"\n            title={`Preview of ${item.name}`}\n            sandbox=\"allow-scripts allow-same-origin\"\n          />\n          <a\n            href={item.url}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"absolute inset-0 bg-transparent cursor-pointer z-10\"\n            title={`Visit ${item.name} - Opens in new tab`}\n            aria-label={`Visit ${item.name} website`}\n          >\n            <span className=\"sr-only\">Visit {item.name} website</span>\n          </a>\n          <div className=\"absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent transition-all duration-300 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 pointer-events-none\">\n            <ExternalLink className=\"h-8 w-8 text-white mb-2\" />\n            <p className=\"text-lg font-medium text-white\">Visit website</p>\n            <p className=\"text-sm text-white/80 mt-1\">Click to open</p>\n          </div>\n        </>\n      );\n    }\n\n    if (previewState === \"screenshot\" && screenshotUrl) {\n      return (\n        <>\n          <img\n            src={screenshotUrl}\n            alt={`${item.name} preview`}\n            className=\"w-full h-full object-cover object-top\"\n          />\n          <div className=\"absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent transition-all duration-300 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 pointer-events-none\">\n            <ExternalLink className=\"h-8 w-8 text-white mb-2\" />\n            <p className=\"text-lg font-medium text-white\">Visit website</p>\n            <p className=\"text-sm text-white/80 mt-1\">Screenshot preview</p>\n          </div>\n          <a\n            href={item.url}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"absolute inset-0 bg-transparent cursor-pointer z-10\"\n            title={`Visit ${item.name} - Opens in new tab`}\n            aria-label={`Visit ${item.name} website`}\n          >\n            <span className=\"sr-only\">Visit {item.name} website</span>\n          </a>\n        </>\n      );\n    }\n\n    // Fallback state\n    return (\n      <div className=\"absolute inset-0 bg-muted flex flex-col items-center justify-center text-center p-6\">\n        <Image className=\"h-12 w-12 text-muted-foreground mb-3\" />\n        <p className=\"text-sm text-muted-foreground mb-2\">\n          Preview not available\n        </p>\n        <p className=\"text-xs text-muted-foreground/70 mb-4 max-w-xs\">\n          This website blocks embedding. Click below to visit directly.\n        </p>\n        <Button asChild size=\"sm\">\n          <a\n            href={item.url}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"flex items-center gap-2\"\n          >\n            <ExternalLink className=\"h-4 w-4\" />\n            Visit Website\n          </a>\n        </Button>\n      </div>\n    );\n  };\n\n  return (\n    <motion.div\n      className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\"\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1 }}\n      transition={{ duration: 0.3 }}\n    >\n      <PageHeader\n        title={item.name}\n        description={item.description}\n        icon={\n          faviconUrl ? (\n            <img\n              src={faviconUrl}\n              alt={`${item.name} favicon`}\n              className=\"h-6 w-6 sm:h-8 sm:w-8 flex-shrink-0\"\n              onError={() => setFaviconUrl(null)}\n            />\n          ) : (\n            <div className=\"h-6 w-6 sm:h-8 sm:w-8 bg-muted flex items-center justify-center flex-shrink-0\">\n              <Globe className=\"h-3 w-3 sm:h-4 sm:w-4 text-muted-foreground\" />\n            </div>\n          )\n        }\n        breadcrumbs={[\n          { label: \"Categories\", href: \"/categories\" },\n          { label: categoryName, href: `/categories/${categorySlug}` },\n          {\n            label: item.name || \"Loading...\",\n            href: `/categories/${categorySlug}/${itemId}`,\n          },\n        ]}\n        actions={\n          <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-2 sm:gap-4 text-xs sm:text-sm text-muted-foreground\">\n            <div className=\"flex items-center gap-2\">\n              <Calendar className=\"h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0\" />\n              <span className=\"break-words\">Added {formatDate(item.date)}</span>\n            </div>\n            <span className=\"hidden sm:inline\">•</span>\n            <div className=\"flex items-center gap-2\">\n              <Tag className=\"h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0\" />\n              <span className=\"break-words\">{item.category}</span>\n            </div>\n          </div>\n        }\n      />\n\n      <div className=\"min-h-screen grid gap-6 sm:gap-8 grid-cols-1 lg:grid-cols-3\">\n        <motion.div\n          className=\"lg:col-span-2 space-y-6 sm:space-y-8\"\n          initial={{ opacity: 0, y: 20 }}\n          animate={{ opacity: 1, y: 0 }}\n          transition={{ duration: 0.5, delay: 0.2 }}\n        >\n          <Card>\n            <CardHeader className=\"p-4 pb-3\">\n              <CardTitle className=\"flex items-center gap-2 text-base sm:text-lg\">\n                <Globe className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n                Preview\n              </CardTitle>\n            </CardHeader>\n            <CardContent className=\"p-4 pt-0\">\n              <div className=\"relative h-[300px] sm:h-[500px] lg:h-[600px] overflow-hidden border group\">\n                <WebsitePreview />\n              </div>\n            </CardContent>\n          </Card>\n        </motion.div>\n\n        <motion.div\n          className=\"space-y-4 sm:space-y-6\"\n          initial={{ opacity: 0, y: 20 }}\n          animate={{ opacity: 1, y: 0 }}\n          transition={{ duration: 0.5, delay: 0.4 }}\n        >\n          <Card>\n            <CardHeader className=\"p-4\">\n              <CardTitle className=\"text-base sm:text-lg\">Category</CardTitle>\n            </CardHeader>\n            <CardContent className=\"p-4 pt-0 space-y-3\">\n              <Badge variant=\"secondary\" className=\"text-xs sm:text-sm\">\n                {item.category}\n              </Badge>\n              <p className=\"text-xs sm:text-sm text-muted-foreground break-words\">\n                {CATEGORY_DESCRIPTIONS[item.category] ||\n                  \"A collection of shadcn/ui related resources\"}\n              </p>\n              <div className=\"space-y-2\">\n                <Button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  asChild\n                  className=\"w-full text-xs sm:text-sm\"\n                >\n                  <Link\n                    href={`/categories/${categoryNameToSlug(item.category)}`}\n                  >\n                    View all in {item.category}\n                  </Link>\n                </Button>\n                <Button asChild className=\"w-full text-xs sm:text-sm\">\n                  <a\n                    href={item.url}\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                    className=\"flex items-center gap-2\"\n                  >\n                    {isGitHubUrl(item.url) ? (\n                      <Github className=\"h-3 w-3 sm:h-4 sm:w-4\" />\n                    ) : (\n                      <ExternalLink className=\"h-3 w-3 sm:h-4 sm:w-4\" />\n                    )}\n                    {isGitHubUrl(item.url) ? \"View on GitHub\" : \"Visit Website\"}\n                  </a>\n                </Button>\n              </div>\n            </CardContent>\n          </Card>\n\n          {relatedItems.length > 0 && (\n            <Card>\n              <CardHeader className=\"p-4\">\n                <CardTitle className=\"text-base sm:text-lg\">\n                  Related Items\n                </CardTitle>\n              </CardHeader>\n              <CardContent className=\"p-4 pt-0 space-y-3\">\n                {relatedItems.map((relatedItem) => (\n                  <Link\n                    key={relatedItem.id}\n                    href={`/categories/${categoryNameToSlug(item.category)}/${relatedItem.id}`}\n                    className=\"block group\"\n                  >\n                    <div className=\"relative overflow-hidden flex items-center justify-between p-3 border hover:bg-muted/50 hover:border-primary/20 transition-all duration-200 cursor-pointer group-hover:shadow-sm\">\n                      <div className=\"absolute inset-0 opacity-0 group-hover:opacity-100 transition-all duration-300 pointer-events-none\">\n                        <div className=\"absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-transparent dark:from-primary/10\" />\n                      </div>\n                      <div className=\"relative z-10 flex items-center justify-between w-full\">\n                        <div className=\"flex-1 min-w-0\">\n                          <p className=\"text-xs sm:text-sm font-medium truncate group-hover:text-primary transition-colors\">\n                            {relatedItem.name}\n                          </p>\n                          <p className=\"text-xs text-muted-foreground truncate group-hover:text-muted-foreground/80 transition-colors\">\n                            {relatedItem.description}\n                          </p>\n                        </div>\n                        <div className=\"flex items-center gap-1 sm:gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex-shrink-0\">\n                          <span className=\"text-xs text-muted-foreground hidden sm:inline\">\n                            View\n                          </span>\n                          <ExternalLink className=\"h-3 w-3 text-muted-foreground group-hover:text-primary transition-colors\" />\n                        </div>\n                      </div>\n                    </div>\n                  </Link>\n                ))}\n              </CardContent>\n            </Card>\n          )}\n        </motion.div>\n      </div>\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/app/categories/[category]/error.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { AlertTriangle, ArrowLeft, Home, RefreshCw } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { useEffect } from \"react\";\n\nexport default function CategoryError({\n  error,\n  reset,\n}: {\n  error: Error & { digest?: string };\n  reset: () => void;\n}) {\n  useEffect(() => {\n    console.error(\"Category error:\", error);\n  }, [error]);\n\n  return (\n    <div className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\">\n      <div className=\"flex flex-col items-center justify-center min-h-[60vh] text-center space-y-6\">\n        <div className=\"flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 rounded-full bg-destructive/10 dark:bg-destructive/20\">\n          <AlertTriangle className=\"h-8 w-8 sm:h-10 sm:w-10 text-destructive\" />\n        </div>\n\n        <div className=\"space-y-2\">\n          <h1 className=\"text-2xl sm:text-3xl font-bold text-foreground\">\n            Failed to load category\n          </h1>\n          <p className=\"text-sm sm:text-base text-muted-foreground max-w-md\">\n            We couldn't load this category page. The category might not exist or\n            there's a temporary issue.\n          </p>\n        </div>\n\n        <div className=\"flex flex-col sm:flex-row gap-3 sm:gap-4\">\n          <Button onClick={reset} className=\"flex items-center gap-2\">\n            <RefreshCw className=\"h-4 w-4\" />\n            Try again\n          </Button>\n          <Button variant=\"outline\" asChild>\n            <Link href=\"/categories\" className=\"flex items-center gap-2\">\n              <ArrowLeft className=\"h-4 w-4\" />\n              Back to categories\n            </Link>\n          </Button>\n          <Button variant=\"outline\" asChild>\n            <Link href=\"/\" className=\"flex items-center gap-2\">\n              <Home className=\"h-4 w-4\" />\n              Homepage\n            </Link>\n          </Button>\n        </div>\n\n        {process.env.NODE_ENV === \"development\" && (\n          <details className=\"mt-6 p-4 bg-muted text-left max-w-2xl w-full\">\n            <summary className=\"cursor-pointer text-sm font-medium\">\n              Error Details (Development)\n            </summary>\n            <pre className=\"mt-2 text-xs text-muted-foreground overflow-auto\">\n              {error.message}\n              {error.stack && `\\n\\n${error.stack}`}\n            </pre>\n          </details>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/app/categories/[category]/page.tsx",
    "content": "\"use client\";\n\nimport { ItemGrid } from \"@/components/item-grid\";\nimport { PageHeader } from \"@/components/layout/page-header\";\nimport { PaginationControls } from \"@/components/pagination-controls\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { useBookmarks } from \"@/hooks/use-bookmark\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { fetchAndParseReadme, Resource } from \"@/hooks/use-readme\";\nimport { slugToCategoryName } from \"@/lib/slugs\";\nimport { motion } from \"motion/react\";\nimport { use, useCallback, useEffect, useState } from \"react\";\n\ninterface CategoryPageProps {\n  params: Promise<{\n    category: string;\n  }>;\n}\n\nconst CATEGORY_DESCRIPTIONS: Record<string, string> = {\n  \"Libs and Components\":\n    \"Essential libraries and reusable components built with shadcn/ui\",\n  Registries: \"Component registries and collections for shadcn/ui\",\n  \"Plugins and Extensions\":\n    \"Tools and extensions that enhance your shadcn/ui workflow\",\n  \"Colors and Customizations\":\n    \"Themes, color palettes, and customization utilities\",\n  Animations: \"Animation libraries and motion components for shadcn/ui\",\n  Tools: \"Development tools, generators, and utilities for shadcn/ui projects\",\n  \"Websites and Portfolios Inspirations\":\n    \"Real-world examples and inspiration for your projects\",\n  Platforms: \"Platforms and services that integrate with shadcn/ui\",\n  Ports: \"Ports of shadcn/ui to other frameworks and technologies\",\n  \"Design System\": \"Complete design systems and component libraries\",\n  \"Boilerplates / Templates\":\n    \"Starter templates and boilerplates for quick project setup\",\n};\n\nconst ITEMS_PER_PAGE_OPTIONS = [20, 40, 60, 80];\n\nexport default function CategoryPage({ params }: CategoryPageProps) {\n  const resolvedParams = use(params);\n  const [items, setItems] = useState<Resource[]>([]);\n  const [searchQuery, setSearchQuery] = useState(\"\");\n  const [filteredItems, setFilteredItems] = useState<Resource[]>([]);\n  const [currentPage, setCurrentPage] = useState(1);\n  const [itemsPerPage, setItemsPerPage] = useState(ITEMS_PER_PAGE_OPTIONS[1]);\n  const [isLoading, setIsLoading] = useState(true);\n  const {\n    bookmarkedItems,\n    toggleBookmark,\n    isLoading: isBookmarkLoading,\n  } = useBookmarks();\n\n  const debouncedSearchQuery = useDebounce(searchQuery, 300);\n\n  const categorySlug = resolvedParams.category;\n  const categoryName = slugToCategoryName(categorySlug);\n\n  useEffect(() => {\n    async function fetchData() {\n      try {\n        setIsLoading(true);\n        const fetchedResources = await fetchAndParseReadme();\n\n        const categoryItems = fetchedResources.filter(\n          (item) => item.category === categoryName,\n        );\n\n        setItems(categoryItems);\n        setFilteredItems(categoryItems);\n      } catch (error) {\n        console.error(\"Error fetching category items:\", error);\n      } finally {\n        setIsLoading(false);\n      }\n    }\n\n    fetchData();\n  }, [categoryName]);\n\n  useEffect(() => {\n    if (debouncedSearchQuery) {\n      const filtered = items.filter(\n        (item) =>\n          item.name\n            ?.toLowerCase()\n            .includes(debouncedSearchQuery.toLowerCase()) ||\n          item.description\n            ?.toLowerCase()\n            .includes(debouncedSearchQuery.toLowerCase()),\n      );\n      setFilteredItems(filtered);\n    } else {\n      setFilteredItems(items);\n    }\n    setCurrentPage(1);\n  }, [debouncedSearchQuery, items]);\n  const totalPages = Math.ceil(filteredItems.length / itemsPerPage);\n  const indexOfLastItem = currentPage * itemsPerPage;\n  const indexOfFirstItem = indexOfLastItem - itemsPerPage;\n  const currentItems = filteredItems.slice(indexOfFirstItem, indexOfLastItem);\n\n  const handlePageChange = useCallback((pageNumber: number) => {\n    setCurrentPage(pageNumber);\n  }, []);\n\n  const handleItemsPerPageChange = useCallback((value: string) => {\n    setItemsPerPage(Number(value));\n    setCurrentPage(1);\n  }, []);\n\n  if (isLoading) {\n    return (\n      <div className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\">\n        <div className=\"space-y-6 sm:space-y-8\">\n          <div className=\"space-y-3 sm:space-y-4\">\n            <Skeleton className=\"h-8 sm:h-12 w-full max-w-96\" />\n            <Skeleton className=\"h-4 sm:h-6 w-full max-w-64\" />\n            <div className=\"flex items-center gap-2 sm:gap-4\">\n              <Skeleton className=\"h-4 w-20\" />\n              <Skeleton className=\"h-4 w-2\" />\n              <Skeleton className=\"h-4 w-16\" />\n            </div>\n            <Skeleton className=\"h-8 sm:h-10 w-full max-w-md\" />\n          </div>\n\n          <div className=\"grid gap-4 sm:gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n            {[...Array(6)].map((_, i) => (\n              <div key={i} className=\"border p-4 sm:p-6 space-y-3 sm:space-y-4\">\n                <div className=\"space-y-2\">\n                  <Skeleton className=\"h-5 sm:h-6 w-3/4\" />\n                  <Skeleton className=\"h-4 sm:h-5 w-20\" />\n                </div>\n                <div className=\"space-y-2\">\n                  <Skeleton className=\"h-3 sm:h-4 w-full\" />\n                  <Skeleton className=\"h-3 sm:h-4 w-5/6\" />\n                </div>\n                <div className=\"flex justify-between items-center\">\n                  <Skeleton className=\"h-8 w-8 sm:h-9 sm:w-9\" />\n                  <Skeleton className=\"h-8 w-8 sm:h-9 sm:w-9\" />\n                </div>\n              </div>\n            ))}\n          </div>\n\n          <div className=\"flex flex-col sm:flex-row items-center justify-between space-y-3 sm:space-y-0 gap-3 sm:gap-0\">\n            <div className=\"flex items-center space-x-2\">\n              <Skeleton className=\"h-4 w-20 sm:w-24\" />\n              <Skeleton className=\"h-8 sm:h-10 w-[60px] sm:w-[70px]\" />\n            </div>\n            <div className=\"flex items-center space-x-1 sm:space-x-2\">\n              <Skeleton className=\"h-8 w-8 sm:h-10 sm:w-10\" />\n              <Skeleton className=\"h-8 w-8 sm:h-10 sm:w-10\" />\n              <Skeleton className=\"h-4 w-12 sm:w-16\" />\n              <Skeleton className=\"h-8 w-8 sm:h-10 sm:w-10\" />\n              <Skeleton className=\"h-8 w-8 sm:h-10 sm:w-10\" />\n            </div>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <motion.div\n      className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\"\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1 }}\n      transition={{ duration: 0.3 }}\n    >\n      <PageHeader\n        title={categoryName}\n        description={\n          CATEGORY_DESCRIPTIONS[categoryName] ||\n          \"A collection of shadcn/ui related resources\"\n        }\n        breadcrumbs={[\n          { label: \"Categories\", href: \"/categories\" },\n          { label: categoryName, href: `/categories/${categorySlug}` },\n        ]}\n        actions={\n          <div className=\"space-y-3 sm:space-y-4 w-full\">\n            <div className=\"flex items-center gap-2 sm:gap-4 text-xs sm:text-sm text-muted-foreground\">\n              <span>\n                {items.length} {items.length === 1 ? \"item\" : \"items\"}\n              </span>\n              <span className=\"hidden sm:inline\">•</span>\n              <span className=\"hidden sm:inline\">Category</span>\n            </div>\n            <div className=\"w-full max-w-md\">\n              <Input\n                type=\"text\"\n                placeholder=\"Search within this category...\"\n                value={searchQuery}\n                onChange={(e) => setSearchQuery(e.target.value)}\n                className=\"h-10\"\n              />\n            </div>\n          </div>\n        }\n      />\n\n      <div className=\"min-h-screen mb-6 sm:mb-8\">\n        <ItemGrid\n          items={currentItems}\n          bookmarkedItems={bookmarkedItems}\n          onBookmark={toggleBookmark}\n          isBookmarkLoading={isBookmarkLoading}\n        />\n      </div>\n\n      {filteredItems.length > 0 && (\n        <div>\n          <PaginationControls\n            currentPage={currentPage}\n            totalPages={totalPages}\n            itemsPerPage={itemsPerPage}\n            handlePageChange={handlePageChange}\n            handleItemsPerPageChange={handleItemsPerPageChange}\n            itemsPerPageOptions={ITEMS_PER_PAGE_OPTIONS}\n          />\n        </div>\n      )}\n\n      <div className=\"text-xs sm:text-sm text-muted-foreground text-center mt-4 sm:mt-6\">\n        Showing {indexOfFirstItem + 1} -{\" \"}\n        {Math.min(indexOfLastItem, filteredItems.length)} of{\" \"}\n        {filteredItems.length} items\n      </div>\n\n      {filteredItems.length === 0 && debouncedSearchQuery && (\n        <div className=\"text-center py-8 sm:py-12 px-4\">\n          <p className=\"text-muted-foreground mb-4 text-sm sm:text-base break-words\">\n            No items found for \"{debouncedSearchQuery}\"\n          </p>\n          <Button\n            variant=\"outline\"\n            onClick={() => setSearchQuery(\"\")}\n            className=\"text-sm sm:text-base\"\n          >\n            Clear search\n          </Button>\n        </div>\n      )}\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/app/categories/error.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { AlertTriangle, Home, RefreshCw } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { useEffect } from \"react\";\n\nexport default function CategoriesError({\n  error,\n  reset,\n}: {\n  error: Error & { digest?: string };\n  reset: () => void;\n}) {\n  useEffect(() => {\n    console.error(\"Categories error:\", error);\n  }, [error]);\n\n  return (\n    <div className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\">\n      <div className=\"flex flex-col items-center justify-center min-h-[60vh] text-center space-y-6\">\n        <div className=\"flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 rounded-full bg-destructive/10 dark:bg-destructive/20\">\n          <AlertTriangle className=\"h-8 w-8 sm:h-10 sm:w-10 text-destructive\" />\n        </div>\n\n        <div className=\"space-y-2\">\n          <h1 className=\"text-2xl sm:text-3xl font-bold text-foreground\">\n            Failed to load categories\n          </h1>\n          <p className=\"text-sm sm:text-base text-muted-foreground max-w-md\">\n            We couldn't load the categories page. This might be a temporary\n            issue with our data source.\n          </p>\n        </div>\n\n        <div className=\"flex flex-col sm:flex-row gap-3 sm:gap-4\">\n          <Button onClick={reset} className=\"flex items-center gap-2\">\n            <RefreshCw className=\"h-4 w-4\" />\n            Try again\n          </Button>\n          <Button variant=\"outline\" asChild>\n            <Link href=\"/\" className=\"flex items-center gap-2\">\n              <Home className=\"h-4 w-4\" />\n              Go to homepage\n            </Link>\n          </Button>\n        </div>\n\n        {process.env.NODE_ENV === \"development\" && (\n          <details className=\"mt-6 p-4 bg-muted text-left max-w-2xl w-full\">\n            <summary className=\"cursor-pointer text-sm font-medium\">\n              Error Details (Development)\n            </summary>\n            <pre className=\"mt-2 text-xs text-muted-foreground overflow-auto\">\n              {error.message}\n              {error.stack && `\\n\\n${error.stack}`}\n            </pre>\n          </details>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/app/categories/page.tsx",
    "content": "\"use client\";\n\nimport { PageHeader } from \"@/components/layout/page-header\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { fetchAndParseReadme, Resource } from \"@/hooks/use-readme\";\nimport { categoryNameToSlug } from \"@/lib/slugs\";\nimport { ArrowRight } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport Link from \"next/link\";\nimport { useEffect, useState } from \"react\";\n\ninterface Category {\n  title: string;\n  items: Resource[];\n  description: string;\n}\n\nconst CATEGORY_DESCRIPTIONS: Record<string, string> = {\n  \"Libs and Components\":\n    \"Essential libraries and reusable components built with shadcn/ui\",\n  Registries: \"Component registries and collections for shadcn/ui\",\n  \"Plugins and Extensions\":\n    \"Tools and extensions that enhance your shadcn/ui workflow\",\n  \"Colors and Customizations\":\n    \"Themes, color palettes, and customization utilities\",\n  Animations: \"Animation libraries and motion components for shadcn/ui\",\n  Tools: \"Development tools, generators, and utilities for shadcn/ui projects\",\n  \"Websites and Portfolios Inspirations\":\n    \"Real-world examples and inspiration for your projects\",\n  Platforms: \"Platforms and services that integrate with shadcn/ui\",\n  Ports: \"Ports of shadcn/ui to other frameworks and technologies\",\n  \"Design System\": \"Complete design systems and component libraries\",\n  \"Boilerplates / Templates\":\n    \"Starter templates and boilerplates for quick project setup\",\n};\n\nexport default function CategoriesPage() {\n  const [categories, setCategories] = useState<Category[]>([]);\n  const [isLoading, setIsLoading] = useState(true);\n\n  useEffect(() => {\n    async function fetchData() {\n      try {\n        setIsLoading(true);\n        const fetchedResources = await fetchAndParseReadme();\n\n        const EXCLUDED_CATEGORIES = [\"Star History\", \"Contributors\"];\n\n        const groupedCategories = fetchedResources.reduce(\n          (acc, resource) => {\n            if (!EXCLUDED_CATEGORIES.includes(resource.category)) {\n              if (!acc[resource.category]) {\n                acc[resource.category] = [];\n              }\n              acc[resource.category].push(resource);\n            }\n            return acc;\n          },\n          {} as Record<string, Resource[]>,\n        );\n\n        const formattedCategories = Object.entries(groupedCategories).map(\n          ([title, items]) => ({\n            title,\n            items,\n            description:\n              CATEGORY_DESCRIPTIONS[title] ||\n              \"A collection of shadcn/ui related resources\",\n          }),\n        );\n\n        setCategories(formattedCategories);\n      } catch (error) {\n        console.error(\"Error fetching categories:\", error);\n      } finally {\n        setIsLoading(false);\n      }\n    }\n\n    fetchData();\n  }, []);\n\n  if (isLoading) {\n    return (\n      <div className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\">\n        <div className=\"space-y-6 sm:space-y-8\">\n          <div className=\"space-y-3 sm:space-y-4\">\n            <Skeleton className=\"h-8 sm:h-12 w-full max-w-96 mx-auto\" />\n            <Skeleton className=\"h-4 sm:h-6 w-full max-w-64 mx-auto\" />\n          </div>\n\n          <div className=\"grid gap-4 sm:gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n            {[...Array(9)].map((_, i) => (\n              <Card key={i} className=\"h-full\">\n                <CardHeader className=\"pb-3 sm:pb-4 p-4 sm:p-6\">\n                  <div className=\"flex items-center justify-between gap-2\">\n                    <Skeleton className=\"h-5 sm:h-6 w-3/4\" />\n                    <Skeleton className=\"h-4 w-4 sm:h-5 sm:w-5\" />\n                  </div>\n                  <Skeleton className=\"h-3 sm:h-4 w-full mt-2\" />\n                  <Skeleton className=\"h-3 sm:h-4 w-5/6\" />\n                </CardHeader>\n                <CardContent className=\"pt-0 p-4 sm:p-6\">\n                  <div className=\"flex items-center justify-between gap-2\">\n                    <Skeleton className=\"h-5 sm:h-6 w-16\" />\n                    <div className=\"flex items-center space-x-1\">\n                      <Skeleton className=\"h-3 w-12\" />\n                      <Skeleton className=\"h-3 w-3\" />\n                    </div>\n                  </div>\n                </CardContent>\n              </Card>\n            ))}\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <motion.div\n      className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\"\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1 }}\n      transition={{ duration: 0.3 }}\n    >\n      <PageHeader\n        title=\"Categories\"\n        description=\"Explore our curated collection of shadcn/ui resources organized by category\"\n        breadcrumbs={[{ label: \"Categories\", href: \"/categories\" }]}\n      />\n\n      <div className=\"min-h-screen grid gap-4 sm:gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n        {categories.map((category) => (\n          <div key={category.title}>\n            <Link href={`/categories/${categoryNameToSlug(category.title)}`}>\n              <Card className=\"h-[280px] group hover:border-primary/40 hover:shadow-lg transition-all duration-200 overflow-hidden cursor-pointer flex flex-col\">\n                <CardHeader className=\"p-4 pb-3\">\n                  <div className=\"flex items-start justify-between gap-2 mb-2\">\n                    <span className=\"text-[10px] font-mono uppercase tracking-wider text-primary border border-primary/30 bg-primary/5 px-2 py-0.5\">\n                      [{category.title}]\n                    </span>\n                    <ArrowRight className=\"h-4 w-4 text-muted-foreground group-hover:text-primary group-hover:translate-x-0.5 transition-all flex-shrink-0\" />\n                  </div>\n                  <CardTitle className=\"text-base font-bold group-hover:text-primary transition-colors duration-200 line-clamp-2 leading-snug\">\n                    {category.title}\n                  </CardTitle>\n                </CardHeader>\n\n                <div className=\"h-px bg-border mx-4\" />\n\n                <CardContent className=\"px-4 py-3 flex-1 flex flex-col gap-3\">\n                  <p className=\"text-sm text-muted-foreground line-clamp-3 flex-1 leading-relaxed overflow-hidden\">\n                    {category.description}\n                  </p>\n                </CardContent>\n\n                <div className=\"h-px bg-border mx-4\" />\n\n                <CardFooter className=\"flex gap-3 items-center p-4 pt-3\">\n                  <Badge variant=\"secondary\" className=\"text-xs flex-shrink-0\">\n                    {category.items.length}{\" \"}\n                    {category.items.length === 1 ? \"item\" : \"items\"}\n                  </Badge>\n                </CardFooter>\n              </Card>\n            </Link>\n          </div>\n        ))}\n      </div>\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/app/error.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { AlertTriangle, RefreshCw } from \"lucide-react\";\nimport { useEffect } from \"react\";\n\nexport default function Error({\n  error,\n  reset,\n}: {\n  error: Error & { digest?: string };\n  reset: () => void;\n}) {\n  useEffect(() => {\n    console.error(\"Homepage error:\", error);\n  }, [error]);\n\n  return (\n    <div className=\"container mx-auto max-w-7xl px-3 sm:px-4 py-4 sm:py-8\">\n      <div className=\"flex flex-col items-center justify-center min-h-[60vh] text-center space-y-6\">\n        <div className=\"flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 rounded-full bg-destructive/10 dark:bg-destructive/20\">\n          <AlertTriangle className=\"h-8 w-8 sm:h-10 sm:w-10 text-destructive\" />\n        </div>\n\n        <div className=\"space-y-2\">\n          <h1 className=\"text-2xl sm:text-3xl font-bold text-foreground\">\n            Something went wrong\n          </h1>\n          <p className=\"text-sm sm:text-base text-muted-foreground max-w-md\">\n            We encountered an error while loading the homepage. This might be a\n            temporary issue.\n          </p>\n        </div>\n\n        <div className=\"flex flex-col sm:flex-row gap-3 sm:gap-4\">\n          <Button onClick={reset} className=\"flex items-center gap-2\">\n            <RefreshCw className=\"h-4 w-4\" />\n            Try again\n          </Button>\n          <Button\n            variant=\"outline\"\n            onClick={() => (window.location.href = \"/\")}\n          >\n            Go to homepage\n          </Button>\n        </div>\n\n        {process.env.NODE_ENV === \"development\" && (\n          <details className=\"mt-6 p-4 bg-muted text-left max-w-2xl w-full\">\n            <summary className=\"cursor-pointer text-sm font-medium\">\n              Error Details (Development)\n            </summary>\n            <pre className=\"mt-2 text-xs text-muted-foreground overflow-auto\">\n              {error.message}\n              {error.stack && `\\n\\n${error.stack}`}\n            </pre>\n          </details>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/app/global-error.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { AlertOctagon } from \"lucide-react\";\nimport { useEffect } from \"react\";\n\nexport default function GlobalError({\n  error,\n  reset,\n}: {\n  error: Error & { digest?: string };\n  reset: () => void;\n}) {\n  useEffect(() => {\n    // Log error\n  }, [error]);\n\n  return (\n    <html lang=\"en\">\n      <body className=\"bg-background text-foreground\">\n        <div className=\"relative flex min-h-screen flex-col items-center justify-center overflow-hidden px-4 py-20 text-center\">\n          <div className=\"relative z-10 mx-auto max-w-xl\">\n            <div className=\"mb-6 flex justify-center\">\n              <AlertOctagon className=\"size-20 text-destructive\" />\n            </div>\n            <h1 className=\"mb-4 text-3xl font-bold tracking-tight sm:text-5xl\">\n              Critical Error\n            </h1>\n            <p className=\"mb-8 text-base text-muted-foreground sm:text-lg\">\n              We've encountered a critical application error.\n              <br className=\"hidden sm:block\" />\n              Our team has been automatically notified and is working to resolve\n              the issue.\n            </p>\n            <div className=\"flex flex-col sm:flex-row justify-center gap-4\">\n              <Button onClick={() => reset()} size=\"lg\" variant=\"default\">\n                Reload Application\n              </Button>\n              <Button asChild size=\"lg\" variant=\"outline\">\n                <a href=\"/\">Return Home</a>\n              </Button>\n            </div>\n            {error.digest && (\n              <p className=\"mt-6 text-xs text-muted-foreground\">\n                Error ID: {error.digest}\n              </p>\n            )}\n          </div>\n        </div>\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "src/app/globals.css",
    "content": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n@import \"shadcn/tailwind.css\";\n\n@custom-variant dark (&:is(.dark *));\n\n:root {\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.141 0.005 285.823);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.141 0.005 285.823);\n  --primary: oklch(0.21 0.006 285.885);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.967 0.001 286.375);\n  --secondary-foreground: oklch(0.21 0.006 285.885);\n  --muted: oklch(0.967 0.001 286.375);\n  --muted-foreground: oklch(0.552 0.016 285.938);\n  --accent: oklch(0.967 0.001 286.375);\n  --accent-foreground: oklch(0.21 0.006 285.885);\n  --destructive: oklch(0.577 0.245 27.325);\n  --destructive-foreground: oklch(1 0 0);\n  --border: oklch(0.92 0.004 286.32);\n  --input: oklch(0.92 0.004 286.32);\n  --ring: oklch(0.705 0.015 286.067);\n  --chart-1: oklch(0.646 0.222 41.116);\n  --chart-2: oklch(0.6 0.118 184.704);\n  --chart-3: oklch(0.398 0.07 227.392);\n  --chart-4: oklch(0.828 0.189 84.429);\n  --chart-5: oklch(0.769 0.188 70.08);\n  --sidebar: oklch(0.985 0 0);\n  --sidebar-foreground: oklch(0.141 0.005 285.823);\n  --sidebar-primary: oklch(0.21 0.006 285.885);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.967 0.001 286.375);\n  --sidebar-accent-foreground: oklch(0.21 0.006 285.885);\n  --sidebar-border: oklch(0.92 0.004 286.32);\n  --sidebar-ring: oklch(0.705 0.015 286.067);\n\n  /* Custom colors for donation button */\n  --donation-bg: oklch(0.95 0.15 95);\n  --donation-bg-hover: oklch(0.92 0.18 95);\n  --donation-text: oklch(0.15 0 0);\n\n  --font-sans: Geist, sans-serif;\n  --font-serif: Georgia, serif;\n  --font-mono: Geist Mono, monospace;\n  --radius: 0.625rem;\n  --shadow-x: 0px;\n  --shadow-y: 1px;\n  --shadow-blur: 2px;\n  --shadow-spread: 0px;\n  --shadow-opacity: 0.18;\n  --shadow-color: hsl(0 0% 0%);\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:\n    0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18);\n  --shadow:\n    0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18);\n  --shadow-md:\n    0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18);\n  --shadow-lg:\n    0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18);\n  --shadow-xl:\n    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  --spacing: 0.25rem;\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.141 0.005 285.823);\n}\n\n.dark {\n  --background: oklch(0.141 0.005 285.823);\n  --foreground: oklch(0.985 0 0);\n  --card: oklch(0.21 0.006 285.885);\n  --card-foreground: oklch(0.985 0 0);\n  --popover: oklch(0.21 0.006 285.885);\n  --popover-foreground: oklch(0.985 0 0);\n  --primary: oklch(0.92 0.004 286.32);\n  --primary-foreground: oklch(0.21 0.006 285.885);\n  --secondary: oklch(0.274 0.006 286.033);\n  --secondary-foreground: oklch(0.985 0 0);\n  --muted: oklch(0.274 0.006 286.033);\n  --muted-foreground: oklch(0.705 0.015 286.067);\n  --accent: oklch(0.274 0.006 286.033);\n  --accent-foreground: oklch(0.985 0 0);\n  --destructive: oklch(0.704 0.191 22.216);\n  --destructive-foreground: oklch(0 0 0);\n  --border: oklch(1 0 0 / 10%);\n  --input: oklch(1 0 0 / 15%);\n  --ring: oklch(0.552 0.016 285.938);\n  --chart-1: oklch(0.488 0.243 264.376);\n  --chart-2: oklch(0.696 0.17 162.48);\n  --chart-3: oklch(0.769 0.188 70.08);\n  --chart-4: oklch(0.627 0.265 303.9);\n  --chart-5: oklch(0.645 0.246 16.439);\n  --sidebar: oklch(0.21 0.006 285.885);\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.274 0.006 286.033);\n  --sidebar-accent-foreground: oklch(0.985 0 0);\n  --sidebar-border: oklch(1 0 0 / 10%);\n  --sidebar-ring: oklch(0.552 0.016 285.938);\n\n  /* Custom colors for donation button */\n  --donation-bg: oklch(0.95 0.15 95);\n  --donation-bg-hover: oklch(0.92 0.18 95);\n  --donation-text: oklch(0.15 0 0);\n\n  --font-sans: Geist, sans-serif;\n  --font-serif: Georgia, serif;\n  --font-mono: Geist Mono, monospace;\n  --radius: 0.5rem;\n  --shadow-x: 0px;\n  --shadow-y: 1px;\n  --shadow-blur: 2px;\n  --shadow-spread: 0px;\n  --shadow-opacity: 0.18;\n  --shadow-color: hsl(0 0% 0%);\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:\n    0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18);\n  --shadow:\n    0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18);\n  --shadow-md:\n    0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18);\n  --shadow-lg:\n    0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18);\n  --shadow-xl:\n    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@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  --color-donation-bg: var(--donation-bg);\n  --color-donation-bg-hover: var(--donation-bg-hover);\n  --color-donation-text: var(--donation-text);\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);\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  --radius-2xl: calc(var(--radius) + 8px);\n  --radius-3xl: calc(var(--radius) + 12px);\n  --radius-4xl: calc(var(--radius) + 16px);\n  --animate-marquee-left: marquee-left var(--marquee-duration) linear var(--marquee-loop-count);\n  --animate-marquee-right: marquee-right var(--marquee-duration) linear var(--marquee-loop-count);\n  --animate-marquee-left-rtl: marquee-left-rtl var(--marquee-duration) linear var(--marquee-loop-count);\n  --animate-marquee-right-rtl: marquee-right-rtl var(--marquee-duration) linear var(--marquee-loop-count);\n  --animate-marquee-up: marquee-up var(--marquee-duration) linear var(--marquee-loop-count);\n  --animate-marquee-down: marquee-down var(--marquee-duration) linear var(--marquee-loop-count);\n  @keyframes marquee-left {\n  0% {\n    transform: translateX(0%);\n    }\n  100% {\n    transform: translateX(calc(-100% - var(--marquee-gap)));\n    }\n  }\n  @keyframes marquee-right {\n  0% {\n    transform: translateX(calc(-100% - var(--marquee-gap)));\n    }\n  100% {\n    transform: translateX(0%);\n    }\n  }\n  @keyframes marquee-up {\n  0% {\n    transform: translateY(0%);\n    }\n  100% {\n    transform: translateY(calc(-100% - var(--marquee-gap)));\n    }\n  }\n  @keyframes marquee-down {\n  0% {\n    transform: translateY(calc(-100% - var(--marquee-gap)));\n    }\n  100% {\n    transform: translateY(0%);\n    }\n  }\n  @keyframes marquee-left-rtl {\n  0% {\n    transform: translateX(0%);\n    }\n  100% {\n    transform: translateX(calc(100% + var(--marquee-gap)));\n    }\n  }\n  @keyframes marquee-right-rtl {\n  0% {\n    transform: translateX(calc(100% + var(--marquee-gap)));\n    }\n  100% {\n    transform: translateX(0%);\n    }\n  }\n}\n\nbody {\n  letter-spacing: var(--tracking-normal);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  html {\n    scroll-behavior: smooth;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n\n@layer utilities {\n  .text-balance {\n    text-wrap: balance;\n  }\n  .no-scrollbar::-webkit-scrollbar {\n    display: none;\n  }\n  .no-scrollbar {\n    -ms-overflow-style: none; /* IE and Edge */\n    scrollbar-width: none; /* Firefox */\n  }\n  .font-mono {\n    font-family: var(--font-mono);\n  }\n  .text-terminal {\n    font-family: var(--font-mono);\n    letter-spacing: -0.02em;\n  }\n}"
  },
  {
    "path": "src/app/layout.tsx",
    "content": "import { Footer } from \"@/components/layout/footer\";\nimport { Header } from \"@/components/layout/header\";\nimport { Providers } from \"@/providers/providers\";\nimport { type Metadata, type Viewport } from \"next\";\nimport { GoogleAnalytics } from \"@next/third-parties/google\";\nimport Script from \"next/script\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\n\nimport type React from \"react\";\nimport { Suspense } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport \"@/app/globals.css\";\nimport { Geist, Geist_Mono } from \"next/font/google\";\n\nexport const metadata: Metadata = {\n  metadataBase: new URL(\"https://awesome-shadcn-ui.vercel.app\"),\n  title: {\n    default: \"awesome-shadcn-ui\",\n    template: `%s | awesome-shadcn-ui`,\n  },\n  description: \"A curated list of awesome things related to shadcn/ui\",\n  keywords: [\n    \"shadcn\",\n    \"ui library\",\n    \"awesome\",\n    \"github\",\n    \"readme\",\n    \"shad\",\n    \"awesome list\",\n    \"awesome shad\",\n    \"shadcn ui\",\n  ],\n  alternates: {\n    canonical: \"https://awesome-shadcn-ui.vercel.app/\",\n  },\n  openGraph: {\n    type: \"website\",\n    locale: \"en_US\",\n    url: \"https://awesome-shadcn-ui.vercel.app/\",\n    siteName: \"awesome-shadcn-ui\",\n    title: \"awesome-shadcn-ui\",\n    description: \"A curated list of awesome things related to shadcn/ui\",\n    images: [\n      {\n        url: \"/seo.png\",\n        width: 1200,\n        height: 630,\n        alt: \"awesome-shadcn-ui\",\n      },\n    ],\n  },\n  twitter: {\n    creator: \"@birobirobiro\",\n    site: \"@birobirobiro\",\n    card: \"summary_large_image\",\n    title: \"awesome-shadcn-ui\",\n    description: \"A curated list of awesome things related to shadcn/ui\",\n    images: [\"/seo.png\"],\n  },\n  icons: {\n    icon: \"/favicon.ico\",\n  },\n};\n\nexport const viewport: Viewport = {\n  width: \"device-width\",\n  initialScale: 1,\n  maximumScale: 1,\n  userScalable: false,\n};\nconst geistSans = Geist({\n  variable: \"--font-sans\",\n  subsets: [\"latin\"],\n});\n\nconst geistMono = Geist_Mono({\n  variable: \"--font-mono\",\n  subsets: [\"latin\"],\n});\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <html lang=\"en\" suppressHydrationWarning>\n      <body\n        className={cn(\n          \"min-h-screen bg-background font-sans antialiased\",\n          geistSans.variable,\n          geistMono.variable,\n        )}\n      >\n        <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID || \"\"} />\n        {process.env.HIMETRICA_API_KEY && (\n          <Script\n            src=\"https://cdn.himetrica.com/tracker.js\"\n            data-api-key={process.env.HIMETRICA_API_KEY}\n            strategy=\"afterInteractive\"\n          />\n        )}\n        <Providers>\n          <TooltipProvider>\n            <Suspense>\n              <div className=\"relative flex min-h-screen flex-col\">\n                <Header />\n                <main className=\"flex-1\">{children}</main>\n                <Footer />\n              </div>\n            </Suspense>\n          </TooltipProvider>\n        </Providers>\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "src/app/not-found.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport Link from \"next/link\";\n\nexport default function NotFound() {\n  return (\n    <div className=\"relative flex flex-1 flex-col items-center justify-center overflow-hidden p-4 text-center\">\n      <svg\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute inset-x-0 top-20 z-0 h-auto w-full max-w-5xl mx-auto text-foreground opacity-[0.04] dark:opacity-[0.03]\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 362 145\"\n      >\n        <path\n          fill=\"currentColor\"\n          d=\"M62.6 142c-2.133 0-3.2-1.067-3.2-3.2V118h-56c-2 0-3-1-3-3V92.8c0-1.333.4-2.733 1.2-4.2L58.2 4c.8-1.333 2.067-2 3.8-2h28c2 0 3 1 3 3v85.4h11.2c.933 0 1.733.333 2.4 1 .667.533 1 1.267 1 2.2v21.2c0 .933-.333 1.733-1 2.4-.667.533-1.467.8-2.4.8H93v20.8c0 2.133-1.067 3.2-3.2 3.2H62.6zM33 90.4h26.4V51.2L33 90.4zM181.67 144.6c-7.333 0-14.333-1.333-21-4-6.666-2.667-12.866-6.733-18.6-12.2-5.733-5.467-10.266-13-13.6-22.6-3.333-9.6-5-20.667-5-33.2 0-12.533 1.667-23.6 5-33.2 3.334-9.6 7.867-17.133 13.6-22.6 5.734-5.467 11.934-9.533 18.6-12.2 6.667-2.8 13.667-4.2 21-4.2 7.467 0 14.534 1.4 21.2 4.2 6.667 2.667 12.8 6.733 18.4 12.2 5.734 5.467 10.267 13 13.6 22.6 3.334 9.6 5 20.667 5 33.2 0 12.533-1.666 23.6-5 33.2-3.333 9.6-7.866 17.133-13.6 22.6-5.6 5.467-11.733 9.533-18.4 12.2-6.666 2.667-13.733 4-21.2 4zm0-31c9.067 0 15.6-3.733 19.6-11.2 4.134-7.6 6.2-17.533 6.2-29.8s-2.066-22.2-6.2-29.8c-4.133-7.6-10.666-11.4-19.6-11.4-8.933 0-15.466 3.8-19.6 11.4-4 7.6-6 17.533-6 29.8s2 22.2 6 29.8c4.134 7.467 10.667 11.2 19.6 11.2zM316.116 142c-2.134 0-3.2-1.067-3.2-3.2V118h-56c-2 0-3-1-3-3V92.8c0-1.333.4-2.733 1.2-4.2l56.6-84.6c.8-1.333 2.066-2 3.8-2h28c2 0 3 1 3 3v85.4h11.2c.933 0 1.733.333 2.4 1 .666.533 1 1.267 1 2.2v21.2c0 .933-.334 1.733-1 2.4-.667.533-1.467.8-2.4.8h-11.2v20.8c0 2.133-1.067 3.2-3.2 3.2h-27.2zm-29.6-51.6h26.4V51.2l-26.4 39.2z\"\n        />\n      </svg>\n\n      <div className=\"relative sm:mt-36 z-10 mx-auto max-w-xl\">\n        <h1 className=\"mb-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl\">\n          Page Not Found\n        </h1>\n        <p className=\"mb-8 text-base text-muted-foreground sm:text-lg\">\n          Sorry, we couldn't find the page you're looking for.\n          <br className=\"hidden sm:block\" />\n          It might have been moved, deleted, or the URL might be incorrect.\n        </p>\n        <Button asChild size=\"lg\">\n          <Link href=\"/\">Return Home</Link>\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/app/page.tsx",
    "content": "\"use client\";\n\nimport { SubmitCTA } from \"@/components/sections/cta-submit\";\nimport Hero from \"@/components/sections/hero\";\nimport ItemList from \"@/components/sections/items-list\";\nimport { Resource, fetchAndParseReadme } from \"@/hooks/use-readme\";\nimport { isValid, parseISO } from \"date-fns\";\nimport { motion } from \"motion/react\";\nimport { useEffect, useState } from \"react\";\n\ninterface Category {\n  title: string;\n  items: Resource[];\n}\n\nconst EXCLUDED_CATEGORIES = [\"Star History\", \"Contributors\"];\n\nexport default function Home() {\n  const [categories, setCategories] = useState<Category[]>([]);\n  const [filteredItems, setFilteredItems] = useState<Resource[]>([]);\n\n  useEffect(() => {\n    async function fetchData() {\n      const fetchedResources = await fetchAndParseReadme();\n\n      const groupedCategories = fetchedResources.reduce(\n        (acc, resource) => {\n          if (!EXCLUDED_CATEGORIES.includes(resource.category)) {\n            if (!acc[resource.category]) {\n              acc[resource.category] = [];\n            }\n            acc[resource.category].push(resource);\n          }\n          return acc;\n        },\n        {} as Record<string, Resource[]>,\n      );\n\n      const formattedCategories = Object.entries(groupedCategories).map(\n        ([title, items]) => ({\n          title,\n          items,\n        }),\n      );\n\n      const eligibleItems = fetchedResources.filter(\n        (item) => !EXCLUDED_CATEGORIES.includes(item.category),\n      );\n\n      const sortedItems = eligibleItems.sort((a, b) => {\n        const dateA =\n          a.date && a.date !== \"Unknown\" ? parseISO(a.date) : new Date(0);\n        const dateB =\n          b.date && b.date !== \"Unknown\" ? parseISO(b.date) : new Date(0);\n\n        if (!isValid(dateA)) return 1;\n        if (!isValid(dateB)) return -1;\n\n        return dateB.getTime() - dateA.getTime();\n      });\n\n      setCategories(formattedCategories);\n      setFilteredItems(sortedItems);\n    }\n\n    fetchData();\n  }, []);\n\n  return (\n    <motion.div\n      className=\"container mx-auto max-w-7xl px-4\"\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1 }}\n      transition={{ duration: 0.3 }}\n    >\n      <Hero />\n\n      <motion.div\n        className=\"mt-12 mb-8\"\n        initial={{ opacity: 0, y: 10 }}\n        animate={{ opacity: 1, y: 0 }}\n        transition={{ duration: 0.3, delay: 0.4 }}\n      >\n        <ItemList\n          key={filteredItems.length}\n          items={filteredItems}\n          categories={categories}\n        />\n      </motion.div>\n\n      <SubmitCTA />\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/components/github-stars.tsx",
    "content": "\"use client\";\n\nimport { Octokit } from \"@octokit/rest\";\nimport { useEffect, useState } from \"react\";\n\nasync function getStars() {\n  const octokit = new Octokit();\n  const { data } = await octokit.repos.get({\n    owner: \"birobirobiro\",\n    repo: \"awesome-shadcn-ui\",\n  });\n  return data.stargazers_count;\n}\n\nexport function GithubStars() {\n  const [stars, setStars] = useState<number | null>(null);\n\n  useEffect(() => {\n    getStars().then(setStars);\n  }, []);\n\n  if (stars === null) return null;\n\n  const formattedStars =\n    stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : stars;\n\n  return <span className=\"text-sm\">{formattedStars}</span>;\n}\n"
  },
  {
    "path": "src/components/item-card.tsx",
    "content": "import {\n  Card,\n  CardContent,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { ArrowRight, Bookmark, ExternalLink } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { categoryNameToSlug } from \"@/lib/slugs\";\nimport { cn } from \"@/lib/utils\";\nimport { motion } from \"motion/react\";\nimport React from \"react\";\n\ninterface ItemCardProps {\n  id: string;\n  title: string;\n  description: string;\n  url: string;\n  category: string;\n  date?: string;\n  isBookmarked: boolean;\n  onBookmark: (id: string) => void;\n  isBookmarkLoading?: boolean;\n}\n\nconst standardAnimations = {\n  initial: { opacity: 0, y: 20 },\n  animate: { opacity: 1, y: 0 },\n  exit: { opacity: 0, y: -20 },\n  transition: { duration: 0.2, ease: \"easeOut\" },\n};\n\nconst ItemCard: React.FC<ItemCardProps> = ({\n  id,\n  title,\n  description,\n  url,\n  category,\n  date,\n  isBookmarked,\n  onBookmark,\n  isBookmarkLoading = false,\n}) => {\n  const handleCardClick = () => {\n    window.location.href = `/categories/${categoryNameToSlug(category)}/${id}`;\n  };\n\n  return (\n    <motion.div layout {...standardAnimations} className=\"h-full\">\n      <Card\n        className=\"h-full group hover:border-primary/40 hover:shadow-lg transition-all duration-200 overflow-hidden cursor-pointer flex flex-col gap-0 py-0\"\n        onClick={handleCardClick}\n      >\n        <CardHeader className=\"p-4 pb-3 shrink-0\">\n          <div className=\"flex items-start justify-between gap-2 mb-2\">\n            <span className=\"text-[10px] font-mono uppercase tracking-wider text-primary border border-primary/30 bg-primary/5 px-2 py-0.5\">\n              [{category}]\n            </span>\n            <ArrowRight className=\"h-4 w-4 text-muted-foreground group-hover:text-primary group-hover:translate-x-0.5 transition-all flex-shrink-0\" />\n          </div>\n          <CardTitle className=\"text-base font-bold group-hover:text-primary transition-colors duration-200 line-clamp-2 leading-snug\">\n            {title}\n          </CardTitle>\n        </CardHeader>\n\n        <div className=\"h-px bg-border mx-4 shrink-0\" />\n\n        <CardContent className=\"px-4 py-3 flex-1 flex flex-col gap-3 min-h-0\">\n          <p className=\"text-sm text-muted-foreground line-clamp-3 flex-1 leading-relaxed overflow-hidden\">\n            {description}\n          </p>\n\n          {date && (\n            <div className=\"flex items-center gap-2 text-xs font-mono text-muted-foreground/70\">\n              <span>Added: {date}</span>\n            </div>\n          )}\n        </CardContent>\n\n        <div className=\"h-px bg-border mx-4 shrink-0\" />\n\n        <CardFooter className=\"flex gap-3 items-center p-4 pt-3 shrink-0\">\n          {/* Bookmark Button */}\n          <Button\n            variant={isBookmarked ? \"default\" : \"ghost\"}\n            size=\"icon\"\n            disabled={isBookmarkLoading}\n            onClick={(e) => {\n              e.preventDefault();\n              e.stopPropagation();\n              if (!isBookmarkLoading) onBookmark(id);\n            }}\n            className={cn(\n              \"h-8 w-8 transition-all duration-200\",\n              isBookmarked\n                ? \"bg-primary hover:bg-primary/90 text-primary-foreground\"\n                : \"text-muted-foreground hover:text-foreground hover:bg-muted\",\n              isBookmarkLoading && \"opacity-50 cursor-not-allowed\",\n            )}\n          >\n            <Bookmark\n              className={cn(\n                \"h-3.5 w-3.5\",\n                isBookmarked && \"fill-current\",\n                isBookmarkLoading && \"animate-pulse\",\n              )}\n            />\n          </Button>\n\n          {/* External Link Button */}\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            asChild\n            className=\"h-8 w-8 text-muted-foreground hover:text-foreground hover:bg-muted transition-all duration-200\"\n          >\n            <a\n              href={url}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              onClick={(e) => e.stopPropagation()}\n            >\n              <ExternalLink className=\"h-3.5 w-3.5\" />\n            </a>\n          </Button>\n        </CardFooter>\n      </Card>\n    </motion.div>\n  );\n};\n\nexport default ItemCard;\n"
  },
  {
    "path": "src/components/item-grid.tsx",
    "content": "import React from \"react\";\nimport { Resource } from \"@/hooks/use-readme\";\nimport { AnimatePresence, LayoutGroup, motion } from \"motion/react\";\nimport ItemCard from \"./item-card\";\nimport SponsorCard from \"./sponsor-card\";\nimport { sponsors } from \"@/components/sponsors/sponsors\";\n\nconst SPONSOR_INTERVAL = 8;\n\ninterface ItemGridProps {\n  items: Resource[];\n  bookmarkedItems: string[];\n  onBookmark: (id: string) => void;\n  isBookmarkLoading?: boolean;\n}\n\nexport function ItemGrid({\n  items,\n  bookmarkedItems,\n  onBookmark,\n  isBookmarkLoading = false,\n}: ItemGridProps) {\n  if (items.length === 0) {\n    return (\n      <motion.div\n        initial={{ opacity: 0, y: 20 }}\n        animate={{ opacity: 1, y: 0 }}\n        transition={{ duration: 0.2 }}\n        className=\"text-center py-12 border border-dashed border-border bg-muted/30\"\n      >\n        <motion.div\n          initial={{ opacity: 0 }}\n          animate={{ opacity: 1 }}\n          transition={{ duration: 0.2 }}\n          className=\"inline-flex justify-center items-center w-16 h-16 bg-muted mb-4\"\n        >\n          <svg\n            className=\"w-8 h-8 text-muted-foreground\"\n            fill=\"none\"\n            viewBox=\"0 0 24 24\"\n            stroke=\"currentColor\"\n          >\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth={1.5}\n              d=\"M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n            />\n          </svg>\n        </motion.div>\n        <p className=\"text-foreground text-lg font-medium\">\n          No items found matching your criteria.\n        </p>\n        <p className=\"text-muted-foreground text-sm mt-1\">\n          Try adjusting your search or filter settings.\n        </p>\n      </motion.div>\n    );\n  }\n\n  const gridItems: React.ReactNode[] = [];\n\n  items.forEach((item, index) => {\n    gridItems.push(\n      <ItemCard\n        key={`item-${item.id}`}\n        id={item.id}\n        title={item.name}\n        description={item.description}\n        url={item.url}\n        category={item.category}\n        date={item.date}\n        isBookmarked={bookmarkedItems.includes(item.id)}\n        onBookmark={onBookmark}\n        isBookmarkLoading={isBookmarkLoading}\n      />\n    );\n\n    if (index % SPONSOR_INTERVAL === 0 && sponsors.length > 0) {\n      const sponsor = sponsors[Math.floor(index / SPONSOR_INTERVAL) % sponsors.length];\n      gridItems.push(\n        <SponsorCard key={`sponsor-${index}`} sponsor={sponsor} />\n      );\n    }\n  });\n\n  return (\n    <LayoutGroup>\n      <AnimatePresence mode=\"wait\">\n        <motion.div\n          key={`grid`}\n          className={`grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4`}\n          initial={{ opacity: 0 }}\n          animate={{ opacity: 1 }}\n          transition={{ duration: 0.2 }}\n          layout\n        >\n          {gridItems}\n        </motion.div>\n      </AnimatePresence>\n    </LayoutGroup>\n  );\n}\n"
  },
  {
    "path": "src/components/kibo-ui/theme-switcher/index.tsx",
    "content": "\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { Monitor, Moon, Sun } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst themes = [\n  {\n    key: \"system\",\n    icon: Monitor,\n    label: \"System theme\",\n  },\n  {\n    key: \"light\",\n    icon: Sun,\n    label: \"Light theme\",\n  },\n  {\n    key: \"dark\",\n    icon: Moon,\n    label: \"Dark theme\",\n  },\n];\n\nexport type ThemeSwitcherProps = {\n  value?: \"light\" | \"dark\" | \"system\";\n  onChange?: (theme: \"light\" | \"dark\" | \"system\") => void;\n  defaultValue?: \"light\" | \"dark\" | \"system\";\n  className?: string;\n};\n\nexport const ThemeSwitcher = ({\n  value,\n  onChange,\n  defaultValue = \"system\",\n  className,\n}: ThemeSwitcherProps) => {\n  const { theme, setTheme, resolvedTheme } = useTheme();\n  const [mounted, setMounted] = useState(false);\n\n  const handleThemeClick = useCallback(\n    (themeKey: \"light\" | \"dark\" | \"system\") => {\n      setTheme(themeKey);\n      onChange?.(themeKey);\n    },\n    [setTheme, onChange],\n  );\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  if (!mounted) {\n    return null;\n  }\n\n  const currentTheme = value || theme || defaultValue;\n\n  return (\n    <div\n      className={cn(\n        \"relative isolate flex h-8 rounded-full bg-background p-1\",\n        className,\n      )}\n    >\n      {themes.map(({ key, icon: Icon, label }) => {\n        const isActive = currentTheme === key;\n\n        return (\n          <button\n            aria-label={label}\n            className=\"relative h-6 w-6 rounded-full\"\n            key={key}\n            onClick={() => handleThemeClick(key as \"light\" | \"dark\" | \"system\")}\n            type=\"button\"\n          >\n            {isActive && (\n              <motion.div\n                className=\"absolute inset-0 rounded-full bg-secondary\"\n                layoutId=\"activeTheme\"\n                transition={{ type: \"spring\", duration: 0.5 }}\n              />\n            )}\n            <Icon\n              className={cn(\n                \"relative z-10 m-auto h-4 w-4\",\n                isActive ? \"text-foreground\" : \"text-muted-foreground\",\n              )}\n            />\n          </button>\n        );\n      })}\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/components/layout/footer.tsx",
    "content": "\"use client\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { ThemeSwitcher } from \"@/components/kibo-ui/theme-switcher\";\nimport { useCategories } from \"@/hooks/use-categories\";\nimport { ArrowRight, ExternalLink, Github } from \"lucide-react\";\nimport { motion, useInView } from \"motion/react\";\nimport Link from \"next/link\";\nimport { useRef } from \"react\";\nimport { Button } from \"../ui/button\";\nimport { GithubStars } from \"@/components/github-stars\";\n\nexport function Footer() {\n  const { categories } = useCategories();\n  const ref = useRef(null);\n  const isInView = useInView(ref, { once: true, amount: 0.3 });\n\n  const containerVariants = {\n    hidden: { opacity: 0 },\n    visible: {\n      opacity: 1,\n      transition: {\n        staggerChildren: 0.1,\n        delayChildren: 0.2,\n      },\n    },\n  };\n\n  const itemVariants = {\n    hidden: { opacity: 0, y: 20 },\n    visible: { opacity: 1, y: 0 },\n  };\n\n  const quickLinks = [\n    { name: \"Home\", href: \"/\" },\n    { name: \"Categories\", href: \"/categories\" },\n    {\n      name: \"GitHub\",\n      href: \"https://github.com/birobirobiro/awesome-shadcn-ui\",\n      external: true,\n    },\n  ];\n\n  const socialLinks = [\n    {\n      name: \"GitHub\",\n      href: \"https://github.com/birobirobiro/awesome-shadcn-ui\",\n      icon: Github,\n    },\n  ];\n\n  return (\n    <footer className=\"border-t bg-background\">\n      <motion.div\n        ref={ref}\n        variants={containerVariants}\n        initial=\"hidden\"\n        animate={isInView ? \"visible\" : \"hidden\"}\n        className=\"container mx-auto px-4 py-6 md:py-8\"\n      >\n        {/* Main Footer Content */}\n        <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-6\">\n          {/* Brand Section */}\n          <motion.div variants={itemVariants} className=\"lg:col-span-1\">\n            <Link href=\"/\" className=\"flex items-center space-x-2 mb-4 group\">\n              <img\n                src=\"/logo.svg\"\n                alt=\"logo\"\n                className=\"h-8 w-auto transition-transform group-hover:scale-105\"\n              />\n              <span className=\"text-lg font-bold\">awesome-shadcn/ui</span>\n            </Link>\n            <p className=\"text-sm text-muted-foreground mb-4 max-w-sm\">\n              A curated list of awesome things related to shadcn/ui. Discover,\n              contribute, and grow the community.\n            </p>\n          </motion.div>\n\n          {/* Quick Links */}\n          <motion.div variants={itemVariants}>\n            <h4 className=\"font-semibold mb-4\">Quick Links</h4>\n            <ul className=\"space-y-2\">\n              {quickLinks.map((link) => (\n                <li key={link.name}>\n                  <Link\n                    href={link.href}\n                    target={link.external ? \"_blank\" : undefined}\n                    rel={link.external ? \"noopener noreferrer\" : undefined}\n                    className=\"text-sm text-muted-foreground hover:text-foreground transition-colors flex items-center gap-2 group\"\n                  >\n                    {link.name}\n                    {link.external && (\n                      <ExternalLink className=\"h-3 w-3 group-hover:translate-x-0.5 transition-transform\" />\n                    )}\n                  </Link>\n                </li>\n              ))}\n            </ul>\n          </motion.div>\n\n          {/* Categories Preview */}\n          <motion.div variants={itemVariants}>\n            <h4 className=\"font-semibold mb-4\">Categories</h4>\n            <ul className=\"space-y-2\">\n              {categories.slice(0, 6).map((category) => (\n                <li key={category.title}>\n                  <Link\n                    href={`/categories/${category.slug}`}\n                    className=\"text-sm text-muted-foreground hover:text-foreground transition-colors flex items-center gap-2 group\"\n                  >\n                    {category.title}\n                    <ArrowRight className=\"h-3 w-3 group-hover:translate-x-0.5 transition-transform\" />\n                  </Link>\n                </li>\n              ))}\n            </ul>\n          </motion.div>\n\n          {/* Categories Preview */}\n          <motion.div variants={itemVariants}>\n            <h4 className=\"font-semibold mb-4\">Categories</h4>\n            <ul className=\"space-y-2\">\n              {categories.slice(6, 12).map((category) => (\n                <li key={category.title}>\n                  <Link\n                    href={`/categories/${category.slug}`}\n                    className=\"text-sm text-muted-foreground hover:text-foreground transition-colors flex items-center gap-2 group\"\n                  >\n                    {category.title}\n                    <ArrowRight className=\"h-3 w-3 group-hover:translate-x-0.5 transition-transform\" />\n                  </Link>\n                </li>\n              ))}\n            </ul>\n          </motion.div>\n\n          {/* Social & Actions */}\n          <motion.div variants={itemVariants}>\n            <h4 className=\"font-semibold mb-4\">Connect</h4>\n            <div className=\"space-y-4\">\n              <div className=\"flex gap-3 items-center\">\n                {socialLinks.map((social) => (\n                  <div key={social.name} className=\"flex items-center gap-2\">\n                    <Button variant=\"outline\" size=\"icon\" asChild>\n                      <a\n                        href={social.href}\n                        target=\"_blank\"\n                        rel=\"noopener noreferrer\"\n                      >\n                        <social.icon className=\"h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors\" />\n                      </a>\n                    </Button>\n                    <GithubStars />\n                  </div>\n                ))}\n              </div>\n\n              <div className=\"flex items-center gap-2\">\n                <ThemeSwitcher />\n              </div>\n            </div>\n          </motion.div>\n        </div>\n\n        <Separator className=\"mb-6\" />\n\n        {/* Bottom Section */}\n        <motion.div\n          variants={itemVariants}\n          className=\"flex flex-col md:flex-row justify-between items-center gap-4\"\n        >\n          <div className=\"flex flex-col md:flex-row items-center gap-4 text-xs text-muted-foreground font-mono\">\n            <p>\n              © {new Date().getFullYear()} awesome-shadcn/ui. All rights\n              reserved.\n            </p>\n          </div>\n\n          <div className=\"flex items-center gap-4 text-xs text-muted-foreground font-mono\">\n            <span>Built with Next.js & shadcn/ui</span>\n          </div>\n        </motion.div>\n      </motion.div>\n    </footer>\n  );\n}\n"
  },
  {
    "path": "src/components/layout/header.tsx",
    "content": "\"use client\";\n\nimport { PRSubmissionDialog } from \"@/components/pr-submission-dialog\";\nimport { ThemeSwitcher } from \"@/components/kibo-ui/theme-switcher\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetHeader,\n  SheetTitle,\n  SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { Github, Menu } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport { useState } from \"react\";\nimport { sponsors } from \"@/components/sponsors/sponsors\";\nimport { GithubStars } from \"@/components/github-stars\";\nimport { Logo } from \"@/components/logo\";\nimport { ModeToggle } from \"@/components/theme-toggle\";\n\nexport function Header() {\n  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n  const pathname = usePathname();\n\n  return (\n    <header className=\"sticky top-0 z-50 border-b backdrop-blur bg-background/80\">\n      <div className=\"container mx-auto px-4\">\n        <div className=\"flex items-center justify-between h-14\">\n          {/* Left: Logo + Navigation */}\n          <div className=\"flex items-center gap-6\">\n            <Link\n              href=\"/\"\n              className=\"flex items-center gap-2 hover:opacity-80 transition-opacity\"\n            >\n              <Logo className=\"h-5 w-auto\" />\n            </Link>\n\n            {/* Navigation - Desktop */}\n            <nav className=\"hidden md:flex items-center gap-6\">\n              <Link\n                href=\"/categories\"\n                className={`text-sm transition-colors ${\n                  pathname === \"/categories\" ||\n                  pathname?.startsWith(\"/categories\")\n                    ? \"text-foreground font-medium\"\n                    : \"text-muted-foreground hover:text-foreground\"\n                }`}\n              >\n                Categories\n              </Link>\n              <Link\n                href=\"/bookmarks\"\n                className={`text-sm transition-colors ${\n                  pathname === \"/bookmarks\"\n                    ? \"text-foreground font-medium\"\n                    : \"text-muted-foreground hover:text-foreground\"\n                }`}\n              >\n                Bookmarks\n              </Link>\n            </nav>\n          </div>\n\n          {/* Right: Actions */}\n          <div className=\"flex items-center gap-2\">\n            {/* Sponsorship - Desktop */}\n            {sponsors.map((sponsor) => (\n              <Tooltip key={sponsor.name}>\n                <TooltipTrigger asChild>\n                  <a\n                    href={sponsor.url}\n                    target=\"_blank\"\n                    rel=\"noopener sponsored\"\n                    className=\"hidden lg:flex items-center justify-center bg-muted/30 p-1.5 transition-colors duration-200 hover:bg-muted/50 border border-border/50\"\n                  >\n                    {sponsor.LogoComponent}\n                  </a>\n                </TooltipTrigger>\n                <TooltipContent>\n                  <p className=\"font-medium\">{sponsor.name}</p>\n                  <p className=\"text-muted-foreground text-xs\">{sponsor.description}</p>\n                </TooltipContent>\n              </Tooltip>\n            ))}\n\n            {/* Submit Button - Desktop - REMOVED, now in search filters */}\n\n            <Button variant=\"outline\" className=\"h-[34px] gap-1\" asChild>\n              <a\n                href=\"https://github.com/birobirobiro/awesome-shadcn-ui\"\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n              >\n                <Github className=\"h-4 w-4\" />\n                <GithubStars />\n              </a>\n            </Button>\n\n            <ModeToggle />\n\n            {/* Mobile Menu */}\n            <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>\n              <SheetTrigger asChild className=\"md:hidden\">\n                <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\">\n                  <Menu className=\"h-4 w-4\" />\n                  <span className=\"sr-only\">Toggle menu</span>\n                </Button>\n              </SheetTrigger>\n              <SheetContent side=\"right\" className=\"w-full sm:w-[380px] p-0\">\n                <div className=\"flex flex-col h-full\">\n                  {/* Header */}\n                  <SheetHeader className=\"border-b px-6 py-4\">\n                    <SheetTitle asChild>\n                      <Link\n                        href=\"/\"\n                        onClick={() => setMobileMenuOpen(false)}\n                        className=\"flex items-center gap-3\"\n                      >\n                        <Logo className=\"h-5 w-auto\" />\n                      </Link>\n                    </SheetTitle>\n                  </SheetHeader>\n\n                  {/* Navigation */}\n                  <div className=\"flex-1 px-6 py-6\">\n                    <nav className=\"flex flex-col gap-2\">\n                      <Link\n                        href=\"/categories\"\n                        onClick={() => setMobileMenuOpen(false)}\n                        className=\"flex items-center gap-3 px-4 py-3 text-sm font-medium bg-muted/50 hover:bg-muted transition-colors\"\n                      >\n                        <span className=\"flex-1\">Categories</span>\n                      </Link>\n                      <Link\n                        href=\"/bookmarks\"\n                        onClick={() => setMobileMenuOpen(false)}\n                        className=\"flex items-center gap-3 px-4 py-3 text-sm font-medium bg-muted/50 hover:bg-muted transition-colors\"\n                      >\n                        <span className=\"flex-1\">Bookmarks</span>\n                      </Link>\n                    </nav>\n\n                    <div className=\"mt-8\">\n                      <PRSubmissionDialog\n                        trigger={\n                          <Button className=\"w-full justify-center gap-2\">\n                            <Github className=\"h-4 w-4\" />\n                            Submit Resource\n                          </Button>\n                        }\n                      />\n                    </div>\n                  </div>\n\n                  {/* Sponsored Section */}\n                  <div className=\"border-t px-6 py-4 bg-muted/30\">\n                    <span className=\"text-[10px] text-muted-foreground uppercase tracking-widest font-mono block text-center mb-3\">\n                      Sponsored by\n                    </span>\n                    <div className=\"flex flex-col gap-2\">\n                      {sponsors.map((sponsor) => (\n                        <a\n                          key={sponsor.name}\n                          href={sponsor.url}\n                          target=\"_blank\"\n                          rel=\"noopener sponsored\"\n                          className=\"flex items-center gap-3 bg-background px-4 py-3 transition-all duration-200 hover:shadow-md border\"\n                        >\n                          {sponsor.LogoComponent}\n                          <div className=\"flex flex-col\">\n                            <span className=\"text-sm font-medium text-foreground\">\n                              {sponsor.name}\n                            </span>\n                            <span className=\"text-xs text-muted-foreground\">\n                              {sponsor.description}\n                            </span>\n                          </div>\n                        </a>\n                      ))}\n                    </div>\n                  </div>\n                </div>\n              </SheetContent>\n            </Sheet>\n          </div>\n        </div>\n      </div>\n    </header>\n  );\n}\n"
  },
  {
    "path": "src/components/layout/page-header.tsx",
    "content": "\"use client\";\n\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport { motion } from \"motion/react\";\nimport Link from \"next/link\";\nimport React, { ReactNode } from \"react\";\n\ninterface PageHeaderProps {\n  title: string;\n  description: string;\n  icon?: ReactNode;\n  breadcrumbs?: Array<{\n    label: string;\n    href: string;\n  }>;\n  actions?: ReactNode;\n  className?: string;\n}\n\nexport function PageHeader({\n  title,\n  description,\n  icon,\n  breadcrumbs = [],\n  actions,\n  className = \"\",\n}: PageHeaderProps) {\n  return (\n    <motion.div\n      className={`space-y-4 sm:space-y-6 mb-8 sm:mb-12 ${className}`}\n      initial={{ opacity: 0, y: 20 }}\n      animate={{ opacity: 1, y: 0 }}\n      transition={{ duration: 0.5, ease: \"easeOut\" }}\n    >\n      {breadcrumbs.length > 0 && (\n        <Breadcrumb>\n          <BreadcrumbList className=\"flex-wrap\">\n            <BreadcrumbItem>\n              <BreadcrumbLink asChild>\n                <Link href=\"/\" className=\"text-sm\">\n                  Home\n                </Link>\n              </BreadcrumbLink>\n            </BreadcrumbItem>\n            {breadcrumbs.map((breadcrumb, index) => (\n              <React.Fragment key={index}>\n                <BreadcrumbSeparator />\n                <BreadcrumbItem>\n                  {index === breadcrumbs.length - 1 ? (\n                    <BreadcrumbPage className=\"text-sm truncate max-w-[150px] sm:max-w-none\">\n                      {breadcrumb.label}\n                    </BreadcrumbPage>\n                  ) : (\n                    <BreadcrumbLink asChild>\n                      <Link\n                        href={breadcrumb.href}\n                        className=\"text-sm truncate max-w-[100px] sm:max-w-none\"\n                      >\n                        {breadcrumb.label}\n                      </Link>\n                    </BreadcrumbLink>\n                  )}\n                </BreadcrumbItem>\n              </React.Fragment>\n            ))}\n          </BreadcrumbList>\n        </Breadcrumb>\n      )}\n\n      <div className=\"space-y-3 sm:space-y-4\">\n        <div className=\"flex items-start sm:items-center gap-2 sm:gap-3\">\n          {icon}\n          <h1 className=\"text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold tracking-tight leading-tight break-words\">\n            {title}\n          </h1>\n        </div>\n        <p className=\"text-base sm:text-lg md:text-xl text-muted-foreground max-w-full break-words\">\n          {description}\n        </p>\n        {actions && <div className=\"w-full overflow-hidden\">{actions}</div>}\n      </div>\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/components/logo.tsx",
    "content": "import Image from \"next/image\";\n\ninterface LogoProps {\n  className?: string;\n}\n\nexport function Logo({ className = \"h-5 w-auto\" }: LogoProps) {\n  return (\n    <Image\n      src=\"/logo-mobile.svg\"\n      alt=\"awesome-shadcn/ui\"\n      width={24}\n      height={24}\n      className={className}\n    />\n  );\n}\n\nexport function HeroLogo({ className = \"h-40 w-auto\" }: LogoProps) {\n  return (\n    <Image\n      src=\"/logo.svg\"\n      alt=\"awesome-shadcn/ui\"\n      width={160}\n      height={160}\n      className={className}\n    />\n  );\n}\n"
  },
  {
    "path": "src/components/pagination-controls.tsx",
    "content": "import {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport {\n  ChevronLeft,\n  ChevronRight,\n  ChevronsLeft,\n  ChevronsRight,\n} from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { motion } from \"motion/react\";\n\ninterface PaginationControlsProps {\n  currentPage: number;\n  totalPages: number;\n  itemsPerPage: number;\n  handlePageChange: (pageNumber: number) => void;\n  handleItemsPerPageChange: (value: string) => void;\n  itemsPerPageOptions: number[];\n}\n\nexport function PaginationControls({\n  currentPage,\n  totalPages,\n  itemsPerPage,\n  handlePageChange,\n  handleItemsPerPageChange,\n  itemsPerPageOptions,\n}: PaginationControlsProps) {\n  return (\n    <motion.div\n      className=\"flex flex-col sm:flex-row items-center justify-between space-y-4 sm:space-y-0\"\n      variants={{\n        hidden: { opacity: 0 },\n        visible: {\n          opacity: 1,\n          transition: {\n            delay: 0.2,\n          },\n        },\n      }}\n    >\n      <div className=\"flex items-center space-x-2\">\n        <span className=\"text-sm text-muted-foreground\">Items per page:</span>\n        <Select\n          value={itemsPerPage.toString()}\n          onValueChange={handleItemsPerPageChange}\n        >\n          <SelectTrigger className=\"w-[70px]\">\n            <SelectValue placeholder={itemsPerPage} />\n          </SelectTrigger>\n          <SelectContent>\n            {itemsPerPageOptions.map((number) => (\n              <SelectItem key={number} value={number.toString()}>\n                {number}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n      </div>\n      <div className=\"flex items-center space-x-2\">\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          onClick={() => handlePageChange(1)}\n          disabled={currentPage === 1}\n        >\n          <ChevronsLeft className=\"h-4 w-4\" />\n        </Button>\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          onClick={() => handlePageChange(currentPage - 1)}\n          disabled={currentPage === 1}\n        >\n          <ChevronLeft className=\"h-4 w-4\" />\n        </Button>\n        <span className=\"text-sm text-muted-foreground mx-2\">\n          Page {currentPage} of {totalPages}\n        </span>\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          onClick={() => handlePageChange(currentPage + 1)}\n          disabled={currentPage === totalPages}\n        >\n          <ChevronRight className=\"h-4 w-4\" />\n        </Button>\n        <Button\n          variant=\"outline\"\n          size=\"icon\"\n          onClick={() => handlePageChange(totalPages)}\n          disabled={currentPage === totalPages}\n        >\n          <ChevronsRight className=\"h-4 w-4\" />\n        </Button>\n      </div>\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/components/pr-submission-dialog.tsx",
    "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\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 { SubmissionData, usePRSubmission } from \"@/hooks/use-pr-submission\";\nimport {\n  AlertCircle,\n  ArrowRight,\n  CheckCircle2,\n  Loader2,\n  Plus,\n} from \"lucide-react\";\nimport { useCallback, useState } from \"react\";\nimport { toast } from \"sonner\";\n\nconst CATEGORIES = [\n  \"Libs and Components\",\n  \"Registries\",\n  \"Plugins and Extensions\",\n  \"Colors and Customizations\",\n  \"Animations\",\n  \"Tools\",\n  \"Websites and Portfolios Inspirations\",\n  \"Platforms\",\n  \"Ports\",\n  \"Design System\",\n  \"Boilerplates / Templates\",\n];\n\ninterface PRSubmissionDialogProps {\n  trigger?: React.ReactNode;\n}\n\nexport function PRSubmissionDialog({ trigger }: PRSubmissionDialogProps) {\n  const [open, setOpen] = useState(false);\n  const [step, setStep] = useState<\"form\" | \"success\">(\"form\");\n  const [formData, setFormData] = useState<SubmissionData>({\n    name: \"\",\n    description: \"\",\n    url: \"\",\n    category: \"\",\n  });\n  const [submissionResult, setSubmissionResult] = useState<{\n    prNumber?: number;\n    prUrl?: string;\n  } | null>(null);\n\n  const {\n    isSubmitting,\n    error: submissionError,\n    submissionStatus,\n    submitPR,\n  } = usePRSubmission();\n\n  const handleSubmit = useCallback(async () => {\n    if (\n      !formData.name ||\n      !formData.description ||\n      !formData.url ||\n      !formData.category\n    ) {\n      toast.error(\"Please fill in all fields\");\n      return;\n    }\n\n    const result = await submitPR(null, formData, { login: \"\" });\n\n    if (result.success) {\n      setSubmissionResult({\n        prNumber: result.prNumber,\n        prUrl: result.prUrl,\n      });\n      setStep(\"success\");\n      toast.success(\"Pull request submitted successfully!\");\n    } else {\n      toast.error(result.error || \"Failed to submit pull request\");\n    }\n  }, [formData, submitPR]);\n\n  const handleClose = useCallback(() => {\n    setOpen(false);\n    setTimeout(() => {\n      setStep(\"form\");\n      setFormData({ name: \"\", description: \"\", url: \"\", category: \"\" });\n      setSubmissionResult(null);\n    }, 300);\n  }, []);\n\n  const handleOpenChange = useCallback(\n    (isOpen: boolean) => {\n      if (!isOpen) {\n        handleClose();\n      } else {\n        setOpen(true);\n      }\n    },\n    [handleClose],\n  );\n\n  const renderFormStep = () => (\n    <div className=\"space-y-3\">\n      <div className=\"text-center space-y-1 mb-2\">\n        <h3 className=\"text-lg font-semibold\">Submit a New Resource</h3>\n        <p className=\"text-sm text-muted-foreground\">\n          Add a new entry to the awesome shadcn/ui list\n        </p>\n      </div>\n\n      <div className=\"space-y-3\">\n        <div>\n          <Label htmlFor=\"category\">Category</Label>\n          <Select\n            value={formData.category}\n            onValueChange={(value) =>\n              setFormData((prev) => ({ ...prev, category: value }))\n            }\n            disabled={isSubmitting}\n          >\n            <SelectTrigger className=\"mt-1.5 h-9 w-full\">\n              <SelectValue placeholder=\"Select a category\" />\n            </SelectTrigger>\n            <SelectContent>\n              {CATEGORIES.map((category) => (\n                <SelectItem key={category} value={category}>\n                  {category}\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n        </div>\n\n        <div>\n          <Label htmlFor=\"name\">Resource Name</Label>\n          <Input\n            id=\"name\"\n            placeholder=\"e.g., shadcn-nextjs-dashboard\"\n            value={formData.name}\n            onChange={(e) =>\n              setFormData((prev) => ({ ...prev, name: e.target.value }))\n            }\n            className=\"mt-1.5 h-9\"\n            disabled={isSubmitting}\n          />\n        </div>\n\n        <div>\n          <Label htmlFor=\"description\">Description</Label>\n          <Input\n            id=\"description\"\n            placeholder=\"e.g., Admin Dashboard UI built with Shadcn and NextJS\"\n            value={formData.description}\n            onChange={(e) =>\n              setFormData((prev) => ({ ...prev, description: e.target.value }))\n            }\n            className=\"mt-1.5 h-9\"\n            disabled={isSubmitting}\n          />\n        </div>\n\n        <div>\n          <Label htmlFor=\"url\">URL</Label>\n          <Input\n            id=\"url\"\n            type=\"url\"\n            placeholder=\"https://github.com/username/project\"\n            value={formData.url}\n            onChange={(e) =>\n              setFormData((prev) => ({ ...prev, url: e.target.value }))\n            }\n            className=\"mt-1.5 h-9\"\n            disabled={isSubmitting}\n          />\n        </div>\n\n        {submissionError && (\n          <div className=\"flex items-center gap-2 text-red-500 text-sm\">\n            <AlertCircle className=\"h-4 w-4\" />\n            {submissionError}\n          </div>\n        )}\n      </div>\n\n      <div className=\"flex gap-2 pt-2\">\n        <Button\n          onClick={handleSubmit}\n          disabled={isSubmitting}\n          className=\"flex-1 h-9\"\n        >\n          {isSubmitting ? (\n            <div className=\"flex items-center justify-center\">\n              <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n              <span>{submissionStatus || \"Submitting...\"}</span>\n            </div>\n          ) : (\n            <>\n              Submit PR\n              <ArrowRight className=\"ml-2 h-4 w-4\" />\n            </>\n          )}\n        </Button>\n      </div>\n    </div>\n  );\n\n  const renderSuccessStep = () => (\n    <div className=\"text-center space-y-4\">\n      <CheckCircle2 className=\"h-12 w-12 mx-auto text-green-500\" />\n      <div>\n        <h3 className=\"text-lg font-semibold\">PR Created Successfully!</h3>\n        <p className=\"text-sm text-muted-foreground\">\n          Your pull request has been created automatically\n        </p>\n      </div>\n\n      {submissionResult && (\n        <div className=\"space-y-3\">\n          <Badge variant=\"secondary\" className=\"text-sm\">\n            PR #{submissionResult.prNumber}\n          </Badge>\n          <div>\n            <Button asChild variant=\"outline\" className=\"w-full\">\n              <a\n                href={submissionResult.prUrl}\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n              >\n                View Pull Request\n                <ArrowRight className=\"ml-2 h-4 w-4\" />\n              </a>\n            </Button>\n          </div>\n          <div className=\"text-xs text-muted-foreground\">\n            <p>PR will be reviewed by maintainers</p>\n          </div>\n        </div>\n      )}\n\n      <Button onClick={handleClose} className=\"w-full\">\n        Close\n      </Button>\n    </div>\n  );\n\n  return (\n    <Dialog open={open} onOpenChange={handleOpenChange}>\n      <DialogTrigger asChild>\n        {trigger || (\n          <Button size=\"lg\" className=\"w-full lg:w-auto text-base\">\n            <Plus className=\"mr-2 h-5 w-5\" />\n            Submit Resource\n            <ArrowRight className=\"ml-2 h-5 w-5\" />\n          </Button>\n        )}\n      </DialogTrigger>\n      <DialogContent className=\"sm:max-w-[425px]\">\n        <DialogHeader>\n          <DialogTitle>\n            {step === \"form\" && \"Submit Resource\"}\n            {step === \"success\" && \"Success!\"}\n          </DialogTitle>\n          <DialogDescription>\n            {step === \"form\" &&\n              \"Fill in your resource details. This will create a fork and PR automatically.\"}\n            {step === \"success\" &&\n              \"Your PR has been created and will be reviewed by maintainers.\"}\n          </DialogDescription>\n        </DialogHeader>\n\n        {step === \"form\" && renderFormStep()}\n        {step === \"success\" && renderSuccessStep()}\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "src/components/search-filter-controls.tsx",
    "content": "import Sort, { SortOption } from \"@/components/sort\";\nimport { Input } from \"@/components/ui/input\";\nimport { MultiSelect } from \"@/components/ui/multi-select\";\nimport { Button } from \"@/components/ui/button\";\nimport { PRSubmissionDialog } from \"@/components/pr-submission-dialog\";\nimport { motion } from \"motion/react\";\nimport type React from \"react\";\nimport { useCallback } from \"react\";\nimport { Github } from \"lucide-react\";\n\ninterface SearchFilterControlsProps {\n  searchQuery: string;\n  setSearchQuery: (query: string) => void;\n  categoryOptions: { label: string; value: string }[];\n  selectedCategories: string[];\n  setSelectedCategories: (categories: string[]) => void;\n  sortOption: SortOption;\n  onSortChange: (option: SortOption) => void;\n}\n\nexport function SearchFilterControls({\n  searchQuery,\n  setSearchQuery,\n  categoryOptions,\n  selectedCategories,\n  setSelectedCategories,\n  sortOption,\n  onSortChange,\n}: SearchFilterControlsProps) {\n  const handleSearchChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      setSearchQuery(e.target.value);\n    },\n    [setSearchQuery],\n  );\n\n  return (\n    <motion.div\n      className=\"flex flex-col gap-3 w-full sm:flex-row sm:items-center sm:justify-between sm:gap-4\"\n      variants={{\n        hidden: { opacity: 0 },\n        visible: {\n          opacity: 1,\n          transition: {\n            delay: 0.2,\n          },\n        },\n      }}\n    >\n      <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-2\">\n        <Input\n          type=\"text\"\n          placeholder=\"Search items...\"\n          value={searchQuery}\n          onChange={handleSearchChange}\n          className=\"w-full sm:w-[200px]\"\n        />\n        <div className=\"flex items-center gap-2 w-full sm:w-auto\">\n          <MultiSelect\n            options={categoryOptions}\n            value={selectedCategories}\n            onValueChange={setSelectedCategories}\n            placeholder=\"Filter by category\"\n            className=\"flex-1 sm:flex-none sm:w-[200px]\"\n          />\n          <Sort\n            sortOption={sortOption}\n            onSortChange={onSortChange}\n            className=\"flex-1 sm:flex-none\"\n          />\n        </div>\n      </div>\n      <PRSubmissionDialog\n        trigger={\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            className=\"w-full sm:w-auto h-9 whitespace-nowrap\"\n          >\n            <Github className=\"mr-1.5 h-3.5 w-3.5\" />\n            Submit new resource\n          </Button>\n        }\n      />\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/components/sections/cta-submit.tsx",
    "content": "\"use client\";\n\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { motion, useInView } from \"motion/react\";\nimport { ArrowRight, Github } from \"lucide-react\";\n\nimport { PRSubmissionDialog } from \"@/components/pr-submission-dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { useRef } from \"react\";\n\nexport function SubmitCTA() {\n  const ref = useRef(null);\n  const isInView = useInView(ref, { once: true, amount: 0.5 });\n\n  const containerVariants = {\n    hidden: { opacity: 0, y: 10 },\n    visible: {\n      opacity: 1,\n      y: 0,\n      transition: {\n        duration: 0.3,\n        staggerChildren: 0.1,\n      },\n    },\n  };\n\n  const itemVariants = {\n    hidden: { opacity: 0, y: 10 },\n    visible: { opacity: 1, y: 0 },\n  };\n\n  return (\n    <Card className=\"w-full overflow-hidden my-16\">\n      <CardContent className=\"p-6 sm:p-8 md:p-10\">\n        <motion.div\n          ref={ref}\n          variants={containerVariants}\n          initial=\"hidden\"\n          animate={isInView ? \"visible\" : \"hidden\"}\n          className=\"flex flex-col lg:flex-row items-center justify-between gap-6\"\n        >\n          <motion.div\n            variants={itemVariants}\n            className=\"text-center lg:text-left space-y-2 lg:space-y-4 max-w-2xl\"\n          >\n            <motion.h2\n              variants={itemVariants}\n              className=\"text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight\"\n            >\n              Contribute to awesome-shadcn/ui\n            </motion.h2>\n            <motion.p\n              variants={itemVariants}\n              className=\"text-sm sm:text-base text-muted-foreground max-w-prose\"\n            >\n              Have an awesome shadcn/ui related project or resource? Share it\n              with the community! Open a PR and help grow this curated list.\n            </motion.p>\n          </motion.div>\n          <motion.div variants={itemVariants} className=\"w-full lg:w-auto\">\n            <div className=\"flex flex-col sm:flex-row gap-2 w-full lg:w-auto\">\n              <PRSubmissionDialog\n                trigger={\n                  <Button size=\"lg\" className=\"w-full sm:w-auto text-base\">\n                    <Github className=\"mr-2 h-5 w-5\" />\n                    <span>Submit Resource</span>\n                    <ArrowRight className=\"ml-2 h-5 w-5\" />\n                  </Button>\n                }\n              />\n              <Button\n                asChild\n                variant=\"outline\"\n                size=\"lg\"\n                className=\"w-full sm:w-auto text-base\"\n              >\n                <a\n                  href=\"https://github.com/birobirobiro/awesome-shadcn-ui/blob/main/.github/pull_request_template.md\"\n                  target=\"_blank\"\n                  rel=\"noopener noreferrer\"\n                  className=\"flex items-center justify-center\"\n                >\n                  <Github className=\"mr-2 h-5 w-5\" />\n                  <span>Manual PR</span>\n                  <ArrowRight className=\"ml-2 h-5 w-5\" />\n                </a>\n              </Button>\n            </div>\n          </motion.div>\n        </motion.div>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "src/components/sections/hero.tsx",
    "content": "\"use client\";\n\nimport { HeroLogo } from \"@/components/logo\";\nimport { Sponsorship } from \"./sponsorship\";\nimport { motion } from \"motion/react\";\n\nconst fadeIn = {\n  hidden: { opacity: 0, y: 8 },\n  visible: {\n    opacity: 1,\n    y: 0,\n    transition: {\n      duration: 0.4,\n      ease: [0.22, 1, 0.36, 1],\n    },\n  },\n};\n\nconst containerVariants = {\n  hidden: { opacity: 0 },\n  visible: {\n    opacity: 1,\n    transition: {\n      staggerChildren: 0.06,\n      delayChildren: 0.1,\n    },\n  },\n};\n\nexport default function Hero() {\n  return (\n    <motion.div\n      className=\"flex flex-col items-center text-center max-w-3xl mx-auto pt-8 sm:pt-12 pb-8 px-4\"\n      variants={containerVariants}\n      initial=\"hidden\"\n      animate=\"visible\"\n    >\n      {/* Logo */}\n      <motion.div variants={fadeIn} className=\"mb-4\">\n        <HeroLogo />\n      </motion.div>\n\n      {/* Title */}\n      <motion.h1\n        variants={fadeIn}\n        className=\"text-4xl sm:text-5xl md:text-6xl font-bold tracking-tight mb-4\"\n      >\n        awesome-shadcn/ui\n      </motion.h1>\n\n      {/* Description */}\n      <motion.p\n        variants={fadeIn}\n        className=\"text-lg sm:text-xl text-muted-foreground max-w-2xl mb-8\"\n      >\n        A curated list of awesome things related to{\" \"}\n        <a\n          href=\"https://ui.shadcn.com/\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"text-foreground hover:underline underline-offset-4\"\n        >\n          shadcn/ui\n        </a>\n      </motion.p>\n\n      {/* Credits */}\n      <motion.div\n        variants={fadeIn}\n        className=\"flex flex-col sm:flex-row items-center gap-2 text-sm text-muted-foreground\"\n      >\n        <span>\n          Created by{\" \"}\n          <a\n            href=\"https://birobirobiro.dev/\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"text-foreground hover:underline underline-offset-4\"\n          >\n            birobirobiro.dev\n          </a>\n        </span>\n        <span className=\"hidden sm:inline\">·</span>\n        <span>\n          Site by{\" \"}\n          <a\n            href=\"https://bankkroll.xyz\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"text-foreground hover:underline underline-offset-4\"\n          >\n            bankkroll.xyz\n          </a>\n        </span>\n      </motion.div>\n\n      {/* Sponsorship */}\n      <motion.div variants={fadeIn} className=\"mt-4\">\n        <Sponsorship />\n      </motion.div>\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/components/sections/items-list.tsx",
    "content": "\"use client\";\n\nimport { isValid, parseISO } from \"date-fns\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { Card, CardContent, CardFooter, CardHeader } from \"../ui/card\";\n\nimport { SortOption } from \"@/components/sort\";\nimport { useBookmarks } from \"@/hooks/use-bookmark\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { Resource } from \"@/hooks/use-readme\";\nimport { ItemGrid } from \"../item-grid\";\nimport { PaginationControls } from \"../pagination-controls\";\nimport { SearchFilterControls } from \"../search-filter-controls\";\nimport { Skeleton } from \"../ui/skeleton\";\n\nconst ITEMS_PER_PAGE_OPTIONS = [20, 40, 60, 80];\n\ninterface Category {\n  title: string;\n  items: Resource[];\n}\n\ninterface ItemListProps {\n  items: Resource[];\n  categories: Category[];\n}\n\nexport default function ItemList({\n  items: initialItems,\n  categories,\n}: ItemListProps) {\n  const [filteredItems, setFilteredItems] = useState<Resource[]>(initialItems);\n  const [currentPage, setCurrentPage] = useState(1);\n  const [itemsPerPage, setItemsPerPage] = useState(ITEMS_PER_PAGE_OPTIONS[1]);\n  const [searchQuery, setSearchQuery] = useState(\"\");\n  const [selectedCategories, setSelectedCategories] = useState<string[]>([]);\n  const [sortOption, setSortOption] = useState<SortOption>(\"date-desc\");\n  const [isLoading, setIsLoading] = useState(true);\n\n  const debouncedSearchQuery = useDebounce(searchQuery, 300);\n  const {\n    bookmarkedItems,\n    toggleBookmark,\n    isLoading: isBookmarkLoading,\n  } = useBookmarks();\n\n  const categoryOptions = useMemo(\n    () =>\n      categories.map((category) => ({\n        label: category.title,\n        value: category.title,\n      })),\n    [categories],\n  );\n\n  const sortItems = useCallback(\n    (items: Resource[]): Resource[] => {\n      return [...items].sort((a, b) => {\n        const aBookmarked = bookmarkedItems.includes(a.id);\n        const bBookmarked = bookmarkedItems.includes(b.id);\n        if (aBookmarked !== bBookmarked) return aBookmarked ? -1 : 1;\n\n        const [field, direction] = sortOption.split(\"-\") as [\n          \"date\" | \"name\",\n          \"asc\" | \"desc\",\n        ];\n\n        if (field === \"name\") {\n          const nameA = a.name?.toLowerCase() || \"\";\n          const nameB = b.name?.toLowerCase() || \"\";\n          const result = nameA.localeCompare(nameB);\n          return direction === \"asc\" ? result : -result;\n        } else {\n          const dateA =\n            a.date && a.date !== \"Unknown\" ? parseISO(a.date) : new Date(0);\n          const dateB =\n            b.date && b.date !== \"Unknown\" ? parseISO(b.date) : new Date(0);\n\n          if (!isValid(dateA) && !isValid(dateB)) return 0;\n          if (!isValid(dateA)) return direction === \"asc\" ? -1 : 1;\n          if (!isValid(dateB)) return direction === \"asc\" ? 1 : -1;\n\n          return direction === \"asc\"\n            ? dateA.getTime() - dateB.getTime()\n            : dateB.getTime() - dateA.getTime();\n        }\n      });\n    },\n    [bookmarkedItems, sortOption],\n  );\n\n  const filterAndSortItems = useCallback(() => {\n    let filtered = [...initialItems];\n\n    if (debouncedSearchQuery) {\n      const lowercaseQuery = debouncedSearchQuery.toLowerCase();\n      filtered = filtered.filter(\n        (item) =>\n          (item.name?.toLowerCase() || \"\").includes(lowercaseQuery) ||\n          (item.description?.toLowerCase() || \"\").includes(lowercaseQuery),\n      );\n    }\n\n    if (selectedCategories.length > 0) {\n      filtered = filtered.filter((item) =>\n        selectedCategories.includes(item.category),\n      );\n    }\n\n    const sortedItems = sortItems(filtered);\n\n    setFilteredItems(sortedItems);\n    setCurrentPage(1);\n  }, [initialItems, debouncedSearchQuery, selectedCategories, sortItems]);\n\n  // Update filteredItems when initialItems change\n  useEffect(() => {\n    setFilteredItems(initialItems);\n    setCurrentPage(1);\n  }, [initialItems]);\n\n  useEffect(() => {\n    filterAndSortItems();\n  }, [filterAndSortItems]);\n\n  useEffect(() => {\n    const timer = setTimeout(() => {\n      setIsLoading(false);\n      filterAndSortItems();\n    }, 100);\n    return () => clearTimeout(timer);\n  }, []);\n\n  // Set loading to false when initialItems are available\n  useEffect(() => {\n    if (initialItems.length > 0) {\n      setIsLoading(false);\n    }\n  }, [initialItems]);\n\n  const totalPages = Math.ceil(filteredItems.length / itemsPerPage);\n  const indexOfLastItem = currentPage * itemsPerPage;\n  const indexOfFirstItem = indexOfLastItem - itemsPerPage;\n  const currentItems = filteredItems.slice(indexOfFirstItem, indexOfLastItem);\n\n  const handlePageChange = useCallback((pageNumber: number) => {\n    setCurrentPage(pageNumber);\n  }, []);\n\n  const handleItemsPerPageChange = useCallback((value: string) => {\n    setItemsPerPage(Number(value));\n    setCurrentPage(1);\n  }, []);\n\n  const handleSortChange = useCallback(\n    (option: SortOption) => {\n      setSortOption(option);\n\n      const sorted = sortItems(\n        filteredItems.filter((item) => {\n          if (\n            selectedCategories.length > 0 &&\n            !selectedCategories.includes(item.category)\n          ) {\n            return false;\n          }\n\n          if (debouncedSearchQuery) {\n            const lowercaseQuery = debouncedSearchQuery.toLowerCase();\n            return (\n              (item.name?.toLowerCase() || \"\").includes(lowercaseQuery) ||\n              (item.description?.toLowerCase() || \"\").includes(lowercaseQuery)\n            );\n          }\n\n          return true;\n        }),\n      );\n\n      setFilteredItems(sorted);\n    },\n    [filteredItems, sortItems, selectedCategories, debouncedSearchQuery],\n  );\n\n  // Get grid column classes\n  const getGridClasses = useCallback(() => {\n    return \"sm:grid-cols-2 lg:grid-cols-3\";\n  }, []);\n\n  // Get item card height\n  const getCardHeightClass = useCallback(() => {\n    return \"min-h-[250px]\";\n  }, []);\n\n  const containerVariants = {\n    hidden: { opacity: 0 },\n    visible: {\n      opacity: 1,\n      transition: {\n        staggerChildren: 0.2,\n      },\n    },\n  };\n\n  const itemVariants = {\n    hidden: { opacity: 0, y: 20 },\n    visible: { opacity: 1, y: 0 },\n  };\n\n  return (\n    <motion.div\n      className=\"space-y-6\"\n      variants={containerVariants}\n      initial=\"hidden\"\n      animate=\"visible\"\n    >\n      <motion.div variants={itemVariants}>\n        <SearchFilterControls\n          searchQuery={searchQuery}\n          setSearchQuery={setSearchQuery}\n          categoryOptions={categoryOptions}\n          selectedCategories={selectedCategories}\n          setSelectedCategories={setSelectedCategories}\n          sortOption={sortOption}\n          onSortChange={handleSortChange}\n        />\n      </motion.div>\n\n      <motion.div variants={itemVariants}>\n        <AnimatePresence mode=\"wait\">\n          <motion.div\n            key={`${isLoading ? \"loading\" : \"loaded\"}`}\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{ duration: 0.3 }}\n            layout\n          >\n            {isLoading ? (\n              <div className={`grid gap-6 ${getGridClasses()}`}>\n                {[...Array(itemsPerPage)].map((_, index) => (\n                  <Card\n                    key={index}\n                    className={`flex flex-col h-full ${getCardHeightClass()} overflow-hidden`}\n                  >\n                    <CardHeader className=\"pb-4\">\n                      <div className=\"flex justify-between items-start\">\n                        <Skeleton className=\"h-6 w-3/4\" />\n                        <Skeleton className=\"h-5 w-16\" />\n                      </div>\n                    </CardHeader>\n                    <CardContent className=\"flex-grow\">\n                      <Skeleton className=\"h-4 w-full mb-2\" />\n                      <Skeleton className=\"h-4 w-5/6\" />\n                    </CardContent>\n                    <CardFooter className=\"pt-4\">\n                      <Skeleton className=\"h-9 w-full\" />\n                    </CardFooter>\n                  </Card>\n                ))}\n              </div>\n            ) : (\n              <ItemGrid\n                items={currentItems}\n                bookmarkedItems={bookmarkedItems}\n                onBookmark={toggleBookmark}\n                isBookmarkLoading={isBookmarkLoading}\n              />\n            )}\n          </motion.div>\n        </AnimatePresence>\n      </motion.div>\n\n      {filteredItems.length > 0 && (\n        <motion.div variants={itemVariants}>\n          <PaginationControls\n            currentPage={currentPage}\n            totalPages={totalPages}\n            itemsPerPage={itemsPerPage}\n            handlePageChange={handlePageChange}\n            handleItemsPerPageChange={handleItemsPerPageChange}\n            itemsPerPageOptions={ITEMS_PER_PAGE_OPTIONS}\n          />\n        </motion.div>\n      )}\n\n      <motion.div\n        variants={itemVariants}\n        className=\"text-sm text-muted-foreground text-center\"\n      >\n        Showing {indexOfFirstItem + 1} -{\" \"}\n        {Math.min(indexOfLastItem, filteredItems.length)} of{\" \"}\n        {filteredItems.length} items\n      </motion.div>\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/components/sections/sponsorship.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Marquee,\n  MarqueeContent,\n  MarqueeEdge,\n  MarqueeItem,\n} from \"@/components/ui/marquee\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { motion } from \"motion/react\";\nimport { Plus } from \"lucide-react\";\nimport { sponsors } from \"@/components/sponsors/sponsors\";\n\nexport function Sponsorship() {\n  return (\n    <motion.div\n      initial={{ opacity: 0, y: 10 }}\n      animate={{ opacity: 1, y: 0 }}\n      transition={{ duration: 0.2 }}\n    >\n      <span className=\"text-[10px] text-muted-foreground uppercase tracking-widest font-mono block text-center mb-2\">\n        Sponsored by\n      </span>\n      <div className=\"flex items-center gap-2\">\n        <Marquee\n          autoFill\n          pauseOnHover\n          speed={15}\n          gap=\"0.5rem\"\n          className=\"flex-1\"\n        >\n          <MarqueeContent>\n            {sponsors.map((sponsor) => (\n              <MarqueeItem key={sponsor.name}>\n                <a\n                  href={sponsor.url}\n                  target=\"_blank\"\n                  rel=\"noopener sponsored\"\n                  className=\"flex items-center gap-2 bg-muted/30 px-3 py-1.5 transition-colors duration-200 hover:bg-muted/50 border border-border/50 max-sm:px-2 max-sm:py-2\"\n                >\n                  {sponsor.LogoComponent}\n                  <div className=\"flex flex-col items-start max-sm:hidden\">\n                    <span className=\"text-sm font-medium text-foreground leading-tight\">\n                      {sponsor.name}\n                    </span>\n                    <span className=\"text-xs text-muted-foreground leading-tight\">\n                      {sponsor.description}\n                    </span>\n                  </div>\n                </a>\n              </MarqueeItem>\n            ))}\n          </MarqueeContent>\n          <MarqueeEdge side=\"left\" size=\"sm\" />\n          <MarqueeEdge side=\"right\" size=\"sm\" />\n        </Marquee>\n\n        <Tooltip>\n          <TooltipTrigger asChild>\n            <Button variant=\"outline\" size=\"sm\" className=\"h-8 shrink-0\" asChild>\n              <a\n                href=\"https://buy.stripe.com/9B6cN6eZq6N136ygPm0Jq02\"\n                target=\"_blank\"\n                rel=\"noopener\"\n              >\n                <Plus className=\"w-5 h-5\" />\n              </a>\n            </Button>\n          </TooltipTrigger>\n          <TooltipContent>\n            <p>Become a sponsor</p>\n          </TooltipContent>\n        </Tooltip>\n      </div>\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "src/components/sort.tsx",
    "content": "import {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\nimport { Calendar, SortAsc, SortDesc, Text } from \"lucide-react\";\n\nexport type SortOption = \"date-desc\" | \"date-asc\" | \"name-asc\" | \"name-desc\";\n\ninterface SortProps {\n  sortOption: SortOption;\n  onSortChange: (option: SortOption) => void;\n  className?: string;\n}\n\nexport default function Sort({ sortOption, onSortChange, className }: SortProps) {\n  const sortOptions = [\n    {\n      value: \"date-desc\",\n      label: \"Date (Newest first)\",\n      icon: <Calendar className=\"h-4 w-4 mr-2\" />,\n      directionIcon: <SortDesc className=\"h-4 w-4 ml-2 inline\" />,\n    },\n    {\n      value: \"date-asc\",\n      label: \"Date (Oldest first)\",\n      icon: <Calendar className=\"h-4 w-4 mr-2\" />,\n      directionIcon: <SortAsc className=\"h-4 w-4 ml-2 inline\" />,\n    },\n    {\n      value: \"name-asc\",\n      label: \"Name (A-Z)\",\n      icon: <Text className=\"h-4 w-4 mr-2\" />,\n      directionIcon: <SortDesc className=\"h-4 w-4 ml-2 inline\" />,\n    },\n    {\n      value: \"name-desc\",\n      label: \"Name (Z-A)\",\n      icon: <Text className=\"h-4 w-4 mr-2\" />,\n      directionIcon: <SortAsc className=\"h-4 w-4 ml-2 inline\" />,\n    },\n  ] as const;\n\n  const selectedOption =\n    sortOptions.find((option) => option.value === sortOption) || sortOptions[0];\n\n  const handleSortChange = (value: string) => {\n    onSortChange(value as SortOption);\n  };\n\n  return (\n    <Select\n      value={sortOption}\n      defaultValue=\"date-desc\"\n      onValueChange={handleSortChange}\n    >\n      <SelectTrigger className={cn(\"w-full sm:w-[200px]\", className)}>\n        <SelectValue>\n          <span className=\"flex items-center\">\n            {selectedOption.icon}\n            <span className=\"truncate\">{selectedOption.label}</span>\n            {selectedOption.directionIcon}\n          </span>\n        </SelectValue>\n      </SelectTrigger>\n      <SelectContent align=\"center\">\n        {sortOptions.map((option) => (\n          <SelectItem key={option.value} value={option.value}>\n            <span className=\"flex items-center\">\n              {option.icon}\n              <span className=\"truncate\">{option.label}</span>\n              {option.directionIcon}\n            </span>\n          </SelectItem>\n        ))}\n      </SelectContent>\n    </Select>\n  );\n}\n"
  },
  {
    "path": "src/components/sponsor-card.tsx",
    "content": "import {\n  Card,\n  CardContent,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { ArrowRight, ExternalLink } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Sponsor } from \"@/components/sponsors/sponsors\";\nimport { motion } from \"motion/react\";\nimport React from \"react\";\n\nconst standardAnimations = {\n  initial: { opacity: 0, y: 20 },\n  animate: { opacity: 1, y: 0 },\n  exit: { opacity: 0, y: -20 },\n  transition: { duration: 0.2, ease: \"easeOut\" },\n};\n\ninterface SponsorCardProps {\n  sponsor: Sponsor;\n}\n\nconst SponsorCard: React.FC<SponsorCardProps> = ({ sponsor }) => {\n  const handleCardClick = () => {\n    window.open(sponsor.url, \"_blank\", \"noopener,noreferrer\");\n  };\n\n  return (\n    <motion.div layout {...standardAnimations} className=\"h-full\">\n      <Card\n        className=\"h-full group hover:border-primary/40 hover:shadow-lg transition-all duration-200 overflow-hidden cursor-pointer flex flex-col gap-0 py-0\"\n        onClick={handleCardClick}\n      >\n        <CardHeader className=\"p-4 pb-3 shrink-0\">\n          <div className=\"flex items-start justify-between gap-2 mb-2\">\n            <span className=\"text-[10px] font-mono uppercase tracking-wider text-muted-foreground/50 border border-border/40 bg-muted/20 px-2 py-0.5\">\n              [sponsored]\n            </span>\n            <ArrowRight className=\"h-4 w-4 text-muted-foreground group-hover:text-primary group-hover:translate-x-0.5 transition-all flex-shrink-0\" />\n          </div>\n          <CardTitle className=\"text-base font-bold group-hover:text-primary transition-colors duration-200 line-clamp-2 leading-snug\">\n            {sponsor.name}\n          </CardTitle>\n        </CardHeader>\n\n        <div className=\"h-px bg-border mx-4 shrink-0\" />\n\n        <CardContent className=\"px-4 py-3 flex-1 flex flex-col gap-3 min-h-0\">\n          <p className=\"text-sm text-muted-foreground line-clamp-3 flex-1 leading-relaxed overflow-hidden\">\n            {sponsor.description}\n          </p>\n        </CardContent>\n\n        <div className=\"h-px bg-border mx-4 shrink-0\" />\n\n        <CardFooter className=\"flex gap-3 items-center p-4 pt-3 shrink-0\">\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            asChild\n            className=\"h-8 w-8 text-muted-foreground hover:text-foreground hover:bg-muted transition-all duration-200\"\n          >\n            <a\n              href={sponsor.url}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              onClick={(e) => e.stopPropagation()}\n            >\n              <ExternalLink className=\"h-3.5 w-3.5\" />\n            </a>\n          </Button>\n        </CardFooter>\n      </Card>\n    </motion.div>\n  );\n};\n\nexport default SponsorCard;\n"
  },
  {
    "path": "src/components/sponsors/shadcn-blocks-logo.tsx",
    "content": "import * as React from \"react\";\n\ninterface ShadcnBlocksLogoProps {\n  className?: string;\n}\n\nfunction ShadcnBlocksLogo({ className = \"w-5 h-5\" }: ShadcnBlocksLogoProps) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={78}\n      height={90}\n      viewBox=\"0 0 78 90\"\n      fill=\"none\"\n      className={className}\n    >\n      <path\n        d=\"M46.73 4.51l-3.105-1.78v14.76l3.105 1.803V4.51zM52.985 8.148L49.877 6.35v14.78l3.108 1.784V8.148zM59.181 11.768l-3.105-1.78v14.76l3.105 1.785V11.768zM6.047 26.018l3.105 1.784V17.246l-3.105 1.78v6.992zM2.939 24.218v-3.353L0 22.55l2.939 1.67z\"\n        fill=\"#000\"\n        className=\"dark:fill-white\"\n      />\n      <path\n        d=\"M77.889 22.59l-3.09-1.784v3.582l-3.11 1.78v-7.143l-3.086-1.78v10.667l-3.11 1.784V15.387l-3.164-1.839v14.783l2.787 1.628v29.905l-.151.092-2.636-1.514v3.05l-3.146 1.78v-6.725l-3.105-1.84v10.367l-3.09 1.836V52.868l-3.11-1.836v17.663l-3.145 1.78V49.193l-3.106-1.84v24.902l-3.109 1.858V45.517l-1.517-1.007.059-29.694 1.458.892V.948L38.89 0l-1.31.737v14.82l-3.143 1.78V2.575l-3.109 1.78V19.12l-3.109 1.799V6.195l-3.087 1.78V22.7l-3.164 1.784V9.778l-3.09 1.835v14.668l-3.11 1.84V13.392L12.3 15.44v14.138l.474.266.115 30.109 2.88 1.688v-3.354l3.109-1.84v7.033l3.09 1.799v-10.67l3.165-1.896V67.06l3.086 1.84V50.877l3.11-1.84V70.68l3.108 1.802V47.18l3.143-1.836v28.973l1.42.836v-.059V89.97l38.945-22.492L78 22.585l-.111.005z\"\n        fill=\"#000\"\n        className=\"dark:fill-white\"\n      />\n    </svg>\n  );\n}\n\nexport default ShadcnBlocksLogo;\n"
  },
  {
    "path": "src/components/sponsors/shadcn-studio-logo.tsx",
    "content": "import * as React from \"react\";\n\ninterface ShadcnStudioLogoProps {\n  className?: string;\n}\n\nfunction ShadcnStudioLogo({ className = \"w-5 h-5\" }: ShadcnStudioLogoProps) {\n  return (\n    <svg\n      viewBox=\"0 0 328 328\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n    >\n      <rect width=\"328\" height=\"328\" rx=\"164\" className=\"fill-foreground\" />\n      <path\n        d=\"M165.018 72.3008V132.771C165.018 152.653 148.9 168.771 129.018 168.771H70.2288\"\n        strokeWidth=\"20\"\n        className=\"stroke-background\"\n      />\n      <path\n        d=\"M166.627 265.241L166.627 204.771C166.627 184.889 182.744 168.771 202.627 168.771L261.416 168.771\"\n        strokeWidth=\"20\"\n        className=\"stroke-background\"\n      />\n      <line\n        x1=\"238.136\"\n        y1=\"98.8184\"\n        x2=\"196.76\"\n        y2=\"139.707\"\n        strokeWidth=\"20\"\n        className=\"stroke-background\"\n      />\n      <line\n        x1=\"135.688\"\n        y1=\"200.957\"\n        x2=\"94.3128\"\n        y2=\"241.845\"\n        stroke=\"white\"\n        strokeWidth=\"20\"\n        className=\"dark:stroke-black\"\n      />\n      <line\n        x1=\"133.689\"\n        y1=\"137.524\"\n        x2=\"92.5566\"\n        y2=\"96.3914\"\n        stroke=\"white\"\n        strokeWidth=\"20\"\n        className=\"dark:stroke-black\"\n      />\n      <line\n        x1=\"237.679\"\n        y1=\"241.803\"\n        x2=\"196.547\"\n        y2=\"200.671\"\n        stroke=\"white\"\n        strokeWidth=\"20\"\n        className=\"dark:stroke-black\"\n      />\n    </svg>\n  );\n}\n\nexport default ShadcnStudioLogo;\n"
  },
  {
    "path": "src/components/sponsors/shadcn-ui-kit-logo.tsx",
    "content": "import * as React from \"react\";\nimport Image from \"next/image\";\n\ninterface ShadcnUiKitLogoProps {\n  className?: string;\n}\n\nfunction ShadcnUiKitLogo({ className = \"w-5 h-5\" }: ShadcnUiKitLogoProps) {\n  return (\n    <Image\n      src=\"/sponsors/shadcnuikit.svg\"\n      alt=\"shadcnuikit.com logo\"\n      width={20}\n      height={20}\n      className={className}\n    />\n  );\n}\n\nexport default ShadcnUiKitLogo;\n"
  },
  {
    "path": "src/components/sponsors/sponsors.tsx",
    "content": "import { ReactNode } from \"react\";\nimport ShadcnStudioLogo from \"./shadcn-studio-logo\";\nimport ShadcnBlocksLogo from \"./shadcn-blocks-logo\";\nimport ShadcnUiKitLogo from \"./shadcn-ui-kit-logo\";\n\nexport interface Sponsor {\n  name: string;\n  description: string;\n  url: string;\n  LogoComponent: ReactNode;\n}\n\nexport const sponsors: Sponsor[] = [\n  {\n    name: \"shadcnstudio.com\",\n    description: \"shadcn blocks & templates\",\n    url: \"https://shadcnstudio.com/?utm_source=awesome-shadcn-ui&utm_medium=banner&utm_campaign=github\",\n    LogoComponent: <ShadcnStudioLogo className=\"w-5 h-5 shrink-0\" />,\n  },\n  {\n    name: \"shadcnblocks.com\",\n    description: \"UI blocks for your next project\",\n    url: \"https://www.shadcnblocks.com/?utm_source=awesome-shadcn-ui&utm_medium=banner&utm_campaign=github\",\n    LogoComponent: <ShadcnBlocksLogo className=\"w-5 h-5 shrink-0\" />,\n  },\n  {\n    name: \"shadcnuikit.com\",\n    description: \"Build faster with pre-built assets\",\n    url: \"https://shadcnuikit.com/?utm_source=awesome-shadcn-ui&utm_medium=banner&utm_campaign=github\",\n    LogoComponent: <ShadcnUiKitLogo className=\"w-5 h-5 shrink-0\" />,\n  },\n];\n"
  },
  {
    "path": "src/components/theme-toggle.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Moon, Sun } from \"lucide-react\";\nimport { useTheme } from \"next-themes\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\nexport function ModeToggle() {\n  const { setTheme } = useTheme();\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button variant=\"outline\" size=\"icon\" className=\"h-[34px] w-9\">\n          <Sun className=\"h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90\" />\n          <Moon className=\"absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0\" />\n          <span className=\"sr-only\">Toggle theme</span>\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuItem onClick={() => setTheme(\"light\")}>\n          Light\n        </DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"dark\")}>\n          Dark\n        </DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"system\")}>\n          System\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "src/components/ui/accordion.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Accordion as AccordionPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChevronDownIcon, ChevronUpIcon } from \"lucide-react\"\n\nfunction Accordion({\n  className,\n  ...props\n}: React.ComponentProps<typeof AccordionPrimitive.Root>) {\n  return (\n    <AccordionPrimitive.Root\n      data-slot=\"accordion\"\n      className={cn(\"flex w-full flex-col\", className)}\n      {...props}\n    />\n  )\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(\"not-last:border-b\", 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          \"focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground group/accordion-trigger relative flex flex-1 items-start justify-between rounded-none border border-transparent py-2.5 text-left text-xs font-medium transition-all outline-none hover:underline focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <ChevronDownIcon data-slot=\"accordion-trigger-icon\" className=\"pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden\" />\n        <ChevronUpIcon data-slot=\"accordion-trigger-icon\" className=\"pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline\" />\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=\"data-open:animate-accordion-down data-closed:animate-accordion-up overflow-hidden text-xs\"\n      {...props}\n    >\n      <div\n        className={cn(\n          \"[&_a]:hover:text-foreground h-(--radix-accordion-content-height) pt-0 pb-2.5 [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4\",\n          className\n        )}\n      >\n        {children}\n      </div>\n    </AccordionPrimitive.Content>\n  )\n}\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent }\n"
  },
  {
    "path": "src/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        \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs\",\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          \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-none p-4 ring-1 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm\",\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-4 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 AlertDialogMedia({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"alert-dialog-media\"\n      className={cn(\n        \"bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-none sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6\",\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-sm font-medium 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(\n        \"text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3\",\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": "src/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  \"grid gap-0.5 rounded-none border px-2.5 py-2 text-left text-xs has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-card text-card-foreground\",\n        destructive:\n          \"text-destructive bg-card *: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        \"[&_a]:hover:text-foreground font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3\",\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        \"text-muted-foreground [&_a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction AlertAction({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"alert-action\"\n      className={cn(\n        \"absolute top-[calc(--spacing(1.25))] right-[calc(--spacing(1.25))]\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Alert, AlertTitle, AlertDescription, AlertAction }\n"
  },
  {
    "path": "src/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": "src/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        \"after:border-border group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten\",\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(\n        \"aspect-square size-full rounded-full object-cover\",\n        className\n      )}\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        \"bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm 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        \"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 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        \"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2\",\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        \"bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-xs ring-2 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  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n}\n"
  },
  {
    "path": "src/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  \"h-5 gap-1 rounded-none border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground [a]:hover:bg-primary/80\",\n        secondary:\n          \"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80\",\n        destructive:\n          \"bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20\",\n        outline:\n          \"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50\",\n        link: \"text-primary underline-offset-4 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": "src/components/ui/breadcrumb.tsx",
    "content": "import * as React from \"react\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChevronRightIcon, MoreHorizontalIcon } from \"lucide-react\"\n\nfunction Breadcrumb({ className, ...props }: React.ComponentProps<\"nav\">) {\n  return (\n    <nav\n      aria-label=\"breadcrumb\"\n      data-slot=\"breadcrumb\"\n      className={cn(className)}\n      {...props}\n    />\n  )\n}\n\nfunction BreadcrumbList({ className, ...props }: React.ComponentProps<\"ol\">) {\n  return (\n    <ol\n      data-slot=\"breadcrumb-list\"\n      className={cn(\n        \"text-muted-foreground flex flex-wrap items-center gap-1.5 text-xs wrap-break-word\",\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\", 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(\"hover:text-foreground transition-colors\", 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(\"text-foreground font-normal\", 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 ?? (\n        <ChevronRightIcon />\n      )}\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(\n        \"flex size-5 items-center justify-center [&>svg]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <MoreHorizontalIcon\n      />\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": "src/components/ui/button-group.tsx",
    "content": "import { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Separator } from \"@/components/ui/separator\"\n\nconst buttonGroupVariants = cva(\n  \"rounded-none has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-none flex w-fit items-stretch *:focus-visible:z-10 *:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1\",\n  {\n    variants: {\n      orientation: {\n        horizontal:\n          \"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none\",\n        vertical:\n          \"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none\",\n      },\n    },\n    defaultVariants: {\n      orientation: \"horizontal\",\n    },\n  }\n)\n\nfunction ButtonGroup({\n  className,\n  orientation,\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof buttonGroupVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"button-group\"\n      data-orientation={orientation}\n      className={cn(buttonGroupVariants({ orientation }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction ButtonGroupText({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  asChild?: boolean\n}) {\n  const Comp = asChild ? Slot.Root : \"div\"\n\n  return (\n    <Comp\n      className={cn(\n        \"bg-muted flex items-center gap-2 rounded-none border px-2.5 text-xs font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ButtonGroupSeparator({\n  className,\n  orientation = \"vertical\",\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"button-group-separator\"\n      orientation={orientation}\n      className={cn(\n        \"bg-input relative self-stretch data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  ButtonGroup,\n  ButtonGroupSeparator,\n  ButtonGroupText,\n  buttonGroupVariants,\n}\n"
  },
  {
    "path": "src/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  \"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-none border border-transparent bg-clip-padding text-xs font-medium focus-visible:ring-1 aria-invalid:ring-1 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground [a]:hover:bg-primary/80\",\n        outline:\n          \"border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground\",\n        destructive:\n          \"bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default:\n          \"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        xs: \"h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: \"h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3\",\n        icon: \"size-8\",\n        \"icon-xs\": \"size-6 rounded-none [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\": \"size-7 rounded-none\",\n        \"icon-lg\": \"size-9\",\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": "src/components/ui/calendar.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  DayPicker,\n  getDefaultClassNames,\n  type DayButton,\n  type Locale,\n} from \"react-day-picker\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button, buttonVariants } from \"@/components/ui/button\"\nimport { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from \"lucide-react\"\n\nfunction Calendar({\n  className,\n  classNames,\n  showOutsideDays = true,\n  captionLayout = \"label\",\n  buttonVariant = \"ghost\",\n  locale,\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        \"bg-background group/calendar p-2 [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-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      locale={locale}\n      formatters={{\n        formatMonthDropdown: (date) =>\n          date.toLocaleString(locale?.code, { month: \"short\" }),\n        ...formatters,\n      }}\n      classNames={{\n        root: cn(\"w-fit\", defaultClassNames.root),\n        months: cn(\n          \"flex gap-4 flex-col md:flex-row relative\",\n          defaultClassNames.months\n        ),\n        month: cn(\"flex flex-col w-full gap-4\", defaultClassNames.month),\n        nav: cn(\n          \"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between\",\n          defaultClassNames.nav\n        ),\n        button_previous: cn(\n          buttonVariants({ variant: buttonVariant }),\n          \"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none\",\n          defaultClassNames.button_previous\n        ),\n        button_next: cn(\n          buttonVariants({ variant: buttonVariant }),\n          \"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none\",\n          defaultClassNames.button_next\n        ),\n        month_caption: cn(\n          \"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)\",\n          defaultClassNames.month_caption\n        ),\n        dropdowns: cn(\n          \"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5\",\n          defaultClassNames.dropdowns\n        ),\n        dropdown_root: cn(\n          \"relative rounded-(--cell-radius)\",\n          defaultClassNames.dropdown_root\n        ),\n        dropdown: cn(\n          \"absolute bg-popover inset-0 opacity-0\",\n          defaultClassNames.dropdown\n        ),\n        caption_label: cn(\n          \"select-none font-medium\",\n          captionLayout === \"label\"\n            ? \"text-sm\"\n            : \"rounded-(--cell-radius) flex items-center gap-1 text-sm [&>svg]:text-muted-foreground [&>svg]:size-3.5\",\n          defaultClassNames.caption_label\n        ),\n        table: \"w-full border-collapse\",\n        weekdays: cn(\"flex\", defaultClassNames.weekdays),\n        weekday: cn(\n          \"text-muted-foreground rounded-(--cell-radius) flex-1 font-normal text-[0.8rem] select-none\",\n          defaultClassNames.weekday\n        ),\n        week: cn(\"flex w-full mt-2\", defaultClassNames.week),\n        week_number_header: cn(\n          \"select-none w-(--cell-size)\",\n          defaultClassNames.week_number_header\n        ),\n        week_number: cn(\n          \"text-[0.8rem] select-none text-muted-foreground\",\n          defaultClassNames.week_number\n        ),\n        day: cn(\n          \"relative w-full rounded-(--cell-radius) h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius) group/day aspect-square select-none\",\n          props.showWeekNumber\n            ? \"[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)\"\n            : \"[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)\",\n          defaultClassNames.day\n        ),\n        range_start: cn(\n          \"rounded-l-(--cell-radius) bg-muted relative after:bg-muted after:absolute after:inset-y-0 after:w-4 after:right-0 z-0 isolate\",\n          defaultClassNames.range_start\n        ),\n        range_middle: cn(\"rounded-none\", defaultClassNames.range_middle),\n        range_end: cn(\n          \"rounded-r-(--cell-radius) bg-muted relative after:bg-muted after:absolute after:inset-y-0 after:w-4 after:left-0 z-0 isolate\",\n          defaultClassNames.range_end\n        ),\n        today: cn(\n          \"bg-muted text-foreground rounded-(--cell-radius) 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 className={cn(\"size-4\", className)} {...props} />\n            )\n          }\n\n          return (\n            <ChevronDownIcon className={cn(\"size-4\", className)} {...props} />\n          )\n        },\n        DayButton: ({ ...props }) => (\n          <CalendarDayButton locale={locale} {...props} />\n        ),\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  locale,\n  ...props\n}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {\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(locale?.code)}\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        \"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-foreground relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) [&>span]:text-xs [&>span]:opacity-70\",\n        defaultClassNames.day,\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Calendar, CalendarDayButton }\n"
  },
  {
    "path": "src/components/ui/card.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Card({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & { size?: \"default\" | \"sm\" }) {\n  return (\n    <div\n      data-slot=\"card\"\n      data-size={size}\n      className={cn(\n        \"ring-foreground/10 bg-card text-card-foreground group/card flex flex-col gap-4 overflow-hidden rounded-none py-4 text-xs/relaxed ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none\",\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        \"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3\",\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(\n        \"text-sm font-medium group-data-[size=sm]/card:text-sm\",\n        className\n      )}\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-muted-foreground text-xs/relaxed\", 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-4 group-data-[size=sm]/card:px-3\", 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(\n        \"flex items-center rounded-none border-t p-4 group-data-[size=sm]/card:p-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n}\n"
  },
  {
    "path": "src/components/ui/carousel.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport useEmblaCarousel, {\n  type UseEmblaCarouselType,\n} from \"embla-carousel-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\"\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-sm\",\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 touch-manipulation\",\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      <ChevronLeftIcon />\n      <span className=\"sr-only\">Previous slide</span>\n    </Button>\n  )\n}\n\nfunction CarouselNext({\n  className,\n  variant = \"outline\",\n  size = \"icon-sm\",\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 touch-manipulation\",\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      <ChevronRightIcon />\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  useCarousel,\n}\n"
  },
  {
    "path": "src/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          \"[&_.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-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 flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.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        \"border-border/50 bg-background grid min-w-32 items-start gap-1.5 rounded-none border 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                  \"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5\",\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=\"text-foreground font-mono font-medium 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                \"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3\"\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\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== \"object\" || payload === null) {\n    return undefined\n  }\n\n  const payloadPayload =\n    \"payload\" in payload &&\n    typeof payload.payload === \"object\" &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined\n\n  let configLabelKey: string = key\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === \"string\"\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string\n  }\n\n  return configLabelKey in config\n    ? config[configLabelKey]\n    : config[key as keyof typeof config]\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n}\n"
  },
  {
    "path": "src/components/ui/checkbox.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Checkbox as CheckboxPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { CheckIcon } from \"lucide-react\"\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        \"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 peer relative flex size-4 shrink-0 items-center justify-center rounded-none border transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-1\",\n        className\n      )}\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        data-slot=\"checkbox-indicator\"\n        className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\n      >\n        <CheckIcon\n        />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  )\n}\n\nexport { Checkbox }\n"
  },
  {
    "path": "src/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": "src/components/ui/combobox.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Combobox as ComboboxPrimitive } from \"@base-ui/react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput,\n} from \"@/components/ui/input-group\"\nimport { ChevronDownIcon, XIcon, CheckIcon } from \"lucide-react\"\n\nconst Combobox = ComboboxPrimitive.Root\n\nfunction ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {\n  return <ComboboxPrimitive.Value data-slot=\"combobox-value\" {...props} />\n}\n\nfunction ComboboxTrigger({\n  className,\n  children,\n  ...props\n}: ComboboxPrimitive.Trigger.Props) {\n  return (\n    <ComboboxPrimitive.Trigger\n      data-slot=\"combobox-trigger\"\n      className={cn(\"[&_svg:not([class*='size-'])]:size-4\", className)}\n      {...props}\n    >\n      {children}\n      <ChevronDownIcon className=\"text-muted-foreground pointer-events-none size-4\" />\n    </ComboboxPrimitive.Trigger>\n  )\n}\n\nfunction ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {\n  return (\n    <ComboboxPrimitive.Clear\n      data-slot=\"combobox-clear\"\n      render={<InputGroupButton variant=\"ghost\" size=\"icon-xs\" />}\n      className={cn(className)}\n      {...props}\n    >\n      <XIcon className=\"pointer-events-none\" />\n    </ComboboxPrimitive.Clear>\n  )\n}\n\nfunction ComboboxInput({\n  className,\n  children,\n  disabled = false,\n  showTrigger = true,\n  showClear = false,\n  ...props\n}: ComboboxPrimitive.Input.Props & {\n  showTrigger?: boolean\n  showClear?: boolean\n}) {\n  return (\n    <InputGroup className={cn(\"w-auto\", className)}>\n      <ComboboxPrimitive.Input\n        render={<InputGroupInput disabled={disabled} />}\n        {...props}\n      />\n      <InputGroupAddon align=\"inline-end\">\n        {showTrigger && (\n          <InputGroupButton\n            size=\"icon-xs\"\n            variant=\"ghost\"\n            asChild\n            data-slot=\"input-group-button\"\n            className=\"group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent\"\n            disabled={disabled}\n          >\n            <ComboboxTrigger />\n          </InputGroupButton>\n        )}\n        {showClear && <ComboboxClear disabled={disabled} />}\n      </InputGroupAddon>\n      {children}\n    </InputGroup>\n  )\n}\n\nfunction ComboboxContent({\n  className,\n  side = \"bottom\",\n  sideOffset = 6,\n  align = \"start\",\n  alignOffset = 0,\n  anchor,\n  ...props\n}: ComboboxPrimitive.Popup.Props &\n  Pick<\n    ComboboxPrimitive.Positioner.Props,\n    \"side\" | \"align\" | \"sideOffset\" | \"alignOffset\" | \"anchor\"\n  >) {\n  return (\n    <ComboboxPrimitive.Portal>\n      <ComboboxPrimitive.Positioner\n        side={side}\n        sideOffset={sideOffset}\n        align={align}\n        alignOffset={alignOffset}\n        anchor={anchor}\n        className=\"isolate z-50\"\n      >\n        <ComboboxPrimitive.Popup\n          data-slot=\"combobox-content\"\n          data-chips={!!anchor}\n          className={cn(\"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:border-input/30 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-none shadow-md ring-1 duration-100 data-[chips=true]:min-w-(--anchor-width) *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:shadow-none\", className )}\n          {...props}\n        />\n      </ComboboxPrimitive.Positioner>\n    </ComboboxPrimitive.Portal>\n  )\n}\n\nfunction ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {\n  return (\n    <ComboboxPrimitive.List\n      data-slot=\"combobox-list\"\n      className={cn(\n        \"no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain data-empty:p-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxItem({\n  className,\n  children,\n  ...props\n}: ComboboxPrimitive.Item.Props) {\n  return (\n    <ComboboxPrimitive.Item\n      data-slot=\"combobox-item\"\n      className={cn(\n        \"data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none 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      {children}\n      <ComboboxPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </ComboboxPrimitive.ItemIndicator>\n    </ComboboxPrimitive.Item>\n  )\n}\n\nfunction ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {\n  return (\n    <ComboboxPrimitive.Group\n      data-slot=\"combobox-group\"\n      className={cn(className)}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxLabel({\n  className,\n  ...props\n}: ComboboxPrimitive.GroupLabel.Props) {\n  return (\n    <ComboboxPrimitive.GroupLabel\n      data-slot=\"combobox-label\"\n      className={cn(\"text-muted-foreground px-2 py-2 text-xs\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {\n  return (\n    <ComboboxPrimitive.Collection data-slot=\"combobox-collection\" {...props} />\n  )\n}\n\nfunction ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {\n  return (\n    <ComboboxPrimitive.Empty\n      data-slot=\"combobox-empty\"\n      className={cn(\n        \"text-muted-foreground hidden w-full justify-center py-2 text-center text-xs group-data-empty/combobox-content:flex\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxSeparator({\n  className,\n  ...props\n}: ComboboxPrimitive.Separator.Props) {\n  return (\n    <ComboboxPrimitive.Separator\n      data-slot=\"combobox-separator\"\n      className={cn(\"bg-border -mx-1 h-px\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxChips({\n  className,\n  ...props\n}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &\n  ComboboxPrimitive.Chips.Props) {\n  return (\n    <ComboboxPrimitive.Chips\n      data-slot=\"combobox-chips\"\n      className={cn(\n        \"dark:bg-input/30 border-input focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-8 flex-wrap items-center gap-1 rounded-none border bg-transparent bg-clip-padding px-2.5 py-1 text-xs transition-colors focus-within:ring-1 has-aria-invalid:ring-1 has-data-[slot=combobox-chip]:px-1\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxChip({\n  className,\n  children,\n  showRemove = true,\n  ...props\n}: ComboboxPrimitive.Chip.Props & {\n  showRemove?: boolean\n}) {\n  return (\n    <ComboboxPrimitive.Chip\n      data-slot=\"combobox-chip\"\n      className={cn(\n        \"bg-muted text-foreground flex h-[calc(--spacing(5.25))] w-fit items-center justify-center gap-1 rounded-none px-1.5 text-xs font-medium whitespace-nowrap has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      {showRemove && (\n        <ComboboxPrimitive.ChipRemove\n          render={<Button variant=\"ghost\" size=\"icon-xs\" />}\n          className=\"-ml-1 opacity-50 hover:opacity-100\"\n          data-slot=\"combobox-chip-remove\"\n        >\n          <XIcon className=\"pointer-events-none\" />\n        </ComboboxPrimitive.ChipRemove>\n      )}\n    </ComboboxPrimitive.Chip>\n  )\n}\n\nfunction ComboboxChipsInput({\n  className,\n  ...props\n}: ComboboxPrimitive.Input.Props) {\n  return (\n    <ComboboxPrimitive.Input\n      data-slot=\"combobox-chip-input\"\n      className={cn(\"min-w-16 flex-1 outline-none\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction useComboboxAnchor() {\n  return React.useRef<HTMLDivElement | null>(null)\n}\n\nexport {\n  Combobox,\n  ComboboxInput,\n  ComboboxContent,\n  ComboboxList,\n  ComboboxItem,\n  ComboboxGroup,\n  ComboboxLabel,\n  ComboboxCollection,\n  ComboboxEmpty,\n  ComboboxSeparator,\n  ComboboxChips,\n  ComboboxChip,\n  ComboboxChipsInput,\n  ComboboxTrigger,\n  ComboboxValue,\n  useComboboxAnchor,\n}\n"
  },
  {
    "path": "src/components/ui/command.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Command as CommandPrimitive } from \"cmdk\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n} from \"@/components/ui/input-group\"\nimport { SearchIcon, CheckIcon } from \"lucide-react\"\n\nfunction Command({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive>) {\n  return (\n    <CommandPrimitive\n      data-slot=\"command\"\n      className={cn(\n        \"bg-popover text-popover-foreground flex size-full flex-col overflow-hidden rounded-none\",\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 = false,\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(\n          \"top-1/3 translate-y-0 overflow-hidden rounded-none p-0\",\n          className\n        )}\n        showCloseButton={showCloseButton}\n      >\n        {children}\n      </DialogContent>\n    </Dialog>\n  )\n}\n\nfunction CommandInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  return (\n    <div data-slot=\"command-input-wrapper\" className=\"border-b pb-0\">\n      <InputGroup className=\"bg-input/30 border-input/30 h-8 border-none shadow-none! *:data-[slot=input-group-addon]:pl-2!\">\n        <CommandPrimitive.Input\n          data-slot=\"command-input\"\n          className={cn(\n            \"w-full text-xs outline-hidden disabled:cursor-not-allowed disabled:opacity-50\",\n            className\n          )}\n          {...props}\n        />\n        <InputGroupAddon>\n          <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n        </InputGroupAddon>\n      </InputGroup>\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        \"no-scrollbar max-h-72 scroll-py-0 overflow-x-hidden overflow-y-auto outline-none\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CommandEmpty({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  return (\n    <CommandPrimitive.Empty\n      data-slot=\"command-empty\"\n      className={cn(\"py-6 text-center text-xs\", className)}\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        \"text-foreground **:[[cmdk-group-heading]]:text-muted-foreground overflow-hidden **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs\",\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(\"bg-border -mx-1 h-px\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CommandItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  return (\n    <CommandPrimitive.Item\n      data-slot=\"command-item\"\n      className={cn(\n        \"data-selected:bg-muted data-selected:text-foreground data-selected:*:[svg]:text-foreground group/command-item relative flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none in-data-[slot=dialog-content]:rounded-none! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <CheckIcon className=\"ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100\" />\n    </CommandPrimitive.Item>\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        \"text-muted-foreground group-data-selected/command-item:text-foreground ml-auto text-xs tracking-widest\",\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": "src/components/ui/context-menu.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ContextMenu as ContextMenuPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChevronRightIcon, CheckIcon } from \"lucide-react\"\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  className,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {\n  return (\n    <ContextMenuPrimitive.Trigger\n      data-slot=\"context-menu-trigger\"\n      className={cn(\"select-none\", className)}\n      {...props}\n    />\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 ContextMenuContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Content> & {\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\"\n}) {\n  return (\n    <ContextMenuPrimitive.Portal>\n      <ContextMenuPrimitive.Content\n        data-slot=\"context-menu-content\"\n        className={cn(\"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground z-50 max-h-(--radix-context-menu-content-available-height) min-w-36 origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-none shadow-md ring-1 duration-100\", className )}\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        \"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive focus:*:[svg]:text-accent-foreground group/context-menu-item relative flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\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        \"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\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(\"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-popover text-popover-foreground z-50 min-w-32 origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-none border shadow-lg duration-100\", className )}\n      {...props}\n    />\n  )\n}\n\nfunction ContextMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem> & {\n  inset?: boolean\n}) {\n  return (\n    <ContextMenuPrimitive.CheckboxItem\n      data-slot=\"context-menu-checkbox-item\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_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 right-2\">\n        <ContextMenuPrimitive.ItemIndicator>\n          <CheckIcon\n          />\n        </ContextMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </ContextMenuPrimitive.CheckboxItem>\n  )\n}\n\nfunction ContextMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem> & {\n  inset?: boolean\n}) {\n  return (\n    <ContextMenuPrimitive.RadioItem\n      data-slot=\"context-menu-radio-item\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_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 right-2\">\n        <ContextMenuPrimitive.ItemIndicator>\n          <CheckIcon\n          />\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        \"text-muted-foreground px-2 py-2 text-xs data-inset:pl-7\",\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(\"bg-border -mx-1 h-px\", 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        \"text-muted-foreground group-focus/context-menu-item:text-accent-foreground ml-auto text-xs tracking-widest\",\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": "src/components/ui/dialog.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Dialog as DialogPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { XIcon } from \"lucide-react\"\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        \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs\",\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>\n      <DialogOverlay />\n      <DialogPrimitive.Content\n        data-slot=\"dialog-content\"\n        className={cn(\n          \"bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-none p-4 text-xs/relaxed ring-1 duration-100 outline-none sm:max-w-sm\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close data-slot=\"dialog-close\" asChild>\n            <Button\n              variant=\"ghost\"\n              className=\"absolute top-2 right-2\"\n              size=\"icon-sm\"\n            >\n              <XIcon\n              />\n              <span className=\"sr-only\">Close</span>\n            </Button>\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-1 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-sm font-medium\", 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(\n        \"text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3\",\n        className\n      )}\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": "src/components/ui/direction.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Direction } from \"radix-ui\"\n\nfunction DirectionProvider({\n  dir,\n  direction,\n  children,\n}: React.ComponentProps<typeof Direction.DirectionProvider> & {\n  direction?: React.ComponentProps<typeof Direction.DirectionProvider>[\"dir\"]\n}) {\n  return (\n    <Direction.DirectionProvider dir={direction ?? dir}>\n      {children}\n    </Direction.DirectionProvider>\n  )\n}\n\nconst useDirection = Direction.useDirection\n\nexport { DirectionProvider, useDirection }\n"
  },
  {
    "path": "src/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        \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 supports-backdrop-filter:backdrop-blur-xs\",\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          \"bg-background group/drawer-content fixed z-50 flex h-auto flex-col text-xs/relaxed 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-none data-[vaul-drawer-direction=bottom]:border-t 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]:rounded-none data-[vaul-drawer-direction=left]:border-r 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]:rounded-none data-[vaul-drawer-direction=right]:border-l 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-none data-[vaul-drawer-direction=top]:border-b data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm\",\n          className\n        )}\n        {...props}\n      >\n        <div className=\"bg-muted mx-auto mt-4 hidden h-1 w-[100px] shrink-0 rounded-none 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-0.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(\"text-foreground text-sm font-medium\", 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-muted-foreground text-xs/relaxed\", 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": "src/components/ui/dropdown-menu.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { CheckIcon, ChevronRightIcon } from \"lucide-react\"\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  align = \"start\",\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        align={align}\n        className={cn(\"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-none shadow-md ring-1 duration-100 data-[state=closed]:overflow-hidden\", className )}\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        \"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem> & {\n  inset?: boolean\n}) {\n  return (\n    <DropdownMenuPrimitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <DropdownMenuPrimitive.ItemIndicator>\n          <CheckIcon\n          />\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  inset,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem> & {\n  inset?: boolean\n}) {\n  return (\n    <DropdownMenuPrimitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <DropdownMenuPrimitive.ItemIndicator>\n          <CheckIcon\n          />\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        \"text-muted-foreground px-2 py-2 text-xs data-inset:pl-7\",\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(\"bg-border -mx-1 h-px\", 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        \"text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest\",\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        \"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\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(\"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground z-50 min-w-[96px] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-none shadow-lg ring-1 duration-100\", className )}\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": "src/components/ui/empty.tsx",
    "content": "import { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Empty({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty\"\n      className={cn(\n        \"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-none border-dashed p-6 text-center text-balance\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-header\"\n      className={cn(\"flex max-w-sm flex-col items-center gap-2\", className)}\n      {...props}\n    />\n  )\n}\n\nconst emptyMediaVariants = cva(\n  \"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        icon: \"bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-none [&_svg:not([class*='size-'])]:size-4\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction EmptyMedia({\n  className,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof emptyMediaVariants>) {\n  return (\n    <div\n      data-slot=\"empty-icon\"\n      data-variant={variant}\n      className={cn(emptyMediaVariants({ variant, className }))}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-title\"\n      className={cn(\"text-sm font-medium\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <div\n      data-slot=\"empty-description\"\n      className={cn(\n        \"text-muted-foreground [&>a:hover]:text-primary text-xs/relaxed [&>a]:underline [&>a]:underline-offset-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction EmptyContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-content\"\n      className={cn(\n        \"flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-xs text-balance\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Empty,\n  EmptyHeader,\n  EmptyTitle,\n  EmptyDescription,\n  EmptyContent,\n  EmptyMedia,\n}\n"
  },
  {
    "path": "src/components/ui/field.tsx",
    "content": "\"use client\"\n\nimport { useMemo } from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/components/ui/label\"\nimport { Separator } from \"@/components/ui/separator\"\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<\"fieldset\">) {\n  return (\n    <fieldset\n      data-slot=\"field-set\"\n      className={cn(\n        \"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldLegend({\n  className,\n  variant = \"legend\",\n  ...props\n}: React.ComponentProps<\"legend\"> & { variant?: \"legend\" | \"label\" }) {\n  return (\n    <legend\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      className={cn(\n        \"mb-2.5 font-medium data-[variant=label]:text-xs data-[variant=legend]:text-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-group\"\n      className={cn(\n        \"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst fieldVariants = cva(\n  \"data-[invalid=true]:text-destructive gap-2 group/field flex w-full\",\n  {\n    variants: {\n      orientation: {\n        vertical: \"flex-col *:w-full [&>.sr-only]:w-auto\",\n        horizontal:\n          \"flex-row items-center *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n        responsive:\n          \"flex-col *:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:*:data-[slot=field-label]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n      },\n    },\n    defaultVariants: {\n      orientation: \"vertical\",\n    },\n  }\n)\n\nfunction Field({\n  className,\n  orientation = \"vertical\",\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"field\"\n      data-orientation={orientation}\n      className={cn(fieldVariants({ orientation }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-content\"\n      className={cn(\n        \"group/field-content flex flex-1 flex-col gap-0.5 leading-snug\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      data-slot=\"field-label\"\n      className={cn(\n        \"has-data-checked:bg-primary/5 has-data-checked:border-primary/30 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10 group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-none has-[>[data-slot=field]]:border *:data-[slot=field]:p-2\",\n        \"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-label\"\n      className={cn(\n        \"flex w-fit items-center gap-2 text-xs/relaxed leading-snug group-data-[disabled=true]/field:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"field-description\"\n      className={cn(\n        \"text-muted-foreground text-left text-xs/relaxed leading-normal font-normal group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5\",\n        \"last:mt-0 nth-last-2:-mt-1\",\n        \"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  children?: React.ReactNode\n}) {\n  return (\n    <div\n      data-slot=\"field-separator\"\n      data-content={!!children}\n      className={cn(\n        \"relative -my-2 h-5 text-xs group-data-[variant=outline]/field-group:-mb-2\",\n        className\n      )}\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"text-muted-foreground bg-background relative mx-auto block w-fit px-2\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  )\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  errors?: Array<{ message?: string } | undefined>\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children\n    }\n\n    if (!errors?.length) {\n      return null\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map((error) => [error?.message, error])).values(),\n    ]\n\n    if (uniqueErrors?.length == 1) {\n      return uniqueErrors[0]?.message\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={index}>{error.message}</li>\n        )}\n      </ul>\n    )\n  }, [children, errors])\n\n  if (!content) {\n    return null\n  }\n\n  return (\n    <div\n      role=\"alert\"\n      data-slot=\"field-error\"\n      className={cn(\"text-destructive text-xs font-normal\", className)}\n      {...props}\n    >\n      {content}\n    </div>\n  )\n}\n\nexport {\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldContent,\n  FieldTitle,\n}\n"
  },
  {
    "path": "src/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          \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-none p-2.5 text-xs/relaxed shadow-md ring-1 outline-hidden duration-100\",\n          className\n        )}\n        {...props}\n      />\n    </HoverCardPrimitive.Portal>\n  )\n}\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent }\n"
  },
  {
    "path": "src/components/ui/input-group.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { Textarea } from \"@/components/ui/textarea\"\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"input-group\"\n      role=\"group\"\n      className={cn(\n        \"border-input dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-disabled:bg-input/50 dark:has-disabled:bg-input/80 group/input-group relative flex h-8 w-full min-w-0 items-center rounded-none border transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-1 has-[[data-slot][aria-invalid=true]]:ring-1 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst inputGroupAddonVariants = cva(\n  \"text-muted-foreground h-auto gap-2 py-1.5 text-xs font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-none [&>svg:not([class*='size-'])]:size-4 flex cursor-text items-center justify-center select-none\",\n  {\n    variants: {\n      align: {\n        \"inline-start\":\n          \"pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem] order-first\",\n        \"inline-end\":\n          \"pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem] order-last\",\n        \"block-start\":\n          \"px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start\",\n        \"block-end\":\n          \"px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start\",\n      },\n    },\n    defaultVariants: {\n      align: \"inline-start\",\n    },\n  }\n)\n\nfunction InputGroupAddon({\n  className,\n  align = \"inline-start\",\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"input-group-addon\"\n      data-align={align}\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest(\"button\")) {\n          return\n        }\n        e.currentTarget.parentElement?.querySelector(\"input\")?.focus()\n      }}\n      {...props}\n    />\n  )\n}\n\nconst inputGroupButtonVariants = cva(\n  \"gap-2 text-xs shadow-none flex items-center\",\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-none px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: \"\",\n        \"icon-xs\": \"size-6 rounded-none p-0 has-[>svg]:p-0\",\n        \"icon-sm\": \"size-8 p-0 has-[>svg]:p-0\",\n      },\n    },\n    defaultVariants: {\n      size: \"xs\",\n    },\n  }\n)\n\nfunction InputGroupButton({\n  className,\n  type = \"button\",\n  variant = \"ghost\",\n  size = \"xs\",\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, \"size\"> &\n  VariantProps<typeof inputGroupButtonVariants>) {\n  return (\n    <Button\n      type={type}\n      data-size={size}\n      variant={variant}\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      className={cn(\n        \"text-muted-foreground flex items-center gap-2 text-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<\"input\">) {\n  return (\n    <Input\n      data-slot=\"input-group-control\"\n      className={cn(\n        \"flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<\"textarea\">) {\n  return (\n    <Textarea\n      data-slot=\"input-group-control\"\n      className={cn(\n        \"flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n}\n"
  },
  {
    "path": "src/components/ui/input-otp.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { OTPInput, OTPInputContext } from \"input-otp\"\n\nimport { cn } from \"@/lib/utils\"\nimport { MinusIcon } from \"lucide-react\"\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        \"cn-input-otp flex items-center has-disabled:opacity-50\",\n        containerClassName\n      )}\n      spellCheck={false}\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(\n        \"has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive flex items-center rounded-none has-aria-invalid:ring-1\",\n        className\n      )}\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        \"dark:bg-input/30 border-input data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive relative flex size-8 items-center justify-center border-y border-r text-xs transition-all outline-none first:rounded-none first:border-l last:rounded-none data-[active=true]:z-10 data-[active=true]:ring-1\",\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=\"animate-caret-blink bg-foreground h-4 w-px duration-1000\" />\n        </div>\n      )}\n    </div>\n  )\n}\n\nfunction InputOTPSeparator({ ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"input-otp-separator\"\n      className=\"flex items-center [&_svg:not([class*='size-'])]:size-4\"\n      role=\"separator\"\n      {...props}\n    >\n      <MinusIcon\n      />\n    </div>\n  )\n}\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }\n"
  },
  {
    "path": "src/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        \"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 file:text-foreground placeholder:text-muted-foreground h-8 w-full min-w-0 rounded-none border bg-transparent px-2.5 py-1 text-xs transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs file:font-medium focus-visible:ring-1 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-1 md:text-xs\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Input }\n"
  },
  {
    "path": "src/components/ui/item.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\"\nimport { Separator } from \"@/components/ui/separator\"\n\nfunction ItemGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      role=\"list\"\n      data-slot=\"item-group\"\n      className={cn(\n        \"group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"item-separator\"\n      orientation=\"horizontal\"\n      className={cn(\"my-2\", className)}\n      {...props}\n    />\n  )\n}\n\nconst itemVariants = cva(\n  \"[a]:hover:bg-muted rounded-none border text-xs w-full group/item focus-visible:border-ring focus-visible:ring-ring/50 flex items-center flex-wrap outline-none transition-colors duration-100 focus-visible:ring-[3px] [a]:transition-colors\",\n  {\n    variants: {\n      variant: {\n        default: \"border-transparent\",\n        outline: \"border-border\",\n        muted: \"bg-muted/50 border-transparent\",\n      },\n      size: {\n        default: \"gap-2.5 px-3 py-2.5\",\n        sm: \"gap-2.5 px-3 py-2.5\",\n        xs: \"gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Item({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"div\"> &\n  VariantProps<typeof itemVariants> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : \"div\"\n  return (\n    <Comp\n      data-slot=\"item\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(itemVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nconst itemMediaVariants = cva(\n  \"gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start flex shrink-0 items-center justify-center [&_svg]:pointer-events-none\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        icon: \"[&_svg:not([class*='size-'])]:size-4\",\n        image:\n          \"size-10 overflow-hidden rounded-none group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction ItemMedia({\n  className,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof itemMediaVariants>) {\n  return (\n    <div\n      data-slot=\"item-media\"\n      data-variant={variant}\n      className={cn(itemMediaVariants({ variant, className }))}\n      {...props}\n    />\n  )\n}\n\nfunction ItemContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"item-content\"\n      className={cn(\n        \"flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"item-title\"\n      className={cn(\n        \"line-clamp-1 flex w-fit items-center gap-2 text-xs font-medium underline-offset-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"item-description\"\n      className={cn(\n        \"text-muted-foreground [&>a:hover]:text-primary line-clamp-2 text-left text-xs/relaxed font-normal group-data-[size=xs]/item:text-xs/relaxed [&>a]:underline [&>a]:underline-offset-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemActions({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"item-actions\"\n      className={cn(\"flex items-center gap-2\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ItemHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"item-header\"\n      className={cn(\n        \"flex basis-full items-center justify-between gap-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ItemFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"item-footer\"\n      className={cn(\n        \"flex basis-full items-center justify-between gap-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Item,\n  ItemMedia,\n  ItemContent,\n  ItemActions,\n  ItemGroup,\n  ItemSeparator,\n  ItemTitle,\n  ItemDescription,\n  ItemHeader,\n  ItemFooter,\n}\n"
  },
  {
    "path": "src/components/ui/kbd.tsx",
    "content": "import { cn } from \"@/lib/utils\"\n\nfunction Kbd({ className, ...props }: React.ComponentProps<\"kbd\">) {\n  return (\n    <kbd\n      data-slot=\"kbd\"\n      className={cn(\n        \"bg-muted text-muted-foreground in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-none px-1 font-sans text-xs font-medium select-none [&_svg:not([class*='size-'])]:size-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction KbdGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <kbd\n      data-slot=\"kbd-group\"\n      className={cn(\"inline-flex items-center gap-1\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Kbd, KbdGroup }\n"
  },
  {
    "path": "src/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-xs leading-none 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": "src/components/ui/marquee.tsx",
    "content": "\"use client\";\r\n\r\nimport { cva, type VariantProps } from \"class-variance-authority\";\r\nimport {\r\n  Direction as DirectionPrimitive,\r\n  Slot as SlotPrimitive,\r\n} from \"radix-ui\";\r\nimport * as React from \"react\";\r\nimport { useComposedRefs } from \"@/lib/compose-refs\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\nconst ROOT_NAME = \"Marquee\";\r\nconst CONTENT_NAME = \"MarqueeContent\";\r\n\r\ntype Side = \"left\" | \"right\" | \"top\" | \"bottom\";\r\ntype Orientation = \"horizontal\" | \"vertical\";\r\ntype Direction = \"ltr\" | \"rtl\";\r\n\r\ntype RootElement = React.ComponentRef<typeof Marquee>;\r\ntype ContentElement = React.ComponentRef<typeof MarqueeContent>;\r\n\r\ninterface Dimensions {\r\n  width: number;\r\n  height: number;\r\n}\r\n\r\ninterface ElementDimensions {\r\n  rootSize: number;\r\n  contentSize: number;\r\n}\r\n\r\nfunction createResizeObserverStore() {\r\n  const listeners = new Set<() => void>();\r\n  let observer: ResizeObserver | null = null;\r\n  const elements = new Map<Element, Dimensions>();\r\n  const refCounts = new Map<Element, number>();\r\n  const isSupported = typeof ResizeObserver !== \"undefined\";\r\n  let notificationScheduled = false;\r\n\r\n  const snapshotCache = new WeakMap<\r\n    Element,\r\n    WeakMap<\r\n      Element,\r\n      { horizontal: ElementDimensions; vertical: ElementDimensions }\r\n    >\r\n  >();\r\n\r\n  function notify() {\r\n    if (notificationScheduled) return;\r\n    notificationScheduled = true;\r\n    queueMicrotask(() => {\r\n      notificationScheduled = false;\r\n      for (const callback of listeners) {\r\n        callback();\r\n      }\r\n    });\r\n  }\r\n\r\n  function cleanup() {\r\n    if (observer) {\r\n      observer.disconnect();\r\n      observer = null;\r\n    }\r\n    elements.clear();\r\n    refCounts.clear();\r\n  }\r\n\r\n  function subscribe(callback: () => void) {\r\n    listeners.add(callback);\r\n    return () => {\r\n      listeners.delete(callback);\r\n      if (listeners.size === 0) {\r\n        cleanup();\r\n      }\r\n    };\r\n  }\r\n\r\n  function getSnapshot(\r\n    rootElement: RootElement | null,\r\n    contentElement: ContentElement | null,\r\n    orientation: Orientation,\r\n  ): ElementDimensions | null {\r\n    if (!rootElement || !contentElement) return null;\r\n\r\n    const rootDims = elements.get(rootElement);\r\n    const contentDims = elements.get(contentElement);\r\n\r\n    if (!rootDims || !contentDims) return null;\r\n\r\n    const rootSize =\r\n      orientation === \"vertical\" ? rootDims.height : rootDims.width;\r\n    const contentSize =\r\n      orientation === \"vertical\" ? contentDims.height : contentDims.width;\r\n\r\n    let rootCache = snapshotCache.get(rootElement);\r\n    if (!rootCache) {\r\n      rootCache = new WeakMap();\r\n      snapshotCache.set(rootElement, rootCache);\r\n    }\r\n\r\n    let contentCache = rootCache.get(contentElement);\r\n    if (!contentCache) {\r\n      contentCache = {\r\n        horizontal: { rootSize: -1, contentSize: -1 },\r\n        vertical: { rootSize: -1, contentSize: -1 },\r\n      };\r\n      rootCache.set(contentElement, contentCache);\r\n    }\r\n\r\n    const cached = contentCache[orientation];\r\n    if (cached.rootSize === rootSize && cached.contentSize === contentSize) {\r\n      return cached;\r\n    }\r\n\r\n    const snapshot = { rootSize, contentSize };\r\n    contentCache[orientation] = snapshot;\r\n    return snapshot;\r\n  }\r\n\r\n  function observe(\r\n    rootElement: RootElement | null,\r\n    contentElement: Element | null,\r\n  ) {\r\n    if (!isSupported || !rootElement || !contentElement) return;\r\n\r\n    if (!observer) {\r\n      observer = new ResizeObserver((entries) => {\r\n        let hasChanged = false;\r\n\r\n        for (const entry of entries) {\r\n          const element = entry.target;\r\n          const { width, height } = entry.contentRect;\r\n\r\n          const currentData = elements.get(element);\r\n\r\n          if (\r\n            !currentData ||\r\n            currentData.width !== width ||\r\n            currentData.height !== height\r\n          ) {\r\n            elements.set(element, { width, height });\r\n            hasChanged = true;\r\n          }\r\n        }\r\n\r\n        if (hasChanged) {\r\n          notify();\r\n        }\r\n      });\r\n    }\r\n\r\n    refCounts.set(rootElement, (refCounts.get(rootElement) ?? 0) + 1);\r\n    refCounts.set(contentElement, (refCounts.get(contentElement) ?? 0) + 1);\r\n\r\n    observer.observe(rootElement);\r\n    observer.observe(contentElement);\r\n\r\n    const rootRect = rootElement.getBoundingClientRect();\r\n    const contentRect = contentElement.getBoundingClientRect();\r\n\r\n    const rootData = { width: rootRect.width, height: rootRect.height };\r\n    const contentData = {\r\n      width: contentRect.width,\r\n      height: contentRect.height,\r\n    };\r\n\r\n    elements.set(rootElement, rootData);\r\n    elements.set(contentElement, contentData);\r\n\r\n    if (\r\n      rootData.width > 0 &&\r\n      rootData.height > 0 &&\r\n      contentData.width > 0 &&\r\n      contentData.height > 0\r\n    ) {\r\n      notify();\r\n    }\r\n  }\r\n\r\n  function unobserve(\r\n    rootElement: RootElement | null,\r\n    contentElement: Element | null,\r\n  ) {\r\n    if (!observer || !rootElement || !contentElement) return;\r\n\r\n    const rootCount = (refCounts.get(rootElement) ?? 1) - 1;\r\n    const contentCount = (refCounts.get(contentElement) ?? 1) - 1;\r\n\r\n    if (rootCount <= 0) {\r\n      observer.unobserve(rootElement);\r\n      elements.delete(rootElement);\r\n      refCounts.delete(rootElement);\r\n    } else {\r\n      refCounts.set(rootElement, rootCount);\r\n    }\r\n\r\n    if (contentCount <= 0) {\r\n      observer.unobserve(contentElement);\r\n      elements.delete(contentElement);\r\n      refCounts.delete(contentElement);\r\n    } else {\r\n      refCounts.set(contentElement, contentCount);\r\n    }\r\n  }\r\n\r\n  return {\r\n    subscribe,\r\n    getSnapshot,\r\n    observe,\r\n    unobserve,\r\n  };\r\n}\r\n\r\nconst resizeObserverStore = createResizeObserverStore();\r\n\r\nfunction useResizeObserverStore(\r\n  rootRef: React.RefObject<RootElement | null>,\r\n  contentRef: React.RefObject<ContentElement | null>,\r\n  orientation: Orientation,\r\n) {\r\n  const onSubscribe = React.useCallback(\r\n    (callback: () => void) => resizeObserverStore.subscribe(callback),\r\n    [],\r\n  );\r\n\r\n  const getSnapshot = React.useCallback(\r\n    () =>\r\n      resizeObserverStore.getSnapshot(\r\n        rootRef.current,\r\n        contentRef.current,\r\n        orientation,\r\n      ),\r\n    [rootRef, contentRef, orientation],\r\n  );\r\n\r\n  return React.useSyncExternalStore(onSubscribe, getSnapshot, getSnapshot);\r\n}\r\n\r\ninterface DivProps extends React.ComponentProps<\"div\"> {\r\n  asChild?: boolean;\r\n}\r\n\r\ninterface MarqueeContextValue {\r\n  side: Side;\r\n  orientation: Orientation;\r\n  dir: Direction;\r\n  speed: number;\r\n  loopCount: number;\r\n  contentRef: React.RefObject<ContentElement | null>;\r\n  rootRef: React.RefObject<RootElement | null>;\r\n  autoFill: boolean;\r\n  pauseOnHover: boolean;\r\n  pauseOnKeyboard: boolean;\r\n  reverse: boolean;\r\n  paused: boolean;\r\n}\r\n\r\nconst MarqueeContext = React.createContext<MarqueeContextValue | null>(null);\r\n\r\nfunction useMarqueeContext(consumerName: string) {\r\n  const context = React.useContext(MarqueeContext);\r\n  if (!context) {\r\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\r\n  }\r\n  return context;\r\n}\r\n\r\ninterface MarqueeProps extends DivProps {\r\n  side?: Side;\r\n  dir?: Direction;\r\n  speed?: number;\r\n  delay?: number;\r\n  loopCount?: number;\r\n  gap?: string | number;\r\n  autoFill?: boolean;\r\n  pauseOnHover?: boolean;\r\n  pauseOnKeyboard?: boolean;\r\n  reverse?: boolean;\r\n}\r\n\r\nfunction Marquee(props: MarqueeProps) {\r\n  const {\r\n    side = \"left\",\r\n    dir: dirProp,\r\n    speed = 50,\r\n    delay = 0,\r\n    loopCount = 0,\r\n    gap = \"1rem\",\r\n    asChild,\r\n    autoFill = false,\r\n    pauseOnHover = false,\r\n    pauseOnKeyboard = false,\r\n    reverse = false,\r\n    className,\r\n    style: styleProp,\r\n    ref,\r\n    ...marqueeProps\r\n  } = props;\r\n\r\n  const orientation: Orientation =\r\n    side === \"top\" || side === \"bottom\" ? \"vertical\" : \"horizontal\";\r\n\r\n  const dir = DirectionPrimitive.useDirection(dirProp);\r\n\r\n  const rootRef = React.useRef<RootElement>(null);\r\n  const contentRef = React.useRef<ContentElement>(null);\r\n  const composedRef = useComposedRefs(ref, rootRef);\r\n\r\n  const [paused, setPaused] = React.useState(false);\r\n\r\n  const onKeyDown = React.useCallback(\r\n    (event: React.KeyboardEvent) => {\r\n      if (pauseOnKeyboard && event.key === \" \") {\r\n        event.preventDefault();\r\n        setPaused((prev) => !prev);\r\n      }\r\n    },\r\n    [pauseOnKeyboard],\r\n  );\r\n\r\n  const dimensions = useResizeObserverStore(rootRef, contentRef, orientation);\r\n\r\n  const duration = React.useMemo(() => {\r\n    const safeSpeed = Math.max(0.001, speed);\r\n\r\n    if (!dimensions) {\r\n      const defaultDistance = autoFill ? 1000 : 2000;\r\n      return defaultDistance / safeSpeed;\r\n    }\r\n\r\n    const { rootSize, contentSize } = dimensions;\r\n\r\n    if (autoFill) {\r\n      const multiplier =\r\n        contentSize < rootSize ? Math.ceil(rootSize / contentSize) : 1;\r\n      return (contentSize * multiplier) / safeSpeed;\r\n    } else {\r\n      return contentSize < rootSize\r\n        ? rootSize / safeSpeed\r\n        : contentSize / safeSpeed;\r\n    }\r\n  }, [dimensions, speed, autoFill]);\r\n\r\n  const style = React.useMemo<React.CSSProperties>(\r\n    () => ({\r\n      \"--marquee-duration\": `${duration}s`,\r\n      \"--marquee-gap\": gap,\r\n      \"--marquee-delay\": `${delay}s`,\r\n      \"--marquee-loop-count\":\r\n        loopCount === 0 || loopCount === Infinity\r\n          ? \"infinite\"\r\n          : loopCount.toString(),\r\n      ...styleProp,\r\n    }),\r\n    [duration, gap, delay, loopCount, styleProp],\r\n  );\r\n\r\n  const contextValue = React.useMemo<MarqueeContextValue>(\r\n    () => ({\r\n      side,\r\n      orientation,\r\n      dir,\r\n      speed,\r\n      loopCount,\r\n      contentRef,\r\n      rootRef,\r\n      autoFill,\r\n      paused,\r\n      pauseOnHover,\r\n      pauseOnKeyboard,\r\n      reverse,\r\n    }),\r\n    [\r\n      side,\r\n      orientation,\r\n      dir,\r\n      speed,\r\n      loopCount,\r\n      autoFill,\r\n      paused,\r\n      pauseOnHover,\r\n      pauseOnKeyboard,\r\n      reverse,\r\n    ],\r\n  );\r\n\r\n  const MarqueePrimitive = asChild ? SlotPrimitive.Slot : \"div\";\r\n\r\n  return (\r\n    <MarqueeContext.Provider value={contextValue}>\r\n      <div data-slot=\"marquee-wrapper\" className=\"grid\">\r\n        <MarqueePrimitive\r\n          role=\"marquee\"\r\n          aria-live=\"off\"\r\n          data-orientation={orientation}\r\n          data-slot=\"marquee\"\r\n          dir={dir}\r\n          tabIndex={pauseOnKeyboard ? 0 : undefined}\r\n          {...marqueeProps}\r\n          ref={composedRef}\r\n          className={cn(\r\n            \"relative flex overflow-hidden motion-reduce:animate-none\",\r\n            orientation === \"vertical\" && \"h-full flex-col\",\r\n            orientation === \"horizontal\" && \"w-full\",\r\n            paused && \"[&_*]:[animation-play-state:paused]\",\r\n            pauseOnHover && \"group\",\r\n            pauseOnKeyboard &&\r\n              \"rounded-md focus-visible:border-ring focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\",\r\n            className,\r\n          )}\r\n          style={style}\r\n          onKeyDown={pauseOnKeyboard ? onKeyDown : undefined}\r\n        />\r\n      </div>\r\n    </MarqueeContext.Provider>\r\n  );\r\n}\r\n\r\nconst marqueeContentVariants = cva(\r\n  \"flex min-w-full shrink-0 gap-(--marquee-gap)\",\r\n  {\r\n    variants: {\r\n      side: {\r\n        left: \"animate-marquee-left\",\r\n        right: \"animate-marquee-right\",\r\n        top: \"min-h-full min-w-auto animate-marquee-up flex-col\",\r\n        bottom: \"min-h-full min-w-auto animate-marquee-down flex-col\",\r\n      },\r\n      dir: {\r\n        ltr: \"\",\r\n        rtl: \"\",\r\n      },\r\n      pauseOnHover: {\r\n        true: \"group-hover:[animation-play-state:paused]\",\r\n        false: \"\",\r\n      },\r\n      reverse: {\r\n        true: \"[animation-direction:reverse]\",\r\n        false: \"\",\r\n      },\r\n    },\r\n    compoundVariants: [\r\n      {\r\n        side: \"left\",\r\n        dir: \"rtl\",\r\n        className: \"animate-marquee-left-rtl\",\r\n      },\r\n      {\r\n        side: \"right\",\r\n        dir: \"rtl\",\r\n        className: \"animate-marquee-right-rtl\",\r\n      },\r\n    ],\r\n    defaultVariants: {\r\n      side: \"left\",\r\n      dir: \"ltr\",\r\n      pauseOnHover: false,\r\n      reverse: false,\r\n    },\r\n  },\r\n);\r\n\r\nfunction MarqueeContent(props: DivProps) {\r\n  const {\r\n    className,\r\n    asChild,\r\n    ref,\r\n    children,\r\n    style: styleProp,\r\n    ...contentProps\r\n  } = props;\r\n\r\n  const context = useMarqueeContext(CONTENT_NAME);\r\n  const composedRef = useComposedRefs(ref, context.contentRef);\r\n\r\n  const isVertical = context.orientation === \"vertical\";\r\n  const isRtl = context.dir === \"rtl\";\r\n\r\n  const dimensions = useResizeObserverStore(\r\n    context.rootRef,\r\n    context.contentRef,\r\n    context.orientation,\r\n  );\r\n\r\n  React.useEffect(() => {\r\n    if (context.rootRef.current && context.contentRef.current) {\r\n      resizeObserverStore.observe(\r\n        context.rootRef.current,\r\n        context.contentRef.current,\r\n      );\r\n\r\n      return () => {\r\n        resizeObserverStore.unobserve(\r\n          context.rootRef.current,\r\n          context.contentRef.current,\r\n        );\r\n      };\r\n    }\r\n  }, [context.rootRef, context.contentRef]);\r\n\r\n  const multiplier = React.useMemo(() => {\r\n    if (!context.autoFill || !dimensions) return 1;\r\n\r\n    const { rootSize, contentSize } = dimensions;\r\n    if (contentSize === 0) return 1;\r\n\r\n    return contentSize < rootSize ? Math.ceil(rootSize / contentSize) : 1;\r\n  }, [context.autoFill, dimensions]);\r\n\r\n  const onMultipliedChildrenRender = React.useCallback(\r\n    (count: number) => {\r\n      return Array.from({ length: Math.max(0, count) }).map((_, i) => (\r\n        <React.Fragment key={i}>{children}</React.Fragment>\r\n      ));\r\n    },\r\n    [children],\r\n  );\r\n\r\n  const style = React.useMemo(\r\n    () => ({\r\n      ...styleProp,\r\n      animationDuration: \"var(--marquee-duration)\",\r\n      animationDelay: \"var(--marquee-delay)\",\r\n      animationIterationCount: \"var(--marquee-loop-count)\",\r\n      animationDirection: context.reverse ? \"reverse\" : \"normal\",\r\n    }),\r\n    [styleProp, context.reverse],\r\n  );\r\n\r\n  const ContentPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\r\n\r\n  return (\r\n    <>\r\n      <ContentPrimitive\r\n        data-orientation={context.orientation}\r\n        data-slot=\"marquee-content\"\r\n        {...contentProps}\r\n        style={style}\r\n        className={cn(\r\n          marqueeContentVariants({\r\n            side: context.side,\r\n            dir: context.dir,\r\n            pauseOnHover: context.pauseOnHover,\r\n            reverse: context.reverse,\r\n            className,\r\n          }),\r\n          isVertical && \"flex-col\",\r\n          isVertical\r\n            ? \"mb-(--marquee-gap)\"\r\n            : isRtl\r\n              ? \"ml-(--marquee-gap)\"\r\n              : \"mr-(--marquee-gap)\",\r\n        )}\r\n      >\r\n        <div\r\n          ref={composedRef}\r\n          className={cn(\r\n            \"flex shrink-0 gap-(--marquee-gap)\",\r\n            isVertical && \"flex-col\",\r\n          )}\r\n        >\r\n          {children}\r\n        </div>\r\n        {onMultipliedChildrenRender(multiplier - 1)}\r\n      </ContentPrimitive>\r\n      <ContentPrimitive\r\n        role=\"presentation\"\r\n        aria-hidden=\"true\"\r\n        {...contentProps}\r\n        style={style}\r\n        className={cn(\r\n          marqueeContentVariants({\r\n            side: context.side,\r\n            dir: context.dir,\r\n            pauseOnHover: context.pauseOnHover,\r\n            reverse: context.reverse,\r\n            className,\r\n          }),\r\n          isVertical && \"flex-col\",\r\n        )}\r\n      >\r\n        {onMultipliedChildrenRender(multiplier)}\r\n      </ContentPrimitive>\r\n    </>\r\n  );\r\n}\r\n\r\nfunction MarqueeItem(props: DivProps) {\r\n  const { className, asChild, ...itemProps } = props;\r\n\r\n  const ItemPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\r\n\r\n  return (\r\n    <ItemPrimitive\r\n      data-slot=\"marquee-item\"\r\n      {...itemProps}\r\n      className={cn(\"shrink-0\", className)}\r\n    />\r\n  );\r\n}\r\n\r\nconst marqueeEdgeVariants = cva(\"pointer-events-none absolute z-10\", {\r\n  variants: {\r\n    side: {\r\n      left: \"top-0 left-0 h-full bg-gradient-to-r from-background to-transparent\",\r\n      right:\r\n        \"top-0 right-0 h-full bg-gradient-to-l from-background to-transparent\",\r\n      top: \"top-0 left-0 w-full bg-gradient-to-b from-background to-transparent\",\r\n      bottom:\r\n        \"bottom-0 left-0 w-full bg-gradient-to-t from-background to-transparent\",\r\n    },\r\n    size: {\r\n      default: \"\",\r\n      sm: \"\",\r\n      lg: \"\",\r\n    },\r\n  },\r\n  compoundVariants: [\r\n    {\r\n      side: [\"left\", \"right\"],\r\n      size: \"default\",\r\n      className: \"w-1/4\",\r\n    },\r\n    {\r\n      side: [\"left\", \"right\"],\r\n      size: \"sm\",\r\n      className: \"w-1/6\",\r\n    },\r\n    {\r\n      side: [\"left\", \"right\"],\r\n      size: \"lg\",\r\n      className: \"w-1/3\",\r\n    },\r\n    {\r\n      side: [\"top\", \"bottom\"],\r\n      size: \"default\",\r\n      className: \"h-1/4\",\r\n    },\r\n    {\r\n      side: [\"top\", \"bottom\"],\r\n      size: \"sm\",\r\n      className: \"h-1/6\",\r\n    },\r\n    {\r\n      side: [\"top\", \"bottom\"],\r\n      size: \"lg\",\r\n      className: \"h-1/3\",\r\n    },\r\n  ],\r\n  defaultVariants: {\r\n    size: \"default\",\r\n  },\r\n});\r\n\r\ninterface MarqueeEdgeProps\r\n  extends VariantProps<typeof marqueeEdgeVariants>,\r\n    DivProps {}\r\n\r\nfunction MarqueeEdge(props: MarqueeEdgeProps) {\r\n  const { side, size, className, asChild, ...edgeProps } = props;\r\n\r\n  const EdgePrimitive = asChild ? SlotPrimitive.Slot : \"div\";\r\n\r\n  return (\r\n    <EdgePrimitive\r\n      data-size={size}\r\n      data-slot=\"marquee-edge\"\r\n      {...edgeProps}\r\n      className={cn(marqueeEdgeVariants({ side, size, className }))}\r\n    />\r\n  );\r\n}\r\n\r\nexport {\r\n  Marquee,\r\n  MarqueeContent,\r\n  MarqueeItem,\r\n  MarqueeEdge,\r\n  //\r\n  type MarqueeProps,\r\n};\r\n"
  },
  {
    "path": "src/components/ui/menubar.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Menubar as MenubarPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { CheckIcon, ChevronRightIcon } from \"lucide-react\"\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        \"bg-background flex h-8 items-center gap-0.5 rounded-none border p-1\",\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        \"hover:bg-muted aria-expanded:bg-muted flex items-center rounded-none px-1.5 py-[calc(--spacing(0.8))] text-xs font-medium outline-hidden select-none\",\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(\"bg-popover text-popover-foreground data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 z-50 min-w-36 origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-none shadow-md ring-1 duration-100\", className )}\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        \"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive! not-data-[variant=destructive]:focus:**:text-accent-foreground group/menubar-item relative flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction MenubarCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem> & {\n  inset?: boolean\n}) {\n  return (\n    <MenubarPrimitive.CheckboxItem\n      data-slot=\"menubar-checkbox-item\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-none py-2 pr-28 pl-8 text-xs outline-hidden select-none data-disabled:pointer-events-none data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4\">\n        <MenubarPrimitive.ItemIndicator>\n          <CheckIcon\n          />\n        </MenubarPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </MenubarPrimitive.CheckboxItem>\n  )\n}\n\nfunction MenubarRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: React.ComponentProps<typeof MenubarPrimitive.RadioItem> & {\n  inset?: boolean\n}) {\n  return (\n    <MenubarPrimitive.RadioItem\n      data-slot=\"menubar-radio-item\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-none py-2 pr-2 pl-8 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_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-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4\">\n        <MenubarPrimitive.ItemIndicator>\n          <CheckIcon\n          />\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(\"px-2 py-2 text-xs data-inset:pl-8\", className)}\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(\"bg-border -mx-1 my-1 h-px\", 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        \"text-muted-foreground group-focus/menubar-item:text-accent-foreground ml-auto text-xs tracking-widest\",\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        \"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-none select-none data-inset:pl-8 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto size-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(\"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 z-50 min-w-32 origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-none shadow-lg ring-1 duration-100\", className )}\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": "src/components/ui/multi-select.tsx",
    "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { CheckIcon, ChevronDown, XCircle, XIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\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 {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\n\nconst multiSelectVariants = cva(\n  \"m-1 transition ease-in-out delay-150 hover:-translate-y-1 duration-300\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"border-foreground/10 text-foreground bg-card hover:bg-card/80\",\n        secondary:\n          \"border-foreground/10 bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        destructive:\n          \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n        inverted: \"inverted\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\ninterface MultiSelectProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof multiSelectVariants> {\n  /**\n   * An array of option objects to be displayed in the multi-select component.\n   * Each option object has a label, value, and an optional icon.\n   */\n  options: {\n    /** The text to display for the option. */\n    label: string;\n    /** The unique value associated with the option. */\n    value: string;\n    /** Optional icon component to display alongside the option. */\n    icon?: React.ComponentType<{ className?: string }>;\n  }[];\n\n  /**\n   * Callback function triggered when the selected values change.\n   * Receives an array of the new selected values.\n   */\n  onValueChange: (value: string[]) => void;\n\n  /** The default selected values when the component mounts. */\n  defaultValue?: string[];\n\n  /**\n   * Placeholder text to be displayed when no values are selected.\n   * Optional, defaults to \"Select options\".\n   */\n  placeholder?: string;\n\n  /**\n   * Animation duration in seconds for the visual effects (e.g., bouncing badges).\n   * Optional, defaults to 0 (no animation).\n   */\n  animation?: number;\n\n  /**\n   * Maximum number of items to display. Extra selected items will be summarized.\n   * Optional, defaults to 3.\n   */\n  maxCount?: number;\n\n  /**\n   * The modality of the popover. When set to true, interaction with outside elements\n   * will be disabled and only popover content will be visible to screen readers.\n   * Optional, defaults to false.\n   */\n  modalPopover?: boolean;\n\n  /**\n   * If true, renders the multi-select component as a child of another component.\n   * Optional, defaults to false.\n   */\n  asChild?: boolean;\n\n  /**\n   * Additional class names to apply custom styles to the multi-select component.\n   * Optional, can be used to add custom styles.\n   */\n  className?: string;\n}\n\nexport const MultiSelect = React.forwardRef<\n  HTMLButtonElement,\n  MultiSelectProps\n>(\n  (\n    {\n      options,\n      onValueChange,\n      variant,\n      defaultValue = [],\n      placeholder = \"Select options\",\n      animation = 0,\n      maxCount = 3,\n      modalPopover = false,\n      asChild = false,\n      className,\n      ...props\n    },\n    ref,\n  ) => {\n    const [selectedValues, setSelectedValues] =\n      React.useState<string[]>(defaultValue);\n    const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);\n    const [isAnimating, setIsAnimating] = React.useState(false);\n\n    const handleInputKeyDown = (\n      event: React.KeyboardEvent<HTMLInputElement>,\n    ) => {\n      if (event.key === \"Enter\") {\n        setIsPopoverOpen(true);\n      } else if (event.key === \"Backspace\" && !event.currentTarget.value) {\n        const newSelectedValues = [...selectedValues];\n        newSelectedValues.pop();\n        setSelectedValues(newSelectedValues);\n        onValueChange(newSelectedValues);\n      }\n    };\n\n    const toggleOption = (option: string) => {\n      const newSelectedValues = selectedValues.includes(option)\n        ? selectedValues.filter((value) => value !== option)\n        : [...selectedValues, option];\n      setSelectedValues(newSelectedValues);\n      onValueChange(newSelectedValues);\n    };\n\n    const handleClear = () => {\n      setSelectedValues([]);\n      onValueChange([]);\n    };\n\n    const handleTogglePopover = () => {\n      setIsPopoverOpen((prev) => !prev);\n    };\n\n    const clearExtraOptions = () => {\n      const newSelectedValues = selectedValues.slice(0, maxCount);\n      setSelectedValues(newSelectedValues);\n      onValueChange(newSelectedValues);\n    };\n\n    const toggleAll = () => {\n      if (selectedValues.length === options.length) {\n        handleClear();\n      } else {\n        const allValues = options.map((option) => option.value);\n        setSelectedValues(allValues);\n        onValueChange(allValues);\n      }\n    };\n\n    return (\n      <Popover\n        open={isPopoverOpen}\n        onOpenChange={setIsPopoverOpen}\n        modal={modalPopover}\n      >\n        <PopoverTrigger asChild>\n          <Button\n            ref={ref}\n            {...props}\n            onClick={handleTogglePopover}\n            variant=\"outline\"\n            className={cn(\"h-8 w-full\", className)}\n          >\n            {selectedValues.length > 0 ? (\n              <div className=\"flex justify-between items-center w-full\">\n                <div className=\"flex flex-wrap items-center\">\n                  {selectedValues.slice(0, maxCount).map((value) => {\n                    const option = options.find((o) => o.value === value);\n                    const IconComponent = option?.icon;\n                    return (\n                      <Badge\n                        key={value}\n                        className={cn(\n                          isAnimating ? \"animate-bounce\" : \"\",\n                          multiSelectVariants({ variant }),\n                        )}\n                        style={{ animationDuration: `${animation}s` }}\n                      >\n                        {IconComponent && (\n                          <IconComponent className=\"h-4 w-4 mr-2\" />\n                        )}\n                        {option?.label}\n                        <XCircle\n                          className=\"ml-2 h-4 w-4 cursor-pointer\"\n                          onClick={(event) => {\n                            event.stopPropagation();\n                            toggleOption(value);\n                          }}\n                        />\n                      </Badge>\n                    );\n                  })}\n                  {selectedValues.length > maxCount && (\n                    <Badge\n                      className={cn(\n                        \"bg-transparent text-foreground border-foreground/1 hover:bg-transparent\",\n                        isAnimating ? \"animate-bounce\" : \"\",\n                        multiSelectVariants({ variant }),\n                      )}\n                      style={{ animationDuration: `${animation}s` }}\n                    >\n                      {`+ ${selectedValues.length - maxCount} more`}\n                      <XCircle\n                        className=\"ml-2 h-4 w-4 cursor-pointer\"\n                        onClick={(event) => {\n                          event.stopPropagation();\n                          clearExtraOptions();\n                        }}\n                      />\n                    </Badge>\n                  )}\n                </div>\n                <div className=\"flex items-center justify-between\">\n                  <XIcon\n                    className=\"h-4 mx-2 cursor-pointer text-muted-foreground\"\n                    onClick={(event) => {\n                      event.stopPropagation();\n                      handleClear();\n                    }}\n                  />\n                  <Separator\n                    orientation=\"vertical\"\n                    className=\"flex min-h-6 h-full\"\n                  />\n                  <ChevronDown className=\"h-4 mx-2 cursor-pointer text-muted-foreground\" />\n                </div>\n              </div>\n            ) : (\n              <div className=\"flex items-center justify-between w-full mx-auto\">\n                <span className=\"text-sm text-muted-foreground mx-3\">\n                  {placeholder}\n                </span>\n                <ChevronDown className=\"h-4 cursor-pointer text-muted-foreground mx-2\" />\n              </div>\n            )}\n          </Button>\n        </PopoverTrigger>\n        <PopoverContent\n          className=\"w-auto p-0\"\n          align=\"center\"\n          onEscapeKeyDown={() => setIsPopoverOpen(false)}\n        >\n          <Command>\n            <CommandInput\n              placeholder=\"Search...\"\n              onKeyDown={handleInputKeyDown}\n            />\n            <CommandList>\n              <CommandEmpty>No results found.</CommandEmpty>\n              <CommandGroup>\n                <CommandItem\n                  key=\"all\"\n                  onSelect={toggleAll}\n                  className=\"cursor-pointer\"\n                >\n                  <div\n                    className={cn(\n                      \"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary\",\n                      selectedValues.length === options.length\n                        ? \"bg-primary text-primary-foreground\"\n                        : \"opacity-50 [&_svg]:invisible\",\n                    )}\n                  >\n                    <CheckIcon className=\"h-4 w-4\" />\n                  </div>\n                  <span>(Select All)</span>\n                </CommandItem>\n                {options.map((option) => {\n                  const isSelected = selectedValues.includes(option.value);\n                  return (\n                    <CommandItem\n                      key={option.value}\n                      onSelect={() => toggleOption(option.value)}\n                      className=\"cursor-pointer\"\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                        <CheckIcon className=\"h-4 w-4\" />\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                    </CommandItem>\n                  );\n                })}\n              </CommandGroup>\n              <CommandSeparator />\n              <CommandGroup>\n                <div className=\"flex items-center justify-between\">\n                  {selectedValues.length > 0 && (\n                    <>\n                      <CommandItem\n                        onSelect={handleClear}\n                        className=\"flex-1 justify-center cursor-pointer\"\n                      >\n                        Clear\n                      </CommandItem>\n                      <Separator\n                        orientation=\"vertical\"\n                        className=\"flex min-h-6 h-full\"\n                      />\n                    </>\n                  )}\n                  <CommandItem\n                    onSelect={() => setIsPopoverOpen(false)}\n                    className=\"flex-1 justify-center cursor-pointer max-w-full\"\n                  >\n                    Close\n                  </CommandItem>\n                </div>\n              </CommandGroup>\n            </CommandList>\n          </Command>\n        </PopoverContent>\n      </Popover>\n    );\n  },\n);\n\nMultiSelect.displayName = \"MultiSelect\";\n"
  },
  {
    "path": "src/components/ui/native-select.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChevronDownIcon } from \"lucide-react\"\n\ntype NativeSelectProps = Omit<React.ComponentProps<\"select\">, \"size\"> & {\n  size?: \"sm\" | \"default\"\n}\n\nfunction NativeSelect({\n  className,\n  size = \"default\",\n  ...props\n}: NativeSelectProps) {\n  return (\n    <div\n      className={cn(\n        \"group/native-select relative w-fit has-[select:disabled]:opacity-50\",\n        className\n      )}\n      data-slot=\"native-select-wrapper\"\n      data-size={size}\n    >\n      <select\n        data-slot=\"native-select\"\n        data-size={size}\n        className=\"border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 w-full min-w-0 appearance-none rounded-none border bg-transparent py-1 pr-8 pl-2.5 text-xs transition-colors outline-none select-none focus-visible:ring-1 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:ring-1 data-[size=sm]:h-7 data-[size=sm]:rounded-none data-[size=sm]:py-0.5\"\n        {...props}\n      />\n      <ChevronDownIcon className=\"text-muted-foreground pointer-events-none absolute top-1/2 right-2.5 size-4 -translate-y-1/2 select-none\" aria-hidden=\"true\" data-slot=\"native-select-icon\" />\n    </div>\n  )\n}\n\nfunction NativeSelectOption({ ...props }: React.ComponentProps<\"option\">) {\n  return <option data-slot=\"native-select-option\" {...props} />\n}\n\nfunction NativeSelectOptGroup({\n  className,\n  ...props\n}: React.ComponentProps<\"optgroup\">) {\n  return (\n    <optgroup\n      data-slot=\"native-select-optgroup\"\n      className={cn(className)}\n      {...props}\n    />\n  )\n}\n\nexport { NativeSelect, NativeSelectOptGroup, NativeSelectOption }\n"
  },
  {
    "path": "src/components/ui/navigation-menu.tsx",
    "content": "import * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { NavigationMenu as NavigationMenuPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChevronDownIcon } from \"lucide-react\"\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-0\",\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  \"bg-background hover:bg-muted focus:bg-muted data-open:hover:bg-muted data-open:focus:bg-muted data-open:bg-muted/50 focus-visible:ring-ring/50 data-popup-open:bg-muted/50 data-popup-open:hover:bg-muted rounded-none px-2.5 py-1.5 text-xs font-medium transition-all focus-visible:ring-1 focus-visible:outline-1 disabled:opacity-50 group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center disabled:pointer-events-none outline-none\"\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 className=\"relative top-px ml-1 size-3 transition duration-300 group-data-open/navigation-menu-trigger:rotate-180 group-data-popup-open/navigation-menu-trigger:rotate-180\" aria-hidden=\"true\" />\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        \"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out 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 group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:ring-foreground/10 top-0 left-0 w-full p-1 ease-[cubic-bezier(0.22,1,0.36,1)] 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-none group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:duration-300 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none md:absolute md:w-auto\",\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          \"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:zoom-out-95 data-open:zoom-in-90 ring-foreground/10 origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded-none shadow ring-1 duration-100 md:w-(--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        \"data-active:focus:bg-muted data-active:hover:bg-muted data-active:bg-muted/50 focus-visible:ring-ring/50 hover:bg-muted focus:bg-muted flex items-center gap-2 rounded-none p-2 text-xs transition-all outline-none focus-visible:ring-1 focus-visible:outline-1 in-data-[slot=navigation-menu-content]:rounded-none [&_svg:not([class*='size-'])]:size-4\",\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        \"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-1 flex h-1.5 items-end justify-center overflow-hidden\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-none 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": "src/components/ui/pagination.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from \"lucide-react\"\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 items-center gap-0.5\", 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    <Button\n      asChild\n      variant={isActive ? \"outline\" : \"ghost\"}\n      size={size}\n      className={cn(className)}\n    >\n      <a\n        aria-current={isActive ? \"page\" : undefined}\n        data-slot=\"pagination-link\"\n        data-active={isActive}\n        {...props}\n      />\n    </Button>\n  )\n}\n\nfunction PaginationPrevious({\n  className,\n  text = \"Previous\",\n  ...props\n}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {\n  return (\n    <PaginationLink\n      aria-label=\"Go to previous page\"\n      size=\"default\"\n      className={cn(\"pl-1.5!\", className)}\n      {...props}\n    >\n      <ChevronLeftIcon data-icon=\"inline-start\" />\n      <span className=\"hidden sm:block\">{text}</span>\n    </PaginationLink>\n  )\n}\n\nfunction PaginationNext({\n  className,\n  text = \"Next\",\n  ...props\n}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {\n  return (\n    <PaginationLink\n      aria-label=\"Go to next page\"\n      size=\"default\"\n      className={cn(\"pr-1.5!\", className)}\n      {...props}\n    >\n      <span className=\"hidden sm:block\">{text}</span>\n      <ChevronRightIcon data-icon=\"inline-end\" />\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(\n        \"flex size-8 items-center justify-center [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <MoreHorizontalIcon\n      />\n      <span className=\"sr-only\">More pages</span>\n    </span>\n  )\n}\n\nexport {\n  Pagination,\n  PaginationContent,\n  PaginationEllipsis,\n  PaginationItem,\n  PaginationLink,\n  PaginationNext,\n  PaginationPrevious,\n}\n"
  },
  {
    "path": "src/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          \"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-2.5 rounded-none p-2.5 text-xs shadow-md ring-1 outline-hidden duration-100\",\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-xs\", 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(\"text-sm 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 text-xs/relaxed\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Popover,\n  PopoverAnchor,\n  PopoverContent,\n  PopoverDescription,\n  PopoverHeader,\n  PopoverTitle,\n  PopoverTrigger,\n}\n"
  },
  {
    "path": "src/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        \"bg-muted relative flex h-1 w-full items-center overflow-x-hidden rounded-none\",\n        className\n      )}\n      {...props}\n    >\n      <ProgressPrimitive.Indicator\n        data-slot=\"progress-indicator\"\n        className=\"bg-primary size-full flex-1 transition-all\"\n        style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n      />\n    </ProgressPrimitive.Root>\n  )\n}\n\nexport { Progress }\n"
  },
  {
    "path": "src/components/ui/radio-group.tsx",
    "content": "\"use client\"\n\nimport * as React from \"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 w-full gap-2\", 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        \"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:aria-invalid:border-destructive/50 group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3\",\n        className\n      )}\n      {...props}\n    >\n      <RadioGroupPrimitive.Indicator\n        data-slot=\"radio-group-indicator\"\n        className=\"flex size-4 items-center justify-center\"\n      >\n        <span className=\"bg-primary-foreground absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full\" />\n      </RadioGroupPrimitive.Indicator>\n    </RadioGroupPrimitive.Item>\n  )\n}\n\nexport { RadioGroup, RadioGroupItem }\n"
  },
  {
    "path": "src/components/ui/resizable.tsx",
    "content": "\"use client\"\n\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        \"bg-border focus-visible:ring-ring ring-offset-background relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-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=\"bg-border z-10 flex h-6 w-1 shrink-0 rounded-none\" />\n      )}\n    </ResizablePrimitive.Separator>\n  )\n}\n\nexport { ResizableHandle, ResizablePanel, ResizablePanelGroup }\n"
  },
  {
    "path": "src/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=\"focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] 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      data-orientation={orientation}\n      orientation={orientation}\n      className={cn(\n        \"flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent\",\n        className\n      )}\n      {...props}\n    >\n      <ScrollAreaPrimitive.ScrollAreaThumb\n        data-slot=\"scroll-area-thumb\"\n        className=\"bg-border relative flex-1 rounded-none\"\n      />\n    </ScrollAreaPrimitive.ScrollAreaScrollbar>\n  )\n}\n\nexport { ScrollArea, ScrollBar }\n"
  },
  {
    "path": "src/components/ui/select.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Select as SelectPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChevronDownIcon, CheckIcon, ChevronUpIcon } from \"lucide-react\"\n\nfunction Select({\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Root>) {\n  return <SelectPrimitive.Root data-slot=\"select\" {...props} />\n}\n\nfunction SelectGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Group>) {\n  return (\n    <SelectPrimitive.Group\n      data-slot=\"select-group\"\n      className={cn(\"scroll-my-1\", className)}\n      {...props}\n    />\n  )\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        \"border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 flex w-fit items-center justify-between gap-1.5 rounded-none border bg-transparent py-2 pr-2 pl-2.5 text-xs whitespace-nowrap transition-colors outline-none select-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-1 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-none *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon asChild>\n        <ChevronDownIcon className=\"text-muted-foreground pointer-events-none size-4\" />\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        data-align-trigger={position === \"item-aligned\"}\n        className={cn(\"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-none shadow-md ring-1 duration-100 data-[align-trigger=true]:animate-none\", position ===\"popper\"&&\"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\", className )}\n        position={position}\n        align={align}\n        {...props}\n      >\n        <SelectScrollUpButton />\n        <SelectPrimitive.Viewport\n          data-position={position}\n          className={cn(\n            \"data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)\",\n            position === \"popper\" && \"\"\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(\"text-muted-foreground px-2 py-2 text-xs\", 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        \"focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\">\n        <SelectPrimitive.ItemIndicator>\n          <CheckIcon className=\"pointer-events-none\" />\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(\"bg-border pointer-events-none -mx-1 h-px\", 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        \"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronUpIcon\n      />\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        \"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronDownIcon\n      />\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": "src/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        \"bg-border shrink-0 data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Separator }\n"
  },
  {
    "path": "src/components/ui/sheet.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Dialog as SheetPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { XIcon } from \"lucide-react\"\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        \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 text-xs/relaxed duration-100 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs\",\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        data-side={side}\n        className={cn(\n          \"bg-background data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col bg-clip-padding text-xs/relaxed shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close data-slot=\"sheet-close\" asChild>\n            <Button\n              variant=\"ghost\"\n              className=\"absolute top-3 right-3\"\n              size=\"icon-sm\"\n            >\n              <XIcon\n              />\n              <span className=\"sr-only\">Close</span>\n            </Button>\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-0.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(\"text-foreground text-sm font-medium\", 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-muted-foreground text-xs/relaxed\", 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": "src/components/ui/sidebar.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\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 {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"@/components/ui/sheet\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { PanelLeftIcon } from \"lucide-react\"\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 SidebarContextProps = {\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<SidebarContextProps | 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\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  defaultOpen?: boolean\n  open?: boolean\n  onOpenChange?: (open: boolean) => void\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 (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\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<SidebarContextProps>(\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      <div\n        data-slot=\"sidebar-wrapper\"\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        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  )\n}\n\nfunction Sidebar({\n  side = \"left\",\n  variant = \"sidebar\",\n  collapsible = \"offcanvas\",\n  className,\n  children,\n  dir,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  side?: \"left\" | \"right\"\n  variant?: \"sidebar\" | \"floating\" | \"inset\"\n  collapsible?: \"offcanvas\" | \"icon\" | \"none\"\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar()\n\n  if (collapsible === \"none\") {\n    return (\n      <div\n        data-slot=\"sidebar\"\n        className={cn(\n          \"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col\",\n          className\n        )}\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          dir={dir}\n          data-sidebar=\"sidebar\"\n          data-slot=\"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          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    )\n  }\n\n  return (\n    <div\n      className=\"group peer text-sidebar-foreground hidden md:block\"\n      data-state={state}\n      data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n      data-variant={variant}\n      data-side={side}\n      data-slot=\"sidebar\"\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        data-slot=\"sidebar-gap\"\n        className={cn(\n          \"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear\",\n          \"group-data-[collapsible=offcanvas]:w-0\",\n          \"group-data-[side=right]:rotate-180\",\n          variant === \"floating\" || variant === \"inset\"\n            ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]\"\n            : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\"\n        )}\n      />\n      <div\n        data-slot=\"sidebar-container\"\n        data-side={side}\n        className={cn(\n          \"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex\",\n          // Adjust the padding for floating and inset variants.\n          variant === \"floating\" || variant === \"inset\"\n            ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]\"\n            : \"group-data-[collapsible=icon]:w-(--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          data-slot=\"sidebar-inner\"\n          className=\"bg-sidebar group-data-[variant=floating]:ring-sidebar-border flex size-full flex-col group-data-[variant=floating]:rounded-none group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar()\n\n  return (\n    <Button\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      variant=\"ghost\"\n      size=\"icon-sm\"\n      className={cn(className)}\n      onClick={(event) => {\n        onClick?.(event)\n        toggleSidebar()\n      }}\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  )\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<\"button\">) {\n  const { toggleSidebar } = useSidebar()\n\n  return (\n    <button\n      data-sidebar=\"rail\"\n      data-slot=\"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 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2\",\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\nfunction SidebarInset({ className, ...props }: React.ComponentProps<\"main\">) {\n  return (\n    <main\n      data-slot=\"sidebar-inset\"\n      className={cn(\n        \"bg-background relative flex w-full flex-1 flex-col md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-none md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      data-slot=\"sidebar-input\"\n      data-sidebar=\"input\"\n      className={cn(\"bg-background h-8 w-full shadow-none\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-header\"\n      data-sidebar=\"header\"\n      className={cn(\"flex flex-col gap-2 p-2\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-footer\"\n      data-sidebar=\"footer\"\n      className={cn(\"flex flex-col gap-2 p-2\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"sidebar-separator\"\n      data-sidebar=\"separator\"\n      className={cn(\"bg-sidebar-border mx-2 w-auto\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-content\"\n      data-sidebar=\"content\"\n      className={cn(\n        \"no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-group\"\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\nfunction SidebarGroupLabel({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"div\"> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : \"div\"\n\n  return (\n    <Comp\n      data-slot=\"sidebar-group-label\"\n      data-sidebar=\"group-label\"\n      className={cn(\n        \"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-none px-2 text-xs outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarGroupAction({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : \"button\"\n\n  return (\n    <Comp\n      data-slot=\"sidebar-group-action\"\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-none p-0 outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-group-content\"\n      data-sidebar=\"group-content\"\n      className={cn(\"w-full text-xs\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu\"\n      data-sidebar=\"menu\"\n      className={cn(\"flex w-full min-w-0 flex-col gap-0\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-item\"\n      data-sidebar=\"menu-item\"\n      className={cn(\"group/menu-item relative\", className)}\n      {...props}\n    />\n  )\n}\n\nconst sidebarMenuButtonVariants = cva(\n  \"ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-none p-2 text-left text-xs transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button 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  {\n    variants: {\n      variant: {\n        default: \"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\n        outline:\n          \"bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]\",\n      },\n      size: {\n        default: \"h-8 text-xs\",\n        sm: \"h-7 text-xs\",\n        lg: \"h-12 text-xs group-data-[collapsible=icon]:p-0!\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction SidebarMenuButton({\n  asChild = false,\n  isActive = false,\n  variant = \"default\",\n  size = \"default\",\n  tooltip,\n  className,\n  ...props\n}: React.ComponentProps<\"button\"> & {\n  asChild?: boolean\n  isActive?: boolean\n  tooltip?: string | React.ComponentProps<typeof TooltipContent>\n} & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const Comp = asChild ? Slot.Root : \"button\"\n  const { isMobile, state } = useSidebar()\n\n  const button = (\n    <Comp\n      data-slot=\"sidebar-menu-button\"\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\nfunction SidebarMenuAction({\n  className,\n  asChild = false,\n  showOnHover = false,\n  ...props\n}: React.ComponentProps<\"button\"> & {\n  asChild?: boolean\n  showOnHover?: boolean\n}) {\n  const Comp = asChild ? Slot.Root : \"button\"\n\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-action\"\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-none p-0 outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0\",\n        showOnHover &&\n          \"peer-data-active/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 md:opacity-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-menu-badge\"\n      data-sidebar=\"menu-badge\"\n      className={cn(\n        \"text-sidebar-foreground peer-hover/menu-button:text-sidebar-accent-foreground peer-data-active/menu-button:text-sidebar-accent-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-none px-1 text-xs font-medium tabular-nums select-none group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  showIcon?: boolean\n}) {\n  // Random width between 50 to 90%.\n  const [width] = React.useState(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`\n  })\n\n  return (\n    <div\n      data-slot=\"sidebar-menu-skeleton\"\n      data-sidebar=\"menu-skeleton\"\n      className={cn(\"flex h-8 items-center gap-2 rounded-none px-2\", className)}\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-none\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\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}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu-sub\"\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 group-data-[collapsible=icon]:hidden\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-sub-item\"\n      data-sidebar=\"menu-sub-item\"\n      className={cn(\"group/menu-sub-item relative\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SidebarMenuSubButton({\n  asChild = false,\n  size = \"md\",\n  isActive = false,\n  className,\n  ...props\n}: React.ComponentProps<\"a\"> & {\n  asChild?: boolean\n  size?: \"sm\" | \"md\"\n  isActive?: boolean\n}) {\n  const Comp = asChild ? Slot.Root : \"a\"\n\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-sub-button\"\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 data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-none px-2 outline-hidden group-data-[collapsible=icon]:hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-xs data-[size=sm]:text-xs [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\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": "src/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(\"bg-muted animate-pulse rounded-none\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Skeleton }\n"
  },
  {
    "path": "src/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-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col\",\n        className\n      )}\n      {...props}\n    >\n      <SliderPrimitive.Track\n        data-slot=\"slider-track\"\n        className=\"bg-muted relative grow overflow-hidden rounded-none data-horizontal:h-1 data-horizontal:w-full data-vertical:h-full data-vertical:w-1\"\n      >\n        <SliderPrimitive.Range\n          data-slot=\"slider-range\"\n          className=\"bg-primary absolute select-none data-horizontal:h-full data-vertical:w-full\"\n        />\n      </SliderPrimitive.Track>\n      {Array.from({ length: _values.length }, (_, index) => (\n        <SliderPrimitive.Thumb\n          data-slot=\"slider-thumb\"\n          key={index}\n          className=\"border-ring ring-ring/50 relative block size-3 shrink-0 rounded-none border bg-white transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-1 focus-visible:ring-1 focus-visible:outline-hidden active:ring-1 disabled:pointer-events-none disabled:opacity-50\"\n        />\n      ))}\n    </SliderPrimitive.Root>\n  )\n}\n\nexport { Slider }\n"
  },
  {
    "path": "src/components/ui/sonner.tsx",
    "content": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\"\nimport { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from \"lucide-react\"\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: (\n          <CircleCheckIcon className=\"size-4\" />\n        ),\n        info: (\n          <InfoIcon className=\"size-4\" />\n        ),\n        warning: (\n          <TriangleAlertIcon className=\"size-4\" />\n        ),\n        error: (\n          <OctagonXIcon className=\"size-4\" />\n        ),\n        loading: (\n          <Loader2Icon className=\"size-4 animate-spin\" />\n        ),\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      toastOptions={{\n        classNames: {\n          toast: \"cn-toast\",\n        },\n      }}\n      {...props}\n    />\n  )\n}\n\nexport { Toaster }\n"
  },
  {
    "path": "src/components/ui/spinner.tsx",
    "content": "import { cn } from \"@/lib/utils\"\nimport { Loader2Icon } from \"lucide-react\"\n\nfunction Spinner({ className, ...props }: React.ComponentProps<\"svg\">) {\n  return (\n    <Loader2Icon role=\"status\" aria-label=\"Loading\" className={cn(\"size-4 animate-spin\", className)} {...props} />\n  )\n}\n\nexport { Spinner }\n"
  },
  {
    "path": "src/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        \"data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-1 aria-invalid:ring-1 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px]\",\n        className\n      )}\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        data-slot=\"switch-thumb\"\n        className=\"bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0\"\n      />\n    </SwitchPrimitive.Root>\n  )\n}\n\nexport { Switch }\n"
  },
  {
    "path": "src/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-xs\", 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        \"bg-muted/50 border-t 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        \"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors\",\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        \"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0\",\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\",\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(\"text-muted-foreground mt-4 text-xs\", 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": "src/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      className={cn(\n        \"group/tabs flex gap-2 data-horizontal:flex-col\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst tabsListVariants = cva(\n  \"rounded-none p-[3px] group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col\",\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        \"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-none border border-transparent px-1.5 py-0.5 text-xs font-medium whitespace-nowrap transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-vertical/tabs:py-[calc(--spacing(1.25))] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        \"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent\",\n        \"data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground\",\n        \"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100\",\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(\"flex-1 text-xs/relaxed outline-none\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }\n"
  },
  {
    "path": "src/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        \"border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full rounded-none border bg-transparent px-2.5 py-2 text-xs transition-colors outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-1 md:text-xs\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Textarea }\n"
  },
  {
    "path": "src/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    orientation?: \"horizontal\" | \"vertical\"\n  }\n>({\n  size: \"default\",\n  variant: \"default\",\n  spacing: 0,\n  orientation: \"horizontal\",\n})\n\nfunction ToggleGroup({\n  className,\n  variant,\n  size,\n  spacing = 0,\n  orientation = \"horizontal\",\n  children,\n  ...props\n}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number\n    orientation?: \"horizontal\" | \"vertical\"\n  }) {\n  return (\n    <ToggleGroupPrimitive.Root\n      data-slot=\"toggle-group\"\n      data-variant={variant}\n      data-size={size}\n      data-spacing={spacing}\n      data-orientation={orientation}\n      style={{ \"--gap\": spacing } as React.CSSProperties}\n      className={cn(\n        \"group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-none data-vertical:flex-col data-vertical:items-stretch data-[size=sm]:rounded-none\",\n        className\n      )}\n      {...props}\n    >\n      <ToggleGroupContext.Provider\n        value={{ variant, size, spacing, orientation }}\n      >\n        {children}\n      </ToggleGroupContext.Provider>\n    </ToggleGroupPrimitive.Root>\n  )\n}\n\nfunction ToggleGroupItem({\n  className,\n  children,\n  variant = \"default\",\n  size = \"default\",\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        \"shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-none group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-none group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-none group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-none group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t\",\n        toggleVariants({\n          variant: context.variant || variant,\n          size: context.size || size,\n        }),\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </ToggleGroupPrimitive.Item>\n  )\n}\n\nexport { ToggleGroup, ToggleGroupItem }\n"
  },
  {
    "path": "src/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  \"hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[state=on]:bg-muted gap-1 rounded-none text-xs font-medium transition-all [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-muted inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        outline: \"border-input hover:bg-muted border bg-transparent\",\n      },\n      size: {\n        default: \"h-8 min-w-8 px-2\",\n        sm: \"h-7 min-w-7 rounded-none px-1.5\",\n        lg: \"h-9 min-w-9 px-2.5\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Toggle({\n  className,\n  variant = \"default\",\n  size = \"default\",\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": "src/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          \"data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-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 bg-foreground text-background z-50 w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) rounded-none px-3 py-1.5 text-xs\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <TooltipPrimitive.Arrow className=\"bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none\" />\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  )\n}\n\nexport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }\n"
  },
  {
    "path": "src/hooks/use-bookmark.ts",
    "content": "import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport function useBookmarks() {\n  const [bookmarkedItems, setBookmarkedItems] = useState<string[]>([]);\n  const [isLoading, setIsLoading] = useState(true);\n  const [error, setError] = useState<string | null>(null);\n  const lastClickedRef = useRef<{ id: string; timestamp: number } | null>(null);\n\n  useEffect(() => {\n    try {\n      const storedBookmarks = localStorage.getItem(\"bookmarkedItems\");\n      if (storedBookmarks) {\n        const parsed = JSON.parse(storedBookmarks);\n        if (\n          Array.isArray(parsed) &&\n          parsed.every((item) => typeof item === \"string\")\n        ) {\n          setBookmarkedItems(parsed);\n        }\n      }\n    } catch (err) {\n      console.error(\"Failed to load bookmarks:\", err);\n    } finally {\n      setIsLoading(false);\n    }\n  }, []);\n\n  const toggleBookmark = useCallback(\n    (id: string) => {\n      if (isLoading) return;\n\n      const now = Date.now();\n      if (\n        lastClickedRef.current &&\n        lastClickedRef.current.id === id &&\n        now - lastClickedRef.current.timestamp < 300\n      ) {\n        return;\n      }\n      lastClickedRef.current = { id, timestamp: now };\n\n      setBookmarkedItems((prevBookmarks) => {\n        const isCurrentlyBookmarked = prevBookmarks.includes(id);\n        const newBookmarks = isCurrentlyBookmarked\n          ? prevBookmarks.filter((bookmarkId) => bookmarkId !== id)\n          : [...prevBookmarks, id];\n\n        try {\n          localStorage.setItem(\"bookmarkedItems\", JSON.stringify(newBookmarks));\n          setError(null);\n        } catch (err) {\n          setError(\"Failed to save bookmark\");\n          return prevBookmarks;\n        }\n\n        return newBookmarks;\n      });\n    },\n    [isLoading],\n  );\n\n  const clearBookmarks = useCallback(() => {\n    setBookmarkedItems([]);\n    try {\n      localStorage.removeItem(\"bookmarkedItems\");\n    } catch (err) {\n      setError(\"Failed to clear bookmarks\");\n    }\n  }, []);\n\n  const isBookmarked = useCallback(\n    (id: string) => {\n      return bookmarkedItems.includes(id);\n    },\n    [bookmarkedItems],\n  );\n\n  return {\n    bookmarkedItems,\n    toggleBookmark,\n    clearBookmarks,\n    isBookmarked,\n    isLoading,\n    error,\n  };\n}\n"
  },
  {
    "path": "src/hooks/use-categories.ts",
    "content": "\"use client\";\n\nimport { categoryNameToSlug } from \"@/lib/slugs\";\nimport { useEffect, useState } from \"react\";\nimport { fetchAndParseReadme, Resource } from \"./use-readme\";\n\nexport interface Category {\n  title: string;\n  slug: string;\n  items: Resource[];\n  description: string;\n}\n\nconst CATEGORY_DESCRIPTIONS: Record<string, string> = {\n  \"Libs and Components\":\n    \"Essential libraries and reusable components built with shadcn/ui\",\n  Registries: \"Component registries and collections for shadcn/ui\",\n  \"Plugins and Extensions\":\n    \"Tools and extensions that enhance your shadcn/ui workflow\",\n  \"Colors and Customizations\":\n    \"Themes, color palettes, and customization utilities\",\n  Animations: \"Animation libraries and motion components for shadcn/ui\",\n  Tools: \"Development tools, generators, and utilities for shadcn/ui projects\",\n  \"Websites and Portfolios Inspirations\":\n    \"Real-world examples and inspiration for your projects\",\n  Platforms: \"Platforms and services that integrate with shadcn/ui\",\n  Ports: \"Ports of shadcn/ui to other frameworks and technologies\",\n  \"Design System\": \"Complete design systems and component libraries\",\n  \"Boilerplates / Templates\":\n    \"Starter templates and boilerplates for quick project setup\",\n};\n\nexport function useCategories() {\n  const [categories, setCategories] = useState<Category[]>([]);\n  const [isLoading, setIsLoading] = useState(true);\n  const [error, setError] = useState<string | null>(null);\n\n  useEffect(() => {\n    async function fetchData() {\n      try {\n        setIsLoading(true);\n        setError(null);\n\n        const fetchedResources = await fetchAndParseReadme();\n\n        const EXCLUDED_CATEGORIES = [\"Star History\", \"Contributors\"];\n\n        const groupedCategories = fetchedResources.reduce(\n          (acc, resource) => {\n            if (!EXCLUDED_CATEGORIES.includes(resource.category)) {\n              if (!acc[resource.category]) {\n                acc[resource.category] = [];\n              }\n              acc[resource.category].push(resource);\n            }\n            return acc;\n          },\n          {} as Record<string, Resource[]>,\n        );\n\n        const formattedCategories = Object.entries(groupedCategories).map(\n          ([title, items]) => ({\n            title,\n            slug: categoryNameToSlug(title),\n            items,\n            description:\n              CATEGORY_DESCRIPTIONS[title] ||\n              \"A collection of shadcn/ui related resources\",\n          }),\n        );\n\n        setCategories(formattedCategories);\n      } catch (error) {\n        console.error(\"Error fetching README:\", error);\n        setError(\"Failed to load categories. Please try again later.\");\n      } finally {\n        setIsLoading(false);\n      }\n    }\n\n    fetchData();\n  }, []);\n\n  return { categories, isLoading, error };\n}\n"
  },
  {
    "path": "src/hooks/use-debounce.ts",
    "content": "import { useEffect, useState } from \"react\";\n\nexport function useDebounce<T>(value: T, delay: number): T {\n  const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n  useEffect(() => {\n    const handler = setTimeout(() => {\n      setDebouncedValue(value);\n    }, delay);\n\n    return () => {\n      clearTimeout(handler);\n    };\n  }, [value, delay]);\n\n  return debouncedValue;\n}\n"
  },
  {
    "path": "src/hooks/use-github-auth.ts",
    "content": "import { Octokit } from \"@octokit/rest\";\nimport { useCallback, useRef, useState } from \"react\";\n\nexport interface GitHubAuthState {\n  isAuthenticated: boolean;\n  username: string | null;\n  avatar: string | null;\n  token: string | null;\n  deviceCode: string | null;\n  userCode: string | null;\n  verificationUri: string | null;\n  isPolling: boolean;\n}\n\nexport interface GitHubUser {\n  login: string;\n  avatar_url: string;\n  name: string;\n  email: string;\n}\n\nexport interface DeviceFlowResponse {\n  device_code: string;\n  user_code: string;\n  verification_uri: string;\n  expires_in: number;\n  interval: number;\n}\n\nexport function useGitHubAuth() {\n  const [authState, setAuthState] = useState<GitHubAuthState>({\n    isAuthenticated: false,\n    username: null,\n    avatar: null,\n    token: null,\n    deviceCode: null,\n    userCode: null,\n    verificationUri: null,\n    isPolling: false,\n  });\n\n  const [isLoading, setIsLoading] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n  const pollingTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n  const startDeviceFlow = useCallback(async () => {\n    setIsLoading(true);\n    setError(null);\n\n    try {\n      // Call our API endpoint instead of GitHub directly\n      const response = await fetch(\"/api/github/device-flow\", {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        body: JSON.stringify({ action: \"start\" }),\n      });\n\n      if (!response.ok) {\n        const errorData = await response.json();\n        throw new Error(errorData.error || \"Failed to start device flow\");\n      }\n\n      const data: DeviceFlowResponse = await response.json();\n\n      setAuthState((prev) => ({\n        ...prev,\n        deviceCode: data.device_code,\n        userCode: data.user_code,\n        verificationUri: data.verification_uri,\n        isPolling: true,\n      }));\n\n      // Start polling for token\n      pollForToken(data.device_code, data.interval);\n\n      return {\n        success: true,\n        userCode: data.user_code,\n        verificationUri: data.verification_uri,\n      };\n    } catch (err: any) {\n      console.error(\"Device flow error:\", err);\n      setError(err.message || \"Failed to start authentication\");\n      return { success: false };\n    } finally {\n      setIsLoading(false);\n    }\n  }, []);\n\n  const pollForToken = useCallback(\n    async (deviceCode: string, interval: number) => {\n      const poll = async () => {\n        try {\n          // Call our API endpoint instead of GitHub directly\n          const response = await fetch(\"/api/github/device-flow\", {\n            method: \"POST\",\n            headers: {\n              \"Content-Type\": \"application/json\",\n            },\n            body: JSON.stringify({\n              action: \"poll\",\n              device_code: deviceCode,\n            }),\n          });\n\n          if (!response.ok) {\n            const errorData = await response.json();\n            throw new Error(errorData.error || \"Polling failed\");\n          }\n\n          const data = await response.json();\n\n          if (data.access_token) {\n            // We got the token! Now get user info\n            const octokit = new Octokit({ auth: data.access_token });\n            const { data: user } = await octokit.rest.users.getAuthenticated();\n\n            setAuthState((prev) => ({\n              ...prev,\n              isAuthenticated: true,\n              username: user.login,\n              avatar: user.avatar_url,\n              token: data.access_token,\n              isPolling: false,\n              deviceCode: null,\n              userCode: null,\n              verificationUri: null,\n            }));\n\n            return { success: true, user };\n          } else if (data.error === \"authorization_pending\") {\n            // Still waiting for user to authorize\n            pollingTimeoutRef.current = setTimeout(poll, interval * 1000);\n          } else if (data.error === \"slow_down\") {\n            // Increase polling interval\n            pollingTimeoutRef.current = setTimeout(poll, (interval + 5) * 1000);\n          } else if (data.error === \"access_denied\") {\n            // User denied the authorization\n            throw new Error(\"Authorization was denied by the user\");\n          } else if (data.error === \"expired_token\") {\n            // The device code expired\n            throw new Error(\"Authorization code expired. Please try again.\");\n          } else {\n            // Some other error occurred\n            throw new Error(\n              data.error_description || data.error || \"Authentication failed\",\n            );\n          }\n        } catch (err: any) {\n          console.error(\"Polling error:\", err);\n          setError(err.message || \"Authentication failed\");\n          setAuthState((prev) => ({\n            ...prev,\n            isPolling: false,\n            deviceCode: null,\n            userCode: null,\n            verificationUri: null,\n          }));\n        }\n      };\n\n      // Start polling\n      pollingTimeoutRef.current = setTimeout(poll, interval * 1000);\n    },\n    [],\n  );\n\n  const stopPolling = useCallback(() => {\n    if (pollingTimeoutRef.current) {\n      clearTimeout(pollingTimeoutRef.current);\n      pollingTimeoutRef.current = null;\n    }\n  }, []);\n\n  const logout = useCallback(() => {\n    // Stop any ongoing polling\n    stopPolling();\n\n    setAuthState({\n      isAuthenticated: false,\n      username: null,\n      avatar: null,\n      token: null,\n      deviceCode: null,\n      userCode: null,\n      verificationUri: null,\n      isPolling: false,\n    });\n    setError(null);\n  }, [stopPolling]);\n\n  const createAuthenticatedOctokit = useCallback(() => {\n    if (!authState.token) return null;\n\n    return new Octokit({\n      auth: authState.token,\n    });\n  }, [authState.token]);\n\n  return {\n    authState,\n    isLoading,\n    error,\n    startDeviceFlow,\n    logout,\n    stopPolling,\n    createAuthenticatedOctokit,\n  };\n}\n"
  },
  {
    "path": "src/hooks/use-mobile.ts",
    "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": "src/hooks/use-pr-submission.ts",
    "content": "import { useCallback, useState } from \"react\";\n\nexport interface SubmissionData {\n  name: string;\n  description: string;\n  url: string;\n  category: string;\n}\n\nexport interface PRSubmissionResult {\n  success: boolean;\n  prNumber?: number;\n  prUrl?: string;\n  error?: string;\n}\n\nexport function usePRSubmission() {\n  const [isSubmitting, setIsSubmitting] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n  const [submissionStatus, setSubmissionStatus] = useState<string | null>(null);\n\n  const validateSubmission = useCallback(\n    async (\n      submission: SubmissionData,\n    ): Promise<{ valid: boolean; error?: string }> => {\n      try {\n        const response = await fetch(\"/api/submit-resource\", {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify(submission),\n        });\n\n        const data = await response.json();\n\n        if (!response.ok) {\n          return { valid: false, error: data.error };\n        }\n\n        return { valid: true };\n      } catch (err: any) {\n        return { valid: false, error: err.message };\n      }\n    },\n    [],\n  );\n\n  const submitPR = useCallback(\n    async (\n      _octokit: unknown,\n      submission: SubmissionData,\n      _userInfo: { login: string; name?: string },\n    ): Promise<PRSubmissionResult> => {\n      setIsSubmitting(true);\n      setError(null);\n      setSubmissionStatus(\"Submitting your resource...\");\n\n      try {\n        const response = await fetch(\"/api/submit-resource\", {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify(submission),\n        });\n\n        const data = await response.json();\n\n        if (!response.ok) {\n          throw new Error(data.error || \"Failed to submit\");\n        }\n\n        return {\n          success: true,\n          prNumber: data.prNumber,\n          prUrl: data.prUrl,\n        };\n      } catch (err: any) {\n        const errorMessage = err.message || \"Failed to submit pull request\";\n        setError(errorMessage);\n        return {\n          success: false,\n          error: errorMessage,\n        };\n      } finally {\n        setIsSubmitting(false);\n        setSubmissionStatus(null);\n      }\n    },\n    [],\n  );\n\n  return {\n    isSubmitting,\n    error,\n    submissionStatus,\n    submitPR,\n    validateSubmission,\n  };\n}\n"
  },
  {
    "path": "src/hooks/use-readme.ts",
    "content": "import { GITHUB_CONFIG } from \"@/lib/config\";\nimport { titleToSlug } from \"@/lib/slugs\";\nimport { Octokit } from \"@octokit/rest\";\n\nconst octokit = new Octokit();\n\nconst EXCLUDED_CATEGORIES = [\"Star History\", \"Contributors\"];\n\nexport interface Resource {\n  id: string;\n  name: string;\n  url: string;\n  description: string;\n  category: string;\n  date: string;\n}\n\n// Simple cache with 30-minute expiration\nlet cachedData: Resource[] | null = null;\nlet cacheTimestamp: number = 0;\nconst CACHE_DURATION = 30 * 60 * 1000; // 30 minutes\n\nexport async function fetchAndParseReadme(): Promise<Resource[]> {\n  // Check if we have valid cached data\n  if (cachedData && Date.now() - cacheTimestamp < CACHE_DURATION) {\n    return cachedData;\n  }\n\n  try {\n    const response = await octokit.repos.getContent({\n      owner: GITHUB_CONFIG.REPO_OWNER,\n      repo: GITHUB_CONFIG.REPO_NAME,\n      path: \"README.md\",\n    });\n\n    if (Array.isArray(response.data) || !(\"content\" in response.data)) {\n      throw new Error(\"Invalid response data\");\n    }\n\n    const content = Buffer.from(response.data.content, \"base64\").toString();\n\n    const resources: Resource[] = [];\n    let currentCategory = \"\";\n    const lines = content.split(\"\\n\");\n\n    for (const line of lines) {\n      if (line.startsWith(\"## \")) {\n        currentCategory = line.replace(\"## \", \"\").trim();\n      } else if (\n        line.startsWith(\"| \") &&\n        line.includes(\" | \") &&\n        currentCategory\n      ) {\n        const parts = line.split(\"|\").map((part) => part.trim());\n        if (parts.length >= 4) {\n          let url = parts[3];\n          let date = \"Unknown\";\n          const name = parts[1];\n\n          // Skip separator rows and header rows\n          if (\n            name === \"Name\" ||\n            name === \"-------------------------------------\" ||\n            name.includes(\"---\") ||\n            name.trim() === \"\" ||\n            parts[2] === \"Description\" ||\n            parts[2].includes(\"---\") ||\n            url === \"Link\" ||\n            url.includes(\"---\")\n          ) {\n            continue;\n          }\n\n          // Extract URL from markdown link format [Link](url)\n          const markdownMatch = url.match(/\\[(.*?)\\]\\((.*?)\\)/);\n          if (markdownMatch && markdownMatch[2]) {\n            url = markdownMatch[2];\n          } else {\n            // If not in markdown format, remove any \"Link:\" prefix\n            url = url.replace(/^Link:?\\s*/i, \"\").trim();\n          }\n\n          // Check if there's a date column (parts.length >= 5)\n          if (parts.length >= 5) {\n            date = parts[4];\n          }\n\n          // Skip if URL is empty or invalid\n          if (!url || url.trim() === \"\" || url === \"Link\") {\n            continue;\n          }\n\n          // Generate unique ID based on title slug\n          const baseSlug = titleToSlug(name);\n          let uniqueId = baseSlug;\n          let counter = 1;\n\n          // Ensure uniqueness by adding counter if needed\n          while (resources.some((r) => r.id === uniqueId)) {\n            uniqueId = `${baseSlug}-${counter}`;\n            counter++;\n          }\n\n          resources.push({\n            id: uniqueId,\n            name: name,\n            description: parts[2],\n            url: url,\n            category: currentCategory,\n            date: date,\n          });\n        }\n      }\n    }\n\n    const filteredResources = resources.filter(\n      (resource) =>\n        resource.name &&\n        resource.name.trim() !== \"\" &&\n        resource.description &&\n        resource.description.trim() !== \"\" &&\n        resource.url &&\n        resource.url.trim() !== \"\" &&\n        !EXCLUDED_CATEGORIES.includes(resource.category),\n    );\n\n    cachedData = filteredResources;\n    cacheTimestamp = Date.now();\n\n    return filteredResources;\n  } catch (error) {\n    console.error(\"Error fetching or parsing README:\", error);\n    throw error;\n  }\n}\n"
  },
  {
    "path": "src/hooks/use-website-preview.ts",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\ninterface WebsitePreviewProps {\n  url: string;\n  name: string;\n}\n\nexport function useWebsitePreview({ url, name }: WebsitePreviewProps) {\n  const [previewState, setPreviewState] = useState<\n    \"loading\" | \"iframe\" | \"screenshot\" | \"fallback\"\n  >(\"loading\");\n  const [screenshotUrl, setScreenshotUrl] = useState<string | null>(null);\n  const [faviconUrl, setFaviconUrl] = useState<string | null>(null);\n\n  useEffect(() => {\n    const domain = new URL(url).hostname;\n    setFaviconUrl(`https://icons.duckduckgo.com/ip3/${domain}.ico`);\n\n    // Try iframe first\n    const checkIframe = () => {\n      const iframe = document.createElement(\"iframe\");\n      iframe.style.display = \"none\";\n      iframe.src = url;\n\n      const timeout = setTimeout(() => {\n        // If iframe takes too long, try screenshot\n        setPreviewState(\"screenshot\");\n        loadScreenshot();\n        document.body.removeChild(iframe);\n      }, 3000);\n\n      iframe.onload = () => {\n        clearTimeout(timeout);\n        setPreviewState(\"iframe\");\n        document.body.removeChild(iframe);\n      };\n\n      iframe.onerror = () => {\n        clearTimeout(timeout);\n        setPreviewState(\"screenshot\");\n        loadScreenshot();\n        document.body.removeChild(iframe);\n      };\n\n      document.body.appendChild(iframe);\n    };\n\n    const loadScreenshot = async () => {\n      try {\n        const microlinkUrl = `https://api.microlink.io?url=${encodeURIComponent(url)}&screenshot=true&meta=false`;\n        const response = await fetch(microlinkUrl);\n        const data = await response.json();\n\n        if (data.status === \"success\" && data.data?.screenshot?.url) {\n          setScreenshotUrl(data.data.screenshot.url);\n          setPreviewState(\"screenshot\");\n        } else {\n          setPreviewState(\"fallback\");\n        }\n      } catch (error) {\n        console.error(\"Error loading screenshot:\", error);\n        setPreviewState(\"fallback\");\n      }\n    };\n\n    checkIframe();\n\n    return () => {\n      // Cleanup\n    };\n  }, [url]);\n\n  return {\n    previewState,\n    screenshotUrl,\n    faviconUrl,\n  };\n}\n"
  },
  {
    "path": "src/lib/compose-refs.ts",
    "content": "import * as React from \"react\";\r\n\r\ntype PossibleRef<T> = React.Ref<T> | undefined;\r\n\r\n/**\r\n * Set a given ref to a given value\r\n * This utility takes care of different types of refs: callback refs and RefObject(s)\r\n */\r\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\r\n  if (typeof ref === \"function\") {\r\n    return ref(value);\r\n  }\r\n\r\n  if (ref !== null && ref !== undefined) {\r\n    ref.current = value;\r\n  }\r\n}\r\n\r\n/**\r\n * A utility to compose multiple refs together\r\n * Accepts callback refs and RefObject(s)\r\n */\r\nfunction composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\r\n  return (node) => {\r\n    let hasCleanup = false;\r\n    const cleanups = refs.map((ref) => {\r\n      const cleanup = setRef(ref, node);\r\n      if (!hasCleanup && typeof cleanup === \"function\") {\r\n        hasCleanup = true;\r\n      }\r\n      return cleanup;\r\n    });\r\n\r\n    // React <19 will log an error to the console if a callback ref returns a\r\n    // value. We don't use ref cleanups internally so this will only happen if a\r\n    // user's ref callback returns a value, which we only expect if they are\r\n    // using the cleanup functionality added in React 19.\r\n    if (hasCleanup) {\r\n      return () => {\r\n        for (let i = 0; i < cleanups.length; i++) {\r\n          const cleanup = cleanups[i];\r\n          if (typeof cleanup === \"function\") {\r\n            cleanup();\r\n          } else {\r\n            setRef(refs[i], null);\r\n          }\r\n        }\r\n      };\r\n    }\r\n  };\r\n}\r\n\r\n/**\r\n * A custom hook that composes multiple refs\r\n * Accepts callback refs and RefObject(s)\r\n */\r\nfunction useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\r\n  // biome-ignore lint/correctness/useExhaustiveDependencies: we want to memoize by all values\r\n  return React.useCallback(composeRefs(...refs), refs);\r\n}\r\n\r\nexport { composeRefs, useComposedRefs };\r\n"
  },
  {
    "path": "src/lib/config.ts",
    "content": "// GitHub OAuth App Configuration\nexport const GITHUB_CONFIG = {\n  // OAuth App Client ID - https://github.com/marketplace/awesome-shadcn-ui\n  // Created by @BankkRoll, Feel free to swap it or use it\n  CLIENT_ID: \"Ov23lizgfZ4yKq0NxcTm\",\n\n  // Repository Configuration\n  REPO_OWNER: \"birobirobiro\",\n  REPO_NAME: \"awesome-shadcn-ui\",\n\n  // OAuth App URLs\n  DEVICE_FLOW_URL: \"https://github.com/login/device/code\",\n  ACCESS_TOKEN_URL: \"https://github.com/login/oauth/access_token\",\n\n  // API Configuration\n  API_HEADERS: {\n    Accept: \"application/vnd.github+json\",\n    \"Content-Type\": \"application/json\",\n    \"User-Agent\": \"awesome-shadcn-ui\",\n  },\n\n  // OAuth Scopes\n  SCOPES: [\"repo\"],\n\n  // Fork Creation Delay (in milliseconds)\n  FORK_CREATION_DELAY: 5000,\n} as const;\n\n// PR Template Configuration\nexport const PR_TEMPLATE = {\n  HEADER: {\n    name: \"feat: Add new awesome resource\",\n    about: \"Propose adding a new awesome resource related to shadcn/ui\",\n    labels: [\"feature\"],\n  },\n\n  SECTIONS: {\n    DESCRIPTION: \"## Describe the awesome resource you want to add\",\n    CATEGORY: \"## **Which section does it belong to?**\",\n    ADDITIONAL_DETAILS: \"**Additional details (optional)**\",\n    CHECKLIST: \"## **Checklist**\",\n  },\n\n  CATEGORIES: [\n    \"Libs and Components\",\n    \"Registries\",\n    \"Plugins and Extensions\",\n    \"Colors and Customizations\",\n    \"Animations\",\n    \"Tools\",\n    \"Websites and Portfolios\",\n    \"Platforms\",\n    \"Ports\",\n    \"Design System\",\n    \"Boilerplates / Templates\",\n  ],\n\n  CHECKLIST_ITEMS: {\n    AUTOMATED: [\n      \"Resource is automatically sorted alphabetically within its section.\",\n      \"Duplicate checking is performed automatically.\",\n      \"Table formatting is handled automatically.\",\n      \"Includes a valid and working link to the resource.\",\n      \"Automatically assigned the correct section to the resource.\",\n    ],\n  },\n} as const;\n\n// Error Messages\nexport const ERROR_MESSAGES = {\n  GITHUB_API: \"GitHub API error\",\n  FORK_CREATION: \"Fork creation failed or is not accessible\",\n  BRANCH_CREATION: \"Failed to create branch\",\n  README_FETCH: \"Could not fetch README.md from original repository\",\n  PR_CREATION: \"Failed to submit pull request\",\n  INVALID_ACTION: \"Invalid action\",\n  INTERNAL_SERVER: \"Internal server error\",\n} as const;\n\n// Status Messages\nexport const STATUS_MESSAGES = {\n  STARTING: \"Starting submission process...\",\n  CHECKING_FORK: \"Checking for repository fork...\",\n  USING_EXISTING_FORK: \"Using existing fork...\",\n  CREATING_FORK: \"Creating new fork...\",\n  VERIFYING_FORK: \"Verifying fork is ready...\",\n  GETTING_COMMIT: \"Getting latest commit from your fork...\",\n  CREATING_BRANCH: \"Creating new branch on your fork...\",\n  READING_README: \"Reading latest README for updates...\",\n  COMMITTING: \"Committing your changes to the new branch...\",\n  CREATING_PR: \"Creating pull request...\",\n} as const;\n"
  },
  {
    "path": "src/lib/slugs.ts",
    "content": "/**\n * Utility functions for converting between category names and URL-friendly slugs\n */\n\nexport function categoryNameToSlug(categoryName: string): string {\n  return categoryName\n    .toLowerCase()\n    .replace(/[^a-z0-9\\s-]/g, \"\") // Remove special characters except spaces and hyphens\n    .replace(/\\s+/g, \"-\") // Replace spaces with hyphens\n    .replace(/-+/g, \"-\") // Replace multiple hyphens with single hyphen\n    .trim();\n}\n\nexport function slugToCategoryName(slug: string): string {\n  // This is a reverse mapping - we need to find the original category name\n  // Since we can't perfectly reverse the slug, we'll need to maintain a mapping\n  const slugToCategoryMap: Record<string, string> = {\n    \"libs-and-components\": \"Libs and Components\",\n    registries: \"Registries\",\n    \"plugins-and-extensions\": \"Plugins and Extensions\",\n    \"colors-and-customizations\": \"Colors and Customizations\",\n    animations: \"Animations\",\n    tools: \"Tools\",\n    \"websites-and-portfolios\": \"Websites and Portfolios\",\n    platforms: \"Platforms\",\n    ports: \"Ports\",\n    \"design-system\": \"Design System\",\n    \"boilerplates-templates\": \"Boilerplates / Templates\",\n  };\n\n  return slugToCategoryMap[slug] || slug;\n}\n\nexport function createCategorySlugMap(\n  categories: string[],\n): Record<string, string> {\n  const map: Record<string, string> = {};\n  categories.forEach((category) => {\n    map[category] = categoryNameToSlug(category);\n  });\n  return map;\n}\n\nexport function createSlugToCategoryMap(\n  categories: string[],\n): Record<string, string> {\n  const map: Record<string, string> = {};\n  categories.forEach((category) => {\n    map[categoryNameToSlug(category)] = category;\n  });\n  return map;\n}\n\nexport function titleToSlug(title: string): string {\n  return title\n    .toLowerCase()\n    .replace(/[^a-z0-9\\s-]/g, \"\") // Remove special characters except spaces and hyphens\n    .replace(/\\s+/g, \"-\") // Replace spaces with hyphens\n    .replace(/-+/g, \"-\") // Replace multiple hyphens with single hyphen\n    .trim();\n}\n\nexport function slugToTitle(slug: string): string {\n  return slug\n    .split(\"-\")\n    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n    .join(\" \");\n}\n"
  },
  {
    "path": "src/lib/utils.ts",
    "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
  },
  {
    "path": "src/providers/providers.tsx",
    "content": "\"use client\";\n\nimport { Toaster } from \"sonner\";\nimport { ThemeProvider } from \"./theme-provider\";\n\nexport function Providers({ children }: { children: React.ReactNode }) {\n  return (\n    <ThemeProvider\n      attribute=\"class\"\n      defaultTheme=\"system\"\n      enableSystem\n      disableTransitionOnChange\n    >\n      {children}\n      <Toaster />\n    </ThemeProvider>\n  );\n}\n"
  },
  {
    "path": "src/providers/theme-provider.tsx",
    "content": "\"use client\";\n\nimport {\n  ThemeProvider as NextThemesProvider,\n  type ThemeProviderProps,\n} from \"next-themes\";\n\nexport function ThemeProvider({ children, ...props }: ThemeProviderProps) {\n  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\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\": \"react-jsx\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\n        \"./src/*\"\n      ]\n    }\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "wrangler.jsonc",
    "content": "{\n  \"$schema\": \"./node_modules/wrangler/config-schema.json\",\n  \"main\": \".open-next/worker.js\",\n  \"name\": \"awesome-shadcn-ui\",\n  \"compatibility_date\": \"2026-01-30\",\n  \"compatibility_flags\": [\n    \"nodejs_compat\"\n  ],\n  \"assets\": {\n    \"directory\": \".open-next/assets\",\n    \"binding\": \"ASSETS\"\n  },\n  \"routes\": [\n    {\n      \"pattern\": \"awesomeshadcn.dev\",\n      \"custom_domain\": true\n    },\n  ]\n}"
  }
]