DEPLOYMENT COMMAND

npm run pages:build && wrangler deploy
[![Contributors][contributors-shield]][contributors-url] [![Issues][issues-shield]][issues-url]
United Tattoo Logo

United Tattoo

Official Website for United-Tattoo, built on Cloudflare's edge network.

View Live Site »

Quick Start · Report Bug · Request Feature

--- ## Table of Contents - [About The Project](#about-the-project) - [Key Features](#key-features) - [Tech Stack](#tech-stack) - [Architecture](#architecture) [Getting Started](#getting-started) [Prerequisites](#prerequisites) - [Installation](#installation) - [Environment Variables](#environment-variables) - [Development](#development) - [Common Commands](#common-commands) - [Database Management](#database-management) - [Deployment](#deployment) - [Documentation](#documentation) - [Roadmap](#roadmap) - [Contributing](#contributing) - [License](#license) - [Contact](#contact) ---
## About The Project
United Tattoo Studio

**United Tattoo** is an all-in-one tool built for [United Tattoo](https://united-tattoos.com), a tattoo studio in **Fountain, Colorado**. It brings together artist portfolios, appointment booking, a flash tattoo marketplace, and calendar sync, all running on Cloudflare for speed and reliability. ### Key Features - Artist portfolios with Instagram links - Simple artist pages and management - Book appointments online - Browse and book flash tattoo designs - Nextcloud sync for calendars and user login - Admin and artist dashboards - File and image uploads

(back to top)

--- ## Tech Stack
### Core Framework [![Next.js][nextjs-badge]][next-url] [![React][react-badge]][react-url] [![TypeScript][typescript-badge]][typescript-url] ### Cloudflare Infrastructure [![Cloudflare Workers][cloudflare-badge]][cloudflare-url] [![OpenNext][opennext-badge]][opennext-url] ### UI & Styling [![ShadCN UI][shadcn-badge]][shadcn-url] [![Tailwind CSS][tailwind-badge]][tailwind-url] [![Framer Motion][framer-badge]][framer-url] ### Database & Storage [![Cloudflare D1][d1-badge]][d1-url] [![Cloudflare R2][r2-badge]][r2-url] ### Authentication & Integration [![NextAuth.js][nextauth-badge]][nextauth-url] [![CalDAV][caldav-badge]][caldav-url] ### Testing & Quality [![Vitest][vitest-badge]][vitest-url] [![ESLint][eslint-badge]][eslint-url] [![Prettier][prettier-badge]][prettier-url]
Dependencies - Next.js (App Router), React, TypeScript - ShadCN UI (Radix UI), Framer Motion, Tailwind CSS, Lenis, next-themes - React Query, Zod, React Hook Form - CalDAV/Calendar: tsdav, react-big-calendar, ical.js, date-fns - Image/file tools: Sharp, heic-convert, AWS SDK (R2) - Testing: Vitest, React Testing Library

(back to top)

--- ## Architecture
```mermaid graph TB subgraph "Client Layer" Browser[Browser] end subgraph "Edge Layer - Cloudflare Workers" NextJS[Next.js 14
App Router] OpenNext[OpenNext
Adapter] end subgraph "Cloudflare Services" D1[(D1 Database
SQLite)] R2[R2 Storage
Files & Images] end subgraph "External Services" Nextcloud[Nextcloud
OAuth + CalDAV] end Browser --> NextJS NextJS --> OpenNext OpenNext --> D1 OpenNext --> R2 OpenNext <--> Nextcloud style Browser fill:#667eea,color:#fff style NextJS fill:#000,color:#fff style OpenNext fill:#f38020,color:#fff style D1 fill:#f38020,color:#fff style R2 fill:#f38020,color:#fff style Nextcloud fill:#0082c9,color:#fff ```
### System Flow
Authentication Flow ``` User Request → NextAuth.js ↓ ├─ Nextcloud OAuth (Primary) │ ├─ Check user groups (via OCS API) │ ├─ Assign role based on group │ │ ├─ "admins" → SUPER_ADMIN │ │ ├─ "shop_admins" → SHOP_ADMIN │ │ ├─ "artists" → ARTIST (auto-create profile) │ │ └─ Other → Deny access │ └─ Create/update user in D1 │ ├─ Credentials (Fallback - Admin Only) │ ├─ Query parameter: ?admin=true │ ├─ Email/password validation │ └─ Dev mode: Auto-create SUPER_ADMIN │ └─ Session Created (JWT) ```
CalDAV Sync Flow ``` Appointment Created/Updated ↓ syncAppointmentToCalendar() ↓ ├─ Get artist's Nextcloud calendar config ├─ Connect to CalDAV server ├─ Create/update VEVENT ├─ Store calendar_event_uid in D1 └─ Log sync operation Background Sync (Periodic) ↓ pullCalendarEventsToDatabase() ↓ ├─ Fetch events from CalDAV ├─ Compare with D1 appointments ├─ Update changed appointments ├─ Import external events └─ Log sync results ```
Database Schema **Core Tables:** | Table | Description | Key Fields | |-------|-------------|------------| | `users` | Authentication & profiles | id, email, name, role, nextcloud_user_id | | `artists` | Artist profiles | id, user_id, slug, bio, specialties (JSON), hourly_rate | | `portfolio_images` | Artist work gallery | id, artist_id, image_url, tags (JSON), order, is_public | | `flash_items` | Pre-drawn designs | id, artist_id, title, price, is_available | | `appointments` | Booking system | id, artist_id, client_id, status, start_time, end_time, deposit | | `availability` | Artist schedules | id, artist_id, day_of_week, start_time, end_time | | `artist_calendars` | CalDAV configuration | id, artist_id, calendar_url, username, password | | `calendar_sync_logs` | Sync monitoring | id, artist_id, operation, status, details | | `site_settings` | Global config | key, value, type | | `file_uploads` | R2 file tracking | id, user_id, file_url, file_size, mime_type | **Indexes:** Optimized for artist lookups, appointment queries, and calendar sync operations.
### Project Structure ``` united-tattoo/ ├── app/ # Next.js App Router │ ├── (public)/ # Public pages (no auth) │ │ ├── artists/ # Artist profiles & portfolios │ │ ├── book/ # Booking pages │ │ └── page.tsx # Homepage │ ├── admin/ # Admin dashboard (SUPER_ADMIN, SHOP_ADMIN) │ ├── artist-dashboard/ # Artist self-service (ARTIST) │ ├── api/ # API routes │ │ ├── artists/ # Artist CRUD │ │ ├── appointments/ # Booking endpoints │ │ ├── caldav/ # Calendar sync │ │ ├── flash/ # Flash items │ │ └── upload/ # R2 file uploads │ └── auth/ # NextAuth pages ├── lib/ # Core logic │ ├── db.ts # D1 database functions │ ├── auth.ts # NextAuth config + helpers │ ├── caldav-client.ts # CalDAV integration │ ├── calendar-sync.ts # Sync logic │ ├── nextcloud-client.ts # Nextcloud API client │ ├── r2-upload.ts # R2 file handling │ └── env.ts # Environment validation ├── components/ # React components │ ├── ui/ # ShadCN components │ └── ... # Feature components ├── sql/ # Database │ ├── schema.sql # Main schema │ └── migrations/ # Migration files ├── docs/ # Documentation ├── .gitea/workflows/ # CI/CD pipelines └── wrangler.toml # Cloudflare config ```

(back to top)

--- ## Getting Started ### Prerequisites
Required Accounts & Access Before starting, ensure you have: - **Cloudflare Account** with access to Workers, D1, R2, and Pages - **Nextcloud Instance** with admin access for OAuth app creation - **Node.js 18+** and npm installed - **Wrangler CLI** version 3+
**Install Wrangler:** ```bash npm install -g wrangler ``` **Cloudflare Resources Required:** - **Workers & Pages**: For hosting the application - **D1 Database**: SQLite database (named `united-tattoo`) - **R2 Buckets**: File storage (`united-tattoo`, `united-tattoo-inc-cache`) ### Installation 1. **Clone the repository** ```bash git clone https://git.biohazardvfx.com/nicholai/united-tattoo.git cd united-tattoo ``` 2. **Install dependencies** ```bash npm install ``` 3. **Configure environment variables** ```bash cp .env.example .env.local # Edit .env.local with your credentials (see Environment Variables section) ``` 4. **Set up Cloudflare D1 database** ```bash # Create D1 database (if not exists) wrangler d1 create united-tattoo # Apply schema to local D1 npm run db:migrate:local # Apply schema to preview/production npm run db:migrate:latest:preview ``` 5. **Configure Nextcloud OAuth** See [`docs/NEXTCLOUD-OAUTH-SETUP.md`](docs/NEXTCLOUD-OAUTH-SETUP.md) for detailed instructions on: - Creating OAuth application in Nextcloud - Configuring group-based role assignment - Setting up service account for CalDAV 6. **Run locally** ```bash # Next.js dev server (port 3000) npm run dev # OR with Cloudflare Workers simulation npm run dev:wrangler ``` 7. **Access the application** - Local: `http://localhost:3000` - Sign in: `http://localhost:3000/auth/signin` - Admin signin: `http://localhost:3000/auth/signin?admin=true`

(back to top)

### Environment Variables
Required Variables | Variable | Description | Example | |----------|-------------|---------| | **Database** | | | | `DATABASE_URL` | PostgreSQL URL (legacy, using D1 via bindings) | `postgresql://...` | | `DIRECT_URL` | Direct database connection (optional) | `postgresql://...` | | **Authentication** | | | | `NEXTAUTH_URL` | Application URL | `https://united-tattoos.com` | | `NEXTAUTH_SECRET` | NextAuth secret key | Generate with `openssl rand -base64 32` | | **File Storage (Cloudflare R2)** | | | | `AWS_ACCESS_KEY_ID` | R2 access key ID | From Cloudflare dashboard | | `AWS_SECRET_ACCESS_KEY` | R2 secret access key | From Cloudflare dashboard | | `AWS_REGION` | Region (any valid AWS region) | `us-east-1` | | `AWS_BUCKET_NAME` | R2 bucket name | `united-tattoo` | | `AWS_ENDPOINT_URL` | R2 endpoint URL | `https://.r2.cloudflarestorage.com` | | **Nextcloud OAuth** | | | | `NEXTCLOUD_BASE_URL` | Nextcloud instance URL | `https://portal.united-tattoos.com` | | `NEXTCLOUD_OAUTH_CLIENT_ID` | OAuth app client ID | From Nextcloud admin | | `NEXTCLOUD_OAUTH_CLIENT_SECRET` | OAuth app client secret | From Nextcloud admin | | `NEXTCLOUD_ARTISTS_GROUP` | Group name for artists | `artists` | | `NEXTCLOUD_ADMINS_GROUP` | Group name for shop admins | `shop_admins` |
Optional Variables | Variable | Description | Default | |----------|-------------|---------| | **CalDAV (Calendar Sync)** | | | | `NEXTCLOUD_USERNAME` | Service account username | — | | `NEXTCLOUD_PASSWORD` | Service account password/app password | — | | `NEXTCLOUD_CALENDAR_BASE_PATH` | CalDAV base path | `/remote.php/dav/calendars` | | **OAuth Providers (Deprecated)** | | | | `GOOGLE_CLIENT_ID` | Google OAuth client ID | — | | `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | — | | `GITHUB_CLIENT_ID` | GitHub OAuth client ID | — | | `GITHUB_CLIENT_SECRET` | GitHub OAuth client secret | — | | **Migration** | | | | `MIGRATE_TOKEN` | Token for public migration endpoint | Generate random string | | **Analytics** | | | | `VERCEL_ANALYTICS_ID` | Vercel Analytics ID | — |
Pro Tip: Use .env.local for local development and configure production variables in Cloudflare dashboard under Settings → Environment Variables.

(back to top)

--- ## Development ### Common Commands
Command Description
Development
npm run dev Start Next.js dev server (port 3000)
npm run dev:wrangler Build and preview with OpenNext/Cloudflare
Testing
npm run test Run Vitest in watch mode
npm run test:ui Run Vitest with interactive UI
npm run test:run Run tests once (CI mode)
npm run test:coverage Run tests with coverage report
Build & Deployment
npm run pages:build Build with OpenNext for Cloudflare
npm run build Standard Next.js build (standalone)
npm run preview Preview OpenNext build locally
npm run deploy Deploy to Cloudflare Pages
Code Quality
npm run ci:lint Run ESLint
npm run ci:typecheck TypeScript type checking (noEmit)
npm run ci:test Run tests with coverage (CI)
npm run ci:build Build for production (CI)
npm run ci:budgets Check bundle size budgets
Formatting
npm run lint Run ESLint
npm run format Format code with Prettier
npm run format:check Check formatting without changing files
### Database Management
Migration Commands **Local Database:** ```bash # Apply schema to local D1 npm run db:migrate:local # View tables in local D1 npm run db:studio:local # Backup local database npm run db:backup:local ``` **Preview Environment (default):** ```bash # Apply schema to preview D1 npm run db:migrate # Apply all migrations from sql/migrations/ npm run db:migrate:latest:preview # View tables in preview D1 npm run db:studio # Backup preview database npm run db:backup ``` **Production Environment:** ```bash # Apply specific migration to production npm run db:migrate:up:prod # Apply all migrations to production npm run db:migrate:latest:prod ``` **Direct Wrangler Commands:** ```bash # Execute SQL query on local D1 wrangler d1 execute united-tattoo --local --command="SELECT * FROM artists" # Apply schema file wrangler d1 execute united-tattoo --file=./sql/schema.sql # Execute with preview (remote) wrangler d1 execute united-tattoo --command="SELECT * FROM users" ```
Creating New Migrations 1. Create migration file in `sql/migrations/` with format: ``` YYYYMMDD_NNNN_description.sql ``` Example: `20250130_0001_add_flash_items_table.sql` 2. Write your SQL migration: ```sql -- Add flash_items table CREATE TABLE IF NOT EXISTS flash_items ( id TEXT PRIMARY KEY, artist_id TEXT NOT NULL, title TEXT NOT NULL, price REAL NOT NULL, is_available INTEGER DEFAULT 1, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE ); ``` 3. Test locally: ```bash npm run db:migrate:local ``` 4. Apply to preview: ```bash npm run db:migrate:latest:preview ``` 5. Apply to production (when ready): ```bash npm run db:migrate:latest:prod ```

(back to top)

--- ## Deployment **Production URL:** [https://united-tattoos.com](https://united-tattoos.com) ### Deployment Process
Quick Deploy Command

npm run pages:build && wrangler deploy
**Step-by-Step:** 1. **Build with OpenNext** ```bash npm run pages:build ``` This creates a Cloudflare-compatible build in `.vercel/output/static` 2. **Deploy to Cloudflare Pages** ```bash wrangler pages deploy .vercel/output/static ``` 3. **Verify deployment** - Check Cloudflare dashboard: Workers & Pages → united-tattoo - Visit production URL: https://united-tattoos.com ### CI/CD Pipeline The project uses **Gitea workflows** for automated CI/CD: **Workflows:** - **`ci.yaml`** - Main CI pipeline - ESLint - TypeScript type checking - Vitest tests with coverage - Production build - Bundle size budgets - **`deploy.yaml`** - Automated deployments - Triggers on push to `main` branch - Builds and deploys to Cloudflare - **`security.yaml`** - Security audits - npm audit - Dependency vulnerability scanning - **`performance.yaml`** - Performance checks - Bundle size analysis - Preview smoke tests **Bundle Size Budgets:** - Total static assets: **3MB max** - Individual assets: **1.5MB max** Enforced by `scripts/budgets.mjs` in CI. ### Docker Support
Docker Deployment (Alternative) The project includes a Dockerfile for self-hosting: ```bash # Build image docker build -t united-tattoo:latest . # Run container docker run --rm -p 3000:3000 \ -e PORT=3000 \ -e NEXTAUTH_URL=http://localhost:3000 \ -e NEXTAUTH_SECRET=your-secret \ # ... other env vars united-tattoo:latest ``` **Note:** Docker deployment bypasses Cloudflare Workers and uses Next.js standalone mode. For production, Cloudflare deployment is recommended.

(back to top)

--- ## Documentation Comprehensive documentation is available in the [`docs/`](docs/) directory: ### Key Documentation | Document | Description | |----------|-------------| | [**NEXTCLOUD-OAUTH-SETUP.md**](docs/NEXTCLOUD-OAUTH-SETUP.md) | Complete guide to setting up Nextcloud OAuth and group-based authentication | | [**CALDAV-SETUP.md**](docs/CALDAV-SETUP.md) | Instructions for configuring CalDAV calendar synchronization | | [**CI-CD-PIPELINE.md**](docs/CI-CD-PIPELINE.md) | Detailed CI/CD pipeline documentation and troubleshooting | | [**BOOKING-WORKFLOW-FINAL-PLAN.md**](docs/BOOKING-WORKFLOW-FINAL-PLAN.md) | Complete booking system architecture and workflow | ### Additional Documentation
View All Documentation Files **Authentication & Integration:** - [`CALDAV-IMPLEMENTATION-SUMMARY.md`](docs/CALDAV-IMPLEMENTATION-SUMMARY.md) - [`NEXTCLOUD-OAUTH-SETUP.md`](docs/NEXTCLOUD-OAUTH-SETUP.md) **Booking & Calendar:** - [`BOOKING-WORKFLOW-FINAL-PLAN.md`](docs/BOOKING-WORKFLOW-FINAL-PLAN.md) - [`BOOKING-WORKFLOW-REVISED-PLAN.md`](docs/BOOKING-WORKFLOW-REVISED-PLAN.md) - [`BOOKING-WORKFLOW-RISKS.md`](docs/BOOKING-WORKFLOW-RISKS.md) - [`CALDAV-SETUP.md`](docs/CALDAV-SETUP.md) **CI/CD & DevOps:** - [`CI-CD-PIPELINE.md`](docs/CI-CD-PIPELINE.md) - [`CI-CD-QUICK-REFERENCE.md`](docs/CI-CD-QUICK-REFERENCE.md) **Performance & SEO:** - [`SEO-AND-PERFORMANCE-IMPROVEMENTS.md`](docs/SEO-AND-PERFORMANCE-IMPROVEMENTS.md) - [`SEO-TESTING-GUIDE.md`](docs/SEO-TESTING-GUIDE.md) - [`PERFORMANCE-SEO-SUMMARY.md`](docs/PERFORMANCE-SEO-SUMMARY.md) **Project Management:** - [`PROJECT-DOCUMENTATION.md`](docs/PROJECT-DOCUMENTATION.md) - [`technical-decisions-template.md`](docs/technical-decisions-template.md)
### AI Development Guide The project includes **[`CLAUDE.md`](CLAUDE.md)** with comprehensive instructions for AI assistants (like Claude Code) working with this codebase, including: - Complete architecture overview - Common commands reference - Database layer patterns - Authentication flows - CalDAV integration details - Development best practices

(back to top)

--- ## Roadmap ### Completed Features - [x] Artist portfolio system with galleries - [x] Nextcloud OAuth with auto-provisioning - [x] CalDAV bidirectional sync - [x] Flash tattoo marketplace - [x] Admin dashboard with analytics - [x] Artist self-service dashboard - [x] Appointment booking system - [x] R2 file storage integration - [x] Role-based access control - [x] CI/CD pipeline with Gitea - [x] Bundle size enforcement - [x] HEIC image conversion - [x] Artist slug-based URLs ### In Progress - [ ] Email notifications for appointments - [ ] SMS reminders for clients - [ ] Advanced calendar conflict resolution - [ ] Payment integration (Stripe/Square) - [ ] Gift card system enhancements - [ ] Enhanced analytics dashboard ### Future Enhancements - [ ] Client self-service portal - [ ] Online deposit payments - [ ] Artist earnings reports - [ ] Inventory management - [ ] Social media auto-posting - [ ] Mobile app (React Native) - [ ] Webhook integrations - [ ] Advanced reporting See the [open issues](https://git.biohazardvfx.com/nicholai/united-tattoo/issues) for a full list of proposed features and known issues.

(back to top)

--- ## Contributing Contributions are welcome! This project follows standard Git workflows and conventional commits. ### Development Workflow 1. **Fork the project** ```bash # Via Gitea UI or git clone git clone https://git.biohazardvfx.com/nicholai/united-tattoo.git ``` 2. **Create your feature branch** ```bash git checkout -b feat/amazing-feature ``` 3. **Make your changes** - Follow existing code style (enforced by ESLint/Prettier) - Add tests for new features - Update documentation as needed 4. **Run quality checks** ```bash npm run lint # Check linting npm run format # Format code npm run ci:typecheck # Check types npm run test:run # Run tests npm run ci:budgets # Check bundle sizes ``` 5. **Commit your changes** ```bash git add . git commit -m "feat: add amazing feature" ``` Use [Conventional Commits](https://www.conventionalcommits.org/) format: - `feat:` New feature - `fix:` Bug fix - `docs:` Documentation changes - `style:` Formatting, missing semicolons, etc. - `refactor:` Code refactoring - `test:` Adding tests - `chore:` Maintenance tasks 6. **Push to your branch** ```bash git push origin feat/amazing-feature ``` 7. **Open a Pull Request** - Via Gitea UI - Provide clear description of changes - Reference any related issues ### Code Style Guidelines - **TypeScript**: Prefer strict typing, avoid `any` - **React**: Use functional components with hooks - **File organization**: Keep components modular - **Comments**: Explain "why", not "what" - **Tests**: Test user-facing behavior, not implementation ### Top Contributors Contributors

(back to top)

--- ## License
License Status

This project currently does not have a LICENSE file in the repository. If you intend to use GNU GPLv3 (as referenced in the original README template), please add a LICENSE or COPYING file with the full license text.

Alternatively, consider: - MIT License - Permissive, allows commercial use - Apache 2.0 - Permissive with patent grant - GNU GPLv3 - Copyleft, requires source disclosure - Proprietary - All rights reserved Choose A License can help you decide.
**Current Status:** No license specified. Please add a LICENSE file to clarify usage terms.

(back to top)

--- ## Contact
**Nicholai Vogel** [![Website](https://img.shields.io/badge/Website-nicholai.work-667eea?style=for-the-badge&logo=google-chrome&logoColor=white)](https://nicholai.work) [![LinkedIn](https://img.shields.io/badge/LinkedIn-Nicholai_Vogel-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://linkedin.com/in/nicholai-vogel) [![Instagram](https://img.shields.io/badge/Instagram-@nicholai.exe-E4405F?style=for-the-badge&logo=instagram&logoColor=white)](https://instagram.com/nicholai.exe) **Project Repository** [https://git.biohazardvfx.com/nicholai/united-tattoo](https://git.biohazardvfx.com/nicholai/united-tattoo) **Live Website** [https://united-tattoos.com](https://united-tattoos.com)
---
### Star this repository if you find it helpful!

(back to top)

--- **Made with love for United Tattoo Studio, Fountain, CO**
[contributors-shield]: https://img.shields.io/badge/Contributors-1-667eea?style=for-the-badge [contributors-url]: https://git.biohazardvfx.com/nicholai/united-tattoo/graphs/contributors [forks-shield]: https://img.shields.io/badge/Forks-0-667eea?style=for-the-badge [forks-url]: https://git.biohazardvfx.com/nicholai/united-tattoo/network/members [stars-shield]: https://img.shields.io/badge/Stars-0-667eea?style=for-the-badge [stars-url]: https://git.biohazardvfx.com/nicholai/united-tattoo/stargazers [issues-shield]: https://img.shields.io/badge/Issues-0-667eea?style=for-the-badge [issues-url]: https://git.biohazardvfx.com/nicholai/united-tattoo/issues [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=0077B5 [linkedin-url]: https://linkedin.com/in/nicholai-vogel [nextjs-badge]: https://img.shields.io/badge/Next.js_14-000000?style=for-the-badge&logo=nextdotjs&logoColor=white [next-url]: https://nextjs.org/ [react-badge]: https://img.shields.io/badge/React_18-20232A?style=for-the-badge&logo=react&logoColor=61DAFB [react-url]: https://react.dev/ [typescript-badge]: https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white [typescript-url]: https://www.typescriptlang.org/ [cloudflare-badge]: https://img.shields.io/badge/Cloudflare_Workers-F38020?style=for-the-badge&logo=cloudflare&logoColor=white [cloudflare-url]: https://developers.cloudflare.com/workers/ [opennext-badge]: https://img.shields.io/badge/OpenNext-18181B?style=for-the-badge&logo=vercel&logoColor=white [opennext-url]: https://opennext.js.org/ [shadcn-badge]: https://img.shields.io/badge/ShadCN_UI-000000?style=for-the-badge&logo=shadcnui&logoColor=white [shadcn-url]: https://ui.shadcn.com [tailwind-badge]: https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white [tailwind-url]: https://tailwindcss.com [framer-badge]: https://img.shields.io/badge/Framer_Motion-0055FF?style=for-the-badge&logo=framer&logoColor=white [framer-url]: https://www.framer.com/motion/ [d1-badge]: https://img.shields.io/badge/D1_Database-F38020?style=for-the-badge&logo=cloudflare&logoColor=white [d1-url]: https://developers.cloudflare.com/d1/ [r2-badge]: https://img.shields.io/badge/R2_Storage-F38020?style=for-the-badge&logo=cloudflare&logoColor=white [r2-url]: https://developers.cloudflare.com/r2/ [nextauth-badge]: https://img.shields.io/badge/NextAuth.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white [nextauth-url]: https://next-auth.js.org/ [caldav-badge]: https://img.shields.io/badge/CalDAV-0082C9?style=for-the-badge&logo=nextcloud&logoColor=white [caldav-url]: https://en.wikipedia.org/wiki/CalDAV [vitest-badge]: https://img.shields.io/badge/Vitest-6E9F18?style=for-the-badge&logo=vitest&logoColor=white [vitest-url]: https://vitest.dev/ [eslint-badge]: https://img.shields.io/badge/ESLint-4B32C3?style=for-the-badge&logo=eslint&logoColor=white [eslint-url]: https://eslint.org/ [prettier-badge]: https://img.shields.io/badge/Prettier-F7B93E?style=for-the-badge&logo=prettier&logoColor=black [prettier-url]: https://prettier.io/