Full Code of jnsahaj/tweakcn for AI

main 9fb9c4c77f75 cached
450 files
1.8 MB
523.9k tokens
1043 symbols
1 requests
Download .txt
Showing preview only (1,957K chars total). Download the full file or copy to clipboard to get everything.
Repository: jnsahaj/tweakcn
Branch: main
Commit: 9fb9c4c77f75
Files: 450
Total size: 1.8 MB

Directory structure:
gitextract_41hngla7/

├── .dockerignore
├── .github/
│   ├── FUNDING.yml
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierignore
├── .prettierrc
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── actions/
│   ├── account.ts
│   ├── ai-usage.ts
│   ├── checkout.ts
│   ├── community-themes.ts
│   ├── customer.ts
│   └── themes.ts
├── app/
│   ├── (auth)/
│   │   └── components/
│   │       └── auth-dialog.tsx
│   ├── (legal)/
│   │   ├── layout.tsx
│   │   └── privacy-policy/
│   │       └── page.tsx
│   ├── ai/
│   │   ├── components/
│   │   │   ├── ai-announcement.tsx
│   │   │   ├── ai-chat-form.tsx
│   │   │   ├── ai-chat-hero.tsx
│   │   │   ├── chat-heading.tsx
│   │   │   ├── community-theme-card.tsx
│   │   │   ├── community-themes.tsx
│   │   │   └── suggested-pill-actions.tsx
│   │   ├── layout.tsx
│   │   ├── loading.tsx
│   │   └── page.tsx
│   ├── api/
│   │   ├── auth/
│   │   │   └── [...all]/
│   │   │       └── route.ts
│   │   ├── enhance-prompt/
│   │   │   └── route.ts
│   │   ├── generate-theme/
│   │   │   └── route.ts
│   │   ├── google-fonts/
│   │   │   └── route.ts
│   │   ├── oauth/
│   │   │   ├── app-info/
│   │   │   │   └── route.ts
│   │   │   ├── authorize/
│   │   │   │   └── route.ts
│   │   │   ├── revoke/
│   │   │   │   └── route.ts
│   │   │   ├── token/
│   │   │   │   └── route.ts
│   │   │   └── userinfo/
│   │   │       └── route.ts
│   │   ├── subscription/
│   │   │   └── route.ts
│   │   ├── v1/
│   │   │   ├── me/
│   │   │   │   └── route.ts
│   │   │   └── themes/
│   │   │       ├── [themeId]/
│   │   │       │   └── route.ts
│   │   │       └── route.ts
│   │   └── webhook/
│   │       └── polar/
│   │           └── route.ts
│   ├── community/
│   │   ├── components/
│   │   │   ├── community-sidebar.tsx
│   │   │   ├── community-theme-card.tsx
│   │   │   ├── community-theme-preview-dialog.tsx
│   │   │   └── community-themes-content.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── dashboard/
│   │   ├── layout.tsx
│   │   ├── loading.tsx
│   │   └── page.tsx
│   ├── editor/
│   │   └── theme/
│   │       └── [[...themeId]]/
│   │           ├── layout.tsx
│   │           ├── loading.tsx
│   │           └── page.tsx
│   ├── figma/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── globals.css
│   ├── layout.tsx
│   ├── loaders.css
│   ├── not-found.tsx
│   ├── oauth/
│   │   └── authorize/
│   │       └── page.tsx
│   ├── page.tsx
│   ├── pricing/
│   │   ├── components/
│   │   │   └── checkout-button.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── r/
│   │   ├── themes/
│   │   │   └── [id]/
│   │   │       └── route.ts
│   │   └── v0/
│   │       └── [id]/
│   │           └── route.ts
│   ├── settings/
│   │   ├── account/
│   │   │   ├── components/
│   │   │   │   └── delete-account-section.tsx
│   │   │   └── page.tsx
│   │   ├── components/
│   │   │   ├── customer-portal-link.tsx
│   │   │   ├── settings-header.tsx
│   │   │   ├── settings-sidebar.tsx
│   │   │   ├── theme-card.tsx
│   │   │   ├── themes-list.tsx
│   │   │   ├── usage-stats.tsx
│   │   │   └── user-info.tsx
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   ├── portal/
│   │   │   └── route.ts
│   │   ├── themes/
│   │   │   └── page.tsx
│   │   └── usage/
│   │       └── page.tsx
│   ├── sitemap.ts
│   ├── success/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   └── themes/
│       └── [themeId]/
│           ├── error.tsx
│           ├── layout.tsx
│           ├── loading.tsx
│           ├── not-found.tsx
│           ├── opengraph-image.alt.txt
│           ├── opengraph-image.tsx
│           └── page.tsx
├── components/
│   ├── ai-elements/
│   │   ├── code-block.tsx
│   │   ├── conversation.tsx
│   │   └── response.tsx
│   ├── auth-dialog-wrapper.tsx
│   ├── block-viewer.tsx
│   ├── copy-button.tsx
│   ├── debug-button.tsx
│   ├── dynamic-font-loader.tsx
│   ├── dynamic-website-preview.tsx
│   ├── editor/
│   │   ├── action-bar/
│   │   │   ├── action-bar.tsx
│   │   │   └── components/
│   │   │       ├── action-bar-buttons.tsx
│   │   │       ├── ai-generate-button.tsx
│   │   │       ├── code-button.tsx
│   │   │       ├── import-button.tsx
│   │   │       ├── mcp-dialog.tsx
│   │   │       ├── more-options.tsx
│   │   │       ├── publish-button.tsx
│   │   │       ├── reset-button.tsx
│   │   │       ├── save-button.tsx
│   │   │       ├── share-button.tsx
│   │   │       ├── theme-toggle.tsx
│   │   │       └── undo-redo-buttons.tsx
│   │   ├── ai/
│   │   │   ├── ai-chat-form-body.tsx
│   │   │   ├── alert-banner.tsx
│   │   │   ├── chat-image-preview.tsx
│   │   │   ├── chat-input.tsx
│   │   │   ├── chat-interface.tsx
│   │   │   ├── chat-theme-preview.tsx
│   │   │   ├── closeable-suggested-pill-actions.tsx
│   │   │   ├── drag-and-drop-image-uploader.tsx
│   │   │   ├── enhance-prompt-button.tsx
│   │   │   ├── image-uploader.tsx
│   │   │   ├── loading-logo.tsx
│   │   │   ├── message-actions.tsx
│   │   │   ├── message-edit-form.tsx
│   │   │   ├── message.tsx
│   │   │   ├── messages.tsx
│   │   │   ├── no-messages-placeholder.tsx
│   │   │   ├── pill-action-button.tsx
│   │   │   ├── stream-text.tsx
│   │   │   └── uploaded-image-preview.tsx
│   │   ├── code-panel-dialog.tsx
│   │   ├── code-panel.tsx
│   │   ├── color-picker.tsx
│   │   ├── color-selector-popover.tsx
│   │   ├── colors-tab-content.tsx
│   │   ├── contrast-checker.tsx
│   │   ├── control-section.tsx
│   │   ├── css-import-dialog.tsx
│   │   ├── custom-textarea.tsx
│   │   ├── editor.tsx
│   │   ├── font-picker.tsx
│   │   ├── hsl-adjustment-controls.tsx
│   │   ├── hsl-preset-button.tsx
│   │   ├── inspector-class-item.tsx
│   │   ├── inspector-overlay.tsx
│   │   ├── mention-list.tsx
│   │   ├── mention-suggestion.ts
│   │   ├── section-context.tsx
│   │   ├── shadow-control.tsx
│   │   ├── share-dialog.tsx
│   │   ├── slider-with-input.tsx
│   │   ├── theme-control-actions.tsx
│   │   ├── theme-control-panel.tsx
│   │   ├── theme-font-select.tsx
│   │   ├── theme-preset-select.tsx
│   │   ├── theme-preview/
│   │   │   ├── color-preview.tsx
│   │   │   ├── components-showcase.tsx
│   │   │   ├── examples-preview-container.tsx
│   │   │   └── tabs-trigger-pill.tsx
│   │   ├── theme-preview-panel.tsx
│   │   └── theme-save-dialog.tsx
│   ├── effects/
│   │   ├── frame-highlight.tsx
│   │   ├── noise-effect.tsx
│   │   └── spotlight.tsx
│   ├── error-boundary.tsx
│   ├── examples/
│   │   ├── ai-chat-demo.tsx
│   │   ├── cards/
│   │   │   ├── activity-goal.tsx
│   │   │   ├── calendar.tsx
│   │   │   ├── chat.tsx
│   │   │   ├── cookie-settings.tsx
│   │   │   ├── create-account.tsx
│   │   │   ├── date-picker-with-range.tsx
│   │   │   ├── exercise-minutes.tsx
│   │   │   ├── forms.tsx
│   │   │   ├── github-card.tsx
│   │   │   ├── index.tsx
│   │   │   ├── payment-method.tsx
│   │   │   ├── payments.tsx
│   │   │   ├── report-issue.tsx
│   │   │   ├── share.tsx
│   │   │   ├── stats.tsx
│   │   │   └── team-members.tsx
│   │   ├── custom/
│   │   │   └── index.tsx
│   │   ├── dashboard/
│   │   │   ├── components/
│   │   │   │   ├── app-sidebar.tsx
│   │   │   │   ├── chart-area-interactive.tsx
│   │   │   │   ├── chart-bar-mixed.tsx
│   │   │   │   ├── chart-pie-donut.tsx
│   │   │   │   ├── data-table.tsx
│   │   │   │   ├── nav-documents.tsx
│   │   │   │   ├── nav-main.tsx
│   │   │   │   ├── nav-secondary.tsx
│   │   │   │   ├── nav-user.tsx
│   │   │   │   ├── section-cards.tsx
│   │   │   │   └── site-header.tsx
│   │   │   ├── data.json
│   │   │   └── index.tsx
│   │   ├── mail/
│   │   │   ├── components/
│   │   │   │   ├── account-switcher.tsx
│   │   │   │   ├── mail-display.tsx
│   │   │   │   ├── mail-list.tsx
│   │   │   │   ├── mail.tsx
│   │   │   │   └── nav.tsx
│   │   │   ├── data.tsx
│   │   │   ├── index.tsx
│   │   │   └── use-mail.ts
│   │   ├── music/
│   │   │   ├── components/
│   │   │   │   ├── album-artwork.tsx
│   │   │   │   ├── menu.tsx
│   │   │   │   ├── podcast-empty-placeholder.tsx
│   │   │   │   └── sidebar.tsx
│   │   │   ├── data/
│   │   │   │   ├── albums.ts
│   │   │   │   └── playlists.ts
│   │   │   └── index.tsx
│   │   ├── pricing/
│   │   │   └── pricing.tsx
│   │   ├── tasks/
│   │   │   ├── components/
│   │   │   │   ├── columns.tsx
│   │   │   │   ├── data-table-column-header.tsx
│   │   │   │   ├── data-table-faceted-filter.tsx
│   │   │   │   ├── data-table-pagination.tsx
│   │   │   │   ├── data-table-row-actions.tsx
│   │   │   │   ├── data-table-toolbar.tsx
│   │   │   │   ├── data-table-view-options.tsx
│   │   │   │   ├── data-table.tsx
│   │   │   │   └── user-nav.tsx
│   │   │   ├── data/
│   │   │   │   ├── data.tsx
│   │   │   │   ├── schema.ts
│   │   │   │   └── tasks.json
│   │   │   └── index.tsx
│   │   └── typography/
│   │       ├── blog-post.tsx
│   │       ├── font-showcase.tsx
│   │       └── typography-demo.tsx
│   ├── figma-export-dialog.tsx
│   ├── figma-header.tsx
│   ├── footer.tsx
│   ├── get-pro-cta.tsx
│   ├── get-pro-dialog-wrapper.tsx
│   ├── header.tsx
│   ├── home/
│   │   ├── ai-generation-cta.tsx
│   │   ├── cta.tsx
│   │   ├── faq.tsx
│   │   ├── features.tsx
│   │   ├── header.tsx
│   │   ├── hero.tsx
│   │   ├── how-it-works.tsx
│   │   ├── testimonials.tsx
│   │   ├── theme-preset-buttons.tsx
│   │   └── theme-preset-selector.tsx
│   ├── horizontal-scroll-area.tsx
│   ├── icons/
│   │   └── tailwind-css.tsx
│   ├── icons.tsx
│   ├── loader.tsx
│   ├── loading.tsx
│   ├── posthog-init.tsx
│   ├── social-link.tsx
│   ├── tag-selector.tsx
│   ├── theme-preview.tsx
│   ├── theme-provider.tsx
│   ├── theme-script.tsx
│   ├── theme-toggle.tsx
│   ├── theme-view.tsx
│   ├── tooltip-wrapper.tsx
│   ├── ui/
│   │   ├── accordion.tsx
│   │   ├── alert-dialog.tsx
│   │   ├── alert.tsx
│   │   ├── aspect-ratio.tsx
│   │   ├── avatar.tsx
│   │   ├── badge.tsx
│   │   ├── base-ui-tabs.tsx
│   │   ├── breadcrumb.tsx
│   │   ├── button.tsx
│   │   ├── calendar.tsx
│   │   ├── card.tsx
│   │   ├── carousel.tsx
│   │   ├── chart.tsx
│   │   ├── checkbox.tsx
│   │   ├── collapsible.tsx
│   │   ├── command.tsx
│   │   ├── context-menu.tsx
│   │   ├── dialog.tsx
│   │   ├── drawer.tsx
│   │   ├── dropdown-menu.tsx
│   │   ├── form.tsx
│   │   ├── hover-card.tsx
│   │   ├── input-otp.tsx
│   │   ├── input.tsx
│   │   ├── label.tsx
│   │   ├── menubar.tsx
│   │   ├── navigation-menu.tsx
│   │   ├── pagination.tsx
│   │   ├── popover.tsx
│   │   ├── progress.tsx
│   │   ├── radio-group.tsx
│   │   ├── resizable.tsx
│   │   ├── revola.tsx
│   │   ├── scroll-area.tsx
│   │   ├── select.tsx
│   │   ├── separator.tsx
│   │   ├── sheet.tsx
│   │   ├── sidebar.tsx
│   │   ├── skeleton.tsx
│   │   ├── slider.tsx
│   │   ├── sonner.tsx
│   │   ├── switch.tsx
│   │   ├── table.tsx
│   │   ├── tabs.tsx
│   │   ├── textarea.tsx
│   │   ├── toast.tsx
│   │   ├── toaster.tsx
│   │   ├── toggle-group.tsx
│   │   ├── toggle.tsx
│   │   ├── tooltip.tsx
│   │   └── use-toast.ts
│   └── user-profile-dropdown.tsx
├── components.json
├── config/
│   └── theme.ts
├── db/
│   ├── index.ts
│   └── schema.ts
├── docker-compose.yml
├── docs/
│   └── oauth-api.md
├── drizzle/
│   ├── 0000_rare_moira_mactaggert.sql
│   ├── 0001_late_mikhail_rasputin.sql
│   ├── 0002_nebulous_randall.sql
│   ├── 0003_bumpy_quasimodo.sql
│   ├── 0004_red_monster_badoon.sql
│   └── meta/
│       ├── 0000_snapshot.json
│       ├── 0001_snapshot.json
│       ├── 0002_snapshot.json
│       ├── 0003_snapshot.json
│       ├── 0004_snapshot.json
│       └── _journal.json
├── drizzle.config.ts
├── eslint.config.mjs
├── hooks/
│   ├── inspector/
│   │   ├── use-inspector-mouse-events.ts
│   │   ├── use-inspector-scroll.ts
│   │   ├── use-inspector-state.ts
│   │   └── use-theme-inspector.ts
│   ├── themes/
│   │   ├── index.ts
│   │   ├── use-community-themes.ts
│   │   ├── use-theme-mutations.ts
│   │   └── use-themes-data.ts
│   ├── use-ai-chat-form.ts
│   ├── use-ai-enhance-prompt.ts
│   ├── use-ai-theme-generation-core.ts
│   ├── use-chat-context.tsx
│   ├── use-contrast-checker.ts
│   ├── use-controls-tab-from-url.ts
│   ├── use-copy-to-clipboard.ts
│   ├── use-debounced-callback.ts
│   ├── use-dialog-actions.tsx
│   ├── use-document-drag-and-drop-intent.ts
│   ├── use-feedback-text.ts
│   ├── use-font-search.ts
│   ├── use-fullscreen.ts
│   ├── use-github-stars.ts
│   ├── use-guards.ts
│   ├── use-iframe-theme-injector.ts
│   ├── use-image-upload-reducer.ts
│   ├── use-image-upload.ts
│   ├── use-media-query.tsx
│   ├── use-mobile.tsx
│   ├── use-mounted.tsx
│   ├── use-post-login-action.ts
│   ├── use-scroll-start-end.ts
│   ├── use-subscription.ts
│   ├── use-theme-inspector-classnames.ts
│   ├── use-theme-inspector-regex.ts
│   ├── use-theme-inspector.ts
│   ├── use-theme-preset-from-url.ts
│   ├── use-toast.ts
│   └── use-website-preview.ts
├── lib/
│   ├── ai/
│   │   ├── generate-theme/
│   │   │   ├── index.ts
│   │   │   └── tools.ts
│   │   ├── parse-ai-sdk-transport-error.ts
│   │   ├── prompts.ts
│   │   └── providers.ts
│   ├── auth-client.ts
│   ├── auth.ts
│   ├── checkout.ts
│   ├── constants.ts
│   ├── error-response.ts
│   ├── figma-constants.ts
│   ├── inspector/
│   │   ├── class-utils.ts
│   │   ├── inspector-state-utils.ts
│   │   ├── segment-classname.ts
│   │   └── theme-class-finder.ts
│   ├── oauth.ts
│   ├── polar.ts
│   ├── posthog.ts
│   ├── query-client.tsx
│   ├── shared.ts
│   ├── subscription.ts
│   └── utils.ts
├── middleware.ts
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── public/
│   ├── live-preview.js
│   └── r/
│       └── registry.json
├── routes.ts
├── scripts/
│   ├── create-oauth-app.ts
│   ├── generate-registry.ts
│   └── generate-theme-registry.ts
├── store/
│   ├── ai-chat-store.ts
│   ├── ai-local-draft-store.ts
│   ├── auth-store.ts
│   ├── color-control-focus-store.ts
│   ├── editor-store.ts
│   ├── get-pro-dialog-store.ts
│   ├── idb-storage.ts
│   ├── preferences-store.ts
│   ├── theme-preset-store.ts
│   └── website-preview-store.ts
├── tsconfig.json
├── types/
│   ├── ai.ts
│   ├── community.ts
│   ├── editor.ts
│   ├── errors.ts
│   ├── fonts.ts
│   ├── index.ts
│   ├── live-preview-embed.ts
│   ├── subscription.ts
│   └── theme.ts
└── utils/
    ├── ai/
    │   ├── ai-prompt.tsx
    │   ├── apply-theme.ts
    │   ├── image-upload.ts
    │   ├── message-converter.ts
    │   ├── messages.ts
    │   └── prompts.ts
    ├── apply-style-to-element.ts
    ├── apply-theme.ts
    ├── color-converter.ts
    ├── contrast-checker.ts
    ├── debounce.ts
    ├── fonts/
    │   ├── google-fonts.ts
    │   └── index.ts
    ├── format.ts
    ├── parse-css-input.ts
    ├── registry/
    │   ├── tailwind-colors.ts
    │   ├── themes.ts
    │   └── v0.ts
    ├── shadows.ts
    ├── subscription.ts
    ├── theme-fonts.ts
    ├── theme-preset-helper.ts
    ├── theme-presets.ts
    ├── theme-style-generator.ts
    ├── theme-styles.ts
    └── try-catch.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
# Ignore node_modules — let Docker install fresh inside the image
node_modules

# Ignore environment files (not needed inside build context)
.env
.env.*



# OS-specific
.DS_Store

# Git stuff
.git
.gitignore




# Build artifacts
.next
dist
out



================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [jnsahaj]


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# bun lock
bun.lock

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem
.cursor

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# build artifacts
public/r/themes


================================================
FILE: .husky/pre-commit
================================================
npm run lint


================================================
FILE: .prettierignore
================================================
# Builds
.next/
dist/
build/
out/

# Dependencies
node_modules/

# Coverage and tests
coverage/
.nyc_output/

# Misc
.git/
.github/
.vscode/
.cursor/
.husky/

# Package files
pnpm-lock.yaml
package-lock.json
yarn.lock

# Generated files
public/
stubs/
drizzle/

# Config files that should maintain their format
*.config.js
*.config.mjs
*.config.cjs
*.config.ts

# Environment files
.env*

# Markdown files
*.md 

================================================
FILE: .prettierrc
================================================
{
  "semi": true,
  "singleQuote": false,
  "trailingComma": "es5",
  "tabWidth": 2,
  "printWidth": 100,
  "bracketSpacing": true,
  "arrowParens": "always",
  "endOfLine": "lf",
  "plugins": ["prettier-plugin-tailwindcss"],
  "pluginSearchDirs": false
} 

================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to tweakcn.com

Thanks for your interest in contributing to tweakcn.com! We're excited to have you here.

Please take a moment to review this document before submitting your first pull request. We also strongly recommend checking open [Issues](https://github.com/jnsahaj/tweakcn/issues) and [Pull Requests](https://github.com/jnsahaj/tweakcn/pulls) to see if someone else is working on something similar.

If you need any help or want to discuss ideas, feel free to join our community on [Discord](https://discord.com/invite/Phs4u2NM3n).

## About This Project

tweakcn.com is a powerful Visual Theme Editor designed for Tailwind CSS & shadcn/ui components. Websites built with shadcn/ui often share a similar look; tweakcn helps you visually customize these components to make your projects stand out.

## Project Structure

This repository contains the Next.js application for tweakcn.com. Here's a simplified overview of the project's directory structure:

```
├── actions/          # Next.js Server Actions
├── app/
    ├── (auth)/       # Authentication routes
    ├── (legal)/      # Legal pages (privacy policy)
    ├── api/          # Public API endpoints
    ├── dashboard/    # User dashboard (saved themes)
    ├── editor/       # Main theme editor route
    ├── layout.tsx    # Root application layout
    └── page.tsx      # Landing page route
├── components/
    ├── editor/       # Theme editor interface components
    ├── examples/     # Demo components for theme previews
    ├── home/         # Landing page components
    └── ui/           # Base shadcn/ui components
├── config/           # App configuration & default values
├── db/               # Database schema & logic (Drizzle ORM)
├── hooks/            # Custom React hooks
├── lib/              # 3rd-party library integrations & helpers
├── public/
    └── r/            # Holds JSON files for the theme registry
├── scripts/          # Utility scripts used during development
├── store/            # Global state management (Zustand)
└── utils/            # General utility functions and helpers
```

## How to Contribute

### Non-Technical

Even if you don't plan to write code, there are many ways to contribute:

- **Create an Issue:** If you find a bug, have an idea for a new feature, or want to suggest an improvement, please [create an issue on GitHub](https://github.com/jnsahaj/tweakcn/issues). This helps us track and prioritize feedback.
- **Spread the Word:** If you like tweakcn.com, please share it with your friends, colleagues, and on social media. Helping grow the community makes the tool better for everyone.
- **Use tweakcn.com:** The best feedback comes from real-world usage! As you use the editor, if you encounter any issues or have ideas for improvement, please let us know by creating an issue or reaching out on [Discord](https://discord.com/invite/Phs4u2NM3n).

### Prerequisites

- Node.js 18+
- npm / yarn / pnpm

### Installation

1.  **Fork the Repository:** Start by creating your own copy of the [tweakcn repository](https://github.com/jnsahaj/tweakcn) on GitHub. Click the "Fork" button in the top-right corner.

2.  **Clone Your Fork:** Clone the repository you just forked to your local machine:

    ```bash
    git clone https://github.com/YOUR_USERNAME/tweakcn.git
    cd tweakcn
    ```

    Replace `YOUR_USERNAME` with your actual GitHub username.

3.  **Install Dependencies:** Install the necessary project dependencies:

    ```bash
    npm install
    ```

### Set up the development environment (follow closely)

1.  **Configure Environment Variables:** 

    ```bash
    cp .env.example .env.local # Copy the example environment file
    ```
    - Open the `.env.local` file and replace the placeholder values with your actual credentials obtained from the services.

2.  **Apply Database Schema:** Push the database schema defined in `db/schema.ts` to your Neon database using Drizzle Kit:

    ```bash
    npx drizzle-kit push
    ```

    - _(Optional)_ You can view your database structure using Drizzle Studio by running `npx drizzle-kit studio`.

3.  **Create a New Branch:** Before making changes, create a dedicated branch for your feature or bug fix:

    ```bash
    git checkout -b your-descriptive-branch-name
    ```

    (e.g., `feature/add-community-gallery`, `fix/login-button-style`)

4.  **Start the Development Server:**

    ```bash
    npm run dev
    ```

5.  Open [http://localhost:3000](http://localhost:3000) in your browser.

You're now ready to start coding!

### Troubleshooting Setup

If you encounter unexpected issues, especially after pulling new changes or related to database/auth setup, try resetting your local environment:

1.  Stop the development server (Ctrl+C).

2.  Delete the `node_modules` and `.next` directories:

    ```bash
    # On macOS / Linux:
    rm -rf node_modules .next

    # On Windows (PowerShell):
    Remove-Item -Recurse -Force node_modules, .next
    ```

3.  Reinstall dependencies:

    ```bash
    npm install
    ```

4.  Re-run the database push command (optional, but good practice if schema might have changed):

    ```bash
    npx drizzle-kit push
    ```

5.  Restart the development server:

    ```bash
    npm run dev
    ```

## Submitting Your Changes (Pull Request Workflow)

Once you've made your changes and tested them locally, follow these steps to submit them for review:

1.  **Stage Your Changes:** Add the files you've modified to the Git staging area.

    ```bash
    git add .
    ```

2.  **Commit Your Changes:** Commit your staged changes with a descriptive message that follows the **Conventional Commits** specification. This helps automate releases and makes the commit history easier to understand.

    ```bash
    git commit -m "feat(editor): Add contrast checker component"
    ```

    - **Format:** `type(scope): description` (e.g., `fix(auth): Correct GitHub redirect URL`, `docs(readme): Update setup instructions`).
    - **Common Types:** `feat` (new feature), `fix` (bug fix), `docs` (documentation), `style` (code style), `chore` (build process, tooling).
    - Refer to the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) for more details.

3.  **Push to Your Fork:** Push your committed changes to the branch on your forked repository on GitHub.

    ```bash
    git push origin your-descriptive-branch-name
    ```

    Replace `your-descriptive-branch-name` with the actual name of your branch.

4.  **Open a Pull Request (PR):**

    - Go to the original [tweakcn repository](https://github.com/jnsahaj/tweakcn) on GitHub.
    - You should see a prompt suggesting you create a Pull Request from your recently pushed branch. Click on it. If not, navigate to the "Pull requests" tab and click "New pull request".
    - Ensure the base repository is `jnsahaj/tweakcn` and the base branch is `main` (or the appropriate target branch).
    - Ensure the head repository is your fork and the compare branch is `your-descriptive-branch-name`.
    - **Write a Clear Description:** Fill out the pull request template (if one exists). Provide a clear title and a detailed description of the changes you've made. Explain _why_ you made the changes and link to any relevant GitHub Issues (e.g., "Closes #123").

5.  **Review Process:**

    - Once submitted, maintainers will review your pull request.
    - Maintainers may provide feedback or request changes directly on the pull request. Please address these comments by pushing further commits to your branch.
    - Once approved, a maintainer will merge your changes into the main project.

Thank you for contributing!

================================================
FILE: Dockerfile
================================================
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "run", "dev"]


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
<div align="center">
  <h1>tweakcn.com</h1>
</div>

<div align="center">
  <a href="https://vercel.com/oss">
    <img alt="Vercel OSS Program" src="https://vercel.com/oss/program-badge.svg" />
  </a>
  <br />
  <br />
  <a href="https://discord.gg/Phs4u2NM3n" target="_blank">
    <img alt="Discord" src="https://img.shields.io/discord/1353416868769173576?style=for-the-badge&logo=discord&logoColor=%23ffffff">
  </a>
  <img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/jnsahaj/tweakcn?style=for-the-badge&logo=github">
  <a href="https://x.com/iamsahaj_xyz">
    <img alt="X (formerly Twitter) URL" src="https://img.shields.io/twitter/url?url=https%3A%2F%2Fx.com%2Fiamsahaj_xyz&style=for-the-badge&logo=x&label=%40iamsahaj_xyz&color=%2300000000" />
  </a>
</div>

<br />

**[tweakcn](https://tweakcn.com)** is a powerful Visual Theme Editor for tailwind CSS & shadcn/ui components. It comes with Beautiful theme presets to get started, while aiming to offer advanced customisation for each aspect of your UI

![tweakcn.com](public/og-image.v050725.png)

## Motivation

Websites made with shadcn/ui famously look the same. tweakcn is a tool that helps you customize shadcn/ui components visually, to make your components stand-out. The goal is to build a platform where a user can discover endless customization options and then have the ability to put their own twist on it.

## Current Features

You can find the full feature list here: https://tweakcn.com/#features

## Run Locally

**IMPORTANT: For contributions, please see [CONTRIBUTING.md](CONTRIBUTING.md).**

### Prerequisites

- Node.js 18+
- npm / yarn / pnpm

### Installation

1. Clone the repository:

```bash
git clone https://github.com/jnsahaj/tweakcn.git
cd tweakcn
```

2. Install dependencies:

```bash
npm install
```

3. Start the development server:

```bash
npm run dev
```

4. Open [http://localhost:3000](http://localhost:3000) in your browser.

## Contributors

<a href="https://github.com/jnsahaj/tweakcn/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=jnsahaj/tweakcn" />
</a>

Made with [contrib.rocks](https://contrib.rocks).

### Interested in Contributing?

Contributions are welcome! Please feel free to submit a Pull Request.

# Star History

<p align="center">
  <a target="_blank" href="https://star-history.com/#jnsahaj/tweakcn&Date">
    <picture>
      <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=jnsahaj/tweakcn&type=Date&theme=dark">
      <img alt="GitHub Star History for jnsahaj/tweakcn" src="https://api.star-history.com/svg?repos=jnsahaj/tweakcn&type=Date">
    </picture>
  </a>
</p>

<!-- GitAds-Verify: HX84XPI5OQ816367AROGJ9SROARUHQER -->


================================================
FILE: actions/account.ts
================================================
"use server";

import { db } from "@/db";
import { user as userTable, subscription } from "@/db/schema";
import { eq } from "drizzle-orm";
import { getCurrentUserId } from "@/lib/shared";
import { logError } from "@/lib/shared";
import { actionError, actionSuccess, ErrorCode, type ActionResult } from "@/types/errors";
import { polar } from "@/lib/polar";

export async function deleteAccount(): Promise<ActionResult<boolean>> {
  try {
    const userId = await getCurrentUserId();

    // Try to delete Polar customer (cancels subscriptions + revokes benefits)
    // Free users won't have a Polar customer, so we catch and ignore errors
    try {
      await polar.customers.deleteExternal({ externalId: userId });
    } catch (_e) {
      // Expected for free users — no Polar customer exists
    }

    // Delete subscription records (no CASCADE on this table)
    await db.delete(subscription).where(eq(subscription.userId, userId));

    // Delete user — CASCADE handles: sessions, accounts, themes,
    // communityThemes, communityThemeTags, themeLikes, aiUsage,
    // oauthAuthorizationCode, oauthToken
    await db.delete(userTable).where(eq(userTable.id, userId));

    return actionSuccess(true);
  } catch (error) {
    logError(error as Error, { action: "deleteAccount" });
    return actionError(ErrorCode.UNKNOWN_ERROR, "Failed to delete account. Please try again.");
  }
}


================================================
FILE: actions/ai-usage.ts
================================================
"use server";

import { db } from "@/db";
import { aiUsage } from "@/db/schema";
import { getCurrentUserId } from "@/lib/shared";
import { ValidationError } from "@/types/errors";
import cuid from "cuid";
import { and, count, eq, gte } from "drizzle-orm";
import { z } from "zod";

const getDaysSinceEpoch = (daysAgo: number = 0) =>
  Math.floor(Date.now() / (24 * 60 * 60 * 1000)) - daysAgo;

// Schema for recording usage events (internal use - still tracks tokens)
const recordUsageSchema = z.object({
  promptTokens: z.number().min(0).default(0),
  completionTokens: z.number().min(0).default(0),
  modelId: z.string(),
});

// Schema for timeframe validation
const timeframeSchema = z.union([z.literal("1d"), z.literal("7d"), z.literal("30d")]);

// Types
type Timeframe = z.infer<typeof timeframeSchema>;

// Simplified user-facing interface - only shows requests
interface UsageStats {
  requests: number;
  timeframe: Timeframe;
}

interface ChartDataPoint {
  daysSinceEpoch?: number;
  hoursSinceEpoch?: number;
  date: string;
  totalRequests: number;
}

export async function recordAIUsage(input: {
  modelId: string;
  promptTokens?: number;
  completionTokens?: number;
}) {
  try {
    const userId = await getCurrentUserId();

    const validation = recordUsageSchema.safeParse(input);
    if (!validation.success) {
      throw new ValidationError("Invalid usage data", validation.error.format());
    }

    const { promptTokens, completionTokens, modelId } = validation.data;
    const daysSinceEpoch = getDaysSinceEpoch(0);

    const [insertedUsage] = await db
      .insert(aiUsage)
      .values({
        id: cuid(),
        userId,
        modelId,
        promptTokens: promptTokens.toString(),
        completionTokens: completionTokens.toString(),
        daysSinceEpoch: daysSinceEpoch.toString(),
        createdAt: new Date(),
      })
      .returning();

    return insertedUsage;
  } catch (error) {
    console.error("Error recording usage:", error);
    throw error;
  }
}

export async function getMyUsageStats(timeframe: Timeframe): Promise<UsageStats> {
  try {
    const userId = await getCurrentUserId();

    const validation = timeframeSchema.safeParse(timeframe);
    if (!validation.success) {
      throw new ValidationError("Invalid timeframe");
    }

    const days = timeframe === "1d" ? 1 : timeframe === "7d" ? 7 : 30;
    const startDay = getDaysSinceEpoch(days);

    // Get user's events in time range
    const events = await db
      .select()
      .from(aiUsage)
      .where(and(eq(aiUsage.userId, userId), gte(aiUsage.daysSinceEpoch, startDay.toString())));

    return {
      requests: events.length,
      timeframe,
    };
  } catch (error) {
    console.error("Error getting usage stats:", error);
    throw error;
  }
}

export async function getMyAllTimeRequestCount(userId: string): Promise<number> {
  try {
    const result = await db
      .select({ count: count() })
      .from(aiUsage)
      .where(eq(aiUsage.userId, userId));

    return result[0]?.count ?? 0;
  } catch (error) {
    console.error("Error getting all-time request count:", error);
    throw error;
  }
}

export async function getMyUsageChartData(timeframe: Timeframe): Promise<ChartDataPoint[]> {
  try {
    const userId = await getCurrentUserId();

    const validation = timeframeSchema.safeParse(timeframe);
    if (!validation.success) {
      throw new ValidationError("Invalid timeframe");
    }

    // For 1d, we want hourly granularity
    if (timeframe === "1d") {
      const hours = 24;
      const startTime = Date.now() - hours * 60 * 60 * 1000;

      // Get user's events in the last 24 hours
      const events = await db
        .select()
        .from(aiUsage)
        .where(and(eq(aiUsage.userId, userId), gte(aiUsage.createdAt, new Date(startTime))));

      // Group by hour
      const chartData: ChartDataPoint[] = [];
      for (let i = hours - 1; i >= 0; i--) {
        const hourStart = Date.now() - i * 60 * 60 * 1000;
        const hourEnd = Date.now() - (i - 1) * 60 * 60 * 1000;
        const hourEvents = events.filter(
          (e) => e.createdAt.getTime() >= hourStart && e.createdAt.getTime() < hourEnd
        );

        const totalRequests = hourEvents.length;

        chartData.push({
          hoursSinceEpoch: Math.floor(hourStart / (60 * 60 * 1000)),
          date: new Date(hourStart).toISOString(),
          totalRequests,
        });
      }

      return chartData;
    }

    // Daily logic for 7d and 30d
    const days = timeframe === "7d" ? 7 : 30;
    const startDay = getDaysSinceEpoch(days);

    // Get user's events in time range
    const events = await db
      .select()
      .from(aiUsage)
      .where(and(eq(aiUsage.userId, userId), gte(aiUsage.daysSinceEpoch, startDay.toString())));

    // Group by day
    const chartData: ChartDataPoint[] = [];
    for (let i = days - 1; i >= 0; i--) {
      const daysSince = getDaysSinceEpoch(i);
      const dayEvents = events.filter((e) => parseInt(e.daysSinceEpoch) === daysSince);

      const totalRequests = dayEvents.length;

      chartData.push({
        daysSinceEpoch: daysSince,
        date: new Date(daysSince * 24 * 60 * 60 * 1000).toISOString().split("T")[0],
        totalRequests,
      });
    }

    return chartData;
  } catch (error) {
    console.error("Error getting usage chart data:", error);
    throw error;
  }
}

// Internal function for detailed usage (including tokens) - not exposed to users
export async function getDetailedUsageStats(
  timeframe: Timeframe,
  modelId: string
): Promise<{
  requests: number;
  promptTokens: number;
  completionTokens: number;
  totalTokens: number;
  timeframe: Timeframe;
}> {
  try {
    const userId = await getCurrentUserId();

    const validation = timeframeSchema.safeParse(timeframe);
    if (!validation.success) {
      throw new ValidationError("Invalid timeframe");
    }

    const days = timeframe === "1d" ? 1 : timeframe === "7d" ? 7 : 30;
    const startDay = getDaysSinceEpoch(days);

    // Get user's events for the model
    const events = await db
      .select()
      .from(aiUsage)
      .where(
        and(
          eq(aiUsage.userId, userId),
          eq(aiUsage.modelId, modelId),
          gte(aiUsage.daysSinceEpoch, startDay.toString())
        )
      );

    const requests = events.length;
    const promptTokens = events.reduce((sum, e) => sum + parseInt(e.promptTokens), 0);
    const completionTokens = events.reduce((sum, e) => sum + parseInt(e.completionTokens), 0);
    const totalTokens = promptTokens + completionTokens;

    return {
      requests,
      promptTokens,
      completionTokens,
      totalTokens,
      timeframe,
    };
  } catch (error) {
    console.error("Error getting detailed usage stats:", error);
    throw error;
  }
}


================================================
FILE: actions/checkout.ts
================================================
"use server";

import { polar } from "@/lib/polar";
import { getCurrentUser, logError } from "@/lib/shared";
import { getOrCreateCustomer } from "./customer";

export const createCheckout = async () => {
  try {
    const user = await getCurrentUser();
    const customer = await getOrCreateCustomer(user);
    const checkout = await polar.checkouts.create({
      products: [process.env.NEXT_PUBLIC_TWEAKCN_PRO_PRODUCT_ID!],
      customerId: customer?.id,
      successUrl: `${process.env.BASE_URL}/success?checkout_id={CHECKOUT_ID}`,
    });

    return { url: checkout.url };
  } catch (error) {
    logError(error as Error, { action: "createCheckout" });
    return { error: "Failed to create checkout" };
  }
};


================================================
FILE: actions/community-themes.ts
================================================
"use server";

import { z } from "zod";
import { db } from "@/db";
import {
  communityTheme,
  communityThemeTag,
  themeLike,
  theme as themeTable,
  user as userTable,
} from "@/db/schema";
import { eq, and, desc, asc, sql, count, inArray } from "drizzle-orm";
import cuid from "cuid";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import {
  UnauthorizedError,
  ValidationError,
  ThemeNotFoundError,
  ErrorCode,
  actionError,
  actionSuccess,
  type ActionResult,
} from "@/types/errors";
import {
  COMMUNITY_THEMES_PAGE_SIZE,
  COMMUNITY_THEME_TAGS,
  MAX_TAGS_PER_THEME,
} from "@/lib/constants";
import type {
  CommunityTheme,
  CommunitySortOption,
  CommunityFilterOption,
  CommunityTimeRange,
  CommunityThemesResponse,
} from "@/types/community";
import { unstable_cache, revalidateTag } from "next/cache";
import { Ratelimit } from "@upstash/ratelimit";
import { kv } from "@vercel/kv";

const ratelimit = new Ratelimit({
  redis: kv,
  limiter: Ratelimit.fixedWindow(5, "3600s"),
});

async function getOptionalUserId(): Promise<string | null> {
  try {
    const session = await auth.api.getSession({
      headers: await headers(),
    });
    return session?.user?.id ?? null;
  } catch {
    return null;
  }
}

async function getCurrentUserId(): Promise<string> {
  const session = await auth.api.getSession({
    headers: await headers(),
  });

  if (!session?.user?.id) {
    throw new UnauthorizedError();
  }

  return session.user.id;
}

function logError(error: Error, context: Record<string, unknown>) {
  if (error.name === "UnauthorizedError" || error.name === "ValidationError") {
    console.warn("Expected error:", { error: error.message, context });
  } else {
    console.error("Unexpected error:", {
      error: error.message,
      stack: error.stack,
      context,
    });
  }
}

const getCommunityThemesSchema = z.object({
  sort: z.enum(["popular", "newest", "oldest"]).default("popular"),
  cursor: z.union([z.string(), z.number()]).optional(),
  limit: z.number().min(1).max(50).default(COMMUNITY_THEMES_PAGE_SIZE),
  filter: z.enum(["all", "mine", "liked"]).default("all"),
  tags: z.array(z.string()).default([]),
  timeRange: z.enum(["weekly", "monthly", "all"]).default("all"),
});

// Core query logic for community themes (no headers() access — cacheable)
async function fetchCommunityThemesCore(
  sort: string,
  cursor: string | number | null,
  limit: number,
  filter: string,
  tags: string[],
  userId: string | null,
  timeRange: string = "all"
): Promise<CommunityThemesResponse> {
  const fetchLimit = limit + 1;
  const conditions = [];

  if (filter === "mine") {
    if (!userId) throw new UnauthorizedError();
    conditions.push(eq(communityTheme.userId, userId));
  }

  if (filter === "liked") {
    if (!userId) throw new UnauthorizedError();
    conditions.push(
      sql`exists(select 1 from theme_like where theme_like.theme_id = ${communityTheme.id} and theme_like.user_id = ${userId})`
    );
  }

  if (tags.length > 0) {
    conditions.push(
      sql`exists(select 1 from community_theme_tag where community_theme_tag.community_theme_id = ${communityTheme.id} and community_theme_tag.tag in (${sql.join(
        tags.map((t) => sql`${t}`),
        sql`, `
      )}))`
    );
  }

  // For weekly/monthly popular sort, filter to themes published within the time range
  if (sort === "popular" && timeRange !== "all") {
    const intervalSql =
      timeRange === "weekly"
        ? sql`interval '7 days'`
        : sql`interval '30 days'`;
    conditions.push(
      sql`${communityTheme.publishedAt} > now() - ${intervalSql}`
    );
  }

  const selectFields = {
    id: communityTheme.id,
    themeId: communityTheme.themeId,
    publishedAt: communityTheme.publishedAt,
    themeName: themeTable.name,
    themeStyles: themeTable.styles,
    authorId: userTable.id,
    authorName: userTable.name,
    authorImage: userTable.image,
    likeCount: communityTheme.likeCount,
    ...(userId
      ? {
          isLikedByMe: sql<boolean>`exists(
            select 1 from theme_like
            where theme_like.theme_id = ${communityTheme.id}
            and theme_like.user_id = ${userId}
          )`.as("is_liked_by_me"),
        }
      : {}),
  };

  const baseQuery = db
    .select(selectFields)
    .from(communityTheme)
    .innerJoin(themeTable, eq(communityTheme.themeId, themeTable.id))
    .innerJoin(userTable, eq(communityTheme.userId, userTable.id));

  let results;

  if (sort === "popular") {
    const offset = typeof cursor === "number" ? cursor : 0;
    results = await baseQuery
      .where(conditions.length > 0 ? and(...conditions) : undefined)
      .orderBy(desc(communityTheme.likeCount), desc(communityTheme.publishedAt))
      .limit(fetchLimit)
      .offset(offset);
  } else if (sort === "newest") {
    if (cursor && typeof cursor === "string") {
      conditions.push(sql`${communityTheme.publishedAt} < ${cursor}`);
    }
    results = await baseQuery
      .where(conditions.length > 0 ? and(...conditions) : undefined)
      .orderBy(desc(communityTheme.publishedAt))
      .limit(fetchLimit);
  } else {
    if (cursor && typeof cursor === "string") {
      conditions.push(sql`${communityTheme.publishedAt} > ${cursor}`);
    }
    results = await baseQuery
      .where(conditions.length > 0 ? and(...conditions) : undefined)
      .orderBy(asc(communityTheme.publishedAt))
      .limit(fetchLimit);
  }

  const hasMore = results.length > limit;
  const themes = results.slice(0, limit);

  let nextCursor: string | number | null = null;
  if (hasMore) {
    if (sort === "popular") {
      nextCursor = (typeof cursor === "number" ? cursor : 0) + limit;
    } else {
      const lastTheme = themes[themes.length - 1];
      nextCursor = lastTheme.publishedAt.toISOString();
    }
  }

  const communityThemeIds = themes.map((t) => t.id);
  const tagsRows =
    communityThemeIds.length > 0
      ? await db
          .select({
            communityThemeId: communityThemeTag.communityThemeId,
            tag: communityThemeTag.tag,
          })
          .from(communityThemeTag)
          .where(
            inArray(communityThemeTag.communityThemeId, communityThemeIds)
          )
      : [];

  const tagsMap = new Map<string, string[]>();
  for (const row of tagsRows) {
    const existing = tagsMap.get(row.communityThemeId) ?? [];
    existing.push(row.tag);
    tagsMap.set(row.communityThemeId, existing);
  }

  const mappedThemes: CommunityTheme[] = themes.map((row) => ({
    id: row.id,
    themeId: row.themeId,
    name: row.themeName,
    styles: row.themeStyles,
    author: {
      id: row.authorId,
      name: row.authorName,
      image: row.authorImage,
    },
    likeCount: Number(row.likeCount),
    isLikedByMe: "isLikedByMe" in row ? Boolean(row.isLikedByMe) : false,
    publishedAt: row.publishedAt.toISOString(),
    tags: tagsMap.get(row.id) ?? [],
  }));

  return { themes: mappedThemes, nextCursor };
}

const getCachedCommunityThemes = unstable_cache(
  fetchCommunityThemesCore,
  ["community-themes"],
  { revalidate: 60, tags: ["community-themes"] }
);

export async function getCommunityThemes(
  sort: CommunitySortOption = "popular",
  cursor?: string | number,
  limit: number = COMMUNITY_THEMES_PAGE_SIZE,
  filter: CommunityFilterOption = "all",
  tags: string[] = [],
  timeRange: CommunityTimeRange = "all"
): Promise<CommunityThemesResponse> {
  try {
    const validation = getCommunityThemesSchema.safeParse({
      sort,
      cursor,
      limit,
      filter,
      tags,
      timeRange,
    });
    if (!validation.success) {
      throw new ValidationError("Invalid input", validation.error.format());
    }

    const userId = await getOptionalUserId();
    return getCachedCommunityThemes(
      sort,
      cursor ?? null,
      limit,
      filter,
      tags,
      userId,
      timeRange
    );
  } catch (error) {
    logError(error as Error, { action: "getCommunityThemes", sort, cursor });
    throw error;
  }
}

export async function publishTheme(
  themeId: string,
  tags: string[] = []
): Promise<ActionResult<{ id: string }>> {
  try {
    const userId = await getCurrentUserId();

    if (!themeId) {
      throw new ValidationError("Theme ID required");
    }

    // Validate tags
    if (tags.length > MAX_TAGS_PER_THEME) {
      throw new ValidationError(
        `You can select at most ${MAX_TAGS_PER_THEME} tags`
      );
    }
    const validTags = tags.filter((t): t is string =>
      (COMMUNITY_THEME_TAGS as readonly string[]).includes(t)
    );

    // Verify theme ownership
    const [existingTheme] = await db
      .select()
      .from(themeTable)
      .where(and(eq(themeTable.id, themeId), eq(themeTable.userId, userId)))
      .limit(1);

    if (!existingTheme) {
      throw new ThemeNotFoundError("Theme not found or not owned by user");
    }

    // Check not already published
    const [existing] = await db
      .select()
      .from(communityTheme)
      .where(eq(communityTheme.themeId, themeId))
      .limit(1);

    if (existing) {
      return actionError(
        ErrorCode.ALREADY_PUBLISHED,
        "This theme is already published to the community."
      );
    }

    // Rate limit
    if (process.env.NODE_ENV !== "development") {
      const { success } = await ratelimit.limit(`publish:${userId}`);
      if (!success) {
        return actionError(
          ErrorCode.UNKNOWN_ERROR,
          "You're publishing too fast. Please try again later."
        );
      }
    }

    const id = cuid();
    await db.insert(communityTheme).values({
      id,
      themeId,
      userId,
      publishedAt: new Date(),
    });

    if (validTags.length > 0) {
      try {
        await db.insert(communityThemeTag).values(
          validTags.map((tag) => ({
            communityThemeId: id,
            tag,
          }))
        );
      } catch (tagError) {
        // Roll back the community theme row if tags insert fails
        await db.delete(communityTheme).where(eq(communityTheme.id, id));
        throw tagError;
      }
    }

    revalidateTag("community-themes");
    revalidateTag("community-tag-counts");

    return actionSuccess({ id });
  } catch (error) {
    logError(error as Error, { action: "publishTheme", themeId });
    throw error;
  }
}

export async function unpublishTheme(
  themeId: string
): Promise<ActionResult<{ success: boolean }>> {
  try {
    const userId = await getCurrentUserId();

    if (!themeId) {
      throw new ValidationError("Theme ID required");
    }

    const [deleted] = await db
      .delete(communityTheme)
      .where(
        and(
          eq(communityTheme.themeId, themeId),
          eq(communityTheme.userId, userId)
        )
      )
      .returning({ id: communityTheme.id });

    if (!deleted) {
      throw new ThemeNotFoundError(
        "Published theme not found or not owned by user"
      );
    }

    revalidateTag("community-themes");
    revalidateTag("community-tag-counts");

    return actionSuccess({ success: true });
  } catch (error) {
    logError(error as Error, { action: "unpublishTheme", themeId });
    throw error;
  }
}

export async function toggleLikeTheme(
  communityThemeId: string
): Promise<ActionResult<{ liked: boolean; likeCount: number }>> {
  try {
    const userId = await getCurrentUserId();

    if (!communityThemeId) {
      throw new ValidationError("Community theme ID required");
    }

    // Check if already liked
    const [existingLike] = await db
      .select()
      .from(themeLike)
      .where(
        and(
          eq(themeLike.userId, userId),
          eq(themeLike.themeId, communityThemeId)
        )
      )
      .limit(1);

    if (existingLike) {
      // Unlike: delete + decrement
      await db
        .delete(themeLike)
        .where(
          and(
            eq(themeLike.userId, userId),
            eq(themeLike.themeId, communityThemeId)
          )
        );
      const [updated] = await db
        .update(communityTheme)
        .set({
          likeCount: sql`GREATEST(${communityTheme.likeCount} - 1, 0)`,
        })
        .where(eq(communityTheme.id, communityThemeId))
        .returning({ likeCount: communityTheme.likeCount });

      revalidateTag("community-themes");

      return actionSuccess({
        liked: false,
        likeCount: updated.likeCount,
      });
    } else {
      // Like: insert + increment
      await db.insert(themeLike).values({
        userId,
        themeId: communityThemeId,
        createdAt: new Date(),
      });
      const [updated] = await db
        .update(communityTheme)
        .set({ likeCount: sql`${communityTheme.likeCount} + 1` })
        .where(eq(communityTheme.id, communityThemeId))
        .returning({ likeCount: communityTheme.likeCount });

      revalidateTag("community-themes");

      return actionSuccess({
        liked: true,
        likeCount: updated.likeCount,
      });
    }
  } catch (error) {
    logError(error as Error, {
      action: "toggleLikeTheme",
      communityThemeId,
    });
    throw error;
  }
}

async function fetchCommunityDataForThemeCore(
  themeId: string,
  userId: string | null
): Promise<{
  communityThemeId: string;
  author: { id: string; name: string; image: string | null };
  likeCount: number;
  isLikedByMe: boolean;
  publishedAt: string;
  tags: string[];
} | null> {
  const [result] = await db
    .select({
      id: communityTheme.id,
      publishedAt: communityTheme.publishedAt,
      authorId: userTable.id,
      authorName: userTable.name,
      authorImage: userTable.image,
      likeCount: communityTheme.likeCount,
      ...(userId
        ? {
            isLikedByMe: sql<boolean>`exists(
              select 1 from theme_like
              where theme_like.theme_id = ${communityTheme.id}
              and theme_like.user_id = ${userId}
            )`.as("is_liked_by_me"),
          }
        : {}),
    })
    .from(communityTheme)
    .innerJoin(userTable, eq(communityTheme.userId, userTable.id))
    .where(eq(communityTheme.themeId, themeId))
    .limit(1);

  if (!result) return null;

  const tagRows = await db
    .select({ tag: communityThemeTag.tag })
    .from(communityThemeTag)
    .where(eq(communityThemeTag.communityThemeId, result.id));

  return {
    communityThemeId: result.id,
    author: {
      id: result.authorId,
      name: result.authorName,
      image: result.authorImage,
    },
    likeCount: Number(result.likeCount),
    isLikedByMe:
      "isLikedByMe" in result ? Boolean(result.isLikedByMe) : false,
    publishedAt: result.publishedAt.toISOString(),
    tags: tagRows.map((r) => r.tag),
  };
}

const getCachedCommunityDataForTheme = unstable_cache(
  fetchCommunityDataForThemeCore,
  ["community-theme-data"],
  { revalidate: 60, tags: ["community-themes"] }
);

export async function getCommunityDataForTheme(
  themeId: string
): Promise<{
  communityThemeId: string;
  author: { id: string; name: string; image: string | null };
  likeCount: number;
  isLikedByMe: boolean;
  publishedAt: string;
  tags: string[];
} | null> {
  try {
    const userId = await getOptionalUserId();
    return getCachedCommunityDataForTheme(themeId, userId);
  } catch (error) {
    logError(error as Error, {
      action: "getCommunityDataForTheme",
      themeId,
    });
    return null;
  }
}

export async function getMyPublishedThemeIds(): Promise<string[]> {
  try {
    const userId = await getCurrentUserId();

    const published = await db
      .select({ themeId: communityTheme.themeId })
      .from(communityTheme)
      .where(eq(communityTheme.userId, userId));

    return published.map((p) => p.themeId);
  } catch (error) {
    logError(error as Error, { action: "getMyPublishedThemeIds" });
    throw error;
  }
}

export async function updateCommunityThemeTags(
  themeId: string,
  tags: string[]
): Promise<ActionResult<{ tags: string[] }>> {
  try {
    const userId = await getCurrentUserId();

    if (!themeId) {
      throw new ValidationError("Theme ID required");
    }

    if (tags.length > MAX_TAGS_PER_THEME) {
      throw new ValidationError(
        `You can select at most ${MAX_TAGS_PER_THEME} tags`
      );
    }

    const validTags = tags.filter((t): t is string =>
      (COMMUNITY_THEME_TAGS as readonly string[]).includes(t)
    );

    // Verify ownership via the community_theme row
    const [ct] = await db
      .select({ id: communityTheme.id })
      .from(communityTheme)
      .where(
        and(
          eq(communityTheme.themeId, themeId),
          eq(communityTheme.userId, userId)
        )
      )
      .limit(1);

    if (!ct) {
      throw new ThemeNotFoundError(
        "Published theme not found or not owned by user"
      );
    }

    // Delete existing tags and insert new ones
    await db
      .delete(communityThemeTag)
      .where(eq(communityThemeTag.communityThemeId, ct.id));

    if (validTags.length > 0) {
      await db.insert(communityThemeTag).values(
        validTags.map((tag) => ({
          communityThemeId: ct.id,
          tag,
        }))
      );
    }

    revalidateTag("community-themes");
    revalidateTag("community-tag-counts");

    return actionSuccess({ tags: validTags });
  } catch (error) {
    logError(error as Error, {
      action: "updateCommunityThemeTags",
      themeId,
    });
    throw error;
  }
}

const getCachedTagCounts = unstable_cache(
  async () => {
    const rows = await db
      .select({
        tag: communityThemeTag.tag,
        count: count().as("tag_count"),
      })
      .from(communityThemeTag)
      .groupBy(communityThemeTag.tag)
      .orderBy(sql`tag_count desc`);

    return rows.map((r) => ({ tag: r.tag, count: Number(r.count) }));
  },
  ["community-tag-counts"],
  { revalidate: 300, tags: ["community-tag-counts"] }
);

export async function getCommunityTagCounts(): Promise<
  { tag: string; count: number }[]
> {
  try {
    return getCachedTagCounts();
  } catch (error) {
    logError(error as Error, { action: "getCommunityTagCounts" });
    return [];
  }
}


================================================
FILE: actions/customer.ts
================================================
import "server-only";

import { polar } from "@/lib/polar";
import { logError } from "@/lib/shared";
import { Customer } from "@polar-sh/sdk/models/components/customer.js";
import { User } from "better-auth";

export const getOrCreateCustomer = async (user: User) => {
  let customer: Customer | null = null;

  try {
    customer = await polar.customers.getExternal({ externalId: user.id });
  } catch (_e) {
    customer = null;
  }

  if (customer) return customer;

  try {
    const newCustomer = await polar.customers.create({
      email: user.email,
      externalId: user.id,
      name: user.name,
    });

    return newCustomer;
  } catch (err) {
    logError(err as Error, { action: "createCustomer", user });
  }

  return null;
};


================================================
FILE: actions/themes.ts
================================================
"use server";

import { z } from "zod";
import { db } from "@/db";
import { theme as themeTable, communityTheme } from "@/db/schema";
import { eq, and, sql } from "drizzle-orm";
import cuid from "cuid";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { themeStylesSchema, type ThemeStyles } from "@/types/theme";
import { cache } from "react";
import {
  UnauthorizedError,
  ValidationError,
  ThemeNotFoundError,
  ErrorCode,
  actionError,
  actionSuccess,
  type ActionResult,
} from "@/types/errors";
import { MAX_FREE_THEMES } from "@/lib/constants";
import { getMyActiveSubscription } from "@/lib/subscription";

// Helper to get user ID with better error handling
async function getCurrentUserId(): Promise<string> {
  const session = await auth.api.getSession({
    headers: await headers(),
  });

  if (!session?.user?.id) {
    throw new UnauthorizedError();
  }

  return session.user.id;
}

// Log errors for observability
function logError(error: Error, context: Record<string, any>) {
  console.error("Theme action error:", error, context);

  // TODO: Add server-side error reporting to PostHog or your preferred service
  // For production, you'd want to send critical errors to an external service
  if (error.name === "UnauthorizedError" || error.name === "ValidationError") {
    // These are expected errors, log but don't report
    console.warn("Expected error:", { error: error.message, context });
  } else {
    // Unexpected errors should be reported
    console.error("Unexpected error:", { error: error.message, stack: error.stack, context });
  }
}

const createThemeSchema = z.object({
  name: z.string().min(1, "Theme name cannot be empty").max(50, "Theme name too long"),
  styles: themeStylesSchema,
});

const updateThemeSchema = z.object({
  id: z.string().min(1, "Theme ID required"),
  name: z.string().min(1, "Theme name cannot be empty").max(50, "Theme name too long").optional(),
  styles: themeStylesSchema.optional(),
});

// Layer 1: Clean server actions with proper error handling
export async function getThemes() {
  try {
    const userId = await getCurrentUserId();
    const userThemes = await db
      .select({
        id: themeTable.id,
        userId: themeTable.userId,
        name: themeTable.name,
        styles: themeTable.styles,
        createdAt: themeTable.createdAt,
        updatedAt: themeTable.updatedAt,
        isPublished: sql<boolean>`${communityTheme.id} is not null`.as(
          "is_published"
        ),
      })
      .from(themeTable)
      .leftJoin(communityTheme, eq(themeTable.id, communityTheme.themeId))
      .where(eq(themeTable.userId, userId));
    return userThemes;
  } catch (error) {
    logError(error as Error, { action: "getThemes" });
    throw error;
  }
}

export const getTheme = cache(async (themeId: string) => {
  try {
    if (!themeId) {
      throw new ValidationError("Theme ID required");
    }

    const [theme] = await db.select().from(themeTable).where(eq(themeTable.id, themeId)).limit(1);

    if (!theme) {
      throw new ThemeNotFoundError();
    }

    return theme;
  } catch (error) {
    logError(error as Error, { action: "getTheme", themeId });
    throw error;
  }
});

export async function createTheme(formData: { name: string; styles: ThemeStyles }) {
  try {
    const userId = await getCurrentUserId();

    const validation = createThemeSchema.safeParse(formData);
    if (!validation.success) {
      throw new ValidationError("Invalid input", validation.error.format());
    }

    // Check theme limit
    const userThemes = await db.select().from(themeTable).where(eq(themeTable.userId, userId));

    if (userThemes.length >= MAX_FREE_THEMES) {
      const activeSubscription = await getMyActiveSubscription(userId);
      const isSubscribed =
        !!activeSubscription &&
        activeSubscription?.productId === process.env.NEXT_PUBLIC_TWEAKCN_PRO_PRODUCT_ID;

      if (!isSubscribed) {
        return actionError(
          ErrorCode.THEME_LIMIT_REACHED,
          `You have reached the limit of ${MAX_FREE_THEMES} themes.`
        );
      }
    }

    const { name, styles } = validation.data;
    const newThemeId = cuid();
    const now = new Date();

    const [insertedTheme] = await db
      .insert(themeTable)
      .values({
        id: newThemeId,
        userId: userId,
        name: name,
        styles: styles,
        createdAt: now,
        updatedAt: now,
      })
      .returning();

    return actionSuccess(insertedTheme);
  } catch (error) {
    logError(error as Error, { action: "createTheme", formData: { name: formData.name } });
    throw error;
  }
}

export async function updateTheme(formData: { id: string; name?: string; styles?: ThemeStyles }) {
  try {
    const userId = await getCurrentUserId();

    const validation = updateThemeSchema.safeParse(formData);
    if (!validation.success) {
      throw new ValidationError("Invalid input", validation.error.format());
    }

    const { id: themeId, name, styles } = validation.data;

    if (!name && !styles) {
      throw new ValidationError("No update data provided");
    }

    const updateData: Partial<typeof themeTable.$inferInsert> = {
      updatedAt: new Date(),
    };
    if (name) updateData.name = name;
    if (styles) updateData.styles = styles;

    const [updatedTheme] = await db
      .update(themeTable)
      .set(updateData)
      .where(and(eq(themeTable.id, themeId), eq(themeTable.userId, userId)))
      .returning();

    if (!updatedTheme) {
      throw new ThemeNotFoundError("Theme not found or not owned by user");
    }

    return updatedTheme;
  } catch (error) {
    logError(error as Error, { action: "updateTheme", themeId: formData.id });
    throw error;
  }
}

export async function deleteTheme(themeId: string) {
  try {
    const userId = await getCurrentUserId();

    if (!themeId) {
      throw new ValidationError("Theme ID required");
    }

    const [deletedTheme] = await db
      .delete(themeTable)
      .where(and(eq(themeTable.id, themeId), eq(themeTable.userId, userId)))
      .returning({ id: themeTable.id, name: themeTable.name });

    if (!deletedTheme) {
      throw new ThemeNotFoundError("Theme not found or not owned by user");
    }

    return deletedTheme;
  } catch (error) {
    logError(error as Error, { action: "deleteTheme", themeId });
    throw error;
  }
}


================================================
FILE: app/(auth)/components/auth-dialog.tsx
================================================
"use client";

import Github from "@/assets/github.svg";
import Google from "@/assets/google.svg";
import { Button } from "@/components/ui/button";
import {
  ResponsiveDialog,
  ResponsiveDialogContent,
  ResponsiveDialogHeader,
  ResponsiveDialogTitle,
  ResponsiveDialogTrigger,
} from "@/components/ui/revola";
import { PostLoginActionType } from "@/hooks/use-post-login-action";
import { authClient } from "@/lib/auth-client";
import { Loader2 } from "lucide-react";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";

interface AuthDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  initialMode?: "signin" | "signup";
  trigger?: React.ReactNode; // Optional trigger element
  postLoginActionType?: PostLoginActionType | null;
}

// Get contextual copy based on the post-login action
function getContextualCopy(actionType?: PostLoginActionType | null) {
  switch (actionType) {
    case "SAVE_THEME":
      return {
        title: "Sign in to Save",
        description: "Sign in to save your theme and access it from anywhere",
      };
    case "SAVE_THEME_FOR_SHARE":
      return {
        title: "Sign in to Share",
        description: "Sign in to save and share your theme with others",
      };
    case "SAVE_THEME_FOR_V0":
      return {
        title: "Sign in to open in v0",
        description: "Sign in to save your theme and open it in v0",
      };
    case "AI_GENERATE_FROM_PAGE":
    case "AI_GENERATE_FROM_CHAT":
    case "AI_GENERATE_FROM_CHAT_SUGGESTION":
    case "AI_GENERATE_EDIT":
    case "AI_GENERATE_RETRY":
      return {
        title: "Sign in for AI",
        description: "Sign in to use AI-powered theme generation",
      };
    case "CHECKOUT":
      return {
        title: "Sign in to continue",
        description: "Sign in to complete your purchase",
      };
    default:
      return null;
  }
}

export function AuthDialog({
  open,
  onOpenChange,
  initialMode = "signin",
  trigger,
  postLoginActionType,
}: AuthDialogProps) {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const [isSignIn, setIsSignIn] = useState(initialMode === "signin");
  const [isGoogleLoading, setIsGoogleLoading] = useState(false);
  const [isGithubLoading, setIsGithubLoading] = useState(false);

  const contextualCopy = getContextualCopy(postLoginActionType);

  const getCallbackUrl = () => {
    const baseUrl = pathname || "/editor/theme";
    const queryString = searchParams.toString();
    return queryString ? `${baseUrl}?${queryString}` : baseUrl;
  };

  useEffect(() => {
    if (open) {
      setIsSignIn(initialMode === "signin");
    }
  }, [open, initialMode]);

  const handleGoogleSignIn = async () => {
    setIsGoogleLoading(true);
    try {
      await authClient.signIn.social({
        provider: "google",
        callbackURL: getCallbackUrl(),
      });
    } catch (error) {
      console.error("Google Sign In Error:", error);
      // Handle error appropriately (e.g., show a toast notification)
    }
  };

  const handleGithubSignIn = async () => {
    setIsGithubLoading(true);
    try {
      await authClient.signIn.social({
        provider: "github",
        callbackURL: getCallbackUrl(),
      });
    } catch (error) {
      console.error("GitHub Sign In Error:", error);
      // Handle error appropriately
    }
  };

  const toggleMode = () => {
    setIsSignIn(!isSignIn);
  };

  return (
    <ResponsiveDialog open={open} onOpenChange={onOpenChange}>
      {trigger && <ResponsiveDialogTrigger asChild>{trigger}</ResponsiveDialogTrigger>}
      <ResponsiveDialogContent className="overflow-hidden sm:max-w-100">
        <div className="space-y-4">
          <ResponsiveDialogHeader className="sm:pt-8">
            <ResponsiveDialogTitle className="text-center text-2xl font-bold">
              {contextualCopy?.title ?? (isSignIn ? "Welcome back" : "Create account")}
            </ResponsiveDialogTitle>
            <p className="text-muted-foreground text-center">
              {contextualCopy?.description ??
                (isSignIn
                  ? "Sign in to your account to continue"
                  : "Sign up to get started with tweakcn")}
            </p>
          </ResponsiveDialogHeader>

          <div className="space-y-6 p-6 pt-2">
            <div className="space-y-3">
              <Button
                size="lg"
                variant="outline"
                onClick={handleGoogleSignIn}
                className="hover:bg-primary/10 hover:text-foreground flex w-full items-center justify-center gap-2"
                disabled={isGoogleLoading || isGithubLoading}
              >
                <Google className="h-5 w-5" />
                <span className="font-medium">Continue with Google</span>
                {isGoogleLoading && <Loader2 className="h-4 w-4 animate-spin" />}
              </Button>

              <Button
                variant="outline"
                onClick={handleGithubSignIn}
                size="lg"
                className="hover:bg-primary/10 hover:text-foreground flex w-full items-center justify-center gap-2"
                disabled={isGoogleLoading || isGithubLoading}
              >
                <Github className="h-5 w-5" />
                <span className="font-medium">Continue with GitHub</span>
                {isGithubLoading && <Loader2 className="h-4 w-4 animate-spin" />}
              </Button>
            </div>

            <div className="pt-2">
              <div className="relative">
                <div className="absolute inset-0 flex items-center">
                  <span className="w-full border-t" />
                </div>
                <div className="relative flex justify-center text-xs uppercase">
                  <span className="bg-background text-muted-foreground px-2">
                    {isSignIn ? "New to tweakcn?" : "Already have an account?"}
                  </span>
                </div>
              </div>

              <div className="mt-6 text-center">
                <button
                  onClick={toggleMode}
                  className="text-primary focus:ring-primary text-sm font-medium hover:underline focus:ring-2 focus:ring-offset-2 focus:outline-none"
                >
                  {isSignIn ? "Create an account" : "Sign in to your account"}
                </button>
              </div>
            </div>
          </div>
        </div>
      </ResponsiveDialogContent>
    </ResponsiveDialog>
  );
}


================================================
FILE: app/(legal)/layout.tsx
================================================
import React from "react";
import { Header } from "@/components/header";
import { Footer } from "@/components/footer";

interface LegalLayoutProps {
  children: React.ReactNode;
}

export default function LegalLayout({ children }: LegalLayoutProps) {
  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="flex-1">{children}</main>
      <Footer />
    </div>
  );
}


================================================
FILE: app/(legal)/privacy-policy/page.tsx
================================================
import { Metadata } from "next";

export const metadata: Metadata = {
  title: "Privacy Policy | tweakcn",
  description: "Privacy Policy for tweakcn.",
};

export default function PrivacyPolicyPage() {
  return (
    <div className="container mx-auto px-4 py-12 md:px-6 md:py-20 lg:max-w-4xl">
      <h1 className="mb-6 text-3xl font-bold">Privacy Policy</h1>
      <p className="text-muted-foreground mb-8 text-sm">Last Updated: 24 Apr 2025</p>

      <section className="mb-8 space-y-4">
        <h2 className="text-xl font-semibold">1. Introduction</h2>
        <p className="text-muted-foreground">
          We value your privacy and are committed to safeguarding your personal data. This privacy
          policy explains how we collect, use, and protect your information when you use our
          website, as well as your privacy rights and how they are protected by law.
        </p>
      </section>

      <section className="mb-8 space-y-4">
        <h2 className="text-xl font-semibold">2. Data Collection</h2>
        <p className="text-muted-foreground">
          When you use our website, we collect and process the following types of data:
        </p>
        <ul className="text-muted-foreground list-inside list-disc space-y-2 pl-4">
          <li>
            <strong>Web Analytics:</strong> Anonymous user data is collecting using PostHog.
          </li>
          <li>
            <strong>Authentication Data:</strong> When you sign up, we collect necessary information
            such as your email address.
          </li>
        </ul>
      </section>

      <section className="mb-8 space-y-4">
        <h2 className="text-xl font-semibold">3. Sharing and Transferring Your Data</h2>
        <p className="text-muted-foreground">
          We do not sell, lease, or trade your personal information. However, we may share your data
          with trusted third parties like to process payments or provide other services on our
          behalf. We ensure that all third-party providers we work with adhere to data protection
          standards, in compliance with relevant laws such as the Information Technology Act 2000
          and Digital Personal Data Protection Act 2023 under Indian law or any other applicable
          laws.
        </p>
      </section>

      <section className="mb-8 space-y-4">
        <h2 className="text-xl font-semibold">4. Data Security</h2>
        <p className="text-muted-foreground">
          We have implemented suitable technical and organizational measures as per the level of
          risk to protect your personal data from unauthorized access, loss, or misuse.
        </p>
      </section>

      <section className="mb-8 space-y-4">
        <h2 className="text-xl font-semibold">5. Data Protection</h2>
        <p className="text-muted-foreground">
          You have the following rights concerning your personal data:
        </p>
        <ul className="text-muted-foreground list-inside list-disc space-y-2 pl-4">
          <li>
            <strong>Access:</strong> You have the right to request a copy of the personal data we
            hold about you.
          </li>
          <li>
            <strong>Correction:</strong> If any of your data is incorrect or incomplete, you can
            request to have it updated.
          </li>
          <li>
            <strong>Erasure:</strong> You can request that we delete your personal data, subject to
            applicable legal exceptions.
          </li>
          <li>
            <strong>Objection:</strong> You can object to the processing of your personal data for
            certain purposes.
          </li>
          <li>
            <strong>Restriction:</strong> You can ask us to restrict the use of your data under
            certain conditions.
          </li>
          <li>
            <strong>Data Portability:</strong> You have the right to transfer your personal data to
            another service provider, if applicable.
          </li>
          <li>
            <strong>Withdrawal of Consent:</strong> You can withdraw your consent to process your
            personal data at any time.
          </li>
        </ul>
      </section>

      <section className="mb-8 space-y-4">
        <h2 className="text-xl font-semibold">6. Cookies</h2>
        <p className="text-muted-foreground">
          Our website uses essential cookies necessary for user authentication and session
          management. These cookies help maintain your login status and ensure secure access to your
          account. We do not use cookies for tracking or advertising purposes.
        </p>
      </section>

      <section className="mb-8 space-y-4">
        <h2 className="text-xl font-semibold">7. Modifications to This Privacy Policy</h2>
        <p className="text-muted-foreground">
          We may update this privacy policy occasionally. Any changes will be posted here, and the
          effective date will be updated at this page. It shall be assumed that you are aware of any
          changes and accept the same.
        </p>
      </section>

      <section className="mb-8 space-y-4">
        <h2 className="text-xl font-semibold">8. Contact Us</h2>
        <p className="text-muted-foreground">
          If you have any questions or concerns about this privacy policy, please reach out at{" "}
          <a href="mailto:sahaj@tweakcn.com" className="text-primary hover:underline">
            sahaj@tweakcn.com
          </a>
        </p>
      </section>

      <p className="text-muted-foreground mt-12 text-sm">
        By continuing to use this website, you confirm that you have read and understood this
        Privacy Policy.
      </p>
    </div>
  );
}


================================================
FILE: app/ai/components/ai-announcement.tsx
================================================
"use client";

import { useSubscription } from "@/hooks/use-subscription";
import { ArrowRight } from "lucide-react";
import Link from "next/link";

export function AIAnnouncement() {
  const { subscriptionStatus, isPending } = useSubscription();
  const isPro = subscriptionStatus?.isSubscribed ?? false;

  if (isPending || isPro) {
    return null;
  }

  return (
    <div className="mx-auto max-w-3xl">
      <Link
        href="/pricing"
        className="group bg-muted flex items-center justify-between gap-2 rounded-full px-2 py-1.5 shadow-sm transition-all duration-200 hover:shadow-md"
      >
        <span className="text-muted-foreground group-hover:text-foreground text-sm font-medium transition-colors">
          Upgrade to Pro for unlimited requests
        </span>

        <ArrowRight className="text-muted-foreground group-hover:text-foreground size-4 -rotate-45 transition-all group-hover:rotate-0" />
      </Link>
    </div>
  );
}


================================================
FILE: app/ai/components/ai-chat-form.tsx
================================================
"use client";

import { AIChatFormBody } from "@/components/editor/ai/ai-chat-form-body";
import { AlertBanner } from "@/components/editor/ai/alert-banner";
import { EnhancePromptButton } from "@/components/editor/ai/enhance-prompt-button";
import { ImageUploader } from "@/components/editor/ai/image-uploader";
import ThemePresetSelect from "@/components/editor/theme-preset-select";
import { Button } from "@/components/ui/button";
import { useAIChatForm } from "@/hooks/use-ai-chat-form";
import { useAIEnhancePrompt } from "@/hooks/use-ai-enhance-prompt";
import { useGuards } from "@/hooks/use-guards";
import { useSubscription } from "@/hooks/use-subscription";
import { MAX_IMAGE_FILES } from "@/lib/constants";
import { cn } from "@/lib/utils";
import { AIPromptData } from "@/types/ai";
import { ArrowUp, Loader, StopCircle } from "lucide-react";

export function AIChatForm({
  onThemeGeneration,
  isGeneratingTheme,
  onCancelThemeGeneration,
}: {
  onThemeGeneration: (promptData: AIPromptData) => void;
  isGeneratingTheme: boolean;
  onCancelThemeGeneration: () => void;
}) {
  const {
    editorContentDraft,
    handleContentChange,
    promptData,
    isEmptyPrompt,
    clearLocalDraft,
    uploadedImages,
    fileInputRef,
    handleImagesUpload,
    handleImageRemove,
    isSomeImageUploading,
    isUserDragging,
    isInitializing,
  } = useAIChatForm();

  const { checkValidSession, checkValidSubscription } = useGuards();
  const { subscriptionStatus } = useSubscription();
  const isPro = subscriptionStatus?.isSubscribed ?? false;
  const hasFreeRequestsLeft = (subscriptionStatus?.requestsRemaining ?? 0) > 0;

  const { startEnhance, stopEnhance, enhancedPromptAsJsonContent, isEnhancingPrompt } =
    useAIEnhancePrompt();

  const handleEnhancePrompt = () => {
    if (!checkValidSession() || !checkValidSubscription()) return;

    // Only send images that are not loading, and strip loading property
    const images = uploadedImages.filter((img) => !img.loading).map(({ url }) => ({ url }));
    startEnhance({ ...promptData, images });
  };

  const handleGenerate = async () => {
    if (!checkValidSession() || !checkValidSubscription()) return; // Act as an early return

    // Only send images that are not loading, and strip loading property
    const images = uploadedImages.filter((img) => !img.loading).map(({ url }) => ({ url }));

    // Proceed only if there is text, or at least one image
    if (isEmptyPrompt && images.length === 0) return;

    onThemeGeneration({
      ...promptData,
      content: promptData?.content ?? "",
      mentions: promptData?.mentions ?? [],
      images,
    });

    clearLocalDraft();
  };

  return (
    <div className="@container/form relative transition-all contain-layout">
      <AlertBanner />

      <div className="bg-background relative z-10 flex size-full min-h-[100px] flex-1 flex-col gap-2 overflow-hidden rounded-lg border p-2 shadow-xs">
        <AIChatFormBody
          isUserDragging={isUserDragging}
          disabled={isEnhancingPrompt}
          canSubmit={
            !isGeneratingTheme &&
            !isEnhancingPrompt &&
            !isEmptyPrompt &&
            !isSomeImageUploading &&
            !isInitializing
          }
          uploadedImages={uploadedImages}
          handleImagesUpload={handleImagesUpload}
          handleImageRemove={handleImageRemove}
          handleContentChange={handleContentChange}
          handleGenerate={handleGenerate}
          initialEditorContent={editorContentDraft ?? undefined}
          textareaKey={editorContentDraft ? "with-draft" : "no-draft"}
          externalEditorContent={enhancedPromptAsJsonContent}
          isStreamingContent={isEnhancingPrompt}
        />

        <div className="flex items-center justify-between gap-2">
          <div className="flex w-full max-w-64 items-center gap-2 overflow-hidden">
            <ThemePresetSelect
              disabled={isGeneratingTheme || isEnhancingPrompt || isInitializing}
              withCycleThemes={false}
              variant="outline"
              size="sm"
              className="shadow-none"
            />
          </div>

          <div className="flex items-center gap-2">
            {(isPro || hasFreeRequestsLeft) && promptData?.content ? (
              <EnhancePromptButton
                isEnhancing={isEnhancingPrompt}
                onStart={handleEnhancePrompt}
                onStop={stopEnhance}
                disabled={isGeneratingTheme || isInitializing}
              />
            ) : null}

            <ImageUploader
              fileInputRef={fileInputRef}
              onImagesUpload={handleImagesUpload}
              onClick={() => fileInputRef.current?.click()}
              disabled={
                isGeneratingTheme ||
                isEnhancingPrompt ||
                isInitializing ||
                uploadedImages.some((img) => img.loading) ||
                uploadedImages.length >= MAX_IMAGE_FILES
              }
            />

            {isGeneratingTheme ? (
              <Button
                variant="destructive"
                size="sm"
                onClick={onCancelThemeGeneration}
                className={cn("flex items-center gap-1", "@max-[350px]/form:w-8")}
              >
                <StopCircle />
                <span className="hidden @[350px]/form:inline-flex">Stop</span>
              </Button>
            ) : (
              <Button
                size="icon"
                className="size-8 shadow-none"
                onClick={handleGenerate}
                disabled={
                  isEmptyPrompt ||
                  isSomeImageUploading ||
                  isGeneratingTheme ||
                  isEnhancingPrompt ||
                  isInitializing
                }
              >
                {isGeneratingTheme ? <Loader className="animate-spin" /> : <ArrowUp />}
              </Button>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}


================================================
FILE: app/ai/components/ai-chat-hero.tsx
================================================
"use client";

import { HorizontalScrollArea } from "@/components/horizontal-scroll-area";
import { useChatContext } from "@/hooks/use-chat-context";
import { useAIThemeGenerationCore } from "@/hooks/use-ai-theme-generation-core";
import { useGuards } from "@/hooks/use-guards";
import { usePostLoginAction } from "@/hooks/use-post-login-action";
import { usePreferencesStore } from "@/store/preferences-store";
import { AIPromptData } from "@/types/ai";
import { useRouter } from "next/navigation";
import { AIChatForm } from "./ai-chat-form";
import { ChatHeading } from "./chat-heading";
import { SuggestedPillActions } from "./suggested-pill-actions";

export function AIChatHero() {
  const { startNewChat } = useChatContext();
  const { generateThemeCore, isGeneratingTheme, cancelThemeGeneration } =
    useAIThemeGenerationCore();
  const { checkValidSession, checkValidSubscription } = useGuards();
  const router = useRouter();

  const { setChatSuggestionsOpen } = usePreferencesStore();

  const handleRedirectAndThemeGeneration = (promptData: AIPromptData) => {
    if (!checkValidSession("signup", "AI_GENERATE_FROM_PAGE", { promptData })) return;
    if (!checkValidSubscription()) return;

    startNewChat();
    setChatSuggestionsOpen(true);

    generateThemeCore(promptData);
    router.push("/editor/theme?tab=ai");
  };

  usePostLoginAction("AI_GENERATE_FROM_PAGE", ({ promptData }) => {
    handleRedirectAndThemeGeneration(promptData);
  });

  return (
    <div className="relative isolate flex w-full flex-1">
      <div className="@container relative isolate z-1 mx-auto flex max-w-[49rem] flex-1 flex-col justify-center px-4">
        <ChatHeading isGeneratingTheme={isGeneratingTheme} />

        {/* Chat form input and suggestions */}
        <div className="relative mx-auto flex w-full flex-col gap-2">
          <div className="relative isolate z-10 w-full">
            <AIChatForm
              onThemeGeneration={handleRedirectAndThemeGeneration}
              isGeneratingTheme={isGeneratingTheme}
              onCancelThemeGeneration={cancelThemeGeneration}
            />
          </div>

          {/* Quick suggestions */}
          <HorizontalScrollArea className="mx-auto py-2">
            <SuggestedPillActions
              onThemeGeneration={handleRedirectAndThemeGeneration}
              isGeneratingTheme={isGeneratingTheme}
            />
          </HorizontalScrollArea>
        </div>
      </div>
    </div>
  );
}


================================================
FILE: app/ai/components/chat-heading.tsx
================================================
export function ChatHeading({ isGeneratingTheme }: { isGeneratingTheme: boolean }) {
  return (
    <h1
      style={
        {
          "--gradient-accent": isGeneratingTheme ? "var(--foreground)" : "var(--foreground)",
          "--gradient-base": isGeneratingTheme ? "var(--muted-foreground)" : "var(--foreground)",
        } as React.CSSProperties
      }
      className="animate-text bg-gradient-to-r from-(--gradient-base) via-(--gradient-accent) to-(--gradient-base) bg-[200%_auto] bg-clip-text pb-4 text-center text-[clamp(24px,7cqw,46px)] font-semibold tracking-tighter text-pretty text-transparent"
    >
      What can I help you theme?
    </h1>
  );
}


================================================
FILE: app/ai/components/community-theme-card.tsx
================================================
"use client";

import Logo from "@/assets/logo.svg";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { useEditorStore } from "@/store/editor-store";
import { Theme, ThemePreset } from "@/types/theme";
import { Moon, MoreVertical, Sun } from "lucide-react";

// This is repeating from `dashboard/components/theme-card.tsx`
type SwatchDefinition = {
  name: string; // Text to display on hover
  bgKey: keyof Theme["styles"]["light" | "dark"]; // Key for background color
  fgKey: keyof Theme["styles"]["light" | "dark"]; // Key for text color
};

// This is repeating from `dashboard/components/theme-card.tsx`
const swatchDefinitions: SwatchDefinition[] = [
  { name: "Primary", bgKey: "primary", fgKey: "primary-foreground" },
  { name: "Secondary", bgKey: "secondary", fgKey: "secondary-foreground" },
  { name: "Accent", bgKey: "accent", fgKey: "accent-foreground" },
  { name: "Muted", bgKey: "muted", fgKey: "muted-foreground" },
  // Special case: Background swatch shows "Foreground" text using the main foreground color
  { name: "Background", bgKey: "background", fgKey: "foreground" },
];

export function CommunityThemeCard({ themePreset }: { themePreset: ThemePreset }) {
  const { themeState } = useEditorStore();
  const mode = themeState.currentMode;

  return (
    <div className="group/card relative flex flex-col gap-2">
      <div className="group/preview relative flex h-56 overflow-hidden rounded-lg border">
        <div className="from-foreground/20 to-foreground-10 absolute inset-0 z-1 flex flex-col items-center justify-center gap-2 bg-gradient-to-b opacity-0 transition-opacity duration-300 ease-in-out group-hover/preview:opacity-100">
          <Button variant="outline" size="sm" className="w-28 drop-shadow">
            View Details
          </Button>
          <Button size="sm" className="w-28 drop-shadow">
            View in Editor
          </Button>
        </div>

        {/* TEMPORARY CARD IMPLEMENTATION: Based on `dashboard/components/theme-card.tsx` */}
        <div className="relative isolate flex flex-1 flex-col">
          {/* Light mode swatches */}
          <div className="group relative flex flex-1">
            <div className="absolute top-2 left-2 z-10 size-6">
              <Sun className="size-full opacity-80 drop-shadow-2xl transition-opacity group-hover/preview:opacity-100" />
            </div>
            {swatchDefinitions.map((swatch) => (
              <div
                key={swatch.name + swatch.bgKey + "light"}
                className={cn(
                  "group/swatch relative h-full flex-1 transition-all duration-300 ease-in-out"
                )}
                style={{ backgroundColor: themePreset.styles.light[swatch.bgKey] }}
              ></div>
            ))}
          </div>

          {/* Dark mode swatches */}
          <div className="group relative flex flex-1">
            <div className="absolute right-2 bottom-2 z-10 size-6">
              <Moon className="size-full opacity-80 drop-shadow-2xl transition-opacity group-hover/preview:opacity-100" />
            </div>
            {swatchDefinitions.map((swatch) => (
              <div
                key={swatch.name + swatch.bgKey + "dark"}
                className={cn(
                  "group/swatch relative h-full flex-1 transition-all duration-300 ease-in-out"
                )}
                style={{ backgroundColor: themePreset.styles.dark[swatch.bgKey] }}
              ></div>
            ))}
          </div>
        </div>
      </div>

      <div className="flex items-center gap-2">
        <div
          className="from-primary to-secondary aspect-square size-10 rounded-full border bg-linear-150/oklch"
          style={
            {
              "--primary": themePreset.styles[mode].primary,
              "--primary-foreground": themePreset.styles[mode]["primary-foreground"],
              "--secondary": themePreset.styles[mode].secondary,
            } as React.CSSProperties
          }
        >
          <Logo className="text-primary-foreground size-full p-2 drop-shadow-lg" />
        </div>
        <div className="flex flex-col gap-1">
          <h3 className="line-clamp-1 text-sm leading-none font-medium">{themePreset.label}</h3>
          <p className="text-muted-foreground line-clamp-1 text-xs">
            {themePreset.createdAt ?? "Unknown creation date"}
          </p>
        </div>

        <Button variant="ghost" size="icon" className="ml-auto">
          <MoreVertical />
        </Button>
      </div>
    </div>
  );
}

export function CommunityThemeCardSkeleton() {
  return (
    <div className="flex flex-col gap-2">
      <Skeleton className="h-56 w-full rounded-lg" />

      <div className="flex items-center gap-2">
        <Skeleton className="aspect-square size-10 rounded-full" />
        <div className="flex w-full flex-col gap-1.5">
          <Skeleton className="line-clamp-1 h-4 w-1/2 rounded-lg leading-none font-medium" />
          <Skeleton className="text-muted-foreground h-3 w-1/3 rounded-lg" />
        </div>

        <MoreVertical className="text-muted ml-auto animate-pulse" />
      </div>
    </div>
  );
}


================================================
FILE: app/ai/components/community-themes.tsx
================================================
import { Button } from "@/components/ui/button";
import { ChevronRight } from "lucide-react";
import { Suspense } from "react";
import { CommunityThemeCard, CommunityThemeCardSkeleton } from "./community-theme-card";
import { ThemePreset } from "@/types/theme";
import { defaultPresets } from "@/utils/theme-presets";

// TODO: Remove this once we have a real API to fetch the community themes
const getDefaultThemePresets = async () => {
  await new Promise((resolve) => setTimeout(resolve, 300));
  return defaultPresets;
};

export async function CommunityThemes() {
  const themePresetsPromise = getDefaultThemePresets();

  return (
    <>
      <div className="flex flex-col gap-1">
        <div className="flex items-center justify-between">
          <h2 className="font-semibold">From the Community</h2>
          <Button variant="link" className="h-fit gap-1 p-0 [&>svg]:size-3">
            View All <ChevronRight />
          </Button>
        </div>
        <p className="text-muted-foreground text-sm">
          Explore the themes the community is creating with tweakcn.
        </p>
      </div>

      <div className="grid grid-cols-1 justify-center gap-6 sm:grid-cols-2 lg:grid-cols-3">
        <Suspense
          fallback={
            <>
              <CommunityThemeCardSkeleton />
              <CommunityThemeCardSkeleton />
              <CommunityThemeCardSkeleton />
            </>
          }
        >
          <CommunityThemeCards themePresetsPromise={themePresetsPromise} />
        </Suspense>
      </div>
    </>
  );
}

interface CommunityThemeCardsProps {
  themePresetsPromise: Promise<Record<string, ThemePreset>>;
}

export async function CommunityThemeCards({ themePresetsPromise }: CommunityThemeCardsProps) {
  const themePresets = await themePresetsPromise;
  const presets = Object.entries(themePresets).reduce(
    (acc, [id, preset]) => {
      acc[id] = {
        label: preset.label,
        styles: preset.styles,
      };
      return acc;
    },
    {} as Record<string, ThemePreset>
  );

  return (
    <>
      {Object.values(presets).map((preset) => (
        <CommunityThemeCard key={preset.label} themePreset={preset} />
      ))}
    </>
  );
}


================================================
FILE: app/ai/components/suggested-pill-actions.tsx
================================================
"use client";

import { PillActionButton } from "@/components/editor/ai/pill-action-button";
import { useImageUpload } from "@/hooks/use-image-upload";
import { imageUploadReducer } from "@/hooks/use-image-upload-reducer";
import { MAX_IMAGE_FILE_SIZE } from "@/lib/constants";
import { AIPromptData } from "@/types/ai";
import { createCurrentThemePrompt } from "@/utils/ai/ai-prompt";
import { PROMPTS } from "@/utils/ai/prompts";
import { ImageIcon, Sparkles } from "lucide-react";
import { useEffect, useReducer } from "react";

export function SuggestedPillActions({
  onThemeGeneration,
  isGeneratingTheme,
}: {
  onThemeGeneration: (promptData: AIPromptData) => void;
  isGeneratingTheme: boolean;
}) {
  const [uploadedImages, dispatch] = useReducer(imageUploadReducer, []);

  const { fileInputRef, handleImagesUpload, canUploadMore, isSomeImageUploading } = useImageUpload({
    maxFiles: 1,
    maxFileSize: MAX_IMAGE_FILE_SIZE,
    images: uploadedImages,
    dispatch,
  });

  // Automatically send prompt when an image is selected and loaded
  useEffect(() => {
    if (uploadedImages.length > 0 && !isSomeImageUploading) {
      onThemeGeneration({
        content: "", // No text prompt
        mentions: [], // No mentions
        images: [uploadedImages[0]],
      });
    }

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [uploadedImages, isSomeImageUploading]);

  const handleSetPrompt = async (prompt: string) => {
    const promptData = createCurrentThemePrompt({ prompt });
    onThemeGeneration(promptData);
  };

  const handleImageButtonClick = () => {
    if (canUploadMore && fileInputRef.current) {
      fileInputRef.current.click();
    }
  };

  const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
    const fileList = event.target.files;
    if (!fileList) return;

    const files = Array.from(fileList);
    handleImagesUpload(files);
  };

  return (
    <>
      <PillActionButton onClick={handleImageButtonClick} disabled={isGeneratingTheme}>
        <input
          type="file"
          accept="image/*"
          multiple={false}
          ref={fileInputRef}
          onChange={handleImageUpload}
          disabled={isGeneratingTheme}
          style={{ display: "none" }}
        />
        <ImageIcon /> From an Image
      </PillActionButton>

      {Object.entries(PROMPTS).map(([key, { label, prompt }]) => (
        <PillActionButton
          key={key}
          onClick={() => handleSetPrompt(prompt)}
          disabled={isGeneratingTheme}
        >
          <Sparkles /> {label}
        </PillActionButton>
      ))}
    </>
  );
}


================================================
FILE: app/ai/layout.tsx
================================================
import { Header } from "@/components/header";

export default function AiLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="relative isolate flex min-h-svh flex-col">
      <Header />
      <main className="isolate flex flex-1 flex-col overflow-y-auto">{children}</main>
    </div>
  );
}


================================================
FILE: app/ai/loading.tsx
================================================
import { Loading } from "@/components/loading";

export default function AiLoading() {
  return <Loading className="flex-1" />;
}


================================================
FILE: app/ai/page.tsx
================================================
import { type Metadata } from "next";
import { AIAnnouncement } from "./components/ai-announcement";
import { AIChatHero } from "./components/ai-chat-hero";

export const metadata: Metadata = {
  title: "Image to shadcn/ui theme. Generate with AI — tweakcn",
  description:
    "Transform images into stunning shadcn/ui themes instantly with tweakcn's AI theme generator. Upload any image or describe your vision—our AI creates custom Tailwind CSS themes with real-time preview. Perfect for developers who want beautiful, production-ready themes in seconds.",
  keywords:
    "ai theme generator, image to theme, shadcn/ui themes, tailwind css generator, ai design tool, theme from image, ui customization, tweakcn, visual theme creator, color palette generator, design system ai, frontend theming, web design automation",
  robots: "index, follow",
};

export default function AiPage() {
  return (
    <div className="relative isolate container mx-auto flex flex-1 flex-col gap-24 overflow-x-hidden overflow-y-auto px-4 md:px-6">
      {/* AI Chat entry point section */}
      <section className="relative isolate flex flex-col gap-4 pt-28 lg:pt-44">
        <AIAnnouncement />
        <AIChatHero />
      </section>
    </div>
  );
}


================================================
FILE: app/api/auth/[...all]/route.ts
================================================
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { GET, POST } = toNextJsHandler(auth.handler);


================================================
FILE: app/api/enhance-prompt/route.ts
================================================
import { ENHANCE_PROMPT_SYSTEM } from "@/lib/ai/prompts";
import { baseProviderOptions, myProvider } from "@/lib/ai/providers";
import { handleError } from "@/lib/error-response";
import { requireSubscriptionOrFreeUsage } from "@/lib/subscription";
import { AIPromptData } from "@/types/ai";
import { buildUserContentPartsFromPromptData } from "@/utils/ai/message-converter";
import { smoothStream, streamText } from "ai";
import { NextRequest } from "next/server";

export async function POST(req: NextRequest) {
  try {
    await requireSubscriptionOrFreeUsage(req);

    const body = await req.json();
    const { prompt: _prompt, promptData }: { prompt: string; promptData: AIPromptData } = body;
    const userContentParts = buildUserContentPartsFromPromptData(promptData);

    const result = streamText({
      system: ENHANCE_PROMPT_SYSTEM,
      messages: [
        {
          role: "user",
          content: userContentParts,
        },
      ],
      model: myProvider.languageModel("prompt-enhancement"),
      providerOptions: baseProviderOptions,
      experimental_transform: smoothStream({
        delayInMs: 10,
        chunking: "word",
      }),
    });

    return result.toUIMessageStreamResponse();
  } catch (error) {
    return handleError(error, { route: "/api/enhance-prompt" });
  }
}


================================================
FILE: app/api/generate-theme/route.ts
================================================
import { recordAIUsage } from "@/actions/ai-usage";
import { THEME_GENERATION_TOOLS } from "@/lib/ai/generate-theme/tools";
import { GENERATE_THEME_SYSTEM } from "@/lib/ai/prompts";
import { baseProviderOptions, myProvider } from "@/lib/ai/providers";
import { handleError } from "@/lib/error-response";
import { getCurrentUserId, logError } from "@/lib/shared";
import { validateSubscriptionAndUsage } from "@/lib/subscription";
import { AdditionalAIContext, ChatMessage } from "@/types/ai";
import { SubscriptionRequiredError } from "@/types/errors";
import { convertMessagesToModelMessages } from "@/utils/ai/message-converter";
import { Ratelimit } from "@upstash/ratelimit";
import { kv } from "@vercel/kv";
import { createUIMessageStream, createUIMessageStreamResponse, stepCountIs, streamText } from "ai";
import { headers } from "next/headers";
import { NextRequest } from "next/server";

const ratelimit = new Ratelimit({
  redis: kv,
  limiter: Ratelimit.fixedWindow(5, "60s"),
});

export async function POST(req: NextRequest) {
  try {
    const userId = await getCurrentUserId(req);
    const headersList = await headers();

    if (process.env.NODE_ENV !== "development") {
      const ip = headersList.get("x-forwarded-for") ?? "anonymous";
      const { success, limit, reset, remaining } = await ratelimit.limit(ip);

      if (!success) {
        return new Response("Rate limit exceeded. Please try again later.", {
          status: 429,
          headers: {
            "X-RateLimit-Limit": limit.toString(),
            "X-RateLimit-Remaining": remaining.toString(),
            "X-RateLimit-Reset": reset.toString(),
          },
        });
      }
    }

    const subscriptionCheck = await validateSubscriptionAndUsage(userId);

    if (!subscriptionCheck.canProceed) {
      throw new SubscriptionRequiredError(subscriptionCheck.error, {
        requestsRemaining: subscriptionCheck.requestsRemaining,
      });
    }

    const { messages }: { messages: ChatMessage[] } = await req.json();
    const modelMessages = await convertMessagesToModelMessages(messages);

    const stream = createUIMessageStream<ChatMessage>({
      execute: ({ writer }) => {
        const context: AdditionalAIContext = { writer };
        const model = myProvider.languageModel("base");

        const result = streamText({
          abortSignal: req.signal,
          model: model,
          providerOptions: baseProviderOptions,
          system: GENERATE_THEME_SYSTEM,
          messages: modelMessages,
          tools: THEME_GENERATION_TOOLS,
          stopWhen: stepCountIs(5),
          onError: (error) => {
            if (error instanceof Error) console.error(error);
          },
          onFinish: async (result) => {
            const { totalUsage } = result;
            try {
              await recordAIUsage({
                modelId: model.modelId,
                promptTokens: totalUsage.inputTokens,
                completionTokens: totalUsage.outputTokens,
              });
            } catch (error) {
              logError(error as Error, { action: "recordAIUsage", totalUsage });
            }
          },
          experimental_context: context,
        });

        writer.merge(
          result.toUIMessageStream({
            messageMetadata: ({ part }) => {
              // `toolName` is not typed for some reason, must be kept in sync with the actual tool names
              if (part.type === "tool-result" && part.toolName === "generateTheme") {
                return { themeStyles: part.output };
              }
            },
          })
        );
      },
    });

    return createUIMessageStreamResponse({ stream });
  } catch (error) {
    if (
      error instanceof Error &&
      (error.name === "AbortError" || error.name === "ResponseAborted")
    ) {
      return new Response("Request aborted by user", { status: 499 });
    }

    return handleError(error, { route: "/api/generate-theme" });
  }
}


================================================
FILE: app/api/google-fonts/route.ts
================================================
import { PaginatedFontsResponse } from "@/types/fonts";
import { FALLBACK_FONTS } from "@/utils/fonts";
import { fetchGoogleFonts } from "@/utils/fonts/google-fonts";
import { unstable_cache } from "next/cache";
import { NextRequest, NextResponse } from "next/server";

const cachedFetchGoogleFonts = unstable_cache(fetchGoogleFonts, ["google-fonts-catalogue"], {
  tags: ["google-fonts-catalogue"],
});

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const query = searchParams.get("q")?.toLowerCase() || "";
    const category = searchParams.get("category")?.toLowerCase();
    const limit = Math.min(Number(searchParams.get("limit")) || 50, 100);
    const offset = Number(searchParams.get("offset")) || 0;

    let googleFonts = FALLBACK_FONTS;

    try {
      googleFonts = await cachedFetchGoogleFonts(process.env.GOOGLE_FONTS_API_KEY);
    } catch (error) {
      console.error("Error fetching Google Fonts:", error);
      console.log("Using fallback fonts");
    }

    // Filter fonts based on search query and category
    let filteredFonts = googleFonts;

    if (query) {
      filteredFonts = filteredFonts.filter((font) => font.family.toLowerCase().includes(query));
    }

    if (category && category !== "all") {
      filteredFonts = filteredFonts.filter((font) => font.category === category);
    }

    const paginatedFonts = filteredFonts.slice(offset, offset + limit);

    const response: PaginatedFontsResponse = {
      fonts: paginatedFonts,
      total: filteredFonts.length,
      offset,
      limit,
      hasMore: offset + limit < filteredFonts.length,
    };

    return NextResponse.json(response);
  } catch (error) {
    console.error("Error in Google Fonts API:", error);
    return NextResponse.json({ error: "Failed to fetch fonts" }, { status: 500 });
  }
}


================================================
FILE: app/api/oauth/app-info/route.ts
================================================
import { db } from "@/db";
import { oauthApp } from "@/db/schema";
import { oauthError } from "@/lib/oauth";
import { eq, and } from "drizzle-orm";
import { NextRequest } from "next/server";

export async function GET(req: NextRequest) {
  const clientId = req.nextUrl.searchParams.get("client_id");

  if (!clientId) {
    return oauthError("invalid_request", "Missing client_id");
  }

  const [app] = await db
    .select({ name: oauthApp.name, description: oauthApp.description })
    .from(oauthApp)
    .where(and(eq(oauthApp.clientId, clientId), eq(oauthApp.isActive, true)))
    .limit(1);

  if (!app) {
    return oauthError("invalid_client", "Unknown client_id");
  }

  return Response.json({ name: app.name, description: app.description });
}


================================================
FILE: app/api/oauth/authorize/route.ts
================================================
import { db } from "@/db";
import { oauthApp, oauthAuthorizationCode } from "@/db/schema";
import { OAUTH_AUTHORIZATION_CODE_EXPIRY_SECONDS } from "@/lib/constants";
import {
  generateSecureToken,
  oauthError,
  parseScopes,
  validateRedirectUri,
  validateScopes,
} from "@/lib/oauth";
import { auth } from "@/lib/auth";
import { eq, and } from "drizzle-orm";
import { headers } from "next/headers";
import cuid from "cuid";
import { NextRequest } from "next/server";

export async function GET(req: NextRequest) {
  const params = req.nextUrl.searchParams;

  const clientId = params.get("client_id");
  const redirectUri = params.get("redirect_uri");
  const responseType = params.get("response_type");
  const scopeParam = params.get("scope");
  const state = params.get("state");
  const codeChallenge = params.get("code_challenge");
  const codeChallengeMethod = params.get("code_challenge_method") ?? "S256";

  // Validate required params
  if (!clientId || !redirectUri || !responseType || !scopeParam) {
    return oauthError(
      "invalid_request",
      "Missing required parameters: client_id, redirect_uri, response_type, scope"
    );
  }

  if (responseType !== "code") {
    return oauthError(
      "unsupported_response_type",
      "Only response_type=code is supported"
    );
  }

  // Look up OAuth app
  const [app] = await db
    .select({ id: oauthApp.id, redirectUris: oauthApp.redirectUris })
    .from(oauthApp)
    .where(and(eq(oauthApp.clientId, clientId), eq(oauthApp.isActive, true)))
    .limit(1);

  if (!app) {
    return oauthError("invalid_client", "Unknown client_id");
  }

  // Validate redirect URI
  if (!validateRedirectUri(redirectUri, app.redirectUris)) {
    return oauthError("invalid_request", "redirect_uri not registered");
  }

  // Validate scopes
  const scopes = parseScopes(scopeParam);
  if (!validateScopes(scopes)) {
    return oauthError("invalid_scope", "Invalid or unsupported scope");
  }

  // Check that the user is logged in
  const session = await auth.api.getSession({ headers: await headers() });
  if (!session?.user?.id) {
    // Redirect to the OAuth authorize page which handles sign-in
    const pageUrl = new URL("/oauth/authorize", req.nextUrl.origin);
    req.nextUrl.searchParams.forEach((value, key) => {
      pageUrl.searchParams.set(key, value);
    });
    return Response.redirect(pageUrl.toString(), 302);
  }

  // Generate authorization code
  const code = generateSecureToken();
  const now = new Date();

  await db.insert(oauthAuthorizationCode).values({
    id: cuid(),
    code,
    appId: app.id,
    userId: session.user.id,
    scopes,
    redirectUri,
    codeChallenge: codeChallenge ?? null,
    codeChallengeMethod: codeChallenge ? codeChallengeMethod : null,
    expiresAt: new Date(
      now.getTime() + OAUTH_AUTHORIZATION_CODE_EXPIRY_SECONDS * 1000
    ),
    createdAt: now,
  });

  // Redirect back to the app with the code
  const redirectUrl = new URL(redirectUri);
  redirectUrl.searchParams.set("code", code);
  if (state) redirectUrl.searchParams.set("state", state);

  return Response.redirect(redirectUrl.toString(), 302);
}


================================================
FILE: app/api/oauth/revoke/route.ts
================================================
import { db } from "@/db";
import { oauthToken } from "@/db/schema";
import { hashToken, oauthError } from "@/lib/oauth";
import { eq, or } from "drizzle-orm";
import { NextRequest } from "next/server";

export async function POST(req: NextRequest) {
  const body = await req.formData().catch(() => null);
  if (!body) {
    return oauthError("invalid_request", "Request body must be form-encoded");
  }

  const token = body.get("token") as string | null;
  if (!token) {
    return oauthError("invalid_request", "Missing required parameter: token");
  }

  const tokenHash = hashToken(token);

  // Try to match as access token or refresh token
  const [record] = await db
    .select({ id: oauthToken.id })
    .from(oauthToken)
    .where(
      or(
        eq(oauthToken.accessTokenHash, tokenHash),
        eq(oauthToken.refreshTokenHash, tokenHash)
      )
    )
    .limit(1);

  if (record) {
    await db
      .update(oauthToken)
      .set({ revokedAt: new Date(), updatedAt: new Date() })
      .where(eq(oauthToken.id, record.id));
  }

  // RFC 7009: always return 200 even if token not found
  return new Response(null, { status: 200 });
}


================================================
FILE: app/api/oauth/token/route.ts
================================================
import { db } from "@/db";
import { oauthAuthorizationCode, oauthToken } from "@/db/schema";
import {
  authenticateClient,
  createTokenPair,
  hashToken,
  oauthError,
  verifyCodeChallenge,
} from "@/lib/oauth";
import { eq, and, isNull } from "drizzle-orm";
import { NextRequest } from "next/server";

export async function POST(req: NextRequest) {
  const body = await req.formData().catch(() => null);
  if (!body) {
    return oauthError("invalid_request", "Request body must be form-encoded");
  }

  const grantType = body.get("grant_type") as string | null;

  if (grantType === "authorization_code") {
    return handleAuthorizationCode(body);
  }

  if (grantType === "refresh_token") {
    return handleRefreshToken(body);
  }

  return oauthError("unsupported_grant_type", "Supported: authorization_code, refresh_token");
}

async function handleAuthorizationCode(body: FormData) {
  const clientId = body.get("client_id") as string | null;
  const clientSecret = body.get("client_secret") as string | null;
  const code = body.get("code") as string | null;
  const redirectUri = body.get("redirect_uri") as string | null;
  const codeVerifier = body.get("code_verifier") as string | null;

  if (!clientId || !clientSecret || !code || !redirectUri) {
    return oauthError(
      "invalid_request",
      "Missing required parameters: client_id, client_secret, code, redirect_uri"
    );
  }

  // Authenticate the client
  const app = await authenticateClient(clientId, clientSecret);
  if (!app) {
    return oauthError("invalid_client", "Invalid client credentials", 401);
  }

  // Look up the authorization code
  const [authCode] = await db
    .select({
      id: oauthAuthorizationCode.id,
      expiresAt: oauthAuthorizationCode.expiresAt,
      redirectUri: oauthAuthorizationCode.redirectUri,
      codeChallenge: oauthAuthorizationCode.codeChallenge,
      codeChallengeMethod: oauthAuthorizationCode.codeChallengeMethod,
      userId: oauthAuthorizationCode.userId,
      scopes: oauthAuthorizationCode.scopes,
    })
    .from(oauthAuthorizationCode)
    .where(
      and(
        eq(oauthAuthorizationCode.code, code),
        eq(oauthAuthorizationCode.appId, app.id),
        isNull(oauthAuthorizationCode.usedAt)
      )
    )
    .limit(1);

  if (!authCode) {
    return oauthError("invalid_grant", "Invalid or already used authorization code");
  }

  // Check expiry
  if (new Date() > authCode.expiresAt) {
    return oauthError("invalid_grant", "Authorization code expired");
  }

  // Check redirect URI matches
  if (authCode.redirectUri !== redirectUri) {
    return oauthError("invalid_grant", "redirect_uri mismatch");
  }

  // Verify PKCE if code challenge was provided during authorization
  if (authCode.codeChallenge) {
    if (!codeVerifier) {
      return oauthError("invalid_request", "code_verifier required for PKCE");
    }
    if (
      !verifyCodeChallenge(
        codeVerifier,
        authCode.codeChallenge,
        authCode.codeChallengeMethod ?? "S256"
      )
    ) {
      return oauthError("invalid_grant", "PKCE verification failed");
    }
  }

  // Mark code as used
  await db
    .update(oauthAuthorizationCode)
    .set({ usedAt: new Date() })
    .where(eq(oauthAuthorizationCode.id, authCode.id));

  // Create tokens
  const tokenResponse = await createTokenPair(
    app.id,
    authCode.userId,
    authCode.scopes
  );

  return Response.json(tokenResponse);
}

async function handleRefreshToken(body: FormData) {
  const clientId = body.get("client_id") as string | null;
  const clientSecret = body.get("client_secret") as string | null;
  const refreshToken = body.get("refresh_token") as string | null;

  if (!clientId || !clientSecret || !refreshToken) {
    return oauthError(
      "invalid_request",
      "Missing required parameters: client_id, client_secret, refresh_token"
    );
  }

  const app = await authenticateClient(clientId, clientSecret);
  if (!app) {
    return oauthError("invalid_client", "Invalid client credentials", 401);
  }

  // Look up the refresh token
  const refreshTokenHash = hashToken(refreshToken);
  const [tokenRecord] = await db
    .select({
      id: oauthToken.id,
      userId: oauthToken.userId,
      scopes: oauthToken.scopes,
      refreshTokenExpiresAt: oauthToken.refreshTokenExpiresAt,
    })
    .from(oauthToken)
    .where(
      and(
        eq(oauthToken.refreshTokenHash, refreshTokenHash),
        eq(oauthToken.appId, app.id),
        isNull(oauthToken.revokedAt)
      )
    )
    .limit(1);

  if (!tokenRecord) {
    return oauthError("invalid_grant", "Invalid refresh token");
  }

  if (
    tokenRecord.refreshTokenExpiresAt &&
    new Date() > tokenRecord.refreshTokenExpiresAt
  ) {
    return oauthError("invalid_grant", "Refresh token expired");
  }

  // Revoke the old token pair
  await db
    .update(oauthToken)
    .set({ revokedAt: new Date(), updatedAt: new Date() })
    .where(eq(oauthToken.id, tokenRecord.id));

  // Issue new token pair
  const tokenResponse = await createTokenPair(
    app.id,
    tokenRecord.userId,
    tokenRecord.scopes
  );

  return Response.json(tokenResponse);
}


================================================
FILE: app/api/oauth/userinfo/route.ts
================================================
import { db } from "@/db";
import { user as userTable } from "@/db/schema";
import { oauthError, requireScope, resolveUserFromBearerToken } from "@/lib/oauth";
import { eq } from "drizzle-orm";
import { NextRequest } from "next/server";

/**
 * OpenID Connect-style userinfo endpoint.
 * Returns flat user fields for compatibility with generic OAuth clients
 * (e.g. Better Auth's genericOAuth plugin).
 */
export async function GET(req: NextRequest) {
  const tokenData = await resolveUserFromBearerToken(
    req.headers.get("authorization")
  );

  if (!tokenData) {
    return oauthError("invalid_token", "Invalid or expired access token", 401);
  }

  if (!requireScope(tokenData.scopes, "profile:read")) {
    return oauthError("insufficient_scope", "Requires profile:read scope", 403);
  }

  const [profile] = await db
    .select({
      id: userTable.id,
      name: userTable.name,
      email: userTable.email,
      image: userTable.image,
    })
    .from(userTable)
    .where(eq(userTable.id, tokenData.userId))
    .limit(1);

  if (!profile) {
    return oauthError("invalid_token", "User not found", 401);
  }

  return Response.json({
    sub: profile.id,
    name: profile.name,
    email: profile.email,
    picture: profile.image,
  });
}


================================================
FILE: app/api/subscription/route.ts
================================================
import { getCurrentUserId, logError } from "@/lib/shared";
import { validateSubscriptionAndUsage } from "@/lib/subscription";
import { SubscriptionStatus } from "@/types/subscription";
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  try {
    const userId = await getCurrentUserId(request);
    const { isSubscribed, requestsRemaining, requestsUsed } =
      await validateSubscriptionAndUsage(userId);

    const response: SubscriptionStatus = {
      isSubscribed,
      requestsRemaining,
      requestsUsed,
    };

    return NextResponse.json(response);
  } catch (error) {
    logError(error as Error);
    return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
  }
}


================================================
FILE: app/api/v1/me/route.ts
================================================
import { db } from "@/db";
import { user as userTable } from "@/db/schema";
import { oauthError, requireAuth } from "@/lib/oauth";
import { eq } from "drizzle-orm";
import { NextRequest } from "next/server";

export async function GET(req: NextRequest) {
  const auth = await requireAuth(req, "profile:read");
  if (auth.error) return auth.error;

  const [profile] = await db
    .select({
      id: userTable.id,
      name: userTable.name,
      email: userTable.email,
      image: userTable.image,
    })
    .from(userTable)
    .where(eq(userTable.id, auth.tokenData.userId))
    .limit(1);

  if (!profile) {
    return oauthError("invalid_token", "User not found", 401);
  }

  return Response.json({ data: profile });
}


================================================
FILE: app/api/v1/themes/[themeId]/route.ts
================================================
import { db } from "@/db";
import { theme as themeTable } from "@/db/schema";
import { oauthError, requireAuth } from "@/lib/oauth";
import { eq, and } from "drizzle-orm";
import { NextRequest } from "next/server";

export async function GET(
  req: NextRequest,
  { params }: { params: Promise<{ themeId: string }> }
) {
  const auth = await requireAuth(req, "themes:read");
  if (auth.error) return auth.error;

  const { themeId } = await params;

  const [theme] = await db
    .select({
      id: themeTable.id,
      name: themeTable.name,
      styles: themeTable.styles,
      createdAt: themeTable.createdAt,
      updatedAt: themeTable.updatedAt,
    })
    .from(themeTable)
    .where(
      and(eq(themeTable.id, themeId), eq(themeTable.userId, auth.tokenData.userId))
    )
    .limit(1);

  if (!theme) {
    return oauthError("not_found", "Theme not found", 404);
  }

  return Response.json({ data: theme });
}


================================================
FILE: app/api/v1/themes/route.ts
================================================
import { db } from "@/db";
import { theme as themeTable } from "@/db/schema";
import { requireAuth } from "@/lib/oauth";
import { eq } from "drizzle-orm";
import { NextRequest } from "next/server";

export async function GET(req: NextRequest) {
  const auth = await requireAuth(req, "themes:read");
  if (auth.error) return auth.error;

  const themes = await db
    .select({
      id: themeTable.id,
      name: themeTable.name,
      styles: themeTable.styles,
      createdAt: themeTable.createdAt,
      updatedAt: themeTable.updatedAt,
    })
    .from(themeTable)
    .where(eq(themeTable.userId, auth.tokenData.userId));

  return Response.json({ data: themes });
}


================================================
FILE: app/api/webhook/polar/route.ts
================================================
import { db } from "@/db";
import { subscription } from "@/db/schema";
import { Webhooks } from "@polar-sh/nextjs";

function safeParseDate(value: string | Date | null | undefined): Date | null {
  if (!value) return null;
  if (value instanceof Date) return value;
  return new Date(value);
}

if (!process.env.POLAR_WEBHOOK_SECRET) {
  throw new Error("POLAR_WEBHOOK_SECRET environment variable is required");
}

export const POST = Webhooks({
  webhookSecret: process.env.POLAR_WEBHOOK_SECRET,
  onPayload: async ({ data, type }) => {
    if (
      type === "subscription.created" ||
      type === "subscription.active" ||
      type === "subscription.canceled" ||
      type === "subscription.revoked" ||
      type === "subscription.uncanceled" ||
      type === "subscription.updated"
    ) {
      console.log("🎯 Processing subscription webhook:", type);
      console.log("📦 Payload data:", JSON.stringify(data, null, 2));
      try {
        const userId = data.customer?.externalId;

        const subscriptionData = {
          id: data.id,
          createdAt: new Date(data.createdAt),
          modifiedAt: safeParseDate(data.modifiedAt),
          amount: data.amount,
          currency: data.currency,
          recurringInterval: data.recurringInterval,
          status: data.status,
          currentPeriodStart: safeParseDate(data.currentPeriodStart) || new Date(),
          currentPeriodEnd: safeParseDate(data.currentPeriodEnd) || new Date(),
          cancelAtPeriodEnd: data.cancelAtPeriodEnd || false,
          canceledAt: safeParseDate(data.canceledAt),
          startedAt: safeParseDate(data.startedAt) || new Date(),
          endsAt: safeParseDate(data.endsAt),
          endedAt: safeParseDate(data.endedAt),
          customerId: data.customerId,
          productId: data.productId,
          discountId: data.discountId || null,
          checkoutId: data.checkoutId || "",
          customerCancellationReason: data.customerCancellationReason || null,
          customerCancellationComment: data.customerCancellationComment || null,
          metadata: data.metadata ? JSON.stringify(data.metadata) : null,
          customFieldData: data.customFieldData ? JSON.stringify(data.customFieldData) : null,
          userId: userId as string | null,
        };

        console.log("💾 Final subscription data:", {
          id: subscriptionData.id,
          status: subscriptionData.status,
          userId: subscriptionData.userId,
          amount: subscriptionData.amount,
        });

        await db
          .insert(subscription)
          .values(subscriptionData)
          .onConflictDoUpdate({
            target: subscription.id,
            set: {
              modifiedAt: subscriptionData.modifiedAt || new Date(),
              amount: subscriptionData.amount,
              currency: subscriptionData.currency,
              recurringInterval: subscriptionData.recurringInterval,
              status: subscriptionData.status,
              currentPeriodStart: subscriptionData.currentPeriodStart,
              currentPeriodEnd: subscriptionData.currentPeriodEnd,
              cancelAtPeriodEnd: subscriptionData.cancelAtPeriodEnd,
              canceledAt: subscriptionData.canceledAt,
              startedAt: subscriptionData.startedAt,
              endsAt: subscriptionData.endsAt,
              endedAt: subscriptionData.endedAt,
              customerId: subscriptionData.customerId,
              productId: subscriptionData.productId,
              discountId: subscriptionData.discountId,
              checkoutId: subscriptionData.checkoutId,
              customerCancellationReason: subscriptionData.customerCancellationReason,
              customerCancellationComment: subscriptionData.customerCancellationComment,
              metadata: subscriptionData.metadata,
              customFieldData: subscriptionData.customFieldData,
              userId: subscriptionData.userId,
            },
          });

        console.log("🎉 Subscription data upserted successfully");
      } catch (error) {
        console.error("❌ Error processing subscription webhook:", error);
      }
    }
  },
});


================================================
FILE: app/community/components/community-sidebar.tsx
================================================
"use client";

import { cn } from "@/lib/utils";
import { useCommunityTagCounts } from "@/hooks/themes";
import { useSessionGuard } from "@/hooks/use-guards";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import type { CommunityFilterOption } from "@/types/community";

interface CommunitySidebarProps {
  filter: CommunityFilterOption;
  selectedTags: string[];
  onFilterChange: (filter: CommunityFilterOption) => void;
  onTagToggle: (tag: string) => void;
}

const filterItems = [
  { value: "all" as const, label: "All Themes" },
  { value: "mine" as const, label: "My Themes" },
  { value: "liked" as const, label: "Liked Themes" },
];

export function CommunitySidebarContent({
  filter,
  selectedTags,
  onFilterChange,
  onTagToggle,
}: CommunitySidebarProps) {
  const { data: tagCounts = [], isLoading: isLoadingTags } =
    useCommunityTagCounts();
  const { checkValidSession } = useSessionGuard();

  const handleFilterClick = (value: CommunityFilterOption) => {
    if (value === "mine" || value === "liked") {
      if (!checkValidSession("signin")) return;
    }
    onFilterChange(value);
  };

  return (
    <nav className="space-y-1">
      {filterItems.map((item) => (
        <button
          key={item.value}
          onClick={() => handleFilterClick(item.value)}
          className={cn(
            "flex w-full items-center rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
            filter === item.value
              ? "bg-foreground/10 text-foreground"
              : "text-muted-foreground hover:bg-foreground/5 hover:text-foreground"
          )}
        >
          {item.label}
        </button>
      ))}

      <Separator className="!my-3" />
      <div className="px-3 py-1">
        <span className="text-muted-foreground text-xs font-semibold uppercase tracking-wider">
          Tags
        </span>
      </div>

      {isLoadingTags ? (
        <div className="space-y-1 px-3">
          {Array.from({ length: 5 }).map((_, i) => (
            <Skeleton key={i} className="h-7 w-full rounded-lg" />
          ))}
        </div>
      ) : tagCounts.length > 0 ? (
        <div className="space-y-0.5">
          {tagCounts.map(({ tag, count }) => (
            <button
              key={tag}
              onClick={() => onTagToggle(tag)}
              className={cn(
                "flex w-full items-center justify-between rounded-lg px-3 py-1.5 text-sm transition-colors",
                selectedTags.includes(tag)
                  ? "bg-foreground/10 text-foreground font-medium"
                  : "text-muted-foreground hover:bg-foreground/5 hover:text-foreground"
              )}
            >
              <span>{tag}</span>
              <span
                className={cn(
                  "text-xs tabular-nums",
                  selectedTags.includes(tag)
                    ? "text-foreground/70"
                    : "text-muted-foreground/60"
                )}
              >
                {count}
              </span>
            </button>
          ))}
        </div>
      ) : null}
    </nav>
  );
}


================================================
FILE: app/community/components/community-theme-card.tsx
================================================
"use client";

import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import { cn } from "@/lib/utils";
import { Heart } from "lucide-react";

import { useTheme } from "@/components/theme-provider";
import { useToggleLike } from "@/hooks/themes";
import { useSessionGuard } from "@/hooks/use-guards";
import { usePostLoginAction } from "@/hooks/use-post-login-action";
import { ThemePreview } from "@/components/theme-preview";
import type { CommunityTheme } from "@/types/community";

interface CommunityThemeCardProps {
  theme: CommunityTheme;
  onPreview: (theme: CommunityTheme) => void;
}

export function CommunityThemeCard({ theme, onPreview }: CommunityThemeCardProps) {
  const { theme: currentTheme } = useTheme();
  const toggleLike = useToggleLike();
  const { checkValidSession } = useSessionGuard();

  usePostLoginAction("LIKE_THEME", (data?: { communityThemeId: string }) => {
    if (data?.communityThemeId === theme.id) {
      toggleLike.mutate(theme.id);
    }
  });

  const handleLike = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();

    if (
      !checkValidSession("signin", "LIKE_THEME", {
        communityThemeId: theme.id,
      })
    ) {
      return;
    }

    toggleLike.mutate(theme.id);
  };

  const authorInitials = theme.author.name
    ?.split(" ")
    .map((n) => n[0])
    .join("")
    .slice(0, 2)
    .toUpperCase();

  const publishedDate = new Date(theme.publishedAt).toLocaleDateString(
    "en-US",
    { day: "numeric", month: "short" }
  );

  return (
    <div
      role="button"
      tabIndex={0}
      onClick={() => onPreview(theme)}
      className="group cursor-pointer"
    >
      <div className="relative h-44 w-full overflow-hidden rounded-xl border shadow-sm transition-all duration-200 group-hover:shadow-md group-hover:border-foreground/20">
        <ThemePreview
          styles={theme.styles[currentTheme]}
          name={theme.name}
          className="transition-transform duration-300 group-hover:scale-102"
        />
        {theme.tags.length > 0 && (
          <div className="pointer-events-none absolute top-2 left-2 flex items-center gap-1">
            {theme.tags.slice(0, 2).map((tag) => (
              <Badge
                key={tag}
                className="border-0 bg-background/80 px-1.5 py-0 text-[10px] text-foreground shadow-sm backdrop-blur-sm"
              >
                {tag}
              </Badge>
            ))}
            {theme.tags.length > 2 && (
              <Badge className="border-0 bg-background/80 px-1.5 py-0 text-[10px] text-foreground shadow-sm backdrop-blur-sm">
                +{theme.tags.length - 2}
              </Badge>
            )}
          </div>
        )}
      </div>

      <div className="flex items-start justify-between gap-2 px-1 pt-2">
        <div className="min-w-0 flex-1">
          <div className="mt-1 flex items-center gap-3">
            <div className="flex min-w-0 max-w-[120px] items-center gap-1.5">
              <Avatar className="h-4 w-4 shrink-0">
                {theme.author.image && (
                  <AvatarImage
                    src={theme.author.image}
                    alt={theme.author.name}
                  />
                )}
                <AvatarFallback className="text-[8px]">
                  {authorInitials}
                </AvatarFallback>
              </Avatar>
              <span className="truncate text-xs text-muted-foreground">
                {theme.author.name}
              </span>
            </div>
            <span className="text-xs text-muted-foreground/60">
              {publishedDate}
            </span>
          </div>
        </div>
        <button
          onClick={handleLike}
          className={cn(
            "flex shrink-0 items-center gap-1 rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
            theme.isLikedByMe
              ? "bg-red-500/10 text-red-500"
              : "text-muted-foreground hover:bg-accent hover:text-foreground"
          )}
        >
          <Heart
            className={cn(
              "h-3.5 w-3.5",
              theme.isLikedByMe && "fill-current"
            )}
          />
          {theme.likeCount > 0 && <span>{theme.likeCount}</span>}
        </button>
      </div>
    </div>
  );
}


================================================
FILE: app/community/components/community-theme-preview-dialog.tsx
================================================
"use client";

import { lazy, useEffect, useCallback } from "react";
import { useRouter } from "next/navigation";
import { cn } from "@/lib/utils";
import { useEditorStore } from "@/store/editor-store";
import { useToggleLike } from "@/hooks/themes";
import { useSessionGuard } from "@/hooks/use-guards";
import { usePostLoginAction } from "@/hooks/use-post-login-action";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import { ScrollArea } from "@/components/ui/scroll-area";
import ExamplesPreviewContainer from "@/components/editor/theme-preview/examples-preview-container";
import { Heart, Moon, Sun, ArrowUpRight, ChevronLeft, ChevronRight, Share2 } from "lucide-react";
import { toast } from "@/components/ui/use-toast";
import type { CommunityTheme } from "@/types/community";

const DemoCards = lazy(() => import("@/components/examples/cards"));

interface CommunityThemePreviewDialogProps {
  theme: CommunityTheme | null;
  themes: CommunityTheme[];
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onNavigate: (theme: CommunityTheme) => void;
}

export function CommunityThemePreviewDialog({
  theme,
  themes,
  open,
  onOpenChange,
  onNavigate,
}: CommunityThemePreviewDialogProps) {
  const router = useRouter();
  const { themeState, setThemeState } = useEditorStore();
  const toggleLike = useToggleLike();
  const { checkValidSession } = useSessionGuard();

  const currentIndex = theme ? themes.findIndex((t) => t.id === theme.id) : -1;
  const hasPrev = currentIndex > 0;
  const hasNext = currentIndex >= 0 && currentIndex < themes.length - 1;

  const goToPrev = useCallback(() => {
    if (hasPrev) onNavigate(themes[currentIndex - 1]);
  }, [hasPrev, themes, currentIndex, onNavigate]);

  const goToNext = useCallback(() => {
    if (hasNext) onNavigate(themes[currentIndex + 1]);
  }, [hasNext, themes, currentIndex, onNavigate]);

  useEffect(() => {
    if (!open) return;
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "ArrowLeft") {
        e.preventDefault();
        goToPrev();
      } else if (e.key === "ArrowRight") {
        e.preventDefault();
        goToNext();
      }
    };
    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [open, goToPrev, goToNext]);

  usePostLoginAction("LIKE_THEME", (data?: { communityThemeId: string }) => {
    if (theme && data?.communityThemeId === theme.id) {
      toggleLike.mutate(theme.id);
    }
  });

  if (!theme) return null;

  const currentMode = themeState.currentMode;

  const handleToggleTheme = () => {
    setThemeState({
      ...themeState,
      currentMode: currentMode === "light" ? "dark" : "light",
    });
  };

  const handleLike = () => {
    if (
      !checkValidSession("signin", "LIKE_THEME", {
        communityThemeId: theme.id,
      })
    ) {
      return;
    }
    toggleLike.mutate(theme.id);
  };

  const handleViewDetails = () => {
    onOpenChange(false);
    router.push(`/themes/${theme.themeId}`);
  };

  const handleShare = () => {
    const url = `https://tweakcn.com/themes/${theme.themeId}`;
    navigator.clipboard.writeText(url);
    toast({
      title: "Theme URL copied to clipboard!",
    });
  };

  const authorInitials = theme.author.name
    ?.split(" ")
    .map((n) => n[0])
    .join("")
    .slice(0, 2)
    .toUpperCase();

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent
        className="flex h-[80vh] w-[95vw] max-w-6xl flex-col gap-0 overflow-visible p-0"
        style={{
          "--tw-enter-translate-x": "0",
          "--tw-enter-translate-y": "0",
          "--tw-exit-translate-x": "0",
          "--tw-exit-translate-y": "0",
        } as React.CSSProperties}
      >
        {/* Navigation arrows — positioned outside the dialog box */}
        {hasPrev && (
          <button
            onClick={goToPrev}
            className="absolute -left-14 top-1/2 -translate-y-1/2 rounded-full border bg-background/80 p-2 text-foreground shadow-lg backdrop-blur-sm transition-all hover:bg-background hover:scale-110"
            aria-label="Previous theme"
          >
            <ChevronLeft className="size-5" />
          </button>
        )}
        {hasNext && (
          <button
            onClick={goToNext}
            className="absolute -right-14 top-1/2 -translate-y-1/2 rounded-full border bg-background/80 p-2 text-foreground shadow-lg backdrop-blur-sm transition-all hover:bg-background hover:scale-110"
            aria-label="Next theme"
          >
            <ChevronRight className="size-5" />
          </button>
        )}
        {/* Inner content wrapper — clips content within dialog bounds */}
        <div className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[inherit]">
          {/* Header: name + author */}
          <DialogHeader className="shrink-0 px-4 pt-4 pb-3">
            <div className="flex items-center gap-3">
              <DialogTitle className="text-base font-semibold">
                {theme.name}
              </DialogTitle>
              <span className="text-muted-foreground/40">|</span>
              <div className="flex items-center gap-1.5 text-muted-foreground">
                <Avatar className="h-5 w-5">
                  {theme.author.image && (
                    <AvatarImage
                      src={theme.author.image}
                      alt={theme.author.name}
                    />
                  )}
                  <AvatarFallback className="text-[9px]">
                    {authorInitials}
                  </AvatarFallback>
                </Avatar>
                <span className="text-xs">{theme.author.name}</span>
              </div>
            </div>
          </DialogHeader>

          {/* Toolbar */}
          <div className="flex shrink-0 items-center justify-between px-4 py-1">
            <span className="text-xs font-medium uppercase tracking-wider text-muted-foreground/70">
              Preview
            </span>
            <div className="flex items-center gap-1">
              <button
                onClick={handleLike}
                className={cn(
                  "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-all",
                  theme.isLikedByMe
                    ? "bg-red-500/10 text-red-500 hover:bg-red-500/20"
                    : "text-muted-foreground hover:bg-accent hover:text-foreground"
                )}
                title={theme.isLikedByMe ? "Unlike theme" : "Like theme"}
              >
                <Heart
                  className={cn(
                    "size-4",
                    theme.isLikedByMe && "fill-current"
                  )}
                />
                {theme.likeCount > 0 && <span>{theme.likeCount}</span>}
              </button>
              <button
                onClick={handleShare}
                className="rounded-md p-2 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
                title="Share theme"
              >
                <Share2 className="size-4" />
              </button>
              <button
                onClick={handleToggleTheme}
                className="rounded-md p-2 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
                title="Toggle light/dark mode"
              >
                {currentMode === "dark" ? (
                  <Sun className="size-4" />
                ) : (
                  <Moon className="size-4" />
                )}
              </button>
              <div className="h-4 w-px bg-border mr-2" />
              <Button variant="link" className="p-0 text-foreground font-semibold" onClick={handleViewDetails}>
                View
                <ArrowUpRight className="size-4" />
              </Button>
            </div>
          </div>

          {/* Card preview — scrollable */}
          <div className="min-h-0 flex-1">
            <ScrollArea className="h-full">
              <ExamplesPreviewContainer className="size-full">
                <DemoCards />
              </ExamplesPreviewContainer>
            </ScrollArea>
          </div>
        </div>
      </DialogContent>
    </Dialog>
  );
}


================================================
FILE: app/community/components/community-themes-content.tsx
================================================
"use client";

import { useCommunityThemes } from "@/hooks/themes";
import { useEditorStore } from "@/store/editor-store";
import type {
  CommunityFilterOption,
  CommunityTimeRange,
  CommunityTheme,
} from "@/types/community";
import {
  useQueryState,
  parseAsStringLiteral,
  parseAsArrayOf,
  parseAsString,
} from "nuqs";
import { useEffect, useRef, useState, useCallback } from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
import {
  Sheet,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuLabel,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Flame, Loader2, Info, SlidersHorizontal, ChevronDown, Check } from "lucide-react";
import { CommunityThemeCard } from "./community-theme-card";
import { CommunityThemePreviewDialog } from "./community-theme-preview-dialog";
import { CommunitySidebarContent } from "./community-sidebar";
import { Skeleton } from "@/components/ui/skeleton";
import Link from "next/link";

const popularOptions: {
  timeRange: CommunityTimeRange;
  label: string;
}[] = [
    { timeRange: "all", label: "All Time" },
    { timeRange: "monthly", label: "This Month" },
    { timeRange: "weekly", label: "This Week" },
  ];

const otherSortOptions: {
  sort: "newest" | "oldest";
  label: string;
}[] = [
    { sort: "newest", label: "Newest" },
    { sort: "oldest", label: "Oldest" },
  ];

export function CommunityThemesContent() {
  const [sort, setSort] = useQueryState(
    "sort",
    parseAsStringLiteral(["popular", "newest", "oldest"] as const).withDefault(
      "popular"
    )
  );
  const [filter, setFilter] = useQueryState(
    "filter",
    parseAsStringLiteral(["all", "mine", "liked"] as const).withDefault("all")
  );
  const [tags, setTags] = useQueryState(
    "tags",
    parseAsArrayOf(parseAsString, ",").withDefault([])
  );
  const [timeRange, setTimeRange] = useQueryState(
    "t",
    parseAsStringLiteral(["weekly", "monthly", "all"] as const).withDefault(
      "all"
    )
  );
  const [sheetOpen, setSheetOpen] = useState(false);
  const [previewThemeId, setPreviewThemeId] = useState<string | null>(null);
  const { themeState, setThemeState } = useEditorStore();

  const handlePreview = useCallback(
    (theme: CommunityTheme) => {
      setThemeState({ ...themeState, styles: theme.styles });
      setPreviewThemeId(theme.id);
    },
    [themeState, setThemeState]
  );

  const handleFilterChange = useCallback(
    (newFilter: CommunityFilterOption) => {
      setFilter(newFilter);
      setSheetOpen(false);
    },
    [setFilter]
  );

  const handleTagToggle = useCallback(
    (tag: string) => {
      setTags((prev) => (prev.includes(tag) ? [] : [tag]));
    },
    [setTags]
  );

  const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
    useCommunityThemes(sort, filter, tags, sort === "popular" ? timeRange : "all");

  const themes = data?.pages.flatMap((page) => page.themes) ?? [];
  const previewTheme = previewThemeId
    ? themes.find((t) => t.id === previewThemeId) ?? null
    : null;

  const sentinelRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const sentinel = sentinelRef.current;
    if (!sentinel) return;

    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
          fetchNextPage();
        }
      },
      { rootMargin: "200px" }
    );

    observer.observe(sentinel);
    return () => observer.disconnect();
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  const hasActiveFilters = filter !== "all" || tags.length > 0;

  const sidebarProps = {
    filter,
    selectedTags: tags,
    onFilterChange: handleFilterChange,
    onTagToggle: handleTagToggle,
  };

  return (
    <div className="flex flex-1">
      {/* Desktop sidebar */}
      <aside className="hidden w-56 shrink-0 border-r lg:block">
        <div className="p-4">
          <Link href="/community" className="block">
            <h1 className="text-lg font-semibold tracking-tight">
              Community Themes
            </h1>
            <p className="text-muted-foreground mt-1 text-xs">
              Discover themes by the community
            </p>
          </Link>
        </div>

        <div className="p-4">
          <CommunitySidebarContent {...sidebarProps} />
        </div>
      </aside>

      {/* Main content */}
      <div className="min-w-0 flex-1">
        <div className="space-y-6 p-4">
          <div className="flex items-center justify-between gap-4">
            <div className="flex items-center gap-2">
              {/* Mobile filter button */}
              <Sheet open={sheetOpen} onOpenChange={setSheetOpen}>
                <SheetTrigger asChild>
                  <Button
                    variant="outline"
                    size="sm"
                    className={cn(
                      "gap-1.5 lg:hidden",
                      hasActiveFilters && "border-primary text-primary"
                    )}
                  >
                    <SlidersHorizontal className="size-3.5" />
                    Filters
                    {hasActiveFilters && (
                      <span className="bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full text-[10px] font-bold">
                        {(filter !== "all" ? 1 : 0) + tags.length}
                      </span>
                    )}
                  </Button>
                </SheetTrigger>
                <SheetContent side="left" className="w-72 p-0">
                  <SheetHeader className="border-b px-6 py-4">
                    <SheetTitle>Community Themes</SheetTitle>
                  </SheetHeader>
                  <div className="p-4">
                    <CommunitySidebarContent {...sidebarProps} />
                  </div>
                </SheetContent>
              </Sheet>

              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <Button
                    variant="outline"
                    size="sm"
                    className="gap-1.5"
                  >
                    {sort === "popular"
                      ? `Popular / ${popularOptions.find((o) => o.timeRange === timeRange)?.label ?? "All Time"}`
                      : sort === "newest"
                        ? "Newest"
                        : "Oldest"}
                    <ChevronDown className="size-3.5 opacity-50" />
                  </Button>
                </DropdownMenuTrigger>
                <DropdownMenuContent align="start" className="w-44">
                  <DropdownMenuLabel>Popular</DropdownMenuLabel>
                  {popularOptions.map((option) => {
                    const isActive =
                      sort === "popular" && timeRange === option.timeRange;
                    return (
                      <DropdownMenuItem
                        key={option.timeRange}
                        onClick={() => {
                          setSort("popular");
                          setTimeRange(option.timeRange);
                        }}
                        className="cursor-pointer justify-between"
                      >
                        {option.label}
                        {isActive && <Check className="size-3.5" />}
                      </DropdownMenuItem>
                    );
                  })}
                  <DropdownMenuSeparator />
                  {otherSortOptions.map((option) => {
                    const isActive = sort === option.sort;
                    return (
                      <DropdownMenuItem
                        key={option.sort}
                        onClick={() => {
                          setSort(option.sort);
                        }}
                        className="cursor-pointer justify-between"
                      >
                        {option.label}
                        {isActive && <Check className="size-3.5" />}
                      </DropdownMenuItem>
                    );
                  })}
                </DropdownMenuContent>
              </DropdownMenu>
            </div>

            <Popover>
              <PopoverTrigger asChild>
                <Button
                  variant="ghost"
                  size="sm"
                  className="text-muted-foreground gap-1.5 text-xs"
                >
                  <Info className="size-3.5" />
                  <span className="hidden sm:inline">How to publish</span>
                </Button>
              </PopoverTrigger>
              <PopoverContent align="end" className="w-72 p-0">
                <div className="p-4">
                  <p className="text-sm font-medium">Publish your theme</p>
                  <p className="text-muted-foreground mt-1.5 text-xs leading-relaxed">
                    After saving a theme, click the{" "}
                    <span className="text-foreground font-medium">
                      Publish
                    </span>{" "}
                    button in the editor to share it.
                  </p>
                </div>
                <Separator />
                <div className="p-4">
                  <p className="text-muted-foreground text-xs leading-relaxed">
                    You can also manage all your saved themes from{" "}
                    <Link
                      href="/settings/themes"
                      className="text-foreground font-medium underline underline-offset-2"
                    >
                      Settings
                    </Link>
                    .
                  </p>
                </div>
              </PopoverContent>
            </Popover>
          </div>

          {isLoading ? (
            <div className="grid gap-5 grid-cols-[repeat(auto-fill,minmax(280px,1fr))]">
              {Array.from({ length: 8 }).map((_, i) => (
                <div
                  key={i}
                  className="space-y-0 overflow-hidden rounded-xl border"
                >
                  <Skeleton className="h-36 rounded-none" />
                  <div className="space-y-2 p-3">
                    <Skeleton className="h-4 w-2/3" />
                    <Skeleton className="h-3 w-1/3" />
                  </div>
                </div>
              ))}
            </div>
          ) : themes.length === 0 ? (
            <div className="py-24 text-center">
              <div className="bg-muted mx-auto mb-4 flex size-16 items-center justify-center rounded-full">
                <Flame className="text-muted-foreground size-8" />
              </div>
              <h3 className="mb-2 text-lg font-semibold">
                {filter === "mine"
                  ? "No published themes"
                  : filter === "liked"
                    ? "No liked themes"
                    : "No themes yet"}
              </h3>
              <p className="text-muted-foreground mx-auto max-w-sm">
                {filter === "mine"
                  ? "You haven't published any themes yet. Save a theme in the editor, then publish it."
                  : filter === "liked"
                    ? "You haven't liked any themes yet. Browse community themes and like your favorites."
                    : "Be the first to publish a theme to the community! Save a theme in the editor, then publish it from your settings."}
              </p>
            </div>
          ) : (
            <>
              <div className="grid gap-5 gap-y-8 grid-cols-[repeat(auto-fill,minmax(280px,1fr))]">
                {themes.map((theme) => (
                  <CommunityThemeCard key={theme.id} theme={theme} onPreview={handlePreview} />
                ))}
              </div>

              <div ref={sentinelRef} className="flex justify-center pt-4">
                {isFetchingNextPage && (
                  <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
                )}
              </div>
            </>
          )}
        </div>
      </div>
      <CommunityThemePreviewDialog
        theme={previewTheme}
        themes={themes}
        open={!!previewThemeId}
        onOpenChange={(open) => {
          if (!open) setPreviewThemeId(null);
        }}
        onNavigate={handlePreview}
      />
    </div>
  );
}


================================================
FILE: app/community/layout.tsx
================================================
import { Footer } from "@/components/footer";
import { Header } from "@/components/header";

export default function CommunityLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="flex flex-1 flex-col">{children}</main>
      <Footer />
    </div>
  );
}


================================================
FILE: app/community/page.tsx
================================================
import { Metadata } from "next";
import { CommunityThemesContent } from "./components/community-themes-content";
import { COMMUNITY_THEME_TAGS } from "@/lib/constants";

export const metadata: Metadata = {
  title: "Community Themes - tweakcn",
  description:
    "Discover and explore beautiful shadcn/ui themes created by the community.",
  keywords: [...COMMUNITY_THEME_TAGS, "shadcn", "theme", "ui"],
  openGraph: {
    title: "Community Themes - tweakcn",
    description:
      "Discover and explore beautiful shadcn/ui themes created by the community.",
    type: "website",
  },
  twitter: {
    card: "summary_large_image",
    title: "Community Themes - tweakcn",
    description:
      "Discover and explore beautiful shadcn/ui themes created by the community.",
  },
};

export default function CommunityPage() {
  return <CommunityThemesContent />;
}


================================================
FILE: app/dashboard/layout.tsx
================================================
import { Footer } from "@/components/footer";
import { Header } from "@/components/header";

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex flex-col">
      <Header />
      <main className="flex min-h-screen flex-1 flex-col">{children}</main>
      <Footer />
    </div>
  );
}


================================================
FILE: app/dashboard/loading.tsx
================================================
import { Loading } from "@/components/loading";

export default function DashboardLoading() {
  return <Loading className="flex-1" />;
}


================================================
FILE: app/dashboard/page.tsx
================================================
import { redirect } from "next/navigation";

// This page is being moved to settings/themes
export default function DashboardRedirect() {
  redirect("/settings/themes");
}


================================================
FILE: app/editor/theme/[[...themeId]]/layout.tsx
================================================
import { Header } from "@/components/header";

export default function EditorLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="relative isolate flex h-svh flex-col overflow-hidden">
      <Header />
      <main className="isolate flex flex-1 flex-col overflow-hidden">{children}</main>
    </div>
  );
}


================================================
FILE: app/editor/theme/[[...themeId]]/loading.tsx
================================================
import { Loading } from "@/components/loading";

export default function EditorLoading() {
  return <Loading className="flex-1" />;
}


================================================
FILE: app/editor/theme/[[...themeId]]/page.tsx
================================================
import { getTheme } from "@/actions/themes";
import Editor from "@/components/editor/editor";
import { Metadata } from "next";

export const metadata: Metadata = {
  title: "tweakcn — Theme Generator for shadcn/ui",
  description:
    "Easily customize and preview your shadcn/ui theme with tweakcn. Modify colors, fonts, and styles in real-time.",
};

export default async function EditorPage({ params }: { params: Promise<{ themeId: string[] }> }) {
  const { themeId } = await params;
  const themePromise = themeId?.length > 0 ? getTheme(themeId?.[0]) : Promise.resolve(null);

  return <Editor themePromise={themePromise} />;
}


================================================
FILE: app/figma/layout.tsx
================================================
import { Metadata } from "next";

export const metadata: Metadata = {
  title: "Apply Your tweakcn Theme to Shadcraft Figma UI Kit | Professional Design System",
  description:
    "Transform your tweakcn themes into stunning Figma designs with Shadcraft's premium UI kit. 51 components, 44 blocks, dark mode support, and 1500+ icons. Professional Figma design system for shadcn/ui themes.",
  keywords:
    "figma ui kit, shadcn ui figma, tweakcn themes, figma design system, ui components figma, design tokens figma, figma plugin, shadcraft, figma templates, design system integration",
  authors: [{ name: "tweakcn Team" }],
  openGraph: {
    title: "Apply Your tweakcn Theme to Shadcraft Figma UI Kit",
    description:
      "Professional Figma UI kit with 51 components, 44 blocks, and seamless tweakcn theme integration. Get the ultimate design system for your projects.",
    url: "https://tweakcn.com/figma",
    siteName: "tweakcn",
    images: [
      {
        url: "https://tweakcn.com/figma-onboarding/shadcraft-preview.jpg",
        width: 1200,
        height: 630,
        alt: "Shadcraft Figma UI Kit Preview",
      },
    ],
    type: "website",
  },
  twitter: {
    card: "summary_large_image",
    title: "Apply Your tweakcn Theme to Shadcraft Figma UI Kit",
    description:
      "Professional Figma UI kit with 51 components, 44 blocks, and seamless tweakcn theme integration.",
    images: ["https://tweakcn.com/figma-onboarding/shadcraft-preview.jpg"],
  },
  robots: "index, follow",
  alternates: {
    canonical: "https://tweakcn.com/figma",
  },
};

export default function FigmaLayout({ children }: { children: React.ReactNode }) {
  return <>{children}</>;
}


================================================
FILE: app/figma/page.tsx
================================================
"use client";

import { useState, useEffect } from "react";
import { FigmaHeader } from "@/components/figma-header";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import Logo from "@/assets/logo.svg";
import Shadcraft from "@/assets/shadcraft.svg";
import FigmaIcon from "@/assets/figma.svg";
import { Check, X, ArrowUpRight, Figma, Cable, Paintbrush } from "lucide-react";
import Link from "next/link";
import { FIGMA_CONSTANTS, redirectToShadcraft } from "@/lib/figma-constants";

export default function FigmaPage() {
  const [isScrolled, setIsScrolled] = useState(false);
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

  const steps = FIGMA_CONSTANTS.steps.map((step, index) => ({
    ...step,
    icon:
      index === 0 ? (
        <Figma className="h-6 w-6" />
      ) : index === 1 ? (
        <Cable className="h-6 w-6" />
      ) : (
        <Paintbrush className="h-6 w-6" />
      ),
  }));

  const handleGetStarted = () => {
    redirectToShadcraft();
  };

  useEffect(() => {
    const handleScroll = () => {
      if (window.scrollY > 10) {
        setIsScrolled(true);
      } else {
        setIsScrolled(false);
      }
    };

    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  // Structured data for SEO
  const structuredData = {
    "@context": "https://schema.org",
    "@type": "Product",
    name: "Shadcraft Figma UI Kit - tweakcn Integration",
    description:
      "Professional Figma UI kit with 51 components, 44 blocks, dark mode support, and seamless tweakcn theme integration",
    image: "https://tweakcn.com/figma-onboarding/shadcraft-preview.jpg",
    brand: {
      "@type": "Brand",
      name: "Shadcraft",
    },
    offers: {
      "@type": "Offer",
      price: "89",
      priceCurrency: "USD",
      priceValidUntil: "2025-12-31",
      availability: "https://schema.org/InStock",
      url: FIGMA_CONSTANTS.shadcraftUrl,
      seller: {
        "@type": "Organization",
        name: "Shadcraft",
      },
    },
    aggregateRating: {
      "@type": "AggregateRating",
      ratingValue: "5",
      reviewCount: "100+",
    },
    category: "Design Software",
    additionalProperty: [
      {
        "@type": "PropertyValue",
        name: "Components",
        value: "51",
      },
      {
        "@type": "PropertyValue",
        name: "Blocks",
        value: "44",
      },
      {
        "@type": "PropertyValue",
        name: "Icons",
        value: "1500+",
      },
    ],
  };

  return (
    <>
      {/* Structured Data */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
      />

      <div className="relative min-h-screen">
        {/* Gradient Background using CSS variables */}
        <div className="from-background via-muted/20 to-accent/10 fixed inset-0 -z-10 bg-gradient-to-br" />
        <div className="from-primary/5 to-secondary/10 fixed inset-0 -z-10 bg-gradient-to-tr via-transparent" />

        {/* Header */}
        <FigmaHeader
          isScrolled={isScrolled}
          mobileMenuOpen={mobileMenuOpen}
          setMobileMenuOpen={setMobileMenuOpen}
        />

        {/* Main Content */}
        <main className="container mx-auto max-w-[600px] p-4 pt-8">
          {/* Header Section */}
          <header className="p-8 pb-5">
            <div className="flex items-center justify-center gap-2">
              <div className="flex items-center gap-2">
                <Logo className="h-6 w-6" alt="tweakcn logo" />
                <div className="text-lg font-bold">tweakcn</div>
              </div>
              <X className="h-4 w-4" aria-hidden="true" />
              <Link href={FIGMA_CONSTANTS.shadcraftUrl} target="_blank" rel="noopener noreferrer">
                <div className="flex items-center gap-2">
                  <Shadcraft className="h-6 w-6" alt="Shadcraft logo" />
                  <div className="text-lg font-bold">shadcraft</div>
                </div>
              </Link>
            </div>
          </header>

          <div className="space-y-16 px-8 pb-32">
            {/* Hero Section */}
            <section className="space-y-6 text-center">
              <h1 className="text-5xl leading-12 font-semibold tracking-tight">
                Apply your theme to the ultimate Figma UI kit
              </h1>

              <div className="flex justify-center gap-3.5">
                <Button size="lg" className="h-10 px-8" onClick={handleGetStarted}>
                  Get started
                </Button>
                <Link href={FIGMA_CONSTANTS.previewUrl} target="_blank" rel="noopener noreferrer">
                  <Button variant="outline" size="lg" className="h-10 gap-2 px-8">
                    <FigmaIcon className="h-4 w-4" />
                    Preview
                  </Button>
                </Link>
              </div>

              <div className="space-y-1.5 pt-1">
                <p className="text-muted-foreground text-sm">Trusted by top designers</p>
                <div
                  className="flex justify-center -space-x-3"
                  role="group"
                  aria-label="Designer avatars"
                >
                  {FIGMA_CONSTANTS.designers.map((designer, index) => (
                    <Avatar key={index} className="border-background h-8 w-8 border-2">
                      <AvatarImage src={designer.avatar} alt={`${designer.name} avatar`} />
                      <AvatarFallback className="text-xs">{designer.fallback}</AvatarFallback>
                    </Avatar>
                  ))}
                </div>
              </div>
            </section>

            {/* How it works */}
            <section className="space-y-4">
              <h2 className="text-center text-2xl font-semibold">How it works</h2>
              <div className="border-border rounded-2xl border px-6">
                <div className="divide-border grid grid-cols-3 divide-x">
                  {steps.map((step, index) => (
                    <article
                      key={index}
                      className="space-y-2 px-6 py-6 text-center first:pl-0 last:pr-0"
                    >
                      <div className="text-foreground mb-2 flex justify-center" aria-hidden="true">
                        {step.icon}
                      </div>
                      <p className="text-muted-foreground text-sm">{step.step}</p>
                      <h3 className="font-semibold">{step.title}</h3>
                      <p className="text-muted-foreground sr-only text-sm">{step.description}</p>
                    </article>
                  ))}
                </div>
              </div>
            </section>

            {/* Feature Description */}
            <section className="space-y-6 text-center">
              <div className="mx-auto max-w-sm space-y-1.5">
                <h2 className="text-2xl font-semibold">
                  Top quality Figma UI kit for professionals
                </h2>
                <p className="text-muted-foreground">
                  Shadcraft is packed with top quality components, true to the shadcn/ui ethos.
                </p>
              </div>

              {/* Demo UI Preview */}
              <figure className="border-border relative overflow-hidden rounded-2xl border">
                <img
                  src="/figma-onboarding/shadcraft-preview.jpg"
                  alt="Shadcraft Figma UI Kit Preview showing various components and design blocks"
                  className="h-auto w-full"
                  loading="lazy"
                  width="600"
                  height="400"
                />
              </figure>

              <Link href={FIGMA_CONSTANTS.shadcraftUrl} target="_blank" rel="noopener noreferrer">
                <Button variant="link" className="gap-1 text-sm">
                  More on Shadcraft
                  <ArrowUpRight className="h-3 w-3" />
                </Button>
              </Link>
            </section>

            {/* Pricing */}
            <section className="space-y-6">
              <h2 className="text-center text-2xl font-semibold">Pricing</h2>

              <Card className="p-6">
                <h3 className="mb-4 font-semibold">What you get with Shadcraft</h3>
                <div className="grid gap-7 md:grid-cols-2">
                  <div className="space-y-4">
                    <ul className="space-y-2" role="list">
                      {FIGMA_CONSTANTS.features.map((feature, index) => (
                        <li key={index} className="flex items-center gap-1.5">
                          <Check className="h-4 w-4 text-green-600" aria-hidden="true" />
                          <span className="text-sm">{feature}</span>
                        </li>
                      ))}
                    </ul>
                  </div>

                  <div className="mt-auto space-y-4">
                    <div className="space-y-1.5">
                      <div className="flex items-end gap-1">
                        <span className="text-5xl font-semibold">$119</span>
                      </div>
                    </div>
                    <div className="flex gap-2">
                      <Button className="flex-1" onClick={handleGetStarted}>
                        Get started
                      </Button>
                      <Link
                        href={FIGMA_CONSTANTS.previewUrl}
                        target="_blank"
                        rel="noopener noreferrer"
                      >
                        <Button variant="outline" className="gap-2">
                          <FigmaIcon className="h-4 w-4" />
                          Preview
                        </Button>
                      </Link>
                    </div>
                  </div>
                </div>
              </Card>
              <p className="text-muted-foreground text-center text-xs">Prices in USD</p>
            </section>
          </div>
        </main>
      </div>
    </>
  );
}


================================================
FILE: app/globals.css
================================================
@import "tailwindcss";
@import "tw-animate-css";
@import "streamdown/styles.css";
@import "./loaders.css";

@source "../node_modules/streamdown/dist";

@custom-variant dark (&:is(.dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);

  --font-sans: var(--font-sans);
  --font-mono: var(--font-mono);
  --font-serif: var(--font-serif);

  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);

  /* Computed Shadow Variants */
  --shadow-2xs: var(--shadow-2xs);
  --shadow-xs: var(--shadow-xs);
  --shadow-sm: var(--shadow-sm);
  --shadow: var(--shadow);
  --shadow-md: var(--shadow-md);
  --shadow-lg: var(--shadow-lg);
  --shadow-xl: var(--shadow-xl);
  --shadow-2xl: var(--shadow-2xl);

  --tracking-tighter: calc(var(--letter-spacing) - 0.05em);
  --tracking-tight: calc(var(--letter-spacing) - 0.025em);
  --tracking-normal: var(--letter-spacing);
  --tracking-wide: calc(var(--letter-spacing) + 0.025em);
  --tracking-wider: calc(var(--letter-spacing) + 0.05em);
  --tracking-widest: calc(var(--letter-spacing) + 0.1em);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
  }
}

* {
  color-scheme: light dark;
  border-color: var(--color-border);
}

body {
  background-color: var(--color-background);
  color: var(--color-foreground);
  -webkit-font-smoothing: antialiased;
  letter-spacing: var(--letter-spacing);
}

@layer base {
  button:not(:disabled),
  [role="button"]:not(:disabled) {
    cursor: pointer;
  }
}

/* View Transition Wave Effect */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

::view-transition-old(root) {
  /* Ensure the outgoing view (old theme) is beneath */
  z-index: 0;
}

::view-transition-new(root) {
  /* Ensure the incoming view (new theme) is always on top */
  z-index: 1;
}

@keyframes reveal {
  from {
    /* Use CSS variables for the origin, defaulting to center if not set */
    clip-path: circle(0% at var(--x, 50%) var(--y, 50%));
    opacity: 0.7;
  }
  to {
    /* Use CSS variables for the origin, defaulting to center if not set */
    clip-path: circle(150% at var(--x, 50%) var(--y, 50%));
    opacity: 1;
  }
}

::view-transition-new(root) {
  /* Apply the reveal animation */
  animation: reveal 0.4s ease-in-out forwards;
}

/* Mention styles */
.mention {
  @apply bg-primary/10 rounded-md px-1 font-mono text-sm font-semibold;
}

.code-inline {
  @apply bg-muted text-muted-foreground px-1.5 py-0.5 font-mono text-xs font-medium;
}

@keyframes text {
  from {
    background-position: 0% center;
  }
  to {
    background-position: -200% center;
  }
}

.animate-text {
  animation: text 3s linear infinite;
}

@utility scrollbar-thin {
  scrollbar-width: thin;
  scrollbar-color: var(--color-border) transparent;

  &::-webkit-scrollbar {
    width: 8px;
  }
}

@utility scrollbar-gutter-stable {
  scrollbar-gutter: stable;
}

@utility scrollbar-gutter-both {
  scrollbar-gutter: stable both-edges;
}


================================================
FILE: app/layout.tsx
================================================
import { AuthDialogWrapper } from "@/components/auth-dialog-wrapper";
import { DynamicFontLoader } from "@/components/dynamic-font-loader";
import { GetProDialogWrapper } from "@/components/get-pro-dialog-wrapper";
import { PostHogInit } from "@/components/posthog-init";
import { ThemeProvider } from "@/components/theme-provider";
import { ThemeScript } from "@/components/theme-script";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { ChatProvider } from "@/hooks/use-chat-context";
import { QueryProvider } from "@/lib/query-client";
import type { Metadata, Viewport } from "next";
import { NuqsAdapter } from "nuqs/adapters/next/app";
import { Suspense } from "react";
import "./globals.css";

export const metadata: Metadata = {
  title: "Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator",
  description:
    "Customize theme for shadcn/ui with tweakcn's interactive editor. Supports Tailwind CSS v4, Shadcn UI, and custom styles. Modify properties, preview changes, and get the code in real time.",
  keywords:
    "theme editor, theme generator, shadcn, ui, components, react, tailwind, button, editor, visual editor, component editor, web development, frontend, design system, UI components, React components, Tailwind CSS, shadcn/ui themes",
  authors: [{ name: "Sahaj Jain" }],
  openGraph: {
    title: "Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator",
    description:
      "Customize theme for shadcn/ui with tweakcn's interactive editor. Supports Tailwind CSS v4, Shadcn UI, and custom styles. Modify properties, preview changes, and get the code in real time.",
    url: "https://tweakcn.com/",
    siteName: "tweakcn",
    images: [
      {
        url: "https://tweakcn.com/og-image.v050725.png",
        width: 1200,
        height: 630,
      },
    ],
    locale: "en_US",
    type: "website",
  },
  twitter: {
    card: "summary_large_image",
    title: "Beautiful themes for shadcn/ui — tweakcn | Theme Editor & Generator",
    description:
      "Customize theme for shadcn/ui with tweakcn's interactive editor. Supports Tailwind CSS v4, Shadcn UI, and custom styles. Modify properties, preview changes, and get the code in real time.",
    images: ["https://tweakcn.com/og-image.v050725.png"],
  },
  robots: "index, follow",
};

export const viewport: Viewport = {
  width: "device-width",
  initialScale: 1.0,
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <ThemeScript />
        <DynamicFontLoader />
        <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
        <link rel="icon" href="/favicon.ico" sizes="any" />
        <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
        <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
        <link
          rel="apple-touch-icon"
          href="/apple-touch-icon.png"
          type="image/png"
          sizes="180x180"
        />
        <link rel="manifest" href="/site.webmanifest" />

        {/* PRELOAD FONTS USED BY BUILT-IN THEMES */}
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
        <link
          href="https://fonts.googleapis.com/css2?family=Architects+Daughter&family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Fira+Code:wght@300..700&family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Lora:ital,wght@0,400..700;1,400..700&family=Merriweather:ital,opsz,wght@0,18..144,300..900;1,18..144,300..900&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Outfit:wght@100..900&family=Oxanium:wght@200..800&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Roboto:ital,wght@0,100..900;1,100..900&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&family=Space+Grotesk:wght@300..700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap"
          rel="stylesheet"
        />
        <meta name="darkreader-lock" />
      </head>
      <body>
        <NuqsAdapter>
          <Suspense>
            <QueryProvider>
              <ThemeProvider defaultTheme="light">
                <TooltipProvider>
                  <AuthDialogWrapper />
                  <GetProDialogWrapper />
                  <Toaster />
                  <ChatProvider>{children}</ChatProvider>
                </TooltipProvider>
              </ThemeProvider>
            </QueryProvider>
          </Suspense>
        </NuqsAdapter>
        <PostHogInit />
      </body>
    </html>
  );
}


================================================
FILE: app/loaders.css
================================================
/* keyframes for loaders */
@theme {
  @keyframes typing {
    0%,
    100% {
      transform: translateY(0);
      opacity: 0.5;
    }
    50% {
      transform: translateY(-2px);
      opacity: 1;
    }
  }

  @keyframes loading-dots {
    0%,
    100% {
      opacity: 0;
    }
    50% {
      opacity: 1;
    }
  }

  @keyframes wave {
    0%,
    100% {
      transform: scaleY(1);
    }
    50% {
      transform: scaleY(0.6);
    }
  }

  @keyframes blink {
    0%,
    100% {
      opacity: 1;
    }
    50% {
      opacity: 0;
    }
  }

  @keyframes text-blink {
    0%,
    100% {
      color: var(--primary);
    }
    50% {
      color: var(--muted-foreground);
    }
  }

  @keyframes bounce-dots {
    0%,
    100% {
      transform: scale(0.8);
      opacity: 0.5;
    }
    50% {
      transform: scale(1.2);
      opacity: 1;
    }
  }

  @keyframes thin-pulse {
    0%,
    100% {
      transform: scale(0.95);
      opacity: 0.8;
    }
    50% {
      transform: scale(1.05);
      opacity: 0.4;
    }
  }

  @keyframes pulse-dot {
    0%,
    100% {
      transform: scale(1);
      opacity: 0.8;
    }
    50% {
      transform: scale(1.5);
      opacity: 1;
    }
  }

  @keyframes shimmer-text {
    0% {
      background-position: 150% center;
    }
    100% {
      background-position: -150% center;
    }
  }

  @keyframes wave-bars {
    0%,
    100% {
      transform: scaleY(1);
      opacity: 0.5;
    }
    50% {
      transform: scaleY(0.6);
      opacity: 1;
    }
  }

  @keyframes shimmer {
    0% {
      background-position: 200% 50%;
    }
    100% {
      background-position: -200% 50%;
    }
  }

  @keyframes spinner-fade {
    0% {
      opacity: 0;
    }
    100% {
      opacity: 1;
    }
  }
}


================================================
FILE: app/not-found.tsx
================================================
"use client";

import { ThemePresetButtons } from "@/components/home/theme-preset-buttons";
import { useTheme } from "@/components/theme-provider";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
import { useEditorStore } from "@/store/editor-store";
import { defaultPresets } from "@/utils/theme-presets";
import { Sun, Moon } from "lucide-react";
import Link from "next/link";

export default function NotFound() {
  const { theme, toggleTheme } = useTheme();
  const { themeState, applyThemePreset } = useEditorStore();
  const mode = themeState.currentMode;
  const presetNames = Object.keys(defaultPresets);
  return (
    <div className="bg-background flex min-h-screen flex-col items-center justify-center px-4">
      <div className="fixed top-4 right-4 z-50">
        <Tooltip>
          <TooltipTrigger asChild>
            <Button
              variant="ghost"
              size="sm"
              aria-label="Toggle theme"
              onClick={(e) => toggleTheme({ x: e.clientX, y: e.clientY })}
            >
              {theme === "light" ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
            </Button>
          </TooltipTrigger>
          <TooltipContent side="bottom">
            <p className="text-xs">Toggle theme</p>
          </TooltipContent>
        </Tooltip>
      </div>

      <span className="text-muted-foreground mb-6 text-[6rem] leading-none font-extrabold select-none">
        404
      </span>
      <h1 className="text-foreground mb-2 text-3xl font-bold">Oops, Lost in Space?</h1>
      <p className="text-muted-foreground mb-8 max-w-md text-center text-lg">
        Go home or try switching the theme!
      </p>

      <Link
        href="/"
        className="bg-primary text-primary-foreground hover:bg-primary/80 mb-10 rounded-md px-6 py-2 font-semibold shadow transition-colors"
      >
        Back to Home
      </Link>

      <div className="flex w-full justify-center">
        <ThemePresetButtons
          presetNames={presetNames}
          mode={mode}
          themeState={themeState}
          applyThemePreset={applyThemePreset}
        />
      </div>
    </div>
  );
}


================================================
FILE: app/oauth/authorize/page.tsx
================================================
"use client";

import Github from "@/assets/github.svg";
import Google from "@/assets/google.svg";
import { Button } from "@/components/ui/button";
import { authClient } from "@/lib/auth-client";
import { Loader2 } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";

const SCOPE_LABELS: Record<string, string> = {
  "themes:read": "Read your saved themes",
  "profile:read": "Read your profile (name, email)",
};

export default function OAuthAuthorizePage() {
  const searchParams = useSearchParams();
  const { data: session, isPending } = authClient.useSession();

  const [loadingProvider, setLoadingProvider] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [appName, setAppName] = useState<string | null>(null);
  const [redirecting, setRedirecting] = useState(false);

  const clientId = searchParams.get("client_id");
  const scopes =
    searchParams
      .get("scope")
      ?.split(/[\s,]+/)
      .filter(Boolean) ?? [];

  useEffect(() => {
    if (!clientId) {
      setError("Missing client_id parameter");
      return;
    }

    fetch(`/api/oauth/app-info?client_id=${encodeURIComponent(clientId)}`)
      .then((res) => res.json())
      .then((data) => {
        if (data.error) {
          setError(data.error_description ?? "Invalid application");
        } else {
          setAppName(data.name);
        }
      })
      .catch(() => setError("Failed to validate application"));
  }, [clientId]);

  useEffect(() => {
    if (!session || redirecting || error) return;

    setRedirecting(true);
    const apiUrl = `/api/oauth/authorize?${searchParams.toString()}`;
    window.location.href = apiUrl;
  }, [session, searchParams, redirecting, error]);

  const callbackURL = `/oauth/authorize?${searchParams.toString()}`;

  const handleSignIn = async (provider: "google" | "github") => {
    setLoadingProvider(provider);
    try {
      await authClient.signIn.social({ provider, callbackURL });
    } catch {
      setLoadingProvider(null);
    }
  };

  const isLoading = loadingProvider !== null;

  if (error) {
    return (
      <div className="flex min-h-svh items-center justify-center bg-background px-4">
        <div className="w-full max-w-sm">
          <div className="rounded-md border border-destructive/20 bg-destructive/5 px-4 py-3 text-sm text-destructive">
            {error}
          </div>
        </div>
      </div>
    );
  }

  if (isPending || redirecting || !appName) {
    return (
      <div className="flex min-h-svh items-center justify-center bg-background">
        <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
      </div>
    );
  }

  return (
    <div className="flex min-h-svh items-center justify-center bg-background px-4">
      <div className="w-full max-w-sm">
        <div className="mb-8 text-center">
          <p className="text-sm text-muted-foreground">Sign in to</p>
          <h1 className="mt-1 text-lg font-semibold text-foreground">
            {appName}
          </h1>
        </div>

        <div className="space-y-3">
          <Button
            variant="outline"
            onClick={() => handleSignIn("google")}
            className="h-10 w-full justify-center gap-2"
            disabled={isLoading}
          >
            <Google className="h-4 w-4" />
            Continue with Google
            {loadingProvider === "google" && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
          </Button>

          <Button
            variant="outline"
            onClick={() => handleSignIn("github")}
            className="h-10 w-full justify-center gap-2"
            disabled={isLoading}
          >
            <Github className="h-4 w-4" />
            Continue with GitHub
            {loadingProvider === "github" && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
          </Button>
        </div>

        {scopes.length > 0 && (
          <div className="mt-6 border-t pt-4">
            <p className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
              Permissions requested
            </p>
            <ul className="space-y-1.5 text-sm text-muted-foreground">
              {scopes.map((scope) => (
                <li key={scope}>{SCOPE_LABELS[scope] ?? scope}</li>
              ))}
            </ul>
          </div>
        )}

        <p className="mt-6 text-center text-xs text-muted-foreground/60">
          Authorizing will grant {appName} access to the permissions above.
        </p>
      </div>
    </div>
  );
}


================================================
FILE: app/page.tsx
================================================
"use client";

import { Footer } from "@/components/footer";
import { AIGenerationCTA } from "@/components/home/ai-generation-cta";
import { CTA } from "@/components/home/cta";
import { FAQ } from "@/components/home/faq";
import { Features } from "@/components/home/features";
import { Header } from "@/components/home/header";
import { Hero } from "@/components/home/hero";
import { HowItWorks } from "@/components/home/how-it-works";
import { Testimonials } from "@/components/home/testimonials";
// import { ThemePresetSelector } from "@/components/home/theme-preset-selector";
import { useEffect, useState } from "react";

export default function Home() {
  const [isScrolled, setIsScrolled] = useState(false);
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

  useEffect(() => {
    const handleScroll = () => {
      if (window.scrollY > 10) {
        setIsScrolled(true);
      } else {
        setIsScrolled(false);
      }
    };

    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return (
    <div className="bg-background text-foreground flex min-h-[100dvh] flex-col items-center justify-items-center">
      <Header
        isScrolled={isScrolled}
        mobileMenuOpen={mobileMenuOpen}
        setMobileMenuOpen={setMobileMenuOpen}
      />
      <main className="w-full flex-1">
        <Hero />
        {/* <ThemePresetSelector /> */}
        <Testimonials />
        <Features />
        <AIGenerationCTA />
        <HowItWorks />
        <FAQ />
        <CTA />
      </main>
      <Footer />
    </div>
  );
}


================================================
FILE: app/pricing/components/checkout-button.tsx
================================================
"use client";

import { createCheckout } from "@/actions/checkout";
import { Button } from "@/components/ui/button";
import { usePostLoginAction } from "@/hooks/use-post-login-action";
import { SUBSCRIPTION_STATUS_QUERY_KEY, useSubscription } from "@/hooks/use-subscription";
import { toast } from "@/hooks/use-toast";
import { authClient } from "@/lib/auth-client";
import { cn } from "@/lib/utils";
import { useAuthStore } from "@/store/auth-store";
import { useQueryClient } from "@tanstack/react-query";
import { Gem, Loader } from "lucide-react";
import { useRouter } from "next/navigation";
import { ComponentProps, useTransition } from "react";

interface CheckoutButtonProps extends ComponentProps<typeof Button> {}

export function CheckoutButton({ disabled, className, ...props }: CheckoutButtonProps) {
  const router = useRouter();
  const [isPending, startTransition] = useTransition();
  const { data: session } = authClient.useSession();
  const { openAuthDialog } = useAuthStore();

  usePostLoginAction("CHECKOUT", () => {
    handleOpenCheckout();
  });

  const queryClient = useQueryClient();
  const { subscriptionStatus } = useSubscription();
  const isPro = subscriptionStatus?.isSubscribed ?? false;

  const handleOpenCheckout = async () => {
    if (!session) {
      openAuthDialog("signup", "CHECKOUT");
      return;
    }

    if (subscriptionStatus?.isSubscribed) {
      router.push("/settings");
      return;
    }

    startTransition(async () => {
      const res = await createCheckout();

      if ("error" in res || !res.url) {
        toast({
          title: "Error",
          description: res.error || "Failed to create checkout",
          variant: "destructive",
        });
        return;
      }

      queryClient.invalidateQueries({ queryKey: [SUBSCRIPTION_STATUS_QUERY_KEY] });
      router.push(res.url);
    });
  };

  return (
    <Button
      variant={isPro ? "ghost" : "default"}
      disabled={isPending || disabled}
      className={cn(isPro ? "border" : "", className)}
      {...props}
      onClick={handleOpenCheckout}
    >
      {isPending ? (
        <div className="flex items-center gap-2">
          <Loader className="size-4 animate-spin" />
          Redirecting to Checkout
        </div>
      ) : isPro ? (
        <span className="flex items-center gap-1.5">
          <Gem />
          {`You're Subscribed to Pro`}
        </span>
      ) : (
        "Upgrade to Pro"
      )}
    </Button>
  );
}


================================================
FILE: app/pricing/layout.tsx
================================================
import { Footer } from "@/components/footer";
import { Header } from "@/components/header";

export default function PricingLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="flex flex-grow flex-col">{children}</main>
      <Footer />
    </div>
  );
}


================================================
FILE: app/pricing/page.tsx
================================================
import { NoiseEffect } from "@/components/effects/noise-effect";
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { AI_REQUEST_FREE_TIER_LIMIT } from "@/lib/constants";
import { cn } from "@/lib/utils";
import { FREE_SUB_FEATURES, PRO_SUB_FEATURES } from "@/utils/subscription";
import { Calendar, Check, Circle, Mail } from "lucide-react";
import Link from "next/link";
import { CheckoutButton } from "./components/checkout-button";
import { Metadata } from "next";
import { Testimonials } from "@/components/home/testimonials";

export const metadata: Metadata = {
  title: "Pricing — tweakcn",
  robots: "index, follow",
};

export default function PricingPage() {
  return (
    <div className="from-background via-background to-muted/20 relative isolate min-h-screen bg-gradient-to-br">
      {/* Background decoration */}
      <div className="absolute inset-0 overflow-hidden">
        <div className="bg-primary/10 absolute top-0 right-0 size-80 translate-x-1/2 -translate-y-1/2 rounded-full blur-3xl" />
        <div className="bg-secondary/10 absolute bottom-0 left-0 size-80 -translate-x-1/2 translate-y-1/2 rounded-full blur-3xl" />
      </div>

      <div className="relative container mx-auto space-y-28 px-4 py-20 md:px-6">
        {/* Header Section */}
        <section className="space-y-2 text-center">
          <h1 className="from-foreground to-foreground/50 bg-gradient-to-r bg-clip-text text-5xl font-bold tracking-tight text-pretty text-transparent md:text-6xl">
            Choose your perfect plan
          </h1>
          <p className="text-muted-foreground mx-auto max-w-3xl text-base text-balance md:text-lg">
            Start building beautiful themes for free. Upgrade to Pro when you&apos;re ready.
          </p>
        </section>

        {/* Pricing Cards */}
        <section className="mx-auto grid max-w-5xl gap-8 md:grid-cols-2 lg:gap-12">
          {/* Free Plan */}
          <Card className="group relative flex flex-col overflow-hidden border-2 transition-all duration-300">
            <CardHeader className="space-y-2 border-b">
              <div className="flex items-center gap-3">
                <CardTitle className="text-4xl font-medium">Free</CardTitle>
              </div>
              <div className="flex items-baseline">
                <span className="text-4xl font-bold tracking-tight lg:text-5xl">$0</span>
                <span className="text-muted-foreground text-lg">/month</span>
              </div>
              <p className="text-muted-foreground text-sm">No credit card required</p>
            </CardHeader>
            <CardContent className="flex-1 pt-6">
              <ul className="space-y-3">
                {FREE_SUB_FEATURES.map((feature, index) => (
                  <li key={index} className="flex items-start gap-3">
                    <div className="bg-primary/15 flex items-center justify-center rounded-full p-1">
                      {feature.status === "done" ? (
                        <Check className="text-primary size-3 stroke-2" />
                      ) : (
                        <Circle className="text-muted-foreground size-3 stroke-2" />
                      )}
                    </div>
                    <span className="text-sm">{feature.description}</span>
                  </li>
                ))}
              </ul>
            </CardContent>
            <CardFooter>
              <Button
                asChild
                variant="outline"
                className="hover:bg-muted/50 h-12 w-full text-base font-medium transition-all duration-200"
                size="lg"
              >
                <Link href="/editor/theme">Get Started Free</Link>
              </Button>
            </CardFooter>
          </Card>

          {/* Pro Plan */}
          <Card className="group ring-primary/50 from-card to-primary/5 relative border-2 bg-gradient-to-b ring-2 transition-all duration-300">
            <div className="relative flex h-full flex-col">
              <CardHeader className="relative space-y-2 border-b">
                <NoiseEffect />

                <div className="flex items-center gap-3">
                  <CardTitle className="text-4xl font-medium">Pro</CardTitle>
                </div>
                <div className="flex items-baseline">
                  <span className="text-4xl font-bold tracking-tight lg:text-5xl">$8</span>
                  <span className="text-muted-foreground text-lg">/
Download .txt
gitextract_41hngla7/

├── .dockerignore
├── .github/
│   ├── FUNDING.yml
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierignore
├── .prettierrc
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── actions/
│   ├── account.ts
│   ├── ai-usage.ts
│   ├── checkout.ts
│   ├── community-themes.ts
│   ├── customer.ts
│   └── themes.ts
├── app/
│   ├── (auth)/
│   │   └── components/
│   │       └── auth-dialog.tsx
│   ├── (legal)/
│   │   ├── layout.tsx
│   │   └── privacy-policy/
│   │       └── page.tsx
│   ├── ai/
│   │   ├── components/
│   │   │   ├── ai-announcement.tsx
│   │   │   ├── ai-chat-form.tsx
│   │   │   ├── ai-chat-hero.tsx
│   │   │   ├── chat-heading.tsx
│   │   │   ├── community-theme-card.tsx
│   │   │   ├── community-themes.tsx
│   │   │   └── suggested-pill-actions.tsx
│   │   ├── layout.tsx
│   │   ├── loading.tsx
│   │   └── page.tsx
│   ├── api/
│   │   ├── auth/
│   │   │   └── [...all]/
│   │   │       └── route.ts
│   │   ├── enhance-prompt/
│   │   │   └── route.ts
│   │   ├── generate-theme/
│   │   │   └── route.ts
│   │   ├── google-fonts/
│   │   │   └── route.ts
│   │   ├── oauth/
│   │   │   ├── app-info/
│   │   │   │   └── route.ts
│   │   │   ├── authorize/
│   │   │   │   └── route.ts
│   │   │   ├── revoke/
│   │   │   │   └── route.ts
│   │   │   ├── token/
│   │   │   │   └── route.ts
│   │   │   └── userinfo/
│   │   │       └── route.ts
│   │   ├── subscription/
│   │   │   └── route.ts
│   │   ├── v1/
│   │   │   ├── me/
│   │   │   │   └── route.ts
│   │   │   └── themes/
│   │   │       ├── [themeId]/
│   │   │       │   └── route.ts
│   │   │       └── route.ts
│   │   └── webhook/
│   │       └── polar/
│   │           └── route.ts
│   ├── community/
│   │   ├── components/
│   │   │   ├── community-sidebar.tsx
│   │   │   ├── community-theme-card.tsx
│   │   │   ├── community-theme-preview-dialog.tsx
│   │   │   └── community-themes-content.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── dashboard/
│   │   ├── layout.tsx
│   │   ├── loading.tsx
│   │   └── page.tsx
│   ├── editor/
│   │   └── theme/
│   │       └── [[...themeId]]/
│   │           ├── layout.tsx
│   │           ├── loading.tsx
│   │           └── page.tsx
│   ├── figma/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── globals.css
│   ├── layout.tsx
│   ├── loaders.css
│   ├── not-found.tsx
│   ├── oauth/
│   │   └── authorize/
│   │       └── page.tsx
│   ├── page.tsx
│   ├── pricing/
│   │   ├── components/
│   │   │   └── checkout-button.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── r/
│   │   ├── themes/
│   │   │   └── [id]/
│   │   │       └── route.ts
│   │   └── v0/
│   │       └── [id]/
│   │           └── route.ts
│   ├── settings/
│   │   ├── account/
│   │   │   ├── components/
│   │   │   │   └── delete-account-section.tsx
│   │   │   └── page.tsx
│   │   ├── components/
│   │   │   ├── customer-portal-link.tsx
│   │   │   ├── settings-header.tsx
│   │   │   ├── settings-sidebar.tsx
│   │   │   ├── theme-card.tsx
│   │   │   ├── themes-list.tsx
│   │   │   ├── usage-stats.tsx
│   │   │   └── user-info.tsx
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   ├── portal/
│   │   │   └── route.ts
│   │   ├── themes/
│   │   │   └── page.tsx
│   │   └── usage/
│   │       └── page.tsx
│   ├── sitemap.ts
│   ├── success/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   └── themes/
│       └── [themeId]/
│           ├── error.tsx
│           ├── layout.tsx
│           ├── loading.tsx
│           ├── not-found.tsx
│           ├── opengraph-image.alt.txt
│           ├── opengraph-image.tsx
│           └── page.tsx
├── components/
│   ├── ai-elements/
│   │   ├── code-block.tsx
│   │   ├── conversation.tsx
│   │   └── response.tsx
│   ├── auth-dialog-wrapper.tsx
│   ├── block-viewer.tsx
│   ├── copy-button.tsx
│   ├── debug-button.tsx
│   ├── dynamic-font-loader.tsx
│   ├── dynamic-website-preview.tsx
│   ├── editor/
│   │   ├── action-bar/
│   │   │   ├── action-bar.tsx
│   │   │   └── components/
│   │   │       ├── action-bar-buttons.tsx
│   │   │       ├── ai-generate-button.tsx
│   │   │       ├── code-button.tsx
│   │   │       ├── import-button.tsx
│   │   │       ├── mcp-dialog.tsx
│   │   │       ├── more-options.tsx
│   │   │       ├── publish-button.tsx
│   │   │       ├── reset-button.tsx
│   │   │       ├── save-button.tsx
│   │   │       ├── share-button.tsx
│   │   │       ├── theme-toggle.tsx
│   │   │       └── undo-redo-buttons.tsx
│   │   ├── ai/
│   │   │   ├── ai-chat-form-body.tsx
│   │   │   ├── alert-banner.tsx
│   │   │   ├── chat-image-preview.tsx
│   │   │   ├── chat-input.tsx
│   │   │   ├── chat-interface.tsx
│   │   │   ├── chat-theme-preview.tsx
│   │   │   ├── closeable-suggested-pill-actions.tsx
│   │   │   ├── drag-and-drop-image-uploader.tsx
│   │   │   ├── enhance-prompt-button.tsx
│   │   │   ├── image-uploader.tsx
│   │   │   ├── loading-logo.tsx
│   │   │   ├── message-actions.tsx
│   │   │   ├── message-edit-form.tsx
│   │   │   ├── message.tsx
│   │   │   ├── messages.tsx
│   │   │   ├── no-messages-placeholder.tsx
│   │   │   ├── pill-action-button.tsx
│   │   │   ├── stream-text.tsx
│   │   │   └── uploaded-image-preview.tsx
│   │   ├── code-panel-dialog.tsx
│   │   ├── code-panel.tsx
│   │   ├── color-picker.tsx
│   │   ├── color-selector-popover.tsx
│   │   ├── colors-tab-content.tsx
│   │   ├── contrast-checker.tsx
│   │   ├── control-section.tsx
│   │   ├── css-import-dialog.tsx
│   │   ├── custom-textarea.tsx
│   │   ├── editor.tsx
│   │   ├── font-picker.tsx
│   │   ├── hsl-adjustment-controls.tsx
│   │   ├── hsl-preset-button.tsx
│   │   ├── inspector-class-item.tsx
│   │   ├── inspector-overlay.tsx
│   │   ├── mention-list.tsx
│   │   ├── mention-suggestion.ts
│   │   ├── section-context.tsx
│   │   ├── shadow-control.tsx
│   │   ├── share-dialog.tsx
│   │   ├── slider-with-input.tsx
│   │   ├── theme-control-actions.tsx
│   │   ├── theme-control-panel.tsx
│   │   ├── theme-font-select.tsx
│   │   ├── theme-preset-select.tsx
│   │   ├── theme-preview/
│   │   │   ├── color-preview.tsx
│   │   │   ├── components-showcase.tsx
│   │   │   ├── examples-preview-container.tsx
│   │   │   └── tabs-trigger-pill.tsx
│   │   ├── theme-preview-panel.tsx
│   │   └── theme-save-dialog.tsx
│   ├── effects/
│   │   ├── frame-highlight.tsx
│   │   ├── noise-effect.tsx
│   │   └── spotlight.tsx
│   ├── error-boundary.tsx
│   ├── examples/
│   │   ├── ai-chat-demo.tsx
│   │   ├── cards/
│   │   │   ├── activity-goal.tsx
│   │   │   ├── calendar.tsx
│   │   │   ├── chat.tsx
│   │   │   ├── cookie-settings.tsx
│   │   │   ├── create-account.tsx
│   │   │   ├── date-picker-with-range.tsx
│   │   │   ├── exercise-minutes.tsx
│   │   │   ├── forms.tsx
│   │   │   ├── github-card.tsx
│   │   │   ├── index.tsx
│   │   │   ├── payment-method.tsx
│   │   │   ├── payments.tsx
│   │   │   ├── report-issue.tsx
│   │   │   ├── share.tsx
│   │   │   ├── stats.tsx
│   │   │   └── team-members.tsx
│   │   ├── custom/
│   │   │   └── index.tsx
│   │   ├── dashboard/
│   │   │   ├── components/
│   │   │   │   ├── app-sidebar.tsx
│   │   │   │   ├── chart-area-interactive.tsx
│   │   │   │   ├── chart-bar-mixed.tsx
│   │   │   │   ├── chart-pie-donut.tsx
│   │   │   │   ├── data-table.tsx
│   │   │   │   ├── nav-documents.tsx
│   │   │   │   ├── nav-main.tsx
│   │   │   │   ├── nav-secondary.tsx
│   │   │   │   ├── nav-user.tsx
│   │   │   │   ├── section-cards.tsx
│   │   │   │   └── site-header.tsx
│   │   │   ├── data.json
│   │   │   └── index.tsx
│   │   ├── mail/
│   │   │   ├── components/
│   │   │   │   ├── account-switcher.tsx
│   │   │   │   ├── mail-display.tsx
│   │   │   │   ├── mail-list.tsx
│   │   │   │   ├── mail.tsx
│   │   │   │   └── nav.tsx
│   │   │   ├── data.tsx
│   │   │   ├── index.tsx
│   │   │   └── use-mail.ts
│   │   ├── music/
│   │   │   ├── components/
│   │   │   │   ├── album-artwork.tsx
│   │   │   │   ├── menu.tsx
│   │   │   │   ├── podcast-empty-placeholder.tsx
│   │   │   │   └── sidebar.tsx
│   │   │   ├── data/
│   │   │   │   ├── albums.ts
│   │   │   │   └── playlists.ts
│   │   │   └── index.tsx
│   │   ├── pricing/
│   │   │   └── pricing.tsx
│   │   ├── tasks/
│   │   │   ├── components/
│   │   │   │   ├── columns.tsx
│   │   │   │   ├── data-table-column-header.tsx
│   │   │   │   ├── data-table-faceted-filter.tsx
│   │   │   │   ├── data-table-pagination.tsx
│   │   │   │   ├── data-table-row-actions.tsx
│   │   │   │   ├── data-table-toolbar.tsx
│   │   │   │   ├── data-table-view-options.tsx
│   │   │   │   ├── data-table.tsx
│   │   │   │   └── user-nav.tsx
│   │   │   ├── data/
│   │   │   │   ├── data.tsx
│   │   │   │   ├── schema.ts
│   │   │   │   └── tasks.json
│   │   │   └── index.tsx
│   │   └── typography/
│   │       ├── blog-post.tsx
│   │       ├── font-showcase.tsx
│   │       └── typography-demo.tsx
│   ├── figma-export-dialog.tsx
│   ├── figma-header.tsx
│   ├── footer.tsx
│   ├── get-pro-cta.tsx
│   ├── get-pro-dialog-wrapper.tsx
│   ├── header.tsx
│   ├── home/
│   │   ├── ai-generation-cta.tsx
│   │   ├── cta.tsx
│   │   ├── faq.tsx
│   │   ├── features.tsx
│   │   ├── header.tsx
│   │   ├── hero.tsx
│   │   ├── how-it-works.tsx
│   │   ├── testimonials.tsx
│   │   ├── theme-preset-buttons.tsx
│   │   └── theme-preset-selector.tsx
│   ├── horizontal-scroll-area.tsx
│   ├── icons/
│   │   └── tailwind-css.tsx
│   ├── icons.tsx
│   ├── loader.tsx
│   ├── loading.tsx
│   ├── posthog-init.tsx
│   ├── social-link.tsx
│   ├── tag-selector.tsx
│   ├── theme-preview.tsx
│   ├── theme-provider.tsx
│   ├── theme-script.tsx
│   ├── theme-toggle.tsx
│   ├── theme-view.tsx
│   ├── tooltip-wrapper.tsx
│   ├── ui/
│   │   ├── accordion.tsx
│   │   ├── alert-dialog.tsx
│   │   ├── alert.tsx
│   │   ├── aspect-ratio.tsx
│   │   ├── avatar.tsx
│   │   ├── badge.tsx
│   │   ├── base-ui-tabs.tsx
│   │   ├── breadcrumb.tsx
│   │   ├── button.tsx
│   │   ├── calendar.tsx
│   │   ├── card.tsx
│   │   ├── carousel.tsx
│   │   ├── chart.tsx
│   │   ├── checkbox.tsx
│   │   ├── collapsible.tsx
│   │   ├── command.tsx
│   │   ├── context-menu.tsx
│   │   ├── dialog.tsx
│   │   ├── drawer.tsx
│   │   ├── dropdown-menu.tsx
│   │   ├── form.tsx
│   │   ├── hover-card.tsx
│   │   ├── input-otp.tsx
│   │   ├── input.tsx
│   │   ├── label.tsx
│   │   ├── menubar.tsx
│   │   ├── navigation-menu.tsx
│   │   ├── pagination.tsx
│   │   ├── popover.tsx
│   │   ├── progress.tsx
│   │   ├── radio-group.tsx
│   │   ├── resizable.tsx
│   │   ├── revola.tsx
│   │   ├── scroll-area.tsx
│   │   ├── select.tsx
│   │   ├── separator.tsx
│   │   ├── sheet.tsx
│   │   ├── sidebar.tsx
│   │   ├── skeleton.tsx
│   │   ├── slider.tsx
│   │   ├── sonner.tsx
│   │   ├── switch.tsx
│   │   ├── table.tsx
│   │   ├── tabs.tsx
│   │   ├── textarea.tsx
│   │   ├── toast.tsx
│   │   ├── toaster.tsx
│   │   ├── toggle-group.tsx
│   │   ├── toggle.tsx
│   │   ├── tooltip.tsx
│   │   └── use-toast.ts
│   └── user-profile-dropdown.tsx
├── components.json
├── config/
│   └── theme.ts
├── db/
│   ├── index.ts
│   └── schema.ts
├── docker-compose.yml
├── docs/
│   └── oauth-api.md
├── drizzle/
│   ├── 0000_rare_moira_mactaggert.sql
│   ├── 0001_late_mikhail_rasputin.sql
│   ├── 0002_nebulous_randall.sql
│   ├── 0003_bumpy_quasimodo.sql
│   ├── 0004_red_monster_badoon.sql
│   └── meta/
│       ├── 0000_snapshot.json
│       ├── 0001_snapshot.json
│       ├── 0002_snapshot.json
│       ├── 0003_snapshot.json
│       ├── 0004_snapshot.json
│       └── _journal.json
├── drizzle.config.ts
├── eslint.config.mjs
├── hooks/
│   ├── inspector/
│   │   ├── use-inspector-mouse-events.ts
│   │   ├── use-inspector-scroll.ts
│   │   ├── use-inspector-state.ts
│   │   └── use-theme-inspector.ts
│   ├── themes/
│   │   ├── index.ts
│   │   ├── use-community-themes.ts
│   │   ├── use-theme-mutations.ts
│   │   └── use-themes-data.ts
│   ├── use-ai-chat-form.ts
│   ├── use-ai-enhance-prompt.ts
│   ├── use-ai-theme-generation-core.ts
│   ├── use-chat-context.tsx
│   ├── use-contrast-checker.ts
│   ├── use-controls-tab-from-url.ts
│   ├── use-copy-to-clipboard.ts
│   ├── use-debounced-callback.ts
│   ├── use-dialog-actions.tsx
│   ├── use-document-drag-and-drop-intent.ts
│   ├── use-feedback-text.ts
│   ├── use-font-search.ts
│   ├── use-fullscreen.ts
│   ├── use-github-stars.ts
│   ├── use-guards.ts
│   ├── use-iframe-theme-injector.ts
│   ├── use-image-upload-reducer.ts
│   ├── use-image-upload.ts
│   ├── use-media-query.tsx
│   ├── use-mobile.tsx
│   ├── use-mounted.tsx
│   ├── use-post-login-action.ts
│   ├── use-scroll-start-end.ts
│   ├── use-subscription.ts
│   ├── use-theme-inspector-classnames.ts
│   ├── use-theme-inspector-regex.ts
│   ├── use-theme-inspector.ts
│   ├── use-theme-preset-from-url.ts
│   ├── use-toast.ts
│   └── use-website-preview.ts
├── lib/
│   ├── ai/
│   │   ├── generate-theme/
│   │   │   ├── index.ts
│   │   │   └── tools.ts
│   │   ├── parse-ai-sdk-transport-error.ts
│   │   ├── prompts.ts
│   │   └── providers.ts
│   ├── auth-client.ts
│   ├── auth.ts
│   ├── checkout.ts
│   ├── constants.ts
│   ├── error-response.ts
│   ├── figma-constants.ts
│   ├── inspector/
│   │   ├── class-utils.ts
│   │   ├── inspector-state-utils.ts
│   │   ├── segment-classname.ts
│   │   └── theme-class-finder.ts
│   ├── oauth.ts
│   ├── polar.ts
│   ├── posthog.ts
│   ├── query-client.tsx
│   ├── shared.ts
│   ├── subscription.ts
│   └── utils.ts
├── middleware.ts
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── public/
│   ├── live-preview.js
│   └── r/
│       └── registry.json
├── routes.ts
├── scripts/
│   ├── create-oauth-app.ts
│   ├── generate-registry.ts
│   └── generate-theme-registry.ts
├── store/
│   ├── ai-chat-store.ts
│   ├── ai-local-draft-store.ts
│   ├── auth-store.ts
│   ├── color-control-focus-store.ts
│   ├── editor-store.ts
│   ├── get-pro-dialog-store.ts
│   ├── idb-storage.ts
│   ├── preferences-store.ts
│   ├── theme-preset-store.ts
│   └── website-preview-store.ts
├── tsconfig.json
├── types/
│   ├── ai.ts
│   ├── community.ts
│   ├── editor.ts
│   ├── errors.ts
│   ├── fonts.ts
│   ├── index.ts
│   ├── live-preview-embed.ts
│   ├── subscription.ts
│   └── theme.ts
└── utils/
    ├── ai/
    │   ├── ai-prompt.tsx
    │   ├── apply-theme.ts
    │   ├── image-upload.ts
    │   ├── message-converter.ts
    │   ├── messages.ts
    │   └── prompts.ts
    ├── apply-style-to-element.ts
    ├── apply-theme.ts
    ├── color-converter.ts
    ├── contrast-checker.ts
    ├── debounce.ts
    ├── fonts/
    │   ├── google-fonts.ts
    │   └── index.ts
    ├── format.ts
    ├── parse-css-input.ts
    ├── registry/
    │   ├── tailwind-colors.ts
    │   ├── themes.ts
    │   └── v0.ts
    ├── shadows.ts
    ├── subscription.ts
    ├── theme-fonts.ts
    ├── theme-preset-helper.ts
    ├── theme-presets.ts
    ├── theme-style-generator.ts
    ├── theme-styles.ts
    └── try-catch.ts
Download .txt
SYMBOL INDEX (1043 symbols across 379 files)

FILE: actions/account.ts
  function deleteAccount (line 11) | async function deleteAccount(): Promise<ActionResult<boolean>> {

FILE: actions/ai-usage.ts
  type Timeframe (line 25) | type Timeframe = z.infer<typeof timeframeSchema>;
  type UsageStats (line 28) | interface UsageStats {
  type ChartDataPoint (line 33) | interface ChartDataPoint {
  function recordAIUsage (line 40) | async function recordAIUsage(input: {
  function getMyUsageStats (line 76) | async function getMyUsageStats(timeframe: Timeframe): Promise<UsageStats> {
  function getMyAllTimeRequestCount (line 104) | async function getMyAllTimeRequestCount(userId: string): Promise<number> {
  function getMyUsageChartData (line 118) | async function getMyUsageChartData(timeframe: Timeframe): Promise<ChartD...
  function getDetailedUsageStats (line 192) | async function getDetailedUsageStats(

FILE: actions/community-themes.ts
  function getOptionalUserId (line 46) | async function getOptionalUserId(): Promise<string | null> {
  function getCurrentUserId (line 57) | async function getCurrentUserId(): Promise<string> {
  function logError (line 69) | function logError(error: Error, context: Record<string, unknown>) {
  function fetchCommunityThemesCore (line 91) | async function fetchCommunityThemesCore(
  function getCommunityThemes (line 248) | async function getCommunityThemes(
  function publishTheme (line 285) | async function publishTheme(
  function unpublishTheme (line 375) | async function unpublishTheme(
  function toggleLikeTheme (line 411) | async function toggleLikeTheme(
  function fetchCommunityDataForThemeCore (line 486) | async function fetchCommunityDataForThemeCore(
  function getCommunityDataForTheme (line 548) | async function getCommunityDataForTheme(
  function getMyPublishedThemeIds (line 570) | async function getMyPublishedThemeIds(): Promise<string[]> {
  function updateCommunityThemeTags (line 586) | async function updateCommunityThemeTags(
  function getCommunityTagCounts (line 669) | async function getCommunityTagCounts(): Promise<

FILE: actions/themes.ts
  function getCurrentUserId (line 25) | async function getCurrentUserId(): Promise<string> {
  function logError (line 38) | function logError(error: Error, context: Record<string, any>) {
  function getThemes (line 64) | async function getThemes() {
  function createTheme (line 108) | async function createTheme(formData: { name: string; styles: ThemeStyles...
  function updateTheme (line 157) | async function updateTheme(formData: { id: string; name?: string; styles...
  function deleteTheme (line 195) | async function deleteTheme(themeId: string) {

FILE: app/(auth)/components/auth-dialog.tsx
  type AuthDialogProps (line 19) | interface AuthDialogProps {
  function getContextualCopy (line 28) | function getContextualCopy(actionType?: PostLoginActionType | null) {
  function AuthDialog (line 64) | function AuthDialog({

FILE: app/(legal)/layout.tsx
  type LegalLayoutProps (line 5) | interface LegalLayoutProps {
  function LegalLayout (line 9) | function LegalLayout({ children }: LegalLayoutProps) {

FILE: app/(legal)/privacy-policy/page.tsx
  function PrivacyPolicyPage (line 8) | function PrivacyPolicyPage() {

FILE: app/ai/components/ai-announcement.tsx
  function AIAnnouncement (line 7) | function AIAnnouncement() {

FILE: app/ai/components/ai-chat-form.tsx
  function AIChatForm (line 18) | function AIChatForm({

FILE: app/ai/components/ai-chat-hero.tsx
  function AIChatHero (line 15) | function AIChatHero() {

FILE: app/ai/components/chat-heading.tsx
  function ChatHeading (line 1) | function ChatHeading({ isGeneratingTheme }: { isGeneratingTheme: boolean...

FILE: app/ai/components/community-theme-card.tsx
  type SwatchDefinition (line 12) | type SwatchDefinition = {
  function CommunityThemeCard (line 28) | function CommunityThemeCard({ themePreset }: { themePreset: ThemePreset ...
  function CommunityThemeCardSkeleton (line 108) | function CommunityThemeCardSkeleton() {

FILE: app/ai/components/community-themes.tsx
  function CommunityThemes (line 14) | async function CommunityThemes() {
  type CommunityThemeCardsProps (line 48) | interface CommunityThemeCardsProps {
  function CommunityThemeCards (line 52) | async function CommunityThemeCards({ themePresetsPromise }: CommunityThe...

FILE: app/ai/components/suggested-pill-actions.tsx
  function SuggestedPillActions (line 13) | function SuggestedPillActions({

FILE: app/ai/layout.tsx
  function AiLayout (line 3) | function AiLayout({ children }: { children: React.ReactNode }) {

FILE: app/ai/loading.tsx
  function AiLoading (line 3) | function AiLoading() {

FILE: app/ai/page.tsx
  function AiPage (line 14) | function AiPage() {

FILE: app/api/enhance-prompt/route.ts
  function POST (line 10) | async function POST(req: NextRequest) {

FILE: app/api/generate-theme/route.ts
  function POST (line 22) | async function POST(req: NextRequest) {

FILE: app/api/google-fonts/route.ts
  function GET (line 11) | async function GET(request: NextRequest) {

FILE: app/api/oauth/app-info/route.ts
  function GET (line 7) | async function GET(req: NextRequest) {

FILE: app/api/oauth/authorize/route.ts
  function GET (line 17) | async function GET(req: NextRequest) {

FILE: app/api/oauth/revoke/route.ts
  function POST (line 7) | async function POST(req: NextRequest) {

FILE: app/api/oauth/token/route.ts
  function POST (line 13) | async function POST(req: NextRequest) {
  function handleAuthorizationCode (line 32) | async function handleAuthorizationCode(body: FormData) {
  function handleRefreshToken (line 119) | async function handleRefreshToken(body: FormData) {

FILE: app/api/oauth/userinfo/route.ts
  function GET (line 12) | async function GET(req: NextRequest) {

FILE: app/api/subscription/route.ts
  function GET (line 6) | async function GET(request: NextRequest) {

FILE: app/api/v1/me/route.ts
  function GET (line 7) | async function GET(req: NextRequest) {

FILE: app/api/v1/themes/[themeId]/route.ts
  function GET (line 7) | async function GET(

FILE: app/api/v1/themes/route.ts
  function GET (line 7) | async function GET(req: NextRequest) {

FILE: app/api/webhook/polar/route.ts
  function safeParseDate (line 5) | function safeParseDate(value: string | Date | null | undefined): Date | ...
  constant POST (line 15) | const POST = Webhooks({

FILE: app/community/components/community-sidebar.tsx
  type CommunitySidebarProps (line 10) | interface CommunitySidebarProps {
  function CommunitySidebarContent (line 23) | function CommunitySidebarContent({

FILE: app/community/components/community-theme-card.tsx
  type CommunityThemeCardProps (line 15) | interface CommunityThemeCardProps {
  function CommunityThemeCard (line 20) | function CommunityThemeCard({ theme, onPreview }: CommunityThemeCardProp...

FILE: app/community/components/community-theme-preview-dialog.tsx
  type CommunityThemePreviewDialogProps (line 26) | interface CommunityThemePreviewDialogProps {
  function CommunityThemePreviewDialog (line 34) | function CommunityThemePreviewDialog({

FILE: app/community/components/community-themes-content.tsx
  function CommunityThemesContent (line 64) | function CommunityThemesContent() {

FILE: app/community/layout.tsx
  function CommunityLayout (line 4) | function CommunityLayout({

FILE: app/community/page.tsx
  function CommunityPage (line 24) | function CommunityPage() {

FILE: app/dashboard/layout.tsx
  function DashboardLayout (line 4) | function DashboardLayout({ children }: { children: React.ReactNode }) {

FILE: app/dashboard/loading.tsx
  function DashboardLoading (line 3) | function DashboardLoading() {

FILE: app/dashboard/page.tsx
  function DashboardRedirect (line 4) | function DashboardRedirect() {

FILE: app/editor/theme/[[...themeId]]/layout.tsx
  function EditorLayout (line 3) | function EditorLayout({ children }: { children: React.ReactNode }) {

FILE: app/editor/theme/[[...themeId]]/loading.tsx
  function EditorLoading (line 3) | function EditorLoading() {

FILE: app/editor/theme/[[...themeId]]/page.tsx
  function EditorPage (line 11) | async function EditorPage({ params }: { params: Promise<{ themeId: strin...

FILE: app/figma/layout.tsx
  function FigmaLayout (line 39) | function FigmaLayout({ children }: { children: React.ReactNode }) {

FILE: app/figma/page.tsx
  function FigmaPage (line 15) | function FigmaPage() {

FILE: app/layout.tsx
  function RootLayout (line 54) | function RootLayout({ children }: { children: React.ReactNode }) {

FILE: app/not-found.tsx
  function NotFound (line 12) | function NotFound() {

FILE: app/oauth/authorize/page.tsx
  constant SCOPE_LABELS (line 11) | const SCOPE_LABELS: Record<string, string> = {
  function OAuthAuthorizePage (line 16) | function OAuthAuthorizePage() {

FILE: app/page.tsx
  function Home (line 15) | function Home() {

FILE: app/pricing/components/checkout-button.tsx
  type CheckoutButtonProps (line 16) | interface CheckoutButtonProps extends ComponentProps<typeof Button> {}
  function CheckoutButton (line 18) | function CheckoutButton({ disabled, className, ...props }: CheckoutButto...

FILE: app/pricing/layout.tsx
  function PricingLayout (line 4) | function PricingLayout({ children }: { children: React.ReactNode }) {

FILE: app/pricing/page.tsx
  function PricingPage (line 24) | function PricingPage() {
  constant PRICING_FAQS (line 192) | const PRICING_FAQS = [

FILE: app/r/themes/[id]/route.ts
  function GET (line 11) | async function GET(_req: Request, { params }: { params: Promise<{ id: st...

FILE: app/r/v0/[id]/route.ts
  function GET (line 8) | async function GET(_req: Request, { params }: { params: Promise<{ id: st...

FILE: app/settings/account/components/delete-account-section.tsx
  constant CONFIRMATION_TEXT (line 22) | const CONFIRMATION_TEXT = "DELETE";
  function DeleteAccountSection (line 24) | function DeleteAccountSection() {

FILE: app/settings/account/page.tsx
  function AccountPage (line 7) | async function AccountPage() {

FILE: app/settings/components/customer-portal-link.tsx
  function CustomerPortalLink (line 7) | async function CustomerPortalLink() {

FILE: app/settings/components/settings-header.tsx
  function SettingsHeader (line 1) | function SettingsHeader({ title, description }: { title: string; descrip...

FILE: app/settings/components/settings-sidebar.tsx
  type NavItem (line 11) | type NavItem =
  constant BASE_NAV_ITEMS (line 24) | const BASE_NAV_ITEMS: NavItem[] = [
  function SettingsSidebar (line 42) | function SettingsSidebar() {

FILE: app/settings/components/theme-card.tsx
  type ThemeCardProps (line 58) | interface ThemeCardProps {
  function ThemeCard (line 64) | function ThemeCard({

FILE: app/settings/components/themes-list.tsx
  type ThemeWithPublished (line 18) | interface ThemeWithPublished extends Theme {
  type ThemesListProps (line 22) | interface ThemesListProps {
  function ThemesList (line 26) | function ThemesList({ themes }: ThemesListProps) {

FILE: app/settings/components/usage-stats.tsx
  type Timeframe (line 17) | type Timeframe = "1d" | "7d" | "30d";
  type UsageStats (line 19) | interface UsageStats {
  type ChartDataPoint (line 24) | interface ChartDataPoint {
  function UsageStats (line 44) | function UsageStats() {

FILE: app/settings/components/user-info.tsx
  function UserInfo (line 7) | function UserInfo() {

FILE: app/settings/layout.tsx
  function SettingsLayout (line 8) | async function SettingsLayout({ children }: { children: React.ReactNode ...

FILE: app/settings/page.tsx
  function SettingsIndex (line 5) | async function SettingsIndex() {

FILE: app/settings/portal/route.ts
  constant GET (line 5) | const GET = CustomerPortal({

FILE: app/settings/themes/page.tsx
  function ThemesPage (line 12) | async function ThemesPage() {

FILE: app/settings/usage/page.tsx
  function UsagePage (line 7) | async function UsagePage() {

FILE: app/sitemap.ts
  function sitemap (line 3) | function sitemap(): MetadataRoute.Sitemap {

FILE: app/success/layout.tsx
  function SuccessLayout (line 1) | function SuccessLayout({ children }: { children: React.ReactNode }) {

FILE: app/success/page.tsx
  function SuccessPage (line 14) | function SuccessPage() {

FILE: app/themes/[themeId]/error.tsx
  function ThemeError (line 7) | function ThemeError({

FILE: app/themes/[themeId]/layout.tsx
  function ThemeLayout (line 4) | function ThemeLayout({ children }: { children: React.ReactNode }) {

FILE: app/themes/[themeId]/loading.tsx
  function ThemeLoading (line 3) | function ThemeLoading() {

FILE: app/themes/[themeId]/not-found.tsx
  function ThemeNotFound (line 4) | function ThemeNotFound() {

FILE: app/themes/[themeId]/opengraph-image.tsx
  function Image (line 13) | async function Image({

FILE: app/themes/[themeId]/page.tsx
  type ThemePageProps (line 6) | interface ThemePageProps {
  function generateMetadata (line 12) | async function generateMetadata({ params }: ThemePageProps): Promise<Met...
  function ThemePage (line 47) | async function ThemePage({ params }: ThemePageProps) {

FILE: components/ai-elements/code-block.tsx
  type CodeBlockContextType (line 12) | type CodeBlockContextType = {
  type CodeBlockProps (line 20) | type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
  type CodeBlockCopyButtonProps (line 78) | type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {

FILE: components/ai-elements/conversation.tsx
  type ConversationProps (line 10) | type ConversationProps = ComponentProps<typeof StickToBottom>;
  type ConversationContentProps (line 22) | type ConversationContentProps = ComponentProps<typeof StickToBottom.Cont...
  type ConversationScrollButtonProps (line 28) | type ConversationScrollButtonProps = ComponentProps<typeof Button>;

FILE: components/ai-elements/response.tsx
  type ResponseProps (line 7) | type ResponseProps = ComponentProps<typeof Streamdown>;

FILE: components/auth-dialog-wrapper.tsx
  function AuthDialogWrapper (line 10) | function AuthDialogWrapper() {

FILE: components/block-viewer.tsx
  type BlockViewerContext (line 11) | type BlockViewerContext = {
  function useBlockViewer (line 19) | function useBlockViewer() {
  function BlockViewerProvider (line 27) | function BlockViewerProvider({ children }: { children: React.ReactNode }) {
  function BlockViewer (line 44) | function BlockViewer({
  function BlockViewerToolbar (line 68) | function BlockViewerToolbar({
  function BlockViewerDisplay (line 120) | function BlockViewerDisplay({

FILE: components/copy-button.tsx
  type CopyButtonProps (line 10) | interface CopyButtonProps extends ComponentProps<typeof Button> {
  function CopyButton (line 18) | function CopyButton({ textToCopy, successMessage, className, ...props }:...

FILE: components/debug-button.tsx
  type DebugButtonProps (line 6) | interface DebugButtonProps extends React.ComponentProps<typeof Button> {
  function DebugButton (line 12) | function DebugButton({ className, debug = isDevMode, ...props }: DebugBu...

FILE: components/dynamic-font-loader.tsx
  function DynamicFontLoader (line 9) | function DynamicFontLoader() {

FILE: components/dynamic-website-preview.tsx
  constant SCRIPT_URL (line 47) | const SCRIPT_URL = "https://tweakcn.com/live-preview.min.js";
  constant HTML_SNIPPET (line 50) | const HTML_SNIPPET = `<!-- Add inside <head> -->\n<script src="${SCRIPT_...
  constant NEXT_APP_SNIPPET (line 52) | const NEXT_APP_SNIPPET = `// app/layout.tsx\nexport default function Roo...
  constant NEXT_PAGES_SNIPPET (line 71) | const NEXT_PAGES_SNIPPET = `// pages/_document.tsx\n
  constant VITE_SNIPPET (line 92) | const VITE_SNIPPET = `<!-- index.html -->\n<!doctype html>
  constant REMIX_SNIPPET (line 105) | const REMIX_SNIPPET = `// app/root.tsx\nimport { Links, Meta, Outlet, Sc...
  type DynamicWebsitePreviewContextType (line 130) | type DynamicWebsitePreviewContextType = ReturnType<typeof useWebsitePrev...
  function useDynamicWebsitePreview (line 137) | function useDynamicWebsitePreview() {
  function DynamicWebsitePreviewProvider (line 147) | function DynamicWebsitePreviewProvider({
  function DynamicWebsitePreview (line 192) | function DynamicWebsitePreview({
  function DynamicWebsitePreviewContent (line 218) | function DynamicWebsitePreviewContent({ name }: { name: string }) {
  function Controls (line 232) | function Controls() {
  function NoWebsitePreviewLoaded (line 329) | function NoWebsitePreviewLoaded() {
  function WebsitePreviewLoading (line 445) | function WebsitePreviewLoading() {
  function WebsitePreviewError (line 459) | function WebsitePreviewError({ error }: { error: string }) {
  function WebsitePreview (line 482) | function WebsitePreview({ name }: { name: string }) {
  constant ICONS (line 650) | const ICONS: Record<IframeStatus, React.ReactNode> = {
  constant TEXTS (line 660) | const TEXTS: Record<IframeStatus, string> = {

FILE: components/editor/action-bar/action-bar.tsx
  function ActionBar (line 7) | function ActionBar() {

FILE: components/editor/action-bar/components/action-bar-buttons.tsx
  type ActionBarButtonsProps (line 17) | interface ActionBarButtonsProps {
  function ActionBarButtons (line 25) | function ActionBarButtons({

FILE: components/editor/action-bar/components/ai-generate-button.tsx
  type AIGenerateButtonProps (line 9) | interface AIGenerateButtonProps {
  function AIGenerateButton (line 13) | function AIGenerateButton({ onClick }: AIGenerateButtonProps) {

FILE: components/editor/action-bar/components/code-button.tsx
  type CodeButtonProps (line 6) | interface CodeButtonProps extends React.ComponentProps<typeof Button> {}
  function CodeButton (line 8) | function CodeButton({ className, ...props }: CodeButtonProps) {

FILE: components/editor/action-bar/components/import-button.tsx
  type ImportButtonProps (line 6) | interface ImportButtonProps extends React.ComponentProps<typeof Button> {}
  function ImportButton (line 8) | function ImportButton({ className, ...props }: ImportButtonProps) {

FILE: components/editor/action-bar/components/mcp-dialog.tsx
  type MCPDialogProps (line 20) | interface MCPDialogProps {
  function MCPDialog (line 37) | function MCPDialog({ open, onOpenChange }: MCPDialogProps) {

FILE: components/editor/action-bar/components/more-options.tsx
  type MoreOptionsProps (line 15) | interface MoreOptionsProps extends React.ComponentProps<typeof DropdownM...
  function MoreOptions (line 17) | function MoreOptions({ ...props }: MoreOptionsProps) {

FILE: components/editor/action-bar/components/publish-button.tsx
  type PublishButtonProps (line 19) | interface PublishButtonProps {
  function PublishButton (line 26) | function PublishButton({

FILE: components/editor/action-bar/components/reset-button.tsx
  type ResetButtonProps (line 6) | interface ResetButtonProps extends React.ComponentProps<typeof Button> {}
  function ResetButton (line 8) | function ResetButton({ className, ...props }: ResetButtonProps) {

FILE: components/editor/action-bar/components/save-button.tsx
  type SaveButtonProps (line 6) | interface SaveButtonProps extends React.ComponentProps<typeof Button> {
  function SaveButton (line 10) | function SaveButton({ isSaving, disabled, className, ...props }: SaveBut...

FILE: components/editor/action-bar/components/share-button.tsx
  type ShareButtonProps (line 6) | interface ShareButtonProps extends React.ComponentProps<typeof Button> {
  function ShareButton (line 10) | function ShareButton({

FILE: components/editor/action-bar/components/theme-toggle.tsx
  function ThemeToggle (line 7) | function ThemeToggle() {

FILE: components/editor/action-bar/components/undo-redo-buttons.tsx
  type UndoRedoButtonsProps (line 6) | interface UndoRedoButtonsProps extends React.ComponentProps<typeof Butto...
  function UndoRedoButtons (line 8) | function UndoRedoButtons({ disabled, ...props }: UndoRedoButtonsProps) {

FILE: components/editor/ai/ai-chat-form-body.tsx
  type AIChatFormBodyProps (line 21) | interface AIChatFormBodyProps {
  function AIChatFormBody (line 36) | function AIChatFormBody({

FILE: components/editor/ai/alert-banner.tsx
  function AlertBanner (line 12) | function AlertBanner() {
  function BannerWrapper (line 90) | function BannerWrapper({ children, show }: { children: React.ReactNode; ...

FILE: components/editor/ai/chat-image-preview.tsx
  type ChatImagePreviewProps (line 13) | interface ChatImagePreviewProps extends ComponentProps<typeof Image> {
  function ChatImagePreview (line 17) | function ChatImagePreview({ name, src, className, alt, ...props }: ChatI...

FILE: components/editor/ai/chat-input.tsx
  type ThemeGenerationPayload (line 21) | type ThemeGenerationPayload = {
  type ChatInputProps (line 28) | interface ChatInputProps {
  function ChatInput (line 34) | function ChatInput({

FILE: components/editor/ai/chat-interface.tsx
  function ChatInterface (line 26) | function ChatInterface() {

FILE: components/editor/ai/chat-theme-preview.tsx
  type ChatThemePreviewProps (line 12) | type ChatThemePreviewProps = ComponentProps<"div"> & ChatThemePreviewPro...
  type ChatThemePreviewPropsBase (line 14) | type ChatThemePreviewPropsBase =
  function ChatThemePreview (line 32) | function ChatThemePreview({
  constant FEEDBACK_MESSAGES (line 161) | const FEEDBACK_MESSAGES = [

FILE: components/editor/ai/closeable-suggested-pill-actions.tsx
  function ClosableSuggestedPillActions (line 12) | function ClosableSuggestedPillActions({

FILE: components/editor/ai/drag-and-drop-image-uploader.tsx
  type DragAndDropImageUploaderProps (line 5) | interface DragAndDropImageUploaderProps {
  function DragAndDropImageUploader (line 11) | function DragAndDropImageUploader({

FILE: components/editor/ai/enhance-prompt-button.tsx
  type EnhancePromptButtonProps (line 6) | interface EnhancePromptButtonProps extends React.ComponentProps<typeof B...
  function EnhancePromptButton (line 12) | function EnhancePromptButton({

FILE: components/editor/ai/image-uploader.tsx
  type ImageUploaderProps (line 9) | interface ImageUploaderProps extends ComponentProps<typeof Button> {
  function ImageUploader (line 14) | function ImageUploader({

FILE: components/editor/ai/loading-logo.tsx
  function LoadingLogo (line 4) | function LoadingLogo() {

FILE: components/editor/ai/message-actions.tsx
  type MessageActionsProps (line 9) | type MessageActionsProps = {
  function MessageActions (line 17) | function MessageActions({

FILE: components/editor/ai/message-edit-form.tsx
  type MessageEditFormProps (line 23) | interface MessageEditFormProps {
  function MessageEditForm (line 30) | function MessageEditForm({

FILE: components/editor/ai/message.tsx
  type MessageProps (line 15) | type MessageProps = {
  function Message (line 26) | function Message({
  type AssistantMessageProps (line 80) | interface AssistantMessageProps {
  function AssistantMessage (line 85) | function AssistantMessage({ message, isLastMessageStreaming }: Assistant...
  type UserMessageProps (line 154) | interface UserMessageProps {
  function UserMessage (line 164) | function UserMessage({

FILE: components/editor/ai/messages.tsx
  type ChatMessagesProps (line 25) | type ChatMessagesProps = {
  function Messages (line 35) | function Messages({

FILE: components/editor/ai/no-messages-placeholder.tsx
  function NoMessagesPlaceholder (line 14) | function NoMessagesPlaceholder({
  type PromptButtonProps (line 102) | interface PromptButtonProps extends ComponentProps<typeof Button> {}
  function PromptButton (line 104) | function PromptButton({ className, children, ...props }: PromptButtonPro...

FILE: components/editor/ai/pill-action-button.tsx
  type AIPillActionButtonProps (line 5) | interface AIPillActionButtonProps extends ComponentProps<typeof Button> {}
  function PillActionButton (line 7) | function PillActionButton({

FILE: components/editor/ai/stream-text.tsx
  type StreamTextProps (line 3) | interface StreamTextProps {
  function StreamText (line 10) | function StreamText({

FILE: components/editor/ai/uploaded-image-preview.tsx
  type ImagePreviewProps (line 10) | interface ImagePreviewProps {
  function UploadedImagePreview (line 17) | function UploadedImagePreview({

FILE: components/editor/code-panel-dialog.tsx
  type CodePanelDialogProps (line 11) | interface CodePanelDialogProps {
  function CodePanelDialog (line 18) | function CodePanelDialog({ open, onOpenChange, themeEditorState, themeId...

FILE: components/editor/code-panel.tsx
  type CodePanelProps (line 33) | interface CodePanelProps {

FILE: components/editor/color-selector-popover.tsx
  type ColorSelectorPopoverProps (line 24) | type ColorSelectorPopoverProps = {
  function ColorSelectorPopover (line 29) | function ColorSelectorPopover({ currentColor, onChange }: ColorSelectorP...
  type ColorSwatchProps (line 167) | interface ColorSwatchProps extends React.HTMLAttributes<HTMLButtonElemen...
  function ColorSwatch (line 174) | function ColorSwatch({

FILE: components/editor/colors-tab-content.tsx
  type ColorEntry (line 14) | type ColorEntry = {
  type ColorGroup (line 20) | type ColorGroup = {
  constant COLOR_GROUPS (line 26) | const COLOR_GROUPS: ColorGroup[] = [
  constant SIDEBAR_SYNC_MAP (line 119) | const SIDEBAR_SYNC_MAP: Partial<Record<keyof ThemeStyleProps, keyof Them...
  constant BASE_TO_SIDEBAR_MAP (line 131) | const BASE_TO_SIDEBAR_MAP = Object.fromEntries(
  type ColorsTabContentProps (line 135) | interface ColorsTabContentProps {
  function ColorsTabContent (line 141) | function ColorsTabContent({ currentStyles, updateStyle, updateStyles }: ...

FILE: components/editor/contrast-checker.tsx
  type ContrastCheckerProps (line 22) | type ContrastCheckerProps = {
  constant MIN_CONTRAST_RATIO (line 26) | const MIN_CONTRAST_RATIO = 4.5;
  type ColorCategory (line 28) | type ColorCategory = "content" | "interactive" | "functional";
  type ColorPair (line 30) | type ColorPair = {

FILE: components/editor/css-import-dialog.tsx
  type CssImportDialogProps (line 16) | interface CssImportDialogProps {

FILE: components/editor/custom-textarea.tsx
  type CustomTextareaProps (line 13) | interface CustomTextareaProps {
  function CustomTextarea (line 26) | function CustomTextarea({

FILE: components/editor/editor.tsx
  type EditorProps (line 15) | interface EditorProps {

FILE: components/editor/font-picker.tsx
  constant POPULAR_FONTS (line 31) | const POPULAR_FONTS: Record<string, FontInfo[]> = {
  type FontPickerProps (line 96) | interface FontPickerProps {
  function FontItem (line 104) | function FontItem({
  function FontPicker (line 147) | function FontPicker({

FILE: components/editor/hsl-adjustment-controls.tsx
  function adjustColorByHsl (line 17) | function adjustColorByHsl(
  constant HSL_PRESETS (line 43) | const HSL_PRESETS = [

FILE: components/editor/hsl-preset-button.tsx
  type HslPresetButtonProps (line 8) | interface HslPresetButtonProps {

FILE: components/editor/inspector-class-item.tsx
  type InspectorClassItemProps (line 10) | interface InspectorClassItemProps {

FILE: components/editor/inspector-overlay.tsx
  type InspectorState (line 12) | interface InspectorState {
  type InspectorOverlayProps (line 17) | interface InspectorOverlayProps {

FILE: components/editor/mention-list.tsx
  type ThemeItem (line 7) | interface ThemeItem {
  type MentionListProps (line 12) | interface MentionListProps {
  type MentionListRef (line 19) | interface MentionListRef {

FILE: components/editor/mention-suggestion.ts
  method onUpdate (line 57) | onUpdate(props: any) {
  method onKeyDown (line 69) | onKeyDown(props: any) {
  method onExit (line 79) | onExit() {

FILE: components/editor/section-context.tsx
  type SectionContextType (line 3) | interface SectionContextType {

FILE: components/editor/shadow-control.tsx
  type ShadowControlProps (line 5) | interface ShadowControlProps {

FILE: components/editor/share-dialog.tsx
  type ShareDialogProps (line 13) | interface ShareDialogProps {
  function ShareDialog (line 19) | function ShareDialog({ open, onOpenChange, url }: ShareDialogProps) {

FILE: components/editor/theme-control-actions.tsx
  type MenuItemProps (line 11) | interface MenuItemProps {
  type ThemeControlActionsProps (line 44) | interface ThemeControlActionsProps {

FILE: components/editor/theme-control-panel.tsx
  type ThemeControlPanelProps (line 28) | interface ThemeControlPanelProps {

FILE: components/editor/theme-font-select.tsx
  type ThemeFontSelectProps (line 13) | interface ThemeFontSelectProps {

FILE: components/editor/theme-preset-select.tsx
  type ThemePresetSelectProps (line 29) | interface ThemePresetSelectProps extends React.ComponentProps<typeof But...
  type ColorBoxProps (line 33) | interface ColorBoxProps {
  type ThemeColorsProps (line 41) | interface ThemeColorsProps {
  type ThemeCycleButtonProps (line 90) | interface ThemeCycleButtonProps extends React.ComponentProps<typeof Butt...
  type ThemePresetCycleControlsProps (line 120) | interface ThemePresetCycleControlsProps extends React.ComponentProps<typ...

FILE: components/editor/theme-preview/color-preview.tsx
  type ColorPreviewProps (line 8) | interface ColorPreviewProps {
  function ColorPreviewItem (line 13) | function ColorPreviewItem({ label, color, name }: { label: string; color...

FILE: components/editor/theme-preview/components-showcase.tsx
  type ComponentsShowcaseProps (line 24) | interface ComponentsShowcaseProps {

FILE: components/editor/theme-save-dialog.tsx
  type ThemeSaveDialogProps (line 32) | interface ThemeSaveDialogProps {
  function ThemeSaveDialog (line 46) | function ThemeSaveDialog({

FILE: components/effects/frame-highlight.tsx
  function FrameHighlight (line 4) | function FrameHighlight({ children, className, ...props }: ComponentProp...
  function Corner (line 23) | function Corner({ className }: ComponentProps<"svg">) {

FILE: components/effects/noise-effect.tsx
  function NoiseEffect (line 1) | function NoiseEffect() {

FILE: components/effects/spotlight.tsx
  type SpotlightProps (line 3) | type SpotlightProps = {
  function Spotlight (line 8) | function Spotlight({ className, fill }: SpotlightProps) {

FILE: components/error-boundary.tsx
  class ComponentErrorBoundary (line 4) | class ComponentErrorBoundary extends React.Component<
    method constructor (line 8) | constructor(props: { children: React.ReactNode; name: string; fallback...
    method getDerivedStateFromError (line 13) | static getDerivedStateFromError() {
    method componentDidCatch (line 17) | componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    method render (line 21) | render() {

FILE: components/examples/ai-chat-demo.tsx
  function AIChatDemo (line 11) | function AIChatDemo({
  constant CHAT_PLACEHOLDER_MESSAGES (line 75) | const CHAT_PLACEHOLDER_MESSAGES: ChatMessage[] = [

FILE: components/examples/cards/activity-goal.tsx
  function CardsActivityGoal (line 67) | function CardsActivityGoal() {

FILE: components/examples/cards/calendar.tsx
  function CardsCalendar (line 10) | function CardsCalendar() {

FILE: components/examples/cards/chat.tsx
  type User (line 57) | type User = (typeof users)[number];
  function CardsChat (line 59) | function CardsChat() {

FILE: components/examples/cards/cookie-settings.tsx
  function CardsCookieSettings (line 15) | function CardsCookieSettings() {

FILE: components/examples/cards/create-account.tsx
  function CardsCreateAccount (line 15) | function CardsCreateAccount() {

FILE: components/examples/cards/date-picker-with-range.tsx
  function DatePickerWithRange (line 14) | function DatePickerWithRange({ className }: React.HTMLAttributes<HTMLDiv...

FILE: components/examples/cards/exercise-minutes.tsx
  function CardsExerciseMinutes (line 62) | function CardsExerciseMinutes() {

FILE: components/examples/cards/forms.tsx
  function CardsForms (line 33) | function CardsForms() {

FILE: components/examples/cards/github-card.tsx
  function GithubCard (line 9) | function GithubCard() {

FILE: components/examples/cards/index.tsx
  function CardsDemo (line 16) | function CardsDemo() {

FILE: components/examples/cards/payment-method.tsx
  function CardsPaymentMethod (line 38) | function CardsPaymentMethod() {

FILE: components/examples/cards/payments.tsx
  type Payment (line 77) | type Payment = {
  function CardsPayments (line 160) | function CardsPayments() {

FILE: components/examples/cards/report-issue.tsx
  function CardsReportIssue (line 25) | function CardsReportIssue() {

FILE: components/examples/cards/share.tsx
  function CardsShare (line 39) | function CardsShare() {

FILE: components/examples/cards/stats.tsx
  function CardsStats (line 54) | function CardsStats() {

FILE: components/examples/cards/team-members.tsx
  function CardsTeamMembers (line 58) | function CardsTeamMembers() {

FILE: components/examples/custom/index.tsx
  function CustomDemo (line 3) | function CustomDemo() {

FILE: components/examples/dashboard/components/app-sidebar.tsx
  function AppSidebar (line 151) | function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {

FILE: components/examples/dashboard/components/chart-area-interactive.tsx
  function ChartAreaInteractive (line 137) | function ChartAreaInteractive() {

FILE: components/examples/dashboard/components/chart-bar-mixed.tsx
  function ChartBarMixed (line 54) | function ChartBarMixed() {

FILE: components/examples/dashboard/components/chart-pie-donut.tsx
  function ChartPieDonut (line 55) | function ChartPieDonut() {

FILE: components/examples/dashboard/components/data-table.tsx
  function DragHandle (line 110) | function DragHandle({ id }: { id: number }) {
  function DraggableRow (line 291) | function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
  function DataTable (line 316) | function DataTable({
  function TableCellViewer (line 638) | function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {

FILE: components/examples/dashboard/components/nav-documents.tsx
  function NavDocuments (line 24) | function NavDocuments({

FILE: components/examples/dashboard/components/nav-main.tsx
  function NavMain (line 13) | function NavMain({

FILE: components/examples/dashboard/components/nav-secondary.tsx
  function NavSecondary (line 14) | function NavSecondary({

FILE: components/examples/dashboard/components/nav-user.tsx
  function NavUser (line 28) | function NavUser({

FILE: components/examples/dashboard/components/section-cards.tsx
  function SectionCards (line 12) | function SectionCards() {

FILE: components/examples/dashboard/components/site-header.tsx
  function SiteHeader (line 4) | function SiteHeader() {

FILE: components/examples/dashboard/index.tsx
  function Dashboard (line 14) | function Dashboard() {

FILE: components/examples/mail/components/account-switcher.tsx
  type AccountSwitcherProps (line 12) | interface AccountSwitcherProps {
  function AccountSwitcher (line 21) | function AccountSwitcher({ isCollapsed, accounts }: AccountSwitcherProps) {

FILE: components/examples/mail/components/mail-display.tsx
  type MailDisplayProps (line 29) | interface MailDisplayProps {
  function MailDisplay (line 33) | function MailDisplay({ mail }: MailDisplayProps) {

FILE: components/examples/mail/components/mail-list.tsx
  type MailListProps (line 10) | interface MailListProps {
  function MailList (line 14) | function MailList({ items }: MailListProps) {
  function getBadgeVariantFromLabel (line 72) | function getBadgeVariantFromLabel(label: string): ComponentProps<typeof ...

FILE: components/examples/mail/components/mail.tsx
  type MailProps (line 29) | interface MailProps {
  function Mail (line 41) | function Mail({

FILE: components/examples/mail/components/nav.tsx
  type NavProps (line 7) | interface NavProps {
  function Nav (line 17) | function Nav({ links, isCollapsed }: NavProps) {

FILE: components/examples/mail/data.tsx
  type Mail (line 174) | type Mail = (typeof mails)[number];
  type Account (line 215) | type Account = (typeof accounts)[number];
  type Contact (line 300) | type Contact = (typeof contacts)[number];

FILE: components/examples/mail/index.tsx
  function MailPage (line 4) | function MailPage() {

FILE: components/examples/mail/use-mail.ts
  type Config (line 4) | interface Config {
  function useMail (line 15) | function useMail(): [Config, (newState: Partial<Config>) => void] {

FILE: components/examples/music/components/album-artwork.tsx
  type AlbumArtworkProps (line 17) | interface AlbumArtworkProps extends React.HTMLAttributes<HTMLDivElement> {
  function AlbumArtwork (line 24) | function AlbumArtwork({

FILE: components/examples/music/components/menu.tsx
  function Menu (line 18) | function Menu() {

FILE: components/examples/music/components/podcast-empty-placeholder.tsx
  function PodcastEmptyPlaceholder (line 14) | function PodcastEmptyPlaceholder() {

FILE: components/examples/music/components/sidebar.tsx
  type SidebarProps (line 9) | interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
  function Sidebar (line 13) | function Sidebar({ className, playlists }: SidebarProps) {

FILE: components/examples/music/data/albums.ts
  type Album (line 1) | interface Album {

FILE: components/examples/music/data/playlists.ts
  type Playlist (line 1) | type Playlist = (typeof playlists)[number];

FILE: components/examples/music/index.tsx
  function MusicPage (line 15) | function MusicPage() {

FILE: components/examples/pricing/pricing.tsx
  type PricingFeature (line 11) | interface PricingFeature {
  type PricingPlan (line 15) | interface PricingPlan {
  type Pricing2Props (line 28) | interface Pricing2Props {

FILE: components/examples/tasks/components/data-table-column-header.tsx
  type DataTableColumnHeaderProps (line 14) | interface DataTableColumnHeaderProps<TData, TValue>
  function DataTableColumnHeader (line 20) | function DataTableColumnHeader<TData, TValue>({

FILE: components/examples/tasks/components/data-table-faceted-filter.tsx
  type DataTableFacetedFilterProps (line 20) | interface DataTableFacetedFilterProps<TData, TValue> {
  function DataTableFacetedFilter (line 30) | function DataTableFacetedFilter<TData, TValue>({

FILE: components/examples/tasks/components/data-table-pagination.tsx
  type DataTablePaginationProps (line 18) | interface DataTablePaginationProps<TData> {
  function DataTablePagination (line 22) | function DataTablePagination<TData>({

FILE: components/examples/tasks/components/data-table-row-actions.tsx
  type DataTableRowActionsProps (line 22) | interface DataTableRowActionsProps<TData> {
  function DataTableRowActions (line 26) | function DataTableRowActions<TData>({

FILE: components/examples/tasks/components/data-table-toolbar.tsx
  type DataTableToolbarProps (line 11) | interface DataTableToolbarProps<TData> {
  function DataTableToolbar (line 15) | function DataTableToolbar<TData>({ table }: DataTableToolbarProps<TData>) {

FILE: components/examples/tasks/components/data-table-view-options.tsx
  type DataTableViewOptionsProps (line 14) | interface DataTableViewOptionsProps<TData> {
  function DataTableViewOptions (line 18) | function DataTableViewOptions<TData>({

FILE: components/examples/tasks/components/data-table.tsx
  type DataTableProps (line 31) | interface DataTableProps<TData, TValue> {
  function DataTable (line 36) | function DataTable<TData, TValue>({

FILE: components/examples/tasks/components/user-nav.tsx
  function UserNav (line 14) | function UserNav() {

FILE: components/examples/tasks/data/schema.ts
  type Task (line 13) | type Task = z.infer<typeof taskSchema>

FILE: components/examples/tasks/index.tsx
  function getTasks (line 10) | function getTasks() {
  function TaskPage (line 14) | function TaskPage() {

FILE: components/examples/typography/blog-post.tsx
  function BlogPost (line 9) | function BlogPost() {

FILE: components/examples/typography/font-showcase.tsx
  function DemoFontShowcase (line 4) | function DemoFontShowcase() {

FILE: components/examples/typography/typography-demo.tsx
  function TypographyDemo (line 4) | function TypographyDemo() {

FILE: components/figma-export-dialog.tsx
  type FigmaExportDialogProps (line 20) | interface FigmaExportDialogProps {
  function FigmaExportDialog (line 25) | function FigmaExportDialog({ open, onOpenChange }: FigmaExportDialogProp...

FILE: components/figma-header.tsx
  type FigmaHeaderProps (line 14) | interface FigmaHeaderProps {
  function FigmaHeader (line 20) | function FigmaHeader({ isScrolled, mobileMenuOpen, setMobileMenuOpen }: ...

FILE: components/footer.tsx
  function Footer (line 7) | function Footer() {

FILE: components/get-pro-cta.tsx
  type GetProCTAProps (line 9) | interface GetProCTAProps extends React.ComponentProps<typeof Button> {}
  function GetProCTA (line 11) | function GetProCTA({ className, ...props }: GetProCTAProps) {

FILE: components/get-pro-dialog-wrapper.tsx
  function GetProDialogWrapper (line 20) | function GetProDialogWrapper() {
  type GetProDialogProps (line 26) | interface GetProDialogProps {
  function GetProDialog (line 31) | function GetProDialog({ isOpen, onClose }: GetProDialogProps) {

FILE: components/header.tsx
  function Header (line 19) | function Header() {

FILE: components/home/ai-generation-cta.tsx
  function AIGenerationCTA (line 7) | function AIGenerationCTA() {

FILE: components/home/cta.tsx
  function CTA (line 6) | function CTA() {

FILE: components/home/faq.tsx
  function FAQ (line 37) | function FAQ() {

FILE: components/home/features.tsx
  function Features (line 58) | function Features() {

FILE: components/home/header.tsx
  type HeaderProps (line 14) | interface HeaderProps {
  function Header (line 43) | function Header({ isScrolled, mobileMenuOpen, setMobileMenuOpen }: Heade...

FILE: components/home/hero.tsx
  function Hero (line 13) | function Hero() {

FILE: components/home/how-it-works.tsx
  function HowItWorks (line 22) | function HowItWorks() {

FILE: components/home/testimonials.tsx
  function Testimonials (line 225) | function Testimonials() {

FILE: components/home/theme-preset-buttons.tsx
  type ThemePresetButtonsProps (line 21) | interface ThemePresetButtonsProps {
  function ThemePresetButtons (line 28) | function ThemePresetButtons({
  type AnimatedRowProps (line 172) | interface AnimatedRowProps {
  function AnimatedRow (line 178) | function AnimatedRow({ children, target, options }: AnimatedRowProps) {

FILE: components/home/theme-preset-selector.tsx
  function ThemePresetSelector (line 16) | function ThemePresetSelector() {

FILE: components/horizontal-scroll-area.tsx
  type HorizontalScrollAreaProps (line 7) | interface HorizontalScrollAreaProps extends React.ComponentPropsWithoutR...
  function HorizontalScrollArea (line 9) | function HorizontalScrollArea({ className, children, ...props }: Horizon...

FILE: components/icons.tsx
  type IconProps (line 1) | type IconProps = React.HTMLAttributes<SVGElement>;

FILE: components/icons/tailwind-css.tsx
  function TailwindCSS (line 5) | function TailwindCSS({ className, ...props }: SVGProps<SVGSVGElement>) {

FILE: components/loader.tsx
  type LoaderProps (line 4) | interface LoaderProps {
  function CircularLoader (line 23) | function CircularLoader({
  function ClassicLoader (line 49) | function ClassicLoader({
  function PulseLoader (line 94) | function PulseLoader({
  function PulseDotLoader (line 115) | function PulseDotLoader({
  function DotsLoader (line 141) | function DotsLoader({
  function TypingLoader (line 179) | function TypingLoader({
  function WaveLoader (line 214) | function WaveLoader({
  function BarsLoader (line 259) | function BarsLoader({
  function TerminalLoader (line 297) | function TerminalLoader({
  function TextBlinkLoader (line 331) | function TextBlinkLoader({
  function TextShimmerLoader (line 359) | function TextShimmerLoader({
  function TextDotsLoader (line 389) | function TextDotsLoader({
  function Loader (line 416) | function Loader({ variant = "circular", size = "md", text, className }: ...

FILE: components/loading.tsx
  type LoadingProps (line 3) | interface LoadingProps {
  function Loading (line 7) | function Loading({ className }: LoadingProps) {

FILE: components/posthog-init.tsx
  function PostHogInit (line 6) | function PostHogInit() {

FILE: components/social-link.tsx
  type SocialLinkProps (line 4) | interface SocialLinkProps extends React.ComponentProps<"a"> {
  function SocialLink (line 8) | function SocialLink({ href, children, className, showIcon = false }: Soc...

FILE: components/tag-selector.tsx
  type TagSelectorProps (line 22) | interface TagSelectorProps {
  function TagSelector (line 28) | function TagSelector({

FILE: components/theme-preview.tsx
  type ThemePreviewProps (line 10) | interface ThemePreviewProps {
  function computeBoxShadow (line 16) | function computeBoxShadow(styles: ThemeStyleProps): string | undefined {
  function ThemePreview (line 33) | function ThemePreview({ styles, name, className }: ThemePreviewProps) {

FILE: components/theme-provider.tsx
  type Theme (line 8) | type Theme = "dark" | "light";
  type ThemeProviderProps (line 10) | type ThemeProviderProps = {
  type Coords (line 15) | type Coords = { x: number; y: number };
  type ThemeProviderState (line 17) | type ThemeProviderState = {
  function ThemeProvider (line 31) | function ThemeProvider({ children, ...props }: ThemeProviderProps) {

FILE: components/theme-script.tsx
  function ThemeScript (line 5) | function ThemeScript() {

FILE: components/theme-toggle.tsx
  type ThemeToggleProps (line 9) | interface ThemeToggleProps extends React.ComponentProps<typeof Button> {}
  function ThemeToggle (line 11) | function ThemeToggle({ className, ...props }: ThemeToggleProps) {

FILE: components/theme-view.tsx
  type CommunityData (line 21) | interface CommunityData {
  type ThemeViewProps (line 30) | interface ThemeViewProps {
  function ThemeView (line 35) | function ThemeView({ theme, communityData }: ThemeViewProps) {
  function CommunityAuthorInfo (line 166) | function CommunityAuthorInfo({
  function LikeButton (line 196) | function LikeButton({ communityData }: { communityData: CommunityData }) {

FILE: components/tooltip-wrapper.tsx
  function TooltipWrapper (line 7) | function TooltipWrapper({

FILE: components/ui/accordion.tsx
  function Accordion (line 9) | function Accordion({
  function AccordionItem (line 15) | function AccordionItem({
  function AccordionTrigger (line 28) | function AccordionTrigger({
  function AccordionContent (line 50) | function AccordionContent({

FILE: components/ui/alert-dialog.tsx
  function AlertDialog (line 9) | function AlertDialog({
  function AlertDialogTrigger (line 15) | function AlertDialogTrigger({
  function AlertDialogPortal (line 23) | function AlertDialogPortal({
  function AlertDialogOverlay (line 31) | function AlertDialogOverlay({
  function AlertDialogContent (line 47) | function AlertDialogContent({
  function AlertDialogHeader (line 70) | function AlertDialogHeader({
  function AlertDialogFooter (line 86) | function AlertDialogFooter({
  function AlertDialogTitle (line 102) | function AlertDialogTitle({
  function AlertDialogDescription (line 118) | function AlertDialogDescription({
  function AlertDialogMedia (line 131) | function AlertDialogMedia({
  function AlertDialogAction (line 147) | function AlertDialogAction({
  function AlertDialogCancel (line 165) | function AlertDialogCancel({

FILE: components/ui/alert.tsx
  function Alert (line 22) | function Alert({
  function AlertTitle (line 37) | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
  function AlertDescription (line 50) | function AlertDescription({

FILE: components/ui/aspect-ratio.tsx
  function AspectRatio (line 5) | function AspectRatio({

FILE: components/ui/avatar.tsx
  function Avatar (line 8) | function Avatar({
  function AvatarImage (line 28) | function AvatarImage({
  function AvatarFallback (line 41) | function AvatarFallback({
  function AvatarBadge (line 57) | function AvatarBadge({ className, ...props }: React.ComponentProps<"span...
  function AvatarGroup (line 73) | function AvatarGroup({ className, ...props }: React.ComponentProps<"div"...
  function AvatarGroupCount (line 86) | function AvatarGroupCount({

FILE: components/ui/badge.tsx
  function Badge (line 29) | function Badge({

FILE: components/ui/breadcrumb.tsx
  function Breadcrumb (line 7) | function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
  function BreadcrumbList (line 11) | function BreadcrumbList({ className, ...props }: React.ComponentProps<"o...
  function BreadcrumbItem (line 24) | function BreadcrumbItem({ className, ...props }: React.ComponentProps<"l...
  function BreadcrumbLink (line 34) | function BreadcrumbLink({
  function BreadcrumbPage (line 52) | function BreadcrumbPage({ className, ...props }: React.ComponentProps<"s...
  function BreadcrumbSeparator (line 65) | function BreadcrumbSeparator({
  function BreadcrumbEllipsis (line 83) | function BreadcrumbEllipsis({

FILE: components/ui/button.tsx
  function Button (line 42) | function Button({

FILE: components/ui/calendar.tsx
  function Calendar (line 18) | function Calendar({
  function CalendarDayButton (line 182) | function CalendarDayButton({

FILE: components/ui/card.tsx
  function Card (line 5) | function Card({ className, ...props }: React.ComponentProps<"div">) {
  function CardHeader (line 18) | function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  function CardTitle (line 31) | function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  function CardDescription (line 41) | function CardDescription({ className, ...props }: React.ComponentProps<"...
  function CardAction (line 51) | function CardAction({ className, ...props }: React.ComponentProps<"div">) {
  function CardContent (line 64) | function CardContent({ className, ...props }: React.ComponentProps<"div"...
  function CardFooter (line 74) | function CardFooter({ className, ...props }: React.ComponentProps<"div">) {

FILE: components/ui/carousel.tsx
  type CarouselApi (line 12) | type CarouselApi = UseEmblaCarouselType[1]
  type UseCarouselParameters (line 13) | type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
  type CarouselOptions (line 14) | type CarouselOptions = UseCarouselParameters[0]
  type CarouselPlugin (line 15) | type CarouselPlugin = UseCarouselParameters[1]
  type CarouselProps (line 17) | type CarouselProps = {
  type CarouselContextProps (line 24) | type CarouselContextProps = {
  function useCarousel (line 35) | function useCarousel() {
  function Carousel (line 45) | function Carousel({
  function CarouselContent (line 135) | function CarouselContent({ className, ...props }: React.ComponentProps<"...
  function CarouselItem (line 156) | function CarouselItem({ className, ...props }: React.ComponentProps<"div...
  function CarouselPrevious (line 174) | function CarouselPrevious({
  function CarouselNext (line 204) | function CarouselNext({

FILE: components/ui/chart.tsx
  constant THEMES (line 9) | const THEMES = { light: "", dark: ".dark" } as const
  type ChartConfig (line 11) | type ChartConfig = {
  type ChartContextProps (line 21) | type ChartContextProps = {
  function useChart (line 27) | function useChart() {
  function ChartContainer (line 37) | function ChartContainer({
  function ChartTooltipContent (line 107) | function ChartTooltipContent({
  function ChartLegendContent (line 255) | function ChartLegendContent({
  function getPayloadConfigFromPayload (line 312) | function getPayloadConfigFromPayload(

FILE: components/ui/checkbox.tsx
  function Checkbox (line 9) | function Checkbox({

FILE: components/ui/collapsible.tsx
  function Collapsible (line 5) | function Collapsible({
  function CollapsibleTrigger (line 11) | function CollapsibleTrigger({
  function CollapsibleContent (line 22) | function CollapsibleContent({

FILE: components/ui/command.tsx
  function Command (line 16) | function Command({
  function CommandDialog (line 32) | function CommandDialog({
  function CommandInput (line 63) | function CommandInput({
  function CommandList (line 85) | function CommandList({
  function CommandEmpty (line 101) | function CommandEmpty({
  function CommandGroup (line 113) | function CommandGroup({
  function CommandSeparator (line 129) | function CommandSeparator({
  function CommandItem (line 142) | function CommandItem({
  function CommandShortcut (line 158) | function CommandShortcut({

FILE: components/ui/context-menu.tsx
  function ContextMenu (line 9) | function ContextMenu({
  function ContextMenuTrigger (line 15) | function ContextMenuTrigger({
  function ContextMenuGroup (line 23) | function ContextMenuGroup({
  function ContextMenuPortal (line 31) | function ContextMenuPortal({
  function ContextMenuSub (line 39) | function ContextMenuSub({
  function ContextMenuRadioGroup (line 45) | function ContextMenuRadioGroup({
  function ContextMenuSubTrigger (line 56) | function ContextMenuSubTrigger({
  function ContextMenuSubContent (line 80) | function ContextMenuSubContent({
  function ContextMenuContent (line 96) | function ContextMenuContent({
  function ContextMenuItem (line 114) | function ContextMenuItem({
  function ContextMenuCheckboxItem (line 137) | function ContextMenuCheckboxItem({
  function ContextMenuRadioItem (line 163) | function ContextMenuRadioItem({
  function ContextMenuLabel (line 187) | function ContextMenuLabel({
  function ContextMenuSeparator (line 207) | function ContextMenuSeparator({
  function ContextMenuShortcut (line 220) | function ContextMenuShortcut({

FILE: components/ui/dialog.tsx
  function Dialog (line 10) | function Dialog({
  function DialogTrigger (line 16) | function DialogTrigger({
  function DialogPortal (line 22) | function DialogPortal({
  function DialogClose (line 28) | function DialogClose({
  function DialogOverlay (line 34) | function DialogOverlay({
  function DialogContent (line 50) | function DialogContent({
  function DialogHeader (line 84) | function DialogHeader({ className, ...props }: React.ComponentProps<"div...
  function DialogFooter (line 94) | function DialogFooter({
  function DialogTitle (line 121) | function DialogTitle({
  function DialogDescription (line 134) | function DialogDescription({

FILE: components/ui/drawer.tsx
  function Drawer (line 8) | function Drawer({
  function DrawerTrigger (line 14) | function DrawerTrigger({
  function DrawerPortal (line 20) | function DrawerPortal({
  function DrawerClose (line 26) | function DrawerClose({
  function DrawerOverlay (line 32) | function DrawerOverlay({
  function DrawerContent (line 48) | function DrawerContent({
  function DrawerHeader (line 75) | function DrawerHeader({ className, ...props }: React.ComponentProps<"div...
  function DrawerFooter (line 88) | function DrawerFooter({ className, ...props }: React.ComponentProps<"div...
  function DrawerTitle (line 98) | function DrawerTitle({
  function DrawerDescription (line 111) | function DrawerDescription({

FILE: components/ui/dropdown-menu.tsx
  function DropdownMenu (line 9) | function DropdownMenu({
  function DropdownMenuPortal (line 15) | function DropdownMenuPortal({
  function DropdownMenuTrigger (line 23) | function DropdownMenuTrigger({
  function DropdownMenuContent (line 34) | function DropdownMenuContent({
  function DropdownMenuGroup (line 54) | function DropdownMenuGroup({
  function DropdownMenuItem (line 62) | function DropdownMenuItem({
  function DropdownMenuCheckboxItem (line 85) | function DropdownMenuCheckboxItem({
  function DropdownMenuRadioGroup (line 111) | function DropdownMenuRadioGroup({
  function DropdownMenuRadioItem (line 122) | function DropdownMenuRadioItem({
  function DropdownMenuLabel (line 146) | function DropdownMenuLabel({
  function DropdownMenuSeparator (line 166) | function DropdownMenuSeparator({
  function DropdownMenuShortcut (line 179) | function DropdownMenuShortcut({
  function DropdownMenuSub (line 195) | function DropdownMenuSub({
  function DropdownMenuSubTrigger (line 201) | function DropdownMenuSubTrigger({
  function DropdownMenuSubContent (line 225) | function DropdownMenuSubContent({

FILE: components/ui/form.tsx
  type FormFieldContextValue (line 21) | type FormFieldContextValue<
  type FormItemContextValue (line 68) | type FormItemContextValue = {
  function FormItem (line 76) | function FormItem({ className, ...props }: React.ComponentProps<"div">) {
  function FormLabel (line 90) | function FormLabel({
  function FormControl (line 107) | function FormControl({ ...props }: React.ComponentProps<typeof Slot.Root...
  function FormDescription (line 125) | function FormDescription({ className, ...props }: React.ComponentProps<"...
  function FormMessage (line 138) | function FormMessage({ className, ...props }: React.ComponentProps<"p">) {

FILE: components/ui/hover-card.tsx
  function HoverCard (line 8) | function HoverCard({
  function HoverCardTrigger (line 14) | function HoverCardTrigger({
  function HoverCardContent (line 22) | function HoverCardContent({

FILE: components/ui/input-otp.tsx
  function InputOTP (line 9) | function InputOTP({
  function InputOTPGroup (line 29) | function InputOTPGroup({ className, ...props }: React.ComponentProps<"di...
  function InputOTPSlot (line 39) | function InputOTPSlot({
  function InputOTPSeparator (line 69) | function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {

FILE: components/ui/input.tsx
  function Input (line 5) | function Input({ className, type, ...props }: React.ComponentProps<"inpu...

FILE: components/ui/label.tsx
  function Label (line 8) | function Label({

FILE: components/ui/menubar.tsx
  function Menubar (line 9) | function Menubar({
  function MenubarMenu (line 25) | function MenubarMenu({
  function MenubarGroup (line 31) | function MenubarGroup({
  function MenubarPortal (line 37) | function MenubarPortal({
  function MenubarRadioGroup (line 43) | function MenubarRadioGroup({
  function MenubarTrigger (line 51) | function MenubarTrigger({
  function MenubarContent (line 67) | function MenubarContent({
  function MenubarItem (line 91) | function MenubarItem({
  function MenubarCheckboxItem (line 114) | function MenubarCheckboxItem({
  function MenubarRadioItem (line 140) | function MenubarRadioItem({
  function MenubarLabel (line 164) | function MenubarLabel({
  function MenubarSeparator (line 184) | function MenubarSeparator({
  function MenubarShortcut (line 197) | function MenubarShortcut({
  function MenubarSub (line 213) | function MenubarSub({
  function MenubarSubTrigger (line 219) | function MenubarSubTrigger({
  function MenubarSubContent (line 243) | function MenubarSubContent({

FILE: components/ui/navigation-menu.tsx
  function NavigationMenu (line 8) | function NavigationMenu({
  function NavigationMenuList (line 32) | function NavigationMenuList({
  function NavigationMenuItem (line 48) | function NavigationMenuItem({
  function NavigationMenuTrigger (line 65) | function NavigationMenuTrigger({
  function NavigationMenuContent (line 85) | function NavigationMenuContent({
  function NavigationMenuViewport (line 102) | function NavigationMenuViewport({
  function NavigationMenuLink (line 124) | function NavigationMenuLink({
  function NavigationMenuIndicator (line 140) | function NavigationMenuIndicator({

FILE: components/ui/pagination.tsx
  function Pagination (line 11) | function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
  function PaginationContent (line 23) | function PaginationContent({
  function PaginationItem (line 36) | function PaginationItem({ ...props }: React.ComponentProps<"li">) {
  type PaginationLinkProps (line 40) | type PaginationLinkProps = {
  function PaginationLink (line 45) | function PaginationLink({
  function PaginationPrevious (line 68) | function PaginationPrevious({
  function PaginationNext (line 85) | function PaginationNext({
  function PaginationEllipsis (line 102) | function PaginationEllipsis({

FILE: components/ui/popover.tsx
  function Popover (line 8) | function Popover({
  function PopoverTrigger (line 14) | function PopoverTrigger({
  function PopoverContent (line 20) | function PopoverContent({
  function PopoverAnchor (line 42) | function PopoverAnchor({
  function PopoverHeader (line 48) | function PopoverHeader({ className, ...props }: React.ComponentProps<"di...
  function PopoverTitle (line 58) | function PopoverTitle({ className, ...props }: React.ComponentProps<"h2"...
  function PopoverDescription (line 68) | function PopoverDescription({

FILE: components/ui/progress.tsx
  function Progress (line 8) | function Progress({

FILE: components/ui/radio-group.tsx
  function RadioGroup (line 9) | function RadioGroup({
  function RadioGroupItem (line 22) | function RadioGroupItem({

FILE: components/ui/resizable.tsx
  function ResizablePanelGroup (line 8) | function ResizablePanelGroup({
  function ResizablePanel (line 24) | function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
  function ResizableHandle (line 28) | function ResizableHandle({

FILE: components/ui/revola.tsx
  type ResponsiveDialogProps (line 14) | type ResponsiveDialogProps = React.ComponentProps<typeof DrawerPrimitive...
  type ResponsiveDialogContextProps (line 16) | type ResponsiveDialogContextProps = {
  type ResponsiveDialogProviderProps (line 26) | type ResponsiveDialogProviderProps = {
  constant MOBILE_BREAKPOINT (line 31) | const MOBILE_BREAKPOINT = "(min-width: 640px)";

FILE: components/ui/scroll-area.tsx
  function ScrollArea (line 8) | function ScrollArea({
  function ScrollBar (line 31) | function ScrollBar({

FILE: components/ui/select.tsx
  function Select (line 9) | function Select({
  function SelectGroup (line 15) | function SelectGroup({
  function SelectValue (line 21) | function SelectValue({
  function SelectTrigger (line 27) | function SelectTrigger({
  function SelectContent (line 53) | function SelectContent({
  function SelectLabel (line 90) | function SelectLabel({
  function SelectItem (line 103) | function SelectItem({
  function SelectSeparator (line 130) | function SelectSeparator({
  function SelectScrollUpButton (line 143) | function SelectScrollUpButton({
  function SelectScrollDownButton (line 161) | function SelectScrollDownButton({

FILE: components/ui/separator.tsx
  function Separator (line 8) | function Separator({

FILE: components/ui/sheet.tsx
  function Sheet (line 9) | function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive....
  function SheetTrigger (line 13) | function SheetTrigger({
  function SheetClose (line 19) | function SheetClose({
  function SheetPortal (line 25) | function SheetPortal({
  function SheetOverlay (line 31) | function SheetOverlay({
  function SheetContent (line 47) | function SheetContent({
  function SheetHeader (line 88) | function SheetHeader({ className, ...props }: React.ComponentProps<"div"...
  function SheetFooter (line 98) | function SheetFooter({ className, ...props }: React.ComponentProps<"div"...
  function SheetTitle (line 108) | function SheetTitle({
  function SheetDescription (line 121) | function SheetDescription({

FILE: components/ui/sidebar.tsx
  constant SIDEBAR_COOKIE_NAME (line 15) | const SIDEBAR_COOKIE_NAME = "sidebar:state";
  constant SIDEBAR_COOKIE_MAX_AGE (line 16) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
  constant SIDEBAR_WIDTH (line 17) | const SIDEBAR_WIDTH = "16rem";
  constant SIDEBAR_WIDTH_MOBILE (line 18) | const SIDEBAR_WIDTH_MOBILE = "18rem";
  constant SIDEBAR_WIDTH_ICON (line 19) | const SIDEBAR_WIDTH_ICON = "3rem";
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 20) | const SIDEBAR_KEYBOARD_SHORTCUT = "b";
  type SidebarContext (line 22) | type SidebarContext = {
  function useSidebar (line 34) | function useSidebar() {

FILE: components/ui/skeleton.tsx
  function Skeleton (line 3) | function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

FILE: components/ui/slider.tsx
  function Slider (line 8) | function Slider({

FILE: components/ui/switch.tsx
  function Switch (line 8) | function Switch({

FILE: components/ui/table.tsx
  function Table (line 7) | function Table({ className, ...props }: React.ComponentProps<"table">) {
  function TableHeader (line 22) | function TableHeader({ className, ...props }: React.ComponentProps<"thea...
  function TableBody (line 32) | function TableBody({ className, ...props }: React.ComponentProps<"tbody"...
  function TableFooter (line 42) | function TableFooter({ className, ...props }: React.ComponentProps<"tfoo...
  function TableRow (line 55) | function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
  function TableHead (line 68) | function TableHead({ className, ...props }: React.ComponentProps<"th">) {
  function TableCell (line 81) | function TableCell({ className, ...props }: React.ComponentProps<"td">) {
  function TableCaption (line 94) | function TableCaption({

FILE: components/ui/tabs.tsx
  function Tabs (line 9) | function Tabs({
  function TabsList (line 43) | function TabsList({
  function TabsTrigger (line 59) | function TabsTrigger({
  function TabsContent (line 75) | function TabsContent({

FILE: components/ui/textarea.tsx
  function Textarea (line 5) | function Textarea({ className, ...props }: React.ComponentProps<"textare...

FILE: components/ui/toast.tsx
  type ToastProps (line 113) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
  type ToastActionElement (line 115) | type ToastActionElement = React.ReactElement<typeof ToastAction>;

FILE: components/ui/toaster.tsx
  function Toaster (line 13) | function Toaster() {

FILE: components/ui/toggle-group.tsx
  function ToggleGroup (line 20) | function ToggleGroup({
  function ToggleGroupItem (line 51) | function ToggleGroupItem({

FILE: components/ui/toggle.tsx
  function Toggle (line 31) | function Toggle({

FILE: components/ui/tooltip.tsx
  function TooltipProvider (line 8) | function TooltipProvider({
  function Tooltip (line 21) | function Tooltip({
  function TooltipTrigger (line 27) | function TooltipTrigger({
  function TooltipContent (line 33) | function TooltipContent({

FILE: components/user-profile-dropdown.tsx
  function UserProfileDropdown (line 25) | function UserProfileDropdown() {

FILE: config/theme.ts
  constant COMMON_STYLES (line 5) | const COMMON_STYLES = [
  constant DEFAULT_FONT_SANS (line 19) | const DEFAULT_FONT_SANS =
  constant DEFAULT_FONT_SERIF (line 22) | const DEFAULT_FONT_SERIF = 'ui-serif, Georgia, Cambria, "Times New Roman...
  constant DEFAULT_FONT_MONO (line 24) | const DEFAULT_FONT_MONO =

FILE: db/index.ts
  method get (line 7) | get(_target, prop) {

FILE: drizzle/0000_rare_moira_mactaggert.sql
  type "account" (line 1) | CREATE TABLE "account" (
  type "session" (line 17) | CREATE TABLE "session" (
  type "theme" (line 29) | CREATE TABLE "theme" (
  type "user" (line 38) | CREATE TABLE "user" (
  type "verification" (line 49) | CREATE TABLE "verification" (

FILE: drizzle/0001_late_mikhail_rasputin.sql
  type "ai_usage" (line 1) | CREATE TABLE "ai_usage" (
  type "subscription" (line 11) | CREATE TABLE "subscription" (

FILE: drizzle/0002_nebulous_randall.sql
  type "community_theme" (line 1) | CREATE TABLE "community_theme" (
  type "theme_like" (line 9) | CREATE TABLE "theme_like" (
  type "community_theme" (line 20) | CREATE INDEX "community_theme_published_at_idx" ON "community_theme" USI...

FILE: drizzle/0003_bumpy_quasimodo.sql
  type "community_theme_tag" (line 1) | CREATE TABLE "community_theme_tag" (
  type "community_theme_tag" (line 8) | CREATE INDEX "community_theme_tag_tag_idx" ON "community_theme_tag" USIN...

FILE: drizzle/0004_red_monster_badoon.sql
  type "community_theme" (line 5) | CREATE INDEX "community_theme_like_count_idx" ON "community_theme" USING...

FILE: hooks/inspector/use-inspector-mouse-events.ts
  type UseInspectorMouseEventsProps (line 5) | interface UseInspectorMouseEventsProps {

FILE: hooks/inspector/use-inspector-scroll.ts
  type UseInspectorScrollProps (line 3) | interface UseInspectorScrollProps {

FILE: hooks/themes/use-community-themes.ts
  function useCommunityThemes (line 40) | function useCommunityThemes(
  function useCommunityTagCounts (line 66) | function useCommunityTagCounts() {
  function useMyPublishedThemeIds (line 74) | function useMyPublishedThemeIds() {
  function usePublishTheme (line 82) | function usePublishTheme() {
  function useUnpublishTheme (line 131) | function useUnpublishTheme() {
  function useToggleLike (line 156) | function useToggleLike() {
  function useUpdateCommunityThemeTags (line 225) | function useUpdateCommunityThemeTags() {

FILE: hooks/themes/use-theme-mutations.ts
  function handleMutationError (line 13) | function handleMutationError(error: Error, operation: string) {
  function useCreateTheme (line 52) | function useCreateTheme() {
  function useUpdateTheme (line 107) | function useUpdateTheme() {
  function useDeleteTheme (line 171) | function useDeleteTheme() {

FILE: hooks/themes/use-themes-data.ts
  type ThemeWithPublished (line 5) | type ThemeWithPublished = Awaited<ReturnType<typeof getThemes>>[number];
  function useThemesData (line 15) | function useThemesData(initialData?: ThemeWithPublished[]) {
  function useThemeData (line 24) | function useThemeData(themeId: string | null, initialData?: Theme) {
  function usePrefetchThemes (line 34) | function usePrefetchThemes() {

FILE: hooks/use-ai-chat-form.ts
  function useAIChatForm (line 12) | function useAIChatForm() {

FILE: hooks/use-ai-enhance-prompt.ts
  function useAIEnhancePrompt (line 14) | function useAIEnhancePrompt() {

FILE: hooks/use-ai-theme-generation-core.ts
  function useAIThemeGenerationCore (line 4) | function useAIThemeGenerationCore() {

FILE: hooks/use-chat-context.tsx
  type ChatContext (line 15) | interface ChatContext extends ReturnType<typeof useChat<ChatMessage>> {
  function ChatProvider (line 22) | function ChatProvider({ children }: { children: React.ReactNode }) {
  function useChatContext (line 93) | function useChatContext() {

FILE: hooks/use-contrast-checker.ts
  type ColorPair (line 5) | type ColorPair = {
  type ContrastResult (line 11) | type ContrastResult = {
  function useContrastChecker (line 21) | function useContrastChecker(colorPairs: ColorPair[]) {

FILE: hooks/use-controls-tab-from-url.ts
  constant TABS (line 3) | const TABS = ["colors", "typography", "other", "ai"] as const;
  constant DEFAULT_TAB (line 4) | const DEFAULT_TAB = TABS[0];
  type ControlTab (line 5) | type ControlTab = (typeof TABS)[number];

FILE: hooks/use-copy-to-clipboard.ts
  function useCopyToClipboard (line 4) | function useCopyToClipboard() {

FILE: hooks/use-debounced-callback.ts
  function useDebouncedCallback (line 3) | function useDebouncedCallback<A extends unknown[]>(

FILE: hooks/use-dialog-actions.tsx
  type PendingAction (line 17) | type PendingAction = "share" | "v0" | null;
  function getSaveDialogCopy (line 20) | function getSaveDialogCopy(pendingAction: PendingAction) {
  type DialogActionsContextType (line 39) | interface DialogActionsContextType {
  function useDialogActionsStore (line 68) | function useDialogActionsStore(): DialogActionsContextType {
  function DialogActionsProvider (line 290) | function DialogActionsProvider({ children }: { children: ReactNode }) {
  function useDialogActions (line 328) | function useDialogActions(): DialogActionsContextType {

FILE: hooks/use-document-drag-and-drop-intent.ts
  function useDocumentDragAndDropIntent (line 3) | function useDocumentDragAndDropIntent() {

FILE: hooks/use-feedback-text.ts
  constant ROTATION_INTERVAL_IN_SECONDS (line 3) | const ROTATION_INTERVAL_IN_SECONDS = 8;
  constant DEFAULT_FEEDBACK_MESSAGES (line 5) | const DEFAULT_FEEDBACK_MESSAGES = ["Loading..."];
  type UseFeedbackTextProps (line 7) | type UseFeedbackTextProps = {
  function useFeedbackText (line 13) | function useFeedbackText({

FILE: hooks/use-font-search.ts
  type FilterFontCategory (line 4) | type FilterFontCategory = "all" | FontCategory;
  type UseFontSearchParams (line 6) | interface UseFontSearchParams {
  function useFontSearch (line 13) | function useFontSearch({

FILE: hooks/use-github-stars.ts
  type GitHubStarsResponse (line 3) | interface GitHubStarsResponse {
  function fetchGithubStars (line 7) | async function fetchGithubStars(
  function useGithubStars (line 18) | function useGithubStars(owner: string, repo: string) {

FILE: hooks/use-guards.ts
  function useGuards (line 7) | function useGuards() {
  function useSessionGuard (line 18) | function useSessionGuard() {
  function useSubscriptionGuard (line 40) | function useSubscriptionGuard() {

FILE: hooks/use-iframe-theme-injector.ts
  constant THEME_UPDATE_DEBOUNCE_MS (line 6) | const THEME_UPDATE_DEBOUNCE_MS = 50;
  type UseIframeThemeInjectorProps (line 8) | interface UseIframeThemeInjectorProps {

FILE: hooks/use-image-upload.ts
  type PromptImageWithLoading (line 11) | type PromptImageWithLoading = PromptImage & { loading: boolean };
  type ImageUploadAction (line 13) | type ImageUploadAction =
  type UseImageUploadOptions (line 21) | interface UseImageUploadOptions {
  function useImageUpload (line 28) | function useImageUpload({ maxFiles, maxFileSize, images, dispatch }: Use...

FILE: hooks/use-mobile.tsx
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768;
  function useIsMobile (line 5) | function useIsMobile() {

FILE: hooks/use-mounted.tsx
  function useMounted (line 3) | function useMounted() {

FILE: hooks/use-post-login-action.ts
  type PostLoginActionType (line 11) | type PostLoginActionType =
  type PostLoginActionPayload (line 23) | interface PostLoginActionPayload<T = any> {
  type StoredPostLoginAction (line 28) | type StoredPostLoginAction = PostLoginActionPayload | null;
  type PostLoginHandler (line 30) | type PostLoginHandler<T = any> = (data?: T) => void | Promise<void>;
  function usePostLoginAction (line 36) | function usePostLoginAction<T = any>(
  function executePostLoginActionInternal (line 63) | async function executePostLoginActionInternal(actionPayload: StoredPostL...
  function executePostLoginAction (line 76) | async function executePostLoginAction(actionPayload: StoredPostLoginActi...

FILE: hooks/use-scroll-start-end.ts
  type IntersectionObserverInitWithoutRoot (line 21) | type IntersectionObserverInitWithoutRoot = Omit<IntersectionObserverInit...
  type UseScrollStartEndProps (line 25) | type UseScrollStartEndProps =
  function useScrollStartEnd (line 41) | function useScrollStartEnd({

FILE: hooks/use-subscription.ts
  function fetchSubscriptionStatus (line 5) | async function fetchSubscriptionStatus(): Promise<SubscriptionStatus> {
  constant SUBSCRIPTION_STATUS_QUERY_KEY (line 10) | const SUBSCRIPTION_STATUS_QUERY_KEY = "subscriptionStatus";
  function useSubscription (line 12) | function useSubscription() {

FILE: hooks/use-theme-inspector-regex.ts
  constant THEME_CLASS_REGEX (line 22) | const THEME_CLASS_REGEX = createThemeClassRegex();

FILE: hooks/use-toast.ts
  constant TOAST_LIMIT (line 7) | const TOAST_LIMIT = 1;
  constant TOAST_REMOVE_DELAY (line 8) | const TOAST_REMOVE_DELAY = 1000000;
  type ToasterToast (line 10) | type ToasterToast = ToastProps & {
  function genId (line 26) | function genId() {
  type ActionType (line 31) | type ActionType = typeof actionTypes;
  type Action (line 33) | type Action =
  type State (line 51) | interface State {
  function dispatch (line 132) | function dispatch(action: Action) {
  type Toast (line 139) | type Toast = Omit<ToasterToast, "id">;
  function toast (line 141) | function toast({ ...props }: Toast) {
  function useToast (line 170) | function useToast() {

FILE: hooks/use-website-preview.ts
  constant LOADING_TIMEOUT_MS (line 4) | const LOADING_TIMEOUT_MS = 5000;
  type WebsitePreviewState (line 6) | interface WebsitePreviewState {
  type Action (line 11) | type Action =
  function reducer (line 23) | function reducer(state: WebsitePreviewState, action: Action): WebsitePre...
  type UseWebsitePreviewProps (line 40) | interface UseWebsitePreviewProps {
  function useWebsitePreview (line 44) | function useWebsitePreview({ allowCrossOrigin = false }: UseWebsitePrevi...

FILE: lib/ai/generate-theme/tools.ts
  constant THEME_GENERATION_TOOLS (line 7) | const THEME_GENERATION_TOOLS = {

FILE: lib/ai/parse-ai-sdk-transport-error.ts
  type ParsedAiSdkTransportError (line 9) | type ParsedAiSdkTransportError = {
  constant KNOWN_CODES (line 16) | const KNOWN_CODES: ReadonlyArray<ApiErrorCode> = [
  function isApiErrorCode (line 23) | function isApiErrorCode(value: unknown): value is ApiErrorCode {
  function parseAiSdkTransportError (line 27) | function parseAiSdkTransportError(

FILE: lib/ai/prompts.ts
  constant GENERATE_THEME_SYSTEM (line 1) | const GENERATE_THEME_SYSTEM = `# Role
  constant ENHANCE_PROMPT_SYSTEM (line 80) | const ENHANCE_PROMPT_SYSTEM = `# Role

FILE: lib/constants.ts
  constant AI_PROMPT_CHARACTER_LIMIT (line 1) | const AI_PROMPT_CHARACTER_LIMIT = 1000;
  constant DEBOUNCE_DELAY (line 3) | const DEBOUNCE_DELAY = 50;
  constant AI_REQUEST_FREE_TIER_LIMIT (line 5) | const AI_REQUEST_FREE_TIER_LIMIT = 5;
  constant MAX_IMAGE_FILES (line 7) | const MAX_IMAGE_FILES = 2;
  constant MAX_IMAGE_FILE_SIZE (line 8) | const MAX_IMAGE_FILE_SIZE = 4 * 1024 * 1024;
  constant MAX_SVG_FILE_SIZE (line 9) | const MAX_SVG_FILE_SIZE = 1 * 1024 * 1024;
  constant MAX_FREE_THEMES (line 11) | const MAX_FREE_THEMES = 10;
  constant COMMUNITY_THEMES_PAGE_SIZE (line 13) | const COMMUNITY_THEMES_PAGE_SIZE = 20;
  constant COMMUNITY_THEME_TAGS (line 15) | const COMMUNITY_THEME_TAGS = [
  constant MAX_TAGS_PER_THEME (line 67) | const MAX_TAGS_PER_THEME = 5;
  constant OAUTH_ACCESS_TOKEN_EXPIRY_SECONDS (line 70) | const OAUTH_ACCESS_TOKEN_EXPIRY_SECONDS = 60 * 60;
  constant OAUTH_REFRESH_TOKEN_EXPIRY_SECONDS (line 71) | const OAUTH_REFRESH_TOKEN_EXPIRY_SECONDS = 60 * 60 * 24 * 30;
  constant OAUTH_AUTHORIZATION_CODE_EXPIRY_SECONDS (line 72) | const OAUTH_AUTHORIZATION_CODE_EXPIRY_SECONDS = 60 * 10;

FILE: lib/error-response.ts
  function jsonError (line 9) | function jsonError(
  function handleError (line 22) | function handleError(error: unknown, context: Record<string, unknown> = ...

FILE: lib/figma-constants.ts
  constant FIGMA_CONSTANTS (line 1) | const FIGMA_CONSTANTS = {

FILE: lib/inspector/inspector-state-utils.ts
  type InspectorState (line 1) | interface InspectorState {

FILE: lib/oauth.ts
  function generateSecureToken (line 15) | function generateSecureToken(): string {
  function hashSecret (line 19) | async function hashSecret(secret: string): Promise<string> {
  function verifySecret (line 23) | async function verifySecret(
  function hashToken (line 31) | function hashToken(token: string): string {
  constant VALID_SCOPES (line 37) | const VALID_SCOPES = ["themes:read", "profile:read"] as const;
  type OAuthScope (line 38) | type OAuthScope = (typeof VALID_SCOPES)[number];
  function validateScopes (line 40) | function validateScopes(scopes: string[]): scopes is OAuthScope[] {
  function parseScopes (line 44) | function parseScopes(scopeString: string): string[] {
  function validateRedirectUri (line 53) | function validateRedirectUri(
  function verifyCodeChallenge (line 62) | function verifyCodeChallenge(
  function createTokenPair (line 81) | async function createTokenPair(
  function resolveUserFromBearerToken (line 121) | async function resolveUserFromBearerToken(
  function requireScope (line 147) | function requireScope(scopes: string[], required: OAuthScope): boolean {
  function requireAuth (line 151) | async function requireAuth(
  function authenticateClient (line 181) | async function authenticateClient(
  function oauthError (line 204) | function oauthError(

FILE: lib/posthog.ts
  function initPostHog (line 3) | function initPostHog() {

FILE: lib/query-client.tsx
  function logClientError (line 8) | function logClientError(error: Error, context: Record<string, unknown>) {
  function createQueryClient (line 25) | function createQueryClient() {
  type QueryProviderProps (line 62) | interface QueryProviderProps {
  function QueryProvider (line 66) | function QueryProvider({ children }: QueryProviderProps) {

FILE: lib/shared.ts
  function getCurrentUserId (line 7) | async function getCurrentUserId(req?: NextRequest): Promise<string> {
  function getCurrentUser (line 19) | async function getCurrentUser(req?: NextRequest): Promise<User> {
  function logError (line 31) | function logError(error: Error, context?: Record<string, unknown>) {

FILE: lib/subscription.ts
  function getMyActiveSubscription (line 13) | async function getMyActiveSubscription(
  function validateSubscriptionAndUsage (line 23) | async function validateSubscriptionAndUsage(userId: string): Promise<Sub...
  function requireSubscriptionOrFreeUsage (line 68) | async function requireSubscriptionOrFreeUsage(req: NextRequest): Promise...

FILE: lib/utils.ts
  function cn (line 6) | function cn(...inputs: ClassValue[]) {
  function isDeepEqual (line 10) | function isDeepEqual(a: unknown, b: unknown): boolean {

FILE: middleware.ts
  function middleware (line 7) | async function middleware(request: NextRequest) {

FILE: public/live-preview.js
  constant REQUIRED_SHADCN_VARS (line 2) | const REQUIRED_SHADCN_VARS = [
  function checkShadcnSupport (line 24) | function checkShadcnSupport() {
  constant DEFAULT_FONT_WEIGHTS (line 33) | const DEFAULT_FONT_WEIGHTS = ["400", "500", "600", "700"];
  function extractFontFamily (line 35) | function extractFontFamily(fontFamilyValue) {
  function buildFontCssUrl (line 47) | function buildFontCssUrl(family, weights) {
  function loadGoogleFont (line 54) | function loadGoogleFont(family, weights) {
  function overrideFontClasses (line 66) | function overrideFontClasses(root, fonts) {
  function overrideShadowClass (line 93) | function overrideShadowClass(root, themeStyles, mode) {
  function loadThemeFonts (line 173) | function loadThemeFonts(root, themeStyles) {
  function applyStyleProperty (line 196) | function applyStyleProperty(root, key, value) {
  function updateThemeModeClass (line 202) | function updateThemeModeClass(root, mode) {
  function applyThemeStyles (line 206) | function applyThemeStyles(root, themeStyles, mode) {
  function applyTheme (line 226) | function applyTheme(themeState) {
  function sendMessageToParent (line 238) | function sendMessageToParent(message) {
  constant TWEAKCN_MESSAGE (line 248) | const TWEAKCN_MESSAGE = {

FILE: routes.ts
  constant API_AUTH_PREFIX (line 1) | const API_AUTH_PREFIX: string = "/api/auth";
  constant DEFAULT_LOGIN_REDIRECT (line 3) | const DEFAULT_LOGIN_REDIRECT: string = "/editor/theme";

FILE: scripts/create-oauth-app.ts
  function parseArgs (line 22) | function parseArgs() {
  function main (line 34) | async function main() {

FILE: scripts/generate-registry.ts
  type ThemeRegistry (line 6) | interface ThemeRegistry {
  function generateRegistry (line 13) | function generateRegistry() {

FILE: scripts/generate-theme-registry.ts
  constant THEMES_DIR (line 10) | const THEMES_DIR = path.join(process.cwd(), "public", "r", "themes");
  constant V0_DIR (line 11) | const V0_DIR = path.join(process.cwd(), "public", "r", "v0");
  function getPresetThemeStylesForScript (line 21) | function getPresetThemeStylesForScript(name: string): ThemeStyles {

FILE: store/ai-chat-store.ts
  type AIChatStore (line 6) | interface AIChatStore {

FILE: store/ai-local-draft-store.ts
  type AILocalDraftStore (line 6) | interface AILocalDraftStore {

FILE: store/auth-store.ts
  type AuthStore (line 5) | interface AuthStore {

FILE: store/color-control-focus-store.ts
  type FocusColorId (line 5) | type FocusColorId =
  type ColorRefEntry (line 39) | interface ColorRefEntry {
  type ColorControlFocusState (line 43) | interface ColorControlFocusState {

FILE: store/editor-store.ts
  constant MAX_HISTORY_COUNT (line 8) | const MAX_HISTORY_COUNT = 30;
  constant HISTORY_OVERRIDE_THRESHOLD_MS (line 9) | const HISTORY_OVERRIDE_THRESHOLD_MS = 500;
  type ThemeHistoryEntry (line 11) | interface ThemeHistoryEntry {
  type EditorStore (line 16) | interface EditorStore {

FILE: store/get-pro-dialog-store.ts
  type GetProDialogState (line 3) | interface GetProDialogState {

FILE: store/preferences-store.ts
  type PackageManager (line 5) | type PackageManager = "pnpm" | "npm" | "yarn" | "bun";
  type ColorSelectorTab (line 6) | type ColorSelectorTab = "list" | "palette";
  type PreferencesStore (line 13) | interface PreferencesStore {

FILE: store/theme-preset-store.ts
  type ThemePresetStore (line 6) | interface ThemePresetStore {

FILE: store/website-preview-store.ts
  type WebsitePreviewStore (line 4) | interface WebsitePreviewStore {

FILE: types/ai.ts
  type MentionReference (line 5) | type MentionReference = {
  type PromptImage (line 14) | type PromptImage = {
  type AIPromptData (line 18) | type AIPromptData = {
  type MyMetadata (line 24) | type MyMetadata = {
  type MyUIDataParts (line 29) | type MyUIDataParts = {
  type ThemeGenerationUITools (line 41) | type ThemeGenerationUITools = InferUITools<typeof THEME_GENERATION_TOOLS>;
  type MyUITools (line 42) | type MyUITools = ThemeGenerationUITools;
  type ChatMessage (line 44) | type ChatMessage = UIMessage<MyMetadata, MyUIDataParts, MyUITools>;
  type AdditionalAIContext (line 46) | type AdditionalAIContext = { writer: UIMessageStreamWriter<ChatMessage> };

FILE: types/community.ts
  type CommunityThemeAuthor (line 3) | interface CommunityThemeAuthor {
  type CommunityTheme (line 9) | interface CommunityTheme {
  type CommunitySortOption (line 21) | type CommunitySortOption = "popular" | "newest" | "oldest";
  type CommunityTimeRange (line 22) | type CommunityTimeRange = "weekly" | "monthly" | "all";
  type CommunityFilterOption (line 23) | type CommunityFilterOption = "all" | "mine" | "liked";
  type CommunityThemesResponse (line 25) | interface CommunityThemesResponse {

FILE: types/editor.ts
  type BaseEditorState (line 4) | interface BaseEditorState {
  type EditorControls (line 9) | interface EditorControls {
  type EditorPreviewProps (line 14) | interface EditorPreviewProps {
  type ThemeEditorState (line 18) | interface ThemeEditorState extends BaseEditorState {
  type EditorType (line 30) | type EditorType = "button" | "input" | "card" | "dialog" | "theme";
  type EditorConfig (line 33) | interface EditorConfig {

FILE: types/errors.ts
  type ErrorCode (line 13) | type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
  type ActionResult (line 16) | type ActionResult<T> =
  function actionError (line 21) | function actionError(code: ErrorCode, message: string): ActionResult<nev...
  function actionSuccess (line 26) | function actionSuccess<T>(data: T): ActionResult<T> {
  class UnauthorizedError (line 30) | class UnauthorizedError extends Error {
    method constructor (line 31) | constructor(message = "Unauthorized") {
  class ValidationError (line 37) | class ValidationError extends Error {
    method constructor (line 38) | constructor(
  class SubscriptionRequiredError (line 47) | class SubscriptionRequiredError extends Error {
    method constructor (line 48) | constructor(
  class ThemeNotFoundError (line 57) | class ThemeNotFoundError extends Error {
    method constructor (line 58) | constructor(message = "Theme not found") {
  type ApiErrorCode (line 64) | type ApiErrorCode =
  class ApiError (line 70) | class ApiError extends Error {
    method constructor (line 71) | constructor(
  type MyErrorResponseType (line 89) | type MyErrorResponseType = z.infer<typeof MyErrorResponseSchema>;

FILE: types/fonts.ts
  type FontCategory (line 1) | type FontCategory = "sans-serif" | "serif" | "display" | "handwriting" |...
  type GoogleFontAxis (line 4) | type GoogleFontAxis = {
  type GoogleFontFiles (line 10) | type GoogleFontFiles = {
  type GoogleFont (line 14) | type GoogleFont = {
  type GoogleFontsAPIResponse (line 27) | type GoogleFontsAPIResponse = {
  type FontInfo (line 33) | type FontInfo = {
  type PaginatedFontsResponse (line 40) | type PaginatedFontsResponse = {

FILE: types/index.ts
  type ControlSectionProps (line 3) | type ControlSectionProps = {
  type ColorPickerProps (line 11) | type ColorPickerProps = {
  type SliderInputProps (line 31) | type SliderInputProps = {
  type ToggleOptionProps (line 41) | type ToggleOptionProps<T> = {
  type ReadOnlyColorDisplayProps (line 48) | type ReadOnlyColorDisplayProps = {
  type ColorFormat (line 54) | type ColorFormat = "hex" | "rgb" | "hsl" | "oklch";
  type ValidTailwindShade (line 56) | type ValidTailwindShade =

FILE: types/live-preview-embed.ts
  constant MESSAGE (line 4) | const MESSAGE = {
  type MessageType (line 15) | type MessageType = (typeof MESSAGE)[keyof typeof MESSAGE];
  type ShadcnStatusPayload (line 17) | interface ShadcnStatusPayload {
  type ThemeUpdatePayload (line 21) | interface ThemeUpdatePayload {
  type EmbedMessage (line 25) | type EmbedMessage =
  type IframeStatus (line 35) | type IframeStatus =

FILE: types/subscription.ts
  type SubscriptionCheck (line 1) | interface SubscriptionCheck extends SubscriptionStatus {
  type SubscriptionStatus (line 6) | interface SubscriptionStatus {

FILE: types/theme.ts
  type ThemeStyleProps (line 75) | type ThemeStyleProps = z.infer<typeof themeStylePropsSchema>;
  type ThemeStyles (line 76) | type ThemeStyles = z.infer<typeof themeStylesSchema>;
  type ThemeStylesWithoutSpacing (line 87) | type ThemeStylesWithoutSpacing = z.infer<typeof themeStylesSchemaWithout...
  type ThemeEditorPreviewProps (line 89) | interface ThemeEditorPreviewProps {
  type ThemeEditorControlsProps (line 94) | interface ThemeEditorControlsProps {
  type ThemePreset (line 100) | type ThemePreset = {
  type Theme (line 110) | type Theme = InferSelectModel<typeof theme>;

FILE: utils/ai/ai-prompt.tsx
  function attachCurrentThemeMention (line 47) | function attachCurrentThemeMention(promptData: AIPromptData): AIPromptDa...
  function createCurrentThemePrompt (line 63) | function createCurrentThemePrompt({ prompt }: { prompt: string }): AIPro...
  function mentionsCurrentTheme (line 78) | function mentionsCurrentTheme(promptData: AIPromptData): boolean {
  function createPromptDataFromMentions (line 82) | function createPromptDataFromMentions(content: string, mentionIds: strin...
  function createPromptDataFromPreset (line 110) | function createPromptDataFromPreset(prompt: string, presetName: string):...
  function extractTextContentAndMentions (line 135) | function extractTextContentAndMentions(node: JSONContent): {
  function convertJSONContentToPromptData (line 185) | function convertJSONContentToPromptData(jsonContent: JSONContent): AIPro...
  function convertPromptDataToJSONContent (line 195) | function convertPromptDataToJSONContent(promptData: AIPromptData): JSONC...
  function isEmptyPromptData (line 335) | function isEmptyPromptData(
  function dedupeMentionReferences (line 345) | function dedupeMentionReferences(mentions: MentionReference[]): MentionR...

FILE: utils/ai/apply-theme.ts
  function applyGeneratedTheme (line 5) | function applyGeneratedTheme(themeStyles: ThemeStyles) {

FILE: utils/ai/image-upload.ts
  constant ALLOWED_IMAGE_TYPES (line 1) | const ALLOWED_IMAGE_TYPES = [
  function validateSvgContent (line 9) | function validateSvgContent(svgText: string): boolean {
  function optimizeSvgContent (line 31) | function optimizeSvgContent(svgText: string): string {

FILE: utils/ai/message-converter.ts
  function buildUserContentPartsFromPromptData (line 5) | function buildUserContentPartsFromPromptData(promptData: AIPromptData): ...
  function convertMessagesToModelMessages (line 62) | async function convertMessagesToModelMessages(

FILE: utils/ai/messages.ts
  function filterMessagesToDisplay (line 3) | function filterMessagesToDisplay(messages: ChatMessage[]): ChatMessage[] {
  function getUserMessages (line 12) | function getUserMessages(messages: ChatMessage[]): ChatMessage[] {
  function getLastUserMessage (line 16) | function getLastUserMessage(messages: ChatMessage[]): ChatMessage | unde...
  function getAssistantMessages (line 20) | function getAssistantMessages(messages: ChatMessage[]): ChatMessage[] {
  function getLastAssistantMessage (line 24) | function getLastAssistantMessage(messages: ChatMessage[]): ChatMessage |...

FILE: utils/ai/prompts.ts
  constant PROMPTS (line 1) | const PROMPTS = {
  type RemixPrompt (line 19) | interface RemixPrompt {
  type Prompt (line 25) | interface Prompt {
  constant CREATE_PROMPTS (line 30) | const CREATE_PROMPTS: Prompt[] = [
  constant REMIX_PROMPTS (line 53) | const REMIX_PROMPTS: RemixPrompt[] = [
  constant VARIANT_PROMPTS (line 71) | const VARIANT_PROMPTS: Prompt[] = [

FILE: utils/apply-style-to-element.ts
  function applyStyleToElement (line 1) | function applyStyleToElement(

FILE: utils/apply-theme.ts
  type Theme (line 8) | type Theme = "dark" | "light";
  constant COMMON_NON_COLOR_KEYS (line 10) | const COMMON_NON_COLOR_KEYS = COMMON_STYLES;

FILE: utils/contrast-checker.ts
  function getLuminance (line 8) | function getLuminance(colorValue: string): number {
  function getContrastRatio (line 30) | function getContrastRatio(color1: string, color2: string): string {

FILE: utils/debounce.ts
  function debounce (line 1) | function debounce<T extends (...args: any[]) => void>(

FILE: utils/fonts/google-fonts.ts
  constant GOOGLE_FONTS_API_URL (line 3) | const GOOGLE_FONTS_API_URL = "https://www.googleapis.com/webfonts/v1/web...
  function fetchGoogleFonts (line 5) | async function fetchGoogleFonts(googleFontsApiKey: string | undefined): ...
  function buildFontCssUrl (line 34) | function buildFontCssUrl(family: string, weights: string[] = ["400"]): s...
  function loadGoogleFont (line 42) | function loadGoogleFont(family: string, weights: string[] = ["400", "700...

FILE: utils/fonts/index.ts
  constant FONT_CATEGORIES (line 4) | const FONT_CATEGORIES = {
  constant FALLBACK_FONTS (line 30) | const FALLBACK_FONTS: FontInfo[] = [
  function buildFontFamily (line 182) | function buildFontFamily(fontFamily: string, category: FontCategory): st...
  function extractFontFamily (line 188) | function extractFontFamily(fontFamilyValue: string): string | null {
  function getDefaultWeights (line 203) | function getDefaultWeights(variants: string[]): string[] {
  function isFontLoaded (line 229) | function isFontLoaded(family: string, weight = "400"): boolean {
  function waitForFont (line 237) | async function waitForFont(
  constant SYSTEM_FONTS (line 259) | const SYSTEM_FONTS = [
  constant SYSTEM_FONTS_FALLBACKS (line 271) | const SYSTEM_FONTS_FALLBACKS = {

FILE: utils/format.ts
  function formatCompactNumber (line 4) | function formatCompactNumber(num: number): string {

FILE: utils/parse-css-input.ts
  constant VARIABLE_PREFIX (line 7) | const VARIABLE_PREFIX = "--";

FILE: utils/registry/tailwind-colors.ts
  constant TAILWIND_PALETTE (line 4) | const TAILWIND_PALETTE = {
  constant TAILWIND_SHADES (line 302) | const TAILWIND_SHADES: ValidTailwindShade[] = [

FILE: utils/registry/v0.ts
  type FontConfig (line 7) | type FontConfig = {
  function extractFontFamily (line 13) | function extractFontFamily(fontFamilyValue: string): string | null {
  function toVariableName (line 21) | function toVariableName(fontFamily: string): string {
  function toCssVariable (line 29) | function toCssVariable(fontFamily: string): string {
  function extractGoogleFonts (line 34) | function extractGoogleFonts(themeStyles: ThemeStyles): FontConfig[] {
  function formatColor (line 58) | function formatColor(color: string): string {
  function generateColorVariables (line 62) | function generateColorVariables(styles: ThemeStyleProps): string {
  function generateV0GlobalsCss (line 98) | function generateV0GlobalsCss(themeStyles: ThemeStyles): string {
  function generateV0LayoutTsx (line 209) | function generateV0LayoutTsx(themeStyles: ThemeStyles): string {
  function generateV0PageTsx (line 253) | function generateV0PageTsx(themeName: string): string {
  type V0RegistryFile (line 359) | type V0RegistryFile = {
  type V0RegistryPayload (line 366) | type V0RegistryPayload = {
  function generateV0RegistryPayload (line 372) | function generateV0RegistryPayload(

FILE: utils/shadows.ts
  function setShadowVariables (line 68) | function setShadowVariables(themeEditorState: ThemeEditorState) {

FILE: utils/subscription.ts
  type Feature (line 3) | type Feature = {
  constant FREE_SUB_FEATURES (line 8) | const FREE_SUB_FEATURES: Feature[] = [
  constant PRO_SUB_FEATURES (line 18) | const PRO_SUB_FEATURES: Feature[] = [

FILE: utils/theme-preset-helper.ts
  function getBuiltInThemeStyles (line 11) | function getBuiltInThemeStyles(name: string): { name: string; styles: Th...
  function mergePresetWithDefaults (line 24) | function mergePresetWithDefaults(presetStyles: {
  function getPresetThemeStyles (line 42) | function getPresetThemeStyles(name: string): ThemeStyles {

FILE: utils/theme-style-generator.ts
  type ThemeMode (line 9) | type ThemeMode = "light" | "dark";
  constant FONT_SLOTS (line 362) | const FONT_SLOTS = [

FILE: utils/theme-styles.ts
  function mergeThemeStylesWithDefaults (line 4) | function mergeThemeStylesWithDefaults(themeStyles: ThemeStyles) {

FILE: utils/try-catch.ts
  function tryCatch (line 1) | async function tryCatch<T, E = Error>(promise: T | Promise<T>) {
Condensed preview — 450 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,032K chars).
[
  {
    "path": ".dockerignore",
    "chars": 247,
    "preview": "# Ignore node_modules — let Docker install fresh inside the image\nnode_modules\n\n# Ignore environment files (not needed i"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 65,
    "preview": "# These are supported funding model platforms\n\ngithub: [jnsahaj]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 834,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".gitignore",
    "chars": 554,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\n/.pnp"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 13,
    "preview": "npm run lint\n"
  },
  {
    "path": ".prettierignore",
    "chars": 411,
    "preview": "# Builds\n.next/\ndist/\nbuild/\nout/\n\n# Dependencies\nnode_modules/\n\n# Coverage and tests\ncoverage/\n.nyc_output/\n\n# Misc\n.gi"
  },
  {
    "path": ".prettierrc",
    "chars": 256,
    "preview": "{\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"trailingComma\": \"es5\",\n  \"tabWidth\": 2,\n  \"printWidth\": 100,\n  \"bracketSpac"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 7654,
    "preview": "# Contributing to tweakcn.com\n\nThanks for your interest in contributing to tweakcn.com! We're excited to have you here.\n"
  },
  {
    "path": "Dockerfile",
    "chars": 123,
    "preview": "FROM node:20-alpine\n\nWORKDIR /app\n\nCOPY package*.json ./\nRUN npm install\n\nCOPY . .\n\nEXPOSE 3000\n\nCMD [\"npm\", \"run\", \"dev"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 2727,
    "preview": "<div align=\"center\">\n  <h1>tweakcn.com</h1>\n</div>\n\n<div align=\"center\">\n  <a href=\"https://vercel.com/oss\">\n    <img al"
  },
  {
    "path": "actions/account.ts",
    "chars": 1392,
    "preview": "\"use server\";\n\nimport { db } from \"@/db\";\nimport { user as userTable, subscription } from \"@/db/schema\";\nimport { eq } f"
  },
  {
    "path": "actions/ai-usage.ts",
    "chars": 6816,
    "preview": "\"use server\";\n\nimport { db } from \"@/db\";\nimport { aiUsage } from \"@/db/schema\";\nimport { getCurrentUserId } from \"@/lib"
  },
  {
    "path": "actions/checkout.ts",
    "chars": 718,
    "preview": "\"use server\";\n\nimport { polar } from \"@/lib/polar\";\nimport { getCurrentUser, logError } from \"@/lib/shared\";\nimport { ge"
  },
  {
    "path": "actions/community-themes.ts",
    "chars": 18115,
    "preview": "\"use server\";\n\nimport { z } from \"zod\";\nimport { db } from \"@/db\";\nimport {\n  communityTheme,\n  communityThemeTag,\n  the"
  },
  {
    "path": "actions/customer.ts",
    "chars": 746,
    "preview": "import \"server-only\";\n\nimport { polar } from \"@/lib/polar\";\nimport { logError } from \"@/lib/shared\";\nimport { Customer }"
  },
  {
    "path": "actions/themes.ts",
    "chars": 6403,
    "preview": "\"use server\";\n\nimport { z } from \"zod\";\nimport { db } from \"@/db\";\nimport { theme as themeTable, communityTheme } from \""
  },
  {
    "path": "app/(auth)/components/auth-dialog.tsx",
    "chars": 6558,
    "preview": "\"use client\";\n\nimport Github from \"@/assets/github.svg\";\nimport Google from \"@/assets/google.svg\";\nimport { Button } fro"
  },
  {
    "path": "app/(legal)/layout.tsx",
    "chars": 412,
    "preview": "import React from \"react\";\nimport { Header } from \"@/components/header\";\nimport { Footer } from \"@/components/footer\";\n\n"
  },
  {
    "path": "app/(legal)/privacy-policy/page.tsx",
    "chars": 5723,
    "preview": "import { Metadata } from \"next\";\n\nexport const metadata: Metadata = {\n  title: \"Privacy Policy | tweakcn\",\n  description"
  },
  {
    "path": "app/ai/components/ai-announcement.tsx",
    "chars": 957,
    "preview": "\"use client\";\n\nimport { useSubscription } from \"@/hooks/use-subscription\";\nimport { ArrowRight } from \"lucide-react\";\nim"
  },
  {
    "path": "app/ai/components/ai-chat-form.tsx",
    "chars": 6016,
    "preview": "\"use client\";\n\nimport { AIChatFormBody } from \"@/components/editor/ai/ai-chat-form-body\";\nimport { AlertBanner } from \"@"
  },
  {
    "path": "app/ai/components/ai-chat-hero.tsx",
    "chars": 2474,
    "preview": "\"use client\";\n\nimport { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { useChatContext } fro"
  },
  {
    "path": "app/ai/components/chat-heading.tsx",
    "chars": 667,
    "preview": "export function ChatHeading({ isGeneratingTheme }: { isGeneratingTheme: boolean }) {\n  return (\n    <h1\n      style={\n  "
  },
  {
    "path": "app/ai/components/community-theme-card.tsx",
    "chars": 5248,
    "preview": "\"use client\";\n\nimport Logo from \"@/assets/logo.svg\";\nimport { Button } from \"@/components/ui/button\";\nimport { Skeleton "
  },
  {
    "path": "app/ai/components/community-themes.tsx",
    "chars": 2205,
    "preview": "import { Button } from \"@/components/ui/button\";\nimport { ChevronRight } from \"lucide-react\";\nimport { Suspense } from \""
  },
  {
    "path": "app/ai/components/suggested-pill-actions.tsx",
    "chars": 2631,
    "preview": "\"use client\";\n\nimport { PillActionButton } from \"@/components/editor/ai/pill-action-button\";\nimport { useImageUpload } f"
  },
  {
    "path": "app/ai/layout.tsx",
    "chars": 323,
    "preview": "import { Header } from \"@/components/header\";\n\nexport default function AiLayout({ children }: { children: React.ReactNod"
  },
  {
    "path": "app/ai/loading.tsx",
    "chars": 130,
    "preview": "import { Loading } from \"@/components/loading\";\n\nexport default function AiLoading() {\n  return <Loading className=\"flex"
  },
  {
    "path": "app/ai/page.tsx",
    "chars": 1239,
    "preview": "import { type Metadata } from \"next\";\nimport { AIAnnouncement } from \"./components/ai-announcement\";\nimport { AIChatHero"
  },
  {
    "path": "app/api/auth/[...all]/route.ts",
    "chars": 151,
    "preview": "import { auth } from \"@/lib/auth\";\nimport { toNextJsHandler } from \"better-auth/next-js\";\n\nexport const { GET, POST } = "
  },
  {
    "path": "app/api/enhance-prompt/route.ts",
    "chars": 1314,
    "preview": "import { ENHANCE_PROMPT_SYSTEM } from \"@/lib/ai/prompts\";\nimport { baseProviderOptions, myProvider } from \"@/lib/ai/prov"
  },
  {
    "path": "app/api/generate-theme/route.ts",
    "chars": 3965,
    "preview": "import { recordAIUsage } from \"@/actions/ai-usage\";\nimport { THEME_GENERATION_TOOLS } from \"@/lib/ai/generate-theme/tool"
  },
  {
    "path": "app/api/google-fonts/route.ts",
    "chars": 1865,
    "preview": "import { PaginatedFontsResponse } from \"@/types/fonts\";\nimport { FALLBACK_FONTS } from \"@/utils/fonts\";\nimport { fetchGo"
  },
  {
    "path": "app/api/oauth/app-info/route.ts",
    "chars": 756,
    "preview": "import { db } from \"@/db\";\nimport { oauthApp } from \"@/db/schema\";\nimport { oauthError } from \"@/lib/oauth\";\nimport { eq"
  },
  {
    "path": "app/api/oauth/authorize/route.ts",
    "chars": 3147,
    "preview": "import { db } from \"@/db\";\nimport { oauthApp, oauthAuthorizationCode } from \"@/db/schema\";\nimport { OAUTH_AUTHORIZATION_"
  },
  {
    "path": "app/api/oauth/revoke/route.ts",
    "chars": 1156,
    "preview": "import { db } from \"@/db\";\nimport { oauthToken } from \"@/db/schema\";\nimport { hashToken, oauthError } from \"@/lib/oauth\""
  },
  {
    "path": "app/api/oauth/token/route.ts",
    "chars": 5155,
    "preview": "import { db } from \"@/db\";\nimport { oauthAuthorizationCode, oauthToken } from \"@/db/schema\";\nimport {\n  authenticateClie"
  },
  {
    "path": "app/api/oauth/userinfo/route.ts",
    "chars": 1262,
    "preview": "import { db } from \"@/db\";\nimport { user as userTable } from \"@/db/schema\";\nimport { oauthError, requireScope, resolveUs"
  },
  {
    "path": "app/api/subscription/route.ts",
    "chars": 761,
    "preview": "import { getCurrentUserId, logError } from \"@/lib/shared\";\nimport { validateSubscriptionAndUsage } from \"@/lib/subscript"
  },
  {
    "path": "app/api/v1/me/route.ts",
    "chars": 730,
    "preview": "import { db } from \"@/db\";\nimport { user as userTable } from \"@/db/schema\";\nimport { oauthError, requireAuth } from \"@/l"
  },
  {
    "path": "app/api/v1/themes/[themeId]/route.ts",
    "chars": 928,
    "preview": "import { db } from \"@/db\";\nimport { theme as themeTable } from \"@/db/schema\";\nimport { oauthError, requireAuth } from \"@"
  },
  {
    "path": "app/api/v1/themes/route.ts",
    "chars": 674,
    "preview": "import { db } from \"@/db\";\nimport { theme as themeTable } from \"@/db/schema\";\nimport { requireAuth } from \"@/lib/oauth\";"
  },
  {
    "path": "app/api/webhook/polar/route.ts",
    "chars": 4167,
    "preview": "import { db } from \"@/db\";\nimport { subscription } from \"@/db/schema\";\nimport { Webhooks } from \"@polar-sh/nextjs\";\n\nfun"
  },
  {
    "path": "app/community/components/community-sidebar.tsx",
    "chars": 3158,
    "preview": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { useCommunityTagCounts } from \"@/hooks/themes\";\nimport { useSes"
  },
  {
    "path": "app/community/components/community-theme-card.tsx",
    "chars": 4379,
    "preview": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Avatar, AvatarImage, AvatarFallback } from \"@/com"
  },
  {
    "path": "app/community/components/community-theme-preview-dialog.tsx",
    "chars": 8467,
    "preview": "\"use client\";\n\nimport { lazy, useEffect, useCallback } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport"
  },
  {
    "path": "app/community/components/community-themes-content.tsx",
    "chars": 12651,
    "preview": "\"use client\";\n\nimport { useCommunityThemes } from \"@/hooks/themes\";\nimport { useEditorStore } from \"@/store/editor-store"
  },
  {
    "path": "app/community/layout.tsx",
    "chars": 361,
    "preview": "import { Footer } from \"@/components/footer\";\nimport { Header } from \"@/components/header\";\n\nexport default function Com"
  },
  {
    "path": "app/community/page.tsx",
    "chars": 864,
    "preview": "import { Metadata } from \"next\";\nimport { CommunityThemesContent } from \"./components/community-themes-content\";\nimport "
  },
  {
    "path": "app/dashboard/layout.tsx",
    "chars": 355,
    "preview": "import { Footer } from \"@/components/footer\";\nimport { Header } from \"@/components/header\";\n\nexport default function Das"
  },
  {
    "path": "app/dashboard/loading.tsx",
    "chars": 137,
    "preview": "import { Loading } from \"@/components/loading\";\n\nexport default function DashboardLoading() {\n  return <Loading classNam"
  },
  {
    "path": "app/dashboard/page.tsx",
    "chars": 172,
    "preview": "import { redirect } from \"next/navigation\";\n\n// This page is being moved to settings/themes\nexport default function Dash"
  },
  {
    "path": "app/editor/theme/[[...themeId]]/layout.tsx",
    "chars": 339,
    "preview": "import { Header } from \"@/components/header\";\n\nexport default function EditorLayout({ children }: { children: React.Reac"
  },
  {
    "path": "app/editor/theme/[[...themeId]]/loading.tsx",
    "chars": 134,
    "preview": "import { Loading } from \"@/components/loading\";\n\nexport default function EditorLoading() {\n  return <Loading className=\""
  },
  {
    "path": "app/editor/theme/[[...themeId]]/page.tsx",
    "chars": 633,
    "preview": "import { getTheme } from \"@/actions/themes\";\nimport Editor from \"@/components/editor/editor\";\nimport { Metadata } from \""
  },
  {
    "path": "app/figma/layout.tsx",
    "chars": 1694,
    "preview": "import { Metadata } from \"next\";\n\nexport const metadata: Metadata = {\n  title: \"Apply Your tweakcn Theme to Shadcraft Fi"
  },
  {
    "path": "app/figma/page.tsx",
    "chars": 10292,
    "preview": "\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { FigmaHeader } from \"@/components/figma-header\";\nimp"
  },
  {
    "path": "app/globals.css",
    "chars": 4492,
    "preview": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n@import \"streamdown/styles.css\";\n@import \"./loaders.css\";\n\n@source \"../"
  },
  {
    "path": "app/layout.tsx",
    "chars": 5414,
    "preview": "import { AuthDialogWrapper } from \"@/components/auth-dialog-wrapper\";\nimport { DynamicFontLoader } from \"@/components/dy"
  },
  {
    "path": "app/loaders.css",
    "chars": 1742,
    "preview": "/* keyframes for loaders */\n@theme {\n  @keyframes typing {\n    0%,\n    100% {\n      transform: translateY(0);\n      opac"
  },
  {
    "path": "app/not-found.tsx",
    "chars": 2233,
    "preview": "\"use client\";\n\nimport { ThemePresetButtons } from \"@/components/home/theme-preset-buttons\";\nimport { useTheme } from \"@/"
  },
  {
    "path": "app/oauth/authorize/page.tsx",
    "chars": 4614,
    "preview": "\"use client\";\n\nimport Github from \"@/assets/github.svg\";\nimport Google from \"@/assets/google.svg\";\nimport { Button } fro"
  },
  {
    "path": "app/page.tsx",
    "chars": 1629,
    "preview": "\"use client\";\n\nimport { Footer } from \"@/components/footer\";\nimport { AIGenerationCTA } from \"@/components/home/ai-gener"
  },
  {
    "path": "app/pricing/components/checkout-button.tsx",
    "chars": 2476,
    "preview": "\"use client\";\n\nimport { createCheckout } from \"@/actions/checkout\";\nimport { Button } from \"@/components/ui/button\";\nimp"
  },
  {
    "path": "app/pricing/layout.tsx",
    "chars": 356,
    "preview": "import { Footer } from \"@/components/footer\";\nimport { Header } from \"@/components/header\";\n\nexport default function Pri"
  },
  {
    "path": "app/pricing/page.tsx",
    "chars": 10167,
    "preview": "import { NoiseEffect } from \"@/components/effects/noise-effect\";\nimport {\n  Accordion,\n  AccordionContent,\n  AccordionIt"
  },
  {
    "path": "app/r/themes/[id]/route.ts",
    "chars": 2342,
    "preview": "import { NextResponse } from \"next/server\";\n\nimport { getTheme } from \"@/actions/themes\";\nimport { generateThemeRegistry"
  },
  {
    "path": "app/r/v0/[id]/route.ts",
    "chars": 1661,
    "preview": "import { NextResponse } from \"next/server\";\nimport { getTheme } from \"@/actions/themes\";\nimport { generateV0RegistryPayl"
  },
  {
    "path": "app/settings/account/components/delete-account-section.tsx",
    "chars": 4268,
    "preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { usePostHog } from"
  },
  {
    "path": "app/settings/account/page.tsx",
    "chars": 609,
    "preview": "import { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\ni"
  },
  {
    "path": "app/settings/components/customer-portal-link.tsx",
    "chars": 777,
    "preview": "import { auth } from \"@/lib/auth\";\nimport { polar } from \"@/lib/polar\";\nimport { cn } from \"@/lib/utils\";\nimport { heade"
  },
  {
    "path": "app/settings/components/settings-header.tsx",
    "chars": 332,
    "preview": "export function SettingsHeader({ title, description }: { title: string; description?: string }) {\n  return (\n    <div cl"
  },
  {
    "path": "app/settings/components/settings-sidebar.tsx",
    "chars": 2356,
    "preview": "\"use client\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { useSubscription } from \"@/hooks/use-subsc"
  },
  {
    "path": "app/settings/components/theme-card.tsx",
    "chars": 11465,
    "preview": "\"use client\";\n\nimport { Theme } from \"@/types/theme\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card } fro"
  },
  {
    "path": "app/settings/components/themes-list.tsx",
    "chars": 3405,
    "preview": "\"use client\";\n\nimport { Card } from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  Se"
  },
  {
    "path": "app/settings/components/usage-stats.tsx",
    "chars": 4994,
    "preview": "\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { Card, CardContent, CardHeader } from \"@/components/"
  },
  {
    "path": "app/settings/components/user-info.tsx",
    "chars": 873,
    "preview": "\"use client\";\n\nimport { useSubscription } from \"@/hooks/use-subscription\";\nimport { authClient } from \"@/lib/auth-client"
  },
  {
    "path": "app/settings/layout.tsx",
    "chars": 1068,
    "preview": "import { Header } from \"@/components/header\";\nimport { ThemeToggle } from \"@/components/theme-toggle\";\nimport { Button }"
  },
  {
    "path": "app/settings/page.tsx",
    "chars": 329,
    "preview": "import { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\n\n"
  },
  {
    "path": "app/settings/portal/route.ts",
    "chars": 493,
    "preview": "import { polar } from \"@/lib/polar\";\nimport { getCurrentUserId } from \"@/lib/shared\";\nimport { CustomerPortal } from \"@p"
  },
  {
    "path": "app/settings/themes/page.tsx",
    "chars": 1846,
    "preview": "import { getThemes } from \"@/actions/themes\";\nimport { ThemesList } from \"@/app/settings/components/themes-list\";\nimport"
  },
  {
    "path": "app/settings/usage/page.tsx",
    "chars": 579,
    "preview": "import { UsageStats } from \"@/app/settings/components/usage-stats\";\nimport { auth } from \"@/lib/auth\";\nimport { headers "
  },
  {
    "path": "app/sitemap.ts",
    "chars": 959,
    "preview": "import { MetadataRoute } from \"next\";\n\nexport default function sitemap(): MetadataRoute.Sitemap {\n  const baseUrl = proc"
  },
  {
    "path": "app/success/layout.tsx",
    "chars": 229,
    "preview": "export default function SuccessLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"flex"
  },
  {
    "path": "app/success/page.tsx",
    "chars": 2647,
    "preview": "import { NoiseEffect } from \"@/components/effects/noise-effect\";\nimport { Button } from \"@/components/ui/button\";\nimport"
  },
  {
    "path": "app/themes/[themeId]/error.tsx",
    "chars": 920,
    "preview": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport Link from \"next/link\";\nimport { Button } from \"@/components/ui/"
  },
  {
    "path": "app/themes/[themeId]/layout.tsx",
    "chars": 351,
    "preview": "import { Footer } from \"@/components/footer\";\nimport { Header } from \"@/components/header\";\n\nexport default function The"
  },
  {
    "path": "app/themes/[themeId]/loading.tsx",
    "chars": 133,
    "preview": "import { Loading } from \"@/components/loading\";\n\nexport default function ThemeLoading() {\n  return <Loading className=\"f"
  },
  {
    "path": "app/themes/[themeId]/not-found.tsx",
    "chars": 640,
    "preview": "import Link from \"next/link\";\nimport { Button } from \"@/components/ui/button\";\n\nexport default function ThemeNotFound() "
  },
  {
    "path": "app/themes/[themeId]/opengraph-image.alt.txt",
    "chars": 26,
    "preview": "Theme preview for tweakcn "
  },
  {
    "path": "app/themes/[themeId]/opengraph-image.tsx",
    "chars": 3408,
    "preview": "import { ImageResponse } from \"next/og\";\nimport { getTheme } from \"@/actions/themes\";\n\n// Image metadata\nexport const si"
  },
  {
    "path": "app/themes/[themeId]/page.tsx",
    "chars": 1646,
    "preview": "import { getTheme } from \"@/actions/themes\";\nimport { getCommunityDataForTheme } from \"@/actions/community-themes\";\nimpo"
  },
  {
    "path": "components/ai-elements/code-block.tsx",
    "chars": 3272,
    "preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { CheckIcon, Co"
  },
  {
    "path": "components/ai-elements/conversation.tsx",
    "chars": 1564,
    "preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { ArrowDownIcon"
  },
  {
    "path": "components/ai-elements/response.tsx",
    "chars": 618,
    "preview": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { type ComponentProps, memo } from \"react\";\nimport { Streamdown "
  },
  {
    "path": "components/auth-dialog-wrapper.tsx",
    "chars": 1333,
    "preview": "\"use client\";\n\nimport { AuthDialog } from \"@/app/(auth)/components/auth-dialog\";\nimport { useAuthStore } from \"@/store/a"
  },
  {
    "path": "components/block-viewer.tsx",
    "chars": 5562,
    "preview": "\"use client\";\nimport { Monitor, Smartphone, Tablet } from \"lucide-react\";\nimport React from \"react\";\nimport { PanelImper"
  },
  {
    "path": "components/copy-button.tsx",
    "chars": 1027,
    "preview": "\"use client\";\n\nimport { useCopyToClipboard } from \"@/hooks/use-copy-to-clipboard\";\nimport { cn } from \"@/lib/utils\";\nimp"
  },
  {
    "path": "components/debug-button.tsx",
    "chars": 656,
    "preview": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {"
  },
  {
    "path": "components/dynamic-font-loader.tsx",
    "chars": 1264,
    "preview": "\"use client\";\n\nimport { useMounted } from \"@/hooks/use-mounted\";\nimport { useEditorStore } from \"@/store/editor-store\";\n"
  },
  {
    "path": "components/dynamic-website-preview.tsx",
    "chars": 20961,
    "preview": "\"use client\";\n\nimport Logo from \"@/assets/logo.svg\";\nimport { CodeBlock, CodeBlockCopyButton } from \"@/components/ai-ele"
  },
  {
    "path": "components/editor/action-bar/action-bar.tsx",
    "chars": 864,
    "preview": "\"use client\";\n\nimport { ActionBarButtons } from \"@/components/editor/action-bar/components/action-bar-buttons\";\nimport {"
  },
  {
    "path": "components/editor/action-bar/components/action-bar-buttons.tsx",
    "chars": 3010,
    "preview": "import { Separator } from \"@/components/ui/separator\";\nimport { useAIThemeGenerationCore } from \"@/hooks/use-ai-theme-ge"
  },
  {
    "path": "components/editor/action-bar/components/ai-generate-button.tsx",
    "chars": 964,
    "preview": "import { Sparkles } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Tooltip,\n  TooltipC"
  },
  {
    "path": "components/editor/action-bar/components/code-button.tsx",
    "chars": 620,
    "preview": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {"
  },
  {
    "path": "components/editor/action-bar/components/import-button.tsx",
    "chars": 637,
    "preview": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {"
  },
  {
    "path": "components/editor/action-bar/components/mcp-dialog.tsx",
    "chars": 4395,
    "preview": "import {\n  Tabs,\n  TabsContent,\n  TabsIndicator,\n  TabsList,\n  TabsTrigger,\n} from \"@/components/ui/base-ui-tabs\";\nimpor"
  },
  {
    "path": "components/editor/action-bar/components/more-options.tsx",
    "chars": 1666,
    "preview": "import McpIcon from \"@/assets/mcp.svg\";\nimport ContrastChecker from \"@/components/editor/contrast-checker\";\nimport { But"
  },
  {
    "path": "components/editor/action-bar/components/publish-button.tsx",
    "chars": 3284,
    "preview": "\"use client\";\n\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/bu"
  },
  {
    "path": "components/editor/action-bar/components/reset-button.tsx",
    "chars": 639,
    "preview": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {"
  },
  {
    "path": "components/editor/action-bar/components/save-button.tsx",
    "chars": 804,
    "preview": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {"
  },
  {
    "path": "components/editor/action-bar/components/share-button.tsx",
    "chars": 905,
    "preview": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {"
  },
  {
    "path": "components/editor/action-bar/components/theme-toggle.tsx",
    "chars": 1623,
    "preview": "import { useTheme } from \"@/components/theme-provider\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\ni"
  },
  {
    "path": "components/editor/action-bar/components/undo-redo-buttons.tsx",
    "chars": 1086,
    "preview": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {"
  },
  {
    "path": "components/editor/ai/ai-chat-form-body.tsx",
    "chars": 3189,
    "preview": "\"use client\";\n\nimport { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { AI_PROMPT_CHARACTER_"
  },
  {
    "path": "components/editor/ai/alert-banner.tsx",
    "chars": 3346,
    "preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { useSubscription } from \"@/hooks/use-subscriptio"
  },
  {
    "path": "components/editor/ai/chat-image-preview.tsx",
    "chars": 1997,
    "preview": "import {\n  ResponsiveDialog,\n  ResponsiveDialogContent,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n  ResponsiveD"
  },
  {
    "path": "components/editor/ai/chat-input.tsx",
    "chars": 7601,
    "preview": "\"use client\";\n\nimport { Loader } from \"@/components/loader\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrappe"
  },
  {
    "path": "components/editor/ai/chat-interface.tsx",
    "chars": 4750,
    "preview": "\"use client\";\n\nimport { useChatContext } from \"@/hooks/use-chat-context\";\nimport { useAIThemeGenerationCore } from \"@/ho"
  },
  {
    "path": "components/editor/ai/chat-theme-preview.tsx",
    "chars": 5653,
    "preview": "import { Loader } from \"@/components/loader\";\nimport { useTheme } from \"@/components/theme-provider\";\nimport { Button } "
  },
  {
    "path": "components/editor/ai/closeable-suggested-pill-actions.tsx",
    "chars": 1767,
    "preview": "\"use client\";\n\nimport { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { Button } from \"@/com"
  },
  {
    "path": "components/editor/ai/drag-and-drop-image-uploader.tsx",
    "chars": 1313,
    "preview": "import { cn } from \"@/lib/utils\";\nimport { Upload } from \"lucide-react\";\nimport { useDropzone } from \"react-dropzone\";\n\n"
  },
  {
    "path": "components/editor/ai/enhance-prompt-button.tsx",
    "chars": 950,
    "preview": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {"
  },
  {
    "path": "components/editor/ai/image-uploader.tsx",
    "chars": 1721,
    "preview": "import { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/button\";\nimport {"
  },
  {
    "path": "components/editor/ai/loading-logo.tsx",
    "chars": 838,
    "preview": "import Logo from \"@/assets/logo.svg\";\nimport { Sparkle } from \"lucide-react\";\n\nexport function LoadingLogo() {\n  return "
  },
  {
    "path": "components/editor/ai/message-actions.tsx",
    "chars": 2454,
    "preview": "import { CopyButton } from \"@/components/copy-button\";\nimport { DebugButton } from \"@/components/debug-button\";\nimport {"
  },
  {
    "path": "components/editor/ai/message-edit-form.tsx",
    "chars": 4985,
    "preview": "import { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { TooltipWrapper } from \"@/components"
  },
  {
    "path": "components/editor/ai/message.tsx",
    "chars": 7026,
    "preview": "import Logo from \"@/assets/logo.svg\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { cn } from \"@/li"
  },
  {
    "path": "components/editor/ai/messages.tsx",
    "chars": 6878,
    "preview": "import Logo from \"@/assets/logo.svg\";\nimport {\n  Conversation,\n  ConversationContent,\n  ConversationScrollButton,\n} from"
  },
  {
    "path": "components/editor/ai/no-messages-placeholder.tsx",
    "chars": 4346,
    "preview": "import { HorizontalScrollArea } from \"@/components/horizontal-scroll-area\";\nimport { Button } from \"@/components/ui/butt"
  },
  {
    "path": "components/editor/ai/pill-action-button.tsx",
    "chars": 1199,
    "preview": "import { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { ComponentProps } from \"react"
  },
  {
    "path": "components/editor/ai/stream-text.tsx",
    "chars": 480,
    "preview": "import { Response } from \"@/components/ai-elements/response\";\n\ninterface StreamTextProps {\n  text: string;\n  animate?: b"
  },
  {
    "path": "components/editor/ai/uploaded-image-preview.tsx",
    "chars": 2223,
    "preview": "\"use client\";\n\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nimport { Button } from \"@/components/ui/bu"
  },
  {
    "path": "components/editor/code-panel-dialog.tsx",
    "chars": 1296,
    "preview": "import CodePanel from \"./code-panel\";\nimport { ThemeEditorState } from \"@/types/editor\";\nimport {\n  ResponsiveDialog,\n  "
  },
  {
    "path": "components/editor/code-panel.tsx",
    "chars": 11438,
    "preview": "import { useMemo, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Copy, Check, Heart "
  },
  {
    "path": "components/editor/color-picker.tsx",
    "chars": 4224,
    "preview": "import { DEBOUNCE_DELAY } from \"@/lib/constants\";\nimport { cn } from \"@/lib/utils\";\nimport { useColorControlFocus } from"
  },
  {
    "path": "components/editor/color-selector-popover.tsx",
    "chars": 7625,
    "preview": "\"use client\";\n\nimport TailwindCSS from \"@/components/icons/tailwind-css\";\nimport { TooltipWrapper } from \"@/components/t"
  },
  {
    "path": "components/editor/colors-tab-content.tsx",
    "chars": 9755,
    "preview": "\"use client\";\n\nimport React, { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { RefreshCw, Search, X }"
  },
  {
    "path": "components/editor/contrast-checker.tsx",
    "chars": 14679,
    "preview": "import { useTheme } from \"@/components/theme-provider\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card, Ca"
  },
  {
    "path": "components/editor/control-section.tsx",
    "chars": 2011,
    "preview": "import React, { useState } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n"
  },
  {
    "path": "components/editor/css-import-dialog.tsx",
    "chars": 3846,
    "preview": "import { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { Button } from \"@/components/ui/button\";\nimport"
  },
  {
    "path": "components/editor/custom-textarea.tsx",
    "chars": 6023,
    "preview": "\"use client\";\n\nimport { suggestion } from \"@/components/editor/mention-suggestion\";\nimport { toast } from \"@/hooks/use-t"
  },
  {
    "path": "components/editor/editor.tsx",
    "chars": 4735,
    "preview": "\"use client\";\n\nimport { ResizableHandle, ResizablePanel, ResizablePanelGroup } from \"@/components/ui/resizable\";\nimport "
  },
  {
    "path": "components/editor/font-picker.tsx",
    "chars": 18161,
    "preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  Co"
  },
  {
    "path": "components/editor/hsl-adjustment-controls.tsx",
    "chars": 8729,
    "preview": "\"use client\";\n\nimport React, { useCallback, useMemo, useRef, useEffect, useState } from \"react\";\nimport { SliderWithInpu"
  },
  {
    "path": "components/editor/hsl-preset-button.tsx",
    "chars": 2466,
    "preview": "\"use client\";\n\nimport type React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui"
  },
  {
    "path": "components/editor/inspector-class-item.tsx",
    "chars": 2814,
    "preview": "\"use client\";\n\nimport React, { memo, useCallback, useMemo } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Squ"
  },
  {
    "path": "components/editor/inspector-overlay.tsx",
    "chars": 3408,
    "preview": "\"use client\";\n\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from \"@/components/ui/hover-card\";\nimport { Sepa"
  },
  {
    "path": "components/editor/mention-list.tsx",
    "chars": 3887,
    "preview": "\"use client\";\n\nimport React, { forwardRef, useEffect, useImperativeHandle, useState, useRef } from \"react\";\nimport { cn "
  },
  {
    "path": "components/editor/mention-suggestion.ts",
    "chars": 2534,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { ReactRenderer } from \"@tiptap/react\";\nimport tippy fro"
  },
  {
    "path": "components/editor/section-context.tsx",
    "chars": 555,
    "preview": "import { createContext } from \"react\";\n\ninterface SectionContextType {\n  /** Whether the parent ControlSection is curren"
  },
  {
    "path": "components/editor/shadow-control.tsx",
    "chars": 1845,
    "preview": "import React from \"react\";\nimport { SliderWithInput } from \"./slider-with-input\";\nimport ColorPicker from \"./color-picke"
  },
  {
    "path": "components/editor/share-dialog.tsx",
    "chars": 1853,
    "preview": "import { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  ResponsiveDia"
  },
  {
    "path": "components/editor/slider-with-input.tsx",
    "chars": 1959,
    "preview": "import { useEffect, useState } from \"react\";\nimport { Label } from \"../ui/label\";\nimport { Input } from \"../ui/input\";\ni"
  },
  {
    "path": "components/editor/theme-control-actions.tsx",
    "chars": 2471,
    "preview": "import { FileCode, Palette, RefreshCw, LucideIcon, Undo2 } from \"lucide-react\";\nimport { Button } from \"../ui/button\";\ni"
  },
  {
    "path": "components/editor/theme-control-panel.tsx",
    "chars": 11235,
    "preview": "\"use client\";\n\nimport { AlertCircle, Sparkle } from \"lucide-react\";\nimport React from \"react\";\n\nimport { ChatInterface }"
  },
  {
    "path": "components/editor/theme-font-select.tsx",
    "chars": 1513,
    "preview": "// THIS COMPONENT MIGHT BE REPLACED BY THE GOOGLE FONT PICKER\n\nimport React, { useMemo } from \"react\";\nimport {\n  Select"
  },
  {
    "path": "components/editor/theme-preset-select.tsx",
    "chars": 16327,
    "preview": "import { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Command, Comman"
  },
  {
    "path": "components/editor/theme-preview/color-preview.tsx",
    "chars": 8745,
    "preview": "import { CopyButton } from \"@/components/copy-button\";\nimport { TooltipWrapper } from \"@/components/tooltip-wrapper\";\nim"
  },
  {
    "path": "components/editor/theme-preview/components-showcase.tsx",
    "chars": 7531,
    "preview": "import { ThemeEditorPreviewProps } from \"@/types/theme\";\nimport { Settings, Info, AlertTriangle, Star } from \"lucide-rea"
  },
  {
    "path": "components/editor/theme-preview/examples-preview-container.tsx",
    "chars": 872,
    "preview": "import { Suspense } from \"react\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { cn } from \"@/lib/utils\";"
  },
  {
    "path": "components/editor/theme-preview/tabs-trigger-pill.tsx",
    "chars": 827,
    "preview": "import { TabsTrigger } from \"@/components/ui/tabs\";\nimport { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\nc"
  },
  {
    "path": "components/editor/theme-preview-panel.tsx",
    "chars": 11365,
    "preview": "\"use client\";\n\nimport ShadcnBlocksLogo from \"@/assets/shadcnblocks.svg\";\nimport { HorizontalScrollArea } from \"@/compone"
  },
  {
    "path": "components/editor/theme-save-dialog.tsx",
    "chars": 4847,
    "preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,"
  },
  {
    "path": "components/effects/frame-highlight.tsx",
    "chars": 1201,
    "preview": "import { cn } from \"@/lib/utils\";\nimport { ComponentProps } from \"react\";\n\nexport function FrameHighlight({ children, cl"
  },
  {
    "path": "components/effects/noise-effect.tsx",
    "chars": 462,
    "preview": "export function NoiseEffect() {\n  return (\n    <svg\n      className=\"pointer-events-none absolute inset-0 z-10 opacity-["
  },
  {
    "path": "components/effects/spotlight.tsx",
    "chars": 1408,
    "preview": "import { cn } from \"@/lib/utils\";\n\ntype SpotlightProps = {\n  className?: string;\n  fill?: string;\n};\n\nexport function Sp"
  },
  {
    "path": "components/error-boundary.tsx",
    "chars": 1167,
    "preview": "import { FileWarning } from \"lucide-react\";\nimport React from \"react\";\n\nexport class ComponentErrorBoundary extends Reac"
  },
  {
    "path": "components/examples/ai-chat-demo.tsx",
    "chars": 3915,
    "preview": "\"use client\";\n\nimport Message from \"@/components/editor/ai/message\";\nimport { ScrollArea } from \"@/components/ui/scroll-"
  },
  {
    "path": "components/examples/cards/activity-goal.tsx",
    "chars": 2601,
    "preview": "\"use client\";\n\nimport { MinusIcon, PlusIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Bar, BarChar"
  },
  {
    "path": "components/examples/cards/calendar.tsx",
    "chars": 573,
    "preview": "\"use client\";\n\nimport { addDays } from \"date-fns\";\n\nimport { Calendar } from \"@/components/ui/calendar\";\nimport { Card, "
  },
  {
    "path": "components/examples/cards/chat.tsx",
    "chars": 8066,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/ava"
  },
  {
    "path": "components/examples/cards/cookie-settings.tsx",
    "chars": 1679,
    "preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  Car"
  },
  {
    "path": "components/examples/cards/create-account.tsx",
    "chars": 5215,
    "preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  Car"
  },
  {
    "path": "components/examples/cards/date-picker-with-range.tsx",
    "chars": 2096,
    "preview": "\"use client\";\n\nimport { addDays, format } from \"date-fns\";\nimport { Calendar as CalendarIcon } from \"lucide-react\";\nimpo"
  },
  {
    "path": "components/examples/cards/exercise-minutes.tsx",
    "chars": 2637,
    "preview": "\"use client\";\n\nimport { CartesianGrid, Line, LineChart, XAxis } from \"recharts\";\n\nimport { Card, CardContent, CardDescri"
  },
  {
    "path": "components/examples/cards/forms.tsx",
    "chars": 4340,
    "preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  Car"
  },
  {
    "path": "components/examples/cards/github-card.tsx",
    "chars": 1562,
    "preview": "\"use client\";\n\nimport { Circle, Star } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { C"
  },
  {
    "path": "components/examples/cards/index.tsx",
    "chars": 2974,
    "preview": "import { CardsActivityGoal } from \"@/components/examples/cards/activity-goal\";\nimport { CardsCalendar } from \"@/componen"
  },
  {
    "path": "components/examples/cards/payment-method.tsx",
    "chars": 4539,
    "preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  Car"
  },
  {
    "path": "components/examples/cards/payments.tsx",
    "chars": 7795,
    "preview": "\"use client\";\n\nimport {\n  ColumnDef,\n  ColumnFiltersState,\n  flexRender,\n  getCoreRowModel,\n  getFilteredRowModel,\n  get"
  },
  {
    "path": "components/examples/cards/report-issue.tsx",
    "chars": 3160,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  Card"
  },
  {
    "path": "components/examples/cards/share.tsx",
    "chars": 2912,
    "preview": "\"use client\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/c"
  },
  {
    "path": "components/examples/cards/stats.tsx",
    "chars": 2953,
    "preview": "\"use client\";\n\nimport { Area, AreaChart, Line, LineChart } from \"recharts\";\n\nimport { Card, CardContent, CardDescription"
  },
  {
    "path": "components/examples/cards/team-members.tsx",
    "chars": 3299,
    "preview": "\"use client\";\n\nimport { ChevronDown } from \"lucide-react\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/compo"
  },
  {
    "path": "components/examples/custom/index.tsx",
    "chars": 203,
    "preview": "import { DynamicWebsitePreview } from \"@/components/dynamic-website-preview\";\n\nexport default function CustomDemo() {\n  "
  },
  {
    "path": "components/examples/dashboard/components/app-sidebar.tsx",
    "chars": 3602,
    "preview": "import * as React from \"react\";\nimport {\n  ArrowUpCircleIcon,\n  BarChartIcon,\n  CameraIcon,\n  ClipboardListIcon,\n  Datab"
  },
  {
    "path": "components/examples/dashboard/components/chart-area-interactive.tsx",
    "chars": 10685,
    "preview": "import * as React from \"react\";\nimport { Area, AreaChart, CartesianGrid, XAxis } from \"recharts\";\n\nimport { useIsMobile "
  },
  {
    "path": "components/examples/dashboard/components/chart-bar-mixed.tsx",
    "chars": 2611,
    "preview": "\"use client\"\n\nimport { TrendingUp } from \"lucide-react\"\nimport { Bar, BarChart, XAxis, YAxis } from \"recharts\"\n\nimport {"
  },
  {
    "path": "components/examples/dashboard/components/chart-pie-donut.tsx",
    "chars": 3618,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { TrendingUp } from \"lucide-react\"\nimport { Label, Pie, PieChart } f"
  },
  {
    "path": "components/examples/dashboard/components/data-table.tsx",
    "chars": 25767,
    "preview": "import * as React from \"react\";\nimport {\n  DndContext,\n  KeyboardSensor,\n  MouseSensor,\n  TouchSensor,\n  closestCenter,\n"
  },
  {
    "path": "components/examples/dashboard/components/nav-documents.tsx",
    "chars": 2276,
    "preview": "import {\n  FolderIcon,\n  MoreHorizontalIcon,\n  ShareIcon,\n  type LucideIcon,\n} from \"lucide-react\";\n\nimport {\n  Dropdown"
  },
  {
    "path": "components/examples/dashboard/components/nav-main.tsx",
    "chars": 1355,
    "preview": "\"use client\";\n\nimport { PlusCircleIcon, type LucideIcon } from \"lucide-react\";\n\nimport {\n  SidebarGroup,\n  SidebarGroupC"
  },
  {
    "path": "components/examples/dashboard/components/nav-secondary.tsx",
    "chars": 925,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { LucideIcon } from \"lucide-react\";\n\nimport {\n  SidebarGroup,\n  Si"
  },
  {
    "path": "components/examples/dashboard/components/nav-user.tsx",
    "chars": 3408,
    "preview": "\"use client\";\n\nimport {\n  BellIcon,\n  CreditCardIcon,\n  LogOutIcon,\n  MoreVerticalIcon,\n  UserCircleIcon,\n} from \"lucide"
  },
  {
    "path": "components/examples/dashboard/components/section-cards.tsx",
    "chars": 4063,
    "preview": "import { TrendingDownIcon, TrendingUpIcon } from \"lucide-react\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport "
  },
  {
    "path": "components/examples/dashboard/components/site-header.tsx",
    "chars": 660,
    "preview": "import { Separator } from \"@/components/ui/separator\";\nimport { SidebarTrigger } from \"@/components/ui/sidebar\";\n\nexport"
  },
  {
    "path": "components/examples/dashboard/data.json",
    "chars": 12795,
    "preview": "[\n  {\n    \"id\": 1,\n    \"header\": \"Cover page\",\n    \"type\": \"Cover page\",\n    \"status\": \"In Process\",\n    \"target\": \"18\","
  }
]

// ... and 250 more files (download for full content)

About this extraction

This page contains the full source code of the jnsahaj/tweakcn GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 450 files (1.8 MB), approximately 523.9k tokens, and a symbol index with 1043 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!