diff --git a/.clinerules b/.clinerules deleted file mode 120000 index ac534a310..000000000 --- a/.clinerules +++ /dev/null @@ -1 +0,0 @@ -AGENT.md \ No newline at end of file diff --git a/.cursor/rules/AGENT.md b/.cursor/rules/AGENT.md deleted file mode 120000 index ec2887d9c..000000000 --- a/.cursor/rules/AGENT.md +++ /dev/null @@ -1 +0,0 @@ -../../AGENT.md \ No newline at end of file diff --git a/.cursorrules b/.cursorrules deleted file mode 120000 index ac534a310..000000000 --- a/.cursorrules +++ /dev/null @@ -1 +0,0 @@ -AGENT.md \ No newline at end of file diff --git a/.env.example b/.env.example index ce2fc13a7..2d2cc7785 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,5 @@ NUXT_CF_API_TOKEN=CloudflareAPIToken NUXT_DATASET=sink NUXT_AI_MODEL="@cf/meta/llama-3-8b-instruct" NUXT_AI_PROMPT="You are a URL shortening assistant......" +NUXT_DISABLE_AUTO_BACKUP=false +NUXT_NOT_FOUND_REDIRECT=/your-own-404-page diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f57d70ee6..a8f8b6589 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -github: ccbikai +github: miantiao-me buy_me_a_coffee: miantiao diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 120000 index 471d0a2c6..000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1 +0,0 @@ -../AGENT.md \ No newline at end of file diff --git a/.github/workflows/issue-assistant.yml b/.github/workflows/issue-assistant.yml new file mode 100644 index 000000000..d52d3ff32 --- /dev/null +++ b/.github/workflows/issue-assistant.yml @@ -0,0 +1,54 @@ +name: issue-assistant + +on: + issues: + types: [opened] + +jobs: + issue-assistant: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + + - name: Install opencode + run: curl -fsSL https://opencode.ai/install | bash + + - name: Analyze issue + env: + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENCODE_PERMISSION: | + { + "bash": { + "*": "deny", + "gh issue*": "allow" + }, + "webfetch": "deny" + } + run: | + opencode run -m ${{ secrets.OPENCODE_MODEL }} "A new issue #${{ github.event.issue.number }} has been created. Lookup this issue, then do the following: + + 1. Read README.md and all files under docs/ directory to see if any documentation answers the issue. + 2. Search existing issues (excluding #${{ github.event.issue.number }}) for potential duplicates by similarity in title, description, error messages, or functionality. + + Based on your findings, post AT MOST ONE comment on #${{ github.event.issue.number }}. Combine all relevant info into a single comment. If nothing relevant found, do not comment at all. + + Comment format example: + '👋 Here are some resources that may help: + + **📖 From docs:** [brief explanation with relevant docs content] + + **🔗 Related issues:** + + - #123: [brief similarity] + + Hope this helps! Feel free to provide more details if these don't address your case.'" diff --git a/.gitignore b/.gitignore index a6ee4c2d0..a2d2b6b8e 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,8 @@ cache public/world.json .dev.* .kiro/steering/locale.md + +.claude +.opencode +.agents +opencode.json diff --git a/.kiro/steering/AGENT.md b/.kiro/steering/AGENT.md deleted file mode 120000 index ec2887d9c..000000000 --- a/.kiro/steering/AGENT.md +++ /dev/null @@ -1 +0,0 @@ -../../AGENT.md \ No newline at end of file diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 000000000..e01fd62a9 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "shadcn-vue": { + "command": "npx", + "args": ["shadcn-vue@latest", "mcp"] + }, + "nuxt": { + "type": "http", + "url": "https://nuxt.com/mcp" + } + } +} diff --git a/.node-version b/.node-version index 7c897d99e..2bd5a0a98 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.15.1 \ No newline at end of file +22 diff --git a/.npmrc b/.npmrc index c483022c0..f855d962b 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,5 @@ -shamefully-hoist=true \ No newline at end of file +shamefully-hoist=false +strict-peer-dependencies=true +auto-install-peers=true +prefer-frozen-lockfile=true +save-dev=true diff --git a/.windsurfrules b/.windsurfrules deleted file mode 120000 index ac534a310..000000000 --- a/.windsurfrules +++ /dev/null @@ -1 +0,0 @@ -AGENT.md \ No newline at end of file diff --git a/AGENT.md b/AGENT.md deleted file mode 100644 index 185ae27e2..000000000 --- a/AGENT.md +++ /dev/null @@ -1,122 +0,0 @@ -# AGENT.md - -This file provides guidance for AI assistants working with the Sink project. - -## Project Overview - -Sink is a URL shortening service built with Nuxt 3 and Cloudflare Workers. It provides analytics, real-time tracking, and a modern web interface. - -## Key Technologies - -- **Frontend**: Nuxt 3, Vue 3, TypeScript, Tailwind CSS, shadcn/ui -- **Backend**: Cloudflare Workers, H3 (Nitro) -- **Database**: Cloudflare KV -- **Analytics**: Cloudflare Analytics Engine -- **Deployment**: Cloudflare Pages/Workers - -## Project Structure - -```txt -Sink/ -├── app/ # Nuxt 3 application -│ ├── components/ # Vue components -│ ├── pages/ # File-based routing -│ ├── layouts/ # Layout components -│ ├── utils/ # Utility functions -│ └── middleware/ # Route middleware -├── server/ # Server-side API -│ ├── api/ # API endpoints -│ ├── middleware/ # Server middleware -│ └── utils/ # Server utilities -├── public/ # Static assets -├── schemas/ # TypeScript schemas -├── tests/ # Test files -└── docs/ # Documentation -``` - -## Development Commands - -- `pnpm dev` - Start development server -- `pnpm build` - Build for production -- `pnpm preview` - Preview production build -- `pnpm test` - Run tests -- `pnpm lint` - Run ESLint - -## Key Conventions - -### Code Style - -- Use TypeScript for all new code -- Follow Vue 3 Composition API patterns -- Use PascalCase for component names -- Use camelCase for variables and functions -- Use kebab-case for CSS classes - -### API Structure - -- RESTful endpoints in `/server/api/` -- Use Zod for schema validation in `/schemas/` -- Implement proper error handling -- Use Cloudflare environment variables for configuration - -### Database - -- Use Cloudflare KV - -### Testing - -- Use Vitest for unit tests -- Test files should be co-located with source files -- Use the existing test utilities in `/tests/utils.ts` - -## Environment Variables - -Key environment variables to be aware of: - -- `NUXT_CF_ACCOUNT_ID` - Cloudflare account ID -- `NUXT_CF_API_TOKEN` - Cloudflare API token -- `NUXT_DATASET` - Analytics engine ID -- `NUXT_SITE_TOKEN` - Site token for management - -## Common Tasks - -### Adding a New API Endpoint - -1. Create file in `/server/api/` with appropriate HTTP method -2. Add Zod schema validation in `/schemas/` -3. Add tests in `/tests/api/` -4. Update documentation if needed - -### Adding a New Component - -1. Use existing UI components from `/app/components/ui/` -2. Follow the established component patterns -3. Add TypeScript interfaces for props -4. Consider adding to storybook if applicable - -## Security Guidelines - -- Never commit secrets or API keys -- Use environment variables for sensitive configuration -- Validate all user input with Zod schemas -- Implement rate limiting on API endpoints -- Follow OWASP guidelines for web security - -## Performance Considerations - -- Use Cloudflare's edge caching where appropriate -- Minimize bundle size by using dynamic imports -- Optimize images and assets -- Use the existing analytics for monitoring - -## Troubleshooting - -- Check Cloudflare dashboard for worker logs -- Use `wrangler tail` for real-time logs -- Verify environment variables are properly set - -## Deployment - -- Staging: Automatic on push to `main` branch -- Production: Manual trigger via Cloudflare dashboard -- Use `wrangler deploy` for manual deployments diff --git a/AGENTS.md b/AGENTS.md deleted file mode 120000 index ac534a310..000000000 --- a/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -AGENT.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..82324a10a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,212 @@ +# Repository Guidelines + +Guidelines for agentic coding agents operating in the Sink codebase. + +## Project Overview + +Sink is a link shortener with analytics, running 100% on Cloudflare. Uses Nuxt 4 frontend and Cloudflare Workers backend. + +**All documentation and comments must be in English.** + +## Project Structure + +``` +app/ # Nuxt 4 application (main app layer) + ├── components/ # Vue components (PascalCase) + │ └── ui/ # shadcn-vue components (DO NOT EDIT - auto-generated) + ├── composables/ # Vue composables (camelCase, use* prefix) + ├── pages/ # File-based routing + ├── types/ # TypeScript types (re-exports from shared/) + ├── utils/ # Utility functions + └── lib/ # Shared helpers +layers/dashboard/ # Dashboard layer (extends app/) + └── app/components/dashboard/ # Dashboard-specific components +shared/ # Shared code (client + server) + ├── schemas/ # Zod validation schemas + └── types/ # Shared TypeScript types +server/ # Nitro server (Cloudflare Workers) + ├── api/ # API endpoints (method suffix: create.post.ts) + └── utils/ # Server utilities (auto-imported) +tests/ # Vitest tests (Cloudflare Workers pool) +``` + +## Commands + +Use **pnpm** (v10.28.2, enforced via `packageManager`) with **Node.js 22+**. + +```bash +# Development +pnpm dev # Start dev server (port 7465) +pnpm build # Production build (needs 8GB heap) +pnpm preview # Worker preview via wrangler +pnpm lint:fix # ESLint with auto-fix (ALWAYS run before commit) +pnpm types:check # TypeScript type check + +# Testing (Vitest + @cloudflare/vitest-pool-workers) +pnpm vitest # Watch mode +pnpm vitest run # CI mode (run once) +pnpm vitest tests/api/link.spec.ts # Single test file +pnpm vitest -t "creates new link" # Match test name pattern + +# Deployment +pnpm deploy:pages # Deploy to Cloudflare Pages +pnpm deploy:worker # Deploy to Cloudflare Workers +``` + +**Important:** `pnpm install` runs `postinstall` which executes `build:map && nuxt prepare`. Do not skip this. + +**Critical lint prerequisite:** ESLint config imports from `.nuxt/eslint.config.mjs` (generated by Nuxt). If lint fails with module not found, run `pnpm postinstall` or `pnpm dev` first to generate it. + +## Code Style + +Uses `@antfu/eslint-config` with `eslint-plugin-better-tailwindcss`. Run `pnpm lint:fix` before committing. + +**Formatting**: 2-space indent | Single quotes | No semicolons | Trailing commas + +### TypeScript + +- Use TypeScript everywhere; prefer `interface` for objects, `type` for unions/aliases +- Avoid `any`; use proper types or `unknown` +- Use Zod for runtime validation in `shared/schemas/` +- Export types with `export type` for type-only exports + +```typescript +// shared/schemas/link.ts - shared validation +export const LinkSchema = z.object({ + id: z.string().trim().max(26), + url: z.string().trim().url().max(2048), + slug: z.string().trim().max(2048).regex(slugRegex), +}) +export type Link = z.infer +``` + +### Vue Components + +Use ` + + +``` + +### Imports + +- **Prefer Nuxt auto-imports**: `ref`, `computed`, `useFetch`, `useState`, `useRuntimeConfig`, etc. +- **Explicit imports for**: external libs, types (`import type { Link } from '@/types'`), icons (`import { Copy } from 'lucide-vue-next'`) +- **Server utils are auto-imported**: Functions in `server/utils/` are available globally in server code + +### Naming Conventions + +| Item | Convention | Example | +| -------------- | ---------------- | ------------------ | +| Components | PascalCase | `LinkEditor.vue` | +| Composables | `use` prefix | `useAuthToken()` | +| API routes | method suffix | `create.post.ts` | +| Directories | kebab-case | `dashboard/links/` | +| Functions/vars | camelCase | `getLink` | +| Constants | UPPER_SNAKE_CASE | `TOKEN_KEY` | + +### Error Handling + +```typescript +// Server API - use createError for HTTP errors +export default eventHandler(async (event) => { + const link = await readValidatedBody(event, LinkSchema.parse) + if (existingLink) { + throw createError({ status: 409, statusText: 'Link already exists' }) + } +}) +``` + +## Cloudflare Bindings + +Access via destructuring `event.context`: + +```typescript +const { cloudflare } = event.context +const { KV, ANALYTICS, AI, R2 } = cloudflare.env +``` + +| Binding | Type | Purpose | +| ----------- | ---------------- | ---------------------------- | +| `KV` | Workers KV | Link storage (`link:{slug}`) | +| `ANALYTICS` | Analytics Engine | Click tracking & analytics | +| `AI` | Workers AI | AI-powered slug generation | +| `R2` | R2 Bucket | Image uploads & backup | + +## Testing Patterns + +Tests use `@cloudflare/vitest-pool-workers` with real Cloudflare bindings (single worker, shared storage). + +```typescript +import { generateMock } from '@anatine/zod-mock' +import { describe, expect, it } from 'vitest' +import { fetchWithAuth, postJson } from '../utils' + +describe.sequential('/api/link/create', () => { + it('creates new link with valid data', async () => { + const response = await postJson('/api/link/create', { url: 'https://example.com', slug: 'test' }) + expect(response.status).toBe(201) + }) +}) +``` + +**Test utilities** (`tests/utils.ts`): + +- `fetchWithAuth(path, options)` - GET with auth header +- `postJson(path, body, withAuth?)` - POST JSON with optional auth +- `putJson(path, body, withAuth?)` - PUT JSON with optional auth +- `fetch(path, options)` - Raw fetch without auth + +Use `describe.sequential` for tests that share state (most API tests). + +## UI Components + +- Use shadcn-vue from `app/components/ui/` - **Never edit** (auto-generated) +- Use `ResponsiveModal` for mobile-optimized dialogs +- Use Tailwind CSS v4 for styling (`@import 'tailwindcss'` syntax) +- Use static English for `aria-label` (no `$t()` translations) +- Icons from `lucide-vue-next` + +## Commits + +Follow Conventional Commits: `feat:`, `fix:`, `docs:`, `chore:`, `refactor:` + +## Pre-commit + +`simple-git-hooks` runs `lint-staged` on commit, auto-runs `eslint --fix` on staged files. + +## API Route Patterns + +API routes use method suffix convention: + +- `create.post.ts` → `POST /api/link/create` +- `query.get.ts` → `GET /api/link/query` +- `edit.put.ts` → `PUT /api/link/edit` + +Server utils in `server/utils/` are auto-imported: + +- `getLink(event, slug)` - Fetch link from KV +- `putLink(event, link)` - Store link in KV +- `deleteLink(event, slug)` - Remove link from KV +- `normalizeSlug(event, slug)` - Case normalization +- `buildShortLink(event, slug)` - Construct full URL + +## OpenAPI + +API routes auto-generate OpenAPI docs via `defineRouteMeta` with `openAPI` property. The spec is served at `/_docs/openapi.json` and UI at `/_docs/scalar` and `/_docs/swagger`. + +## Architecture Notes + +- **Dashboard layer (`layers/dashboard/`) is client-only**: `ssr: false` with prerendered routes. Dashboard pages are CSR-only. +- **Nitro preset**: `cloudflare-module` (set conditionally when not in CI) +- **i18n**: `@nuxtjs/i18n` with `no_prefix` strategy; locales defined in `i18n/i18n.ts` diff --git a/CLAUDE.md b/CLAUDE.md index ac534a310..47dc3e3d8 120000 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1 @@ -AGENT.md \ No newline at end of file +AGENTS.md \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md deleted file mode 120000 index ac534a310..000000000 --- a/GEMINI.md +++ /dev/null @@ -1 +0,0 @@ -AGENT.md \ No newline at end of file diff --git a/README.md b/README.md index e4b10e30b..04fd37402 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ccbikai/Sink | Trendshift @@ -35,7 +35,7 @@ /> -[DeepWiki](https://deepwiki.com/ccbikai/Sink) +[DeepWiki](https://deepwiki.com/miantiao-me/Sink) ![Cloudflare](https://img.shields.io/badge/Cloudflare-F69652?style=flat&logo=cloudflare&logoColor=white) ![Nuxt](https://img.shields.io/badge/Nuxt-00DC82?style=flat&logo=nuxtdotjs&logoColor=white) ![Tailwind CSS](https://img.shields.io/badge/Tailwind%20CSS-06B6D4?style=flat&logo=tailwindcss&logoColor=white) @@ -47,12 +47,18 @@ ## ✨ Features -- **URL Shortening:** Compress your URLs to their minimal length. -- **Analytics:** Monitor link analytics and gather insightful statistics. -- **Serverless:** Deploy without the need for traditional servers. -- **Customizable Slug:** Support for personalized slugs and case sensitivity. -- **🪄 AI Slug:** Leverage AI to generate slugs. -- **Link Expiration:** Set expiration dates for your links. +- **🔗 URL Shortening:** Compress your URLs to their minimal length. +- **📈 Analytics:** Monitor link analytics and gather insightful statistics. +- **☁️ Serverless:** Deploy without the need for traditional servers. +- **🎨 Customizable Slug:** Support personalized slugs, UTM parameters, and case sensitivity. +- **🪄 AI Assistance:** Generate slugs and OpenGraph metadata from page content. +- **⏰ Link Control:** Set expirations, passwords, and unsafe-link warning pages. +- **📱 Smart Routing:** Redirect visitors by device or country. +- **🖼️ Social Preview:** Customize social previews with titles, descriptions, and images. +- **📊 Real-time Analytics:** Live 3D globe and real-time event logs. +- **🔲 QR Code:** Generate QR codes for your short links. +- **📦 Import/Export:** Bulk link migration via JSON and access analytics via CSV. +- **🌍 Multi-language:** Full i18n support for dashboard and redirect pages. ## 🪧 Demo @@ -83,13 +89,14 @@ Site Token: SinkCool We welcome your contributions and PRs. - [x] Browser Extension - [Sink Tool](https://github.com/zhuzhuyule/sink-extension) +- [x] Chrome Extension - [Sink Quick Shorten](https://chromewebstore.google.com/detail/sink-quick-shorten/emlojomjpenjgkaphajcokijobpkejih) - [x] Raycast Extension - [Raycast-Sink](https://github.com/foru17/raycast-sink) - [x] Apple Shortcuts - [Sink Shortcuts](https://s.search1api.com/sink001) - [x] iOS App - [Sink](https://apps.apple.com/app/id6745417598) - [ ] Enhanced Link Management (with Cloudflare D1) - [ ] Analytics Enhancements (Support for merging filter conditions) -- [ ] Dashboard Performance Optimization (Infinite loading) -- [ ] Units Test +- [x] Dashboard Performance Optimization (Infinite loading) +- [x] API, migration, backup, and redirect tests ## 🏗️ Deployment @@ -105,6 +112,14 @@ We currently support deployment to [Cloudflare Workers](./docs/deployment/worker [API Docs](./docs/api.md) +## 🤖 AI Skills + +Install Sink AI Skills for enhanced coding assistance: + +```bash +npx skills add miantiao-me/sink +``` + ## 🧰 MCP We currently do not support native MCP Server, but we have OpenAPI documentation, and you can use the following method to support MCP. @@ -124,7 +139,7 @@ We currently do not support native MCP Server, but we have OpenAPI documentation "env": { "OPENAPI_SPEC_URL": "https://sink.cool/_docs/openapi.json", "API_KEY": "SinkCool", - "TOOL_WHITELIST": "/api/link/create" + "TOOL_WHITELIST": "/api/link" } } } @@ -140,8 +155,9 @@ We currently do not support native MCP Server, but we have OpenAPI documentation 1. [**Cloudflare**](https://www.cloudflare.com/) 2. [**NuxtHub**](https://hub.nuxt.com/) 3. [**Astroship**](https://astroship.web3templates.com/) +4. [**Tailark**](https://tailark.com/) ## ☕ Sponsor -1. [Follow Me on X(Twitter)](https://404.li/kai). -2. [Become a sponsor to on GitHub](https://github.com/sponsors/ccbikai). +1. [Follow Me on X(Twitter)](https://404.li/x). +2. [Become a sponsor to on GitHub](https://github.com/sponsors/miantiao-me). diff --git a/app/app.config.ts b/app/app.config.ts index 2d16db23f..64a604c6f 100644 --- a/app/app.config.ts +++ b/app/app.config.ts @@ -1,11 +1,9 @@ export default defineAppConfig({ title: 'Sink', - email: 'sink.cool@miantiao.me', - github: 'https://github.com/ccbikai/sink', - twitter: 'https://sink.cool/kai', + github: 'https://github.com/miantiao-me/sink', + coffee: 'https://sink.cool/coffee', + twitter: 'https://sink.cool/x', telegram: 'https://sink.cool/telegram', - mastodon: 'https://sink.cool/mastodon', - blog: 'https://sink.cool/blog', description: 'A Simple / Speedy / Secure Link Shortener with Analytics, 100% run on Cloudflare.', image: 'https://sink.cool/banner.png', previewTTL: 300, // 5 minutes diff --git a/app/app.vue b/app/app.vue index f2897c7bb..9c7f96a47 100644 --- a/app/app.vue +++ b/app/app.vue @@ -1,8 +1,12 @@ - diff --git a/app/assets/css/tailwind.css b/app/assets/css/tailwind.css index ff80f343b..ca4c6ee3c 100644 --- a/app/assets/css/tailwind.css +++ b/app/assets/css/tailwind.css @@ -1,91 +1,148 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 240 10% 3.9%; - - --muted: 240 4.8% 95.9%; - --muted-foreground: 240 3.8% 46.1%; - - --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - - --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; - - --border: 240 5.9% 90%; - --input: 240 5.9% 90%; - - --primary: 240 5.9% 10%; - --primary-foreground: 0 0% 98%; - - --secondary: 240 4.8% 95.9%; - --secondary-foreground: 240 5.9% 10%; - - --accent: 240 4.8% 95.9%; - --accent-foreground: 240 5.9% 10%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - - --ring: 240 10% 3.9%; - - --radius: 0.5rem; - - --vis-tooltip-background-color: none !important; - --vis-tooltip-border-color: none !important; - --vis-tooltip-text-color: none !important; - --vis-tooltip-shadow-color: none !important; - --vis-tooltip-backdrop-filter: none !important; - --vis-tooltip-padding: none !important; - - --vis-primary-color: 158 64% 52%; - --vis-secondary-color: 198 93% 60%; - /* --vis-secondary-color: 160 81% 40%; */ - /* --vis-secondary-color: var(--primary); */ - --vis-text-color: var(--muted-foreground); - } - - .dark { - --background: 240 10% 3.9%; - --foreground: 0 0% 98%; - - --muted: 240 3.7% 15.9%; - --muted-foreground: 240 5% 64.9%; - - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - - --primary: 0 0% 98%; - --primary-foreground: 240 5.9% 10%; - - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - - --accent: 240 3.7% 15.9%; - --accent-foreground: 0 0% 98%; +@import 'tailwindcss'; +@import 'tw-animate-css'; + +@source "../../../layers"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --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-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); +} - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.141 0.005 285.823); + --card: oklch(1 0 0); + --card-foreground: oklch(0.141 0.005 285.823); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.141 0.005 285.823); + --primary: oklch(0.21 0.006 285.885); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); + --muted: oklch(0.967 0.001 286.375); + --muted-foreground: oklch(0.552 0.016 285.938); + --accent: oklch(0.967 0.001 286.375); + --accent-foreground: oklch(0.21 0.006 285.885); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.92 0.004 286.32); + --input: oklch(0.92 0.004 286.32); + --ring: oklch(0.705 0.015 286.067); + --chart-1: oklch(0.871 0.15 154.449); + --chart-2: oklch(0.723 0.219 149.579); + --chart-3: oklch(0.627 0.194 149.214); + --chart-4: oklch(0.527 0.154 150.069); + --chart-5: oklch(0.448 0.119 151.328); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.141 0.005 285.823); + --sidebar-primary: oklch(0.21 0.006 285.885); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.967 0.001 286.375); + --sidebar-accent-foreground: oklch(0.21 0.006 285.885); + --sidebar-border: oklch(0.92 0.004 286.32); + --sidebar-ring: oklch(0.705 0.015 286.067); +} - --ring: 240 4.9% 83.9%; - } +.dark { + --background: oklch(0.141 0.005 285.823); + --foreground: oklch(0.985 0 0); + --card: oklch(0.21 0.006 285.885); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.92 0.004 286.32); + --primary-foreground: oklch(0.21 0.006 285.885); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.274 0.006 286.033); + --muted-foreground: oklch(0.705 0.015 286.067); + --accent: oklch(0.274 0.006 286.033); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.552 0.016 285.938); + --chart-1: oklch(0.871 0.15 154.449); + --chart-2: oklch(0.723 0.219 149.579); + --chart-3: oklch(0.627 0.194 149.214); + --chart-4: oklch(0.527 0.154 150.069); + --chart-5: oklch(0.448 0.119 151.328); + --sidebar: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.552 0.016 285.938); } @layer base { * { - @apply border-border; + @apply border-border outline-ring/50; + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; } body { @apply bg-background text-foreground; } + + button:not(:disabled), + [role='button']:not(:disabled), + [role='menuitem']:not(:disabled), + [role='tab']:not(:disabled), + [role='option']:not(:disabled), + [role='switch']:not(:disabled), + [role='checkbox']:not(:disabled), + [role='radio']:not(:disabled), + summary, + label { + @apply cursor-pointer; + } + + ::-webkit-scrollbar { + width: 5px; + } + ::-webkit-scrollbar-track { + background: transparent; + } + ::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 5px; + } } diff --git a/app/components/ResponsiveModal.vue b/app/components/ResponsiveModal.vue new file mode 100644 index 000000000..583a3b573 --- /dev/null +++ b/app/components/ResponsiveModal.vue @@ -0,0 +1,73 @@ + + + diff --git a/app/components/SwitchLanguage.vue b/app/components/SwitchLanguage.vue index 183a2d4dd..7143a3f05 100644 --- a/app/components/SwitchLanguage.vue +++ b/app/components/SwitchLanguage.vue @@ -1,22 +1,15 @@ -